ETHEREUM VIRTUAL MACHINE

EVM以太坊虚拟机 —— 驱动"世界计算机"的确定性执行内核。每一笔交易,都是一次被 Gas 精确计量的状态跃迁。

σ′Υ(σ, T) 状态转换函数 Υ:给定当前世界状态 σ 与交易 T,EVM 逐条执行字节码,产出新状态 σ′
栈 Stack 内存 Memory 存储 Storage Gas 燃料 只读输入
STACK · 256-BIT · DETERMINISTIC
01 — 本质

EVM 是什么

它不是一台物理机器,而是一份被网络中每个节点严格复现的规范。同样的输入,在任何机器上都产出分毫不差的输出——这正是去中心化共识的前提。

架构

栈式虚拟机

没有寄存器。所有运算在一个后进先出(LIFO)的栈上完成,最深 1024 项,每项都是 256 位字。

可预测性

完全确定性

没有随机、没有系统时间、没有网络 I/O。相同状态与交易永远得到相同结果,节点才能达成共识。

计算能力

准图灵完备

逻辑上可计算任意程序,但每一步都消耗 Gas。Gas 耗尽即停机——用经济手段驯服了停机问题。

计量

Gas 计量

每个操作码明码标价。计算、存储、内存扩展都要付费,从根本上杜绝了资源滥用与无限循环。

隔离

沙箱执行

合约只能触及自己的存储与被显式传入的数据,无法访问宿主文件系统、进程或其他合约的私有状态。

字长

256 位原生字

字宽为 32 字节,天然契合 Keccak-256 哈希与椭圆曲线密码学,让链上加密运算成为一等公民。

02 — 定位

从源码到状态

EVM 处在整条流水线的中央:高级语言编译成字节码,EVM 执行字节码,执行结果落地为新的世界状态。它是"执行"这一环的唯一裁判。

Source

高级语言

Solidity / Vyper / Yul 编写的合约逻辑,面向开发者。

Compile

编译器

solc 等把源码降级为 EVM 操作码序列。

Bytecode

字节码

部署上链的十六进制指令流,如 60 80 60 40…

Execute

EVM

逐条取指、解码、执行,读写栈/内存/存储。

State

世界状态

余额、存储、代码的更新落入 σ′,达成共识。

03 — 构造

拆开这台机器

EVM 运行时由若干数据区与寄存器构成,它们的生命周期读写权限各不相同。点击任意模块,检视它的定位。

◉ EVM 执行上下文 · Execution Context
04 — 运行原理

数据在机器里如何流动

把这些模块接起来,就是一台活的机器。字节码从 Code 出发,经 PC 定位、解码,交给执行引擎;引擎在上运算、按需读写内存存储,每一步都从 Gas 里扣费——持久化的写入最终汇入世界状态。

Code字节码 · 只读CODECOPY Calldata输入 · 只读 PC程序计数器 Decode解码操作码 Execution执行引擎 / ALUfetch·decode·execute Stack256-bit LIFO≤ 1024 项 · 运算台 Gas剩余配额 · 每步扣减 Memory易失字节数组调用结束清零 Storage持久键值 · 最贵跨交易存续 World Stateσ′ · 落地上链 Return Data返回 / 回滚
读什么

只读来源:不可变的 Code 与调用方传入的 Calldata。引擎能读、不能改。

算在哪

所有运算都在 上进行;内存 作临时草稿,超出运算范围的数据放这里中转。

写去哪

要永久保存就写 存储,它随交易汇入 世界状态;否则一切在调用结束时蒸发。

取指 解码 执行 FETCH DECODE EXECUTE
  • 取指 Fetch

    PC 指向的偏移,从字节码中取出下一个操作码字节(PUSH 系列还要连带取出其立即数)。

  • 解码 Decode

    识别该字节对应哪个操作码,确定它需要几个栈操作数、要做什么动作、以及标价多少 Gas。

  • 执行 Execute

    从栈取操作数、完成运算或读写、扣减 Gas,并推进 PC(JUMP 则改写 PC)。周而复始,直到 STOP / RETURN / REVERT 或 Gas 耗尽。

