加密日记【013】关于Assembly的一切
April 7th, 2023

Assembly是指使用汇编器转换为机器代码的任何低级编程语言。汇编语言与物理机或虚拟机相关联,因为它们实现了其指令集。一条指令只是告诉 CPU 执行一些基本任务,例如将两个数字相加

为什么Solidity中使用Assembly?

  • **细粒度控制(**汇编给了你更多的控制权来执行一些仅靠 Solidity 可能无法实现的逻辑。例如,指向特定的内存插槽。)

  • 降低gas成本

  • 增强功能

assembly基础

assembly { ... }出现在solidity内部叫内联汇编,{}之内的变量不能与外面进行访问,不同的内联汇编块不共享名称空间

demo

function add(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) // 将结果存储在内存中
         
        // 从内存地址 0x0 返回 32 个字节
        
        return(0x0, 32)           
    
    }
}

声明变量必须用let :=

let指令在幕后做了什么?

在 EVM 的内部工作中,let执行以下操作:

  1. 创建一个新的栈槽

  2. 新槽是为变量保留的

  3. 当到达块的末尾时,插槽将再次自动删除

文字也以与 Solidity 相同的方式编写。但是,字符串文字最多可以包含 32 个字符。

局部变量

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

Loops in Assembly

solidity

function for_loop_solidity(uint n, uint value) public pure returns(uint) {
         
    for ( uint i = 0; i < n; i++ ) {
        value = 2 * value;
    }
    return value;
}

Assembly

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

没有while字段,但是可以用for改造

assembly {
    let x := 0
    let i := 0
    for { } lt(i, 0x100) { } {   // while(i < 256), 100 (hex) = 256
        x := add(x, mload(i))
        i := add(i, 0x20)
    }
}

Conditional Statements in Assembly

if 必须要有{}框住主体

assembly {    if slt(x, 0) { x := sub(0, x) }  // Ok
            
    if eq(value, 0) revert(0, 0)    // Error, curly braces needed}

多个条件考虑switch

  • case列表不需要大括号,但case主体需要大括号。

  • 所有 case 值都需要具有 1) 相同的类型,以及 2) 不同的值。

  • 如果涵盖了表达式类型的所有可能值,则不允许出现default case。

assembly {
             
    let x := 34
             
    switch lt(x, 30)
    case true {
        // do something
    }
    case false {
        // do something els
    }
    default {
        // this is not allowed
    }
             
}
  • Solidity 支持 if { ... } else { ... } 语句、while、do 和 for 循环,但不支持 switch 语句。

  • 当比较的值与一种情况匹配时,控制流停止。它不会从一个case继续到下一个case

Function in Assembly

assembly {
    
    function allocate(length) -> pos {
        pos := mload(0x40)
        mstore(0x40, add(pos, length))
    }
    let free_memory_pointer := allocate(64)
}
  • 从堆栈中获取参数

  • 将结果入栈

  • 使用符号指定返回值->

  • 没有明确的return说法,只需在最终语句中将其分配给返回变量,如果您在汇编函数中编写return,它将停止整个执行上下文(内部消息调用),而不仅仅是当前的汇编函数

  • 关键字leave可以放在汇编函数体内的任何位置,以停止其执行流程并退出,该函数将返回最后分配给返回变量的任何值

opcodes

  • 操作码总是 从堆栈的顶部获取参数(在括号中给出) 。

  • 标有-(第二列)的操作码不会将项目压入堆栈。对于列表中的大多数,它们返回内存中的值

  • 所有其他操作码将项目压入堆栈(它们的*“返回”*值)。

  • 操作码标有FH和自 Frontier、Homestead、拜占庭和君士坦丁堡版本的以太坊以来BC存在。

  • mem[a...b)表示从 position 开始的内存字节数a(但不包括 position b

  • storage[p]表示位置 的存储内容p

返回多个值

assembly {
            
      function f() -> a, b {}
      let c, d := f()
            
}

函数式封装

// Functional style
mstore(0x80, add(mload(0x80), 3))

为了提高效率,EVM Assembly 将每个值都视为 256 位数字。

高阶位仅在必要时被清除(例如:在执行比较或写入内存之前)

Subscribe to 0x3c
Receive the latest updates directly to your inbox.
Nft graphic
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.
More from 0x3c

Skeleton

Skeleton

Skeleton