在用Solidity开发以太坊智能合约时,使用汇编可以直接与EVM交互,降低 gas开销成本,更精细的控制智能合约的行为,因此值得Solidity开发者学习 并加以利用。本文是Solidity汇编开发的简明教程,旨在帮助你快速熟悉 如何在Solidity智能合约代码中嵌入汇编代码。

以太坊教程链接: Dapp入门 | 电商Dapp实战 | Token实战 | Php对接 | Java对接 | Python对接 | C#对接 | Dart对接

2、以太坊虚拟机和堆栈结构机器

以太坊虚拟机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 基础

3、堆栈结构机器的操作码

为了能够解决实际问题,栈结构机器需要实现一些额外的指令,例如 ADD、SUBSTRACT等等。指令执行时通常会先从堆栈弹出一个或多个值作为参数, 再将执行结果压回堆栈。这通常被称为逆波兰表示法(RPN:Reverse Polish Notation):

a + b      // 标准表示法Infixa b add    // 逆波兰表示法RPN

4、在Solidity合约中使用内联汇编

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

5、Solidity汇编中的变量定义与赋值

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

6、Solidity汇编中let指令的运行机制

在EVM的内部,let指令执行如下任务:

  • 创建一个新的堆栈槽位
  • 为变量保留该槽位
  • 当到达代码块结束时自动销毁该槽位

因此,使用let指令在汇编代码块中定义的变量,在该代码块 外部是无法访问的。

7、Solidity汇编中的注释

在Yul汇编中注释的写法和Solidity一样,可以使用单行注释// 或多行注释/* */。例如:

