日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[译】Diving Into The Ethereum VM

發布時間:2025/3/15 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [译】Diving Into The Ethereum VM 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Solidity提供了許多高級語言抽象,但是這些特性使我很難理解當我的程序運行時發生了什么。 閱讀Solidity文檔仍然讓我對基本的東西感到困惑。

字符串,字節32,字節[],字節之間有什么區別?

  • 什么時候用?
  • 當我將字符串轉換為字節時發生了什么? 我可以投到byte []嗎?
  • 他們花多少錢?

EVM如何存儲映射?

  • 為什么我不能刪除映射?
  • 我可以有映射的映射嗎? (是的,但這是如何工作的?)
  • 為什么有存儲映射,但沒有內存映射?

編譯的合同如何看待EVM?

  • 合同是如何創建的?
  • 什么是構造函數,真的嗎?
  • 什么是后備功能?

我認為了解像Solidity這樣的高級語言如何在以太坊VM(EVM)上運行是一項很好的投資。 由于幾個原因。

  • 堅定不是硬道理。 更好的EVM語言即將到來。 (可以嗎?)
  • EVM是一個數據庫引擎。 要理解智能合約如何以任何EVM語言工作,您必須了解數據的組織,存儲和操作方式。
  • 知道如何成為貢獻者。 以太坊工具鏈還很早。 很好地了解EVM將幫助您為自己和其他人制作出令人敬畏的工具。
  • 智力挑戰。 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 50

    EVM基本上是一個從上到下執行每條指令的循環。 讓我們使用相應的字節碼來標注匯編代碼(在標簽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字節
    //在字節碼中表示為0x01 16:32 0x00000000000000000000000000000000 00:16 0x00000000000000000000000000000001
    • 0x2(16字節),使用更高的16字節
    //在字節碼中表示為0x200000000000000000000000000000000 16:32 0x00000000000000000000000000000002 00:16 0x00000000000000000000000000000000
    • not(sub(exp(0x2, 0x80), 0x1))
    //高16位字節的位掩碼 16:32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 00:16 0x00000000000000000000000000000000
    • sub(exp(0x2, 0x80), 0x1)
    //低16位字節的位掩碼 16:32 0x00000000000000000000000000000000 00:16 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

    該代碼執行一些比特 - 使用這些值進行混洗以達到期望的結果:

    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

    與50位技術專家面對面20年技術見證,附贈技術全景圖

    總結

    以上是生活随笔為你收集整理的[译】Diving Into The Ethereum VM的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。