@@ -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.
@@ -5080,6 +5237,118 @@ class Opcodes(Opcode, Enum):
50805237 Source: [evm.codes/#A4](https://www.evm.codes/#A4)
50815238 """
50825239
5240+ DUPN = Opcode (
5241+ 0xE6 ,
5242+ pushed_stack_items = 1 ,
5243+ data_portion_length = 1 ,
5244+ data_portion_formatter = _dupn_swapn_encoder ,
5245+ stack_properties_modifier = _dupn_stack_properties_modifier ,
5246+ )
5247+ """
5248+ DUPN()
5249+ ----
5250+
5251+ Description
5252+ ----
5253+
5254+ - deduct 3 gas
5255+ - read uint8 operand imm
5256+ - n = imm + 1
5257+ - n'th (1-based) stack item is duplicated at the top of the stack
5258+ - Stack validation: stack_height >= n
5259+
5260+
5261+ Inputs
5262+ ----
5263+
5264+ Outputs
5265+ ----
5266+
5267+ Fork
5268+ ----
5269+ Amsterdam
5270+
5271+ Gas
5272+ ----
5273+ 3
5274+
5275+ """
5276+
5277+ SWAPN = Opcode (
5278+ 0xE7 ,
5279+ data_portion_length = 1 ,
5280+ data_portion_formatter = _dupn_swapn_encoder ,
5281+ stack_properties_modifier = _swapn_stack_properties_modifier ,
5282+ )
5283+ """
5284+ SWAPN()
5285+ ----
5286+
5287+ Description
5288+ ----
5289+
5290+ - deduct 3 gas
5291+ - read uint8 operand imm
5292+ - n = imm + 1
5293+ - n + 1th stack item is swapped with the top stack item (1-based).
5294+ - Stack validation: stack_height >= n + 1
5295+
5296+
5297+ Inputs
5298+ ----
5299+
5300+ Outputs
5301+ ----
5302+
5303+ Fork
5304+ ----
5305+ Amsterdam
5306+
5307+ Gas
5308+ ----
5309+ 3
5310+
5311+ """
5312+
5313+ EXCHANGE = Opcode (
5314+ 0xE8 ,
5315+ data_portion_length = 1 ,
5316+ data_portion_formatter = _exchange_encoder ,
5317+ stack_properties_modifier = _exchange_stack_properties_modifier ,
5318+ )
5319+ """
5320+ EXCHANGE[x, y]
5321+ ----
5322+
5323+ Description
5324+ ----
5325+ Exchanges two stack positions. Two nybbles, n is high 4 bits + 1,
5326+ then m is 4 low bits + 1.
5327+ Exchanges the n+1'th item with the n + m + 1 item.
5328+
5329+ Inputs x and y when the opcode is used as `EXCHANGE[x, y]`, are equal to:
5330+ - x = n + 1
5331+ - y = n + m + 1
5332+ Which each equals to 1-based stack positions swapped.
5333+
5334+ Inputs
5335+ ----
5336+ n + m + 1, or ((imm >> 4) + (imm &0x0F) + 3) from the raw immediate,
5337+
5338+ Outputs
5339+ ----
5340+ n + m + 1, or ((imm >> 4) + (imm &0x0F) + 3) from the raw immediate,
5341+
5342+ Fork
5343+ ----
5344+ Amsterdam
5345+
5346+ Gas
5347+ ----
5348+ 3
5349+
5350+ """
5351+
50835352 CREATE = Opcode (
50845353 0xF0 ,
50855354 popped_stack_items = 3 ,
0 commit comments