您好,登錄后才能下訂單哦!
本篇內容主要講解“Solidity內聯匯編怎么使用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Solidity內聯匯編怎么使用”吧!
在用Solidity開發以太坊智能合約時,使用匯編可以直接與EVM交互,降低gas開銷成本,更精細的控制智能合約的行為,因此值得Solidity開發者學習并加以利用。
以太坊虛擬機EVM有自己的指令集,該指令集中目前包含了144個操作碼,詳情參考Geth源代碼
這些指令是Solidity抽象出來的,可以在Solidity內聯使用。例如:
contract Assembler { function do_something_cpu() public { assembly { // start writing evm assembler language } } }
EVM是一個棧虛擬機,棧這種數據結構只允許兩個操作:壓入(PUSH)或彈出(POP)數據。最后壓入的數據位于棧頂,因此將被第一個彈出,這被稱為后進先出(LIFO:Last In, First Out):
棧虛擬機將所有的操作數保存在棧上,關于棧虛擬機的詳細信息可以參考stack machine 基礎
為了能夠解決實際問題,棧結構機器需要實現一些額外的指令,例如ADD、SUBSTRACT等等。指令執行時通常會先從堆棧彈出一個或多個值作為參數,再將執行結果壓回堆棧。這通常被稱為逆波蘭表示法(RPN:Reverse Polish Notation):
a + b // 標準表示法Infix a b add // 逆波蘭表示法RPN
可以在Solidity中使用assembly{}
來嵌入匯編代碼段,這被稱為內聯匯編:
assembly { // some assembly code here }
在assembly
塊內的代碼開發語言被稱為Yul,為了簡化我們稱其為匯編或EVM匯編。
另一個需要注意的問題時,匯編代碼塊之間不能通信,也就是說在一個匯編代碼塊里定義的變量,在另一個匯編代碼塊中不可以訪問。例如:
assembly { let x := 2 } assembly { let y := x // Error }
上面的代碼編譯時會報如下錯誤:
// DeclarationError: identifier not found // let y := x // ^
下面的代碼使用內聯匯編代碼計算函數的兩個參數的和并返回結果:
function addition(uint x, uint y) public pure returns (uint) { assembly { let result := add(x, y) // x + y mstore(0x0, result) // 在內存中保存結果 return(0x0, 32) // 從內存中返回32字節 } }
讓我們重寫上面的代碼,補充一些更詳細的注釋,以便說明每個指令在EVM內部的運行原理。
function addition(uint x, uint y) public pure returns (uint) { assembly { // 創建一個新的變量result // -> 使用add操作碼計算x+y // -> 將計算結果賦值給變量result let result := add(x, y) // x + y // 使用mstore操作碼 // -> 將result變量的值存入內存 // -> 指定內存地址 0x0 mstore(0x0, result) // 將結果存入內存 // 從內存地址0x返回32字節 return(0x0, 32) } }
在Yul中,使用let
關鍵字定義變量。使用:=
操作符給變量賦值:
assembly { let x := 2 }
如果沒有使用:=
操作符給變量賦值,那么該變量自動初始化為0值:
assembly { let x // 自動初始化為 x = 0 x := 5 // x 現在的值是5 }
你可以使用復雜的表達式為變量賦值,例如:
assembly { let x := 7 let y := add(x, 3) let z := add(keccak256(0x0, 0x20), div(slength, 32)) let n }
在EVM的內部,let
指令執行如下任務:
創建一個新的堆棧槽位
為變量保留該槽位
當到達代碼塊結束時自動銷毀該槽位
因此,使用let指令在匯編代碼塊中定義的變量,在該代碼塊外部是無法訪問的。
在Yul匯編中注釋的寫法和Solidity一樣,可以使用單行注釋//
或多行注釋/* */
。例如:
assembly { // single line comment /* Multi line comment */ }
在Solidity匯編中字面量的寫法與Solidity一致。不過,字符串字面量最多可以包含32個字符。
assembly { let a := 0x123 // 16進制 let b := 42 // 10進制 let c := "hello world" // 字符串 let d := "very long string more than 32 bytes" // 超長字符串,錯誤! }
在Solidity匯編中,變量的作用范圍遵循標準規則。一個塊的范圍使用一對大括號標識。
在下面的示例中,y和z僅在定義所在塊范圍內有效。因此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 // ^
作用范圍的唯一例外是函數和for循環,我們將在下面解釋。
在Solidity匯編中,只需要使用變量名就可以訪問局部變量,無論該變量是定義在匯編塊中,還是Solidity代碼中,不過變量必須是函數的局部變量:
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) } }
先看一下Solidity中循環的使用。下面的Solidity函數代碼中計算變量的倍數n次,其中value和n是函數的參數:
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) } }
類似于其他開發語言中的for循環,在Solidity匯編中,for循環也包含3個元素:
初始化:let i := 0
執行條件:lt(i, n)
,必須是函數風格表達式
迭代后續步驟:add(i, 1)
注意:for循環中變量的作用范圍略有不同。在初始化部分定義的變量在循環的其他部分都有效。
在Solidity匯編中實際上是沒有while循環關鍵字的,但是可以使用for循環實現同樣的功能:只要留空for循環的初始化部分和迭代后續步驟即可。
assembly { let x := 0 let i := 0 for { } lt(i, 0x100) { } { // 等價于:while(i < 0x100) x := add(x, mload(i)) i := add(i, 0x20) } }
Solidity內聯匯編支持使用if
語句來設置代碼執行的條件,但是沒有其他語言中的else
部分。
assembly { if slt(x, 0) { x := sub(0, x) } // Ok if eq(value, 0) revert(0, 0) // Error, 需要大括號 }
if語句強制要求代碼塊使用大括號,即使需要保護的代碼只有一行,也需要使用大括號。這和solidity不同。
如果需要在Solidity內聯匯編中檢查多種條件,可以考慮使用switch
語句。
EVM匯編中也有switch
語句,它將一個表達式的值于多個常量進行對比,并選擇相應的代碼分支來執行。switch
語句支持一個默認分支default
,當表達式的值不匹配任何其他分支條件時,將執行默認分支的代碼。
assembly { let x := 0 switch calldataload(4) case 0 { x := calldataload(0x24) } default { x := calldataload(0x44) } sstore(0, div(x, 2)) }
switch
語句有一些限制:
分支列表不需要大括號,但是分支的代碼塊需要大括號- 所有的分支條件值必須:1)具有相同的類型 2)具有不同的值- 如果分支條件已經涵蓋所有可能的值,那么不允許再出現default條件
assembly { let x := 34 switch lt(x, 30) case true { // do something } case false { // do something els } default { // 不允許 } }
也可以在Solidity內聯匯編中定義底層函數。調用這些自定義的函數和使用內置的操作碼一樣。
下面的匯編函數用來分配指定長度的內存,并返回內存指針pos:
assembly { function allocate(length) -> pos { pos := mload(0x40) mstore(0x40, add(pos, length)) } let free_memory_pointer := allocate(64) }
匯編函數的運行機制如下:
從堆棧提取參數
將結果壓入堆棧
和Solidity函數不同,不需要指定匯編函數的可見性,例如public或private,因為匯編函數僅在定義所在的匯編代碼塊內有效。
EVM操作碼可以分為以下幾類:
算數和比較操作
位操作
密碼學操作,目前僅包含keccak256
環境操作,主要指與區塊鏈相關的全局信息,例如blockhash
或coinbase
收款賬號
存儲、內存和棧操作
交易與合約調用操作
停機操作
日志操作
到此,相信大家對“Solidity內聯匯編怎么使用”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。