本文参考:https://learnblockchain.cn/article/675

碎片

  1. 汇编可以直接于EVM进行交互

  2. 汇编相当于直接操作OPCODE,每个OPCODE都会有一个对应的汇编语法,比如:mstore是汇编,MSTORE是OPCODE一样

  3. 以太坊一共有:144个操作吗,即指令集,这些指令杯solidity抽象之后,用来写智能合约(画个图)

  4. PC(程序计数器)和SP(堆栈指针)

  5. 指令执行时,通常会先从堆栈弹出一个或多个值作为参数,再将执行结果压回堆栈,这通常被称为逆波兰表示法(Reverse Polish Notatioin)

    a + b
    a b add
    

使用汇编的好处

更加细颗粒度的控制:

有些操作合约solidity语法无法直接支持,例如:指向特定的内存slot,很多标准库的实现都离不开汇编,例如:

  1. bytes-utils:https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol
  2. stringutils:https://github.com/Arachnid/solidity-stringutils/blob/master/src/strings.sol

节约gas

 function addAssembly(uint x, uint y) public pure returns (uint) {
      assembly {
          // Add some code here
          let result := add(x, y)
          mstore(0x0, result)
          return(0x0, 32)
      }
  }

  function addSolidity(uint x, uint y) public pure returns (uint) {
      return x + y;
  }

增强功能

解释汇编的强大之处:

两种汇编

  1. 内联汇编(inline assembley):可以在内部solidity源代码中使用。
  2. 独立汇编(standalone Assembly):可以单独使用,无需solidity。

汇编语法

使用时需要注意:每个操作码是有参数的,例如指定内存地址,起始位置,偏移量等,这些在solidity中不需要指定。

1. 引入汇编

assembly {
 // some assembly code here
}

在assembly块内的开发语言为Yul,且多个代码块之前的变量不共享,各自独立。

assembly { 
    let x := 2
}

assembly {
    let y := x          // Error
}
// DeclarationError: identifier not found
// let y := x
// ^

2. 简单汇编示例

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字节
 }
}

详解:

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

3. 定义变量与赋值

使用let定义变量,使用 := 进行赋值,自动初始化为0

assembly {
    let x := 7 
     let y := add(x, 3)
  // 对从0x00开始,20个字节的内容进行hash处理
     let z := add(keccak256(0x0, 0x20), div(slength, 32)) 
     let n  // 自动初始化为 n = 0
}

4.let指令的作用

  1. 创建一个新的堆栈槽位
  2. 为变量保留该槽位
  3. 当到达代码块结束时,自动销毁该槽位(因此在外部block无法使用这个变量)

5. 汇编的注释

单行注释//

多行注释/**/

6. 汇编中的字面量

字符串字面量最多包含32字符。(每个字符1byte,所以最大为32byte,正好时一个槽位)

assembly { 
 let a := 0x123             // 16进制
 let b := 42                // 10进制
 let c := "hello world"     // 字符串

 let d := "very long string more than 32 bytes" // 超长字符串,出错!
}

// TypeError: String literal too long (35 < 32)
// let d := "really long string more than 32 bytes"
//

7. 块和作用域

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
// ^