assembly {       // single line comment  /*    Multi    line    comment  */}

8、Solidity汇编中的字面量

在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" // 超长字符串,错误!}

9、Solidity汇编中的块和作用范围

在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循环,我们将在下面解释。

10、在Solidity汇编中使用函数的局部变量

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

11、在Solidity汇编中使用for循环

先看一下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循环中变量的作用范围略有不同。在初始化部分定义的变量 在循环的其他部分都有效。

12、在Solidity汇编中使用while循环

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

13、在Solidity汇编中使用if语句

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语句。

14、在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 {      // 不允许  }             }

15、在Solidity汇编中使用函数

也可以在Solidity内联汇编中定义底层函数。调用这些自定义的函数 和使用内置的操作码一样。

下面的汇编函数用来分配指定长度的内存,并返回内存指针pos:

assembly {      function allocate(length) -> pos {    pos := mload(0x40)    mstore(0x40, add(pos, length))  }      let free_memory_pointer := allocate(64)}

汇编函数的运行机制如下:

  • 从堆栈提取参数
  • 将结果压入堆栈

和Solidity函数不同,不需要指定汇编函数的可见性,例如public或private, 因为汇编函数仅在定义所在的汇编代码块内有效。

16、Solidity汇编中的操作码

EVM操作码可以分为以下几类:

  • 算数和比较操作
  • 位操作
  • 密码学操作,目前仅包含keccak256
  • 环境操作,主要指与区块链相关的全局信息,例如blockhash或coinbase收款账号
  • 存储、内存和栈操作
  • 交易与合约调用操作
  • 停机操作
  • 日志操作

详细的操作码可以查看Solidity文档。


原文链接:http://blog.hubwiz.com/2020/02/15/solidity-assembly-tutorial/

汇编 div_Solidity汇编开发简明教程相关推荐

  1. webgame开发简明教程

    webgame开发简明教程(1) 研究所有点缺乏资料啊.讨论的问题怪怪的.只有很少是能用到的. 一.简单的程序框架. webgame程序构成: 三大部分. 第一是数据流程.第二是程序.第三是美术. 其 ...

  2. Java手机游戏开发简明教程 (SunJava开发者认证程序员 郎锐)

    原文发布时间为:2008-07-30 -- 来源于本人的百度文章 [由搬家工具导入] Java手机游戏开发实例简明教程 (SunJava开发者认证程序员 郎锐) 一.手机游戏编写基础 1.手机游戏设计 ...

  3. Mixin 开发简明教程 1:写个机器人

    我是 @lyric,本教程应 Mixin COO @薄荷 邀约而写,旨在向区块链领域以外的工程师们介绍如何以依托 Mixin Network,用最简单的方式开发自己的区块链应用程序. 本文是系列教程的 ...

  4. mixin机器人java开发_Mixin 开发简明教程 1:写个机器人

    作者:Lyric  编辑:比特汪 本文已通过PRESSone合约购买个人授权许可(非排他性),可以点击阅读原文查看. 我是 @lyric,本教程应 Mixin COO @薄荷 邀约而写,旨在向区块链领 ...

  5. ActiveMQ开发简明教程

    资源链接 ActiveMQ入门实例 activemq-5141-release下载 入门文档(英文,官方) 图解ActiveMQ 概念 ActiveMQ特性 ActiveMQ特性详细介绍 Active ...

  6. Pico Neo3使用Unity开发简明教程

    前言 项目开发需要,最近开始整Pico VR,Pico被字节收编了,入局元宇宙,估计会有不错的应用和发展.简单使用了下,与Quest2对比,最大便利自然是网络环境.其他的,就不说了.这里重点是开发流程 ...

  7. GCC编译器简明教程(Linux下C语言开发环境的搭建)

    GCC编译器简明教程(Linux下C语言开发环境的搭建) 市面上常见的Linux都是发行版本,典型的Linux发行版包含了Linux内核.桌面环境(例如GNOME.KDE.Unity等)和各种常用的必 ...

  8. 嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十一)驱动进化之路:设备树的引入及简明教程

    文章目录 1.驱动进化之路:设备树的引入及简明教程 1.1 设备树的引入与作用 1.2 设备树的语法 1.2.1 Devicetree格式 1.2.1.1 DTS文件的格式 1.2.1.2 node的 ...

  9. 安卓python3开发环境搭建,Python3开发环境搭建简明教程

    Python3开发环境建立简明教程 ★ 「锐玩道」原文链接 " 剁手得很决绝的今日 那就来份平平无奇的教程吧 (作为 Scrapy 系列的开篇) Python 环境设备 下载 Python ...

最新文章

  1. 13个你一定要知道的PyTorch特性
  2. 我的Java开发学习之旅------JAVA 笔记ClassLoader.getResourceAsStream() 与 Class.getResourceAsStream()的区别...
  3. 学习笔记-nil NULL NSNull Nil的区别
  4. java之面向对象知识体系_JAVA基础知识总结5(面向对象特征之一:继承)
  5. 在配置静态IP的时候遇到 :bringing up interface eth0 : error unknown connection
  6. 如何开启php socket,如何用php实现websocket?
  7. 计算机组成原理—Cache和主存的映射模式
  8. 【Leetcode】Remove Duplicates from Sorted Array II
  9. Java注解之自定义注解
  10. 【error】error: failed to push some refs to ‘远程仓库地址‘ git报错解决
  11. pcie总线连接两台电脑_PCIe总线学习笔记(一、PCI基础知识简介)(转载)
  12. 计算机网络的核心概念
  13. 利用matlab来设计FIR滤波器参数
  14. C++ 求解最小公倍数
  15. 史上最全场景文字识别资源汇集(56篇重要论文 + 20 个开源代码 + 330 个实验结果 + 1882个统计信息)...
  16. 磨金石教育设计干货分享|20个海报设计小技巧,果断打包带走
  17. 我知道的风俗 和我喜欢的节日
  18. 赛龙代小权终审无罪释放,重燃创业之心
  19. php接口内session,php4的session功能评述(一)
  20. 【转载】Python3.6安装报错 configure: error: no acceptable C compiler found in $PATH

热门文章

  1. 如何理解signal函数声明
  2. WaitForSingleObject
  3. Spring泛型依赖注入
  4. Html5实践之EventSource
  5. php实现斐波那契数列
  6. 跨服务器Session共享的四种方法
  7. Linux的僵尸进程产生原因及解决方法
  8. 监控操作系统和服务器,LoadRunner如何监控不同操作系统的服务器?
  9. mysql 逻辑处理_mysql 逻辑查询处理流程
  10. python自定义全局异常_flask中主动抛出异常及统一异常处理代码示例