فهم مواصفات EVM في الورقة الصفراء
الورقة الصفراء (opens in a new tab) هي المواصفات الرسمية لإيثيريوم. باستثناء ما تم تعديله بواسطة عملية مقترحات تحسين إيثيريوم (EIP)، فإنها تحتوي على الوصف الدقيق لكيفية عمل كل شيء. تمت كتابتها كورقة بحثية رياضية، والتي تتضمن مصطلحات قد لا يجدها المبرمجون مألوفة. في هذه المقالة، ستتعلم كيفية قراءتها، وبالتالي قراءة الأوراق الرياضية الأخرى ذات الصلة.
أي ورقة صفراء؟
مثل كل شيء تقريبًا في إيثيريوم، تتطور الورقة الصفراء بمرور الوقت. لكي أتمكن من الإشارة إلى إصدار معين، قمت برفع الإصدار الحالي وقت الكتابة. ستشير أرقام الأقسام والصفحات والمعادلات التي أستخدمها إلى ذلك الإصدار. من الجيد إبقاؤها مفتوحة في نافذة مختلفة أثناء قراءة هذا المستند.
لماذا EVM؟
كُتبت الورقة الصفراء الأصلية في بداية تطوير إيثيريوم. وهي تصف آلية الإجماع الأصلية القائمة على إثبات العمل (PoW) التي استُخدمت في الأصل لتأمين الشبكة. ومع ذلك، أوقفت إيثيريوم إثبات العمل (PoW) وبدأت في استخدام الإجماع القائم على إثبات الحصة (PoS) في سبتمبر 2022. سيركز هذا البرنامج التعليمي على أجزاء الورقة الصفراء التي تُعرّف آلة إيثيريوم الافتراضية (EVM). لم تتغير EVM بالانتقال إلى إثبات الحصة (PoS) (باستثناء القيمة المرجعة لرمز التشغيل DIFFICULTY).
9 نموذج التنفيذ
يتضمن هذا القسم (ص 12-14) معظم تعريف EVM.
يتضمن مصطلح حالة النظام كل ما تحتاج لمعرفته حول النظام لتشغيله. في الحاسوب النموذجي، يعني هذا الذاكرة، ومحتوى السجلات، وما إلى ذلك.
آلة تورنغ (opens in a new tab) هي نموذج حسابي. في الأساس، هي نسخة مبسطة من الحاسوب، والتي ثبت أن لديها نفس القدرة على تشغيل العمليات الحسابية التي يمكن للحاسوب العادي القيام بها (كل ما يمكن للحاسوب حسابه يمكن لآلة تورنغ حسابه والعكس صحيح). يجعل هذا النموذج من السهل إثبات النظريات المختلفة حول ما هو قابل للحساب وما هو غير قابل للحساب.
يعني مصطلح مكتملة تورنغ (Turing-complete) (opens in a new tab) حاسوبًا يمكنه تشغيل نفس الحسابات مثل آلة تورنغ. يمكن لآلات تورنغ الدخول في حلقات لا نهائية، ولا يمكن لـ EVM القيام بذلك لأنها ستنفد من الغاز، لذا فهي شبه مكتملة تورنغ فقط.
9.1 الأساسيات
يقدم هذا القسم أساسيات EVM وكيفية مقارنتها بالنماذج الحسابية الأخرى.
آلة المكدس (stack machine) (opens in a new tab) هي حاسوب يخزن البيانات الوسيطة ليس في السجلات، ولكن في مكدس (stack) (opens in a new tab). هذه هي البنية المفضلة للآلات الافتراضية لأنه من السهل تنفيذها مما يعني أن الأخطاء والثغرات الأمنية أقل احتمالًا بكثير. تنقسم الذاكرة في المكدس إلى كلمات بحجم 256-bit. تم اختيار هذا لأنه مناسب لعمليات التشفير الأساسية في إيثيريوم مثل عملية التجزئة كيكاك-256 وحسابات منحنى إهليلجي. الحد الأقصى لحجم المكدس هو 1024 عنصرًا (1024 x 256 bits). عند تنفيذ رموز التشغيل، فإنها عادة ما تحصل على معاملاتها من المكدس. هناك رموز تشغيل مخصصة لإعادة تنظيم العناصر في المكدس مثل POP (يزيل العنصر من أعلى المكدس)، و DUP_N (يكرر العنصر رقم N في المكدس)، وما إلى ذلك.
تحتوي EVM أيضًا على مساحة متطايرة تسمى الذاكرة (memory) والتي تُستخدم لتخزين البيانات أثناء التنفيذ. يتم تنظيم هذه الذاكرة في كلمات بحجم 32-byte. تتم تهيئة جميع مواقع الذاكرة إلى الصفر. إذا قمت بتنفيذ كود Yul (opens in a new tab) هذا لإضافة كلمة إلى الذاكرة، فسيملأ 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) (opens in a new tab) القياسية بتخزين الكود والبيانات في نفس الذاكرة. لا تتبع EVM هذا المعيار لأسباب أمنية - فمشاركة الذاكرة المتطايرة تجعل من الممكن تغيير كود البرنامج. بدلاً من ذلك، يتم حفظ الكود في التخزين.
هناك حالتان فقط يتم فيهما تنفيذ الكود من الذاكرة:
- عندما يقوم عقد بإنشاء عقد آخر (باستخدام
CREATE(opens in a new tab) أوCREATE2(opens in a new tab))، فإن كود مُنشئ العقد يأتي من الذاكرة. - أثناء إنشاء أي عقد، يتم تشغيل كود المُنشئ ثم يعود بكود العقد الفعلي، من الذاكرة أيضًا.
يعني مصطلح التنفيذ الاستثنائي استثناءً يتسبب في توقف تنفيذ العقد الحالي.
9.2 نظرة عامة على الرسوم
يشرح هذا القسم كيفية حساب رسوم الغاز. هناك ثلاث تكاليف:
تكلفة رمز التشغيل
التكلفة المتأصلة لرمز التشغيل المحدد. للحصول على هذه القيمة، ابحث عن مجموعة تكلفة رمز التشغيل في الملحق H (ص 28، تحت المعادلة (327))، وابحث عن مجموعة التكلفة في المعادلة (324). يمنحك هذا دالة تكلفة، والتي تستخدم في معظم الحالات معاملات من الملحق G (ص 27).
على سبيل المثال، رمز التشغيل CALLDATACOPY (opens in a new tab) هو عضو في المجموعة 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 في ص 13. وفقًا لذلك القسم، فإن أحد معاملات حالة الآلة هو s للمكدس. بوضع كل ذلك معًا، يبدو أن μs[2] هو الموقع رقم 2 في المكدس. بالنظر إلى رمز التشغيل (opens in a new tab)، فإن الموقع رقم 2 في المكدس هو حجم البيانات بالبايت. بالنظر إلى رموز التشغيل الأخرى في المجموعة Wcopy، CODECOPY (opens in a new tab) و RETURNDATACOPY (opens in a new tab)، فإن لديهم أيضًا حجم بيانات في نفس الموقع. لذا فإن ⌈μs[2]÷32⌉ هو عدد الكلمات بحجم 32 byte المطلوبة لتخزين البيانات التي يتم نسخها. بوضع كل شيء معًا، فإن التكلفة المتأصلة لـ CALLDATACOPY (opens in a new tab) هي 3 غاز زائد 3 لكل كلمة من البيانات التي يتم نسخها.
تكلفة التشغيل
تكلفة تشغيل الكود الذي نستدعيه.
- في حالة
CREATE(opens in a new tab) وCREATE2(opens in a new tab)، مُنشئ العقد الجديد. - في حالة
CALL(opens in a new tab)، أوCALLCODE(opens in a new tab)، أوSTATICCALL(opens in a new tab)، أوDELEGATECALL(opens in a new tab)، العقد الذي نستدعيه.
تكلفة توسيع الذاكرة
تكلفة توسيع الذاكرة (إذا لزم الأمر).
في المعادلة 324، تُكتب هذه القيمة كـ Cmem(μi')-Cmem(μi). بالنظر إلى القسم 9.4.1 مرة أخرى، نرى أن μi هو عدد الكلمات في الذاكرة. لذا فإن μi هو عدد الكلمات في الذاكرة قبل رمز التشغيل و μi' هو عدد الكلمات في الذاكرة بعد رمز التشغيل.
تم تعريف الدالة Cmem في المعادلة 326: 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 (opens in a new tab) | address(this) |
| Io | ORIGIN (opens in a new tab) | tx.origin |
| Ip | GASPRICE (opens in a new tab) | tx.gasprice |
| Id | CALLDATALOAD (opens in a new tab)، إلخ. | msg.data |
| Is | CALLER (opens in a new tab) | msg.sender |
| Iv | CALLVALUE (opens in a new tab) | msg.value |
| Ib | CODECOPY (opens in a new tab) | address(this).code |
| IH | حقول رأس الكتلة، مثل NUMBER (opens in a new tab) و DIFFICULTY (opens in a new tab) | block.number، block.difficulty، إلخ. |
| Ie | عمق مكدس الاستدعاء للاستدعاءات بين العقود (بما في ذلك إنشاء العقد) | |
| Iw | هل يُسمح لـ EVM بتغيير الحالة، أم أنها تعمل بشكل ثابت |
هناك بعض المعاملات الأخرى الضرورية لفهم بقية القسم 9:
| المعامل | مُعرّف في القسم | المعنى |
|---|---|---|
| σ | 2 (ص 2، المعادلة 1) | حالة سلسلة الكتل |
| g | 9.3 (ص 13) | الغاز المتبقي |
| A | 6.1 (ص 8) | الحالة الفرعية المتراكمة (التغييرات المجدولة عند انتهاء المعاملة) |
| o | 9.3 (ص 13) | المخرجات - النتيجة المرجعة في حالة معاملة داخلية (عندما يستدعي عقد عقدًا آخر) واستدعاءات دوال العرض (عندما تطلب معلومات فقط، لذلك لا داعي لانتظار معاملة) |
9.4 نظرة عامة على التنفيذ
الآن بعد أن أصبح لدينا جميع المقدمات، يمكننا أخيرًا البدء في العمل على كيفية عمل EVM.
تعطينا المعادلات 137-142 الشروط الأولية لتشغيل EVM:
| الرمز | القيمة الأولية | المعنى |
|---|---|---|
| μg | g | الغاز المتبقي |
| μpc | 0 | عداد البرنامج، عنوان التعليمة التالية المراد تنفيذها |
| μm | (0, 0, ...) | الذاكرة، مهيأة كلها بأصفار |
| μi | 0 | أعلى موقع ذاكرة مستخدم |
| μs | () | المكدس، فارغ في البداية |
| μo | ∅ | المخرجات، مجموعة فارغة حتى وما لم نتوقف إما ببيانات مرجعة (RETURN (opens in a new tab) أو REVERT (opens in a new tab)) أو بدونها (STOP (opens in a new tab) أو SELFDESTRUCT (opens in a new tab)). |
تخبرنا المعادلة 143 أن هناك أربعة شروط محتملة في كل نقطة زمنية أثناء التنفيذ، وما يجب فعله معها:
Z(σ,μ,A,I). يمثل Z دالة تختبر ما إذا كانت العملية تنشئ انتقال حالة غير صالح (انظر التوقف الاستثنائي). إذا تم تقييمها إلى True، فإن الحالة الجديدة مطابقة للحالة القديمة (باستثناء احتراق الغاز) لأن التغييرات لم يتم تنفيذها.- إذا كان رمز التشغيل الذي يتم تنفيذه هو
REVERT(opens in a new tab)، فإن الحالة الجديدة هي نفس الحالة القديمة، ويُفقد بعض الغاز. - إذا انتهى تسلسل العمليات، كما هو موضح بواسطة
RETURN(opens in a new tab))، يتم تحديث الحالة إلى الحالة الجديدة. - إذا لم نكن في أحد شروط النهاية 1-3، فاستمر في التشغيل.
9.4.1 حالة الآلة
يشرح هذا القسم حالة الآلة بمزيد من التفصيل. يحدد أن w هو رمز التشغيل الحالي. إذا كان μpc أقل من ||Ib||، وهو طول الكود، فإن ذلك البايت (Ib[μpc]) هو رمز التشغيل. بخلاف ذلك، يتم تعريف رمز التشغيل على أنه STOP (opens in a new tab).
نظرًا لأن هذه آلة مكدس (opens in a new tab)، نحتاج إلى تتبع عدد العناصر التي تم إخراجها (δ) وإدخالها (α) بواسطة كل رمز تشغيل.
9.4.2 التوقف الاستثنائي
يُعرّف هذا القسم الدالة Z، والتي تحدد متى يكون لدينا إنهاء غير طبيعي. هذه دالة منطقية (Boolean) (opens in a new tab)، لذا فهي تستخدم ∨ لـ "أو" المنطقية (opens in a new tab) و ∧ لـ "و" المنطقية (opens in a new tab).
لدينا توقف استثنائي إذا كان أي من هذه الشروط صحيحًا:
-
μg < C(σ,μ,A,I) كما رأينا في القسم 9.2، C هي الدالة التي تحدد تكلفة الغاز. لا يوجد غاز كافٍ متبقٍ لتغطية رمز التشغيل التالي.
-
δw=∅ إذا كان عدد العناصر المخرجة لرمز تشغيل غير محدد، فإن رمز التشغيل نفسه غير محدد.
-
|| μs || < δw نقص في المكدس، لا توجد عناصر كافية في المكدس لرمز التشغيل الحالي.
-
w = JUMP ∧ μs[0]∉D(Ib) رمز التشغيل هو
JUMP(opens in a new tab) والعنوان ليسJUMPDEST(opens in a new tab). القفزات صالحة فقط عندما تكون الوجهةJUMPDEST(opens in a new tab). -
w = JUMPI ∧ μs[1]≠0 ∧ μs[0] ∉ D(Ib) رمز التشغيل هو
JUMPI(opens in a new tab)، والشرط صحيح (غير صفري) لذا يجب أن يحدث القفز، والعنوان ليسJUMPDEST(opens in a new tab). القفزات صالحة فقط عندما تكون الوجهةJUMPDEST(opens in a new tab). -
w = RETURNDATACOPY ∧ μs[1]+μs[2]>|| μo || رمز التشغيل هو
RETURNDATACOPY(opens in a new tab). في رمز التشغيل هذا، عنصر المكدس μs[1] هو الإزاحة للقراءة منها في المخزن المؤقت للبيانات المرجعة، وعنصر المكدس μs[2] هو طول البيانات. يحدث هذا الشرط عندما تحاول القراءة بعد نهاية المخزن المؤقت للبيانات المرجعة. لاحظ أنه لا يوجد شرط مماثل لبيانات الاستدعاء أو للكود نفسه. عندما تحاول القراءة بعد نهاية تلك المخازن المؤقتة، فإنك تحصل على أصفار فقط. -
|| μs || - δw + αw > 1024
تجاوز السعة في المكدس. إذا كان تشغيل رمز التشغيل سيؤدي إلى مكدس يحتوي على أكثر من 1024 عنصرًا، فسيتم الإحباط.
-
¬Iw ∧ W(w,μ) هل نعمل بشكل ثابت (¬ هو النفي (opens in a new tab) و Iw صحيح عندما يُسمح لنا بتغيير حالة سلسلة الكتل)؟ إذا كان الأمر كذلك، وكنا نحاول عملية تغيير الحالة، فلا يمكن أن يحدث ذلك.
تم تعريف الدالة W(w,μ) لاحقًا في المعادلة 150. تكون W(w,μ) صحيحة إذا كان أحد هذه الشروط صحيحًا:
-
w ∈ {CREATE, CREATE2, SSTORE, SELFDESTRUCT} تغير رموز التشغيل هذه الحالة، إما عن طريق إنشاء عقد جديد، أو تخزين قيمة، أو تدمير ذاتي للعقد الحالي.
-
LOG0≤w ∧ w≤LOG4 إذا تم استدعاؤنا بشكل ثابت، فلا يمكننا إصدار إدخالات سجل. جميع رموز تشغيل السجل تقع في النطاق بين
LOG0(A0) (opens in a new tab) وLOG4(A4) (opens in a new tab). يحدد الرقم بعد رمز تشغيل السجل عدد المواضيع التي يحتوي عليها إدخال السجل. -
w=CALL ∧ μs[2]≠0 يمكنك استدعاء عقد آخر عندما تكون ثابتًا، ولكن إذا فعلت ذلك فلا يمكنك تحويل ETH إليه.
-
-
w = SSTORE ∧ μg ≤ Gcallstipend لا يمكنك تشغيل
SSTORE(opens in a new tab) ما لم يكن لديك أكثر من Gcallstipend (مُعرّف كـ 2300 في الملحق G) غاز.
9.4.3 صلاحية وجهة القفز
هنا نُعرّف رسميًا ما هي رموز التشغيل JUMPDEST (opens in a new tab). لا يمكننا فقط البحث عن قيمة البايت 0x5B، لأنها قد تكون داخل PUSH (وبالتالي فهي بيانات وليست رمز تشغيل).
في المعادلة (153) نُعرّف دالة، N(i,w). المعامل الأول، i، هو موقع رمز التشغيل. والثاني، w، هو رمز التشغيل نفسه. إذا كان w∈[PUSH1, PUSH32] فهذا يعني أن رمز التشغيل هو PUSH (تحدد الأقواس المربعة نطاقًا يتضمن نقاط النهاية). في هذه الحالة، يكون رمز التشغيل التالي عند i+2+(w−PUSH1). بالنسبة لـ PUSH1 (opens in a new tab) نحتاج إلى التقدم بمقدار بايتين (PUSH نفسه وقيمة البايت الواحد)، وبالنسبة لـ PUSH2 (opens in a new tab) نحتاج إلى التقدم بمقدار ثلاثة بايتات لأنها قيمة من بايتين، وما إلى ذلك. جميع رموز تشغيل EVM الأخرى يبلغ طولها بايتًا واحدًا فقط، لذا في جميع الحالات الأخرى N(i,w)=i+1.
تُستخدم هذه الدالة في المعادلة (152) لتعريف DJ(c,i)، وهي مجموعة (opens in a new tab) جميع وجهات القفز الصالحة في الكود c، بدءًا من موقع رمز التشغيل i. يتم تعريف هذه الدالة بشكل متكرر (recursively). إذا كان i≥||c||، فهذا يعني أننا في نهاية الكود أو بعدها. لن نجد أي وجهات قفز أخرى، لذا قم بإرجاع المجموعة الفارغة فقط.
في جميع الحالات الأخرى، ننظر إلى بقية الكود بالانتقال إلى رمز التشغيل التالي والحصول على المجموعة بدءًا منه. c[i] هو رمز التشغيل الحالي، لذا فإن N(i,c[i]) هو موقع رمز التشغيل التالي. وبالتالي فإن DJ(c,N(i,c[i])) هي مجموعة وجهات القفز الصالحة التي تبدأ عند رمز التشغيل التالي. إذا لم يكن رمز التشغيل الحالي هو JUMPDEST، فما عليك سوى إرجاع تلك المجموعة. إذا كان JUMPDEST، فقم بتضمينه في مجموعة النتائج وأرجع ذلك.
9.4.4 التوقف الطبيعي
يمكن لدالة التوقف H أن تُرجع ثلاثة أنواع من القيم.
- إذا لم نكن في رمز تشغيل توقف، فأرجع ∅، المجموعة الفارغة. بالاصطلاح، يتم تفسير هذه القيمة على أنها خطأ منطقي (Boolean false).
- إذا كان لدينا رمز تشغيل توقف لا ينتج مخرجات (إما
STOP(opens in a new tab) أوSELFDESTRUCT(opens in a new tab))، فأرجع تسلسلًا بحجم صفر بايت كقيمة مرجعة. لاحظ أن هذا يختلف تمامًا عن المجموعة الفارغة. تعني هذه القيمة أن EVM قد توقفت بالفعل، فقط لا توجد بيانات مرجعة لقراءتها. - إذا كان لدينا رمز تشغيل توقف ينتج مخرجات (إما
RETURN(opens in a new tab) أوREVERT(opens in a new tab))، فأرجع تسلسل البايتات المحدد بواسطة رمز التشغيل هذا. يتم أخذ هذا التسلسل من الذاكرة، والقيمة الموجودة في أعلى المكدس (μs[0]) هي البايت الأول، والقيمة التي تليها (μs[1]) هي الطول.
H.2 مجموعة التعليمات
قبل أن ننتقل إلى القسم الفرعي الأخير من EVM، 9.5، دعونا نلقي نظرة على التعليمات نفسها. تم تعريفها في الملحق H.2 الذي يبدأ في ص 29. أي شيء لم يتم تحديده على أنه يتغير مع رمز التشغيل المحدد هذا يُتوقع أن يظل كما هو. يتم تحديد المتغيرات التي تتغير بـ <something>′.
على سبيل المثال، دعونا نلقي نظرة على رمز التشغيل ADD (opens in a new tab).
| القيمة | الرمز التذكيري | δ | α | الوصف |
|---|---|---|---|---|
| 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 المعرفة في المعادلة 328 في ص 29.
| القيمة | الرمز التذكيري | δ | α | الوصف |
|---|---|---|---|---|
| 0x31 | BALANCE | 1 | 1 | الحصول على رصيد الحساب المحدد. |
| ... |
العنوان الذي نحتاج إلى إيجاد رصيده هو μs[0] mod 2160. أعلى المكدس هو العنوان، ولكن نظرًا لأن العناوين تبلغ 160 bits فقط، فإننا نحسب القيمة باقي القسمة (modulo) (opens in a new tab) 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 في ص 8. يمكنك قراءة المزيد حول هذا الموضوع في EIP-2929 (opens in a new tab).
| القيمة | الرمز التذكيري | δ | α | الوصف |
|---|---|---|---|---|
| 0x8F | DUP16 | 16 | 17 | تكرار عنصر المكدس السادس عشر. |
| μ′s[0] ≡ μs[15] |
لاحظ أنه لاستخدام أي عنصر في المكدس، نحتاج إلى إخراجه، مما يعني أننا نحتاج أيضًا إلى إخراج جميع عناصر المكدس الموجودة فوقه. في حالة DUP<n> (opens in a new tab) و SWAP<n> (opens in a new tab)، يعني هذا الاضطرار إلى إخراج ثم إدخال ما يصل إلى ستة عشر قيمة.
9.5 دورة التنفيذ
الآن بعد أن أصبح لدينا جميع الأجزاء، يمكننا أخيرًا فهم كيفية توثيق دورة تنفيذ EVM.
تنص المعادلة (155) على أنه بالنظر إلى الحالة:
- σ (حالة سلسلة الكتل العالمية)
- μ (حالة EVM)
- A (الحالة الفرعية، التغييرات التي ستحدث عند انتهاء المعاملة)
- I (بيئة التنفيذ)
الحالة الجديدة هي (σ', μ', A', I').
تُعرّف المعادلات (156)-(158) المكدس والتغيير فيه بسبب رمز التشغيل (μs). المعادلة (159) هي التغيير في الغاز (μg). المعادلة (160) هي التغيير في عداد البرنامج (μpc). أخيرًا، تحدد المعادلات (161)-(164) أن المعاملات الأخرى تظل كما هي، ما لم يتم تغييرها صراحةً بواسطة رمز التشغيل.
وبهذا يتم تعريف EVM بالكامل.
الخاتمة
التدوين الرياضي دقيق وقد سمح للورقة الصفراء بتحديد كل تفاصيل إيثيريوم. ومع ذلك، فإن لها بعض العيوب:
- لا يمكن فهمها إلا من قبل البشر، مما يعني أنه يجب كتابة اختبارات الامتثال (opens in a new tab) يدويًا.
- يفهم المبرمجون كود الحاسوب. قد يفهمون أو لا يفهمون التدوين الرياضي.
ربما لهذه الأسباب، تمت كتابة مواصفات طبقة الإجماع (opens in a new tab) الأحدث بلغة Python. هناك مواصفات طبقة التنفيذ بلغة Python (opens in a new tab)، لكنها غير مكتملة. حتى وما لم تتم ترجمة الورقة الصفراء بأكملها أيضًا إلى Python أو لغة مشابهة، ستستمر الورقة الصفراء في الخدمة، ومن المفيد أن تكون قادرًا على قراءتها.