返回知识库
Day 4

EVM原理与Input Data解析

深入理解以太坊虚拟机架构、操作码、存储模型,学会解析交易Input Data

2025-01-14
EVM操作码StorageMemoryInput Data函数选择器

核心概念

什么是EVM?

一句话定义: EVM (Ethereum Virtual Machine) 是以太坊的"计算引擎",所有智能合约都在这个沙箱环境中执行。

类比理解:

Java程序 → JVM执行 → 跨平台运行
Solidity合约 → EVM执行 → 所有节点运行相同结果

为什么需要EVM?

  • 确定性: 相同输入必定产生相同输出,这是共识的基础
  • 隔离性: 合约在沙箱中运行,不能访问外部系统
  • 计量: 通过Gas限制计算资源,防止无限循环

EVM 架构详解

整体架构

┌─────────────────────────────────────────────────┐
│                    EVM 执行环境                   │
├─────────────────────────────────────────────────┤
│                                                 │
│   ┌─────────┐   ┌─────────┐   ┌─────────────┐   │
│   │  Stack  │   │ Memory  │   │   Storage   │   │
│   │  (栈)   │   │ (内存)  │   │   (存储)    │   │
│   │         │   │         │   │             │   │
│   │ 临时计算 │   │ 临时数据 │   │  永久状态   │   │
│   │ 最多1024│   │ 按字节寻址│   │ key-value  │   │
│   │  免费   │   │ 便宜     │   │   昂贵      │   │
│   └─────────┘   └─────────┘   └─────────────┘   │
│                                                 │
│   ┌─────────────────────────────────────────┐   │
│   │              Program Counter             │   │
│   │           (指向当前执行的操作码)            │   │
│   └─────────────────────────────────────────┘   │
│                                                 │
│   ┌─────────────────────────────────────────┐   │
│   │                Gas Counter               │   │
│   │            (剩余Gas,用完则回滚)           │   │
│   └─────────────────────────────────────────┘   │
│                                                 │
└─────────────────────────────────────────────────┘

代码执行流程

Solidity源码
    ↓ 编译器(solc)
字节码(Bytecode) ← 部署到链上存储
    ↓ 执行时
操作码(Opcodes) → EVM逐条执行
    ↓
状态变更 → 写入区块链

示例:

// Solidity
function add(uint a, uint b) public pure returns (uint) {
    return a + b;
}

编译后的字节码片段:

PUSH1 0x60   // 将0x60压入栈
PUSH1 0x40   // 将0x40压入栈
MSTORE       // 存储到内存
...
ADD          // 弹出栈顶两个数,相加,结果压回栈

三种数据存储

对比表

存储类型生命周期Gas成本用途
Stack单次操作最低(3 Gas)临时计算、局部变量
Memory单次调用中等函数参数、临时数组
Storage永久最高(20000 Gas)合约状态变量

Gas成本详解

操作类型          Gas消耗
─────────────────────────
PUSH (栈操作):     3 Gas
MSTORE (写内存):   3 Gas + 扩展成本
MLOAD (读内存):    3 Gas

SSTORE (写存储):
  - 新值:          20000 Gas
  - 更新:          5000 Gas
  - 清零:          获得退款

SLOAD (读存储):    2100 Gas

优化启示

这就是为什么:

  • 循环中避免频繁写Storage
  • 先在Memory中计算,最后写Storage
  • 清零Storage可以获得Gas退款
  • 读Storage也很贵,考虑缓存到Memory

常见操作码(Opcodes)

算术运算

操作码说明Gas
ADD加法3
SUB减法3
MUL乘法5
DIV除法5
MOD取模5
EXP指数10+

栈操作

操作码说明Gas
PUSH1-32压入1-32字节3
POP弹出栈顶2
DUP1-16复制栈元素3
SWAP1-16交换栈元素3

存储操作

操作码说明Gas
SLOAD读Storage2100
SSTORE写Storage20000/5000
MLOAD读Memory3
MSTORE写Memory3

合约调用

操作码说明上下文
CALL调用其他合约被调用合约的上下文
DELEGATECALL委托调用调用者的上下文(危险!)
STATICCALL只读调用不能修改状态
CREATE创建新合约-
CREATE2确定性创建可预测地址

CALL vs DELEGATECALL

CALL:
合约A调用合约B
→ msg.sender = A
→ 修改B的Storage

DELEGATECALL:
合约A委托调用合约B
→ msg.sender = 原始调用者
→ 修改A的Storage (使用B的代码!)

用途: 代理合约、可升级合约
风险: 如果B是恶意代码,会破坏A的状态

函数调用原理

Input Data 结构

当你调用合约函数时,交易的Input Data包含:

Input Data 结构:
┌──────────────────┬─────────────────────────────┐
│ 函数选择器(4字节) │     参数(ABI编码)            │
│   0xa9059cbb     │  000000000000...           │
└──────────────────┴─────────────────────────────┘

函数选择器计算

函数签名: transfer(address,uint256)
           ↓ keccak256哈希
哈希值: 0xa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b
           ↓ 取前4字节
选择器: 0xa9059cbb

EVM执行流程

1. 读取Input Data前4字节 → 0xa9059cbb
2. 与合约中所有函数选择器对比
3. 匹配到 transfer(address,uint256)
4. 解析后续参数(每个参数32字节)
5. 跳转到对应函数代码执行

