revm_interpreter/instructions/contract/
call_helpers.rs

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