Skip to main content

revm_interpreter/instructions/
macros.rs

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