rtvm_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]
5macro_rules! require_non_staticcall {
6    ($interp:expr) => {
7        if $interp.is_static {
8            $interp.instruction_result = $crate::InstructionResult::StateChangeDuringStaticCall;
9            return;
10        }
11    };
12}
13
14/// Error if the current call is executing EOF.
15#[macro_export]
16macro_rules! require_eof {
17    ($interp:expr) => {
18        if !$interp.is_eof {
19            $interp.instruction_result = $crate::InstructionResult::EOFOpcodeDisabledInLegacy;
20            return;
21        }
22    };
23}
24
25/// Error if not init eof call.
26#[macro_export]
27macro_rules! require_init_eof {
28    ($interp:expr) => {
29        if !$interp.is_eof_init {
30            $interp.instruction_result = $crate::InstructionResult::ReturnContractInNotInitEOF;
31            return;
32        }
33    };
34}
35
36/// Check if the `SPEC` is enabled, and fail the instruction if it is not.
37#[macro_export]
38macro_rules! check {
39    ($interp:expr, $min:ident) => {
40        // TODO: Force const-eval on the condition with a `const {}` block once they are stable
41        if !<SPEC as $crate::primitives::Spec>::enabled($crate::primitives::SpecId::$min) {
42            $interp.instruction_result = $crate::InstructionResult::NotActivated;
43            return;
44        }
45    };
46}
47
48/// Records a `gas` cost and fails the instruction if it would exceed the available gas.
49#[macro_export]
50macro_rules! gas {
51    ($interp:expr, $gas:expr) => {
52        $crate::gas!($interp, $gas, ())
53    };
54    ($interp:expr, $gas:expr, $ret:expr) => {
55        if !$interp.gas.record_cost($gas) {
56            $interp.instruction_result = $crate::InstructionResult::OutOfGas;
57            return $ret;
58        }
59    };
60}
61
62/// Records a `gas` refund.
63#[macro_export]
64macro_rules! refund {
65    ($interp:expr, $gas:expr) => {
66        $interp.gas.record_refund($gas)
67    };
68}
69
70/// Same as [`gas!`], but with `gas` as an option.
71#[macro_export]
72macro_rules! gas_or_fail {
73    ($interp:expr, $gas:expr) => {
74        match $gas {
75            Some(gas_used) => $crate::gas!($interp, gas_used),
76            None => {
77                $interp.instruction_result = $crate::InstructionResult::OutOfGas;
78                return;
79            }
80        }
81    };
82}
83
84/// Resizes the interpreter memory if necessary. Fails the instruction if the memory or gas limit
85/// is exceeded.
86#[macro_export]
87macro_rules! resize_memory {
88    ($interp:expr, $offset:expr, $len:expr) => {
89        $crate::resize_memory!($interp, $offset, $len, ())
90    };
91    ($interp:expr, $offset:expr, $len:expr, $ret:expr) => {
92        let size = $offset.saturating_add($len);
93        if size > $interp.shared_memory.len() {
94            // We are fine with saturating to usize if size is close to MAX value.
95            let rounded_size = $crate::interpreter::next_multiple_of_32(size);
96
97            #[cfg(feature = "memory_limit")]
98            if $interp.shared_memory.limit_reached(size) {
99                $interp.instruction_result = $crate::InstructionResult::MemoryLimitOOG;
100                return $ret;
101            }
102
103            // Gas is calculated in evm words (256 bits).
104            let words_num = rounded_size / 32;
105            if !$interp
106                .gas
107                .record_memory($crate::gas::memory_gas(words_num))
108            {
109                $interp.instruction_result = $crate::InstructionResult::MemoryLimitOOG;
110                return $ret;
111            }
112            $interp.shared_memory.resize(rounded_size);
113        }
114    };
115}
116
117/// Pops `Address` values from the stack. Fails the instruction if the stack is too small.
118#[macro_export]
119macro_rules! pop_address {
120    ($interp:expr, $x1:ident) => {
121        pop_address_ret!($interp, $x1, ())
122    };
123    ($interp:expr, $x1:ident, $x2:ident) => {
124        pop_address_ret!($interp, $x1, $x2, ())
125    };
126}
127
128/// Pop `Address` values from the stack, returns `ret` on stack underflow.
129#[macro_export]
130macro_rules! pop_address_ret {
131    ($interp:expr, $x1:ident, $ret:expr) => {
132        if $interp.stack.len() < 1 {
133            $interp.instruction_result = $crate::InstructionResult::StackUnderflow;
134            return $ret;
135        }
136        // SAFETY: Length is checked above.
137        let $x1 = $crate::primitives::Address::from_word($crate::primitives::B256::from(unsafe {
138            $interp.stack.pop_unsafe()
139        }));
140    };
141    ($interp:expr, $x1:ident, $x2:ident, $ret:expr) => {
142        if $interp.stack.len() < 2 {
143            $interp.instruction_result = $crate::InstructionResult::StackUnderflow;
144            return $ret;
145        }
146        // SAFETY: Length is checked above.
147        let $x1 = $crate::primitives::Address::from_word($crate::primitives::B256::from(unsafe {
148            $interp.stack.pop_unsafe()
149        }));
150        let $x2 = $crate::primitives::Address::from_word($crate::primitives::B256::from(unsafe {
151            $interp.stack.pop_unsafe()
152        }));
153    };
154}
155
156/// Pops `U256` values from the stack. Fails the instruction if the stack is too small.
157#[macro_export]
158macro_rules! pop {
159    ($interp:expr, $x1:ident) => {
160        $crate::pop_ret!($interp, $x1, ())
161    };
162    ($interp:expr, $x1:ident, $x2:ident) => {
163        $crate::pop_ret!($interp, $x1, $x2, ())
164    };
165    ($interp:expr, $x1:ident, $x2:ident, $x3:ident) => {
166        $crate::pop_ret!($interp, $x1, $x2, $x3, ())
167    };
168    ($interp:expr, $x1:ident, $x2:ident, $x3:ident, $x4:ident) => {
169        $crate::pop_ret!($interp, $x1, $x2, $x3, $x4, ())
170    };
171    ($interp:expr, $x1:ident, $x2:ident, $x3:ident, $x4:ident, $x5:ident) => {
172        pop_ret!($interp, $x1, $x2, $x3, $x4, $x5, ())
173    };
174}
175
176/// Pops `U256` values from the stack, and returns `ret`.
177/// Fails the instruction if the stack is too small.
178#[macro_export]
179macro_rules! pop_ret {
180    ($interp:expr, $x1:ident, $ret:expr) => {
181        if $interp.stack.len() < 1 {
182            $interp.instruction_result = $crate::InstructionResult::StackUnderflow;
183            return $ret;
184        }
185        // SAFETY: Length is checked above.
186        let $x1 = unsafe { $interp.stack.pop_unsafe() };
187    };
188    ($interp:expr, $x1:ident, $x2:ident, $ret:expr) => {
189        if $interp.stack.len() < 2 {
190            $interp.instruction_result = $crate::InstructionResult::StackUnderflow;
191            return $ret;
192        }
193        // SAFETY: Length is checked above.
194        let ($x1, $x2) = unsafe { $interp.stack.pop2_unsafe() };
195    };
196    ($interp:expr, $x1:ident, $x2:ident, $x3:ident, $ret:expr) => {
197        if $interp.stack.len() < 3 {
198            $interp.instruction_result = $crate::InstructionResult::StackUnderflow;
199            return $ret;
200        }
201        // SAFETY: Length is checked above.
202        let ($x1, $x2, $x3) = unsafe { $interp.stack.pop3_unsafe() };
203    };
204    ($interp:expr, $x1:ident, $x2:ident, $x3:ident, $x4:ident, $ret:expr) => {
205        if $interp.stack.len() < 4 {
206            $interp.instruction_result = $crate::InstructionResult::StackUnderflow;
207            return $ret;
208        }
209        // SAFETY: Length is checked above.
210        let ($x1, $x2, $x3, $x4) = unsafe { $interp.stack.pop4_unsafe() };
211    };
212    ($interp:expr, $x1:ident, $x2:ident, $x3:ident, $x4:ident, $x5:ident, $ret:expr) => {
213        if $interp.stack.len() < 4 {
214            $interp.instruction_result = $crate::InstructionResult::StackUnderflow;
215            return $ret;
216        }
217        // SAFETY: Length is checked above.
218        let ($x1, $x2, $x3, $x4, $x5) = unsafe { $interp.stack.pop5_unsafe() };
219    };
220}
221
222/// Pops `U256` values from the stack, and returns a reference to the top of the stack.
223/// Fails the instruction if the stack is too small.
224#[macro_export]
225macro_rules! pop_top {
226    ($interp:expr, $x1:ident) => {
227        if $interp.stack.len() < 1 {
228            $interp.instruction_result = $crate::InstructionResult::StackUnderflow;
229            return;
230        }
231        // SAFETY: Length is checked above.
232        let $x1 = unsafe { $interp.stack.top_unsafe() };
233    };
234    ($interp:expr, $x1:ident, $x2:ident) => {
235        if $interp.stack.len() < 2 {
236            $interp.instruction_result = $crate::InstructionResult::StackUnderflow;
237            return;
238        }
239        // SAFETY: Length is checked above.
240        let ($x1, $x2) = unsafe { $interp.stack.pop_top_unsafe() };
241    };
242    ($interp:expr, $x1:ident, $x2:ident, $x3:ident) => {
243        if $interp.stack.len() < 3 {
244            $interp.instruction_result = $crate::InstructionResult::StackUnderflow;
245            return;
246        }
247        // SAFETY: Length is checked above.
248        let ($x1, $x2, $x3) = unsafe { $interp.stack.pop2_top_unsafe() };
249    };
250}
251
252/// Pushes `B256` values onto the stack. Fails the instruction if the stack is full.
253#[macro_export]
254macro_rules! push_b256 {
255	($interp:expr, $($x:expr),* $(,)?) => ($(
256        match $interp.stack.push_b256($x) {
257            Ok(()) => {},
258            Err(e) => {
259                $interp.instruction_result = e;
260                return;
261            },
262        }
263    )*)
264}
265
266/// Pushes a `B256` value onto the stack. Fails the instruction if the stack is full.
267#[macro_export]
268macro_rules! push {
269    ($interp:expr, $($x:expr),* $(,)?) => ($(
270        match $interp.stack.push($x) {
271            Ok(()) => {},
272            Err(e) => {
273                $interp.instruction_result = e;
274                return;
275            }
276        }
277    )*)
278}
279
280/// Converts a `U256` value to a `u64`, saturating to `MAX` if the value is too large.
281#[macro_export]
282macro_rules! as_u64_saturated {
283    ($v:expr) => {{
284        let x: &[u64; 4] = $v.as_limbs();
285        if x[1] == 0 && x[2] == 0 && x[3] == 0 {
286            x[0]
287        } else {
288            u64::MAX
289        }
290    }};
291}
292
293/// Converts a `U256` value to a `usize`, saturating to `MAX` if the value is too large.
294#[macro_export]
295macro_rules! as_usize_saturated {
296    ($v:expr) => {
297        usize::try_from($crate::as_u64_saturated!($v)).unwrap_or(usize::MAX)
298    };
299}
300
301/// Converts a `U256` value to a `isize`, saturating to `isize::MAX` if the value is too large.
302#[macro_export]
303macro_rules! as_isize_saturated {
304    ($v:expr) => {
305        // `isize_try_from(u64::MAX)`` will fail and return isize::MAX
306        // this is expected behavior as we are saturating the value.
307        isize::try_from($crate::as_u64_saturated!($v)).unwrap_or(isize::MAX)
308    };
309}
310
311/// Converts a `U256` value to a `usize`, failing the instruction if the value is too large.
312#[macro_export]
313macro_rules! as_usize_or_fail {
314    ($interp:expr, $v:expr) => {
315        $crate::as_usize_or_fail_ret!($interp, $v, ())
316    };
317    ($interp:expr, $v:expr, $reason:expr) => {
318        $crate::as_usize_or_fail_ret!($interp, $v, $reason, ())
319    };
320}
321
322/// Converts a `U256` value to a `usize` and returns `ret`,
323/// failing the instruction if the value is too large.
324#[macro_export]
325macro_rules! as_usize_or_fail_ret {
326    ($interp:expr, $v:expr, $ret:expr) => {
327        $crate::as_usize_or_fail_ret!(
328            $interp,
329            $v,
330            $crate::InstructionResult::InvalidOperandOOG,
331            $ret
332        )
333    };
334
335    ($interp:expr, $v:expr, $reason:expr, $ret:expr) => {{
336        let x = $v.as_limbs();
337        if x[1] != 0 || x[2] != 0 || x[3] != 0 {
338            $interp.instruction_result = $reason;
339            return $ret;
340        }
341        let Ok(val) = usize::try_from(x[0]) else {
342            $interp.instruction_result = $reason;
343            return $ret;
344        };
345        val
346    }};
347}