mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 15:47:21 +00:00
core/vm: implement EIP-8024 (#33095)
EIP-8024: Backward compatible SWAPN, DUPN, EXCHANGE Introduces additional instructions for manipulating the stack which allow accessing the stack at higher depths. This is an initial implementation of the EIP, which is still in Review stage.
This commit is contained in:
parent
ed4d00fd83
commit
689ea10f35
3 changed files with 293 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
|
||||
|
|
|
|||
|
|
@ -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 + 1, r + 1
|
||||
}
|
||||
return r + 1, 29 - 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,164 @@ 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,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "EXCHANGE",
|
||||
codeHex: "600060016002e801",
|
||||
wantVals: []uint64{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,
|
||||
},
|
||||
{
|
||||
name: "UNDERFLOW_EXCHANGE",
|
||||
codeHex: "60016002e801", // (n,m)=(1,2), need 3 items, have 2
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "MISSING_IMMEDIATE_DUPN",
|
||||
codeHex: "e6", // no operand
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "MISSING_IMMEDIATE_SWAPN",
|
||||
codeHex: "e7", // no operand
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "MISSING_IMMEDIATE_EXCHANGE",
|
||||
codeHex: "e8", // no operand
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue