Deep EVM #8: بناء مبادل رموز في Yul خالص
Engineering Team
لماذا مبادل رموز في Yul؟
بناء مبادل رموز (Token Swap) في Yul خالص هو التمرين الأمثل لإتقان EVM. يتطلب كل مهارة تعلمناها: إدارة الذاكرة، الوصول إلى التخزين، فك ترميز calldata، وترميز ABI يدوي.
عقدنا سيقبل رمز A من المستخدم ويعيد رمز B بناءً على سعر صرف محدد.
بنية العقد
object "TokenSwap" {
code {
// كود المُنشئ
sstore(0, caller()) // تخزين المالك
datacopy(0, dataoffset("runtime"), datasize("runtime"))
return(0, datasize("runtime"))
}
object "runtime" {
code {
// منع إرسال ETH
if callvalue() { revert(0, 0) }
// استخراج المحدد
let selector := shr(224, calldataload(0))
switch selector
case 0x5f575529 /* swap(address,address,uint256) */ {
_swap()
}
case 0x8da5cb5b /* owner() */ {
mstore(0, sload(0))
return(0, 32)
}
default {
revert(0, 0)
}
function _swap() {
let tokenIn := calldataload(0x04)
let tokenOut := calldataload(0x24)
let amountIn := calldataload(0x44)
// التحقق من المبلغ
if iszero(amountIn) { revert(0, 0) }
// حساب المبلغ الخارج (سعر صرف ثابت 1:1 للبساطة)
let amountOut := amountIn
// نقل tokenIn من المستدعي إلى هذا العقد
_transferFrom(tokenIn, caller(), address(), amountIn)
// نقل tokenOut من هذا العقد إلى المستدعي
_transfer(tokenOut, caller(), amountOut)
}
function _transferFrom(token, from, to, amount) {
// ترميز transferFrom(address,address,uint256)
mstore(0, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(0x04, from)
mstore(0x24, to)
mstore(0x44, amount)
let success := call(gas(), token, 0, 0, 0x64, 0, 0x20)
if iszero(success) { revert(0, 0) }
// التحقق من القيمة المرجعة
if returndatasize() {
if iszero(mload(0)) { revert(0, 0) }
}
}
function _transfer(token, to, amount) {
// ترميز transfer(address,uint256)
mstore(0, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(0x04, to)
mstore(0x24, amount)
let success := call(gas(), token, 0, 0, 0x44, 0, 0x20)
if iszero(success) { revert(0, 0) }
if returndatasize() {
if iszero(mload(0)) { revert(0, 0) }
}
}
}
}
}
فك ترميز Calldata يدوياً
بدلاً من abi.decode الذي يولده Solidity، نقرأ المعاملات مباشرة:
// calldata layout for swap(address,address,uint256):
// [0x00..0x04]: selector (4 بايت)
// [0x04..0x24]: tokenIn (address، محشو إلى 32 بايت)
// [0x24..0x44]: tokenOut
// [0x44..0x64]: amountIn
let tokenIn := calldataload(0x04)
let tokenOut := calldataload(0x24)
let amountIn := calldataload(0x44)
لا حاجة للتحقق من الحجم أو فك الترميز الديناميكي — المعاملات ثابتة الحجم.
التعامل مع رموز ERC-20 غير المتوافقة
بعض الرموز (مثل USDT) لا تُرجع قيمة بوليانية من transfer. يجب التعامل مع هذه الحالة:
let success := call(gas(), token, 0, ptr, 0x44, 0, 0x20)
if iszero(success) { revert(0, 0) }
// التعامل مع الرموز غير المتوافقة
switch returndatasize()
case 0 {
// لا قيمة مرجعة — نفترض النجاح (مثل USDT)
}
case 32 {
// تحقق من القيمة المرجعة
if iszero(mload(0)) { revert(0, 0) }
}
default {
// حجم غير متوقع
revert(0, 0)
}
مقارنة حجم البايتكود
| التنفيذ | حجم التشغيل | غاز النشر |
|---|---|---|
| Solidity + SafeERC20 | ~2,400 بايت | ~480,000 غاز |
| Yul خالص | ~350 بايت | ~70,000 غاز |
توفير 85% في حجم البايتكود يترجم مباشرة إلى توفير 85% في تكلفة النشر.
اعتبارات الأمان
حتى في Yul، يجب اتباع أفضل ممارسات الأمان:
- التحقق من المدخلات — تأكد من أن العناوين والمبالغ صالحة
- الحماية من إعادة الدخول — حدّث الحالة قبل الاستدعاءات الخارجية
- التعامل مع الفشل — تحقق من قيمة إرجاع call
- التحكم في الوصول — تأكد من أن الدوال الإدارية محمية
الخلاصة
بناء مبادل رموز في Yul خالص يوضح القوة والمسؤولية التي تأتي مع البرمجة منخفضة المستوى لـ EVM. حصلنا على عقد أصغر بـ 7 مرات وأرخص بـ 7 مرات في النشر، لكن على حساب قابلية القراءة وسهولة التدقيق. في المقالة التالية ننتقل إلى Huff — لغة أقل تجريداً حتى تسمح بالتحكم في كل بايت من البايتكود.