Stack-machines
Ethereum VM يعتمد على المكدس.هذا يعني أن المعاملات للتعليمات مأخوذة من المكدس ، وهي أيضًا حيث تتم إضافة النتائج.
فيما يلي مثال بسيط للغاية على كيفية إضافة 5 و 10 في آلة المكدس.افترض أن هناك تعليمات “الدفع” تضيف عددًا صحيحًا إلى المكدس ، وتعليمات “Add` التي تنبثق عن عنصرين على المكدس ، وتضيفها ، وتضع المبلغ فوق المكدس.هذه هي الطريقة التي ستبدو بها الإضافة:
|
|
يحتوي المكدس الآن على عنصر واحد: 15.
إذا أردنا إضافة 5 و 10 و 20 ، فيمكن أن يتم ذلك بطريقة مماثلة:
|
|
هناك طرق أخرى للقيام بذلك أيضًا ، على سبيل المثال ، يمكننا دفع الأرقام الثلاثة على المكدس مباشرةً ونقوم فقط بتشغيل add
مرتين.
|
|
يجب أن تأتي البيانات أيضًا من مكان ما.الطريقة التي يمكن بها القيام بها هي إعطاء كل تعليمات رمز بايت ، أي رقم من 0 إلى 255. من خلال القيام بذلك ، يمكننا تمرير التعليمات وقيم الإدخال في صفيف بايت بسيط.يمكننا استخدام هذه القواعد:
PUSH
is the byte0x01
ADD
is the byte0x02
Numbers are interpreted as regular 32 bit integers meaning they will occupy 4 bytes, e.g. 5 is
0x00000005
.
We also add a few rules, so that the bytecode can be interpreted.
The first byte is always an instruction.
The byte following an
ADD
is always an instruction.The four bytes following a
PUSH
is to be interpreted as a number.
Now we can write byte-code for the first program.
|
|
لاحظ أنه لا يزال من الممكن أن يفشل لأنه على سبيل المثال ، يمكن إضافة تعليمات “إضافة” قبل وجود عنصرين على المكدس ، لكن هذا ليس مهمًا الآن.
في كلتا الحالتين ، إذا قمنا بتشغيل هذا الآن ، فسيكون من الممكن دفع الأرقام وإضافة أرقام ؛على سبيل المثال ، يمكن أن تفعل وظيفة JavaScript هذه (ما لم تصبح الأرقام كبيرة جدًا ؛ لا يوجد معالجة مناسبة للعدد الصحيح/تدفق Under):
|
|
The EVM Stack
EVM أكثر تعقيدًا بكثير ، ومن الواضح أن هذا المثال البسيط ، ولكن الإضافة سيتم في الواقع بنفس الطريقة.أحد الاختلافات الكبرى هو أن EVM يبلغ حجم كلمة 32 بايت بدلاً من البايتات الأربعة التي استخدمتها في المثال.كما أنه يحتوي على العديد من تعليمات “الدفع” - واحدة لكل عدد ممكن من البايتات (push1
إلى push32
).هكذا سيبدو:
|
|
This should add 1 and 3.
PUSH1
is 0x60
and ADD
is 0x01
, and we will add a STOP
at the end 0x00
, so the bytecode array becomes:
600160030100
Running this in the JavaScript VM (using my own tools) produces this:
|
|
معظم العمليات الحسابية الأخرى (ومتشابهة) تعمل بنفس الطريقة.
معالجة المكدس اليدوي
من الممكن معالجة العناصر الموجودة على المكدس باستخدام “البوب” و “المبادلة” و “Dup”.
Pop
سوف POP إزالة العنصر العلوي.سأضيفه الآن إلى الكود من المثال السابق ، قبل “توقف” مباشرة.
|
|
POP
is 0x50
, so the bytecode is: 60016003015000
Output:
|
|
تبديل
يحتوي “Swap” على العديد من الإصدارات ، اعتمادًا على العناصر التي تريد تبديلها.سيقوم “swap1” بتبديل الأول بالثاني ، “Swap2” الأول مع الثالث ، وهكذا حتى “SWAP16”.
سأقوم بتشغيل المثال الأصلي باستثناء تغيير “Add” إلى “swap1”.هذا يعني تغيير 0x01
إلى 0x90
.
|
|
Bytecode: 600160039000
Output:
|
|
DUP
سيقوم DuP` بنسخ عنصر المكدس ووضع النسخة أعلى المكدس.لديها العديد من الإصدارات أيضًا ، والمبدأ هو نفسه.سيقوم “DUP1” بتكرار العنصر الأول ، “DUP2” الثالث ، وهكذا حتى “DUP16”.
سأقوم بتشغيل المثال الأصلي باستثناء تغيير “Add” إلى “DUP2” ، ونسخ عنصر المكدس الثاني.هذا يعني تغيير 0x01
إلى 0x81
.
|
|
Bytecode: 600160038100
Output:
|
|
التحكم في التدفق
يتم التحكم في التدفق من خلال القفزات والبيانات الشرطية.ستعمل jump
على تعيين البرنامج على القيمة الموجودة حاليًا على رأس المكدس.Jumpi
هي قفزة مشروطة.سيقوم بتعيين برنامج العداد على القيمة أعلى المكدس ، إذا لم يكن عنصر المكدس التالي صفراً.غالبًا ما يتم استخدامه مع الشرطية مثل “Eq” و “LT”.
لا يمكن ضبط الكمبيوتر على أي قيمة ، يجب أن يكون الرمز في تلك المرحلة “أسرف”.سأضيف الآن عددًا من العناصر إلى المكدس ، لكن قم بقفزة تتخطى بعضها.
|
|
This should add 0x01
and 0x02
to the stack, jump past the next two PUSH1
s to index 11, then stop.
Bytecode: 60016002600B56600360045B00
|
|
يمكن القيام بـ Jumpi
بنفس الطريقة ، ولكن بدون إدخال ، يصبح الأمر معقدًا ، لذلك سيتم بدلاً من ذلك إجراء قفزة مشروطة حقيقية في منشور لاحق حول Calldata.الآن سأقوم ببساطة بإعداد مثالين - أحدهما أدفع 0 كـ “الحالة” ، وواحد حيث أمر 1.
الوثب المشروط الناجح:
|
|
Bytecode: 60016001600B57600360045B00
|
|
Failed conditional jump:
|
|
Bytecode: 60016000600B57600360045B00
|
|
لاحظ أنه سيمر “القفز” في المثال الفاشل أيضًا.هذا جيد ، لأن “القفز” لا يؤثر على التنفيذ الطبيعي ؛إنه ببساطة يتحرك إلى ما وراءه إلى التعليمات التالية.
المشاركات القادمة
إن معرفة كيفية العمل مع مكدس وبرنامج Counter هي بداية جيدة.الحساب ، bitwise ، عمليات المقارنة سهلة الاستخدام ، ولا تحتاج إلى الكثير من التفسير.نفس الشيء مع عمليات مثل “العنوان” و “المتصل” و “Blockhash” وما إلى ذلك ، بدلاً من ذلك ، من المحتمل أن تكون المنشورات القادمة على CallData والذاكرة ، نظرًا لأن الذاكرة تجعل من الممكن إرجاع البيانات ، ويمكّن CallData من توفير الإدخال.سيتم التعامل مع التخزين أيضا.
بمجرد تغطية الأساسيات ، سأنتقل إلى أشياء أكثر تقدماً مثل استدعاء العقود من العقود ، وإنشاء عقود جديدة ، والاتصال بالمكتبات يدويًا ، ونسخ رمز Bytecode ، إلخ.