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        #[cfg(feature = "memory_limit")]
136        if $interpreter.memory.limit_reached($offset, $len) {
137            $interpreter.halt_memory_limit_oog();
138            return $ret;
139        }
140        if !$crate::interpreter::resize_memory(
141            &mut $interpreter.gas,
142            &mut $interpreter.memory,
143            $offset,
144            $len,
145        ) {
146            $interpreter.halt_memory_oog();
147            return $ret;
148        }
149    };
150}
151
152/// Pops n values from the stack. Fails the instruction if n values can't be popped.
153#[macro_export]
154#[collapse_debuginfo(yes)]
155macro_rules! popn {
156    ([ $($x:ident),* ],$interpreter:expr $(,$ret:expr)? ) => {
157        let Some([$( $x ),*]) = $interpreter.stack.popn() else {
158            $interpreter.halt_underflow();
159            return $($ret)?;
160        };
161    };
162}
163
164#[doc(hidden)]
165#[macro_export]
166#[collapse_debuginfo(yes)]
167macro_rules! _count {
168    (@count) => { 0 };
169    (@count $head:tt $($tail:tt)*) => { 1 + _count!(@count $($tail)*) };
170    ($($arg:tt)*) => { _count!(@count $($arg)*) };
171}
172
173/// Pops n values from the stack and returns the top value. Fails the instruction if n values can't be popped.
174#[macro_export]
175#[collapse_debuginfo(yes)]
176macro_rules! popn_top {
177    ([ $($x:ident),* ], $top:ident, $interpreter:expr $(,$ret:expr)? ) => {
178        /*
179        let Some(([$( $x ),*], $top)) = $interpreter.stack.popn_top() else {
180            $interpreter.halt($crate::InstructionResult::StackUnderflow);
181            return $($ret)?;
182        };
183        */
184
185        // Workaround for https://github.com/rust-lang/rust/issues/144329.
186        if $interpreter.stack.len() < (1 + $crate::_count!($($x)*)) {
187            $interpreter.halt_underflow();
188            return $($ret)?;
189        }
190        let ([$( $x ),*], $top) = unsafe { $interpreter.stack.popn_top().unwrap_unchecked() };
191    };
192}
193
194/// Pushes a `B256` value onto the stack. Fails the instruction if the stack is full.
195#[macro_export]
196#[collapse_debuginfo(yes)]
197macro_rules! push {
198    ($interpreter:expr, $x:expr $(,$ret:item)?) => (
199        if !($interpreter.stack.push($x)) {
200            $interpreter.halt_overflow();
201            return $($ret)?;
202        }
203    )
204}
205
206/// Converts a `U256` value to a `u64`, saturating to `MAX` if the value is too large.
207#[macro_export]
208#[collapse_debuginfo(yes)]
209macro_rules! as_u64_saturated {
210    ($v:expr) => {
211        match $v.as_limbs() {
212            x => {
213                if (x[1] == 0) & (x[2] == 0) & (x[3] == 0) {
214                    x[0]
215                } else {
216                    u64::MAX
217                }
218            }
219        }
220    };
221}
222
223/// Converts a `U256` value to a `usize`, saturating to `MAX` if the value is too large.
224#[macro_export]
225#[collapse_debuginfo(yes)]
226macro_rules! as_usize_saturated {
227    ($v:expr) => {
228        usize::try_from($crate::as_u64_saturated!($v)).unwrap_or(usize::MAX)
229    };
230}
231
232/// Converts a `U256` value to a `isize`, saturating to `isize::MAX` if the value is too large.
233#[macro_export]
234#[collapse_debuginfo(yes)]
235macro_rules! as_isize_saturated {
236    ($v:expr) => {
237        // `isize_try_from(u64::MAX)`` will fail and return isize::MAX
238        // This is expected behavior as we are saturating the value.
239        isize::try_from($crate::as_u64_saturated!($v)).unwrap_or(isize::MAX)
240    };
241}
242
243/// Converts a `U256` value to a `usize`, failing the instruction if the value is too large.
244#[macro_export]
245#[collapse_debuginfo(yes)]
246macro_rules! as_usize_or_fail {
247    ($interpreter:expr, $v:expr) => {
248        $crate::as_usize_or_fail_ret!($interpreter, $v, ())
249    };
250    ($interpreter:expr, $v:expr, $reason:expr) => {
251        $crate::as_usize_or_fail_ret!($interpreter, $v, $reason, ())
252    };
253}
254
255/// Converts a `U256` value to a `usize` and returns `ret`,
256/// failing the instruction if the value is too large.
257#[macro_export]
258#[collapse_debuginfo(yes)]
259macro_rules! as_usize_or_fail_ret {
260    ($interpreter:expr, $v:expr, $ret:expr) => {
261        $crate::as_usize_or_fail_ret!(
262            $interpreter,
263            $v,
264            $crate::InstructionResult::InvalidOperandOOG,
265            $ret
266        )
267    };
268
269    ($interpreter:expr, $v:expr, $reason:expr, $ret:expr) => {
270        match $v.as_limbs() {
271            x => {
272                if (x[0] > usize::MAX as u64) | (x[1] != 0) | (x[2] != 0) | (x[3] != 0) {
273                    $interpreter.halt($reason);
274                    return $ret;
275                }
276                x[0] as usize
277            }
278        }
279    };
280}