Featured image of post الصلابة المتقدمة 2- مقدمة صلابة اللغة

الصلابة المتقدمة 2- مقدمة صلابة اللغة

موضوعات الصلابة المتقدمة - برمجة العقد Ethereum.

Stack-machines

Ethereum VM يعتمد على المكدس.هذا يعني أن المعاملات للتعليمات مأخوذة من المكدس ، وهي أيضًا حيث تتم إضافة النتائج.

فيما يلي مثال بسيط للغاية على كيفية إضافة 5 و 10 في آلة المكدس.افترض أن هناك تعليمات “الدفع” تضيف عددًا صحيحًا إلى المكدس ، وتعليمات “Add` التي تنبثق عن عنصرين على المكدس ، وتضيفها ، وتضع المبلغ فوق المكدس.هذه هي الطريقة التي ستبدو بها الإضافة:

1
2
3
4
5
0: PUSH 5       Stack: [ 5 ] 

1: PUSH 10      Stack: [ 5, 10 ]

2: ADD          Stack: [ 15 ]

يحتوي المكدس الآن على عنصر واحد: 15.

إذا أردنا إضافة 5 و 10 و 20 ، فيمكن أن يتم ذلك بطريقة مماثلة:

1
2
3
4
5
6
7
8
9
0: PUSH 5       Stack: [ 5 ] 

1: PUSH 10      Stack: [ 5, 10 ]

2: ADD          Stack: [ 15 ]

3: PUSH 20      Stack: [ 15, 20 ]

4: ADD          Stack: [ 35 ]

هناك طرق أخرى للقيام بذلك أيضًا ، على سبيل المثال ، يمكننا دفع الأرقام الثلاثة على المكدس مباشرةً ونقوم فقط بتشغيل add مرتين.

1
2
3
4
5
6
7
8
9
0: PUSH 5       Stack: [ 5 ] 

1: PUSH 10      Stack: [ 5, 10 ]

2: PUSH 20      Stack: [ 5, 10, 20 ]

3: ADD          Stack: [ 5, 30 ]

4: ADD          Stack: [ 35 ]

يجب أن تأتي البيانات أيضًا من مكان ما.الطريقة التي يمكن بها القيام بها هي إعطاء كل تعليمات رمز بايت ، أي رقم من 0 إلى 255. من خلال القيام بذلك ، يمكننا تمرير التعليمات وقيم الإدخال في صفيف بايت بسيط.يمكننا استخدام هذه القواعد:

  1. PUSH is the byte 0x01

  2. ADD is the byte 0x02

  3. 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.

  1. The first byte is always an instruction.

  2. The byte following an ADD is always an instruction.

  3. The four bytes following a PUSH is to be interpreted as a number.

Now we can write byte-code for the first program.

1
2
3
4
5
6
PUSH    5           PUSH    10          ADD
0x01    0x00000005  0x01    0x0000000A  0x02

Result:

0100000005010000000A02

لاحظ أنه لا يزال من الممكن أن يفشل لأنه على سبيل المثال ، يمكن إضافة تعليمات “إضافة” قبل وجود عنصرين على المكدس ، لكن هذا ليس مهمًا الآن.

في كلتا الحالتين ، إذا قمنا بتشغيل هذا الآن ، فسيكون من الممكن دفع الأرقام وإضافة أرقام ؛على سبيل المثال ، يمكن أن تفعل وظيفة JavaScript هذه (ما لم تصبح الأرقام كبيرة جدًا ؛ لا يوجد معالجة مناسبة للعدد الصحيح/تدفق Under):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function run(bytecode) {
    var len = bytecode.length;
    if (!len || len % 2)
        throw new Error("Bad input");
    var hl = len >> 1;
    
    var code = new Array(hl);
    for (var i = 0; i < hl; i++) {
        code[i] = parseInt(bytecode.substr(i * 2, 2), 16);
    }

    var codeSize = code.length;
    var stack = [];
    var pc = -1;

    while (++pc < codeSize) {
        switch (code[pc]) {
            case 1:
                var num = (code[++pc] << 24) | (code[++pc] << 16) | (code[++pc] << 8) | code[++pc];
                stack.push(num);
                console.log("PUSH: %d", num);
                break;
            case 2:
                if (stack.length < 2)
                    throw new Error("Stack underflow");
                var a = stack.pop(), b = stack.pop();
                var sum = a + b;
                stack.push(sum);
                console.log("ADD: %d + %d = %d", a, b, sum);
                break;
            default:
                throw new Error("Bad instruction");
        }
        console.log("Stack:", stack);
    }
    console.log("Done");
}

run("0100000005010000000A02");

The EVM Stack

EVM أكثر تعقيدًا بكثير ، ومن الواضح أن هذا المثال البسيط ، ولكن الإضافة سيتم في الواقع بنفس الطريقة.أحد الاختلافات الكبرى هو أن EVM يبلغ حجم كلمة 32 بايت بدلاً من البايتات الأربعة التي استخدمتها في المثال.كما أنه يحتوي على العديد من تعليمات “الدفع” - واحدة لكل عدد ممكن من البايتات (push1 إلى push32).هكذا سيبدو:

1
PUSH1 0x01 PUSH1 0x03 ADD STOP

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/usr/local/bin/node runcode.js
PC: 0
Opcode: PUSH1
Stack: []
PC: 2
Opcode: PUSH1
Stack: [ '01' ]
PC: 4
Opcode: ADD
Stack: [ '01', '03' ]
PC: 5
Opcode: STOP
Stack: [ '04' ]
returned: 

Process finished with exit code 0

معظم العمليات الحسابية الأخرى (ومتشابهة) تعمل بنفس الطريقة.

معالجة المكدس اليدوي

من الممكن معالجة العناصر الموجودة على المكدس باستخدام “البوب” و “المبادلة” و “Dup”.

Pop

سوف POP إزالة العنصر العلوي.سأضيفه الآن إلى الكود من المثال السابق ، قبل “توقف” مباشرة.

1
PUSH1 0x01 PUSH1 0x03 ADD POP STOP

POP is 0x50, so the bytecode is: 60016003015000

Output:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
/usr/local/bin/node runcode.js
PC: 0
Opcode: PUSH1
Stack: []
PC: 2
Opcode: PUSH1
Stack: [ '01' ]
PC: 4
Opcode: ADD
Stack: [ '01', '03' ]
PC: 5
Opcode: POP
Stack: [ '04' ]
PC: 6
Opcode: STOP
Stack: []
returned: 

Process finished with exit code 0
تبديل

يحتوي “Swap” على العديد من الإصدارات ، اعتمادًا على العناصر التي تريد تبديلها.سيقوم “swap1” بتبديل الأول بالثاني ، “Swap2” الأول مع الثالث ، وهكذا حتى “SWAP16”.

سأقوم بتشغيل المثال الأصلي باستثناء تغيير “Add” إلى “swap1”.هذا يعني تغيير 0x01 إلى 0x90.

1
PUSH1 0x01 PUSH1 0x03 SWAP1 STOP

Bytecode: 600160039000

Output:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/usr/local/bin/node runcode.js
PC: 0
Opcode: PUSH1
Stack: []
PC: 2
Opcode: PUSH1
Stack: [ '01' ]
PC: 4
Opcode: SWAP1
Stack: [ '01', '03' ]
PC: 5
Opcode: STOP
Stack: [ '03', '01' ]
returned: 

Process finished with exit code 0
DUP

سيقوم DuP` بنسخ عنصر المكدس ووضع النسخة أعلى المكدس.لديها العديد من الإصدارات أيضًا ، والمبدأ هو نفسه.سيقوم “DUP1” بتكرار العنصر الأول ، “DUP2” الثالث ، وهكذا حتى “DUP16”.

