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 | 读Storage | 2100 |
| SSTORE | 写Storage | 20000/5000 |
| MLOAD | 读Memory | 3 |
| MSTORE | 写Memory | 3 |
合约调用
| 操作码 | 说明 | 上下文 |
|---|---|---|
| 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字节
选择器: 0xa9059cbbEVM执行流程
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) │
└─────────────────────────────────────────────────────────────────┘常见函数选择器速查
| 选择器 | 函数 | 用途 |
|---|---|---|
0xa9059cbb | transfer(address,uint256) | ERC20转账 |
0x095ea7b3 | approve(address,uint256) | ERC20授权 |
0x23b872dd | transferFrom(address,address,uint256) | 授权转账 |
0x70a08231 | balanceOf(address) | 查询余额 |
0x3593564c | execute(bytes,bytes[],uint256) | Uniswap V3 |
0x5ae401dc | multicall(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合约
- 合约阅读笔记