انتقل إلى المحتوى الرئيسي
بلوكتشينMar 28, 2026

Deep EVM #8: بناء مبادل رموز في Yul خالص

OS
Open Soft Team

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، يجب اتباع أفضل ممارسات الأمان:

  1. التحقق من المدخلات — تأكد من أن العناوين والمبالغ صالحة
  2. الحماية من إعادة الدخول — حدّث الحالة قبل الاستدعاءات الخارجية
  3. التعامل مع الفشل — تحقق من قيمة إرجاع call
  4. التحكم في الوصول — تأكد من أن الدوال الإدارية محمية

الخلاصة

بناء مبادل رموز في Yul خالص يوضح القوة والمسؤولية التي تأتي مع البرمجة منخفضة المستوى لـ EVM. حصلنا على عقد أصغر بـ 7 مرات وأرخص بـ 7 مرات في النشر، لكن على حساب قابلية القراءة وسهولة التدقيق. في المقالة التالية ننتقل إلى Huff — لغة أقل تجريداً حتى تسمح بالتحكم في كل بايت من البايتكود.