Skip to content

Commit ad3a93f

Browse files
jrheaprestoalvarez
authored andcommitted
core/vm: implement EIP-8024 (ethereum#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.
1 parent cf820ff commit ad3a93f

File tree

3 files changed

+293
-0
lines changed

3 files changed

+293
-0
lines changed

core/vm/eips.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ var activators = map[int]func(*JumpTable){
4242
4762: enable4762,
4343
7702: enable7702,
4444
7939: enable7939,
45+
8024: enable8024,
4546
}
4647

4748
// EnableEIP enables the given EIP on the config.
@@ -342,6 +343,28 @@ func enable6780(jt *JumpTable) {
342343
}
343344
}
344345

346+
// enable8024 applies EIP-8024 (DUPN, SWAPN, EXCHANGE)
347+
func enable8024(jt *JumpTable) {
348+
jt[DUPN] = &operation{
349+
execute: opDupN,
350+
constantGas: GasFastestStep,
351+
minStack: minStack(1, 0),
352+
maxStack: maxStack(0, 1),
353+
}
354+
jt[SWAPN] = &operation{
355+
execute: opSwapN,
356+
constantGas: GasFastestStep,
357+
minStack: minStack(2, 0),
358+
maxStack: maxStack(0, 0),
359+
}
360+
jt[EXCHANGE] = &operation{
361+
execute: opExchange,
362+
constantGas: GasFastestStep,
363+
minStack: minStack(2, 0),
364+
maxStack: maxStack(0, 0),
365+
}
366+
}
367+
345368
func opExtCodeCopyEIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
346369
var (
347370
stack = scope.Stack

core/vm/instructions.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,115 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro
920920
return nil, errStopToken
921921
}
922922

923+
func decodeSingle(x byte) int {
924+
if x <= 90 {
925+
return int(x) + 17
926+
}
927+
return int(x) - 20
928+
}
929+
930+
func decodePair(x byte) (int, int) {
931+
var k int
932+
if x <= 79 {
933+
k = int(x)
934+
} else {
935+
k = int(x) - 48
936+
}
937+
q, r := k/16, k%16
938+
if q < r {
939+
return q + 1, r + 1
940+
}
941+
return r + 1, 29 - q
942+
}
943+
944+
func opDupN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
945+
code := scope.Contract.Code
946+
i := *pc + 1
947+
948+
// Ensure an immediate byte exists after DUPN
949+
if i >= uint64(len(code)) {
950+
return nil, &ErrInvalidOpCode{opcode: INVALID}
951+
}
952+
x := code[i]
953+
954+
// This range is excluded to preserve compatibility with existing opcodes.
955+
if x > 90 && x < 128 {
956+
return nil, &ErrInvalidOpCode{opcode: OpCode(x)}
957+
}
958+
n := decodeSingle(x)
959+
960+
// DUPN duplicates the n'th stack item, so the stack must contain at least n elements.
961+
if scope.Stack.len() < n {
962+
return nil, &ErrStackUnderflow{stackLen: scope.Stack.len(), required: n}
963+
}
964+
965+
//The n‘th stack item is duplicated at the top of the stack.
966+
scope.Stack.push(scope.Stack.Back(n - 1))
967+
*pc += 2
968+
return nil, nil
969+
}
970+
971+
func opSwapN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
972+
code := scope.Contract.Code
973+
i := *pc + 1
974+
975+
// Ensure an immediate byte exists after SWAPN
976+
if i >= uint64(len(code)) {
977+
return nil, &ErrInvalidOpCode{opcode: INVALID}
978+
}
979+
x := code[i]
980+
981+
// This range is excluded to preserve compatibility with existing opcodes.
982+
if x > 90 && x < 128 {
983+
return nil, &ErrInvalidOpCode{opcode: OpCode(x)}
984+
}
985+
n := decodeSingle(x)
986+
987+
// SWAPN operates on the top and n+1 stack items, so the stack must contain at least n+1 elements.
988+
if scope.Stack.len() < n+1 {
989+
return nil, &ErrStackUnderflow{stackLen: scope.Stack.len(), required: n + 1}
990+
}
991+
992+
// The (n+1)‘th stack item is swapped with the top of the stack.
993+
indexTop := scope.Stack.len() - 1
994+
indexN := scope.Stack.len() - 1 - n
995+
scope.Stack.data[indexTop], scope.Stack.data[indexN] = scope.Stack.data[indexN], scope.Stack.data[indexTop]
996+
*pc += 2
997+
return nil, nil
998+
}
999+
1000+
func opExchange(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
1001+
code := scope.Contract.Code
1002+
i := *pc + 1
1003+
1004+
// Ensure an immediate byte exists after EXCHANGE
1005+
if i >= uint64(len(code)) {
1006+
return nil, &ErrInvalidOpCode{opcode: INVALID}
1007+
}
1008+
x := code[i]
1009+
1010+
// This range is excluded both to preserve compatibility with existing opcodes
1011+
// and to keep decode_pair’s 16-aligned arithmetic mapping valid (0–79, 128–255).
1012+
if x > 79 && x < 128 {
1013+
return nil, &ErrInvalidOpCode{opcode: OpCode(x)}
1014+
}
1015+
n, m := decodePair(x)
1016+
need := max(n, m) + 1
1017+
1018+
// EXCHANGE operates on the (n+1)'th and (m+1)'th stack items,
1019+
// so the stack must contain at least max(n, m)+1 elements.
1020+
if scope.Stack.len() < need {
1021+
return nil, &ErrStackUnderflow{stackLen: scope.Stack.len(), required: need}
1022+
}
1023+
1024+
// The (n+1)‘th stack item is swapped with the (m+1)‘th stack item.
1025+
indexN := scope.Stack.len() - 1 - n
1026+
indexM := scope.Stack.len() - 1 - m
1027+
scope.Stack.data[indexN], scope.Stack.data[indexM] = scope.Stack.data[indexM], scope.Stack.data[indexN]
1028+
*pc += 2
1029+
return nil, nil
1030+
}
1031+
9231032
// following functions are used by the instruction jump table
9241033

