汇编 div_Solidity汇编开发简明教程
在用Solidity開發(fā)以太坊智能合約時,使用匯編可以直接與EVM交互,降低 gas開銷成本,更精細(xì)的控制智能合約的行為,因此值得Solidity開發(fā)者學(xué)習(xí) 并加以利用。本文是Solidity匯編開發(fā)的簡明教程,旨在幫助你快速熟悉 如何在Solidity智能合約代碼中嵌入?yún)R編代碼。
以太坊教程鏈接: Dapp入門 | 電商Dapp實戰(zhàn) | Token實戰(zhàn) | Php對接 | Java對接 | Python對接 | C#對接 | Dart對接
2、以太坊虛擬機和堆棧結(jié)構(gòu)機器
以太坊虛擬機EVM有自己的指令集,該指令集中目前包含了 144個操作碼,詳情參考Geth源代碼
這些指令是Solidity抽象出來的,可以在Solidity內(nèi)聯(lián)使用。例如:
contract Assembler { function do_something_cpu() public { assembly { // start writing evm assembler language } }}EVM是一個棧虛擬機,棧這種數(shù)據(jù)結(jié)構(gòu)只允許兩個操作:壓入(PUSH)或彈出(POP)數(shù)據(jù)。 最后壓入的數(shù)據(jù)位于棧頂,因此將被第一個彈出,這被稱為后進(jìn)先出 (LIFO:Last In, First Out):
棧虛擬機將所有的操作數(shù)保存在棧上,關(guān)于棧虛擬機的詳細(xì)信息 可以參考stack machine 基礎(chǔ)
3、堆棧結(jié)構(gòu)機器的操作碼
為了能夠解決實際問題,棧結(jié)構(gòu)機器需要實現(xiàn)一些額外的指令,例如 ADD、SUBSTRACT等等。指令執(zhí)行時通常會先從堆棧彈出一個或多個值作為參數(shù), 再將執(zhí)行結(jié)果壓回堆棧。這通常被稱為逆波蘭表示法(RPN:Reverse Polish Notation):
a + b // 標(biāo)準(zhǔn)表示法Infixa b add // 逆波蘭表示法RPN4、在Solidity合約中使用內(nèi)聯(lián)匯編
可以在Solidity中使用assembly{}來嵌入?yún)R編代碼段,這被稱為內(nèi)聯(lián)匯編:
assembly { // some assembly code here}在assembly塊內(nèi)的代碼開發(fā)語言被稱為Yul,為了簡化我們稱其為 匯編或EVM匯編。
另一個需要注意的問題時,匯編代碼塊之間不能通信,也就是說在 一個匯編代碼塊里定義的變量,在另一個匯編代碼塊中不可以訪問。 例如:
assembly { let x := 2} assembly { let y := x // Error}上面的代碼編譯時會報如下錯誤:
// DeclarationError: identifier not found// let y := x// ^下面的代碼使用內(nèi)聯(lián)匯編代碼計算函數(shù)的兩個參數(shù)的和并返回結(jié)果:
function addition(uint x, uint y) public pure returns (uint) { assembly { let result := add(x, y) // x + y mstore(0x0, result) // 在內(nèi)存中保存結(jié)果 return(0x0, 32) // 從內(nèi)存中返回32字節(jié) } }讓我們重寫上面的代碼,補充一些更詳細(xì)的注釋,以便說明每個指令 在EVM內(nèi)部的運行原理。
function addition(uint x, uint y) public pure returns (uint) { assembly { // 創(chuàng)建一個新的變量result // -> 使用add操作碼計算x+y // -> 將計算結(jié)果賦值給變量result let result := add(x, y) // x + y // 使用mstore操作碼 // -> 將result變量的值存入內(nèi)存 // -> 指定內(nèi)存地址 0x0 mstore(0x0, result) // 將結(jié)果存入內(nèi)存 // 從內(nèi)存地址0x返回32字節(jié) return(0x0, 32) }}5、Solidity匯編中的變量定義與賦值
在Yul中,使用let關(guān)鍵字定義變量。使用:=操作符給變量賦值:
assembly { let x := 2}如果沒有使用:=操作符給變量賦值,那么該變量自動初始化為0值:
assembly { let x // 自動初始化為 x = 0 x := 5 // x 現(xiàn)在的值是5}你可以使用復(fù)雜的表達(dá)式為變量賦值,例如:
assembly { let x := 7 let y := add(x, 3) let z := add(keccak256(0x0, 0x20), div(slength, 32)) let n }6、Solidity匯編中l(wèi)et指令的運行機制
在EVM的內(nèi)部,let指令執(zhí)行如下任務(wù):
- 創(chuàng)建一個新的堆棧槽位
- 為變量保留該槽位
- 當(dāng)?shù)竭_(dá)代碼塊結(jié)束時自動銷毀該槽位
因此,使用let指令在匯編代碼塊中定義的變量,在該代碼塊 外部是無法訪問的。
7、Solidity匯編中的注釋
在Yul匯編中注釋的寫法和Solidity一樣,可以使用單行注釋// 或多行注釋/* */。例如:
assembly { // single line comment /* Multi line comment */}8、Solidity匯編中的字面量
在Solidity匯編中字面量的寫法與Solidity一致。不過,字符串字面量 最多可以包含32個字符。
assembly { let a := 0x123 // 16進(jìn)制 let b := 42 // 10進(jìn)制 let c := "hello world" // 字符串 let d := "very long string more than 32 bytes" // 超長字符串,錯誤!}9、Solidity匯編中的塊和作用范圍
在Solidity匯編中,變量的作用范圍遵循標(biāo)準(zhǔn)規(guī)則。一個塊的范圍使用 一對大括號標(biāo)識。
在下面的示例中,y和z僅在定義所在塊范圍內(nèi)有效。因此y變量的作用 范圍是scope 1,z變量的作用范圍是scope 2。
assembly { let x := 3 // x在各處可見 // Scope 1 { let y := x // ok } // 到此處會銷毀y // Scope 2 { let z := y // Error } // 到此處會銷毀z}// DeclarationError: identifier not found// let z := y// ^作用范圍的唯一例外是函數(shù)和for循環(huán),我們將在下面解釋。
10、在Solidity匯編中使用函數(shù)的局部變量
在Solidity匯編中,只需要使用變量名就可以訪問局部變量, 無論該變量是定義在匯編塊中,還是Solidity代碼中,不過 變量必須是函數(shù)的局部變量:
function assembly_local_var_access() public pure { uint b = 5; assembly { // defined inside an assembly block let x := add(2, 3) let y := 10 z := add(x, y) } assembly { // defined outside an assembly block let x := add(2, 3) let y := mul(x, b) }}11、在Solidity匯編中使用for循環(huán)
先看一下Solidity中循環(huán)的使用。下面的Solidity函數(shù)代碼中 計算變量的倍數(shù)n次,其中value和n是函數(shù)的參數(shù):
function for_loop_solidity(uint n, uint value) public pure returns(uint) { for ( uint i = 0; i < n; i++ ) { value = 2 * value; } return value;}等效的Solidity匯編代碼如下:
function for_loop_assembly(uint n, uint value) public pure returns (uint) { assembly { for { let i := 0 } lt(i, n) { i := add(i, 1) } { value := mul(2, value) } mstore(0x0, value) return(0x0, 32) } }類似于其他開發(fā)語言中的for循環(huán),在Solidity匯編中,for循環(huán)也包含 3個元素:
- 初始化:let i := 0
- 執(zhí)行條件:lt(i, n) ,必須是函數(shù)風(fēng)格表達(dá)式
- 迭代后續(xù)步驟:add(i, 1)
注意:for循環(huán)中變量的作用范圍略有不同。在初始化部分定義的變量 在循環(huán)的其他部分都有效。
12、在Solidity匯編中使用while循環(huán)
在Solidity匯編中實際上是沒有while循環(huán)關(guān)鍵字的,但是可以使用 for循環(huán)實現(xiàn)同樣的功能:只要留空for循環(huán)的初始化部分和迭代后續(xù)步驟即可。
assembly { let x := 0 let i := 0 for { } lt(i, 0x100) { } { // 等價于:while(i < 0x100) x := add(x, mload(i)) i := add(i, 0x20) }}13、在Solidity匯編中使用if語句
Solidity內(nèi)聯(lián)匯編支持使用if語句來設(shè)置代碼執(zhí)行的條件,但是 沒有其他語言中的else部分。
assembly { if slt(x, 0) { x := sub(0, x) } // Ok if eq(value, 0) revert(0, 0) // Error, 需要大括號}if語句強制要求代碼塊使用大括號,即使需要保護(hù)的代碼只有一行, 也需要使用大括號。這和solidity不同。
如果需要在Solidity內(nèi)聯(lián)匯編中檢查多種條件,可以考慮使用 switch語句。
14、在Solidity匯編中使用switch語句
EVM匯編中也有switch語句,它將一個表達(dá)式的值于多個常量 進(jìn)行對比,并選擇相應(yīng)的代碼分支來執(zhí)行。switch語句支持 一個默認(rèn)分支default,當(dāng)表達(dá)式的值不匹配任何其他分支條件時,將 執(zhí)行默認(rèn)分支的代碼。
assembly { let x := 0 switch calldataload(4) case 0 { x := calldataload(0x24) } default { x := calldataload(0x44) } sstore(0, div(x, 2))}switch語句有一些限制:
- 分支列表不需要大括號,但是分支的代碼塊需要大括號
- 所有的分支條件值必須:1)具有相同的類型 2)具有不同的值
- 如果分支條件已經(jīng)涵蓋所有可能的值,那么不允許再出現(xiàn)default條件
15、在Solidity匯編中使用函數(shù)
也可以在Solidity內(nèi)聯(lián)匯編中定義底層函數(shù)。調(diào)用這些自定義的函數(shù) 和使用內(nèi)置的操作碼一樣。
下面的匯編函數(shù)用來分配指定長度的內(nèi)存,并返回內(nèi)存指針pos:
assembly { function allocate(length) -> pos { pos := mload(0x40) mstore(0x40, add(pos, length)) } let free_memory_pointer := allocate(64)}匯編函數(shù)的運行機制如下:
- 從堆棧提取參數(shù)
- 將結(jié)果壓入堆棧
和Solidity函數(shù)不同,不需要指定匯編函數(shù)的可見性,例如public或private, 因為匯編函數(shù)僅在定義所在的匯編代碼塊內(nèi)有效。
16、Solidity匯編中的操作碼
EVM操作碼可以分為以下幾類:
- 算數(shù)和比較操作
- 位操作
- 密碼學(xué)操作,目前僅包含keccak256
- 環(huán)境操作,主要指與區(qū)塊鏈相關(guān)的全局信息,例如blockhash或coinbase收款賬號
- 存儲、內(nèi)存和棧操作
- 交易與合約調(diào)用操作
- 停機操作
- 日志操作
詳細(xì)的操作碼可以查看Solidity文檔。
原文鏈接:http://blog.hubwiz.com/2020/02/15/solidity-assembly-tutorial/
總結(jié)
以上是生活随笔為你收集整理的汇编 div_Solidity汇编开发简明教程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑C盘数据丢失怎么恢复电脑如何恢复c盘
- 下一篇: 学会用电脑刻录光盘苹果电脑如何刻录光盘