[译】Diving Into The Ethereum VM
字符串,字節32,字節[],字節之間有什么區別?
- 什么時候用?
- 當我將字符串轉換為字節時發生了什么? 我可以投到byte []嗎?
- 他們花多少錢?
EVM如何存儲映射?
- 為什么我不能刪除映射?
- 我可以有映射的映射嗎? (是的,但這是如何工作的?)
- 為什么有存儲映射,但沒有內存映射?
編譯的合同如何看待EVM?
- 合同是如何創建的?
- 什么是構造函數,真的嗎?
- 什么是后備功能?
我認為了解像Solidity這樣的高級語言如何在以太坊VM(EVM)上運行是一項很好的投資。 由于幾個原因。
在一系列文章中,我想解構簡單的Solidity合約,以便了解它如何用作EVM字節碼。
我希望學習和寫作的概述:
- EVM字節碼的基礎知識。
- 如何表示不同的類型(映射,數組)。
- 新合同創建時發生了什么。
- 當一個方法被調用時發生了什么。
- ABI如何橋接不同的EVM語言。
我的最終目標是能夠理解整個編譯的Solidity合同。 首先閱讀一些基本的EVM字節碼!
此EVM指令集表格將是一個有用的參考。
簡單的合同
我們的第一份合同有一個構造函數和一個狀態變量:
// c1.sol 雜注扎實0.4.11; 合同C { uint256 a; 函數C(){ a = 1; } }用solc編譯這個合同:
$ solc --bin --asm c1.sol ======= c1.sol:C ======= EVM組件: / *“c1.sol”:26:94合約C {... * / mstore(0x40,0x60) / *“c1.sol”:59:92 function C(){... * / jumpi(tag_1,iszero(callvalue)) 為0x0 DUP1 還原 TAG_1: TAG_2: / *“c1.sol”:84:85 1 * / 為0x1 / *“c1.sol”:80:81 a * / 為0x0 / *“c1.sol”:80:85 a = 1 * / DUP2 swap1 sstore 流行的 / *“c1.sol”:59:92 function C(){... * / tag_3: / *“c1.sol”:26:94合約C {... * / tag_4: 數據尺寸(sub_0) DUP1 dataOffset(sub_0) 為0x0 codecopy 為0x0 返回 停止 sub_0:程序集{ / *“c1.sol”:26:94合約C {... * / mstore(0x40,0x60) TAG_1: 為0x0 DUP1 還原 auxdata:0xa165627a7a72305820af3193f6fd31031a0e0d2de1ad2c27352b1ce081b4f3c92b5650ca4dd542bb770029 } 二進制: 60606040523415600e57600080fd5b5b60016000819055505b5b60368060266000396000f30060606040525b600080fd00a165627a7a72305820af3193f6fd31031a0e0d2de1ad2c27352b1ce081b4f3c92b5650ca4dd542bb770029數字6060604052...是EVM實際運行的字節碼。
在嬰兒步驟
一半的編譯程序集樣板文件在大多數Solidity程序中都是相似的。 我們稍后再看看。 現在,讓我們來看看我們合約的獨特部分,簡單的存儲變量賦值:
a = 1這個任務由字節碼6001600081905550表示。 讓我們把它分解成每行一條指令:
60 01 60 00 81 90 55 50EVM基本上是一個從上到下執行每條指令的循環。 讓我們使用相應的字節碼來標注匯編代碼(在標簽tag_2下縮進),以更好地了解它們的關聯方式:
TAG_2: // 60 01 為0x1 // 60 00 為0x0 // 81 DUP2 // 90 swap1 // 55 sstore // 50 流行的請注意,匯編代碼中的0x1實際上是push(0x1)的簡寫push(0x1) 。 該指令將數字1推入堆棧。
盯著它看看發生了什么仍然很難。 不要擔心,很容易一行一行地模擬EVM。
模擬EVM
EVM是一個堆棧機器。 指令可能使用堆棧中的值作為參數,并將值作為結果推送到堆棧。 讓我們考慮操作add 。
假設堆棧中有兩個值:
[1 2]當EVM看到add ,它將前兩項添加到一起,并將答案推回到堆棧上,導致:
[3]在下面的內容中,我們將用[]注釋堆棧:
//空的堆棧 堆棧:[] //堆疊三個項目。 最上面的項目是3.最下面的項目是1。 堆疊:[3 2 1]并用{}標記合約存儲空間:
//存儲空間不存在 商店:{} //值0x1存儲在位置0x0。 存儲:{0x0 => 0x1}現在我們來看看一些真正的字節碼。 我們將按照EVM模擬字節碼序列6001600081905550 ,并在每條指令后打印機器狀態:
// 60 01:將1推入堆棧 為0x1 堆棧:[0x1] // 60 00:將0推入堆棧 為0x0 堆棧:[0x0 0x1] // 81:復制堆棧中的第二個項目 DUP2 堆棧:[0x1 0x0 0x1] // 90:交換前兩個項目 swap1 堆棧:[0x0 0x1 0x1] // 55:將值0x1存儲在位置0x0 //這條指令消耗前兩項 sstore 堆棧:[0x1] 存儲:{0x0 => 0x1} // 50:流行(丟掉頂級商品) 流行的 堆棧:[] 存儲:{0x0 => 0x1}結束。 堆棧是空的,并且存儲中有一個項目。
值得注意的是, uint256 a決定將狀態變量uint256 a存儲在位置0x0 。 其他語言可以選擇在別處存儲狀態變量。
在偽代碼中,EVM為6001600081905550的實質上是:
// a = 1 sstore(0x0,0x1)仔細看,你會發現dup2,swap1,pop是多余的。 匯編代碼可能更簡單:
為0x1 為0x0 sstore您可以嘗試模擬上述3條指令,并確保它們確實導致相同的機器狀態:
堆棧:[] 存儲:{0x0 => 0x1}兩個存儲變量
我們添加一個相同類型的額外存儲變量:
// c2.sol 雜注扎實0.4.11; 合同C { uint256 a; uint256 b; 函數C(){ a = 1; b = 2; } }編譯,重點關注tag_2 :
$ solc --bin --asm c2.sol // ...更多的東西被省略 TAG_2: / *“c2.sol”:99:100 1 * / 為0x1 / *“c2.sol”:95:96 a * / 為0x0 / *“c2.sol”:95:100 a = 1 * / DUP2 swap1 sstore 流行的 / *“c2.sol”:112:113 2 * / 0X2 / *“c2.sol”:108:109 b * / 為0x1 / *“c2.sol”:108:113 b = 2 * / DUP2 swap1 sstore 流行的偽代碼中的程序集:
// a = 1 sstore(0x0,0x1) // b = 2 sstore(0x1,0x2)我們在這里學到的是兩個存儲變量0x0定位,位置為0x0 ,位置為0x1 。
存儲包裝
每個插槽存儲可以存儲32個字節。 如果一個變量只需要16個字節,那么使用全部32個字節是浪費的。 如果可能,通過將兩個較小的數據類型打包到一個存儲插槽中,Solidity可優化存儲效率。
讓我們改變a和b使它們每個只有16個字節:
雜注扎實0.4.11; 合同C { uint128 a; uint128 b; 函數C(){ a = 1; b = 2; } }編制合同:
$ solc --bin --asm c3.sol生成的程序集現在更復雜:
TAG_2: // a = 1 為0x1 為0x0 DUP1 為0x100 EXP DUP2 SLOAD DUP2 0xffffffffffffffffffffffffffffffff MUL 不 和 swap1 dup4 0xffffffffffffffffffffffffffffffff 和 MUL 要么 swap1 sstore 流行的 // b = 2 0X2 為0x0 為0x10 為0x100 EXP DUP2 SLOAD DUP2 0xffffffffffffffffffffffffffffffff MUL 不 和 swap1 dup4 0xffffffffffffffffffffffffffffffff 和 MUL 要么 swap1 sstore 流行的上述匯編代碼將這兩個變量一起打包在一個存儲位置( 0x0 )中,如下所示:
[b] [a] [16字節/ 128位] [16字節/ 128位]打包的理由是因為目前最昂貴的操作是存儲使用情況:
- sstore花費20000瓦斯首先寫入新的位置。
- sstore花費5000瓦斯用于后續寫入現有位置。
- sload 500個天然氣。
- 大多數指令需要3?10個氣體。
通過使用相同的存儲位置,Solidity為第二個存儲變量支付5000而不是20000,為我們節省了15000個氣體。
更多優化
應該可以將兩個128位的數字一起打包在內存中,然后使用一個sstore存儲它們,從而節省額外的5000個氣體,而不是使用兩個單獨的sstore指令來存儲a和b 。
您可以通過打開optimize標志來讓Solidity進行優化:
$ solc --bin --asm --optimize c3.sol它生成僅使用一個sload和一個sstore的匯編代碼:
TAG_2: / *“c3.sol”:95:96 a * / 為0x0 / *“c3.sol”:95:100 a = 1 * / DUP1 SLOAD / *“c3.sol”:108:113 b = 2 * / 0x200000000000000000000000000000000 not(sub(exp(0x2,0x80),0x1)) / *“c3.sol”:95:100 a = 1 * / swap1 swap2 和 / *“c3.sol”:99:100 1 * / 為0x1 / *“c3.sol”:95:100 a = 1 * / 要么 sub(exp(0x2,0x80),0x1) / *“c3.sol”:108:113 b = 2 * / 和 要么 swap1 sstore字節碼是:
600080547002000000000000000000000000000000006001608060020a03199091166001176001608060020a0316179055并將字節碼格式化為每行一條指令:
//按下0x0 60 00 // dup1 80 // sload 54 // push17將下一個17字節作為32字節的數字 70 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 / * not(sub(exp(0x2,0x80),0x1))* / //推送0x1 60 01 //按0x80(32) 60 80 //按0x80(2) 60 02 // exp 0A //子 03 //不是 19 // swap1 90 // swap2 91 //和 16 //推送0x1 60 01 // 要么 17 / * sub(exp(0x2,0x80),0x1)* / //推送0x1 60 01 //按下0x80 60 80 //推送0x02 60 02 // exp 0A //子 03 //和 16 // 要么 17 // swap1 90 // sstore 55匯編代碼中使用了四個魔術值:
- 0x1(16字節),使用較低的16字節
- 0x2(16字節),使用更高的16字節
- not(sub(exp(0x2, 0x80), 0x1))
- sub(exp(0x2, 0x80), 0x1)
該代碼執行一些比特 - 使用這些值進行混洗以達到期望的結果:
16:32 0x00000000000000000000000000000002 00:16 0x00000000000000000000000000000001最后,這個32字節的值被存儲在位置0x0 。
燃氣使用
60008054700 200000000000000000000000000000000 6001608060020a03199091166001176001608060020a0316179055請注意, 0x200000000000000000000000000000000嵌入在字節碼中。 但是編譯器也可以選擇用指令exp(0x2, 0x81)來計算值,這會導致較短的字節碼序列。
但事實證明, 0x200000000000000000000000000000000比exp(0x2, 0x81)便宜。 我們來看看所涉及的天然氣費用:
- 對于交易的每個零字節的數據或代碼支付4種氣體。
- 對于交易的每個非零字節的數據或代碼,有68個氣體。
讓我們來比較一下在天然氣中的代表費用。
- 字節碼為0x200000000000000000000000000000000 。 它有很多零,價格便宜。
(1 * 68)+(16 * 4)= 196。
- 字節碼608160020a 。 更短,但沒有零。
5 * 68 = 340。
更多零的更長序列實際上更便宜!
概要
EVM編譯器不會針對字節碼大小,速度或內存效率進行精確優化。 相反,它優化了天然氣使用量,這是一個間接的層面,可以激勵以太坊區塊鏈可以有效地進行計算。
我們已經看到了EVM的一些古怪的方面:
- EVM是一款256位機器。 以32字節的塊操作數據是最自然的。
- 持久存儲非常昂貴。
- Solidity編譯器做出了有趣的選擇,以最大限度地減少燃氣使用。
天然氣成本有些任意設定,未來可能會發生改變。 隨著成本的變化,編譯器會做出不同的選擇。
在這篇關于EVM的文章系列中,我寫到:
- EVM匯編代碼簡介。
- 如何表示固定長度的數據類型。
- 如何表示動態數據類型。
- ABI如何編碼外部方法調用。
- 新合同創建時發生了什么。
https://blog.qtum.org/diving-into-the-ethereum-vm-6e8d5d2f3c30
總結
以上是生活随笔為你收集整理的[译】Diving Into The Ethereum VM的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【译】A Hitchhiker’s Gu
- 下一篇: 【译】Diving Into The E