溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Solidity內(nèi)聯(lián)匯編怎么使用

發(fā)布時(shí)間:2021-12-07 15:14:07 來(lái)源:億速云 閱讀:236 作者:iii 欄目:互聯(lián)網(wǎng)科技

本篇內(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í)并加以利用。

2、以太坊虛擬機(jī)和堆棧結(jié)構(gòu)機(jī)器

以太坊虛擬機(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):

Solidity內(nèi)聯(lián)匯編怎么使用

棧虛擬機(jī)將所有的操作數(shù)保存在棧上,關(guān)于棧虛擬機(jī)的詳細(xì)信息可以參考stack machine 基礎(chǔ)

3、堆棧結(jié)構(gòu)機(jī)器的操作碼

為了能夠解決實(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

4、在Solidity合約中使用內(nèi)聯(lián)匯編

可以在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)          
  }
}

5、Solidity匯編中的變量定義與賦值

在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            
}

6、Solidity匯編中l(wèi)et指令的運(yùn)行機(jī)制

在EVM的內(nèi)部,let指令執(zhí)行如下任務(wù):

  • 創(chuàng)建一個(gè)新的堆棧槽位

  • 為變量保留該槽位

  • 當(dāng)?shù)竭_(dá)代碼塊結(jié)束時(shí)自動(dòng)銷毀該槽位

因此,使用let指令在匯編代碼塊中定義的變量,在該代碼塊外部是無(wú)法訪問(wèn)的。

7、Solidity匯編中的注釋

在Yul匯編中注釋的寫法和Solidity一樣,可以使用單行注釋//或多行注釋/* */。例如:

assembly {     
  // single line comment

  /*
    Multi
    line
    comment
  */
}

8、Solidity匯編中的字面量

在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ò)誤!
}

9、Solidity匯編中的塊和作用范圍

在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),我們將在下面解釋。

10、在Solidity匯編中使用函數(shù)的局部變量

在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)
  }
}

11、在Solidity匯編中使用for循環(huán)

先看一下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)的其他部分都有效。

12、在Solidity匯編中使用while循環(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)
  }
}

13、在Solidity匯編中使用if語(yǔ)句

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ǔ)句。

14、在Solidity匯編中使用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 {
      // 不允許
  }             
}

15、在Solidity匯編中使用函數(shù)

也可以在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)有效。

16、Solidity匯編中的操作碼

EVM操作碼可以分為以下幾類:

  • 算數(shù)和比較操作

  • 位操作

  • 密碼學(xué)操作,目前僅包含keccak256

  • 環(huán)境操作,主要指與區(qū)塊鏈相關(guān)的全局信息,例如blockhashcoinbase收款賬號(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í)!

向AI問(wèn)一下細(xì)節(jié)

免責(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)容。

AI