链上实操:解析 Input Data

示例:ERC20 Transfer

Input Data:
0xa9059cbb
0000000000000000000000001234567890abcdef1234567890abcdef12345678
0000000000000000000000000000000000000000000000000000000005f5e100

拆解:
┌─────────────────────────────────────────────────────────────────┐
│ 0xa9059cbb                                                      │
│ ↑ 函数选择器 = transfer(address,uint256)                         │
├─────────────────────────────────────────────────────────────────┤
│ 0000000000000000000000001234567890abcdef1234567890abcdef12345678│
│ ↑ 参数1: address (32字节,左边补0)                                │
│ = 0x1234567890abcdef1234567890abcdef12345678                    │
├─────────────────────────────────────────────────────────────────┤
│ 0000000000000000000000000000000000000000000000000000000005f5e100│
│ ↑ 参数2: uint256 (32字节)                                        │
│ = 0x5f5e100 = 100000000 (如USDT是6位小数,即100 USDT)            │
└─────────────────────────────────────────────────────────────────┘

常见函数选择器速查

选择器函数用途
0xa9059cbbtransfer(address,uint256)ERC20转账
0x095ea7b3approve(address,uint256)ERC20授权
0x23b872ddtransferFrom(address,address,uint256)授权转账
0x70a08231balanceOf(address)查询余额
0x3593564cexecute(bytes,bytes[],uint256)Uniswap V3
0x5ae401dcmulticall(uint256,bytes[])批量调用

实操步骤

1. 打开 Etherscan: https://etherscan.io

2. 找一笔合约交易

3. 查看 Input Data

4. 点击 "Decode Input Data" 查看解码结果

5. 或去 https://openchain.xyz/signatures 手动查询选择器


世界状态(World State)

状态结构

以太坊的状态是一个巨大的映射:

World State (全局状态)
│
├── 0x123... (账户A - EOA)
│   ├── nonce: 5
│   ├── balance: 1.5 ETH
│   ├── codeHash: empty
│   └── storageRoot: empty
│
├── 0x456... (账户B - 合约)
│   ├── nonce: 1
│   ├── balance: 10 ETH
│   ├── codeHash: 0x789... (合约代码哈希)
│   └── storageRoot: 0xabc... (存储状态树根)
│
└── ... (更多账户)

状态转换

State(n) + Transaction → EVM执行 → State(n+1)

每个区块 = 一批状态转换
区块链 = 状态转换的历史记录

今日思考

问题1: 为什么Storage这么贵?

  • Storage数据永久存储在所有节点
  • 每个节点都要维护完整状态
  • 状态膨胀是区块链扩展性的核心挑战
  • 高成本激励开发者优化存储使用

问题2: DELEGATECALL有什么风险?

  • 被调用合约的代码在调用者的上下文执行
  • 可以修改调用者的Storage
  • 如果调用恶意合约,可能导致资产被盗
  • 代理合约必须谨慎设计

问题3: 如何优化合约Gas消耗?

  • 减少Storage读写,使用Memory缓存
  • 使用合适的数据类型(uint256最优)
  • 批量操作代替多次单独操作
  • 利用Storage清零退款机制

学习资源

视频教程

资源语言说明
EVM Deep Dive - Smart Contract Programmer英文EVM底层讲解
Opcodes Explained英文操作码详解

文档阅读

资源说明
ethereum.org EVM官方EVM文档
EVM Opcodes操作码交互式参考
Ethereum Yellow Paper技术规范(进阶)

工具网站

工具用途
evm.codes操作码参考和Playground
openchain.xyz/signatures函数选择器查询
Etherscan交易解析
Tenderly交易调试和模拟

面试题准备

Q: 什么是EVM?为什么需要它?

30秒版本:

EVM是以太坊虚拟机,是一个确定性的沙箱执行环境。所有节点运行相同的EVM,确保对同一交易产生相同结果,这是区块链共识的基础。它通过Gas机制限制计算资源,防止无限循环攻击。

2分钟版本:

  • 定义: 以太坊的计算引擎,执行智能合约字节码
  • 特性:
  • 确定性: 相同输入=相同输出
  • 隔离性: 沙箱环境,无法访问外部
  • 图灵完备: 可执行任意计算(受Gas限制)
  • 架构: 栈式结构,最大深度1024
  • 存储: Stack(临时)、Memory(调用内)、Storage(永久)
  • 意义: 使区块链不仅仅是账本,而是"世界计算机"

Q: Storage和Memory的区别?

30秒版本:

Storage是永久存储,写入成本高(20000 Gas),数据存在链上;Memory是临时存储,调用结束即销毁,成本低。优化合约时应尽量减少Storage操作,先在Memory计算,最后写入Storage。

可能追问:

  • 为什么Storage这么贵? → 永久存储在所有节点,状态膨胀问题
  • 如何优化? → 缓存到Memory,批量操作,利用清零退款

Q: 什么是函数选择器?

30秒版本:

函数选择器是函数签名的keccak256哈希的前4字节,用于标识调用哪个函数。例如transfer(address,uint256)的选择器是0xa9059cbb。EVM通过匹配选择器来路由函数调用。


明日预告

Day 5: Solidity基础(1) - 数据类型与函数

  • 值类型 vs 引用类型
  • 函数可见性(public/private/internal/external)
  • 读懂ERC20合约
  • 合约阅读笔记