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,
|
4762: enable4762,
|
||||||
7702: enable7702,
|
7702: enable7702,
|
||||||
7939: enable7939,
|
7939: enable7939,
|
||||||
|
8024: enable8024,
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnableEIP enables the given EIP on the config.
|
// 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) {
|
func opExtCodeCopyEIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
var (
|
var (
|
||||||
stack = scope.Stack
|
stack = scope.Stack
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,8 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon
|
||||||
evm.precompiles = activePrecompiledContracts(evm.chainRules)
|
evm.precompiles = activePrecompiledContracts(evm.chainRules)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
case evm.chainRules.IsAmsterdam:
|
||||||
|
evm.table = &amsterdamInstructionSet
|
||||||
case evm.chainRules.IsOsaka:
|
case evm.chainRules.IsOsaka:
|
||||||
evm.table = &osakaInstructionSet
|
evm.table = &osakaInstructionSet
|
||||||
case evm.chainRules.IsVerkle:
|
case evm.chainRules.IsVerkle:
|
||||||
|
|
|
||||||
|
|
@ -920,6 +920,115 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro
|
||||||
return nil, errStopToken
|
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
|
// following functions are used by the instruction jump table
|
||||||
|
|
||||||
// make log instruction function
|
// 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()
|
verkleInstructionSet = newVerkleInstructionSet()
|
||||||
pragueInstructionSet = newPragueInstructionSet()
|
pragueInstructionSet = newPragueInstructionSet()
|
||||||
osakaInstructionSet = newOsakaInstructionSet()
|
osakaInstructionSet = newOsakaInstructionSet()
|
||||||
|
amsterdamInstructionSet = newAmsterdamInstructionSet()
|
||||||
)
|
)
|
||||||
|
|
||||||
// JumpTable contains the EVM opcodes supported at a given fork.
|
// JumpTable contains the EVM opcodes supported at a given fork.
|
||||||
|
|
@ -92,6 +93,12 @@ func newVerkleInstructionSet() JumpTable {
|
||||||
return validate(instructionSet)
|
return validate(instructionSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newAmsterdamInstructionSet() JumpTable {
|
||||||
|
instructionSet := newOsakaInstructionSet()
|
||||||
|
enable8024(&instructionSet) // EIP-8024 (DUPN, SWAPN, EXCHANGE)
|
||||||
|
return validate(instructionSet)
|
||||||
|
}
|
||||||
|
|
||||||
func newOsakaInstructionSet() JumpTable {
|
func newOsakaInstructionSet() JumpTable {
|
||||||
instructionSet := newPragueInstructionSet()
|
instructionSet := newPragueInstructionSet()
|
||||||
enable7939(&instructionSet) // EIP-7939 (CLZ opcode)
|
enable7939(&instructionSet) // EIP-7939 (CLZ opcode)
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ func LookupInstructionSet(rules params.Rules) (JumpTable, error) {
|
||||||
switch {
|
switch {
|
||||||
case rules.IsVerkle:
|
case rules.IsVerkle:
|
||||||
return newCancunInstructionSet(), errors.New("verkle-fork not defined yet")
|
return newCancunInstructionSet(), errors.New("verkle-fork not defined yet")
|
||||||
|
case rules.IsAmsterdam:
|
||||||
|
return newAmsterdamInstructionSet(), nil
|
||||||
case rules.IsOsaka:
|
case rules.IsOsaka:
|
||||||
return newOsakaInstructionSet(), nil
|
return newOsakaInstructionSet(), nil
|
||||||
case rules.IsPrague:
|
case rules.IsPrague:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue