Implement DUPN/SWAPN/EXCHANGE (EIP-8024)#1429
Conversation
3fc2e51 to
d7c8b12
Compare
Implement the DUPN, SWAPN, and EXCHANGE instructions for the Amsterdam revision as specified in EIP-8024. Based on #1429, converted from EVMC_EXPERIMENTAL to EVMC_AMSTERDAM. https://eips.ethereum.org/EIPS/eip-8024
Implement the DUPN, SWAPN, and EXCHANGE instructions for the Amsterdam revision as specified in EIP-8024. Based on #1429, converted from EVMC_EXPERIMENTAL to EVMC_AMSTERDAM. https://eips.ethereum.org/EIPS/eip-8024
# Conflicts: # lib/evmone/instructions_traits.hpp
The immediate_size-based guard skipped stack overflow checks for all instructions with immediates, including PUSH1..PUSH32. Use an explicit opcode check for DUPN/SWAPN/EXCHANGE instead. Fix the dupn_implicit_immediate test which used an invalid immediate due to ret_top() bytes following the DUPN opcode.
The existing swap<N> template avoids std::swap due to a clang missed optimization (llvm/llvm-project#59116). Apply the same manual component-wise swap in swapn and exchange for consistency.
Replace two-comparison validity check (x <= 0x5a || x >= 0x80) with a single unsigned range check on the invalid range. Compiles to one sub + one cmp instead of two cmp + or.
Replace the q < r branch with a sign-bit mask to select between the two triangle halves of the (n,m) pair space. Eliminates a branch in the EXCHANGE hot path. Verified exhaustively against the branching version for all 210 valid immediate values.
- SWAPN invalid immediate boundary sweep: all boundary values of the forbidden range (0x5b..0x7f) plus adjacent valid values (0x5a, 0x80). - DUPN stack overflow: fill stack to 1024 items then DUPN (which pushes).
Coverage analysis showed the stack underflow branches in swapn and exchange handlers were never taken. Add dedicated tests to achieve 100% branch coverage of all three EIP-8024 instruction handlers.
Replace separate is_valid + decode calls with decode_dupn_swapn_imm() and decode_exchange_imm() that return std::optional. Returns nullopt for invalid immediates, eliminating the separate validation step.
This reverts commit 622d97287f9459f3392d10b99a772b398b9d5ce9. # Conflicts: # test/utils/bytecode.hpp
The cleanup in 240e710 changed `static_cast<uint8_t>(imm + 145)` to `imm + 145`, losing the modular wrap. For imm >= 111 (e.g. 0x80=128), the result exceeds 255 instead of wrapping to the correct depth (128+145=273 instead of 17). Restore the uint8_t cast.
Replace decimal 145 with hex 0x91 for consistency with the other hex constants in the EIP-8024 encoding. Both GCC and clang already generate optimal code (branchless on clang, single range check on GCC).
- dupn_stack_overflow: document the UB risk in baseline invoke() when stack pointer is adjusted past the array end on DUPN overflow error. - dupn/swapn/exchange_out_of_gas: verify OOG at exactly gas-1 and success at exact gas cost.
- Add EIP8024_TEST macro to eliminate repeated is_advanced()/rev boilerplate - Group tests by opcode (DUPN, SWAPN, EXCHANGE) with section comments - Remove redundant local variables where execute() is called once - Add push1_stack_overflow_at_amsterdam regression test - Consistent comment style: imm=0xNN → decoded value
There was a problem hiding this comment.
Pull request overview
This PR adds support for EIP-8024 stack instructions (DUPN, SWAPN, EXCHANGE) to evmone by introducing the new opcodes, wiring them into the opcode/traits tables, implementing the baseline interpreter handlers (including immediate decoding), and adding dedicated unit tests for expected behavior and edge cases.
Changes:
- Add opcode identifiers for
0xe6..0xe8(DUPN/SWAPN/EXCHANGE) and register them in the opcode xmacro. - Define gas costs + traits (immediate size, stack delta, activation revision) and implement baseline execution handlers with immediate decoding + stack checks.
- Add a new unit test suite for EIP-8024 and adjust “undefined before activation” tests to use
EVMC_OSAKA.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/unittests/instructions_test.cpp | Extends traits validation to assert 1-byte immediates for DUPN/SWAPN/EXCHANGE. |
| test/unittests/evm_undefined_instructions_test.cpp | Updates pre-activation undefined-instruction checks to run at EVMC_OSAKA. |
| test/unittests/evm_eip8024_swapn_dupn_exchange_test.cpp | Adds baseline-focused EIP-8024 behavior/edge-case tests (skipping Advanced). |
| test/unittests/CMakeLists.txt | Includes the new EIP-8024 unit test source in the unit test target. |
| lib/evmone/instructions_xmacro.hpp | Changes 0xe6–0xe8 from undefined opcodes to named opcode identifiers. |
| lib/evmone/instructions_traits.hpp | Adds Amsterdam gas costs and traits entries for the new opcodes. |
| lib/evmone/instructions_opcodes.hpp | Introduces OP_DUPN, OP_SWAPN, OP_EXCHANGE enum values. |
| lib/evmone/instructions.hpp | Adds immediate decoders and baseline core implementations for DUPN/SWAPN/EXCHANGE. |
| lib/evmone/baseline_execution.cpp | Skips generic stack checks for these opcodes (stack depth depends on immediate). |
| lib/evmone/advanced_instructions.cpp | Forces these opcodes to op_undefined in the Advanced interpreter table. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The if constexpr guard excluding DUPN/SWAPN/EXCHANGE from stack checks is not needed because the traits already encode the correct behavior: - DUPN: stack_height_change=+1 enables overflow check (correct). - SWAPN/EXCHANGE: stack_height_change=0, stack_height_required=0, so both checks are compile-time eliminated. This also fixes a UB issue: the overflow check now runs in check_requirements (before invoke), preventing the stack pointer from being advanced past the array end on DUPN overflow. Remove the now-redundant overflow check from the dupn handler.
- Revert to TEST_P(evm, ...) with explicit rev/advanced guards - Add gas boundary tests (dupn/swapn/exchange_out_of_gas) - Add stack underflow tests for SWAPN and EXCHANGE - Add push1_stack_overflow_at_amsterdam regression test - Group tests by opcode with section comments
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Test that 0x5b in the immediate position of DUPN is a valid JUMPDEST in both Amsterdam (where DUPN is defined but 0x5b is a forbidden immediate) and pre-Amsterdam (where 0xe6 is an undefined opcode). JUMPDEST analysis is unchanged by EIP-8024.
Implements the DUPN (
0xe6), SWAPN (0xe7), and EXCHANGE (0xe8) stack instructions for the Amsterdam revision as specified in EIP-8024 (c0aa8d7).Changes
Opcodes & traits (
instructions_opcodes.hpp,instructions_traits.hpp,instructions_xmacro.hpp):OP_DUPN,OP_SWAPN,OP_EXCHANGEwith gas cost 3, activation atEVMC_AMSTERDAM.stack_height_change=+1for DUPN enables the overflow check incheck_requirements.stack_height_required=0for all three — underflow depends on the decoded immediate and is checked in each handler.Immediate decoding (
instructions.hpp):decode_dupn_swapn_imm()→std::optional<int>: validates the forbidden range (0x5b..0x7f) and decodesn = (imm + 0x91) mod 256in one step.decode_exchange_imm()→std::optional<std::pair<int, int>>: validates the forbidden range (0x52..0x7f) and decodes(n, m)via XOR + divmod-16 grid.Handlers (
instructions.hpp):dupn: decode, check underflow (n > stack_size), pushstack[n-1].swapn: decode, check underflow (n >= stack_size),fast_swap(top, stack[n]).exchange: decode, check underflow (m >= stack_size),fast_swap(stack[n], stack[m]).fast_swapuses the manual 4-limb copy to match the existingswap<N>workaround for clang (llvm#59116).Advanced interpreter (
advanced_instructions.cpp):op_undefined— not implemented in advanced (per maintainer decision).JUMPDEST analysis: Unchanged. The EIP's forbidden immediate range ensures
0x5bafter these opcodes always causes an invalid-immediate halt, preserving0x5bas a valid JUMPDEST.Tests
0x00immediate), invalid immediates (single + boundary sweep), stack overflow/underflow for all three opcodes, gas boundary (OOG at gas-1, success at exact gas).exchange_max_m: regression for decode off-by-one at(n=1, m=29)— the same bug that caused Nethermind's consensus failure on bal-devnet-2.dupn_immediate_0x5b_is_jumpdest: JUMPDEST compatibility —0x5bin the immediate position is a valid jump target in both Amsterdam and pre-Amsterdam.push1_stack_overflow: regression ensuring the stack overflow check is not bypassed for PUSH instructions.evm_undefined_instructions_test.cpp) updated fromEVMC_MAX_REVISIONtoEVMC_OSAKA.