您当前的位置: 首页 > 

mutourend

暂无认证

  • 8浏览

    0关注

    661博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

剖析Solidity合约创建EVM bytecode

mutourend 发布时间:2022-10-17 16:29:10 ,浏览量:8

1. 引言

前序博客有:

  • Ethereum EVM简介
  • 揭秘EVM Opcodes

在以太坊中,当合约创建时,init code将作为交易的一部分发送,然后返回该合约的实际bytecode——runtime code。详细可参看以太坊黄皮书第7章。

当交易中的recipient地址为空(即0)时,该交易为创建合约交易:

  • 创建合约交易中可包含value值,即创建合约的同时也给新创建的合约转账(此时,Solidity合约的构造函数需标记payable关键字)。
  • 执行交易中的init code,返回存储在新创建合约的bytecode(runtime code)。【返回用到RETURN opcode,从虚拟机memory取output,相应的offset取决于stack的top值,相应的length取决约stack的第二个top值。】

若合约有构造函数,则会在copy和return runtime code之前,执行该构造函数,详细可参看Solidity源码的编译处理流程:https://github.com/ethereum/solidity/blob/ccdc11ea5b7b11cfcc3f01f7b7aaba79a116fc0e/libsolidity/codegen/ContractCompiler.cpp#L174

以太坊EVM为stack machine,即所有的操作都是从stack中pop参数,然后经operation后,再将结果push回stack中。每个stack item均为32 byte word,使用big endian notation。

1.1 为何需要init code呢?

答案是:智能合约的构造函数仅执行一次,由于其后续不再执行,无需将构造函数存储在链上,因此,将构造函数的执行写在init code中,而不是runtime code中。

此外,用户可在创建合约时给合约发送native currency,因此有必要验证该合约接收了native currency——这种检查在部署时运行,在创建合约时,若创建者发送native currency 而 该合约无法接收native currency,则该合约创建将被revert。

init code中为在部署时执行的完整脚本。

init code负责准备合约并返回runtime code,而runtime code为后续每次触发交易时所执行的bytecode。

init code可:

  • 可根据需要对合约地址状态做任何修改(如初始化某些状态变量)。
  • 将runtime code放入memory某处。
  • 将runtime code的length推入stack。
  • 将runtime code在memory中的offset值(即在memory中的起始地址)推入stack。
  • 执行RETURN statement。
2. runtime code VS init code

部署以太坊智能合约为向null地址发送data payload,该data由2部分组成:

  • 1)runtime code:调用合约时,EVM所执行的代码。
  • 2)init code:用于设置(构造)合约,返回的runtime code会存储在链上。【合约创建时init code的目的为,将runtime code返回给EVM并将runtime code存储在链上。】
{
  "to": null,
  "value": 0,
  "data": ""
}

runtime code,通常在区块浏览器上看到的就是合约的runtime code。每次调用合约时,EVM会执行runtime code。以下合约为将数字2和4相加,将结果返回:

60 02 // PUSH1 2 - Push 2 on the stack
60 04 // PUSH1 4 - Push 4 on the stack
01 // ADD - Add stack[0] to stack[1]

60 00 // PUSH1 0 - Push 0 on the stack (destination in memory)
53 // MSTORE - Store result to memory

60 20 // PUSH1 32 - Push 32 on the stack (length of data to return)
60 00 // PUSH1 00 - Push 0 on the stack (location in memory)
F3 // Return

注意,上述合约的长度为13个字节。 接下来,需要使用 init code 将上述合约部署到链上:

  • 1)将合约的runtime code 复制到 memory中:【注意,交易data中,runtime code在init code之后,需要指定复制的位置】
    60 0D // PUSH1 13 (The length of our runtime code)
    60 0C // PUSH1 12 (The position of the runtime code in the transaction data)
    60 00 // PUSH1 00 (The destination in memory)
    39 // CODECOPY
    
  • 2)将在memory中的合约runtime code 返回:
    60 0D // PUSH1 13 (The length of our runtime code)
    60 00 // PUSH1 00 (The memory location holding our runtime code)
    F3 // RETURN
    

完整的合约创建交易data为:

0x600D600C600039600D6000F3600260040160005360206000F3
-------^init code^-------|------^runtime code^-----

仍以上述合约为例,改为合约构造函数具有参数2和4,则相应的data格式为:【构造函数的参数附加在runtime code之后】

{
  "to": null,
  "value": 0,
  "data": "0000000200000004"
                                  // param1^|param2^
}

则:

  • 1)init code中:会将这些参数CODECOPY到内存中。
  • 2)通过SSTORE将这些参数持久化存储到合约状态中。
  • 3)runtime code中:将SLOAD加载这些合约状态内的数字(2和4)到stack中,然后执行加法运算。
3. bytecode VS deployedBytecode

如以Remix中的Storage.sol合约为例:

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0             
关注
打赏
1664532908
查看更多评论
0.0406s