Skip to main content

revm_interpreter/instructions/
contract.rs

1mod call_helpers;
2
3pub use call_helpers::{
4    get_memory_input_and_out_ranges, load_acc_and_calc_gas, load_account_delegated,
5    load_account_delegated_handle_error, resize_memory,
6};
7
8use crate::{
9    instructions::utility::IntoAddress,
10    interpreter_action::FrameInput,
11    interpreter_types::{InputsTr, InterpreterTypes, LoopControl, MemoryTr, RuntimeFlag, StackTr},
12    CallInput, CallInputs, CallScheme, CallValue, CreateInputs, Host, InstructionResult,
13    InterpreterAction,
14};
15use context_interface::CreateScheme;
16use primitives::{hardfork::SpecId, Address, Bytes, B256, U256};
17use std::boxed::Box;
18
19use crate::InstructionContext;
20
21/// Implements the CREATE/CREATE2 instruction.
22///
23/// Creates a new contract with provided bytecode.
24pub fn create<WIRE: InterpreterTypes, const IS_CREATE2: bool, H: Host + ?Sized>(
25    context: InstructionContext<'_, H, WIRE>,
26) {
27    // Static call check is before gas charging (unlike execution-specs where it's
28    // inside generic_create). This is safe because CREATE in a static context is
29    // always an error regardless of gas accounting.
30    require_non_staticcall!(context.interpreter);
31
32    // EIP-1014: Skinny CREATE2
33    if IS_CREATE2 {
34        check!(context.interpreter, PETERSBURG);
35    }
36
37    popn!([value, code_offset, len], context.interpreter);
38    let len = as_usize_or_fail!(context.interpreter, len);
39
40    let mut code = Bytes::new();
41    if len != 0 {
42        // EIP-3860: Limit and meter initcode
43        if context
44            .interpreter
45            .runtime_flag
46            .spec_id()
47            .is_enabled_in(SpecId::SHANGHAI)
48        {
49            // Limit is set as double of max contract bytecode size
50            if len > context.host.max_initcode_size() {
51                context
52                    .interpreter
53                    .halt(InstructionResult::CreateInitCodeSizeLimit);
54                return;
55            }
56            gas!(
57                context.interpreter,
58                context.host.gas_params().initcode_cost(len)
59            );
60        }
61
62        let code_offset = as_usize_or_fail!(context.interpreter, code_offset);
63        resize_memory!(
64            context.interpreter,
65            context.host.gas_params(),
66            code_offset,
67            len
68        );
69
70        code = Bytes::copy_from_slice(
71            context
72                .interpreter
73                .memory
74                .slice_len(code_offset, len)
75                .as_ref(),
76        );
77    }
78
79    // EIP-1014: Skinny CREATE2
80    let scheme = if IS_CREATE2 {
81        popn!([salt], context.interpreter);
82        // SAFETY: `len` is reasonable in size as gas for it is already deducted.
83        gas!(
84            context.interpreter,
85            context.host.gas_params().create2_cost(len)
86        );
87        CreateScheme::Create2 { salt }
88    } else {
89        gas!(context.interpreter, context.host.gas_params().create_cost());
90        CreateScheme::Create
91    };
92
93    // State gas for account creation + contract metadata (EIP-8037)
94    if context.host.is_amsterdam_eip8037_enabled() {
95        state_gas!(
96            context.interpreter,
97            context.host.gas_params().create_state_gas()
98        );
99    }
100
101    let mut gas_limit = context.interpreter.gas.remaining();
102
103    // EIP-150: Gas cost changes for IO-heavy operations
104    if context
105        .interpreter
106        .runtime_flag
107        .spec_id()
108        .is_enabled_in(SpecId::TANGERINE)
109    {
110        // Take remaining gas and deduce l64 part of it.
111        gas_limit = context.host.gas_params().call_stipend_reduction(gas_limit);
112    }
113    gas!(context.interpreter, gas_limit);
114
115    // Call host to interact with target contract
116    let create_inputs = CreateInputs::new(
117        context.interpreter.input.target_address(),
118        scheme,
119        value,
120        code,
121        gas_limit,
122        context.interpreter.gas.reservoir(),
123    );
124    context
125        .interpreter
126        .bytecode
127        .set_action(InterpreterAction::NewFrame(FrameInput::Create(Box::new(
128            create_inputs,
129        ))));
130}
131
132/// Implements the CALL instruction.
133///
134/// Message call with value transfer to another account.
135pub fn call<WIRE: InterpreterTypes, H: Host + ?Sized>(
136    mut context: InstructionContext<'_, H, WIRE>,
137) {
138    popn!([local_gas_limit, to, value], context.interpreter);
139    let to = to.into_address();
140    // Max gas limit is not possible in real ethereum situation.
141    let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);
142    let has_transfer = !value.is_zero();
143
144    if context.interpreter.runtime_flag.is_static() && has_transfer {
145        context
146            .interpreter
147            .halt(InstructionResult::CallNotAllowedInsideStatic);
148        return;
149    }
150
151    let Some((input, return_memory_offset)) =
152        get_memory_input_and_out_ranges(context.interpreter, context.host.gas_params())
153    else {
154        return;
155    };
156
157    let Some((gas_limit, bytecode, bytecode_hash)) =
158        load_acc_and_calc_gas(&mut context, to, has_transfer, true, local_gas_limit)
159    else {
160        return;
161    };
162
163    // Call host to interact with target contract
164    context
165        .interpreter
166        .bytecode
167        .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(
168            CallInputs {
169                input: CallInput::SharedBuffer(input),
170                gas_limit,
171                target_address: to,
172                caller: context.interpreter.input.target_address(),
173                bytecode_address: to,
174                known_bytecode: (bytecode_hash, bytecode),
175                value: CallValue::Transfer(value),
176                scheme: CallScheme::Call,
177                is_static: context.interpreter.runtime_flag.is_static(),
178                return_memory_offset,
179                reservoir: context.interpreter.gas.reservoir(),
180            },
181        ))));
182}
183
184/// Implements the CALLCODE instruction.
185///
186/// Message call with alternative account's code.
187pub fn call_code<WIRE: InterpreterTypes, H: Host + ?Sized>(
188    mut context: InstructionContext<'_, H, WIRE>,
189) {
190    popn!([local_gas_limit, to, value], context.interpreter);
191    let to = Address::from_word(B256::from(to));
192    // Max gas limit is not possible in real ethereum situation.
193    let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);
194    let has_transfer = !value.is_zero();
195
196    let Some((input, return_memory_offset)) =
197        get_memory_input_and_out_ranges(context.interpreter, context.host.gas_params())
198    else {
199        return;
200    };
201
202    let Some((gas_limit, bytecode, bytecode_hash)) =
203        load_acc_and_calc_gas(&mut context, to, has_transfer, false, local_gas_limit)
204    else {
205        return;
206    };
207
208    // Call host to interact with target contract
209    context
210        .interpreter
211        .bytecode
212        .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(
213            CallInputs {
214                input: CallInput::SharedBuffer(input),
215                gas_limit,
216                target_address: context.interpreter.input.target_address(),
217                caller: context.interpreter.input.target_address(),
218                bytecode_address: to,
219                known_bytecode: (bytecode_hash, bytecode),
220                value: CallValue::Transfer(value),
221                scheme: CallScheme::CallCode,
222                is_static: context.interpreter.runtime_flag.is_static(),
223                return_memory_offset,
224                reservoir: context.interpreter.gas.reservoir(),
225            },
226        ))));
227}
228
229/// Implements the DELEGATECALL instruction.
230///
231/// Message call with alternative account's code but same sender and value.
232pub fn delegate_call<WIRE: InterpreterTypes, H: Host + ?Sized>(
233    mut context: InstructionContext<'_, H, WIRE>,
234) {
235    check!(context.interpreter, HOMESTEAD);
236    popn!([local_gas_limit, to], context.interpreter);
237    let to = Address::from_word(B256::from(to));
238    // Max gas limit is not possible in real ethereum situation.
239    let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);
240
241    let Some((input, return_memory_offset)) =
242        get_memory_input_and_out_ranges(context.interpreter, context.host.gas_params())
243    else {
244        return;
245    };
246
247    let Some((gas_limit, bytecode, bytecode_hash)) =
248        load_acc_and_calc_gas(&mut context, to, false, false, local_gas_limit)
249    else {
250        return;
251    };
252
253    // Call host to interact with target contract
254    context
255        .interpreter
256        .bytecode
257        .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(
258            CallInputs {
259                input: CallInput::SharedBuffer(input),
260                gas_limit,
261                target_address: context.interpreter.input.target_address(),
262                caller: context.interpreter.input.caller_address(),
263                bytecode_address: to,
264                known_bytecode: (bytecode_hash, bytecode),
265                value: CallValue::Apparent(context.interpreter.input.call_value()),
266                scheme: CallScheme::DelegateCall,
267                is_static: context.interpreter.runtime_flag.is_static(),
268                return_memory_offset,
269                reservoir: context.interpreter.gas.reservoir(),
270            },
271        ))));
272}
273
274/// Implements the STATICCALL instruction.
275///
276/// Static message call (cannot modify state).
277pub fn static_call<WIRE: InterpreterTypes, H: Host + ?Sized>(
278    mut context: InstructionContext<'_, H, WIRE>,
279) {
280    check!(context.interpreter, BYZANTIUM);
281    popn!([local_gas_limit, to], context.interpreter);
282    let to = Address::from_word(B256::from(to));
283    // Max gas limit is not possible in real ethereum situation.
284    let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);
285
286    let Some((input, return_memory_offset)) =
287        get_memory_input_and_out_ranges(context.interpreter, context.host.gas_params())
288    else {
289        return;
290    };
291
292    let Some((gas_limit, bytecode, bytecode_hash)) =
293        load_acc_and_calc_gas(&mut context, to, false, false, local_gas_limit)
294    else {
295        return;
296    };
297
298    // Call host to interact with target contract
299    context
300        .interpreter
301        .bytecode
302        .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(
303            CallInputs {
304                input: CallInput::SharedBuffer(input),
305                gas_limit,
306                target_address: to,
307                caller: context.interpreter.input.target_address(),
308                bytecode_address: to,
309                known_bytecode: (bytecode_hash, bytecode),
310                value: CallValue::Transfer(U256::ZERO),
311                scheme: CallScheme::StaticCall,
312                is_static: true,
313                return_memory_offset,
314                reservoir: context.interpreter.gas.reservoir(),
315            },
316        ))));
317}