05 — 账户

两类账户,一套状态

世界状态是一张从地址到账户的映射。账户分两种,但都由同样的四个字段描述。

EOA · 外部账户

由私钥掌控

用户钱包即属此类。持有余额、有 nonce,但没有代码。只有 EOA 能签名并主动发起交易——它是所有链上活动的起点。

Contract · 合约账户

由代码掌控

部署时写入不可变的字节码,拥有专属持久存储。它从不主动行动,只在被调用时按代码执行——是纯粹的被动响应者。

账户状态字段含义EOA合约
nonce已发送交易数 / 已创建合约数,防重放
balance该地址持有的 Wei 数量
codeHash账户代码的 Keccak-256 哈希空代码哈希合约字节码哈希
storageRoot该账户存储 Trie 的根哈希持久键值状态
06 — 数据区

栈 · 内存 · 存储

三个最易混淆的概念。区别的核心只有两点:活多久,以及多贵

维度栈 Stack内存 Memory存储 Storage
结构LIFO 栈,≤1024 项线性字节数组,按需扩展256→256 位键值映射
寻址单位整字(32 字节)字节,可读写任意偏移32 字节槽位(slot)
生命周期单条指令间单次调用内,调用结束清零永久,跨交易存续
成本量级≈3 gas低,但扩展成本二次增长极高(首次写入 20000 gas)
典型用途操作数与运算中转函数内临时缓冲、返回数据合约的持久状态变量
是否上链是(进入世界状态)
07 — 指令集

操作码:EVM 的原子动作

合约字节码由一连串单字节操作码构成,总计约 140 余个。按类别筛选,检视它们各自吞吐的栈数据与价格。

08 — 亲手执行

单步驱动一台 EVM

这是一台真实运行的 EVM 子集解释器。选一段程序逐条推进,读右侧的逐指令旁白看每一步到底做了什么、扣了多少 Gas;或在底部粘贴你自己的字节码

evm://sandbox — bytecode interpreter
◇ Bytecode · Program Counter
▷ 当前指令 · Now Executing
按「单步」开始执行 —
PC
0
Gas Used
0
Step
0
Stack 0 / 1024
— 空栈 —
Memory 0 bytes
— 未写入 —
Storage persistent
— 无持久写入 —
就绪 · READY
自定义字节码
※ 支持子集:PUSH/DUP/SWAP、ADD MUL SUB DIV MOD EXP、LT GT EQ ISZERO、AND OR XOR NOT SHL SHR、POP MLOAD MSTORE MSTORE8 SLOAD SSTORE、JUMP JUMPI PC JUMPDEST、STOP RETURN REVERT。Gas 为教学简化值。
09 — 路由

调用如何找到函数

合约里没有"函数"这个概念——只有一段代码。所谓调用某函数,其实是把 calldata 的前 4 字节(函数选择器)与合约内预置的常量逐一比对,命中就跳转。下面是实时用 Keccak-256 算出的选择器。

① 函数签名
transfer(address,uint256)
② Keccak-256 哈希
③ 取前 4 字节 = 选择器
0x…
10 — 燃料

Gas 与手续费

Gas 是计算量的度量单位,与它的价格是两回事。EIP-1559 之后,一笔费用被拆成两部分。

费用公式

fee = gasUsed × (baseFee + tip)

baseFee 基础费:由协议按区块拥堵度自动调节,并被销毁(burn),不归任何人。

tip 优先费:付给出块验证者的小费,用来竞争打包优先级。

三种成本来源

你在为什么付费

  • 计算:每个操作码的固有开销
  • 状态:SSTORE 写入、账户创建等改变世界状态的动作最昂贵
  • 内存/数据:内存扩展成本随大小二次增长;calldata 按字节计费
