@@ -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+
514671class 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 ,
0 commit comments