Skip to main content

revm_interpreter/instructions/contract/
call_helpers.rs

1use crate::{
2    interpreter::Interpreter,
3    interpreter_types::{InterpreterTypes as ITy, MemoryTr, RuntimeFlag, StackTr},
4    InstructionContext as Ictx, InstructionResult,
5};
6use context_interface::{cfg::GasParams, host::LoadError, Host};
7use core::{cmp::min, ops::Range};
8use primitives::{
9    hardfork::SpecId::{self, *},
10    Address, B256, U256,
11};
12use state::Bytecode;
13
14/// Gets memory input and output ranges for call instructions.
15#[inline]
16pub fn get_memory_input_and_out_ranges(
17    interpreter: &mut Interpreter<impl ITy>,
18    gas_params: &GasParams,
19) -> Result<(Range<usize>, Range<usize>), InstructionResult> {
20    popn!([in_offset, in_len, out_offset, out_len], interpreter);
21
22    let mut in_range = resize_memory(interpreter, gas_params, 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, gas_params, out_offset, out_len)?;
30    Ok((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 ITy>,
38    gas_params: &GasParams,
39    offset: U256,
40    len: U256,
41) -> Result<Range<usize>, InstructionResult> {
42    let len = as_usize_or_fail!(interpreter, len);
43    let offset = if len != 0 {
44        let offset = as_usize_or_fail!(interpreter, offset);
45        interpreter.resize_memory(gas_params, offset, len)?;
46        offset
47    } else {
48        usize::MAX // unrealistic value so we are sure it is not used
49    };
50    Ok(offset..offset + len)
51}
52
53/// Calculates gas cost and limit for call instructions.
54///
55/// The trailing bool in the returned tuple is `charged_new_account_state_gas`:
56/// `true` iff this call upfront-charged EIP-8037 `new_account_state_gas`
57/// (transfers value into a previously-empty account). Callers should propagate
58/// it onto `CallInputs` so the parent can refund the charge if the resulting
59/// frame reverts/halts.
60#[inline(never)]
61pub fn load_acc_and_calc_gas<H: Host + ?Sized>(
62    context: &mut Ictx<'_, H, impl ITy>,
63    to: Address,
64    transfers_value: bool,
65    create_empty_account: bool,
66    stack_gas_limit: u64,
67) -> Result<(u64, Bytecode, B256, bool), InstructionResult> {
68    // Transfer value cost
69    if transfers_value {
70        gas!(
71            context.interpreter,
72            context.host.gas_params().transfer_value_cost()
73        );
74    }
75
76    // load account delegated and deduct dynamic gas.
77    let (gas, state_gas_cost, bytecode, code_hash) =
78        load_account_delegated_handle_error(context, to, transfers_value, create_empty_account)?;
79    let charged_new_account_state_gas = state_gas_cost > 0;
80    let interpreter = &mut context.interpreter;
81
82    // deduct dynamic gas.
83    gas!(interpreter, gas);
84
85    // deduct state gas (EIP-8037) if any.
86    state_gas!(interpreter, state_gas_cost);
87
88    let interpreter = &mut context.interpreter;
89    let host = &mut context.host;
90
91    // EIP-150: Gas cost changes for IO-heavy operations
92    let mut gas_limit = if interpreter.runtime_flag.spec_id().is_enabled_in(TANGERINE) {
93        // On mainnet this will take return 63/64 of gas_limit.
94        let reduced_gas_limit = host
95            .gas_params()
96            .call_stipend_reduction(interpreter.gas.remaining());
97        min(reduced_gas_limit, stack_gas_limit)
98    } else {
99        stack_gas_limit
100    };
101    gas!(interpreter, gas_limit);
102
103    // Add call stipend if there is value to be transferred.
104    if transfers_value {
105        gas_limit = gas_limit.saturating_add(host.gas_params().call_stipend());
106    }
107
108    Ok((
109        gas_limit,
110        bytecode,
111        code_hash,
112        charged_new_account_state_gas,
113    ))
114}
115
116/// Loads accounts and its delegate account.
117///
118/// Returns `(regular_gas_cost, state_gas_cost, bytecode, code_hash)`.
119#[inline]
120pub fn load_account_delegated_handle_error<H: Host + ?Sized>(
121    context: &mut Ictx<'_, H, impl ITy>,
122    to: Address,
123    transfers_value: bool,
124    create_empty_account: bool,
125) -> Result<(u64, u64, Bytecode, B256), InstructionResult> {
126    // move this to static gas.
127    let remaining_gas = context.interpreter.gas.remaining();
128    Ok(load_account_delegated(
129        context.host,
130        context.interpreter.runtime_flag.spec_id(),
131        remaining_gas,
132        to,
133        transfers_value,
134        create_empty_account,
135    )?)
136}
137
138/// Loads accounts and its delegate account.
139///
140/// Assumption is that warm gas is already deducted.
141///
142/// Returns `(regular_gas_cost, state_gas_cost, bytecode, code_hash)`.
143/// `state_gas_cost` is non-zero only when creating a new empty account (EIP-8037).
144#[inline]
145pub fn load_account_delegated<H: Host + ?Sized>(
146    host: &mut H,
147    spec: SpecId,
148    remaining_gas: u64,
149    address: Address,
150    transfers_value: bool,
151    create_empty_account: bool,
152) -> Result<(u64, u64, Bytecode, B256), LoadError> {
153    let mut cost = 0;
154    let mut state_gas_cost = 0;
155    let is_berlin = spec.is_enabled_in(SpecId::BERLIN);
156    let is_spurious_dragon = spec.is_enabled_in(SpecId::SPURIOUS_DRAGON);
157
158    let additional_cold_cost = host.gas_params().cold_account_additional_cost();
159    let warm_storage_read_cost = host.gas_params().warm_storage_read_cost();
160
161    let skip_cold_load = is_berlin && remaining_gas < additional_cold_cost;
162    let account = host.load_account_info_skip_cold_load(address, true, skip_cold_load)?;
163    if is_berlin && account.is_cold {
164        cost += additional_cold_cost;
165    }
166    let mut bytecode = account.code.clone().unwrap_or_default();
167    let mut code_hash = account.code_hash();
168    // New account cost, as account is empty there is no delegated account and we can return early.
169    if create_empty_account && account.is_empty {
170        cost += host
171            .gas_params()
172            .new_account_cost(is_spurious_dragon, transfers_value);
173        if host.is_amsterdam_eip8037_enabled() && transfers_value {
174            state_gas_cost += host.gas_params().new_account_state_gas();
175        }
176        return Ok((cost, state_gas_cost, bytecode, code_hash));
177    }
178
179    // load delegate code if account is EIP-7702
180    if let Some(address) = account.code.as_ref().and_then(Bytecode::eip7702_address) {
181        // EIP-7702 is enabled after berlin hardfork.
182        cost += warm_storage_read_cost;
183        if cost > remaining_gas {
184            return Err(LoadError::ColdLoadSkipped);
185        }
186
187        // skip cold load if there is enough gas to cover the cost.
188        let skip_cold_load = remaining_gas < cost + additional_cold_cost;
189        let delegate_account =
190            host.load_account_info_skip_cold_load(address, true, skip_cold_load)?;
191
192        if delegate_account.is_cold {
193            cost += additional_cold_cost;
194        }
195        bytecode = delegate_account.code.clone().unwrap_or_default();
196        code_hash = delegate_account.code_hash();
197    }
198
199    Ok((cost, state_gas_cost, bytecode, code_hash))
200}