سأقوم بتشغيل المثال الأصلي باستثناء تغيير “Add” إلى “DUP2” ، ونسخ عنصر المكدس الثاني.هذا يعني تغيير 0x01 إلى 0x81.

1
PUSH1 0x01 PUSH1 0x03 DUP2 STOP

Bytecode: 600160038100

Output:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/usr/local/bin/node runcode.js
PC: 0
Opcode: PUSH1
Stack: []
PC: 2
Opcode: PUSH1
Stack: [ '01' ]
PC: 4
Opcode: DUP2
Stack: [ '01', '03' ]
PC: 5
Opcode: STOP
Stack: [ '01', '03', '01' ]
returned: 

Process finished with exit code 0

التحكم في التدفق

يتم التحكم في التدفق من خلال القفزات والبيانات الشرطية.ستعمل jump على تعيين البرنامج على القيمة الموجودة حاليًا على رأس المكدس.Jumpi هي قفزة مشروطة.سيقوم بتعيين برنامج العداد على القيمة أعلى المكدس ، إذا لم يكن عنصر المكدس التالي صفراً.غالبًا ما يتم استخدامه مع الشرطية مثل “Eq” و “LT”.

لا يمكن ضبط الكمبيوتر على أي قيمة ، يجب أن يكون الرمز في تلك المرحلة “أسرف”.سأضيف الآن عددًا من العناصر إلى المكدس ، لكن قم بقفزة تتخطى بعضها.

