Deep EVM #10: إدارة المكدس في Huff — takes() وreturns() وفن dup/swap
Engineering Team
النموذج العقلي لآلة المكدس
EVM هي آلة مكدسية. لا توجد سجلات ولا متغيرات مسماة — فقط مكدس آخر ما يدخل أول ما يخرج من كلمات 32 بايت بعمق 1024 فتحة. كل كود تشغيل إما يدفع أو يسحب أو يعيد ترتيب عناصر في هذا المكدس. إذا لم تستطع الاحتفاظ بحالة المكدس الحالية في ذهنك، ستنتج بايتكود خاطئ.
اتفاقية الترميز
نمثل حالة المكدس بأقواس حيث العنصر الأيسر هو أعلى المكدس:
// [أعلى, ثاني, ثالث, ..., أسفل]
0x01 // [1]
0x02 // [2, 1]
add // [3]
كل ماكرو Huff يجب أن يحتوي تعليق مكدس بعد كل كود تشغيل. هذا ليس اختيارياً — إنه الطريقة الوحيدة لتدقيق الصحة.
DUP: تكرار عناصر المكدس
EVM توفر DUP1 حتى DUP16. DUPn ينسخ العنصر n من الأعلى ويدفعه على المكدس. ينمو المكدس بمقدار 1.
// المكدس: [a, b, c, d]
dup1 // [a, a, b, c, d] — نسخ الأعلى
dup3 // [c, a, a, b, c, d] — نسخ الثالث من الأعلى
تكلفة الغاز: 3 غاز لأي DUPn. من أرخص العمليات في EVM.
متى تستخدم DUP
DUP هي أداتك للقراءة غير المدمرة. كثير من أكواد التشغيل تستهلك معاملاتها (ADD يسحب اثنين ويدفع واحد)، لذا إذا احتجت قيمة مرة أخرى لاحقاً، كررها بـ DUP قبل تمريرها لكود مستهلك.
#define macro SAFE_SUB() = takes(2) returns(1) {
// takes: [a, b] — حساب a - b، تراجع إذا b > a
dup2 dup2 // [a, b, a, b]
lt // [a < b?, a, b]
revert_underflow jumpi // [a, b]
sub // [a - b]
done jump
revert_underflow:
0x00 0x00 revert
done:
}
SWAP: إعادة ترتيب المكدس
EVM توفر SWAP1 حتى SWAP16. SWAPn يبادل العنصر العلوي مع العنصر (n+1). حجم المكدس يبقى نفسه.
// المكدس: [a, b, c, d]
swap1 // [b, a, c, d] — تبديل الأعلى مع الثاني
swap3 // [d, a, c, b] — تبديل الأعلى مع الرابع
قيد العمق 16
DUP وSWAP تصل فقط حتى عمق 16. إذا كانت قيمة في الموضع 17 أو أعمق، لا يمكنك الوصول إليها بكود تشغيل واحد. هذا قيد صلب في EVM.
استراتيجيات للمكدسات العميقة:
- أعد هيكلة المنطق لإبقاء القيم المطلوبة قرب الأعلى
- استخدم الذاكرة كمساحة مؤقتة. خزّن قيمة بـ MSTORE واسترجعها لاحقاً بـ MLOAD. تكلفة 3+3=6 غاز مقابل 3 غاز لـ DUP، لكن تكسر حاجز العمق.
- قسّم الماكرو إلى ماكرو أصغر يعمل كل منها على عناصر مكدس أقل.
الأنماط الشائعة
النمط 1: الاحتفاظ بقيمة خلال عملية مستهلكة
// نريد: حساب تجزئة x، لكن نحتفظ بـ x
// المكدس: [x]
dup1 // [x, x]
0x00 mstore // [x] — memory[0] = x
0x20 0x00 // [0, 32, x]
keccak256 // [hash, x]
النمط 2: تدوير ثلاثة عناصر
لديك [a, b, c] وتريد [c, a, b]:
swap2 // [c, b, a]
swap1 // [c, a, b]
النمط 3: تنظيف عناصر المكدس غير المرغوبة
// المكدس: [result, garbage1, garbage2]
swap1 pop // [result, garbage2]
swap1 pop // [result]
النمط 4: تكرار زوج
// المكدس: [a, b]
dup2 // [b, a, b]
dup2 // [a, b, a, b]
انضباط تصور المكدس
عند كتابة Huff، اتبع هذا الانضباط:
- علّق كل سطر بحالة المكدس بعد التنفيذ
- تحقق من takes/returns — عُد عناصر المكدس عند الدخول والخروج
- تتبع كل فرع — عند كل JUMPI، كلا المسارين يجب أن يتركا المكدس في حالة صالحة
- راقب انحراف المكدس — إذا لم يوازن جسم الحلقة الدفع والسحب تماماً، سينمو المكدس أو ينكمش في كل تكرار
تصحيح أخطاء المكدس
أشهر الأخطاء في Huff:
- نقص المكدس — السحب من مكدس فارغ. EVM يتراجع في وقت التشغيل
- عدم توازن المكدس عند JUMP — JUMPDEST يُصل إليه من مسارين مختلفين يتوقعان حالات مكدس مختلفة
- خطأ واحد في DUP/SWAP —
dup3مقابلdup4عندما أضفت push إضافي سابقاً
huffc لديه علم --stack-check يجري تحليل مكدس أساسي.
الملخص
إدارة المكدس هي المهارة الأساسية لتطوير Huff. DUP للقراءة غير المدمرة، SWAP لإعادة الترتيب، والذاكرة للقيم أبعد من عمق 16. علّق كل سطر بحالة المكدس. تحقق من كل فرع.