core/vm: Switch to branchless normalization and extend EXCHANGE (#33869)

For bal-devnet-3 we need to update the EIP-8024 implementation to the
latest spec changes: https://github.com/ethereum/EIPs/pull/11306

> Note: I deleted tests not specified in the EIP bc maintaining them
through EIP changes is too error prone.
This commit is contained in:
Jonny Rhea 2026-03-04 03:34:27 -06:00 committed by GitHub
parent 6d99759f01
commit 814edc5308
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 48 additions and 83 deletions

View file

@ -946,24 +946,34 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro
return nil, errStopToken
}
// decodeSingle decodes the immediate operand of a backward-compatible DUPN or SWAPN instruction (EIP-8024)
// https://eips.ethereum.org/EIPS/eip-8024
func decodeSingle(x byte) int {
if x <= 90 {
return int(x) + 17
}
return int(x) - 20
// Depths 1-16 are already covered by the legacy opcodes. The forbidden byte range [91, 127] removes
// 37 values from the 256 possible immediates, leaving 219 usable values, so this encoding covers depths
// 17 through 235. The immediate is encoded as (x + 111) % 256, where 111 is chosen so that these values
// avoid the forbidden range. Decoding is simply the modular inverse (i.e. 111+145=256).
return (int(x) + 145) % 256
}
// decodePair decodes the immediate operand of a backward-compatible EXCHANGE
// instruction (EIP-8024) into stack indices (n, m) where 1 <= n < m
// and n + m <= 30. The forbidden byte range [82, 127] removes 46 values from
// the 256 possible immediates, leaving exactly 210 usable bytes.
// https://eips.ethereum.org/EIPS/eip-8024
func decodePair(x byte) (int, int) {
var k int
if x <= 79 {
k = int(x)
} else {
k = int(x) - 48
}
// XOR with 143 remaps the forbidden bytes [82, 127] to an unused corner
// of the 16x16 grid below.
k := int(x ^ 143)
// Split into row q and column r of a 16x16 grid. The 210 valid pairs
// occupy two triangles within this grid.
q, r := k/16, k%16
// Upper triangle (q < r): pairs where m <= 16, encoded directly as
// (q+1, r+1).
if q < r {
return q + 1, r + 1
}
// Lower triangle: pairs where m > 16, recovered as (r+1, 29-q).
return r + 1, 29 - q
}
@ -1034,8 +1044,8 @@ func opExchange(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
// This range is excluded both to preserve compatibility with existing opcodes
// and to keep decode_pairs 16-aligned arithmetic mapping valid (079, 128255).
if x > 79 && x < 128 {
// and to keep decode_pairs 16-aligned arithmetic mapping valid (081, 128255).
if x > 81 && x < 128 {
return nil, &ErrInvalidOpCode{opcode: OpCode(x)}
}
n, m := decodePair(x)

View file

@ -1022,16 +1022,7 @@ func TestEIP8024_Execution(t *testing.T) {
}{
{
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: "DUPN_MISSING_IMMEDIATE",
codeHex: "60016000808080808080808080808080808080e6",
codeHex: "60016000808080808080808080808080808080e680",
wantVals: []uint64{
1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@ -1040,7 +1031,7 @@ func TestEIP8024_Execution(t *testing.T) {
},
{
name: "SWAPN",
codeHex: "600160008080808080808080808080808080806002e700",
codeHex: "600160008080808080808080808080808080806002e780",
wantVals: []uint64{
1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@ -1048,22 +1039,23 @@ func TestEIP8024_Execution(t *testing.T) {
},
},
{
name: "SWAPN_MISSING_IMMEDIATE",
codeHex: "600160008080808080808080808080808080806002e7",
name: "EXCHANGE_MISSING_IMMEDIATE",
codeHex: "600260008080808080600160008080808080808080e8",
wantVals: []uint64{
1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2,
0, 0, 0, 0, 0, 0, 0, 0, 0,
2, // 10th from top
0, 0, 0, 0, 0, 0,
1, // bottom
},
},
{
name: "EXCHANGE",
codeHex: "600060016002e801",
codeHex: "600060016002e88e",
wantVals: []uint64{2, 0, 1},
},
{
name: "EXCHANGE_MISSING_IMMEDIATE",
codeHex: "600060006000600060006000600060006000600060006000600060006000600060006000600060006000600060006000600060006000600060016002e8",
name: "EXCHANGE",
codeHex: "600080808080808080808080808080808080808080808080808080808060016002e88f",
wantVals: []uint64{
2,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@ -1077,68 +1069,31 @@ func TestEIP8024_Execution(t *testing.T) {
wantOpcode: SWAPN,
},
{
name: "JUMP over INVALID_DUPN",
name: "JUMP_OVER_INVALID_DUPN",
codeHex: "600456e65b",
wantErr: nil,
},
{
name: "UNDERFLOW_DUPN_1",
codeHex: "6000808080808080808080808080808080e600",
name: "EXCHANGE",
codeHex: "60008080e88e15",
wantVals: []uint64{1, 0, 0},
},
{
name: "INVALID_EXCHANGE",
codeHex: "e852",
wantErr: &ErrInvalidOpCode{},
wantOpcode: EXCHANGE,
},
{
name: "UNDERFLOW_DUPN",
codeHex: "6000808080808080808080808080808080e680",
wantErr: &ErrStackUnderflow{},
wantOpcode: DUPN,
},
// Additional test cases
{
name: "INVALID_DUPN_LOW",
codeHex: "e65b",
wantErr: &ErrInvalidOpCode{},
wantOpcode: DUPN,
},
{
name: "INVALID_EXCHANGE_LOW",
codeHex: "e850",
wantErr: &ErrInvalidOpCode{},
wantOpcode: EXCHANGE,
},
{
name: "INVALID_DUPN_HIGH",
codeHex: "e67f",
wantErr: &ErrInvalidOpCode{},
wantOpcode: DUPN,
},
{
name: "INVALID_SWAPN_HIGH",
codeHex: "e77f",
wantErr: &ErrInvalidOpCode{},
wantOpcode: SWAPN,
},
{
name: "INVALID_EXCHANGE_HIGH",
codeHex: "e87f",
wantErr: &ErrInvalidOpCode{},
wantOpcode: EXCHANGE,
},
{
name: "UNDERFLOW_DUPN_2",
codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe600", // (n=17, need 17 items, have 16)
wantErr: &ErrStackUnderflow{},
wantOpcode: DUPN,
},
{
name: "UNDERFLOW_SWAPN",
codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe700", // (n=17, need 18 items, have 17)
wantErr: &ErrStackUnderflow{},
wantOpcode: SWAPN,
},
{
name: "UNDERFLOW_EXCHANGE",
codeHex: "60016002e801", // (n,m)=(1,2), need 3 items, have 2
wantErr: &ErrStackUnderflow{},
wantOpcode: EXCHANGE,
},
{
name: "PC_INCREMENT",
codeHex: "600060006000e80115",
codeHex: "600060006000e88e15",
wantVals: []uint64{1, 0, 0},
},
}