Deep EVM #11: جداول القفز في Huff — إرسال الدوال O(1) بدون حمل Solidity
Engineering Team
مشكلة مرسل Solidity
عندما تستدعي عقد Solidity، أول ما تنفذه EVM هو مرسل الدوال. Solidity يولد سلسلة if-else خطية تقارن أول 4 بايت من calldata (محدد الدالة) مع كل محدد معروف:
CALLDATALOAD 0x00
SHR 224
DUP1
PUSH4 0x70a08231 // balanceOf(address)
EQ
PUSH2 dest1
JUMPI
...
لعقد بـ N دالة، هذا O(N) — أسوأ حالة تفحص جميع المحددات قبل العثور على تطابق. كل مقارنة تكلف ~22 غاز. عقد بـ 20 دالة يهدر حتى 440 غاز على الإرسال فقط.
لروبوت MEV يُستدعى ملايين المرات، تلك 400+ وحدة غاز لكل استدعاء تتراكم إلى ETH حقيقي.
نهج جدول القفز: O(1)
جدول القفز يربط محدد الدالة مباشرة بإزاحة الكود باستخدام الحساب، وليس المقارنة. الفكرة مستعارة من بنية المعالج — GOTOs المحسوبة استُخدمت منذ الستينيات.
المفهوم:
- استخراج محدد الدالة من calldata (4 بايت)
- استخدام الحساب لحساب وجهة القفز من المحدد
- القفز مباشرة إلى تلك الوجهة
لا مقارنات، لا تفريع، وقت ثابت بغض النظر عن عدد الدوال.
النهج 1: ترميز محدد بسيط
إذا كان عقدك يحتوي عدداً صغيراً من الدوال (1-8)، يمكنك تعيين المحددات يدوياً بتعدين محددات مخصصة حيث البايت الأول أو الثاني يرمز عدداً صحيحاً صغيراً فريداً:
#define macro DISPATCHER() = takes(0) returns(0) {
0x00 calldataload // [calldata_word]
0xe0 shr // [selector]
0x18 shr // [first_byte]
0x02 mul // [offset_in_table]
__tablestart(JumpTable) // [table_start, offset_in_table]
add // [entry_address]
codecopy_dest:
0x00 codecopy
0x00 mload
0xf0 shr
jump
}
#define jumptable JumpTable {
swap_exact
add_liq
remove_liq
flash_loan
}
النهج 2: جدول كود محزوم
للكثافة القصوى، يمكنك حزم جدول القفز مباشرة في البايتكود باستخدام __tablestart و__tablesize. Huff يدعم جداول القفز كبنى أصلية.
مقارنة الغاز
| الدوال | Solidity (if-else) | Solidity (ثنائي) | جدول قفز Huff |
|---|---|---|---|
| 2 | 22-44 غاز | 22-44 غاز | 15 غاز |
| 4 | 22-88 غاز | 22-66 غاز | 15 غاز |
| 8 | 22-176 غاز | 22-88 غاز | 15 غاز |
| 16 | 22-352 غاز | 22-110 غاز | 15 غاز |
| 32 | 22-704 غاز | 22-132 غاز | 15 غاز |
تكلفة جدول القفز ثابتة: CALLDATALOAD (3) + SHR (3) + حساب (3-6) + JUMP (8) = ~15-18 غاز. لا تتغير أبداً بغض النظر عن عدد الدوال.
تعدين محددات مخصصة
لكي يعمل نهج جدول القفز، تحتاج محددات دوال ببايتات توجيه متوقعة:
import hashlib
import itertools
target_byte = 0x00
base_name = "swap"
for suffix in itertools.count():
name = f"{base_name}{suffix}(uint256,address)"
selector = hashlib.sha3_256(name.encode()).digest()[:4]
if selector[0] == target_byte:
print(f"Found: {name} -> 0x{selector.hex()}")
break
تأثير حجم البايتكود
حجم البايتكود يؤثر مباشرة على تكلفة النشر (200 غاز لكل بايت عبر CREATE):
| النهج | بايتكود التشغيل | غاز النشر |
|---|---|---|
| Solidity (8 دوال) | ~800 بايت | 160,000 غاز |
| جدول قفز Huff (8 دوال) | ~200 بايت | 40,000 غاز |
| Huff بسيط (دالتان) | ~61 بايت | 12,200 غاز |
القيود
- ABI غير قياسي — الأدوات الخارجية لا تستطيع فك تشفير calldata بدون تعريفات ABI مخصصة
- تعدين المحددات — يتطلب عملاً مسبقاً ويقيد تسمية الدوال
- تكلفة الصيانة — Huff أصعب في التدقيق والتعديل من Solidity
الملخص
جداول القفز تستبدل سلسلة إرسال O(N) في Solidity بقفزات محسوبة O(1). التوفير في الغاز يتراكم عبر ملايين الاستدعاءات — ميزة ذات معنى للعقود عالية التردد مثل روبوتات MEV وموجهات DEX.