9251034
// make log instruction function

core/vm/instructions_test.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,3 +1008,164 @@ func TestOpCLZ(t *testing.T) {
10081008
}
10091009
}
10101010
}
1011+
1012+
func TestEIP8024_Execution(t *testing.T) {
1013+
evm := NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
1014+
1015+
tests := []struct {
1016+
name string
1017+
codeHex string
1018+
wantErr bool
1019+
wantVals []uint64
1020+
}{
1021+
{
1022+
name: "DUPN",
1023+
codeHex: "60016000808080808080808080808080808080e600",
1024+
wantVals: []uint64{
1025+
1,
1026+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1027+
1,
1028+
},
1029+
},
1030+
{
1031+
name: "SWAPN",
1032+
codeHex: "600160008080808080808080808080808080806002e700",
1033+
wantVals: []uint64{
1034+
1,
1035+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1036+
2,
1037+
},
1038+
},
1039+
{
1040+
name: "EXCHANGE",
1041+
codeHex: "600060016002e801",
1042+
wantVals: []uint64{2, 0, 1},
1043+
},
1044+
{
1045+
name: "INVALID_SWAPN_LOW",
1046+
codeHex: "e75b",
1047+
wantErr: true,
1048+
},
1049+
{
1050+
name: "JUMP over INVALID_DUPN",
1051+
codeHex: "600456e65b",
1052+
wantErr: false,
1053+
},
1054+
// Additional test cases
1055+
{
1056+
name: "INVALID_DUPN_LOW",
1057+
codeHex: "e65b",
1058+
wantErr: true,
1059+
},
1060+
{
1061+
name: "INVALID_EXCHANGE_LOW",
1062+
codeHex: "e850",
1063+
wantErr: true,
1064+
},
1065+
{
1066+
name: "INVALID_DUPN_HIGH",
1067+
codeHex: "e67f",
1068+
wantErr: true,
1069+
},
1070+
{
1071+
name: "INVALID_SWAPN_HIGH",
1072+
codeHex: "e77f",
1073+
wantErr: true,
1074+
},
1075+
{
1076+
name: "INVALID_EXCHANGE_HIGH",
1077+
codeHex: "e87f",
1078+
wantErr: true,
1079+
},
1080+
{
1081+
name: "UNDERFLOW_DUPN",
1082+
codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe600", // (n=17, need 17 items, have 16)
1083+
wantErr: true,
1084+
},
1085+
{
1086+
name: "UNDERFLOW_SWAPN",
1087+
codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe700", // (n=17, need 18 items, have 17)
1088+
wantErr: true,
1089+
},
1090+
{
1091+
name: "UNDERFLOW_EXCHANGE",
1092+
codeHex: "60016002e801", // (n,m)=(1,2), need 3 items, have 2
1093+
wantErr: true,
1094+
},
1095+
{
1096+
name: "MISSING_IMMEDIATE_DUPN",
1097+
codeHex: "e6", // no operand
1098+
wantErr: true,
1099+
},
1100+
{
1101+
name: "MISSING_IMMEDIATE_SWAPN",
1102+
codeHex: "e7", // no operand
1103+
wantErr: true,
1104+
},
1105+
{
1106+
name: "MISSING_IMMEDIATE_EXCHANGE",
1107+
codeHex: "e8", // no operand
1108+
wantErr: true,
1109+
},
1110+
}
1111+
1112+
for _, tc := range tests {
1113+
t.Run(tc.name, func(t *testing.T) {
1114+
code := common.FromHex(tc.codeHex)
1115+
stack := newstack()
1116+
pc := uint64(0)
1117+
scope := &ScopeContext{Stack: stack, Contract: &Contract{Code: code}}
1118+
var err error
1119+
for pc < uint64(len(code)) && err == nil {
1120+
op := code[pc]
1121+
switch op {
1122+
case 0x00:
1123+
return
1124+
case 0x60:
1125+
_, err = opPush1(&pc, evm, scope)
1126+
pc++
1127+
case 0x80:
1128+
dup1 := makeDup(1)
1129+
_, err = dup1(&pc, evm, scope)
1130+
pc++
1131+
case 0x56:
1132+
_, err = opJump(&pc, evm, scope)
1133+
pc++
1134+
case 0x5b:
1135+
_, err = opJumpdest(&pc, evm, scope)
1136+
pc++
1137+
case 0xe6:
1138+
_, err = opDupN(&pc, evm, scope)
1139+
case 0xe7:
1140+
_, err = opSwapN(&pc, evm, scope)
1141+
case 0xe8:
1142+
_, err = opExchange(&pc, evm, scope)
1143+
default:
1144+
err = &ErrInvalidOpCode{opcode: OpCode(op)}
1145+
}
1146+
}
1147+
if tc.wantErr {
1148+
if err == nil {
1149+
t.Fatalf("expected error, got nil")
1150+
}
1151+
return
1152+
}
1153+
if err != nil {
1154+
t.Fatalf("unexpected error: %v", err)
1155+
}
1156+
got := make([]uint64, 0, stack.len())
1157+
for i := stack.len() - 1; i >= 0; i-- {
1158+
got = append(got, stack.data[i].Uint64())
1159+
}
1160+
if len(got) != len(tc.wantVals) {
1161+
t.Fatalf("stack len=%d; want %d", len(got), len(tc.wantVals))
1162+
}
1163+
for i := range got {
1164+
if got[i] != tc.wantVals[i] {
1165+
t.Fatalf("[%s] stack[%d]=%d; want %d\nstack=%v",
1166+
tc.name, i, got[i], tc.wantVals[i], got)
1167+
}
1168+
}
1169+
})
1170+
}
1171+
}

0 commit comments

Comments
 (0)