Deep EVM #9: تمهيد لغة Huff — الماكرو والعلامات وأكواد التشغيل الخام
Engineering Team
لماذا توجد Huff
Solidity تجريد رائع — حتى لا يكون كذلك. عندما تحتاج لعقد يتسع في 100 بايت من بايتكود التشغيل، أو يرسل الدوال في O(1) بجدول قفز محزوم، أو يوفر 200 غاز في مسار ساخن يُنفذ ملايين المرات يومياً، تحتاج شيئاً أقرب للمعدن. هذا الشيء هو Huff.
Huff هي لغة تجميع EVM منخفضة المستوى مع نظام ماكرو بسيط مُلصق فوقها. لا تحتوي على متغيرات أو أنواع أو مترجم يحسّن خلف ظهرك. ما تكتبه هو ما ينتهي على السلسلة — كود تشغيل بكود تشغيل.
تثبيت Huff
المترجم القياسي هو huffc، مكتوب بـ Rust:
curl -L get.huff.sh | bash
huffup
huffc --version
عالم مرحباً: عقد بسيط
لنكتب عقداً يُرجع الكلمة 32 بايت 0x01 لأي استدعاء:
#define macro MAIN() = takes(0) returns(0) {
0x01 // [0x01]
0x00 // [0x00, 0x01]
mstore // [] — memory[0x00..0x20] = 0x01
0x20 // [0x20]
0x00 // [0x00, 0x20]
return // توقف — إرجاع memory[0x00..0x20]
}
ترجمة:
huffc src/HelloWorld.huff -r
العلم -r يُخرج بايتكود التشغيل. سترى شيئاً مثل 600160005260206000f3 — 10 بايت. عقد Solidity يُرجع 1 يترجم إلى 200+ بايت تقريباً لأن solc يُصدر مرسل دوال كامل وتجزئة البيانات الوصفية وإعداد مؤشر الذاكرة الحرة ومرمز ABI.
الماكرو مقابل الدوال
Huff لديها نوعان لإعادة استخدام الكود: الماكرو والدوال.
الماكرو (#define macro)
يتم تضمين الماكرو في كل موقع استدعاء. لا حمل JUMP إضافي، لا غاز إضافي — المترجم ينسخ أكواد التشغيل حرفياً في المستدعي. هذا هو الخيار الافتراضي والمفضل للكود الحرج من حيث الغاز.
#define macro REQUIRE_NOT_ZERO() = takes(1) returns(0) {
// takes: [value]
continue // [continue_dest, value]
jumpi // [] — قفز إذا value != 0
0x00 0x00 revert
continue:
}
الدوال (#define fn)
الدوال تولد زوج JUMP/JUMPDEST فعلي. توفر حجم البايتكود على حساب ~22 غاز إضافي لكل استدعاء. استخدمها فقط عندما يكون حجم البايتكود أهم من الغاز.
العلامات ووجهات القفز
العلامات في Huff هي مواقع JUMPDEST مسماة. يحلها المترجم إلى إزاحات بايتكود محددة في وقت الترجمة.
#define macro LOOP_EXAMPLE() = takes(1) returns(1) {
// takes: [n]
0x00 // [acc, n]
loop:
dup2 // [n, acc, n]
iszero // [n==0?, acc, n]
done jumpi // [acc, n]
swap1 // [n, acc]
0x01 swap1 sub // [n-1, acc]
swap1 // [acc, n-1]
0x01 add // [acc+1, n-1]
loop jump
done:
swap1 pop // [acc]
}
takes() وreturns()
توصيفات takes(n) وreturns(m) على الماكرو والدوال هي توثيق وتلميحات للمترجم. تخبر القارئ — ومدقق المكدس في مترجم Huff — عن عدد عناصر المكدس التي يتوقع الكتلة استهلاكها وإنتاجها.
المقارنة: Huff مقابل بايتكود Solidity
لنأخذ دالة عرض بسيطة getValue() تُرجع فتحة تخزين:
Solidity:
function getValue() external view returns (uint256) {
return value;
}
solc يولد ~40 بايت للمرسل + ترميز ABI.
مكافئ Huff:
#define function getValue() view returns (uint256)
#define macro GET_VALUE() = takes(0) returns(0) {
[VALUE_SLOT] // [slot]
sload // [value]
0x00 mstore // [] — تخزين في الذاكرة
0x20 0x00 return
}
نسخة Huff هي 12 بايت من البايتكود للجسم. بدون حمل ترميز ABI أو مؤشر الذاكرة الحرة أو تجزئة البيانات الوصفية.
الثوابت وفتحات التخزين
ثوابت Huff هي قيم وقت الترجمة يتم تضمينها كتعليمات PUSH:
#define constant VALUE_SLOT = 0x00
#define constant OWNER_SLOT = 0x01
#define constant MAX_UINT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
الاستخدام: [VALUE_SLOT] يدفع 0x00. الثوابت تحسن القراءة بدون تكلفة غاز — هي بنيوية بحتة.
التضمينات وبنية المشروع
مشاريع Huff الحقيقية تقسم الكود عبر ملفات متعددة:
// src/Main.huff
#include "./utils/SafeMath.huff"
#include "./interfaces/IERC20.huff"
#include "./Dispatcher.huff"
#define macro MAIN() = takes(0) returns(0) {
DISPATCHER()
}
متى تستخدم Huff
Huff ليست لغة عامة الأغراض. استخدمها عندما:
- الغاز هو القيد الرئيسي — عقود MEV حيث 100 غاز تحدد الربحية
- حجم البايتكود مهم — عقود مُنشأة بواسطة عقود أخرى (مصانع CREATE2)
- تحتاج إرسال مخصص — جداول القفز، محددات محزومة بالبتات
- تتعلم EVM — لا شيء يعلم EVM أفضل من كتابة أكواد تشغيل خام
الملخص
Huff تمنحك خطاً مباشراً إلى بايتكود EVM مع تجريد كافٍ للبقاء عاقلاً. الماكرو تضمن الكود لإعادة استخدام بدون حمل. العلامات تتعامل مع حساب إزاحات القفز. توصيفات takes/returns تكتشف أخطاء المكدس مبكراً.