本文深入探讨了以太坊虚拟机(EVM)的架构、工作原理、指令集、执行过程、安全性和性能优化。EVM作为以太坊的核心组件,负责执行智能合约和处理交易。文章详细介绍了EVM的内存结构、存储布局、关键操作码,以及Solidity代码如何转换为EVM字节码并在以太坊上执行的过程,此外,还讨论了gas优化策略和智能合约安全性问题。
自从 2015 年推出以来,以太坊已成为区块链技术中最具影响力的平台之一。其核心组件之一是以太坊虚拟机(EVM),负责执行智能合约和处理交易。本文深入探讨了 EVM 的架构、工作原理、指令集、执行过程、安全性以及性能优化。
以太坊虚拟机(EVM)旨在执行以太坊区块链上的智能合约。智能合约是存储在区块链上的程序,由 EVM 执行以实现各种复杂的业务逻辑。EVM 的执行环境包括堆栈、内存、存储和 gas 等几个组件,这些组件协同工作以确保智能合约的正确执行。EVM 的主要特性包括:
以下是 EVM 架构的组成部分:
以太坊内存是线性的和易失性的,这意味着数据不会在交易之间持久存在,并且仅在交易执行期间保留。虽然内存的成本随着其大小的增加而呈二次方增长,但它仍然比存储便宜。可以使用 MSTORE
和 MLOAD
等操作来访问它。内存主要用于无法放入堆栈中的值,例如数组和字符串。
内存分配在以下 4 个槽中:
在以太坊中,合约的状态变量紧凑地存储,并且多个值可能共享同一个存储槽。第一个状态变量存储在槽 0 中。小于 32 字节的变量可以打包到单个存储槽中,遵循以下规则:
注意:对于使用继承的合约,状态变量的顺序由 C3 线性化顺序确定。这对于可升级合约来说是一个潜在的风险。
对于像 mapping
或 bytes
这样的动态结构,存储布局在以下资源中有详细解释:https://docs.soliditylang.org/en/v0.8.29/internals/layout_in_storage.html#mappings-and-dynamic-arrays
EVM 指令集包含大约 150 个操作码,涵盖算术运算、堆栈运算、内存运算、存储运算等等。一些常见的操作码包括:
ADD
(加法)、MUL
(乘法)、LT
(小于比较)。PUSH
(推入堆栈)、POP
(从堆栈弹出)。MLOAD
(从内存加载数据)、MSTORE
(将数据存储到内存)。SLOAD
(从存储加载数据)、SSTORE
(将数据存储到存储)。JUMP
(无条件跳转)、JUMPI
(条件跳转)。更多操作码可以在以下链接找到:EVM Codes — Opcodes。
当用户发起一笔交易时,交易数据会被打包到一个区块中,经过矿工的验证和执行。一笔交易包含各种字段,如 nonce
、gas price
、gas limit
、to
(接收者地址)、value
(要发送的以太币数量)和 data
(calldata)。Calldata 是与智能合约交互的关键部分,因为它经过加密,并且包含要调用的函数及其参数。
合约交互交易示例:
to: 0x6b175474e89094c44da98b954eedeac495271d0f95271d0f地址
from: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
value: 0x0
data: 0x60fe47b10000000000000000000000000000000000000000000000000000000000010f2c
gasPrice: 500000
gasLimit: 210000
v:
r:
s:
高级交易流程:
v
、r
和 s
值(这些值对应于 ECDSA 签名输出),以确保 nonce 和签名有效,并且与发送者的地址相关联。从更深层次分析与智能合约的交互:
交易数据的前四个字节指定函数选择器(函数签名的哈希值),其余数据表示编码的参数。该示例演示了对合约的 set 方法的调用,编码为十六进制负载,如下所示:
0x60fe47b10000000000000000000000000000000000000000000000000000000000010f2c
0x60fe47b1 是函数 set () 的接口和参数编码
如果你的交易是智能合约的部署,则合约代码会被编译为字节码并存储在区块链上。
当使用 Solidity 编写智能合约时,需要将其编译为字节码才能在以太坊网络上运行。考虑以下简单的 Solidity 合约:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.23;
contract Simple {
uint256 public val1;
uint256 public val2;
constructor() public {
val2 = 3;
}
function set(uint256 _param) external {
val1 = _param;
}
}
那么,这段代码是如何转换为字节码并在以太坊上执行的呢?
编译器从 .sol
(Solidity)文件中获取代码,并生成一个 .bin
(二进制)文件和一个 ABI(应用程序二进制接口)文件。.bin
文件包含编译的字节码,ABI 文件提供智能合约接口。在上面的示例合约中,没有构造函数参数。但是,如果合约有构造函数参数,则此一次性初始化字节码将被添加到合约部署交易的 calldata 中。此字节码与存储在合约存储中的字节码(运行时字节码)不同,因为它还包含构造函数字节码。
编译后的字节码用于将合约部署到以太坊,ABI 用作与已部署合约交互的接口。构造函数字节码在合约部署期间执行,它初始化合约的状态。部署后,合约的运行时字节码用于所有后续交互。
6080604052348015600e575f80fd5b506101438061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c806360fe47b1146100385780636d4ce63c14610054575b5f80fd5b610052600480360381019061004d91906100ba565b610072565b005b61005c61007b565b60405161006991906100f4565b60405180910390f35b805f8190555050565b5f8054905090565b5f80fd5b5f819050919050565b61009981610087565b81146100a3575f80fd5b50565b5f813590506100b481610090565b92915050565b5f602082840312156100cf576100ce610083565b5b5f6100dc848285016100a6565b91505092915050565b6100ee81610087565b82525050565b5f6020820190506101075f8301846100e5565b9291505056fea2646970667358221220fe2a712e6758ca6e067fd552b99e33f169a13afa9b0c54fdd2e92518f3aa766764736f6c63430008190033
此字节码实际上对应于 EVM 的操作码
PC: 0x0, opcode: PUSH1 0x80
PC: 0x2, opcode: PUSH1 0x40
PC: 0x4, opcode: MSTORE
PC: 0x5, opcode: CALLVALUE
PC: 0x6, opcode: DUP1
PC: 0x7, opcode: ISZERO
PC: 0x8, opcode: PUSH2 0x0010
PC: 0xb, opcode: JUMPI
PC: 0xc, opcode: PUSH1 0x00
PC: 0xe, opcode: DUP1
PC: 0xf, opcode: REVERT
PC: 0x10, opcode: JUMPDEST
这里,0x4
表示分配一个空的内存指针,分配 128 (0x80) 字节的内存并将指针移动到第 64 (0x40) 字节的开头。换句话说,memory[0x40] = 0x80
,这意味着从 0x80
开始的内存可供 Solidity 代码使用。
要了解更多详细信息并探索代码和字节码之间的转换,我们可以参考网站 evm.codes。
基于 EVM 操作码,很明显,存储操作在 gas 方面非常昂贵。因此,优化这些操作对于最小化 gas 成本至关重要。有关详细的 gas 优化策略,请参阅文章
https://learnblockchain.cn/article/14215
对 EVM 的更深入理解也有助于编写更安全的合约。智能合约中常见的安全问题在以下链接中讨论:
https://learnblockchain.cn/article/14214
- 原文链接: blog.blockmagnates.com/d...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!