Skip to content

Commit 018f548

Browse files
committed
Merge remote-tracking branch 'upstream/eips/amsterdam/eip-8024' into devnets/bal/2
2 parents 3e2b631 + 75c22b3 commit 018f548

14 files changed

Lines changed: 2732 additions & 10 deletions

File tree

packages/testing/src/execution_testing/forks/forks/forks.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3393,16 +3393,20 @@ def is_deployed(cls) -> bool:
33933393
def valid_opcodes(
33943394
cls, *, block_number: int = 0, timestamp: int = 0
33953395
) -> List[Opcodes]:
3396-
"""Add SLOTNUM opcode for Amsterdam (EIP-7843)."""
3397-
return [Opcodes.SLOTNUM] + super(Amsterdam, cls).valid_opcodes(
3398-
block_number=block_number, timestamp=timestamp
3399-
)
3396+
"""Return list of Opcodes that are valid to work on this fork."""
3397+
del block_number, timestamp
3398+
return [
3399+
Opcodes.SWAPN,
3400+
Opcodes.DUPN,
3401+
Opcodes.EXCHANGE,
3402+
Opcodes.SLOTNUM,
3403+
] + super(Amsterdam, cls).valid_opcodes()
34003404

34013405
@classmethod
34023406
def opcode_gas_map(
34033407
cls, *, block_number: int = 0, timestamp: int = 0
34043408
) -> Dict[OpcodeBase, int | Callable[[OpcodeBase], int]]:
3405-
"""Add SLOTNUM opcode gas cost for Amsterdam (EIP-7843)."""
3409+
"""Add Amsterdam opcodes gas costs."""
34063410
gas_costs = cls.gas_costs(
34073411
block_number=block_number, timestamp=timestamp
34083412
)
@@ -3411,6 +3415,9 @@ def opcode_gas_map(
34113415
)
34123416
return {
34133417
**base_map,
3418+
Opcodes.SWAPN: gas_costs.G_VERY_LOW,
3419+
Opcodes.DUPN: gas_costs.G_VERY_LOW,
3420+
Opcodes.EXCHANGE: gas_costs.G_VERY_LOW,
34143421
Opcodes.SLOTNUM: gas_costs.G_BASE,
34153422
}
34163423

packages/testing/src/execution_testing/vm/opcodes.py

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,163 @@ def __call__(self, *args_t: OpcodeCallArg, **kwargs: Any) -> Bytecode:
511511
return pre_opcode_bytecode + self
512512

513513

