referrerpolicy=no-referrer-when-downgrade

pallet_revive/vm/evm/instructions/
macros.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Utility macros to help implementing opcode instruction functions.
19
20/// `const` Option `?`.
21#[macro_export]
22macro_rules! tri {
23	($e:expr) => {
24		match $e {
25			Some(v) => v,
26			None => return None,
27		}
28	};
29}
30
31/// Fails the instruction if the current call is static.
32#[macro_export]
33macro_rules! require_non_staticcall {
34	($interpreter:expr) => {
35		if $interpreter.runtime_flag.is_static() {
36			$interpreter.halt(revm::interpreter::InstructionResult::StateChangeDuringStaticCall);
37			return;
38		}
39	};
40}
41
42/// Macro for optional try - returns early if the expression evaluates to None.
43/// Similar to the `?` operator but for use in instruction implementations.
44#[macro_export]
45macro_rules! otry {
46	($expression: expr) => {{
47		let Some(value) = $expression else {
48			return;
49		};
50		value
51	}};
52}
53
54/// Error if the current call is executing EOF.
55#[macro_export]
56macro_rules! require_eof {
57	($interpreter:expr) => {
58		if !$interpreter.runtime_flag.is_eof() {
59			$interpreter.halt(revm::interpreter::InstructionResult::EOFOpcodeDisabledInLegacy);
60			return;
61		}
62	};
63}
64
65/// Check if the `SPEC` is enabled, and fail the instruction if it is not.
66#[macro_export]
67macro_rules! check {
68	($interpreter:expr, $min:ident) => {
69		if !$interpreter
70			.runtime_flag
71			.spec_id()
72			.is_enabled_in(revm::primitives::hardfork::SpecId::$min)
73		{
74			$interpreter.halt(revm::interpreter::InstructionResult::NotActivated);
75			return;
76		}
77	};
78}
79
80/// Records a `gas` cost and fails the instruction if it would exceed the available gas.
81#[macro_export]
82macro_rules! gas_legacy {
83	($interpreter:expr, $gas:expr) => {
84		gas_legacy!($interpreter, $gas, ())
85	};
86	($interpreter:expr, $gas:expr, $ret:expr) => {
87		if $interpreter.extend.gas_meter_mut().charge_evm_gas($gas).is_err() {
88			$interpreter.halt(revm::interpreter::InstructionResult::OutOfGas);
89			return $ret;
90		}
91	};
92}
93
94#[macro_export]
95macro_rules! gas {
96	($interpreter:expr, $gas:expr) => {
97		gas!($interpreter, $gas, ())
98	};
99	($interpreter:expr, $gas:expr, $ret:expr) => {
100		let meter = $interpreter.extend.gas_meter_mut();
101		if meter.charge_evm_gas(1).is_err() || meter.charge($gas).is_err() {
102			$interpreter.halt(revm::interpreter::InstructionResult::OutOfGas);
103			return $ret;
104		}
105	};
106}
107
108/// Same as [`gas_legacy!`], but with `gas` as an option.
109#[macro_export]
110macro_rules! gas_or_fail_legacy {
111	($interpreter:expr, $gas:expr) => {
112		gas_or_fail_legacy!($interpreter, $gas, ())
113	};
114	($interpreter:expr, $gas:expr, $ret:expr) => {
115		match $gas {
116			Some(gas_used) => gas_legacy!($interpreter, gas_used, $ret),
117			None => {
118				$interpreter.halt(revm::interpreter::InstructionResult::OutOfGas);
119				return $ret;
120			},
121		}
122	};
123}
124
125use crate::vm::Ext;
126use revm::interpreter::gas::{MemoryExtensionResult, MemoryGas};
127
128/// Adapted from
129/// https://docs.rs/revm/latest/revm/interpreter/struct.Gas.html#method.record_memory_expansion
130pub fn record_memory_expansion<E: Ext>(
131	memory: &mut MemoryGas,
132	ext: &mut E,
133	new_len: usize,
134) -> MemoryExtensionResult {
135	let Some(additional_cost) = memory.record_new_len(new_len) else {
136		return MemoryExtensionResult::Same;
137	};
138
139	if ext.gas_meter_mut().charge_evm_gas(additional_cost).is_err() {
140		return MemoryExtensionResult::OutOfGas;
141	}
142
143	MemoryExtensionResult::Extended
144}
145
146/// Resizes the interpreterreter memory if necessary. Fails the instruction if the memory or gas
147/// limit is exceeded.
148#[macro_export]
149macro_rules! resize_memory {
150	($interpreter:expr, $offset:expr, $len:expr) => {
151		resize_memory!($interpreter, $offset, $len, ())
152	};
153	($interpreter:expr, $offset:expr, $len:expr, $ret:expr) => {
154		let words_num = revm::interpreter::num_words($offset.saturating_add($len));
155		match crate::vm::evm::instructions::macros::record_memory_expansion(
156			$interpreter.gas.memory_mut(),
157			$interpreter.extend,
158			words_num,
159		) {
160			revm::interpreter::gas::MemoryExtensionResult::Extended => {
161				$interpreter.memory.resize(words_num * 32);
162			},
163			revm::interpreter::gas::MemoryExtensionResult::OutOfGas => {
164				$interpreter.halt(revm::interpreter::InstructionResult::MemoryOOG);
165				return $ret;
166			},
167			revm::interpreter::gas::MemoryExtensionResult::Same => (), // no action
168		};
169	};
170}
171
172/// Pops n values from the stack. Fails the instruction if n values can't be popped.
173#[macro_export]
174macro_rules! popn {
175    ([ $($x:ident),* ],$interpreterreter:expr $(,$ret:expr)? ) => {
176        let Some([$( $x ),*]) = <_ as StackTr>::popn(&mut $interpreterreter.stack) else {
177            $interpreterreter.halt(revm::interpreter::InstructionResult::StackUnderflow);
178            return $($ret)?;
179        };
180    };
181}
182
183/// Pops n values from the stack and returns the top value. Fails the instruction if n values can't
184/// be popped.
185#[macro_export]
186macro_rules! popn_top {
187    ([ $($x:ident),* ], $top:ident, $interpreter:expr $(,$ret:expr)? ) => {
188		let Some(([$($x),*], $top)) = <_ as StackTr>::popn_top(&mut $interpreter.stack) else {
189            $interpreter.halt(revm::interpreter::InstructionResult::StackUnderflow);
190            return $($ret)?;
191        };
192    };
193}
194
195/// Pushes a `B256` value onto the stack. Fails the instruction if the stack is full.
196#[macro_export]
197macro_rules! push {
198    ($interpreter:expr, $x:expr $(,$ret:item)?) => (
199        if !($interpreter.stack.push($x)) {
200            $interpreter.halt(revm::interpreter::InstructionResult::StackOverflow);
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]
208macro_rules! as_u64_saturated {
209	($v:expr) => {
210		match $v.as_limbs() {
211			x =>
212				if (x[1] == 0) & (x[2] == 0) & (x[3] == 0) {
213					x[0]
214				} else {
215					u64::MAX
216				},
217		}
218	};
219}
220
221/// Converts a `U256` value to a `usize`, saturating to `MAX` if the value is too large.
222#[macro_export]
223macro_rules! as_usize_saturated {
224	($v:expr) => {
225		usize::try_from(as_u64_saturated!($v)).unwrap_or(usize::MAX)
226	};
227}
228
229/// Converts a `U256` value to a `isize`, saturating to `isize::MAX` if the value is too large.
230#[macro_export]
231macro_rules! as_isize_saturated {
232	($v:expr) => {
233		// `isize_try_from(u64::MAX)`` will fail and return isize::MAX
234		// This is expected behavior as we are saturating the value.
235		isize::try_from(as_u64_saturated!($v)).unwrap_or(isize::MAX)
236	};
237}
238
239/// Converts a `U256` value to a `usize`, failing the instruction if the value is too large.
240#[macro_export]
241macro_rules! as_usize_or_fail {
242	($interpreter:expr, $v:expr) => {
243		as_usize_or_fail_ret!($interpreter, $v, ())
244	};
245	($interpreter:expr, $v:expr, $reason:expr) => {
246		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]
253macro_rules! as_usize_or_fail_ret {
254	($interpreter:expr, $v:expr, $ret:expr) => {
255		as_usize_or_fail_ret!(
256			$interpreter,
257			$v,
258			revm::interpreter::InstructionResult::InvalidOperandOOG,
259			$ret
260		)
261	};
262
263	($interpreter:expr, $v:expr, $reason:expr, $ret:expr) => {
264		match $v.as_limbs() {
265			x => {
266				if (x[0] > usize::MAX as u64) | (x[1] != 0) | (x[2] != 0) | (x[3] != 0) {
267					$interpreter.halt($reason);
268					return $ret;
269				}
270				x[0] as usize
271			},
272		}
273	};
274}