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