514+
def _exchange_encoder(*args: int | bytes) -> bytes:
515+
"""
516+
Encoder for EXCHANGE opcode following encode_pair logic from EIP-8024.
517+
518+
Supports two modes:
519+
1. bytes input: Returns verbatim (for testing invalid immediate bytes)
520+
2. int input(s): Validates and encodes using encode_pair logic
521+
522+
Parameters
523+
----------
524+
*args : int | bytes
525+
Either bytes (returned verbatim) or 1-2 ints (encoded)
526+
527+
Returns
528+
-------
529+
bytes
530+
The immediate byte for EXCHANGE opcode
531+
532+
"""
533+
# If bytes are provided, return them verbatim (for testing invalid ranges)
534+
if len(args) == 1 and isinstance(args[0], bytes):
535+
return args[0]
536+
537+
# If one int is provided, it's the immediate byte value directly
538+
if len(args) == 1 and isinstance(args[0], int):
539+
return int.to_bytes(args[0], 1, "big")
540+
541+
# If two ints are provided, use encode_pair logic from EIP-8024
542+
if len(args) == 2:
543+
n, m = args
544+
if not isinstance(n, int) or not isinstance(m, int):
545+
raise TypeError(
546+
"EXCHANGE requires int arguments when using two parameters"
547+
)
548+
549+
# encode_pair logic from EIP-8024
550+
# n is first stack index (1-13), m is second (must be > n, up to 29)
551+
if not (1 <= n <= 13 and n < m <= 29 and n + m <= 30):
552+
raise ValueError(
553+
f"EXCHANGE indices must satisfy: 1 <= n <= 13, "
554+
f"n < m <= 29, n + m <= 30, got n={n}, m={m}"
555+
)
556+
if m <= 16:
557+
q, r = n - 1, m - 1
558+
else:
559+
q, r = 29 - m, n - 1
560+
k = 16 * q + r
561+
imm = k if k <= 79 else k + 48
562+
return int.to_bytes(imm, 1, "big")
563+
564+
raise ValueError(f"EXCHANGE requires 1 or 2 arguments, got {len(args)}")
565+
566+
567+
def _dupn_swapn_encoder(*args: int | bytes) -> bytes:
568+
"""
569+
Encoder for DUPN/SWAPN opcodes following encode_single logic from EIP-8024.
570+
571+
Supports two modes:
572+
1. bytes input: Returns verbatim (for testing invalid immediate bytes)
573+
2. int input: Validates and encodes using encode_single logic
574+
575+
Parameters
576+
----------
577+
*args : int | bytes
578+
Either bytes (returned verbatim) or a single int (encoded)
579+
580+
Returns
581+
-------
582+
bytes
583+
The immediate byte for DUPN/SWAPN opcode
584+
585+
"""
586+
if len(args) != 1:
587+
raise ValueError(
588+
f"DUPN/SWAPN requires exactly 1 argument, got {len(args)}"
589+
)
590+
591+
arg = args[0]
592+
593+
# If bytes are provided, return them verbatim (for testing invalid ranges)
594+
if isinstance(arg, bytes):
595+
return arg
596+
597+
# If int is provided, use encode_single logic from EIP-8024
598+
if isinstance(arg, int):
599+
# encode_single logic: n is stack index (17-235)
600+
if not (17 <= arg <= 235):
601+
raise ValueError(
602+
f"DUPN/SWAPN index must be in range [17, 235], got {arg}"
603+
)
604+
if arg <= 107:
605+
imm = arg - 17
606+
else:
607+
imm = arg + 20
608+
return int.to_bytes(imm, 1, "big")
609+
610+
raise TypeError(
611+
f"DUPN/SWAPN requires int or bytes argument, got {type(arg)}"
612+
)
613+
614+
615+
def _swapn_stack_properties_modifier(data: bytes) -> tuple[int, int, int, int]:
616+
n = int.from_bytes(data, "big")
617+
if n <= 90:
618+
min_stack_height = n + 17
619+
elif n >= 128:
620+
min_stack_height = n - 20
621+
else:
622+
# Undefined behavior
623+
min_stack_height = 0
624+
return (
625+
0,
626+
0,
627+
min_stack_height + 1,
628+
min_stack_height + 1,
629+
)
630+
631+
632+
def _dupn_stack_properties_modifier(data: bytes) -> tuple[int, int, int, int]:
633+
n = int.from_bytes(data, "big")
634+
if n <= 90:
635+
min_stack_height = n + 17
636+
elif n >= 128:
637+
min_stack_height = n - 20
638+
else:
639+
# Undefined behavior
640+
min_stack_height = 0
641+
return (
642+
0,
643+
1,
644+
min_stack_height,
645+
min_stack_height + 1,
646+
)
647+
648+
649+
def _exchange_stack_properties_modifier(
650+
data: bytes,
651+
) -> tuple[int, int, int, int]:
652+
n = int.from_bytes(data, "big")
653+
if n > 79 and n < 128:
654+
# Undefined behavior
655+
min_stack_height = 0
656+
else:
657+
k = n if n <= 79 else n - 48
658+
q, r = divmod(k, 16)
659+
if q < r:
660+
min_stack_height = max(q + 1, r + 1)
661+
else:
662+
min_stack_height = max(r + 1, 29 - q)
663+
return (
664+
0,
665+
0,
666+
min_stack_height + 1,
667+
min_stack_height + 1,
668+
)
669+
670+
514671
class Opcodes(Opcode, Enum):
515672
"""
516673
Enum containing all known opcodes.
@@ -5110,6 +5267,118 @@ class Opcodes(Opcode, Enum):
51105267
Source: [evm.codes/#A4](https://www.evm.codes/#A4)
51115268
"""
51125269