1
PUSH1 0x01 PUSH1 0x02 PUSH1 0x0B JUMP PUSH1 0x03 PUSH1 0x04 JUMPDEST STOP

This should add 0x01 and 0x02 to the stack, jump past the next two PUSH1s to index 11, then stop.

Bytecode: 60016002600B56600360045B00

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/usr/local/bin/node runcode.js
PC: 0
Opcode: PUSH1
Stack: []
PC: 2
Opcode: PUSH1
Stack: [ '01' ]
PC: 4
Opcode: PUSH1
Stack: [ '01', '02' ]
PC: 6
Opcode: JUMP
Stack: [ '01', '02', '0b' ]
PC: 11
Opcode: JUMPDEST
Stack: [ '01', '02' ]
PC: 12
Opcode: STOP
Stack: [ '01', '02' ]
returned: 

Process finished with exit code 0

يمكن القيام بـ Jumpi بنفس الطريقة ، ولكن بدون إدخال ، يصبح الأمر معقدًا ، لذلك سيتم بدلاً من ذلك إجراء قفزة مشروطة حقيقية في منشور لاحق حول Calldata.الآن سأقوم ببساطة بإعداد مثالين - أحدهما أدفع 0 كـ “الحالة” ، وواحد حيث أمر 1.

الوثب المشروط الناجح:

1
PUSH1 0x01 PUSH1 0x01 PUSH1 0x0B JUMPI PUSH1 0x03 PUSH1 0x04 JUMPDEST STOP

Bytecode: 60016001600B57600360045B00

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/usr/local/bin/node runcode.js
PC: 0
Opcode: PUSH1
Stack: []
PC: 2
Opcode: PUSH1
Stack: [ '01' ]
PC: 4
Opcode: PUSH1
Stack: [ '01', '01' ]
PC: 6
Opcode: JUMPI
Stack: [ '01', '01', '0b' ]
PC: 11
Opcode: JUMPDEST
Stack: [ '01' ]
PC: 12
Opcode: STOP
Stack: [ '01' ]
returned: 

Process finished with exit code 0

Failed conditional jump:

1
PUSH1 0x01 PUSH1 0x00 PUSH1 0x0B JUMPI PUSH1 0x03 PUSH1 0x04 JUMPDEST STOP

Bytecode: 60016000600B57600360045B00

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/usr/local/bin/node runcode.js
PC: 0
Opcode: PUSH1
Stack: []
PC: 2
Opcode: PUSH1
Stack: [ '01' ]
PC: 4
Opcode: PUSH1
Stack: [ '01', '00' ]
PC: 6
Opcode: JUMPI
Stack: [ '01', '00', '0b' ]
PC: 7
Opcode: PUSH1
Stack: [ '01' ]
PC: 9
Opcode: PUSH1
Stack: [ '01', '03' ]
PC: 11
Opcode: JUMPDEST
Stack: [ '01', '03', '04' ]
PC: 12
Opcode: STOP
Stack: [ '01', '03', '04' ]
returned: 

Process finished with exit code 0

لاحظ أنه سيمر “القفز” في المثال الفاشل أيضًا.هذا جيد ، لأن “القفز” لا يؤثر على التنفيذ الطبيعي ؛إنه ببساطة يتحرك إلى ما وراءه إلى التعليمات التالية.

المشاركات القادمة

إن معرفة كيفية العمل مع مكدس وبرنامج Counter هي بداية جيدة.الحساب ، bitwise ، عمليات المقارنة سهلة الاستخدام ، ولا تحتاج إلى الكثير من التفسير.نفس الشيء مع عمليات مثل “العنوان” و “المتصل” و “Blockhash” وما إلى ذلك ، بدلاً من ذلك ، من المحتمل أن تكون المنشورات القادمة على CallData والذاكرة ، نظرًا لأن الذاكرة تجعل من الممكن إرجاع البيانات ، ويمكّن CallData من توفير الإدخال.سيتم التعامل مع التخزين أيضا.

بمجرد تغطية الأساسيات ، سأنتقل إلى أشياء أكثر تقدماً مثل استدعاء العقود من العقود ، وإنشاء عقود جديدة ، والاتصال بالمكتبات يدويًا ، ونسخ رمز Bytecode ، إلخ.

comments powered by Disqus
مبني بستخدام Hugo
قالب Stack مصمم من Jimmy