mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 03:10:48 +00:00
f(eip-8141): add opcodes
This commit is contained in:
parent
23c6a6c174
commit
0b11863ec4
6 changed files with 440 additions and 0 deletions
|
|
@ -38,6 +38,7 @@ var activators = map[int]func(*JumpTable){
|
|||
2200: enable2200,
|
||||
1884: enable1884,
|
||||
1344: enable1344,
|
||||
8141: enable8141,
|
||||
1153: enable1153,
|
||||
4762: enable4762,
|
||||
7702: enable7702,
|
||||
|
|
@ -579,3 +580,37 @@ func enable7702(jt *JumpTable) {
|
|||
jt[STATICCALL].dynamicGas = gasStaticCallEIP7702
|
||||
jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7702
|
||||
}
|
||||
|
||||
func enable8141(jt *JumpTable) {
|
||||
jt[APPROVE] = &operation{
|
||||
execute: opApprove,
|
||||
constantGas: 0,
|
||||
dynamicGas: gasApprove,
|
||||
minStack: minStack(3, 0),
|
||||
maxStack: maxStack(3, 0),
|
||||
memorySize: memoryApprove,
|
||||
}
|
||||
|
||||
jt[TXPARAMLOAD] = &operation{
|
||||
execute: opTxParamLoad,
|
||||
constantGas: GasFastestStep,
|
||||
minStack: minStack(3, 1),
|
||||
maxStack: maxStack(3, 1),
|
||||
}
|
||||
|
||||
jt[TXPARAMSIZE] = &operation{
|
||||
execute: opTxParamSize,
|
||||
constantGas: GasQuickStep,
|
||||
minStack: minStack(2, 1),
|
||||
maxStack: maxStack(2, 1),
|
||||
}
|
||||
|
||||
jt[TXPARAMCOPY] = &operation{
|
||||
execute: opTxParamCopy,
|
||||
constantGas: GasFastestStep,
|
||||
dynamicGas: gasTxParamCopy,
|
||||
minStack: minStack(5, 0),
|
||||
maxStack: maxStack(5, 0),
|
||||
memorySize: memoryTxParamCopy,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,36 @@ type EVM struct {
|
|||
|
||||
readOnly bool // Whether to throw on stateful modifications
|
||||
returnData []byte // Last CALL's return data for subsequent reuse
|
||||
|
||||
// FrameTxContext holds frame transaction state, set during frame tx execution.
|
||||
FrameTxCtx *FrameTxContext
|
||||
}
|
||||
|
||||
type FrameTxContext struct {
|
||||
Sender common.Address
|
||||
Frames []FrameInfo
|
||||
FrameStatuses []uint8
|
||||
SenderApproved bool
|
||||
PayerApproved bool
|
||||
Payer common.Address
|
||||
SigHash common.Hash
|
||||
CurrentFrame int
|
||||
Nonce uint64
|
||||
GasTipCap *big.Int
|
||||
GasFeeCap *big.Int
|
||||
TxFee *big.Int
|
||||
ApproveCalledInFrame bool
|
||||
|
||||
BlobFeeCap *big.Int
|
||||
BlobVersionedHashes []common.Hash
|
||||
}
|
||||
|
||||
// FrameInfo holds the definition of a single frame within a FrameTx.
|
||||
type FrameInfo struct {
|
||||
Mode uint8
|
||||
Target common.Address
|
||||
GasLimit uint64
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// NewEVM constructs an EVM instance with the supplied block context, state
|
||||
|
|
@ -144,6 +174,8 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon
|
|||
evm.precompiles = activePrecompiledContracts(evm.chainRules)
|
||||
|
||||
switch {
|
||||
case evm.chainRules.IsBogota:
|
||||
evm.table = &bogotaInstructionSet
|
||||
case evm.chainRules.IsOsaka:
|
||||
evm.table = &osakaInstructionSet
|
||||
case evm.chainRules.IsVerkle:
|
||||
|
|
|
|||
|
|
@ -1144,3 +1144,344 @@ func makeDup(size int) executionFunc {
|
|||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 8141
|
||||
|
||||
func getTxParamData(evm *EVM, ftx *FrameTxContext, selector, frameIdx uint64) ([]byte, bool) {
|
||||
switch selector {
|
||||
|
||||
case 0x00: // tx type = 6
|
||||
val := new(uint256.Int).SetUint64(6)
|
||||
b32 := val.Bytes32()
|
||||
return b32[:], true
|
||||
|
||||
case 0x01: // nonce
|
||||
val := new(uint256.Int).SetUint64(ftx.Nonce)
|
||||
b32 := val.Bytes32()
|
||||
return b32[:], true
|
||||
|
||||
case 0x02: // sender (left-padded to 32 bytes)
|
||||
return common.LeftPadBytes(ftx.Sender.Bytes(), 32), true
|
||||
|
||||
case 0x03: // max_priority_fee_per_gas
|
||||
val, _ := uint256.FromBig(ftx.GasTipCap)
|
||||
b32 := val.Bytes32()
|
||||
return b32[:], true
|
||||
|
||||
case 0x04: // max_fee_per_gas
|
||||
val, _ := uint256.FromBig(ftx.GasFeeCap)
|
||||
b32 := val.Bytes32()
|
||||
return b32[:], true
|
||||
|
||||
case 0x05: // max_fee_per_blob_gas
|
||||
if ftx.BlobFeeCap == nil {
|
||||
b32 := new(uint256.Int).Bytes32()
|
||||
return b32[:], true
|
||||
}
|
||||
val, _ := uint256.FromBig(ftx.BlobFeeCap)
|
||||
b32 := val.Bytes32()
|
||||
return b32[:], true
|
||||
|
||||
case 0x06: // max cost
|
||||
val, _ := uint256.FromBig(ftx.TxFee)
|
||||
b32 := val.Bytes32()
|
||||
return b32[:], true
|
||||
|
||||
case 0x07: // len(blob_versioned_hashes)
|
||||
count := uint64(len(ftx.BlobVersionedHashes))
|
||||
val := new(uint256.Int).SetUint64(count)
|
||||
b32 := val.Bytes32()
|
||||
return b32[:], true
|
||||
|
||||
case 0x08: // compute_sig_hash(tx)
|
||||
return ftx.SigHash[:], true
|
||||
|
||||
case 0x09: // len(frames)
|
||||
val := new(uint256.Int).SetUint64(uint64(len(ftx.Frames)))
|
||||
b32 := val.Bytes32()
|
||||
return b32[:], true
|
||||
|
||||
case 0x10: // current frame index
|
||||
if ftx.CurrentFrame < 0 {
|
||||
return nil, false
|
||||
}
|
||||
val := new(uint256.Int).SetUint64(uint64(ftx.CurrentFrame))
|
||||
b32 := val.Bytes32()
|
||||
return b32[:], true
|
||||
|
||||
case 0x11: // frame[index].target
|
||||
if int(frameIdx) >= len(ftx.Frames) {
|
||||
return nil, false // TxParamOutOfBounds → exceptional halt
|
||||
}
|
||||
f := ftx.Frames[frameIdx]
|
||||
if f.Target == (common.Address{}) {
|
||||
// null target
|
||||
return make([]byte, 32), true
|
||||
}
|
||||
return common.LeftPadBytes(f.Target.Bytes(), 32), true
|
||||
|
||||
case 0x12: // frame[index].data
|
||||
// EELS:
|
||||
// if index >= len(tx.frames): raise TxParamOutOfBounds
|
||||
// if frame.mode == FRAME_MODE_VERIFY: return (b"", Uint(0))
|
||||
// data = bytes(frame.data)
|
||||
// return (data, Uint(len(data)))
|
||||
if int(frameIdx) >= len(ftx.Frames) {
|
||||
return nil, false // TxParamOutOfBounds
|
||||
}
|
||||
f := ftx.Frames[frameIdx]
|
||||
if f.Mode == types.FrameModeVerify {
|
||||
return []byte{}, true
|
||||
}
|
||||
return f.Data, true
|
||||
|
||||
case 0x13: // frame[index].gas_limit
|
||||
if int(frameIdx) >= len(ftx.Frames) {
|
||||
return nil, false
|
||||
}
|
||||
val := new(uint256.Int).SetUint64(ftx.Frames[frameIdx].GasLimit)
|
||||
b32 := val.Bytes32()
|
||||
return b32[:], true
|
||||
|
||||
case 0x14: // frame[index].mode
|
||||
if int(frameIdx) >= len(ftx.Frames) {
|
||||
return nil, false
|
||||
}
|
||||
val := new(uint256.Int).SetUint64(uint64(ftx.Frames[frameIdx].Mode))
|
||||
b32 := val.Bytes32()
|
||||
return b32[:], true
|
||||
|
||||
case 0x15: // frame[index].status
|
||||
if int(frameIdx) >= len(ftx.Frames) {
|
||||
return nil, false
|
||||
}
|
||||
if int(frameIdx) >= ftx.CurrentFrame {
|
||||
return nil, false
|
||||
}
|
||||
if int(frameIdx) >= len(ftx.FrameStatuses) {
|
||||
return nil, false
|
||||
}
|
||||
val := new(uint256.Int).SetUint64(uint64(ftx.FrameStatuses[frameIdx]))
|
||||
b32 := val.Bytes32()
|
||||
return b32[:], true
|
||||
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
// TXPARAMLOAD (0xB0)
|
||||
// Stack: [selector, index, offset] → [value]
|
||||
// Gas: GAS_VERY_LOW (3)
|
||||
|
||||
func opTxParamLoad(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||
ftx := evm.FrameTxCtx
|
||||
if ftx == nil {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
selector := scope.Stack.pop()
|
||||
index := scope.Stack.pop()
|
||||
offset := scope.Stack.pop()
|
||||
data, ok := getTxParamData(evm, ftx, selector.Uint64(), index.Uint64())
|
||||
if !ok {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
var result [32]byte
|
||||
offsetU64 := offset.Uint64()
|
||||
if offsetU64 < uint64(len(data)) {
|
||||
copy(result[:], data[offsetU64:])
|
||||
}
|
||||
scope.Stack.push(new(uint256.Int).SetBytes(result[:]))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// TXPARAMSIZE (0xB1)
|
||||
// Stack: [selector, index] → [size]
|
||||
// Gas: GAS_BASE (2)
|
||||
|
||||
func opTxParamSize(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||
ftx := evm.FrameTxCtx
|
||||
if ftx == nil {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
selector := scope.Stack.pop()
|
||||
index := scope.Stack.pop()
|
||||
data, ok := getTxParamData(evm, ftx, selector.Uint64(), index.Uint64())
|
||||
if !ok {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
|
||||
scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(data))))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// TXPARAMCOPY (0xB2)
|
||||
// Stack: [selector, index, dest_offset, src_offset, length] → []
|
||||
// Gas: GAS_VERY_LOW + GAS_VERY_LOW * words + memory_expansion
|
||||
|
||||
func opTxParamCopy(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||
selector := scope.Stack.pop()
|
||||
index := scope.Stack.pop()
|
||||
memOffset := scope.Stack.pop()
|
||||
dataOffset := scope.Stack.pop()
|
||||
length := scope.Stack.pop()
|
||||
lengthU64 := length.Uint64()
|
||||
if lengthU64 == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
ftx := evm.FrameTxCtx
|
||||
if ftx == nil {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
data, ok := getTxParamData(evm, ftx, selector.Uint64(), index.Uint64())
|
||||
if !ok {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
dataOffsetU64 := dataOffset.Uint64()
|
||||
dataCopy := make([]byte, lengthU64)
|
||||
if dataOffsetU64 < uint64(len(data)) {
|
||||
end := dataOffsetU64 + lengthU64
|
||||
if end > uint64(len(data)) {
|
||||
end = uint64(len(data))
|
||||
}
|
||||
copy(dataCopy, data[dataOffsetU64:end])
|
||||
}
|
||||
scope.Memory.Set(memOffset.Uint64(), lengthU64, dataCopy)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// APPROVE (0xAA)
|
||||
// Stack: [offset, length, scope] -> [] (terminates frame)
|
||||
// Gas: GAS_ZERO + memory_expansion
|
||||
|
||||
func opApprove(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||
offset := scope.Stack.pop()
|
||||
length := scope.Stack.pop()
|
||||
scp := scope.Stack.pop()
|
||||
|
||||
// GAS already charged + memory already extended by gasApprove()
|
||||
ftx := evm.FrameTxCtx
|
||||
if ftx == nil {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
|
||||
// EELS: tx_approval = _get_frame_tx_approval(evm)
|
||||
s := scp.Uint64()
|
||||
if s > 2 {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
|
||||
if ftx.CurrentFrame < 0 || ftx.CurrentFrame >= len(ftx.Frames) {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
frame := ftx.Frames[ftx.CurrentFrame]
|
||||
|
||||
var frameTarget common.Address
|
||||
if frame.Target == (common.Address{}) {
|
||||
frameTarget = ftx.Sender
|
||||
} else {
|
||||
frameTarget = frame.Target
|
||||
}
|
||||
|
||||
if scope.Contract.Caller() != frameTarget {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
|
||||
switch s {
|
||||
|
||||
case 0x0:
|
||||
if ftx.SenderApproved {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
if scope.Contract.Caller() != ftx.Sender {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
ftx.SenderApproved = true
|
||||
|
||||
case 0x1:
|
||||
if ftx.PayerApproved {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
if !ftx.SenderApproved {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
balance := evm.StateDB.GetBalance(frameTarget).ToBig()
|
||||
if balance.Cmp(ftx.TxFee) < 0 {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
fee, _ := uint256.FromBig(ftx.TxFee)
|
||||
evm.StateDB.SubBalance(frameTarget, fee, tracing.BalanceDecreaseGasBuy)
|
||||
evm.StateDB.SetNonce(ftx.Sender, evm.StateDB.GetNonce(ftx.Sender)+1, tracing.NonceChangeEoACall)
|
||||
ftx.PayerApproved = true
|
||||
ftx.Payer = frameTarget
|
||||
|
||||
case 0x2:
|
||||
if ftx.SenderApproved || ftx.PayerApproved {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
if scope.Contract.Caller() != ftx.Sender {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
balance := evm.StateDB.GetBalance(frameTarget).ToBig()
|
||||
if balance.Cmp(ftx.TxFee) < 0 {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
fee, _ := uint256.FromBig(ftx.TxFee)
|
||||
evm.StateDB.SubBalance(frameTarget, fee, tracing.BalanceDecreaseGasBuy)
|
||||
evm.StateDB.SetNonce(ftx.Sender, evm.StateDB.GetNonce(ftx.Sender)+1, tracing.NonceChangeEoACall)
|
||||
ftx.SenderApproved = true
|
||||
ftx.PayerApproved = true
|
||||
ftx.Payer = frameTarget
|
||||
}
|
||||
|
||||
ftx.ApproveCalledInFrame = true
|
||||
|
||||
// Mirror opReturn
|
||||
ret := scope.Memory.GetCopy(offset.Uint64(), length.Uint64())
|
||||
return ret, errStopToken
|
||||
}
|
||||
|
||||
// Todo(jvn): All these Memory extension and Gas Estimation verify correctness across EELS
|
||||
|
||||
// memoryApprove computes memory size for APPROVE dynamic gas.
|
||||
func memoryApprove(stack *Stack) (uint64, bool) {
|
||||
// Stack at this point (before pops): [offset, length, scope]
|
||||
return calcMemSize64(stack.Back(0), stack.Back(1))
|
||||
}
|
||||
|
||||
// gasApprove computes dynamic gas for APPROVE (memory expansion only).
|
||||
// EELS: charge_gas(evm, GAS_ZERO + extend_memory.cost)
|
||||
func gasApprove(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
return memoryGasCost(mem, memorySize)
|
||||
}
|
||||
|
||||
// memoryTxParamCopy computes memory size for TXPARAMCOPY dynamic gas.
|
||||
func memoryTxParamCopy(stack *Stack) (uint64, bool) {
|
||||
// destOffset is Back(2), length is Back(4)
|
||||
return calcMemSize64(stack.Back(2), stack.Back(4))
|
||||
}
|
||||
|
||||
// gasTxParamCopy computes dynamic gas for TXPARAMCOPY.
|
||||
func gasTxParamCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
gas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
length := stack.Back(4)
|
||||
words, overflow := length.Uint64WithOverflow()
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
// words = ceil(length / 32)
|
||||
wordSize := toWordSize(words)
|
||||
copyGas := wordSize * params.CopyGas
|
||||
// overflow check
|
||||
if wordSize != 0 && copyGas/wordSize != params.CopyGas {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
total := gas + copyGas
|
||||
if total < gas {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ type StateDB interface {
|
|||
|
||||
GetTransientState(addr common.Address, key common.Hash) common.Hash
|
||||
SetTransientState(addr common.Address, key, value common.Hash)
|
||||
ClearTransientStorage()
|
||||
|
||||
SelfDestruct(common.Address)
|
||||
HasSelfDestructed(common.Address) bool
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ var (
|
|||
verkleInstructionSet = newVerkleInstructionSet()
|
||||
pragueInstructionSet = newPragueInstructionSet()
|
||||
osakaInstructionSet = newOsakaInstructionSet()
|
||||
bogotaInstructionSet = newBogotaInstructionSet()
|
||||
)
|
||||
|
||||
// JumpTable contains the EVM opcodes supported at a given fork.
|
||||
|
|
@ -92,6 +93,12 @@ func newVerkleInstructionSet() JumpTable {
|
|||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
func newBogotaInstructionSet() JumpTable {
|
||||
instructionSet := newOsakaInstructionSet()
|
||||
enable8141(&instructionSet) // EIP-7939 (CLZ opcode)
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
func newOsakaInstructionSet() JumpTable {
|
||||
instructionSet := newPragueInstructionSet()
|
||||
enable7939(&instructionSet) // EIP-7939 (CLZ opcode)
|
||||
|
|
|
|||
|
|
@ -212,6 +212,18 @@ const (
|
|||
LOG4
|
||||
)
|
||||
|
||||
// 0xaa range - Frame Transaction opcodes.
|
||||
const (
|
||||
APPROVE OpCode = 0xaa //Approve execution/payment in VERIFY frame.
|
||||
)
|
||||
|
||||
// 0xb0 range - transaction parameter opcodes.
|
||||
const (
|
||||
TXPARAMLOAD OpCode = 0xb0 // Load 32-byte word from tx parameter.
|
||||
TXPARAMSIZE OpCode = 0xb1 // Get size of tx parameter.
|
||||
TXPARAMCOPY OpCode = 0xb2 // Copy tx parameter data to memory.
|
||||
)
|
||||
|
||||
// 0xd0 range - eof operations.
|
||||
const (
|
||||
DATALOAD OpCode = 0xd0
|
||||
|
|
@ -416,6 +428,14 @@ var opCodeToString = [256]string{
|
|||
LOG3: "LOG3",
|
||||
LOG4: "LOG4",
|
||||
|
||||
// 0xaa - opcodes.
|
||||
APPROVE: "APPROVE",
|
||||
|
||||
// 0xb0 range - tx param opcodes.
|
||||
TXPARAMLOAD: "TXPARAMLOAD",
|
||||
TXPARAMSIZE: "TXPARAMSIZE",
|
||||
TXPARAMCOPY: "TXPARAMCOPY",
|
||||
|
||||
// 0xd range - eof ops.
|
||||
DATALOAD: "DATALOAD",
|
||||
DATALOADN: "DATALOADN",
|
||||
|
|
@ -631,6 +651,10 @@ var stringToOp = map[string]OpCode{
|
|||
"REVERT": REVERT,
|
||||
"INVALID": INVALID,
|
||||
"SELFDESTRUCT": SELFDESTRUCT,
|
||||
"APPROVE": APPROVE,
|
||||
"TXPARAMLOAD": TXPARAMLOAD,
|
||||
"TXPARAMSIZE": TXPARAMSIZE,
|
||||
"TXPARAMCOPY": TXPARAMCOPY,
|
||||
}
|
||||
|
||||
// StringToOp finds the opcode whose name is stored in `str`.
|
||||
|
|
|
|||
Loading…
Reference in a new issue