revm_interpreter/instructions/contract/
call_helpers.rs

1use crate::{
2    gas::params::GasParams,
3    interpreter::Interpreter,
4    interpreter_types::{InterpreterTypes, MemoryTr, RuntimeFlag, StackTr},
5    InstructionContext,
6};
7use context_interface::{host::LoadError, Host};
8use core::{cmp::min, ops::Range};
9use primitives::{
10    hardfork::SpecId::{self, *},
11    Address, B256, U256,
12};
13use state::Bytecode;
14
15/// Gets memory input and output ranges for call instructions.
16#[inline]
17pub fn get_memory_input_and_out_ranges(
18    interpreter: &mut Interpreter<impl InterpreterTypes>,
19) -> Option<(Range<usize>, Range<usize>)> {
20    popn!([in_offset, in_len, out_offset, out_len], interpreter, None);
21
22    let mut in_range = resize_memory(interpreter, in_offset, in_len)?;
23
24    if !in_range.is_empty() {
25        let offset = interpreter.memory.local_memory_offset();
26        in_range = in_range.start.saturating_add(offset)..in_range.end.saturating_add(offset);
27    }
28
29    let ret_range = resize_memory(interpreter, out_offset, out_len)?;
30    Some((in_range, ret_range))
31}
32
33/// Resize memory and return range of memory.
34/// If `len` is 0 dont touch memory and return `usize::MAX` as offset and 0 as length.
35#[inline]
36pub fn resize_memory(
37    interpreter: &mut Interpreter<impl InterpreterTypes>,
38    offset: U256,
39    len: U256,
40) -> Option<Range<usize>> {
41    let len = as_usize_or_fail_ret!(interpreter, len, None);
42    let offset = if len != 0 {
43        let offset = as_usize_or_fail_ret!(interpreter, offset, None);
44        resize_memory!(interpreter, offset, len, None);
45        offset
46    } else {
47        usize::MAX //unrealistic value so we are sure it is not used
48    };
49    Some(offset..offset + len)
50}
51
52/// Calculates gas cost and limit for call instructions.
53#[inline(never)]
54pub fn load_acc_and_calc_gas<H: Host + ?Sized>(
55    context: &mut InstructionContext<'_, H, impl InterpreterTypes>,
56    to: Address,
57    transfers_value: bool,
58    create_empty_account: bool,
59    stack_gas_limit: u64,
60) -> Option<(u64, Bytecode, B256)> {
61    // Transfer value cost
62    if transfers_value {
63        gas!(
64            context.interpreter,
65            context.interpreter.gas_params.transfer_value_cost(),
66            None
67        );
68    }
69
70    // load account delegated and deduct dynamic gas.
71    let (gas, bytecode, code_hash) =
72        load_account_delegated_handle_error(context, to, transfers_value, create_empty_account)?;
73    let interpreter = &mut context.interpreter;
74
75    // deduct dynamic gas.
76    gas!(interpreter, gas, None);
77
78    let interpreter = &mut context.interpreter;
79
80    // EIP-150: Gas cost changes for IO-heavy operations
81    let mut gas_limit = if interpreter.runtime_flag.spec_id().is_enabled_in(TANGERINE) {
82        // On mainnet this will take return 63/64 of gas_limit.
83        let reduced_gas_limit = interpreter
84            .gas_params
85            .call_stipend_reduction(interpreter.gas.remaining());
86        min(reduced_gas_limit, stack_gas_limit)
87    } else {
88        stack_gas_limit
89    };
90    gas!(interpreter, gas_limit, None);
91
92    // Add call stipend if there is value to be transferred.
93    if transfers_value {
94        gas_limit = gas_limit.saturating_add(interpreter.gas_params.call_stipend());
95    }
96
97    Some((gas_limit, bytecode, code_hash))
98}
99
100/// Loads accounts and its delegate account.
101#[inline]
102pub fn load_account_delegated_handle_error<H: Host + ?Sized>(
103    context: &mut InstructionContext<'_, H, impl InterpreterTypes>,
104    to: Address,
105    transfers_value: bool,
106    create_empty_account: bool,
107) -> Option<(u64, Bytecode, B256)> {
108    // move this to static gas.
109    let remaining_gas = context.interpreter.gas.remaining();
110    let gas_table = &context.interpreter.gas_params;
111    match load_account_delegated(
112        context.host,
113        gas_table,
114        context.interpreter.runtime_flag.spec_id(),
115        remaining_gas,
116        to,
117        transfers_value,
118        create_empty_account,
119    ) {
120        Ok(out) => return Some(out),
121        Err(LoadError::ColdLoadSkipped) => {
122            context.interpreter.halt_oog();
123        }
124        Err(LoadError::DBError) => {
125            context.interpreter.halt_fatal();
126        }
127    }
128    None
129}
130
131/// Loads accounts and its delegate account.
132///
133/// Assumption is that warm gas is already deducted.
134#[inline]
135pub fn load_account_delegated<H: Host + ?Sized>(
136    host: &mut H,
137    gas_table: &GasParams,
138    spec: SpecId,
139    remaining_gas: u64,
140    address: Address,
141    transfers_value: bool,
142    create_empty_account: bool,
143) -> Result<(u64, Bytecode, B256), LoadError> {
144    let mut cost = 0;
145    let is_berlin = spec.is_enabled_in(SpecId::BERLIN);
146    let is_spurious_dragon = spec.is_enabled_in(SpecId::SPURIOUS_DRAGON);
147
148    let additional_cold_cost = gas_table.cold_account_additional_cost();
149
150    let skip_cold_load = is_berlin && remaining_gas < additional_cold_cost;
151    let account = host.load_account_info_skip_cold_load(address, true, skip_cold_load)?;
152    if is_berlin && account.is_cold {
153        cost += additional_cold_cost;
154    }
155    let mut bytecode = account.code.clone().unwrap_or_default();
156    let mut code_hash = account.code_hash();
157    // New account cost, as account is empty there is no delegated account and we can return early.
158    if create_empty_account && account.is_empty {
159        cost += gas_table.new_account_cost(is_spurious_dragon, transfers_value);
160        return Ok((cost, bytecode, code_hash));
161    }
162
163    // load delegate code if account is EIP-7702
164    if let Some(Bytecode::Eip7702(code)) = &account.code {
165        // EIP-7702 is enabled after berlin hardfork.
166        cost += gas_table.warm_storage_read_cost();
167        if cost > remaining_gas {
168            return Err(LoadError::ColdLoadSkipped);
169        }
170        let address = code.address();
171
172        // skip cold load if there is enough gas to cover the cost.
173        let skip_cold_load = remaining_gas < cost + additional_cold_cost;
174        let delegate_account =
175            host.load_account_info_skip_cold_load(address, true, skip_cold_load)?;
176
177        if delegate_account.is_cold {
178            cost += additional_cold_cost;
179        }
180        bytecode = delegate_account.code.clone().unwrap_or_default();
181        code_hash = delegate_account.code_hash();
182    }
183
184    Ok((cost, bytecode, code_hash))
185}