mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-22 20:56:42 +00:00
core/vm: add EIP-8024 DUPN/SWAPN/EXCHANGE opcodes and tests
This commit is contained in:
parent
243407a3aa
commit
342d3f415c
6 changed files with 291 additions and 0 deletions
|
|
@ -42,6 +42,7 @@ var activators = map[int]func(*JumpTable){
|
|||
4762: enable4762,
|
||||
7702: enable7702,
|
||||
7939: enable7939,
|
||||
8024: enable8024,
|
||||
}
|
||||
|
||||
// EnableEIP enables the given EIP on the config.
|
||||
|
|
@ -342,6 +343,28 @@ func enable6780(jt *JumpTable) {
|
|||
}
|
||||
}
|
||||
|
||||
// enable8024 applies EIP-8024 (DUPN, SWAPN, EXCHANGE)
|
||||
func enable8024(jt *JumpTable) {
|
||||
jt[DUPN] = &operation{
|
||||
execute: opDupN,
|
||||
constantGas: GasFastestStep,
|
||||
minStack: minStack(1, 0),
|
||||
maxStack: maxStack(0, 1),
|
||||
}
|
||||
jt[SWAPN] = &operation{
|
||||
execute: opSwapN,
|
||||
constantGas: GasFastestStep,
|
||||
minStack: minStack(2, 0),
|
||||
maxStack: maxStack(0, 0),
|
||||
}
|
||||
jt[EXCHANGE] = &operation{
|
||||
execute: opExchange,
|
||||
constantGas: GasFastestStep,
|
||||
minStack: minStack(2, 0),
|
||||
maxStack: maxStack(0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func opExtCodeCopyEIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||
var (
|
||||
stack = scope.Stack
|
||||
|
|
|
|||
|
|
@ -149,6 +149,8 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon
|
|||
evm.precompiles = activePrecompiledContracts(evm.chainRules)
|
||||
|
||||
switch {
|
||||
case evm.chainRules.IsAmsterdam:
|
||||
evm.table = &amsterdamInstructionSet
|
||||
case evm.chainRules.IsOsaka:
|
||||
evm.table = &osakaInstructionSet
|
||||
case evm.chainRules.IsVerkle:
|
||||
|
|
|
|||
|
|
@ -920,6 +920,115 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro
|
|||
return nil, errStopToken
|
||||
}
|
||||
|
||||
func decodeSingle(x byte) int {
|
||||
if x <= 90 {
|
||||
return int(x) + 17
|
||||
}
|
||||
return int(x) - 20
|
||||
}
|
||||
|
||||
func decodePair(x byte) (int, int) {
|
||||
var k int
|
||||
if x <= 79 {
|
||||
k = int(x)
|
||||
} else {
|
||||
k = int(x) - 48
|
||||
}
|
||||
q, r := k/16, k%16
|
||||
if q < r {
|
||||
return q + 2, r + 2
|
||||
}
|
||||
return r + 2, 30 - q
|
||||
}
|
||||
|
||||
func opDupN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||
code := scope.Contract.Code
|
||||
i := *pc + 1
|
||||
|
||||
// Ensure an immediate byte exists after DUPN
|
||||
if i >= uint64(len(code)) {
|
||||
return nil, &ErrInvalidOpCode{opcode: INVALID}
|
||||
}
|
||||
x := code[i]
|
||||
|
||||
// This range is excluded to preserve compatibility with existing opcodes.
|
||||
if x > 90 && x < 128 {
|
||||
return nil, &ErrInvalidOpCode{opcode: OpCode(x)}
|
||||
}
|
||||
n := decodeSingle(x)
|
||||
|
||||
// DUPN duplicates the n-th stack item, so the stack must contain at least n elements.
|
||||
if scope.Stack.len() < n {
|
||||
return nil, &ErrStackUnderflow{stackLen: scope.Stack.len(), required: n}
|
||||
}
|
||||
|
||||
//The n‘th stack item is duplicated at the top of the stack.
|
||||
scope.Stack.push(scope.Stack.Back(n - 1))
|
||||
*pc += 2
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func opSwapN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||
code := scope.Contract.Code
|
||||
i := *pc + 1
|
||||
|
||||
// Ensure an immediate byte exists after SWAPN
|
||||
if i >= uint64(len(code)) {
|
||||
return nil, &ErrInvalidOpCode{opcode: INVALID}
|
||||
}
|
||||
x := code[i]
|
||||
|
||||
// This range is excluded to preserve compatibility with existing opcodes.
|
||||
if x > 90 && x < 128 {
|
||||
return nil, &ErrInvalidOpCode{opcode: OpCode(x)}
|
||||
}
|
||||
n := decodeSingle(x)
|
||||
|
||||
// SWAPN operates on the top and n+1 stack items, so the stack must contain at least n+1 elements.
|
||||
if scope.Stack.len() < n+1 {
|
||||
return nil, &ErrStackUnderflow{stackLen: scope.Stack.len(), required: n + 1}
|
||||
}
|
||||
|
||||
// The (n+1)‘th stack item is swapped with the top of the stack.
|
||||
indexTop := scope.Stack.len() - 1
|
||||
indexN := scope.Stack.len() - 1 - n
|
||||
scope.Stack.data[indexTop], scope.Stack.data[indexN] = scope.Stack.data[indexN], scope.Stack.data[indexTop]
|
||||
*pc += 2
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func opExchange(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||
code := scope.Contract.Code
|
||||
i := *pc + 1
|
||||
|
||||
// Ensure an immediate byte exists after EXCHANGE
|
||||
if i >= uint64(len(code)) {
|
||||
return nil, &ErrInvalidOpCode{opcode: INVALID}
|
||||
}
|
||||
x := code[i]
|
||||
|
||||
// This range is excluded both to preserve compatibility with existing opcodes
|
||||
// and to keep decode_pair’s 16-aligned arithmetic mapping valid (0–79, 128–255).
|
||||
if x > 79 && x < 128 {
|
||||
return nil, &ErrInvalidOpCode{opcode: OpCode(x)}
|
||||
}
|
||||
n, m := decodePair(x)
|
||||
need := max(n, m) + 1
|
||||
|
||||
// EXCHANGE operates on the (n+1)-th and (m+1)-th stack items,
|
||||
// so the stack must contain at least max(n, m)+1 elements.
|
||||
if scope.Stack.len() < need {
|
||||
return nil, &ErrStackUnderflow{stackLen: scope.Stack.len(), required: need}
|
||||
}
|
||||
|
||||
// The (n+1)‘th stack item is swapped with the (m+1)‘th stack item.
|
||||
indexN := scope.Stack.len() - 1 - n
|
||||
indexM := scope.Stack.len() - 1 - m
|
||||
scope.Stack.data[indexN], scope.Stack.data[indexM] = scope.Stack.data[indexM], scope.Stack.data[indexN]
|
||||
*pc += 2
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// following functions are used by the instruction jump table
|
||||
|
||||
// make log instruction function
|
||||
|
|
|
|||
|
|
@ -1008,3 +1008,151 @@ func TestOpCLZ(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEIP8024_Execution(t *testing.T) {
|
||||
evm := NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
codeHex string
|
||||
wantErr bool
|
||||
wantVals []uint64
|
||||
}{
|
||||
{
|
||||
name: "DUPN",
|
||||
codeHex: "60016000808080808080808080808080808080e600",
|
||||
wantVals: []uint64{
|
||||
1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SWAPN",
|
||||
codeHex: "600160008080808080808080808080808080806002e700",
|
||||
wantVals: []uint64{
|
||||
1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
2,
|
||||
},
|
||||
},
|
||||
{
|
||||
//NOTE: Spec test seems wrong. Added additional stack item to make it pass.
|
||||
name: "EXCHANGE",
|
||||
codeHex: "6000600160026003e801",
|
||||
wantVals: []uint64{3, 2, 0, 1},
|
||||
},
|
||||
{
|
||||
name: "INVALID_SWAPN_LOW",
|
||||
codeHex: "e75b",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "JUMP over INVALID_DUPN",
|
||||
codeHex: "600456e65b",
|
||||
wantErr: false,
|
||||
},
|
||||
// Additional test cases
|
||||
{
|
||||
name: "INVALID_DUPN_LOW",
|
||||
codeHex: "e65b",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "INVALID_EXCHANGE_LOW",
|
||||
codeHex: "e850",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "INVALID_DUPN_HIGH",
|
||||
codeHex: "e67f",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "INVALID_SWAPN_HIGH",
|
||||
codeHex: "e77f",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "INVALID_EXCHANGE_HIGH",
|
||||
codeHex: "e87f",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "UNDERFLOW_DUPN",
|
||||
codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe600", // (n=17, need 17 items have 16)
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "UNDERFLOW_SWAPN",
|
||||
codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe700", // (n=17, need 18 items have 17)
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
// Note: This is the spec test case, but we expect it to fail
|
||||
name: "UNDERFLOW_EXCHANGE",
|
||||
codeHex: "600060016002e801", // (n,m=2,3 need at least 4 items have 3)
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
code := common.FromHex(tc.codeHex)
|
||||
stack := newstack()
|
||||
pc := uint64(0)
|
||||
scope := &ScopeContext{Stack: stack, Contract: &Contract{Code: code}}
|
||||
var err error
|
||||
for pc < uint64(len(code)) && err == nil {
|
||||
op := code[pc]
|
||||
switch op {
|
||||
case 0x00:
|
||||
return
|
||||
case 0x60:
|
||||
_, err = opPush1(&pc, evm, scope)
|
||||
pc++
|
||||
case 0x80:
|
||||
dup1 := makeDup(1)
|
||||
_, err = dup1(&pc, evm, scope)
|
||||
pc++
|
||||
case 0x56:
|
||||
_, err = opJump(&pc, evm, scope)
|
||||
pc++
|
||||
case 0x5b:
|
||||
_, err = opJumpdest(&pc, evm, scope)
|
||||
pc++
|
||||
case 0xe6:
|
||||
_, err = opDupN(&pc, evm, scope)
|
||||
case 0xe7:
|
||||
_, err = opSwapN(&pc, evm, scope)
|
||||
case 0xe8:
|
||||
_, err = opExchange(&pc, evm, scope)
|
||||
default:
|
||||
err = &ErrInvalidOpCode{opcode: OpCode(op)}
|
||||
}
|
||||
}
|
||||
if tc.wantErr {
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
got := make([]uint64, 0, stack.len())
|
||||
for i := stack.len() - 1; i >= 0; i-- {
|
||||
got = append(got, stack.data[i].Uint64())
|
||||
}
|
||||
if len(got) != len(tc.wantVals) {
|
||||
t.Fatalf("stack len=%d; want %d", len(got), len(tc.wantVals))
|
||||
}
|
||||
for i := range got {
|
||||
if got[i] != tc.wantVals[i] {
|
||||
t.Fatalf("[%s] stack[%d]=%d; want %d\nstack=%v",
|
||||
tc.name, i, got[i], tc.wantVals[i], got)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ var (
|
|||
verkleInstructionSet = newVerkleInstructionSet()
|
||||
pragueInstructionSet = newPragueInstructionSet()
|
||||
osakaInstructionSet = newOsakaInstructionSet()
|
||||
amsterdamInstructionSet = newAmsterdamInstructionSet()
|
||||
)
|
||||
|
||||
// JumpTable contains the EVM opcodes supported at a given fork.
|
||||
|
|
@ -92,6 +93,12 @@ func newVerkleInstructionSet() JumpTable {
|
|||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
func newAmsterdamInstructionSet() JumpTable {
|
||||
instructionSet := newOsakaInstructionSet()
|
||||
enable8024(&instructionSet) // EIP-8024 (DUPN, SWAPN, EXCHANGE)
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
func newOsakaInstructionSet() JumpTable {
|
||||
instructionSet := newPragueInstructionSet()
|
||||
enable7939(&instructionSet) // EIP-7939 (CLZ opcode)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ func LookupInstructionSet(rules params.Rules) (JumpTable, error) {
|
|||
switch {
|
||||
case rules.IsVerkle:
|
||||
return newCancunInstructionSet(), errors.New("verkle-fork not defined yet")
|
||||
case rules.IsAmsterdam:
|
||||
return newAmsterdamInstructionSet(), nil
|
||||
case rules.IsOsaka:
|
||||
return newOsakaInstructionSet(), nil
|
||||
case rules.IsPrague:
|
||||
|
|
|
|||
Loading…
Reference in a new issue