Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 24 additions & 28 deletions EIPS/eip-8024.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
eip: 8024
title: Backward compatible SWAPN, DUPN, EXCHANGE
description: Introduce additional instructions for manipulating the stack which allow accessing the stack at higher depths
author: Francisco Giordano (@frangio), Charles Cooper (@charles-cooper), Alex Beregszaszi (@axic)
author: Francisco Giordano (@frangio), Charles Cooper (@charles-cooper), Alex Beregszaszi (@axic), Paweł Bylica (@chfast)
discussions-to: https://ethereum-magicians.org/t/eip-8024-backward-compatible-swapn-dupn-exchange/25486
status: Review
type: Standards Track
Expand Down Expand Up @@ -61,7 +61,7 @@ Formally, when `code[pc]` is one of these opcodes, the VM executes as follows:
- `EXCHANGE`:
1. Charge 3 gas.
2. Let `x = code[pc + 1]`.
3. If `79 < x < 128`, halt with exceptional failure.
3. If `81 < x < 128`, halt with exceptional failure.
4. Let `n, m = decode_pair(x)`.
5. If `m + 1 > len(stack)`, halt with exceptional failure.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot @frangio :)

I have a question about the EXCHANGE depth check. If I understand correctly, the forbidden range implicitly ensures that decode_pair always produces n < m (with max n = 14, max m = 29). This invariant can be inferred from the encode_pair precondition, but not as a postcondition of decode_pair. Since the depth check only validates m, I believe it would be valuable for implementers to know that decode_pair also guarantees n < m, otherwise stack[top - n] could be an out-of-bounds access.

Would it make sense to either change the depth check to be defensive

Suggested change
5. If `m + 1 > len(stack)`, halt with exceptional failure.
5. If `max(n, m) + 1 > len(stack)`, halt with exceptional failure.

Or, since decode_pair should always produce n < m, maybe add an assert to make the invariant explicit like in encode_pair?

  def decode_pair(x: int) -> tuple[int, int]:
      # ...
      if q < r:
          n, m = q + 1, r + 1
      else:
          n, m = r + 1, 29 - q
      assert 1 <= n < m <= 29 and n + m <= 30
      return n, m

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry, my mistake. I thought the diff showed you removing the assertion on the input x rather than modifying it. So asserting the result seems unnecessary.

Copy link
Copy Markdown
Contributor Author

@frangio frangio Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think there is an opportunity to make this clearer, though I prefer to keep the instruction spec as it is now. See #11351

By the way I realized with the new EXCHANGE it's much simpler to check whether a pair is swappable. Basically just n + m <= 30.

5. Swap `stack[top - n]` and `stack[top - m]`.
Expand All @@ -78,14 +78,11 @@ The auxiliary functions `decode_single` and `decode_pair` are defined by the fol
```python
def decode_single(x: int) -> int:
assert 0 <= x <= 90 or 128 <= x <= 255
if x <= 90:
return x + 17
else:
return x - 20
return (x + 145) % 256

def decode_pair(x: int) -> tuple[int, int]:
assert 0 <= x <= 79 or 128 <= x <= 255
k = x if x <= 79 else x - 48
assert 0 <= x <= 81 or 128 <= x <= 255
k = x ^ 143
q, r = divmod(k, 16)
if q < r:
return q + 1, r + 1
Expand All @@ -98,19 +95,16 @@ Assemblers and compilers must emit a 1-byte immediate after each of these opcode
```python
def encode_single(n: int) -> int:
assert 17 <= n <= 235
if n <= 107:
return n - 17
else:
return n + 20
return (n + 111) % 256

def encode_pair(n: int, m: int) -> int:
assert 1 <= n <= 13 and n < m <= 29 and n + m <= 30
assert 1 <= n <= 14 and n < m <= 29 and n + m <= 30
if m <= 16:
q, r = n - 1, m - 1
else:
q, r = 29 - m, n - 1
k = 16 * q + r
return k if k <= 79 else k + 48
return k ^ 143
```

## Rationale
Expand Down Expand Up @@ -166,29 +160,31 @@ This has no effect on contracts that would never attempt to execute the opcodes

### Assembly/Disassembly

- `e600` is `[DUPN 17]`
- `e780` is `[SWAPN 108]`
- `e6005b` is `[DUPN 17, JUMPDEST]`
- `e680` is `[DUPN 17]`
- `e7db` is `[SWAPN 108]`
- `e6805b` is `[DUPN 17, JUMPDEST]`
- `e75b` is `[INVALID_SWAPN, JUMPDEST]`
- `e6605b` is `[INVALID_DUPN, PUSH1 0x5b]`
- `e7610000` is `[INVALID_SWAPN, PUSH2 0x0000]`
- `e65f` is `[INVALID_DUPN, PUSH0]`
- `e812` is `[EXCHANGE 2 3]`
- `e8d0` is `[EXCHANGE 1 19]`
- `e850` is `[INVALID_EXCHANGE, POP]`
- `e89d` is `[EXCHANGE 2 3]`
- `e82f` is `[EXCHANGE 1 19]`
- `e850` is `[EXCHANGE 14 16]`
- `e851` is `[EXCHANGE 14 15]`
- `e852` is `[INVALID_EXCHANGE, MSTORE]`

### Execution

- `60016000808080808080808080808080808080e600` results in 18 stack items, the top of the stack valued 1, the bottom of the stack valued 1, the rest valued 0
- `60016000808080808080808080808080808080e6` at the end of the code is equivalent to above
- `600160008080808080808080808080808080806002e700` results in 18 stack items, the top of the stack valued 1, the bottom of the stack valued 2, the rest valued 0
- `600160008080808080808080808080808080806002e7` at the end of the code is equivalent to above
- `600060016002e801` results in 3 stack items, from top to bottom: [2, 0, 1]
- `600060006000600060006000600060006000600060006000600060006000600060006000600060006000600060006000600060006000600060016002e8` results in 30 stack items, the top of the stack valued 2, the bottom of the stack valued 1, the rest valued 0
- `60016000808080808080808080808080808080e680` results in 18 stack items, the top of the stack valued 1, the bottom of the stack valued 1, the rest valued 0
- `600160008080808080808080808080808080806002e780` results in 18 stack items, the top of the stack valued 1, the bottom of the stack valued 2, the rest valued 0
- `600260008080808080600160008080808080808080e8` at the end of code results in 17 stack items, the bottom of the stack valued 1, the 10th stack item from the top valued 2, the rest valued 0
- `600060016002e88e` results in 3 stack items, from top to bottom: [2, 0, 1]
- `600080808080808080808080808080808080808080808080808080808060016002e88f` results in 30 stack items, the top of the stack valued 2, the bottom of the stack valued 1, the rest valued 0
- `e75b` reverts
- `600456e65b` executes successfully (`PUSH 04 JUMP INVALID_DUPN JUMPDEST`)
- `600060006000e80115` results in 3 stack items, the top of the stack valued 1, the rest valued 0
- `6000808080808080808080808080808080e600` results in exceptional halt
- `60008080e88e15` results in 3 stack items, the top of the stack valued 1, the rest valued 0
- `e852` reverts
- `6000808080808080808080808080808080e680` results in exceptional halt

## Security Considerations

Expand Down