动作操作码约成本 (gas)为何如此
栈运算ADD / SUB / LT3纯 CPU,几乎免费
读存储SLOAD100 / 2100热槽位 100,冷槽位首访 2100
写存储(0→非0)SSTORE20000永久占用全网状态,代价最高
部署合约CREATE32000 +写入代码 200 gas/字节
交易基础费21000每笔交易固定入场费
内存扩展成本 · 二次增长   C(a) = 3a + ⌊a²/512⌋
扩展到 64 字(2048 字节)→ 累计 200 gas

前段近乎线性,几乎免费;但因 项,随内存增大成本急剧抬升——这是对"用光内存"的隐形约束。

11 — 交易生命周期

一笔交易的一生

从钱包里的一次签名,到世界状态被永久改写,交易要穿过这条流水线。EVM 只是其中执行的那一环。

12 — 部署生命周期

合约是怎样诞生的

部署合约是一种特殊交易:它携带的不是最终代码,而是一段初始化代码(initcode)。EVM 先执行它,把它返回的字节作为合约的运行时代码永久落盘。

① Initcode

初始化代码

交易的 data 字段(to 为空)。含构造函数逻辑:设置初始存储、校验参数,最后返回运行时代码。

② EVM 执行

运行 initcode

在新地址的上下文中执行。构造期可写存储、可调用他方合约,但此时合约代码尚未落盘。

③ Runtime Code

运行时代码落盘

initcode 的 RETURN 返回值即为部署字节码,写入账户 codeHash。此后每次被调用执行的都是它。

# 地址如何确定
CREATE  address = keccak256(rlp[部署者地址, nonce])[12:]  ← 依赖 nonce,难以预测
CREATE2 address = keccak256(0xff ‖ 部署者salt ‖ keccak256(initcode))[12:]  ← 可提前算出,适合反事实部署
13 — 调用

合约如何互相调用

合约之间的每次交互都是一次"消息调用"。关键差异在于:代码在谁的上下文里执行、能不能改状态。切换看看上下文如何变化。

调用会层层嵌套

A 调 B、B 再调 C——每次调用都压入一个调用帧,形成执行栈。RETURN 弹出一帧并交还控制权;REVERT 则连同本帧的所有状态变更一起撤销。亲手推推看。

当前只有交易入口帧。点击上方按钮压入 / 弹出调用帧。
14 — 终止

执行如何收场

每一次执行都必然停机,但结局大不相同——决定了状态是被保留、被回滚,还是 Gas 被吞噬殆尽。

STOP / RETURN

正常结束。状态变更全部生效;RETURN 还会附带返回数据。剩余 Gas 退还给发起者。

REVERT

主动回滚。本次调用的所有状态变更撤销,可返回错误信息,剩余 Gas 退还——这是 require 失败的底层机制。

Out of Gas

燃料耗尽。所有状态变更回滚,但已消耗的 Gas 不退——这是对占用算力的惩罚。

Invalid Opcode

遇到未定义指令(如 0xfe)。等同于耗尽全部 Gas 并回滚,常用作断言失败的哨兵。

SELFDESTRUCT

合约请求销毁自身并转出余额。语义在近期硬分叉中被大幅弱化,已不推荐使用。

15 — 演进

EVM 不是一成不变的

通过硬分叉,EVM 的指令集与计价规则持续演化。点击时间轴上的里程碑,看它给虚拟机带来了什么。

16 — 预编译

藏在固定地址里的加速器

有些运算(尤其是密码学)若用操作码硬算会极其昂贵。EVM 把它们做成预编译合约,安置在 0x01 起的固定地址:像普通合约一样被 CALL,却以原生代码高速执行。

地址名称作用
0x01ecRecover从签名恢复出签名者地址(ECDSA),支撑一切链上验签
0x02SHA-256SHA-256 哈希
0x03RIPEMD-160RIPEMD-160 哈希
0x04identity原样返回输入,常用于高效拷贝数据
0x05modexp大数模幂,RSA / 高级密码学的基石
0x06–08bn256 系列椭圆曲线加法、乘法与配对,zk-SNARK 验证核心
0x09blake2fBLAKE2 压缩函数,跨链验证等场景使用
0x0aKZG point eval验证 blob 承诺(Cancun 随 Proto-Danksharding 引入)