EVM字节码执行引擎深度解析:从Opcodes到Gas计费

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执行引擎核心:

  1. 三区模型 --- Stack(临时计算)→Memory(调用内共享)→Storage(永久)
  2. Gas经济 --- 每步操作精确计费,SSTORE最高(20000),ADD仅3
  3. 调用模型 --- CALL/DELEGATECALL/STATICCALL三种上下文传递
  4. 预编译合约 --- bn256Pairing(双线性对)是ZK-Rollup验证器基础
相关推荐
大鱼>12 小时前
共识算法:从PBFT到HotStuff的拜占庭容错演进
区块链·php·共识算法
磐链科技1 天前
区块链钱包开发新范式:如何用原生技术打造下一代Web3入口
web3·区块链
水无痕simon1 天前
15 转账对接文档
java·区块链
汇策研习社2 天前
双指标共振交易体系:GMMA趋势骨架 + MACD动能验证实战全解
大数据·人工智能·信息可视化·金融·区块链·fastbull
ZDGJ60992 天前
一文理清国际期货各大品类
大数据·区块链
Web3李李2 天前
如何选择适合自己的Web3项目本位模式?金本位与币本位选型指南
web3·区块链·软件开发·金本位·币本位·dapp经济模型设计·defi质押理财
磐链科技2 天前
区块链怎么实现经济效益,现在及未来的经济前景如何?
区块链
IvorySQL3 天前
PostgreSQL 日报|修复 WAL 归档恢复竞态条件(8 月 5 日)
数据库·postgresql·区块链
磐链科技3 天前
区块链选框架,本质是选信任模型
区块链