revm_interpreter/instructions/
macros.rs

1//! Utility macros to help implementing opcode instruction functions.
2
3/// `const` Option `?`.
4#[macro_export]
5#[collapse_debuginfo(yes)]
6macro_rules! tri {
7    ($e:expr) => {
8        match $e {
9            Some(v) => v,
10            None => return None,
11        }
12    };
13}
14
15/// Fails the instruction if the current call is static.
16#[macro_export]
17#[collapse_debuginfo(yes)]
18macro_rules! require_non_staticcall {
19    ($interpreter:expr) => {
20        if $interpreter.runtime_flag.is_static() {
21            $interpreter.halt($crate::InstructionResult::StateChangeDuringStaticCall);
22            return;
23        }
24    };
25}
26
27/// Macro for optional try - returns early if the expression evaluates to None.
28/// Similar to the `?` operator but for use in instruction implementations.
29#[macro_export]
30#[collapse_debuginfo(yes)]
31macro_rules! otry {
32    ($expression: expr) => {{
33        let Some(value) = $expression else {
34            return;
35        };
36        value
37    }};
38}
39
40/// Check if the `SPEC` is enabled, and fail the instruction if it is not.
41#[macro_export]
42#[collapse_debuginfo(yes)]
43macro_rules! check {
44    ($interpreter:expr, $min:ident) => {
45        if !$interpreter
46            .runtime_flag
47            .spec_id()
48            .is_enabled_in(primitives::hardfork::SpecId::$min)
49        {
50            $interpreter.halt_not_activated();
51            return;
52        }
53    };
54}
55
56/// Records a `gas` cost and fails the instruction if it would exceed the available gas.
57#[macro_export]
58#[collapse_debuginfo(yes)]
59macro_rules! gas {
60    ($interpreter:expr, $gas:expr) => {
61        $crate::gas!($interpreter, $gas, ())
62    };
63    ($interpreter:expr, $gas:expr, $ret:expr) => {
64        if !$interpreter.gas.record_cost($gas) {
65            $interpreter.halt_oog();
66            return $ret;
67        }
68    };
69}
70
71/// Loads account and account berlin gas cost accounting.
72#[macro_export]
73#[collapse_debuginfo(yes)]
74macro_rules! berlin_load_account {
75    ($context:expr, $address:expr, $load_code:expr) => {
76        $crate::berlin_load_account!($context, $address, $load_code, ())
77    };
78    ($context:expr, $address:expr, $load_code:expr, $ret:expr) => {{
79        $crate::gas!($context.interpreter, WARM_STORAGE_READ_COST, $ret);
80        let skip_cold_load =
81            $context.interpreter.gas.remaining() < COLD_ACCOUNT_ACCESS_COST_ADDITIONAL;
82        match $context
83            .host
84            .load_account_info_skip_cold_load($address, $load_code, skip_cold_load)
85        {
86            Ok(account) => {
87                if account.is_cold {
88                    $crate::gas!(
89                        $context.interpreter,
90                        COLD_ACCOUNT_ACCESS_COST_ADDITIONAL,
91                        $ret
92                    );
93                }
94                account
95            }
96            Err(LoadError::ColdLoadSkipped) => {
97                $context.interpreter.halt_oog();
98                return $ret;
99            }
100            Err(LoadError::DBError) => {
101                $context.interpreter.halt_fatal();
102                return $ret;
103            }
104        }
105    }};
106}
107
108/// Same as [`gas!`], but with `gas` as an option.
109#[macro_export]
110#[collapse_debuginfo(yes)]
111macro_rules! gas_or_fail {
112    ($interpreter:expr, $gas:expr) => {
113        $crate::gas_or_fail!($interpreter, $gas, ())
114    };
115    ($interpreter:expr, $gas:expr, $ret:expr) => {
116        match $gas {
117            Some(gas_used) => $crate::gas!($interpreter, gas_used, $ret),
118            None => {
119                $interpreter.halt_oog();
120                return $ret;
121            }
122        }
123    };
124}
125
126/// Resizes the interpreter memory if necessary. Fails the instruction if the memory or gas limit
127/// is exceeded.
128#[macro_export]
129#[collapse_debuginfo(yes)]
130macro_rules! resize_memory {
131    ($interpreter:expr, $offset:expr, $len:expr) => {
132        $crate::resize_memory!($interpreter, $offset, $len, ())
133    };
134    ($interpreter:expr, $offset:expr, $len:expr, $ret:expr) => {
135        if !$crate::interpreter::resize_memory(
136            &mut $interpreter.gas,
137            &mut $interpreter.memory,
138            $offset,
139            $len,
140        ) {
141            $interpreter.halt_memory_oog();
142            return $ret;
143        }
144    };
145}
146
147/// Pops n values from the stack. Fails the instruction if n values can't be popped.
148#[macro_export]
149#[collapse_debuginfo(yes)]
150macro_rules! popn {
151    ([ $($x:ident),* ],$interpreter:expr $(,$ret:expr)? ) => {
152        let Some([$( $x ),*]) = $interpreter.stack.popn() else {
153            $interpreter.halt_underflow();
154            return $($ret)?;
155        };
156    };
157}
158
159#[doc(hidden)]
160#[macro_export]
161#[collapse_debuginfo(yes)]
162macro_rules! _count {
163    (@count) => { 0 };
164    (@count $head:tt $($tail:tt)*) => { 1 + _count!(@count $($tail)*) };
165    ($($arg:tt)*) => { _count!(@count $($arg)*) };
166}
167
168/// Pops n values from the stack and returns the top value. Fails the instruction if n values can't be popped.
169#[macro_export]
170#[collapse_debuginfo(yes)]
171macro_rules! popn_top {
172    ([ $($x:ident),* ], $top:ident, $interpreter:expr $(,$ret:expr)? ) => {
173        /*
174        let Some(([$( $x ),*], $top)) = $interpreter.stack.popn_top() else {
175            $interpreter.halt($crate::InstructionResult::StackUnderflow);
176            return $($ret)?;
177        };
178        */
179
180        // Workaround for https://github.com/rust-lang/rust/issues/144329.
181        if $interpreter.stack.len() < (1 + $crate::_count!($($x)*)) {
182            $interpreter.halt_underflow();
183            return $($ret)?;
184        }
185        let ([$( $x ),*], $top) = unsafe { $interpreter.stack.popn_top().unwrap_unchecked() };
186    };
187}
188
189/// Pushes a `B256` value onto the stack. Fails the instruction if the stack is full.
190#[macro_export]
191#[collapse_debuginfo(yes)]
192macro_rules! push {
193    ($interpreter:expr, $x:expr $(,$ret:item)?) => (
194        if !($interpreter.stack.push($x)) {
195            $interpreter.halt_overflow();
196            return $($ret)?;
197        }
198    )
199}
200
201/// Converts a `U256` value to a `u64`, saturating to `MAX` if the value is too large.
202#[macro_export]
203#[collapse_debuginfo(yes)]
204macro_rules! as_u64_saturated {
205    ($v:expr) => {
206        match $v.as_limbs() {
207            x => {
208                if (x[1] == 0) & (x[2] == 0) & (x[3] == 0) {
209                    x[0]
210                } else {
211                    u64::MAX
212                }
213            }
214        }
215    };
216}
217
218/// Converts a `U256` value to a `usize`, saturating to `MAX` if the value is too large.
219#[macro_export]
220#[collapse_debuginfo(yes)]
221macro_rules! as_usize_saturated {
222    ($v:expr) => {
223        usize::try_from($crate::as_u64_saturated!($v)).unwrap_or(usize::MAX)
224    };
225}
226
227/// Converts a `U256` value to a `isize`, saturating to `isize::MAX` if the value is too large.
228#[macro_export]
229#[collapse_debuginfo(yes)]
230macro_rules! as_isize_saturated {
231    ($v:expr) => {
232        // `isize_try_from(u64::MAX)`` will fail and return isize::MAX
233        // This is expected behavior as we are saturating the value.
234        isize::try_from($crate::as_u64_saturated!($v)).unwrap_or(isize::MAX)
235    };
236}
237
238/// Converts a `U256` value to a `usize`, failing the instruction if the value is too large.
239#[macro_export]
240#[collapse_debuginfo(yes)]
241macro_rules! as_usize_or_fail {
242    ($interpreter:expr, $v:expr) => {
243        $crate::as_usize_or_fail_ret!($interpreter, $v, ())
244    };
245    ($interpreter:expr, $v:expr, $reason:expr) => {
246        $crate::as_usize_or_fail_ret!($interpreter, $v, $reason, ())
247    };
248}
249
250/// Converts a `U256` value to a `usize` and returns `ret`,
251/// failing the instruction if the value is too large.
252#[macro_export]
253#[collapse_debuginfo(yes)]
254macro_rules! as_usize_or_fail_ret {
255    ($interpreter:expr, $v:expr, $ret:expr) => {
256        $crate::as_usize_or_fail_ret!(
257            $interpreter,
258            $v,
259            $crate::InstructionResult::InvalidOperandOOG,
260            $ret
261        )
262    };
263
264    ($interpreter:expr, $v:expr, $reason:expr, $ret:expr) => {
265        match $v.as_limbs() {
266            x => {
267                if (x[0] > usize::MAX as u64) | (x[1] != 0) | (x[2] != 0) | (x[3] != 0) {
268                    $interpreter.halt($reason);
269                    return $ret;
270                }
271                x[0] as usize
272            }
273        }
274    };
275}