revm_interpreter/instructions/
contract.rs

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