EVM系统深入研究

本文深入探讨了以太坊虚拟机(EVM)的架构、工作原理、指令集、执行过程、安全性和性能优化。EVM作为以太坊的核心组件,负责执行智能合约和处理交易。文章详细介绍了EVM的内存结构、存储布局、关键操作码,以及Solidity代码如何转换为EVM字节码并在以太坊上执行的过程,此外,还讨论了gas优化策略和智能合约安全性问题。

自从 2015 年推出以来,以太坊已成为区块链技术中最具影响力的平台之一。其核心组件之一是以太坊虚拟机(EVM),负责执行智能合约和处理交易。本文深入探讨了 EVM 的架构、工作原理、指令集、执行过程、安全性以及性能优化。

EVM 的基本概念

以太坊虚拟机(EVM)旨在执行以太坊区块链上的智能合约。智能合约是存储在区块链上的程序,由 EVM 执行以实现各种复杂的业务逻辑。EVM 的执行环境包括堆栈、内存、存储和 gas 等几个组件,这些组件协同工作以确保智能合约的正确执行。EVM 的主要特性包括:

  • 确定性执行:相同的输入必须产生相同的输出。
  • 隔离执行:合约执行必须完全隔离,不能访问其他进程。
  • Gas 机制:Gas 限制了计算资源的使用。
  • 图灵完备性:EVM 支持完整的计算能力。

EVM 架构

以下是 EVM 架构的组成部分:

  • 内存(Memory):一种动态分配的空间,在合约执行期间充当临时存储。它的大小从 0 开始,并根据需要扩展。
  • 存储(Storage):用于存储持久性合约数据。它映射到 32 字节的槽位。
  • 程序计数器(PC):跟踪要执行的下一条指令。
  • 堆栈(Stack):一种基于堆栈的结构(LIFO),最多可容纳 1024 个元素,每个元素大小为 32 字节,用于存储操作数和中间结果。
  • Gas:Gas 用于衡量计算资源的消耗。每个操作都消耗特定数量的 gas,以防止无限循环和资源滥用。

内存布局

以太坊内存是线性的和易失性的,这意味着数据不会在交易之间持久存在,并且仅在交易执行期间保留。虽然内存的成本随着其大小的增加而呈二次方增长,但它仍然比存储便宜。可以使用 MSTOREMLOAD 等操作来访问它。内存主要用于无法放入堆栈中的值,例如数组和字符串。

内存分配在以下 4 个槽中:

  • 0x00–0x3f (64 字节):用于哈希运算的临时空间。
  • 0x40–0x5f (32 字节):当前内存分配大小(也称为空闲内存指针)。
  • 0x60–0x7f (32 字节):用于初始化动态内存数组的零值槽,从不写入数据。
  • 初始可用内存指针位于 0x80

存储布局

在以太坊中,合约的状态变量紧凑地存储,并且多个值可能共享同一个存储槽。第一个状态变量存储在槽 0 中。小于 32 字节的变量可以打包到单个存储槽中,遵循以下规则:

  • 存储槽中的第一个项目以 低字节对齐(右对齐)存储。
  • 值类型仅使用其存储所需的字节。
  • 如果值类型不适合槽的剩余部分,则将其存储在下一个槽中。
  • 结构体和数组从新的存储槽开始,它们的项目紧密地打包在一起。
  • 结构体或数组之后的变量始终存储在新的存储槽中。

注意:对于使用继承的合约,状态变量的顺序由 C3 线性化顺序确定。这对于可升级合约来说是一个潜在的风险。

对于像 mappingbytes 这样的动态结构,存储布局在以下资源中有详细解释:https://docs.soliditylang.org/en/v0.8.29/internals/layout_in_storage.html#mappings-and-dynamic-arrays

EVM 操作码

EVM 指令集包含大约 150 个操作码,涵盖算术运算、堆栈运算、内存运算、存储运算等等。一些常见的操作码包括:

  • 算术运算ADD(加法)、MUL(乘法)、LT(小于比较)。
  • 堆栈运算PUSH(推入堆栈)、POP(从堆栈弹出)。
  • 内存运算MLOAD(从内存加载数据)、MSTORE(将数据存储到内存)。
  • 存储运算SLOAD(从存储加载数据)、SSTORE(将数据存储到存储)。
  • 跳转指令JUMP(无条件跳转)、JUMPI(条件跳转)。

更多操作码可以在以下链接找到:EVM Codes — Opcodes

EVM 执行过程

当用户发起一笔交易时,交易数据会被打包到一个区块中,经过矿工的验证和执行。一笔交易包含各种字段,如 noncegas pricegas limitto(接收者地址)、value(要发送的以太币数量)和 data(calldata)。Calldata 是与智能合约交互的关键部分,因为它经过加密,并且包含要调用的函数及其参数。

合约交互交易示例

to: 0x6b175474e89094c44da98b954eedeac495271d0f95271d0f地址
from: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
value: 0x0
data: 0x60fe47b10000000000000000000000000000000000000000000000000000000000010f2c
gasPrice: 500000
gasLimit: 210000
v:
r:
s:

高级交易流程

  1. EVM 检查交易的哈希负载(RLP 编码),并解码接收者、价值和负载等值。
  2. 验证交易的 vrs 值(这些值对应于 ECDSA 签名输出),以确保 nonce 和签名有效,并且与发送者的地址相关联。
  3. 创建一个空的内存空间和堆栈上下文来执行堆栈操作。
  4. EVM 按照程序计数器(PC)执行字节码中的每个操作码指令,按顺序处理每个操作码指令,并将结果存储在世界状态中。

从更深层次分析与智能合约的交互:

交易数据的前四个字节指定函数选择器(函数签名的哈希值),其余数据表示编码的参数。该示例演示了对合约的 set 方法的调用,编码为十六进制负载,如下所示:

0x60fe47b10000000000000000000000000000000000000000000000000000000000010f2c

0x60fe47b1 是函数 set () 的接口和参数编码

如果你的交易是智能合约的部署,则合约代码会被编译为字节码并存储在区块链上。

从 solidity 到 Bytecode

当使用 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

Gas 优化策略

基于 EVM 操作码,很明显,存储操作在 gas 方面非常昂贵。因此,优化这些操作对于最小化 gas 成本至关重要。有关详细的 gas 优化策略,请参阅文章

https://learnblockchain.cn/article/14215

智能合约安全

对 EVM 的更深入理解也有助于编写更安全的合约。智能合约中常见的安全问题在以下链接中讨论:

https://learnblockchain.cn/article/14214

  • 原文链接: blog.blockmagnates.com/d...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
blockmagnates
blockmagnates
江湖只有他的大名,没有他的介绍。