EVM字节码执行引擎深度解析:从Opcodes到Gas计费
一、引言
以太坊虚拟机(EVM)是以太坊的"CPU",每条合约调用都在EVM中执行字节码。理解EVM不仅关乎Solidity开发优化,更是看懂MEV、Layer2 ZK-EVM、合约审计的基础。
本文将手写一个EVM执行引擎,逐条实现核心Opcodes,解析Memory/Storage/Stack三区模型和Gas计费机制。
二、EVM架构三区模型
┌────────────────────────────────────────────┐
│ EVM 执行上下文 │
├────────────┬────────────┬──────────────────┤
│ Stack │ Memory │ Storage │
│ (LIFO栈) │ (字节数组) │ (持久化键值对) │
├────────────┼────────────┼──────────────────┤
│ 最大1024层 │ 按字扩展 │ 32字节→32字节 │
│ 32字节/层 │ 按32字节计费 │ 永久存储(Gas高) │
│ 临时数据 │ 调用内共享 │ 跨调用持久化 │
└────────────┴────────────┴──────────────────┘
2.1 Go语言EVM核心结构
go
package evm
import (
"math/big"
"github.com/holiman/uint256"
)
type EVM struct {
// 持久化存储访问接口
StateDB StateDB
// 当前执行上下文
Context ExecutionContext
Stack *Stack // 最大1024层
Memory *Memory // 按需扩展字节数组
Contract *Contract // 当前执行的合约
// 执行控制
PC uint64 // 程序计数器(字节码位置)
Gas uint64 // 剩余Gas
ReturnData []byte // 上一个调用的返回数据
Stop bool // 是否停止
// 日志与自毁
Logs []*Log
}
type Stack struct {
data []uint256.Int // 使用uint256库(比big.Int快10x)
size int
}
const MaxStackSize = 1024
func (s *Stack) Push(val *uint256.Int) {
if s.size >= MaxStackSize {
panic("stack overflow")
}
s.data = append(s.data[:s.size], *val)
s.size++
}
func (s *Stack) Pop() *uint256.Int {
if s.size == 0 {
panic("stack underflow")
}
s.size--
val := s.data[s.size]
return &val
}
func (s *Stack) Peek(n int) *uint256.Int {
return &s.data[s.size - n - 1]
}
func (s *Stack) Swap(n int) {
s.data[s.size-1], s.data[s.size-n-1] =
s.data[s.size-n-1], s.data[s.size-1]
}
func (s *Stack) Dup(n int) {
s.Push(s.Peek(n - 1))
}
// Memory: 按需扩展的字节数组
type Memory struct {
store []byte
lastGasCost uint64
}
func (m *Memory) Set(offset, size uint64, value []byte) {
if size > 0 {
if offset+size > uint64(len(m.store)) {
m.store = append(m.store, make([]byte, offset+size-uint64(len(m.store)))...)
}
copy(m.store[offset:], value)
}
}
func (m *Memory) Get(offset, size uint64) []byte {
if size == 0 { return nil }
if len(m.store) > int(offset) {
return m.store[offset : offset+size]
}
return make([]byte, size)
}
三、Opcodes完整实现与Gas计费
3.1 STOP与算术运算
go
const (
STOP = 0x00
ADD = 0x01
MUL = 0x02
SUB = 0x03
DIV = 0x04
SDIV = 0x05 // 有符号除法
MOD = 0x06
SMOD = 0x07
ADDMOD = 0x08
MULMOD = 0x09
EXP = 0x0A
SIGNEXTEND = 0x0B
)
func (evm *EVM) execute() ([]byte, error) {
code := evm.Contract.Code
opcodesLen := len(code)
for evm.PC < uint64(opcodesLen) && !evm.Stop {
opcode := code[evm.PC]
evm.PC++
switch opcode {
case STOP:
evm.Stop = true
return nil, nil
case ADD:
x, y := evm.Stack.Pop(), evm.Stack.Pop()
result := new(uint256.Int).Add(x, y)
evm.Stack.Push(result)
case MUL:
x, y := evm.Stack.Pop(), evm.Stack.Pop()
result := new(uint256.Int).Mul(x, y)
evm.Stack.Push(result)
case SUB:
x, y := evm.Stack.Pop(), evm.Stack.Pop()
result := new(uint256.Int).Sub(x, y)
evm.Stack.Push(result)
case DIV:
x, y := evm.Stack.Pop(), evm.Stack.Pop()
if y.IsZero() {
evm.Stack.Push(new(uint256.Int)) // 除零返回0
} else {
result := new(uint256.Int).Div(x, y)
evm.Stack.Push(result)
}
case EXP:
base, exp := evm.Stack.Pop(), evm.Stack.Pop()
result := new(uint256.Int).Exp(base, exp)
// ★ EXP动态Gas计费: 指数每字节50 Gas
expBytes := exp.ByteLen()
dynamicGas := uint64(expBytes) * 50
if evm.Gas < dynamicGas {
return nil, ErrOutOfGas
}
evm.Gas -= dynamicGas
evm.Stack.Push(result)
case SIGNEXTEND:
b, x := evm.Stack.Pop(), evm.Stack.Pop()
result := signExtend(b, x)
evm.Stack.Push(result)
}
}
return nil, nil
}
func signExtend(b, x *uint256.Int) *uint256.Int {
if b.Cmp(uint256.NewInt(31)) >= 0 { return x }
bitIndex := uint(b.Uint64())*8 + 7
mask := new(uint256.Int).Lsh(uint256.NewInt(1), bitIndex)
mask.Sub(mask, uint256.NewInt(1))
if new(uint256.Int).Rsh(x, bitIndex).Uint64()&1 == 1 {
return new(uint256.Int).Or(x, new(uint256.Int).Not(mask))
}
return new(uint256.Int).And(x, mask)
}
3.2 比较与位运算
go
case LT:
x, y := evm.Stack.Pop(), evm.Stack.Pop()
if x.Lt(y) { evm.Stack.Push(uint256.NewInt(1)) }
else { evm.Stack.Push(new(uint256.Int)) }
case GT:
x, y := evm.Stack.Pop(), evm.Stack.Pop()
if x.Gt(y) { evm.Stack.Push(uint256.NewInt(1)) }
else { evm.Stack.Push(new(uint256.Int)) }
case EQ:
x, y := evm.Stack.Pop(), evm.Stack.Pop()
if x.Eq(y) { evm.Stack.Push(uint256.NewInt(1)) }
else { evm.Stack.Push(new(uint256.Int)) }
case ISZERO:
x := evm.Stack.Pop()
if x.IsZero() { evm.Stack.Push(uint256.NewInt(1)) }
else { evm.Stack.Push(new(uint256.Int)) }
case AND:
x, y := evm.Stack.Pop(), evm.Stack.Pop()
evm.Stack.Push(new(uint256.Int).And(x, y))
case OR:
x, y := evm.Stack.Pop(), evm.Stack.Pop()
evm.Stack.Push(new(uint256.Int).Or(x, y))
case XOR:
x, y := evm.Stack.Pop(), evm.Stack.Pop()
evm.Stack.Push(new(uint256.Int).Xor(x, y))
case NOT:
x := evm.Stack.Pop()
evm.Stack.Push(new(uint256.Int).Not(x))
case SHL:
shift, val := evm.Stack.Pop(), evm.Stack.Pop()
if shift.Cmp(uint256.NewInt(256)) >= 0 {
evm.Stack.Push(new(uint256.Int))
} else {
evm.Stack.Push(new(uint256.Int).Lsh(val, uint(shift.Uint64())))
}
case SHR:
shift, val := evm.Stack.Pop(), evm.Stack.Pop()
if shift.Cmp(uint256.NewInt(256)) >= 0 {
evm.Stack.Push(new(uint256.Int))
} else {
evm.Stack.Push(new(uint256.Int).Rsh(val, uint(shift.Uint64())))
}
3.3 控制流
go
case JUMP:
dest := evm.Stack.Pop()
evm.PC = dest.Uint64()
// ★ 必须跳到JUMPDEST位置,否则revert
if evm.PC >= uint64(len(code)) || code[evm.PC] != JUMPDEST {
return nil, ErrInvalidJump
}
case JUMPI: // 条件跳转
dest, cond := evm.Stack.Pop(), evm.Stack.Pop()
if !cond.IsZero() {
evm.PC = dest.Uint64()
if evm.PC >= uint64(len(code)) || code[evm.PC] != JUMPDEST {
return nil, ErrInvalidJump
}
}
case JUMPDEST:
// 空操作,仅作为跳转目标标记
// Gas: 1
case PC:
evm.Stack.Push(uint256.NewInt(evm.PC - 1))
3.4 Memory与Storage操作
go
case MLOAD:
offset := evm.Stack.Pop()
val := new(uint256.Int).SetBytes(evm.Memory.Get(offset.Uint64(), 32))
evm.Stack.Push(val)
case MSTORE:
offset, val := evm.Stack.Pop(), evm.Stack.Pop()
evm.Memory.Set(offset.Uint64(), 32, val.Bytes32())
case MSTORE8: // 只存1字节
offset, val := evm.Stack.Pop(), evm.Stack.Pop()
evm.Memory.Set(offset.Uint64(), 1, []byte{byte(val.Uint64())})
case SLOAD: // ★ 从持久化存储加载(Gas最高:2100 warm/2600 cold)
key := evm.Stack.Pop()
value := evm.StateDB.GetState(evm.Contract.Address, key.Bytes32())
evm.Stack.Push(new(uint256.Int).SetBytes(value.Bytes()))
case SSTORE: // ★ 持久化存储写入(20000 Gas首次/2900覆写)
key, value := evm.Stack.Pop(), evm.Stack.Pop()
original := evm.StateDB.GetCommittedState(evm.Contract.Address, key.Bytes32())
current := evm.StateDB.GetState(evm.Contract.Address, key.Bytes32())
// EIP-2200/3529: SSTORE Gas计费规则
gasCost := sstoreGasCost(current, new(uint256.Int).SetBytes(original.Bytes()), value)
if evm.Gas < gasCost {
return nil, ErrOutOfGas
}
evm.Gas -= gasCost
evm.StateDB.SetState(evm.Contract.Address, key.Bytes32(), value.Bytes32())
3.5 调用与返回
go
case CALL:
gas := evm.Stack.Pop()
addr := common.BytesToAddress(evm.Stack.Pop().Bytes20())
value := evm.Stack.Pop()
argsOffset, argsSize := evm.Stack.Pop(), evm.Stack.Pop()
retOffset, retSize := evm.Stack.Pop(), evm.Stack.Pop()
args := evm.Memory.Get(argsOffset.Uint64(), argsSize.Uint64())
// ★ 值为0: 普通调用 | 值>0: 转账调用(额外9000 Gas)
ret, err := evm.Call(addr, args, gas.Uint64(), value)
if err == nil {
evm.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
evm.Stack.Push(uint256.NewInt(1)) // 成功
} else {
evm.Stack.Push(new(uint256.Int)) // 失败返回0
}
case DELEGATECALL:
// 类似CALL,但使用调用者的Storage和上下文
gas := evm.Stack.Pop()
addr := common.BytesToAddress(evm.Stack.Pop().Bytes20())
argsOffset, argsSize := evm.Stack.Pop(), evm.Stack.Pop()
retOffset, retSize := evm.Stack.Pop(), evm.Stack.Pop()
args := evm.Memory.Get(argsOffset.Uint64(), argsSize.Uint64())
ret, err := evm.DelegateCall(addr, args, gas.Uint64())
if err == nil {
evm.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
evm.Stack.Push(uint256.NewInt(1))
} else {
evm.Stack.Push(new(uint256.Int))
}
case RETURN:
offset, size := evm.Stack.Pop(), evm.Stack.Pop()
evm.Stop = true
return evm.Memory.Get(offset.Uint64(), size.Uint64()), nil
case REVERT:
offset, size := evm.Stack.Pop(), evm.Stack.Pop()
evm.Stop = true
return nil, NewRevertError(evm.Memory.Get(offset.Uint64(), size.Uint64()))
四、Gas计费完整表
go
// EIP-2929 + EIP-3529 后Gas模型
var gasTable = map[OpCode]uint64{
STOP: 0,
ADD: 3, // W_{verylow}
MUL: 5, // W_{low}
SUB: 3,
DIV: 5,
EXP: 10, // + exponent_byte_cost * 50
SIGNEXTEND: 5,
LT: 3,
GT: 3,
EQ: 3,
ISZERO: 3,
AND: 3,
OR: 3,
XOR: 3,
NOT: 3,
MLOAD: 3, // + memory_expansion_cost
MSTORE: 3, // + memory_expansion_cost
MSTORE8: 3,
SLOAD: 100, // warm (cold=2100)
SSTORE: 100, // warm (cold=20000, dirty=2900)
JUMP: 8, // W_{mid}
JUMPI: 10,
JUMPDEST: 1,
CALL: 100, // warm (cold=2600) + value_transfer(9000)
DELEGATECALL:100,
STATICCALL: 100,
CREATE: 32000,
CREATE2: 32000 + hashing_cost,
BALANCE: 100, // warm (cold=2600)
EXTCODESIZE: 100,
EXTCODECOPY: 100,
LOG0: 375,
LOG1: 750,
LOG2: 1125,
LOG3: 1500,
LOG4: 1875,
SELFDESTRUCT: 5000, // + 25000 if creates new account
}
// Memory扩展Gas: G_memory = 3*a + a^2/512
func memoryGasCost(currentSize, newSize uint64) uint64 {
if newSize <= currentSize { return 0 }
newWords := (newSize + 31) / 32
cost := 3 * newWords + newWords * newWords / 512
currentWords := (currentSize + 31) / 32
currentCost := 3 * currentWords + currentWords * currentWords / 512
return cost - currentCost
}
五、预编译合约
go
// 以太坊内置9个预编译合约(地址0x01~0x09)
var PrecompiledContracts = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{}, // EIP-198
common.BytesToAddress([]byte{6}): &bn256Add{}, // EIP-196
common.BytesToAddress([]byte{7}): &bn256ScalarMul{},
common.BytesToAddress([]byte{8}): &bn256Pairing{}, // 双线性对(ZK核心)
common.BytesToAddress([]byte{9}): &blake2F{}, // EIP-152
}
// SHA-256预编译示例
type sha256hash struct{}
func (s *sha256hash) RequiredGas(input []byte) uint64 {
return uint64(len(input)+31)/32*12 + 60 // 每32字节12Gas + 基础60Gas
}
func (s *sha256hash) Run(input []byte) ([]byte, error) {
hash := sha256.Sum256(input)
return hash[:], nil
}
六、代理模式与存储碰撞
solidity
// 代理模式中最大的坑: Storage Slot Collision
contract Proxy {
address implementation; // slot 0
fallback() external payable {
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize())
let result := delegatecall(gas(), sload(0), ptr, calldatasize(), 0, 0)
returndatacopy(ptr, 0, returndatasize())
switch result
case 0 { revert(ptr, returndatasize()) }
default { return(ptr, returndatasize()) }
}
}
}
contract LogicV1 {
uint256 public value; // slot 0 ← 与Proxy的implementation冲突!
address public owner; // slot 1
// 正确做法: EIP-1967规定的伪随机slot
// bytes32 private constant IMPLEMENTATION_SLOT =
// keccak256("eip1967.proxy.implementation") - 1;
}
七、总结
EVM执行引擎核心:
- 三区模型 --- Stack(临时计算)→Memory(调用内共享)→Storage(永久)
- Gas经济 --- 每步操作精确计费,SSTORE最高(20000),ADD仅3
- 调用模型 --- CALL/DELEGATECALL/STATICCALL三种上下文传递
- 预编译合约 --- bn256Pairing(双线性对)是ZK-Rollup验证器基础