前序博客有:
- 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。
部署以太坊智能合约为向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中,然后执行加法运算。
如以Remix中的Storage.sol合约为例:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?