@@ -92,6 +92,86 @@ pub fn makeSwapFn(comptime n: u8) InstructionFn {
9292 }.op ;
9393}
9494
95+ /// DUPN (0xE6): Duplicate item at depth n from top (EIP-8024, Amsterdam+).
96+ /// Reads 1 immediate byte `imm`. Valid range: 0..=90 or 128..=255 (91..=127 → exceptional halt).
97+ /// Depth n = decode_single(imm) = (imm + 145) % 256, with 17 <= n <= 235.
98+ /// Gas: 3 (G_VERYLOW, charged by dispatch).
99+ pub fn opDupN (ctx : * InstructionContext ) void {
100+ const stack = & ctx .interpreter .stack ;
101+ const imm = ctx .interpreter .bytecode .readImmediates (1 )[0 ];
102+ ctx .interpreter .bytecode .relativeJump (1 );
103+ // EIP-8024: immediates 91..=127 (0x5B..=0x7F) are invalid per decode_single.
104+ if (imm > 90 and imm < 128 ) {
105+ ctx .interpreter .halt (.invalid_opcode );
106+ return ;
107+ }
108+ const n : usize = (@as (usize , imm ) + 145 ) % 256 ;
109+ if (! stack .hasItems (n )) {
110+ ctx .interpreter .halt (.stack_underflow );
111+ return ;
112+ }
113+ if (! stack .hasSpace (1 )) {
114+ ctx .interpreter .halt (.stack_overflow );
115+ return ;
116+ }
117+ stack .dupUnsafe (n );
118+ }
119+
120+ /// SWAPN (0xE7): Swap top with item at 0-indexed depth n (EIP-8024, Amsterdam+).
121+ /// Reads 1 immediate byte `imm`. Valid range: 0..=90 or 128..=255 (91..=127 → exceptional halt).
122+ /// Depth n = decode_single(imm) = (imm + 145) % 256, with 17 <= n <= 235.
123+ /// Gas: 3 (G_VERYLOW, charged by dispatch).
124+ pub fn opSwapN (ctx : * InstructionContext ) void {
125+ const stack = & ctx .interpreter .stack ;
126+ const imm = ctx .interpreter .bytecode .readImmediates (1 )[0 ];
127+ ctx .interpreter .bytecode .relativeJump (1 );
128+ // EIP-8024: immediates 91..=127 (0x5B..=0x7F) are invalid per decode_single.
129+ if (imm > 90 and imm < 128 ) {
130+ ctx .interpreter .halt (.invalid_opcode );
131+ return ;
132+ }
133+ const n : usize = (@as (usize , imm ) + 145 ) % 256 ;
134+ if (! stack .hasItems (n + 1 )) {
135+ ctx .interpreter .halt (.stack_underflow );
136+ return ;
137+ }
138+ stack .swapUnsafe (n );
139+ }
140+
141+ /// EXCHANGE (0xE8): Swap two non-top stack items (EIP-8024, Amsterdam+).
142+ /// Immediate byte `x` decoded via EIP-8024 decode_pair:
143+ /// k = x ^ 143; q = k >> 4; r = k & 0xF
144+ /// if q < r: n = q+1, m = r+1
145+ /// else: n = r+1, m = 29-q
146+ /// Valid range: 0..=81 or 128..=255 (82..=127 → exceptional failure).
147+ /// Swaps stack[top - n] and stack[top - m], needs m+1 items (n < m always).
148+ /// Gas: 3 (G_VERYLOW, charged by dispatch).
149+ pub fn opExchange (ctx : * InstructionContext ) void {
150+ const stack = & ctx .interpreter .stack ;
151+ const imm = ctx .interpreter .bytecode .readImmediates (1 )[0 ];
152+ ctx .interpreter .bytecode .relativeJump (1 );
153+ // Immediates 82..=127 (0x52..=0x7F) are invalid per EIP-8024.
154+ if (imm >= 82 and imm <= 127 ) {
155+ ctx .interpreter .halt (.invalid_opcode );
156+ return ;
157+ }
158+ // decode_pair: k = imm XOR 143, q = k >> 4, r = k & 0xF
159+ const k : usize = @as (usize , imm ) ^ 143 ;
160+ const q : usize = k >> 4 ;
161+ const r : usize = k & 0xF ;
162+ const n : usize = if (q < r ) q + 1 else r + 1 ; // smaller depth (1..14)
163+ const m : usize = if (q < r ) r + 1 else 29 - q ; // larger depth (n < m)
164+ // Need m+1 items on stack.
165+ if (! stack .hasItems (m + 1 )) {
166+ ctx .interpreter .halt (.stack_underflow );
167+ return ;
168+ }
169+ const top_idx = stack .length - 1 ;
170+ const tmp = stack .data [top_idx - n ];
171+ stack .data [top_idx - n ] = stack .data [top_idx - m ];
172+ stack .data [top_idx - m ] = tmp ;
173+ }
174+
95175test {
96176 _ = @import ("stack_tests.zig" );
97177}
0 commit comments