Skip to main content

revmc_codegen/bytecode/passes/
const_fold.rs

1//! Constant folding for EVM arithmetic during abstract interpretation.
2
3use super::block_analysis::AbsValue;
4use crate::{
5    InstData,
6    bytecode::{U256Imm, U256Interner},
7};
8use revm_bytecode::opcode as op;
9use revm_interpreter::instructions::i256::{i256_cmp, i256_div, i256_mod};
10use revm_primitives::U256;
11use std::cmp::Ordering;
12
13/// Returns the compiler gas cost of constant-folding the given opcode with the given inputs.
14///
15/// Uses the real EVM gas schedule: most arithmetic is cheap (3–5 gas), but `EXP` costs
16/// `10 + 50 * byte_size(exponent)` which can be weaponised with large exponents.
17///
18/// Returns `None` for opcodes that `try_const_fold` does not handle.
19pub(crate) fn const_fold_gas(
20    opcode: u8,
21    inputs: &[AbsValue],
22    interner: &U256Interner,
23) -> Option<u64> {
24    let gas: u64 = match opcode {
25        op::CODESIZE | op::PC => 2,
26        op::ISZERO | op::NOT => 3,
27        op::CLZ => 3,
28        op::ADD | op::SUB => 3,
29        op::MUL | op::DIV | op::SDIV | op::MOD | op::SMOD => 5,
30        op::ADDMOD | op::MULMOD => 8,
31        op::SIGNEXTEND => 5,
32        op::LT | op::GT | op::SLT | op::SGT | op::EQ => 3,
33        op::AND | op::OR | op::XOR => 3,
34        op::BYTE | op::SHL | op::SHR | op::SAR => 3,
35        op::EXP => {
36            // EXP: 10 + 50 * byte_size(exponent).
37            // The exponent is the second operand (TOS - 1 in EVM stack order, inputs[0] here).
38            let exponent_bytes = match inputs.first() {
39                Some(&AbsValue::Const(imm)) => {
40                    let val = imm.get(interner);
41                    (256 - val.leading_zeros()).div_ceil(8)
42                }
43                _ => return None,
44            };
45            10 + 50 * exponent_bytes as u64
46        }
47        _ => return None,
48    };
49    Some(gas)
50}
51
52/// Try to constant-fold an instruction.
53///
54/// `code_len` is the length of the bytecode (for `CODESIZE`).
55pub(crate) fn try_const_fold(
56    inst: &InstData,
57    inputs: &[AbsValue],
58    interner: &mut U256Interner,
59    code_len: usize,
60) -> Option<AbsValue> {
61    let opcode = inst.opcode;
62    let result = match opcode {
63        // 0 -> 1
64        op::CODESIZE => U256::from(code_len),
65        op::PC => U256::from(inst.pc_imm()),
66
67        // 1 -> 1
68        op::ISZERO | op::NOT | op::CLZ => {
69            let &[AbsValue::Const(ai)] = inputs else {
70                return None;
71            };
72            let a = ai.get(interner);
73            match opcode {
74                op::ISZERO => U256::from(a.is_zero()),
75                op::NOT => !a,
76                op::CLZ => U256::from(a.leading_zeros()),
77                _ => unreachable!(),
78            }
79        }
80
81        // 2 -> 1
82        op::ADD
83        | op::MUL
84        | op::SUB
85        | op::DIV
86        | op::SDIV
87        | op::MOD
88        | op::SMOD
89        | op::EXP
90        | op::SIGNEXTEND
91        | op::LT
92        | op::GT
93        | op::SLT
94        | op::SGT
95        | op::EQ
96        | op::AND
97        | op::OR
98        | op::XOR
99        | op::BYTE
100        | op::SHL
101        | op::SHR
102        | op::SAR => {
103            let &[AbsValue::Const(bi), AbsValue::Const(ai)] = inputs else {
104                return None;
105            };
106            let a = ai.get(interner);
107            let b = bi.get(interner);
108            match opcode {
109                op::ADD => a.wrapping_add(b),
110                op::MUL => a.wrapping_mul(b),
111                op::SUB => a.wrapping_sub(b),
112                op::DIV => {
113                    if !b.is_zero() {
114                        a.wrapping_div(b)
115                    } else {
116                        U256::ZERO
117                    }
118                }
119                op::SDIV => i256_div(a, b),
120                op::MOD => {
121                    if !b.is_zero() {
122                        a.wrapping_rem(b)
123                    } else {
124                        U256::ZERO
125                    }
126                }
127                op::SMOD => i256_mod(a, b),
128                op::EXP => a.pow(b),
129                op::SIGNEXTEND => {
130                    if a < U256::from(31) {
131                        let ext = a.as_limbs()[0];
132                        let bit_index = (8 * ext + 7) as usize;
133                        let bit = b.bit(bit_index);
134                        let mask = (U256::from(1) << bit_index) - U256::from(1);
135                        if bit { b | !mask } else { b & mask }
136                    } else {
137                        b
138                    }
139                }
140                op::LT => U256::from(a < b),
141                op::GT => U256::from(a > b),
142                op::SLT => U256::from(i256_cmp(&a, &b) == Ordering::Less),
143                op::SGT => U256::from(i256_cmp(&a, &b) == Ordering::Greater),
144                op::EQ => U256::from(a == b),
145                op::AND => a & b,
146                op::OR => a | b,
147                op::XOR => a ^ b,
148                op::BYTE => {
149                    let i = a.saturating_to::<usize>();
150                    if i < 32 { U256::from(b.byte(31 - i)) } else { U256::ZERO }
151                }
152                op::SHL => {
153                    let shift = a.saturating_to::<usize>();
154                    if shift < 256 { b << shift } else { U256::ZERO }
155                }
156                op::SHR => {
157                    let shift = a.saturating_to::<usize>();
158                    if shift < 256 { b >> shift } else { U256::ZERO }
159                }
160                op::SAR => {
161                    let shift = a.saturating_to::<usize>();
162                    if shift < 256 {
163                        b.arithmetic_shr(shift)
164                    } else if b.bit(255) {
165                        U256::MAX
166                    } else {
167                        U256::ZERO
168                    }
169                }
170                _ => unreachable!(),
171            }
172        }
173
174        // 3 -> 1
175        op::ADDMOD | op::MULMOD => {
176            let &[AbsValue::Const(ci), AbsValue::Const(bi), AbsValue::Const(ai)] = inputs else {
177                return None;
178            };
179            let a = ai.get(interner);
180            let b = bi.get(interner);
181            let n = ci.get(interner);
182            match opcode {
183                op::ADDMOD => a.add_mod(b, n),
184                op::MULMOD => a.mul_mod(b, n),
185                _ => unreachable!(),
186            }
187        }
188
189        _ => return None,
190    };
191    debug_assert!(
192        const_fold_gas(inst.opcode, inputs, interner).is_some(),
193        "try_const_fold handled opcode {} but const_fold_gas did not",
194        inst.opcode,
195    );
196    Some(AbsValue::Const(U256Imm::new(result, interner)))
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    use crate::bytecode::{
203        Bytecode,
204        passes::block_analysis::tests::{Inst, analyze_asm},
205    };
206    use revm_bytecode::opcode::OpCode;
207    use revm_primitives::U256;
208    use std::{fmt::Write, time::Instant};
209
210    /// Builds bytecode that pushes operands, executes `opcode`, sinks the result
211    /// with `PUSH0 MSTORE STOP`, analyzes it, and returns the folded result.
212    ///
213    /// Operands are in natural order matching the yellow paper specification:
214    /// for `SUB(a, b)` = `a - b`, pass `&[a, b]` (first popped = first element).
215    fn const_fold(opcode: u8, operands: &[U256]) -> Option<U256> {
216        use std::fmt::Write;
217        let mut src = String::new();
218        let mut num_insts = 0usize;
219        // Push in reverse so that operands[0] ends up on TOS.
220        for v in operands.iter().rev() {
221            writeln!(src, "PUSH {v}").unwrap();
222            num_insts += 1;
223        }
224        writeln!(src, "{}", OpCode::new(opcode).unwrap()).unwrap();
225        num_insts += 1;
226        // Sink: PUSH0 + MSTORE + STOP to consume the result.
227        let mstore_inst = num_insts + 1;
228        writeln!(src, "PUSH0\nMSTORE\nSTOP").unwrap();
229
230        let bytecode = analyze_asm(&src);
231        // The folded result is operand 1 (second from top) at MSTORE.
232        bytecode.const_operand(Inst::from_usize(mstore_inst), 1)
233    }
234
235    #[test]
236    fn const_fold_0_to_1() {
237        // CODESIZE: code = CODESIZE(1) + PUSH0(1) + MSTORE(1) + STOP(1) = 4 bytes.
238        assert_eq!(const_fold(op::CODESIZE, &[]), Some(U256::from(4)));
239
240        // PC: at position 0.
241        assert_eq!(const_fold(op::PC, &[]), Some(U256::ZERO));
242    }
243
244    #[test]
245    fn const_fold_1_to_1() {
246        // ISZERO.
247        assert_eq!(const_fold(op::ISZERO, &[U256::ZERO]), Some(U256::from(1)));
248        assert_eq!(const_fold(op::ISZERO, &[U256::from(42)]), Some(U256::ZERO));
249
250        // NOT.
251        assert_eq!(const_fold(op::NOT, &[U256::ZERO]), Some(U256::MAX));
252        assert_eq!(const_fold(op::NOT, &[U256::MAX]), Some(U256::ZERO));
253    }
254
255    #[test]
256    fn const_fold_add() {
257        assert_eq!(const_fold(op::ADD, &[U256::from(3), U256::from(4)]), Some(U256::from(7)));
258        assert_eq!(const_fold(op::ADD, &[U256::MAX, U256::from(1)]), Some(U256::ZERO));
259    }
260
261    #[test]
262    fn const_fold_mul() {
263        assert_eq!(const_fold(op::MUL, &[U256::from(3), U256::from(7)]), Some(U256::from(21)));
264        assert_eq!(const_fold(op::MUL, &[U256::from(5), U256::ZERO]), Some(U256::ZERO));
265    }
266
267    #[test]
268    fn const_fold_sub() {
269        // SUB(10, 3) = 7.
270        assert_eq!(const_fold(op::SUB, &[U256::from(10), U256::from(3)]), Some(U256::from(7)));
271        assert_eq!(const_fold(op::SUB, &[U256::ZERO, U256::from(1)]), Some(U256::MAX));
272    }
273
274    #[test]
275    fn const_fold_div() {
276        // DIV(10, 3) = 3.
277        assert_eq!(const_fold(op::DIV, &[U256::from(10), U256::from(3)]), Some(U256::from(3)));
278        assert_eq!(const_fold(op::DIV, &[U256::from(7), U256::ZERO]), Some(U256::ZERO));
279    }
280
281    #[test]
282    fn const_fold_sdiv() {
283        assert_eq!(const_fold(op::SDIV, &[U256::from(10), U256::from(3)]), Some(U256::from(3)));
284        let neg10 = U256::ZERO.wrapping_sub(U256::from(10));
285        let neg3 = U256::ZERO.wrapping_sub(U256::from(3));
286        // SDIV(-10, 3) = -3.
287        assert_eq!(const_fold(op::SDIV, &[neg10, U256::from(3)]), Some(neg3));
288        assert_eq!(const_fold(op::SDIV, &[U256::from(7), U256::ZERO]), Some(U256::ZERO));
289    }
290
291    #[test]
292    fn const_fold_mod() {
293        // MOD(10, 3) = 1.
294        assert_eq!(const_fold(op::MOD, &[U256::from(10), U256::from(3)]), Some(U256::from(1)));
295        assert_eq!(const_fold(op::MOD, &[U256::from(7), U256::ZERO]), Some(U256::ZERO));
296    }
297
298    #[test]
299    fn const_fold_smod() {
300        let neg8 = U256::ZERO.wrapping_sub(U256::from(8));
301        let neg3 = U256::ZERO.wrapping_sub(U256::from(3));
302        let neg2 = U256::ZERO.wrapping_sub(U256::from(2));
303        // SMOD(-8, 3) = -2.
304        assert_eq!(const_fold(op::SMOD, &[neg8, U256::from(3)]), Some(neg2));
305        // SMOD(-8, -3) = -2.
306        assert_eq!(const_fold(op::SMOD, &[neg8, neg3]), Some(neg2));
307    }
308
309    #[test]
310    fn const_fold_addmod() {
311        // ADDMOD(10, 10, 8) = 4.
312        assert_eq!(
313            const_fold(op::ADDMOD, &[U256::from(10), U256::from(10), U256::from(8)]),
314            Some(U256::from(4)),
315        );
316        assert_eq!(
317            const_fold(op::ADDMOD, &[U256::from(1), U256::from(2), U256::ZERO]),
318            Some(U256::ZERO),
319        );
320    }
321
322    #[test]
323    fn const_fold_mulmod() {
324        // MULMOD(10, 10, 8) = 4.
325        assert_eq!(
326            const_fold(op::MULMOD, &[U256::from(10), U256::from(10), U256::from(8)]),
327            Some(U256::from(4)),
328        );
329    }
330
331    #[test]
332    fn const_fold_exp() {
333        // EXP(2, 10) = 1024.
334        assert_eq!(const_fold(op::EXP, &[U256::from(2), U256::from(10)]), Some(U256::from(1024)));
335        // EXP(5, 0) = 1.
336        assert_eq!(const_fold(op::EXP, &[U256::from(5), U256::ZERO]), Some(U256::from(1)));
337    }
338
339    #[test]
340    fn const_fold_signextend() {
341        // SIGNEXTEND(0, 0xFF) -> all ones.
342        assert_eq!(const_fold(op::SIGNEXTEND, &[U256::ZERO, U256::from(0xFF)]), Some(U256::MAX));
343        // SIGNEXTEND(0, 0x7F) -> 0x7F.
344        assert_eq!(
345            const_fold(op::SIGNEXTEND, &[U256::ZERO, U256::from(0x7F)]),
346            Some(U256::from(0x7F)),
347        );
348        // ext >= 31 -> no-op.
349        assert_eq!(
350            const_fold(op::SIGNEXTEND, &[U256::from(31), U256::from(0xFF)]),
351            Some(U256::from(0xFF)),
352        );
353    }
354
355    #[test]
356    fn const_fold_lt_gt_eq() {
357        // LT(1, 2) = 1.
358        assert_eq!(const_fold(op::LT, &[U256::from(1), U256::from(2)]), Some(U256::from(1)));
359        assert_eq!(const_fold(op::LT, &[U256::from(2), U256::from(1)]), Some(U256::ZERO));
360        // GT(2, 1) = 1.
361        assert_eq!(const_fold(op::GT, &[U256::from(2), U256::from(1)]), Some(U256::from(1)));
362        assert_eq!(const_fold(op::GT, &[U256::from(1), U256::from(2)]), Some(U256::ZERO));
363        assert_eq!(const_fold(op::EQ, &[U256::from(5), U256::from(5)]), Some(U256::from(1)));
364        assert_eq!(const_fold(op::EQ, &[U256::from(5), U256::from(6)]), Some(U256::ZERO));
365    }
366
367    #[test]
368    fn const_fold_slt_sgt() {
369        let neg1 = U256::MAX;
370        // SLT(-1, 1) = 1.
371        assert_eq!(const_fold(op::SLT, &[neg1, U256::from(1)]), Some(U256::from(1)));
372        // SLT(1, -1) = 0.
373        assert_eq!(const_fold(op::SLT, &[U256::from(1), neg1]), Some(U256::ZERO));
374        // SGT(1, -1) = 1.
375        assert_eq!(const_fold(op::SGT, &[U256::from(1), neg1]), Some(U256::from(1)));
376    }
377
378    #[test]
379    fn const_fold_and_or_xor() {
380        assert_eq!(
381            const_fold(op::AND, &[U256::from(0xFF), U256::from(0x0F)]),
382            Some(U256::from(0x0F)),
383        );
384        assert_eq!(
385            const_fold(op::OR, &[U256::from(0xF0), U256::from(0x0F)]),
386            Some(U256::from(0xFF)),
387        );
388        assert_eq!(const_fold(op::XOR, &[U256::from(0xFF), U256::from(0xFF)]), Some(U256::ZERO));
389    }
390
391    #[test]
392    fn const_fold_byte() {
393        // BYTE(31, 0xFF) = 0xFF (last byte).
394        assert_eq!(
395            const_fold(op::BYTE, &[U256::from(31), U256::from(0xFF)]),
396            Some(U256::from(0xFF)),
397        );
398        // BYTE(0, 0xFF) = 0 (0xFF is in byte 31 only).
399        assert_eq!(const_fold(op::BYTE, &[U256::ZERO, U256::from(0xFF)]), Some(U256::ZERO));
400        // Out of bounds.
401        assert_eq!(const_fold(op::BYTE, &[U256::from(32), U256::from(0xFF)]), Some(U256::ZERO));
402    }
403
404    #[test]
405    fn const_fold_shl_shr() {
406        // SHL(1, 0x80) = 0x100.
407        assert_eq!(
408            const_fold(op::SHL, &[U256::from(1), U256::from(0x80)]),
409            Some(U256::from(0x100)),
410        );
411        assert_eq!(const_fold(op::SHL, &[U256::from(256), U256::from(1)]), Some(U256::ZERO));
412        // SHR(1, 0x80) = 0x40.
413        assert_eq!(const_fold(op::SHR, &[U256::from(1), U256::from(0x80)]), Some(U256::from(0x40)),);
414        assert_eq!(const_fold(op::SHR, &[U256::from(256), U256::from(1)]), Some(U256::ZERO));
415    }
416
417    #[test]
418    fn const_fold_sar() {
419        // SAR(1, 2) = 1.
420        assert_eq!(const_fold(op::SAR, &[U256::from(1), U256::from(2)]), Some(U256::from(1)));
421        // SAR(4, -1) = -1.
422        assert_eq!(const_fold(op::SAR, &[U256::from(4), U256::MAX]), Some(U256::MAX));
423        // SAR(256, -1) = -1.
424        assert_eq!(const_fold(op::SAR, &[U256::from(256), U256::MAX]), Some(U256::MAX));
425        // SAR(256, 1) = 0.
426        assert_eq!(const_fold(op::SAR, &[U256::from(256), U256::from(1)]), Some(U256::ZERO));
427    }
428
429    /// Builds bytecode for N repetitions of `PUSH U256::MAX, PUSH U256::MAX, EXP, POP`.
430    fn build_exp_bomb(n: usize) -> Vec<u8> {
431        let mut src = String::new();
432        for _ in 0..n {
433            writeln!(src, "PUSH {}", U256::MAX).unwrap();
434            writeln!(src, "PUSH {}", U256::MAX).unwrap();
435            writeln!(src, "EXP").unwrap();
436            writeln!(src, "POP").unwrap();
437        }
438        writeln!(src, "STOP").unwrap();
439        crate::parse_asm(&src).unwrap()
440    }
441
442    /// Adversarial input: many EXP(U256::MAX, U256::MAX) operations.
443    /// The gas limit must prevent the compiler from spending unbounded time on these.
444    #[test]
445    fn compiler_gas_limit_exp_bomb() {
446        crate::tests::init_tracing();
447
448        let code = build_exp_bomb(500);
449        let start = Instant::now();
450        let mut bytecode = Bytecode::test(code);
451        bytecode.analyze().unwrap();
452        let elapsed = start.elapsed();
453
454        // With the default 100k gas budget, EXP(U256::MAX, U256::MAX) costs 10+50*32=1610 gas
455        // each, so we can fold ~62 before hitting the limit. The remaining 438 stay dynamic,
456        // but the point is the wall-clock bound.
457        assert!(
458            elapsed.as_secs() < 30,
459            "compilation took too long ({elapsed:?}), gas limit may not be working",
460        );
461        assert!(bytecode.compiler_gas_used <= bytecode.compiler_gas_limit);
462    }
463
464    /// Proves that without a gas limit, high-volume folding is measurably slower.
465    ///
466    /// With the default 100k budget the same input finishes much faster because most folds
467    /// are skipped after the budget runs out.
468    #[test]
469    fn compiler_gas_limit_no_limit_is_slower() {
470        crate::tests::init_tracing();
471
472        // 100k cheap ADD folds — enough to show a measurable difference.
473        let mut src = String::new();
474        for _ in 0..100_000 {
475            writeln!(src, "PUSH 0x01").unwrap();
476            writeln!(src, "PUSH 0x02").unwrap();
477            writeln!(src, "ADD").unwrap();
478            writeln!(src, "POP").unwrap();
479        }
480        writeln!(src, "STOP").unwrap();
481        let code = crate::parse_asm(&src).unwrap();
482
483        // With default (100k) gas limit.
484        let start_limited = Instant::now();
485        let mut limited = Bytecode::test(code.clone());
486        limited.analyze().unwrap();
487        let elapsed_limited = start_limited.elapsed();
488
489        // With unlimited gas.
490        let start_unlimited = Instant::now();
491        let mut unlimited = Bytecode::test(code);
492        unlimited.compiler_gas_limit = u64::MAX;
493        unlimited.analyze().unwrap();
494        let elapsed_unlimited = start_unlimited.elapsed();
495
496        eprintln!("limited  (100k): {elapsed_limited:?} (gas used: {})", limited.compiler_gas_used);
497        eprintln!(
498            "unlimited (MAX): {elapsed_unlimited:?} (gas used: {})",
499            unlimited.compiler_gas_used,
500        );
501
502        // The unlimited run folds everything so it must use more gas.
503        assert!(unlimited.compiler_gas_used > limited.compiler_gas_used);
504        // And the limited run should have hit the cap.
505        assert!(limited.compiler_gas_used <= limited.compiler_gas_limit);
506    }
507
508    /// Adversarial input: thousands of cheap EXP to exhaust gas via volume.
509    #[test]
510    fn compiler_gas_limit_cheap_exp_volume() {
511        crate::tests::init_tracing();
512
513        // 20K repetitions of EXP(base, small_exponent) — cheap per-op but high volume.
514        let mut src = String::new();
515        for _ in 0..20_000 {
516            writeln!(src, "PUSH {}", U256::MAX).unwrap();
517            writeln!(src, "PUSH 0xff").unwrap(); // 1 byte exponent = 60 gas
518            writeln!(src, "EXP").unwrap();
519            writeln!(src, "POP").unwrap();
520        }
521        writeln!(src, "STOP").unwrap();
522
523        let code = crate::parse_asm(&src).unwrap();
524        let start = Instant::now();
525        let mut bytecode = Bytecode::test(code);
526        bytecode.analyze().unwrap();
527        let elapsed = start.elapsed();
528
529        assert!(elapsed.as_secs() < 30, "compilation took too long ({elapsed:?})",);
530        assert!(bytecode.compiler_gas_used <= bytecode.compiler_gas_limit);
531    }
532
533    /// Verify that setting compiler_gas_limit to 0 disables constant folding entirely.
534    #[test]
535    fn compiler_gas_limit_zero_disables_folding() {
536        crate::tests::init_tracing();
537
538        let src = "PUSH 2\nPUSH 3\nADD\nPUSH0\nMSTORE\nSTOP\n";
539        let code = crate::parse_asm(src).unwrap();
540        let mut bytecode = Bytecode::test(code);
541        bytecode.compiler_gas_limit = 0;
542        bytecode.analyze().unwrap();
543
544        // With gas limit 0, no folding should occur.
545        assert_eq!(bytecode.compiler_gas_used, 0);
546        // inst layout: PUSH(0), PUSH(1), ADD(2), PUSH0(3), MSTORE(4), STOP(5).
547        // The ADD result should NOT be folded — operand 1 at MSTORE should be None.
548        assert!(bytecode.const_operand(Inst::from_usize(4), 1).is_none());
549    }
550
551    /// Verify that a very large gas limit allows all folding to proceed.
552    #[test]
553    fn compiler_gas_limit_unlimited() {
554        crate::tests::init_tracing();
555
556        let src = "PUSH 2\nPUSH 3\nADD\nPUSH0\nMSTORE\nSTOP\n";
557        let code = crate::parse_asm(src).unwrap();
558        let mut bytecode = Bytecode::test(code);
559        bytecode.compiler_gas_limit = u64::MAX;
560        bytecode.analyze().unwrap();
561
562        assert!(bytecode.compiler_gas_used > 0);
563        // inst layout: PUSH(0), PUSH(1), ADD(2), PUSH0(3), MSTORE(4), STOP(5).
564        assert_eq!(bytecode.const_operand(Inst::from_usize(4), 1), Some(U256::from(5)));
565    }
566
567    /// Exhaustively verify that `const_fold_gas` and `try_const_fold` agree on which opcodes
568    /// they handle. For every opcode, if `try_const_fold` returns `Some` then `const_fold_gas`
569    /// must also return `Some`, and vice versa.
570    #[test]
571    fn const_fold_gas_sync() {
572        let mut interner: crate::bytecode::U256Interner = crate::bytecode::Interner::new();
573        let one = U256Imm::new(U256::from(1), &mut interner);
574        let two = U256Imm::new(U256::from(2), &mut interner);
575        let three = U256Imm::new(U256::from(3), &mut interner);
576        let c1 = AbsValue::Const(one);
577        let c2 = AbsValue::Const(two);
578        let c3 = AbsValue::Const(three);
579
580        for opcode in 0..=u8::MAX {
581            let inst = crate::InstData { opcode, ..crate::InstData::new(opcode) };
582            let (inp, _) = inst.stack_io();
583
584            let inputs: &[AbsValue] = match inp {
585                0 => &[],
586                1 => &[c1],
587                2 => &[c1, c2],
588                3 => &[c1, c2, c3],
589                _ => continue,
590            };
591
592            let gas = const_fold_gas(opcode, inputs, &interner);
593            let fold = try_const_fold(&inst, inputs, &mut interner, 100);
594
595            assert_eq!(
596                gas.is_some(),
597                fold.is_some(),
598                "const_fold_gas and try_const_fold disagree on opcode {opcode:#04x} ({}): \
599                 gas={gas:?}, fold={fold:?}",
600                revm_bytecode::opcode::OpCode::new(opcode).map_or("unknown", |o| o.as_str()),
601            );
602        }
603    }
604}