简化EVM世界启发你-合约编译成字节码及EVM执行过程

上一篇中初步介绍了一下EVM的结构,这篇文章主要是介绍solidity高级语言编写的智能合约编译成字节码过程及EVM执行过程

你真的理解了EVM吗?简化EVM世界启发你

  在这之前,为什么理解EVM对我们来说很重要:

1、solidity高级语言编写的智能合约是如何在EVM上执行的

2、理解EVM是如何组织数据、存储和操作的

3、如何编写出更优Gas的智能合约

图片

    使用solidity开发出来的合约(人能够理解的)会被编译成上图中右则的字节码(它是一种用16进制表示的编码,是EVM能够理解的语言),EVM在执行的时候实际上是把字节码翻译成上图左边的带有操作指令的汇编码。在EOA在调用合约时,EVM按照解释出来的操作指令顺序执行,直到结束或者遇到错误后停止或者gas不足后中断。

    对于人来说我们可以理解一条条的语句,对于EVM来说是怎么理解要执行的那串东西?我们知道EVM是一个虚拟机堆栈机,它是一个运算虚拟机器,只要把要执行的东西丢进去它就能给出对应的结果。

图片

    EVM之所以能理解那一串操作,是人为设置了一些预定义指令叫做操作码,有了这个操作码之后,EVM就知道把操作数放入哪些存储位置和如何操作了。

图片

使用1个字节定义了最多256个操作指令,具体可以参考

  • EIP-150 操作码 Gas 成本

定义的操作指令及花费情况

图片

或者到 https://www.evm.codes/?fork=shanghai这里看

图片

接下来通过一个简单智能合约来理解如何从高级语言到字节码再到解析成操作码的过程

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract Test{
    uint256 i;

    function setI() external {
        i = 1;
    }
}

我这里使用的是remix,可能通过 下面的“编译详情”或者“Bytecode”复制字节码

图片

 6080604052348015600e575f80fd5b50606a80601a5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c80636322ce3814602a575b5f80fd5b603260015f55565b00fea2646970667358221220d94b4a3dd0484e9634e33c01d93beb7b61eebac191ef0cb1e31f75ce427b1e4a64736f6c63430008180033

也可以使用命令行的方式来编译

MACOS安装地址:https://docs.soliditylang.org/en/latest/installing-solidity.html#macos-packages

brew updatebrew upgradebrew tap ethereum/ethereumbrew install solidity

安装完后使用的版本是最新的 0.8.24

图片

solc命令的参数超多,我这里就不贴出来了

solc --help

solc --bin --asm Test.sol

======= contracts/Test.sol:Test =======
EVM assembly:
    /* "contracts/Test.sol":70:154  contract Test{... */
  mstore(0x40, 0x80)
  callvalue
  dup1
  iszero
  tag_1
  jumpi
  0x00
  dup1
  revert
tag_1:
  pop
  dataSize(sub_0)
  dup1
  dataOffset(sub_0)
  0x00
  codecopy
  0x00
  return
stop

sub_0: assembly {
        /* "contracts/Test.sol":70:154  contract Test{... */
      mstore(0x40, 0x80)
      callvalue
      dup1
      iszero
      tag_1
      jumpi
      0x00
      dup1
      revert
    tag_1:
      pop
      jumpi(tag_2, lt(calldatasize, 0x04))
      shr(0xe0, calldataload(0x00))
      dup1
      0x6322ce38
      eq
      tag_3
      jumpi
    tag_2:
      0x00
      dup1
      revert
        /* "contracts/Test.sol":105:152  function setI() external {... */
    tag_3:
      tag_4
      tag_5
      jump      // in
    tag_4:
      stop
    tag_5:
        /* "contracts/Test.sol":144:145  1 */
      0x01
        /* "contracts/Test.sol":140:141  i */
      0x00
        /* "contracts/Test.sol":140:145  i = 1 */
      dup2
      swap1
      sstore
      pop
        /* "contracts/Test.sol":105:152  function setI() external {... */
      jump      // out

    auxdata: 0xa26469706673582212201305b3c86700efce9739e3838b2e286fa6189828eccafda7c7db4c84f32e965564736f6c63430008180033
}

Binary:
6080604052348015600e575f80fd5b50607180601a5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c80636322ce3814602a575b5f80fd5b60306032565b005b60015f8190555056fea26469706673582212201305b3c86700efce9739e3838b2e286fa6189828eccafda7c7db4c84f32e965564736f6c63430008180033

上面那部份我加入了--asm参数,就是让它生成 汇编码方看它是怎么解释的

最后那一段 Binary 实际就是字节码

最后通过graph图来看一下字节码最后的指令结构图

图片

https://www.evm.codes/playground?fork=shanghai

也提供了方便的操作

图片

我们先看一下字节码前面的 60 80 60 40   实际对应的就是opcodes里的 

push1 80 push1 40

图片

图片

60或PUSH1操作码告诉我们合约将1 字节大小的值添加到堆栈中。(当您添加2字节大小的值时,需要使用PUSH2或61)

[ 60 80 ]

在第一个操作中,我们使用操作码60 或 PUSH1将值 80 加载到堆栈中。

[ 60 40 ]

在第二个操作中,我们使用代码60 或 PUSH1将值 40 加载到堆栈中。

我们可以 evm.codes 中尝试执行一个代码

点击右上角的 step into时,把80推入了堆顶

图片

再点一次是把40推到栈顶

图片

执行完 MSTORE指令后

图片

上面三个步实际是执行了操作指令MSTORE(0X40,0X80),这个指令的作用就是偏移64个字节的地方保存值80(16进制值)

图片

计算了一下刚好是 192位,每2位代表的是1个字节,就是96个字节(偏移64个字节+80的存的是32个字节为一个slot =96)

现在回头看一下我们之前写的合约

i = 1;

字节码 60015f5556 对应的操作码指令

tag_5:        /* "contracts/Test.sol":144:145  1 */      0x01        /* "contracts/Test.sol":140:141  i */      0x00        /* "contracts/Test.sol":140:145  i = 1 */      dup2      swap1      sstore      pop        /* "contracts/Test.sol":105:152  function setI() external {... */ 
     jump      // out

图片

运行一下,首先把01压进栈

图片

然后把0压进栈顶

图片

最后把01值存储进STORAGE

多出两个东西,一个是slot,这个是槽的位置,上面的contract暂时不解,后面理解了再补回来

图片

总结:

  1、我们通过solidity编写智能合约后,最终会被编绎成字节码,字节码在编译的过程已经加入了EVM定义的操作码;

2、操作码定义占了一个字节,总共支持256个指令,实际上目前只用到了一部份

3、EVM在执行的指令时,通过指令的意义把操作数存入对应的存储类型中

  • 45
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值