فهم مواصفات EVM في الورقة الصفراء
الورقة الصفراء (يفتح في علامة تبويب جديدة) هي المواصفات الرسمية لإيثيريوم. باستثناء ما تم تعديله بواسطة عملية مقترحات تحسين إيثيريوم (EIP)، فإنها تحتوي على الوصف الدقيق لكيفية عمل كل شيء. تمت كتابتها كورقة بحثية رياضية، والتي تتضمن مصطلحات قد لا يجدها المبرمجون مألوفة. في هذه المقالة، ستتعلم كيفية قراءتها، وبالتالي قراءة الأوراق الرياضية الأخرى ذات الصلة.
أي ورقة صفراء؟
مثل كل شيء تقريبًا في إيثيريوم، تتطور الورقة الصفراء بمرور الوقت. لكي أتمكن من الإشارة إلى إصدار معين، قمت برفع الإصدار الحالي وقت الكتابة (يفتح في علامة تبويب جديدة). ستشير أرقام الأقسام والصفحات والمعادلات التي أستخدمها إلى ذلك الإصدار. من الجيد إبقاؤها مفتوحة في نافذة مختلفة أثناء قراءة هذا المستند.
لماذا EVM؟
كُتبت الورقة الصفراء الأصلية في بداية تطوير إيثيريوم. وهي تصف آلية الإجماع الأصلية القائمة على إثبات العمل (PoW) التي استُخدمت في الأصل لتأمين الشبكة. ومع ذلك، أوقفت إيثيريوم إثبات العمل (PoW) وبدأت في استخدام الإجماع القائم على إثبات الحصة (PoS) في سبتمبر 2022. سيركز هذا البرنامج التعليمي على أجزاء الورقة الصفراء التي تُعرّف آلة إيثيريوم الافتراضية (EVM). لم تتغير EVM بالانتقال إلى إثبات الحصة (PoS) (باستثناء القيمة المرجعة لرمز التشغيل DIFFICULTY).
9 نموذج التنفيذ
يتضمن هذا القسم (ص 14-16) معظم تعريف EVM.
يتضمن مصطلح حالة النظام (system state) كل ما تحتاج لمعرفته حول النظام لتشغيله. في الحاسوب النموذجي، يعني هذا الذاكرة، ومحتوى السجلات، وما إلى ذلك.
آلة تورنغ (Turing machine) (يفتح في علامة تبويب جديدة) هي نموذج حسابي. في الأساس، هي نسخة مبسطة من الحاسوب، والتي ثبت أن لديها نفس القدرة على تشغيل العمليات الحسابية التي يمكن للحاسوب العادي القيام بها (كل ما يمكن للحاسوب حسابه يمكن لآلة تورنغ حسابه والعكس صحيح). يسهل هذا النموذج إثبات النظريات المختلفة حول ما هو قابل للحساب وما هو غير قابل للحساب.
يعني مصطلح مكتملة تورنغ (Turing-complete) (يفتح في علامة تبويب جديدة) حاسوبًا يمكنه تشغيل نفس الحسابات مثل آلة تورنغ. يمكن لآلات تورنغ الدخول في حلقات لا نهائية، ولا يمكن لـ EVM القيام بذلك لأنها ستنفد من الغاز، لذا فهي شبه مكتملة تورنغ فقط.
9.1 الأساسيات
يقدم هذا القسم أساسيات EVM وكيفية مقارنتها بالنماذج الحسابية الأخرى.
آلة المكدس (stack machine) (يفتح في علامة تبويب جديدة) هي حاسوب يخزن البيانات الوسيطة ليس في السجلات، ولكن في مكدس (stack) (يفتح في علامة تبويب جديدة). هذه هي البنية المفضلة للآلات الافتراضية لأنه من السهل تنفيذها مما يعني أن الأخطاء والثغرات الأمنية أقل احتمالًا بكثير. تنقسم الذاكرة في المكدس إلى كلمات بحجم 256-bit. تم اختيار هذا لأنه مناسب لعمليات التشفير الأساسية في إيثيريوم مثل عملية التجزئة كيكاك-256 وحسابات منحنى إهليلجي. الحد الأقصى لحجم المكدس هو 1024 عنصرًا (1024 x 256 bits). عند تنفيذ رموز التشغيل، فإنها عادة ما تحصل على معاملاتها من المكدس. هناك رموز تشغيل مخصصة لإعادة تنظيم العناصر في المكدس مثل POP (يزيل العنصر من أعلى المكدس)، و DUP_N (يكرر العنصر رقم N في المكدس)، وما إلى ذلك.
تحتوي EVM أيضًا على مساحة متطايرة تسمى الذاكرة (memory) والتي تُستخدم لتخزين البيانات أثناء التنفيذ. يتم تنظيم هذه الذاكرة في كلمات بحجم 32-byte. تتم تهيئة جميع مواقع الذاكرة إلى الصفر. إذا قمت بتنفيذ كود Yul (يفتح في علامة تبويب جديدة) هذا لإضافة كلمة إلى الذاكرة، فسيملأ 32 bytes من الذاكرة عن طريق حشو المساحة الفارغة في الكلمة بالأصفار، أي أنه ينشئ كلمة واحدة - مع أصفار في المواقع 0-29، و 0x60 إلى 30، و 0xA7 إلى 31.
mstore(0, 0x60A7)
mstore هو أحد رموز التشغيل الثلاثة التي توفرها EVM للتفاعل مع الذاكرة - فهو يحمل كلمة في الذاكرة. الاثنان الآخران هما mstore8 الذي يحمل بايت واحد في الذاكرة، و mload الذي ينقل كلمة من الذاكرة إلى المكدس.
تحتوي EVM أيضًا على نموذج تخزين (storage) منفصل غير متطاير يتم الحفاظ عليه كجزء من حالة النظام - يتم تنظيم هذه الذاكرة في مصفوفات كلمات (على عكس مصفوفات البايت القابلة للعنونة بالكلمات في المكدس). هذا التخزين هو المكان الذي تحتفظ فيه العقود بالبيانات الدائمة - لا يمكن للعقد التفاعل إلا مع التخزين الخاص به. يتم تنظيم التخزين في تعيينات مفتاح-قيمة.
على الرغم من عدم ذكره في هذا القسم من الورقة الصفراء، فمن المفيد أيضًا معرفة أن هناك نوعًا رابعًا من الذاكرة. بيانات الاستدعاء (Calldata) هي ذاكرة للقراءة فقط قابلة للعنونة بالبايت تُستخدم لتخزين القيمة الممررة مع معامل data لمعاملة. تحتوي EVM على رموز تشغيل محددة لإدارة calldata. calldatasize يُرجع حجم البيانات. calldataload يُحمل البيانات في المكدس. calldatacopy ينسخ البيانات إلى الذاكرة.
تقوم بنية فون نيومان (Von Neumann architecture) (يفتح في علامة تبويب جديدة) القياسية بتخزين الكود والبيانات في نفس الذاكرة. لا تتبع EVM هذا المعيار لأسباب أمنية - فمشاركة الذاكرة المتطايرة تجعل من الممكن تغيير كود البرنامج. بدلاً من ذلك، يتم حفظ الكود في التخزين.
هناك حالتان فقط يتم فيهما تنفيذ الكود من الذاكرة:
- عندما يقوم عقد بإنشاء عقد آخر (باستخدام
CREATE(يفتح في علامة تبويب جديدة) أوCREATE2(يفتح في علامة تبويب جديدة))، فإن كود مُنشئ العقد يأتي من الذاكرة. - أثناء إنشاء أي عقد، يتم تشغيل كود المُنشئ ثم يعود بكود العقد الفعلي، من الذاكرة أيضًا.
يعني مصطلح التنفيذ الاستثنائي استثناءً يتسبب في توقف تنفيذ العقد الحالي.
9.2 نظرة عامة على الرسوم
يشرح هذا القسم كيفية حساب رسوم الغاز. هناك ثلاث تكاليف:
تكلفة رمز التشغيل
التكلفة المتأصلة لرمز التشغيل المحدد. للحصول على هذه القيمة، ابحث عن مجموعة تكلفة رمز التشغيل في الملحق H (ص 29، تحت المعادلة (329))، وابحث عن مجموعة التكلفة في المعادلة (326). يمنحك هذا دالة تكلفة، والتي تستخدم في معظم الحالات معلمات من الملحق G (ص 28).
على سبيل المثال، رمز التشغيل CALLDATACOPY (يفتح في علامة تبويب جديدة) هو عضو في المجموعة Wcopy. تكلفة رمز التشغيل لتلك المجموعة هي Gverylow+Gcopy×⌈μs[2]÷32⌉. بالنظر إلى الملحق G، نرى أن كلا الثابتين هما 3، مما يعطينا 3+3×⌈μs[2]÷32⌉.
لا يزال يتعين علينا فك تشفير التعبير ⌈μs[2]÷32⌉. الجزء الخارجي، ⌈ <value> ⌉ هو دالة السقف (ceiling function)، وهي دالة تُرجع عند إعطائها قيمة أصغر عدد صحيح لا يزال غير أصغر من القيمة. على سبيل المثال، ⌈2.5⌉ = ⌈3⌉ = 3. الجزء الداخلي هو μs[2]÷32. بالنظر إلى القسم 3 (الاصطلاحات) في ص 3، μ هي حالة الآلة. يتم تعريف حالة الآلة في القسم 9.4.1 في ص 15. وفقًا لذلك القسم، فإن إحدى معلمات حالة الآلة هي s للمكدس. بجمع كل ذلك معًا، يبدو أن μs[2] هو الموقع رقم 2 في المكدس. بالنظر إلى رمز التشغيل (يفتح في علامة تبويب جديدة)، فإن الموقع رقم 2 في المكدس هو حجم البيانات بالبايت. بالنظر إلى رموز التشغيل الأخرى في المجموعة Wcopy، وهي CODECOPY (يفتح في علامة تبويب جديدة) و RETURNDATACOPY (يفتح في علامة تبويب جديدة)، فإن لديها أيضًا حجم بيانات في نفس الموقع. لذا فإن ⌈μs[2]÷32⌉ هو عدد الكلمات بحجم 32-byte المطلوبة لتخزين البيانات التي يتم نسخها. بجمع كل شيء معًا، فإن التكلفة المتأصلة لـ CALLDATACOPY (يفتح في علامة تبويب جديدة) هي 3 غاز زائد 3 لكل كلمة من البيانات التي يتم نسخها.
تكلفة التشغيل
تكلفة تشغيل الكود الذي نستدعيه.
- في حالة
CREATE(يفتح في علامة تبويب جديدة) وCREATE2(يفتح في علامة تبويب جديدة)، مُنشئ العقد الجديد. - في حالة
CALL(يفتح في علامة تبويب جديدة)، أوCALLCODE(يفتح في علامة تبويب جديدة)، أوSTATICCALL(يفتح في علامة تبويب جديدة)، أوDELEGATECALL(يفتح في علامة تبويب جديدة)، العقد الذي نستدعيه.
تكلفة توسيع الذاكرة
تكلفة توسيع الذاكرة (إذا لزم الأمر).
في المعادلة 326، تُكتب هذه القيمة كـ Cmem(μi')-Cmem(μi). بالنظر إلى القسم 9.4.1 مرة أخرى، نرى أن μi هو عدد الكلمات في الذاكرة. لذا فإن μi هو عدد الكلمات في الذاكرة قبل رمز التشغيل و μi' هو عدد الكلمات في الذاكرة بعد رمز التشغيل.
يتم تعريف الدالة Cmem في المعادلة 328: Cmem(a) = Gmemory × a + ⌊a2 ÷ 512⌋. ⌊x⌋ هي دالة الأرضية (floor function)، وهي دالة تُرجع عند إعطائها قيمة أكبر عدد صحيح لا يزال غير أكبر من القيمة. على سبيل المثال، ⌊2.5⌋ = ⌊2⌋ = 2. عندما يكون a < √512، فإن a2 < 512، وتكون نتيجة دالة الأرضية صفرًا. لذا بالنسبة لأول 22 كلمة (704 bytes)، ترتفع التكلفة خطيًا مع عدد كلمات الذاكرة المطلوبة. بعد تلك النقطة يكون ⌊a2 ÷ 512⌋ موجبًا. عندما تكون الذاكرة المطلوبة عالية بما يكفي، تكون تكلفة الغاز متناسبة مع مربع مقدار الذاكرة.
ملاحظة أن هذه العوامل تؤثر فقط على تكلفة الغاز المتأصلة - فهي لا تأخذ في الاعتبار سوق الرسوم أو الإكراميات للمدققين التي تحدد المبلغ الذي يُطلب من المستخدم النهائي دفعه - هذه مجرد التكلفة الأولية لتشغيل عملية معينة على EVM.
9.3 بيئة التنفيذ
بيئة التنفيذ هي صف (tuple)، I، يتضمن معلومات ليست جزءًا من حالة سلسلة الكتل أو EVM.
| المعلمة | رمز التشغيل للوصول إلى البيانات | كود Solidity للوصول إلى البيانات |
|---|---|---|
| Ia | ADDRESS (يفتح في علامة تبويب جديدة) | address(this) |
| Io | ORIGIN (يفتح في علامة تبويب جديدة) | tx.origin |
| Ip | GASPRICE (يفتح في علامة تبويب جديدة) | tx.gasprice |
| Id | CALLDATALOAD (يفتح في علامة تبويب جديدة)، إلخ. | msg.data |
| Is | CALLER (يفتح في علامة تبويب جديدة) | msg.sender |
| Iv | CALLVALUE (يفتح في علامة تبويب جديدة) | msg.value |
| Ib | CODECOPY (يفتح في علامة تبويب جديدة) | address(this).code |
| IH | حقول رأس الكتلة، مثل NUMBER (يفتح في علامة تبويب جديدة) و DIFFICULTY (يفتح في علامة تبويب جديدة) | block.number، block.difficulty، إلخ. |
| Ie | عمق مكدس الاستدعاء للاستدعاءات بين العقود (بما في ذلك إنشاء العقد) | |
| Iw | هل يُسمح لـ EVM بتغيير الحالة، أم أنها تعمل بشكل ثابت |
هناك بعض المعلمات الأخرى الضرورية لفهم بقية القسم 9:
| المعلمة | محددة في القسم | المعنى |
|---|---|---|
| σ | 2 (ص 2، المعادلة 1) | حالة سلسلة الكتل |
| g | 9.3 (ص 14) | الغاز المتبقي |
| A | 6.1 (ص 9) | الحالة الفرعية المتراكمة (التغييرات المجدولة عند انتهاء المعاملة) |
| o | 9.3 (ص 14) | المخرجات - النتيجة المرجعة في حالة معاملة داخلية (عندما يستدعي عقد عقدًا آخر) واستدعاءات دوال العرض (عندما تطلب معلومات فقط، لذلك لا داعي لانتظار معاملة) |
9.4 نظرة عامة على التنفيذ
الآن بعد أن أصبح لدينا جميع المقدمات، يمكننا أخيرًا البدء في العمل على كيفية عمل EVM.
تعطينا المعادلات 146-151 الشروط الأولية لتشغيل EVM:
| الرمز | القيمة الأولية | المعنى |
|---|---|---|
| μg | g | الغاز المتبقي |
| μpc | 0 | عداد البرنامج، عنوان التعليمة التالية المراد تنفيذها |
| μm | (0, 0, ...) | الذاكرة، مهيأة كلها بالأصفار |
| μi | 0 | أعلى موقع ذاكرة مستخدم |
| μs | () | المكدس، فارغ في البداية |
| μo | ∅ | المخرجات، مجموعة فارغة حتى وما لم نتوقف إما ببيانات مرجعة (RETURN (يفتح في علامة تبويب جديدة) أو REVERT (يفتح في علامة تبويب جديدة)) أو بدونها (STOP (يفتح في علامة تبويب جديدة) أو SELFDESTRUCT (يفتح في علامة تبويب جديدة)). |
تخبرنا المعادلة 152 أن هناك أربعة شروط محتملة في كل نقطة زمنية أثناء التنفيذ، وما يجب فعله معها:
Z(σ,μ,A,I). يمثل Z دالة تختبر ما إذا كانت العملية تنشئ انتقال حالة غير صالح (انظر التوقف الاستثنائي). إذا تم تقييمها إلى صحيح (True)، فإن الحالة الجديدة مطابقة للحالة القديمة (باستثناء احتراق الغاز) لأن التغييرات لم يتم تنفيذها.- إذا كان رمز التشغيل الذي يتم تنفيذه هو
REVERT(يفتح في علامة تبويب جديدة)، فإن الحالة الجديدة هي نفس الحالة القديمة، ويُفقد بعض الغاز. - إذا انتهى تسلسل العمليات، كما هو موضح بواسطة
RETURN(يفتح في علامة تبويب جديدة)، يتم تحديث الحالة إلى الحالة الجديدة. - إذا لم نكن في أحد شروط النهاية 1-3، فاستمر في التشغيل.
9.4.1 حالة الآلة
يشرح هذا القسم حالة الآلة بمزيد من التفصيل. يحدد أن w هو رمز التشغيل الحالي. إذا كان μpc أقل من ||Ib||، وهو طول الكود، فإن ذلك البايت (Ib[μpc]) هو رمز التشغيل. بخلاف ذلك، يتم تعريف رمز التشغيل على أنه STOP (يفتح في علامة تبويب جديدة).
نظرًا لأن هذه آلة مكدس (يفتح في علامة تبويب جديدة)، نحتاج إلى تتبع عدد العناصر التي تم إخراجها (δ) وإدخالها (α) بواسطة كل رمز تشغيل.
9.4.2 التوقف الاستثنائي
يحدد هذا القسم الدالة Z، التي تحدد متى يكون لدينا إنهاء غير طبيعي. هذه دالة منطقية (Boolean) (يفتح في علامة تبويب جديدة)، لذا فهي تستخدم ∨ لـ "أو" المنطقية (يفتح في علامة تبويب جديدة) و ∧ لـ "و" المنطقية (يفتح في علامة تبويب جديدة).
لدينا توقف استثنائي إذا كان أي من هذه الشروط صحيحًا:
-
μg < C(σ,μ,A,I) كما رأينا في القسم 9.2، C هي الدالة التي تحدد تكلفة الغاز. لا يوجد غاز كافٍ متبقٍ لتغطية رمز التشغيل التالي.
-
δw=∅ إذا كان عدد العناصر المخرجة لرمز تشغيل غير محدد، فإن رمز التشغيل نفسه غير محدد.
-
|| μs || < δw نقص سعة المكدس (Stack underflow)، لا توجد عناصر كافية في المكدس لرمز التشغيل الحالي.
-
w = JUMP ∧ μs[0]∉D(Ib) رمز التشغيل هو
JUMP(يفتح في علامة تبويب جديدة) والعنوان ليسJUMPDEST(يفتح في علامة تبويب جديدة). تكون القفزات صالحة فقط عندما تكون الوجهةJUMPDEST(يفتح في علامة تبويب جديدة). -
w = JUMPI ∧ μs[1]≠0 ∧ μs[0] ∉ D(Ib) رمز التشغيل هو
JUMPI(يفتح في علامة تبويب جديدة)، والشرط صحيح (غير صفري) لذا يجب أن تحدث القفزة، والعنوان ليسJUMPDEST(يفتح في علامة تبويب جديدة). تكون القفزات صالحة فقط عندما تكون الوجهةJUMPDEST(يفتح في علامة تبويب جديدة). -
w = RETURNDATACOPY ∧ μs[1]+μs[2]>|| μo || رمز التشغيل هو
RETURNDATACOPY(يفتح في علامة تبويب جديدة). في رمز التشغيل هذا، عنصر المكدس μs[1] هو الإزاحة للقراءة منها في المخزن المؤقت للبيانات المرجعة، وعنصر المكدس μs[2] هو طول البيانات. يحدث هذا الشرط عندما تحاول القراءة بعد نهاية المخزن المؤقت للبيانات المرجعة. لاحظ أنه لا يوجد شرط مماثل لبيانات الاستدعاء أو للكود نفسه. عندما تحاول القراءة بعد نهاية تلك المخازن المؤقتة، فإنك تحصل على أصفار فقط. -
|| μs || - δw + αw > 1024
تجاوز سعة المكدس. إذا كان تشغيل رمز التشغيل سيؤدي إلى مكدس يحتوي على أكثر من 1024 عنصرًا، فقم بالإحباط.
-
¬Iw ∧ W(w,μ) هل نعمل بشكل ثابت (¬ هو النفي (يفتح في علامة تبويب جديدة) و Iw صحيح عندما يُسمح لنا بتغيير حالة سلسلة الكتل)؟ إذا كان الأمر كذلك، وكنا نحاول إجراء عملية تغيير حالة، فلا يمكن أن يحدث ذلك.
يتم تعريف الدالة W(w,μ) لاحقًا في المعادلة 159. تكون W(w,μ) صحيحة إذا كان أحد هذه الشروط صحيحًا:
-
w ∈ {CREATE, CREATE2, SSTORE, SELFDESTRUCT} تغير رموز التشغيل هذه الحالة، إما عن طريق إنشاء عقد جديد، أو تخزين قيمة، أو تدمير ذاتي للعقد الحالي.
-
LOG0≤w ∧ w≤LOG4 إذا تم استدعاؤنا بشكل ثابت، فلا يمكننا إصدار إدخالات سجل. تقع جميع رموز تشغيل السجل في النطاق بين
LOG0(A0) (يفتح في علامة تبويب جديدة) وLOG4(A4) (يفتح في علامة تبويب جديدة). يحدد الرقم بعد رمز تشغيل السجل عدد المواضيع التي يحتوي عليها إدخال السجل. -
w=CALL ∧ μs[2]≠0 يمكنك استدعاء عقد آخر عندما تكون ثابتًا، ولكن إذا قمت بذلك فلا يمكنك تحويل ETH إليه.
-
-
w = SSTORE ∧ μg ≤ Gcallstipend لا يمكنك تشغيل
SSTORE(يفتح في علامة تبويب جديدة) ما لم يكن لديك أكثر من Gcallstipend (مُعرّف كـ 2300 في الملحق G) غاز.
9.4.3 صلاحية وجهة القفز
هنا نحدد رسميًا ما هي رموز التشغيل JUMPDEST (يفتح في علامة تبويب جديدة). لا يمكننا فقط البحث عن قيمة البايت 0x5B، لأنها قد تكون داخل PUSH (وبالتالي فهي بيانات وليست رمز تشغيل).
في المعادلة (162) نحدد دالة، N(i,w). المعلمة الأولى، i، هي موقع رمز التشغيل. والثانية، w، هي رمز التشغيل نفسه. إذا كان w∈[PUSH1, PUSH32] فهذا يعني أن رمز التشغيل هو PUSH (تحدد الأقواس المربعة نطاقًا يتضمن نقاط النهاية). في هذه الحالة، يكون رمز التشغيل التالي في i+2+(w−PUSH1). بالنسبة لـ PUSH1 (يفتح في علامة تبويب جديدة) نحتاج إلى التقدم بمقدار بايتين (PUSH نفسه وقيمة البايت الواحد)، وبالنسبة لـ PUSH2 (يفتح في علامة تبويب جديدة) نحتاج إلى التقدم بمقدار ثلاثة بايتات لأنها قيمة من بايتين، وهكذا. جميع رموز تشغيل EVM الأخرى يبلغ طولها بايتًا واحدًا فقط، لذا في جميع الحالات الأخرى N(i,w)=i+1.
تُستخدم هذه الدالة في المعادلة (161) لتعريف DJ(c,i)، وهي مجموعة (set) (يفتح في علامة تبويب جديدة) جميع وجهات القفز الصالحة في الكود c، بدءًا من موقع رمز التشغيل i. يتم تعريف هذه الدالة بشكل متكرر. إذا كان i≥||c||، فهذا يعني أننا في نهاية الكود أو بعدها. لن نجد أي وجهات قفز أخرى، لذا أرجع المجموعة الفارغة فقط.
في جميع الحالات الأخرى، ننظر إلى بقية الكود بالانتقال إلى رمز التشغيل التالي والحصول على المجموعة بدءًا منه. c[i] هو رمز التشغيل الحالي، لذا فإن N(i,c[i]) هو موقع رمز التشغيل التالي. وبالتالي فإن DJ(c,N(i,c[i])) هي مجموعة وجهات القفز الصالحة التي تبدأ عند رمز التشغيل التالي. إذا لم يكن رمز التشغيل الحالي JUMPDEST، فأرجع تلك المجموعة فقط. وإذا كان JUMPDEST، فقم بتضمينه في مجموعة النتائج وأرجع ذلك.
9.4.4 التوقف الطبيعي
يمكن لدالة التوقف H أن تُرجع ثلاثة أنواع من القيم.
- إذا لم نكن في رمز تشغيل توقف، فأرجع ∅، المجموعة الفارغة. بالاصطلاح، يتم تفسير هذه القيمة على أنها خطأ منطقي (Boolean false).
- إذا كان لدينا رمز تشغيل توقف لا ينتج مخرجات (إما
STOP(يفتح في علامة تبويب جديدة) أوSELFDESTRUCT(يفتح في علامة تبويب جديدة))، فأرجع تسلسلًا بحجم صفر بايت كقيمة مرجعة. لاحظ أن هذا يختلف تمامًا عن المجموعة الفارغة. تعني هذه القيمة أن EVM قد توقفت بالفعل، فقط لا توجد بيانات مرجعة لقراءتها. - إذا كان لدينا رمز تشغيل توقف ينتج مخرجات (إما
RETURN(يفتح في علامة تبويب جديدة) أوREVERT(يفتح في علامة تبويب جديدة))، فأرجع تسلسل البايتات المحدد بواسطة رمز التشغيل هذا. يتم أخذ هذا التسلسل من الذاكرة، والقيمة الموجودة في أعلى المكدس (μs[0]) هي البايت الأول، والقيمة التي تليها (μs[1]) هي الطول.
H.2 مجموعة التعليمات
قبل أن ننتقل إلى القسم الفرعي الأخير من EVM، 9.5، دعونا نلقي نظرة على التعليمات نفسها. تم تعريفها في الملحق H.2 الذي يبدأ في ص 30. يُتوقع أن يظل أي شيء لم يتم تحديده على أنه يتغير مع رمز التشغيل المحدد هذا كما هو. يتم تحديد المتغيرات التي تتغير بـ <something>′.
على سبيل المثال، دعونا نلقي نظرة على رمز التشغيل ADD (يفتح في علامة تبويب جديدة).
| القيمة | الرمز التذكيري | δ | α | الوصف |
|---|---|---|---|---|
| 0x01 | ADD | 2 | 1 | عملية الجمع. |
| μ′s[0] ≡ μs[0] + μs[1] |
δ هو عدد القيم التي نخرجها من المكدس. في هذه الحالة اثنتان، لأننا نجمع أعلى قيمتين.
α هو عدد القيم التي ندخلها مرة أخرى. في هذه الحالة واحدة، وهي المجموع.
لذا فإن أعلى المكدس الجديد (μ′s[0]) هو مجموع أعلى المكدس القديم (μs[0]) والقيمة القديمة أسفله (μs[1]).
بدلاً من استعراض جميع رموز التشغيل بقائمة مملة، تشرح هذه المقالة فقط رموز التشغيل التي تقدم شيئًا جديدًا.
| القيمة | الرمز التذكيري | δ | α | الوصف |
|---|---|---|---|---|
| 0x20 | KECCAK256 | 2 | 1 | حساب تجزئة كيكاك-256. |
| μ′s[0] ≡ KEC(μm[μs[0] . . . (μs[0] + μs[1] − 1)]) | ||||
| μ′i ≡ M(μi,μs[0],μs[1]) |
هذا هو أول رمز تشغيل يصل إلى الذاكرة (في هذه الحالة، للقراءة فقط). ومع ذلك، قد يتوسع إلى ما هو أبعد من الحدود الحالية للذاكرة، لذا نحتاج إلى تحديث μi. نقوم بذلك باستخدام الدالة M المحددة في المعادلة 330 في ص 30.
| القيمة | الرمز التذكيري | δ | α | الوصف |
|---|---|---|---|---|
| 0x31 | BALANCE | 1 | 1 | الحصول على رصيد الحساب المحدد. |
| ... |
العنوان الذي نحتاج إلى العثور على رصيده هو μs[0] mod 2160. أعلى المكدس هو العنوان، ولكن نظرًا لأن العناوين تبلغ 160 bits فقط، فإننا نحسب القيمة باقي القسمة (modulo) (يفتح في علامة تبويب جديدة) 2160.
إذا كان σ[μs[0] mod 2160] ≠ ∅، فهذا يعني أن هناك معلومات حول هذا العنوان. في هذه الحالة، σ[μs[0] mod 2160]b هو الرصيد لذلك العنوان. إذا كان σ[μs[0] mod 2160] = ∅، فهذا يعني أن هذا العنوان غير مهيأ والرصيد صفر. يمكنك رؤية قائمة حقول معلومات الحساب في القسم 4.1 في ص 4.
المعادلة الثانية، A'a ≡ Aa ∪ {μs[0] mod 2160}، تتعلق بالفرق في التكلفة بين الوصول إلى التخزين الدافئ (التخزين الذي تم الوصول إليه مؤخرًا ومن المحتمل أن يكون مخزنًا مؤقتًا) والتخزين البارد (التخزين الذي لم يتم الوصول إليه ومن المحتمل أن يكون في تخزين أبطأ وأكثر تكلفة في الاسترجاع). Aa هي قائمة العناوين التي تم الوصول إليها مسبقًا بواسطة المعاملة، والتي يجب أن يكون الوصول إليها أرخص، كما هو محدد في القسم 6.1 في ص 9. يمكنك قراءة المزيد حول هذا الموضوع في EIP-2929 (يفتح في علامة تبويب جديدة).
| القيمة | الرمز التذكيري | δ | α | الوصف |
|---|---|---|---|---|
| 0x8F | DUP16 | 16 | 17 | تكرار عنصر المكدس السادس عشر. |
| μ′s[0] ≡ μs[15] |
لاحظ أنه لاستخدام أي عنصر في المكدس، نحتاج إلى إخراجه، مما يعني أننا نحتاج أيضًا إلى إخراج جميع عناصر المكدس الموجودة فوقه. في حالة DUP<n> (يفتح في علامة تبويب جديدة) و SWAP<n> (يفتح في علامة تبويب جديدة)، يعني هذا الاضطرار إلى إخراج ثم إدخال ما يصل إلى ستة عشر قيمة.
9.5 دورة التنفيذ
الآن بعد أن أصبح لدينا جميع الأجزاء، يمكننا أخيرًا فهم كيفية توثيق دورة تنفيذ EVM.
تنص المعادلة (164) على أنه بالنظر إلى الحالة:
- σ (حالة سلسلة الكتل العالمية)
- μ (حالة EVM)
- A (الحالة الفرعية، التغييرات التي ستحدث عند انتهاء المعاملة)
- I (بيئة التنفيذ)
الحالة الجديدة هي (σ', μ', A', I').
تحدد المعادلات (165)-(167) المكدس والتغيير فيه بسبب رمز التشغيل (μs). المعادلة (168) هي التغيير في الغاز (μg). المعادلة (169) هي التغيير في عداد البرنامج (μpc). أخيرًا، تحدد المعادلات (170)-(173) أن المعلمات الأخرى تظل كما هي، ما لم يتم تغييرها صراحةً بواسطة رمز التشغيل.
وبهذا يتم تعريف EVM بالكامل.
الخاتمة
التدوين الرياضي دقيق وقد سمح للورقة الصفراء بتحديد كل تفاصيل إيثيريوم. ومع ذلك، فإن لها بعض العيوب:
- لا يمكن فهمها إلا من قبل البشر، مما يعني أنه يجب كتابة اختبارات الامتثال (يفتح في علامة تبويب جديدة) يدويًا.
- يفهم المبرمجون كود الحاسوب. قد يفهمون أو لا يفهمون التدوين الرياضي.
ربما لهذه الأسباب، تمت كتابة مواصفات طبقة الإجماع (يفتح في علامة تبويب جديدة) الأحدث بلغة Python. هناك مواصفات طبقة التنفيذ بلغة Python (يفتح في علامة تبويب جديدة)، لكنها غير مكتملة. حتى وما لم تتم ترجمة الورقة الصفراء بأكملها أيضًا إلى Python أو لغة مشابهة، ستستمر الورقة الصفراء في الخدمة، ومن المفيد أن تكون قادرًا على قراءتها.