5270+
DUPN = Opcode(
5271+
0xE6,
5272+
pushed_stack_items=1,
5273+
data_portion_length=1,
5274+
data_portion_formatter=_dupn_swapn_encoder,
5275+
stack_properties_modifier=_dupn_stack_properties_modifier,
5276+
)
5277+
"""
5278+
DUPN()
5279+
----
5280+
5281+
Description
5282+
----
5283+
5284+
- deduct 3 gas
5285+
- read uint8 operand imm
5286+
- n = imm + 1
5287+
- n'th (1-based) stack item is duplicated at the top of the stack
5288+
- Stack validation: stack_height >= n
5289+
5290+
5291+
Inputs
5292+
----
5293+
5294+
Outputs
5295+
----
5296+
5297+
Fork
5298+
----
5299+
Amsterdam
5300+
5301+
Gas
5302+
----
5303+
3
5304+
5305+
"""
5306+
5307+
SWAPN = Opcode(
5308+
0xE7,
5309+
data_portion_length=1,
5310+
data_portion_formatter=_dupn_swapn_encoder,
5311+
stack_properties_modifier=_swapn_stack_properties_modifier,
5312+
)
5313+
"""
5314+
SWAPN()
5315+
----
5316+
5317+
Description
5318+
----
5319+
5320+
- deduct 3 gas
5321+
- read uint8 operand imm
5322+
- n = imm + 1
5323+
- n + 1th stack item is swapped with the top stack item (1-based).
5324+
- Stack validation: stack_height >= n + 1
5325+
5326+
5327+
Inputs
5328+
----
5329+
5330+
Outputs
5331+
----
5332+
5333+
Fork
5334+
----
5335+
Amsterdam
5336+
5337+
Gas
5338+
----
5339+
3
5340+
5341+
"""
5342+
5343+
EXCHANGE = Opcode(
5344+
0xE8,
5345+
data_portion_length=1,
5346+
data_portion_formatter=_exchange_encoder,
5347+
stack_properties_modifier=_exchange_stack_properties_modifier,
5348+
)
5349+
"""
5350+
EXCHANGE[x, y]
5351+
----
5352+
5353+
Description
5354+
----
5355+
Exchanges two stack positions. Two nybbles, n is high 4 bits + 1,
5356+
then m is 4 low bits + 1.
5357+
Exchanges the n+1'th item with the n + m + 1 item.
5358+
5359+
Inputs x and y when the opcode is used as `EXCHANGE[x, y]`, are equal to:
5360+
- x = n + 1
5361+
- y = n + m + 1
5362+
Which each equals to 1-based stack positions swapped.
5363+
5364+
Inputs
5365+
----
5366+
n + m + 1, or ((imm >> 4) + (imm &0x0F) + 3) from the raw immediate,
5367+
5368+
Outputs
5369+
----
5370+
n + m + 1, or ((imm >> 4) + (imm &0x0F) + 3) from the raw immediate,
5371+
5372+
Fork
5373+
----
5374+
Amsterdam
5375+
5376+
Gas
5377+
----
5378+
3
5379+
5380+
"""
5381+
51135382
CREATE = Opcode(
51145383
0xF0,
51155384
popped_stack_items=3,

src/ethereum/forks/amsterdam/vm/instructions/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ class Ops(enum.Enum):
189189
SWAP15 = 0x9E
190190
SWAP16 = 0x9F
191191

192+
# EIP-8024: Stack access instructions
193+
DUPN = 0xE6
194+
SWAPN = 0xE7
195+
EXCHANGE = 0xE8
196+
192197
# Memory Operations
193198
MLOAD = 0x51
194199
MSTORE = 0x52
@@ -352,6 +357,9 @@ class Ops(enum.Enum):
352357
Ops.SWAP14: stack_instructions.swap14,
353358
Ops.SWAP15: stack_instructions.swap15,
354359
Ops.SWAP16: stack_instructions.swap16,
360+
Ops.DUPN: stack_instructions.dupn,
361+
Ops.SWAPN: stack_instructions.swapn,
362+
Ops.EXCHANGE: stack_instructions.exchange,
355363
Ops.LOG0: log_instructions.log0,
356364
Ops.LOG1: log_instructions.log1,
357365
Ops.LOG2: log_instructions.log2,

0 commit comments

Comments
 (0)