Skip to main content

revmc_builtins/
utils.rs

1use core::{hint::cold_path, num::NonZero};
2use revm_context_interface::journaled_state::AccountInfoLoad;
3use revm_interpreter::{InstructionResult, as_usize_saturated, host::LoadError};
4use revm_primitives::Address;
5use revmc_context::{EvmContext, EvmWord};
6
7pub type BuiltinResult = Result<(), BuiltinError>;
8
9/// Represents an error that occurred during a builtin execution.
10#[derive(Debug)]
11#[repr(transparent)]
12pub struct BuiltinError(NonZero<u8>);
13
14impl From<BuiltinError> for InstructionResult {
15    #[inline]
16    fn from(value: BuiltinError) -> Self {
17        // SAFETY: BuiltinError is always created from a valid InstructionResult.
18        unsafe { core::mem::transmute::<_, _>(value.0.get()) }
19    }
20}
21
22impl From<InstructionResult> for BuiltinError {
23    #[inline]
24    fn from(value: InstructionResult) -> Self {
25        cold_path();
26        Self(unsafe { NonZero::new_unchecked(value as u8) })
27    }
28}
29
30impl From<LoadError> for BuiltinError {
31    #[inline]
32    fn from(value: LoadError) -> Self {
33        cold_path();
34        match value {
35            LoadError::ColdLoadSkipped => InstructionResult::OutOfGas.into(),
36            LoadError::DBError => InstructionResult::FatalExternalError.into(),
37        }
38    }
39}
40
41/// Extension trait to convert `Option<T>` to `BuiltinResult`.
42pub(crate) trait OkOrFatal<T> {
43    fn ok_or_fatal(self) -> Result<T, BuiltinError>;
44}
45
46impl<T> OkOrFatal<T> for Option<T> {
47    #[inline]
48    fn ok_or_fatal(self) -> Result<T, BuiltinError> {
49        self.ok_or_else(|| InstructionResult::FatalExternalError.into())
50    }
51}
52
53/// Loads an account, handling cold load gas accounting.
54///
55/// Pre-Berlin, `cold_account_additional_cost` is 0, so the cold load logic is a no-op.
56pub(crate) fn load_account<'a>(
57    ecx: &'a mut EvmContext<'_>,
58    address: Address,
59    load_code: bool,
60) -> Result<AccountInfoLoad<'a>, BuiltinError> {
61    let cold_load_gas = ecx.gas_params.cold_account_additional_cost();
62    let skip_cold_load = ecx.gas.remaining() < cold_load_gas;
63    let account = ecx.host.load_account_info_skip_cold_load(address, load_code, skip_cold_load)?;
64    if account.is_cold {
65        gas!(ecx, cold_load_gas);
66    }
67    Ok(account)
68}
69
70/// Splits the stack pointer into `N` elements by casting it to an array.
71///
72/// NOTE: this returns the arguments in **reverse order**. Use [`read_words!`] to get them in order.
73///
74/// The returned lifetime is valid for the entire duration of the builtin.
75///
76/// # Safety
77///
78/// Caller must ensure that `N` matches the number of elements popped in JIT code.
79#[inline(always)]
80pub(crate) unsafe fn read_words_rev<'a, const N: usize>(sp: *mut EvmWord) -> &'a mut [EvmWord; N] {
81    unsafe { &mut *sp.cast::<[EvmWord; N]>() }
82}
83
84#[inline]
85pub(crate) fn ensure_memory(ecx: &mut EvmContext<'_>, offset: usize, len: usize) -> BuiltinResult {
86    revm_interpreter::interpreter::resize_memory(
87        &mut ecx.gas,
88        ecx.memory,
89        &ecx.gas_params,
90        offset,
91        len,
92    )?;
93    ecx.refresh_memory_cache();
94    Ok(())
95}
96
97pub(crate) unsafe fn copy_operation(
98    ecx: &mut EvmContext<'_>,
99    rev![memory_offset, data_offset, len]: &mut [EvmWord; 3],
100    data: &[u8],
101) -> BuiltinResult {
102    let len = try_into_usize!(len);
103    if len != 0 {
104        gas!(ecx, ecx.gas_params.copy_cost(len));
105        let memory_offset = try_into_usize!(memory_offset);
106        ensure_memory(ecx, memory_offset, len)?;
107        let data_offset = data_offset.to_u256();
108        let data_offset = as_usize_saturated!(data_offset);
109        ecx.memory.set_data(memory_offset, data_offset, len, data);
110    }
111    Ok(())
112}