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::{self, cost_per_word, EOF_CREATE_GAS, KECCAK256WORD, MIN_CALLEE_GAS},
7    interpreter::Interpreter,
8    primitives::{
9        eof::EofHeader, keccak256, Address, BerlinSpec, Bytes, Eof, Spec, SpecId::*, B256, U256,
10    },
11    CallInputs, CallScheme, CallValue, CreateInputs, CreateScheme, EOFCreateInputs, Host,
12    InstructionResult, InterpreterAction, InterpreterResult, MAX_INITCODE_SIZE,
13};
14use core::cmp::max;
15use std::boxed::Box;
16
17/// EOF Create instruction
18pub fn eofcreate<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
19    require_eof!(interpreter);
20    require_non_staticcall!(interpreter);
21    gas!(interpreter, EOF_CREATE_GAS);
22    let initcontainer_index = unsafe { *interpreter.instruction_pointer };
23    pop!(interpreter, value, salt, data_offset, data_size);
24
25    let sub_container = interpreter
26        .eof()
27        .expect("EOF is set")
28        .body
29        .container_section
30        .get(initcontainer_index as usize)
31        .cloned()
32        .expect("EOF is checked");
33
34    // resize memory and get return range.
35    let Some(input_range) = resize_memory(interpreter, data_offset, data_size) else {
36        return;
37    };
38
39    let input = if !input_range.is_empty() {
40        interpreter
41            .shared_memory
42            .slice_range(input_range)
43            .to_vec()
44            .into()
45    } else {
46        Bytes::new()
47    };
48
49    let eof = Eof::decode(sub_container.clone()).expect("Subcontainer is verified");
50
51    if !eof.body.is_data_filled {
52        // should be always false as it is verified by eof verification.
53        panic!("Panic if data section is not full");
54    }
55
56    // deduct gas for hash that is needed to calculate address.
57    gas_or_fail!(
58        interpreter,
59        cost_per_word(sub_container.len() as u64, KECCAK256WORD)
60    );
61
62    let created_address = interpreter
63        .contract
64        .target_address
65        .create2(salt.to_be_bytes(), keccak256(sub_container));
66
67    let gas_limit = interpreter.gas().remaining_63_of_64_parts();
68    gas!(interpreter, gas_limit);
69    // Send container for execution container is preverified.
70    interpreter.instruction_result = InstructionResult::CallOrCreate;
71    interpreter.next_action = InterpreterAction::EOFCreate {
72        inputs: Box::new(EOFCreateInputs::new_opcode(
73            interpreter.contract.target_address,
74            created_address,
75            value,
76            eof,
77            gas_limit,
78            input,
79        )),
80    };
81
82    interpreter.instruction_pointer = unsafe { interpreter.instruction_pointer.offset(1) };
83}
84
85pub fn return_contract<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
86    require_init_eof!(interpreter);
87    let deploy_container_index = unsafe { *interpreter.instruction_pointer };
88    pop!(interpreter, aux_data_offset, aux_data_size);
89    let aux_data_size = as_usize_or_fail!(interpreter, aux_data_size);
90    // important: offset must be ignored if len is zeros
91    let container = interpreter
92        .eof()
93        .expect("EOF is set")
94        .body
95        .container_section
96        .get(deploy_container_index as usize)
97        .expect("EOF is checked")
98        .clone();
99
100    // convert to EOF so we can check data section size.
101    let (eof_header, _) = EofHeader::decode(&container).expect("valid EOF header");
102
103    let aux_slice = if aux_data_size != 0 {
104        let aux_data_offset = as_usize_or_fail!(interpreter, aux_data_offset);
105        resize_memory!(interpreter, aux_data_offset, aux_data_size);
106
107        interpreter
108            .shared_memory
109            .slice(aux_data_offset, aux_data_size)
110    } else {
111        &[]
112    };
113
114    let static_aux_size = eof_header.eof_size() - container.len();
115
116    // data_size - static_aux_size give us current data `container` size.
117    // and with aux_slice len we can calculate new data size.
118    let new_data_size = eof_header.data_size as usize - static_aux_size + aux_slice.len();
119    if new_data_size > 0xFFFF {
120        // aux data is too big
121        interpreter.instruction_result = InstructionResult::EofAuxDataOverflow;
122        return;
123    }
124    if new_data_size < eof_header.data_size as usize {
125        // aux data is too small
126        interpreter.instruction_result = InstructionResult::EofAuxDataTooSmall;
127        return;
128    }
129    let new_data_size = (new_data_size as u16).to_be_bytes();
130
131    let mut output = [&container, aux_slice].concat();
132    // set new data size in eof bytes as we know exact index.
133    output[eof_header.data_size_raw_i()..][..2].clone_from_slice(&new_data_size);
134    let output: Bytes = output.into();
135
136    let result = InstructionResult::ReturnContract;
137    interpreter.instruction_result = result;
138    interpreter.next_action = crate::InterpreterAction::Return {
139        result: InterpreterResult {
140            output,
141            gas: interpreter.gas,
142            result,
143        },
144    };
145}
146
147pub fn extcall_input(interpreter: &mut Interpreter) -> Option<Bytes> {
148    pop_ret!(interpreter, input_offset, input_size, None);
149
150    let return_memory_offset = resize_memory(interpreter, input_offset, input_size)?;
151
152    if return_memory_offset.is_empty() {
153        return Some(Bytes::new());
154    }
155
156    Some(Bytes::copy_from_slice(
157        interpreter
158            .shared_memory
159            .slice_range(return_memory_offset.clone()),
160    ))
161}
162
163pub fn extcall_gas_calc<H: Host + ?Sized>(
164    interpreter: &mut Interpreter,
165    host: &mut H,
166    target: Address,
167    transfers_value: bool,
168) -> Option<u64> {
169    let Some(account_load) = host.load_account_delegated(target) else {
170        interpreter.instruction_result = InstructionResult::FatalExternalError;
171        return None;
172    };
173    // account_load.is_empty will be accounted if there is transfer value.
174    let call_cost = gas::call_cost(BerlinSpec::SPEC_ID, transfers_value, account_load);
175    gas!(interpreter, call_cost, None);
176
177    // 7. Calculate the gas available to callee as caller’s
178    // remaining gas reduced by max(ceil(gas/64), MIN_RETAINED_GAS) (MIN_RETAINED_GAS is 5000).
179    let gas_reduce = max(interpreter.gas.remaining() / 64, 5000);
180    let gas_limit = interpreter.gas().remaining().saturating_sub(gas_reduce);
181
182    // The MIN_CALLEE_GAS rule is a replacement for stipend:
183    // it simplifies the reasoning about the gas costs and is
184    // applied uniformly for all introduced EXT*CALL instructions.
185    //
186    // If Gas available to callee is less than MIN_CALLEE_GAS trigger light failure (Same as Revert).
187    if gas_limit < MIN_CALLEE_GAS {
188        // Push 1 to stack to indicate that call light failed.
189        // It is safe to ignore stack overflow error as we already popped multiple values from stack.
190        let _ = interpreter.stack_mut().push(U256::from(1));
191        interpreter.return_data_buffer.clear();
192        // Return none to continue execution.
193        return None;
194    }
195
196    gas!(interpreter, gas_limit, None);
197    Some(gas_limit)
198}
199
200/// Pop target address from stack and check if it is valid.
201///
202/// Valid address has first 12 bytes as zeroes.
203#[inline]
204pub fn pop_extcall_target_address(interpreter: &mut Interpreter) -> Option<Address> {
205    pop_ret!(interpreter, target_address, None);
206    let target_address = B256::from(target_address);
207    // Check if target is left padded with zeroes.
208    if target_address[..12].iter().any(|i| *i != 0) {
209        interpreter.instruction_result = InstructionResult::InvalidEXTCALLTarget;
210        return None;
211    }
212    // discard first 12 bytes.
213    Some(Address::from_word(target_address))
214}
215
216pub fn extcall<H: Host + ?Sized, SPEC: Spec>(interpreter: &mut Interpreter, host: &mut H) {
217    require_eof!(interpreter);
218
219    // pop target address
220    let Some(target_address) = pop_extcall_target_address(interpreter) else {
221        return;
222    };
223
224    // input call
225    let Some(input) = extcall_input(interpreter) else {
226        return;
227    };
228
229    pop!(interpreter, value);
230    let has_transfer = !value.is_zero();
231    if interpreter.is_static && has_transfer {
232        interpreter.instruction_result = InstructionResult::CallNotAllowedInsideStatic;
233        return;
234    }
235
236    let Some(gas_limit) = extcall_gas_calc(interpreter, host, target_address, has_transfer) else {
237        return;
238    };
239
240    // Call host to interact with target contract
241    interpreter.next_action = InterpreterAction::Call {
242        inputs: Box::new(CallInputs {
243            input,
244            gas_limit,
245            target_address,
246            caller: interpreter.contract.target_address,
247            bytecode_address: target_address,
248            value: CallValue::Transfer(value),
249            scheme: CallScheme::ExtCall,
250            is_static: interpreter.is_static,
251            is_eof: true,
252            return_memory_offset: 0..0,
253        }),
254    };
255    interpreter.instruction_result = InstructionResult::CallOrCreate;
256}
257
258pub fn extdelegatecall<H: Host + ?Sized, SPEC: Spec>(interpreter: &mut Interpreter, host: &mut H) {
259    require_eof!(interpreter);
260
261    // pop target address
262    let Some(target_address) = pop_extcall_target_address(interpreter) else {
263        return;
264    };
265
266    // input call
267    let Some(input) = extcall_input(interpreter) else {
268        return;
269    };
270
271    let Some(gas_limit) = extcall_gas_calc(interpreter, host, target_address, false) else {
272        return;
273    };
274
275    // Call host to interact with target contract
276    interpreter.next_action = InterpreterAction::Call {
277        inputs: Box::new(CallInputs {
278            input,
279            gas_limit,
280            target_address: interpreter.contract.target_address,
281            caller: interpreter.contract.caller,
282            bytecode_address: target_address,
283            value: CallValue::Apparent(interpreter.contract.call_value),
284            scheme: CallScheme::ExtDelegateCall,
285            is_static: interpreter.is_static,
286            is_eof: true,
287            return_memory_offset: 0..0,
288        }),
289    };
290    interpreter.instruction_result = InstructionResult::CallOrCreate;
291}
292
293pub fn extstaticcall<H: Host + ?Sized>(interpreter: &mut Interpreter, host: &mut H) {
294    require_eof!(interpreter);
295
296    // pop target address
297    let Some(target_address) = pop_extcall_target_address(interpreter) else {
298        return;
299    };
300
301    // input call
302    let Some(input) = extcall_input(interpreter) else {
303        return;
304    };
305
306    let Some(gas_limit) = extcall_gas_calc(interpreter, host, target_address, false) else {
307        return;
308    };
309
310    // Call host to interact with target contract
311    interpreter.next_action = InterpreterAction::Call {
312        inputs: Box::new(CallInputs {
313            input,
314            gas_limit,
315            target_address,
316            caller: interpreter.contract.target_address,
317            bytecode_address: target_address,
318            value: CallValue::Transfer(U256::ZERO),
319            scheme: CallScheme::ExtStaticCall,
320            is_static: true,
321            is_eof: true,
322            return_memory_offset: 0..0,
323        }),
324    };
325    interpreter.instruction_result = InstructionResult::CallOrCreate;
326}
327
328pub fn create<const IS_CREATE2: bool, H: Host + ?Sized, SPEC: Spec>(
329    interpreter: &mut Interpreter,
330    host: &mut H,
331) {
332    require_non_staticcall!(interpreter);
333
334    // EIP-1014: Skinny CREATE2
335    if IS_CREATE2 {
336        check!(interpreter, PETERSBURG);
337    }
338
339    pop!(interpreter, value, code_offset, len);
340    let len = as_usize_or_fail!(interpreter, len);
341
342    let mut code = Bytes::new();
343    if len != 0 {
344        // EIP-3860: Limit and meter initcode
345        if SPEC::enabled(SHANGHAI) {
346            // Limit is set as double of max contract bytecode size
347            let max_initcode_size = host
348                .env()
349                .cfg
350                .limit_contract_code_size
351                .map(|limit| limit.saturating_mul(2))
352                .unwrap_or(MAX_INITCODE_SIZE);
353            if len > max_initcode_size {
354                interpreter.instruction_result = InstructionResult::CreateInitCodeSizeLimit;
355                return;
356            }
357            gas!(interpreter, gas::initcode_cost(len as u64));
358        }
359
360        let code_offset = as_usize_or_fail!(interpreter, code_offset);
361        resize_memory!(interpreter, code_offset, len);
362        code = Bytes::copy_from_slice(interpreter.shared_memory.slice(code_offset, len));
363    }
364
365    // EIP-1014: Skinny CREATE2
366    let scheme = if IS_CREATE2 {
367        pop!(interpreter, salt);
368        // SAFETY: len is reasonable in size as gas for it is already deducted.
369        gas_or_fail!(interpreter, gas::create2_cost(len.try_into().unwrap()));
370        CreateScheme::Create2 { salt }
371    } else {
372        gas!(interpreter, gas::CREATE);
373        CreateScheme::Create
374    };
375
376    let mut gas_limit = interpreter.gas().remaining();
377
378    // EIP-150: Gas cost changes for IO-heavy operations
379    if SPEC::enabled(TANGERINE) {
380        // take remaining gas and deduce l64 part of it.
381        gas_limit -= gas_limit / 64
382    }
383    gas!(interpreter, gas_limit);
384
385    // Call host to interact with target contract
386    interpreter.next_action = InterpreterAction::Create {
387        inputs: Box::new(CreateInputs {
388            caller: interpreter.contract.target_address,
389            scheme,
390            value,
391            init_code: code,
392            gas_limit,
393        }),
394    };
395    interpreter.instruction_result = InstructionResult::CallOrCreate;
396}
397
398pub fn call<H: Host + ?Sized, SPEC: Spec>(interpreter: &mut Interpreter, host: &mut H) {
399    pop!(interpreter, local_gas_limit);
400    pop_address!(interpreter, to);
401    // max gas limit is not possible in real ethereum situation.
402    let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);
403
404    pop!(interpreter, value);
405    let has_transfer = !value.is_zero();
406    if interpreter.is_static && has_transfer {
407        interpreter.instruction_result = InstructionResult::CallNotAllowedInsideStatic;
408        return;
409    }
410
411    let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(interpreter) else {
412        return;
413    };
414
415    let Some(account_load) = host.load_account_delegated(to) else {
416        interpreter.instruction_result = InstructionResult::FatalExternalError;
417        return;
418    };
419    let Some(mut gas_limit) =
420        calc_call_gas::<SPEC>(interpreter, account_load, has_transfer, local_gas_limit)
421    else {
422        return;
423    };
424
425    gas!(interpreter, gas_limit);
426
427    // add call stipend if there is value to be transferred.
428    if has_transfer {
429        gas_limit = gas_limit.saturating_add(gas::CALL_STIPEND);
430    }
431
432    // Call host to interact with target contract
433    interpreter.next_action = InterpreterAction::Call {
434        inputs: Box::new(CallInputs {
435            input,
436            gas_limit,
437            target_address: to,
438            caller: interpreter.contract.target_address,
439            bytecode_address: to,
440            value: CallValue::Transfer(value),
441            scheme: CallScheme::Call,
442            is_static: interpreter.is_static,
443            is_eof: false,
444            return_memory_offset,
445        }),
446    };
447    interpreter.instruction_result = InstructionResult::CallOrCreate;
448}
449
450pub fn call_code<H: Host + ?Sized, SPEC: Spec>(interpreter: &mut Interpreter, host: &mut H) {
451    pop!(interpreter, local_gas_limit);
452    pop_address!(interpreter, to);
453    // max gas limit is not possible in real ethereum situation.
454    let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);
455
456    pop!(interpreter, value);
457    let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(interpreter) else {
458        return;
459    };
460
461    let Some(mut load) = host.load_account_delegated(to) else {
462        interpreter.instruction_result = InstructionResult::FatalExternalError;
463        return;
464    };
465    // set is_empty to false as we are not creating this account.
466    load.is_empty = false;
467    let Some(mut gas_limit) =
468        calc_call_gas::<SPEC>(interpreter, load, !value.is_zero(), local_gas_limit)
469    else {
470        return;
471    };
472
473    gas!(interpreter, gas_limit);
474
475    // add call stipend if there is value to be transferred.
476    if !value.is_zero() {
477        gas_limit = gas_limit.saturating_add(gas::CALL_STIPEND);
478    }
479
480    // Call host to interact with target contract
481    interpreter.next_action = InterpreterAction::Call {
482        inputs: Box::new(CallInputs {
483            input,
484            gas_limit,
485            target_address: interpreter.contract.target_address,
486            caller: interpreter.contract.target_address,
487            bytecode_address: to,
488            value: CallValue::Transfer(value),
489            scheme: CallScheme::CallCode,
490            is_static: interpreter.is_static,
491            is_eof: false,
492            return_memory_offset,
493        }),
494    };
495    interpreter.instruction_result = InstructionResult::CallOrCreate;
496}
497
498pub fn delegate_call<H: Host + ?Sized, SPEC: Spec>(interpreter: &mut Interpreter, host: &mut H) {
499    check!(interpreter, HOMESTEAD);
500    pop!(interpreter, local_gas_limit);
501    pop_address!(interpreter, to);
502    // max gas limit is not possible in real ethereum situation.
503    let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);
504
505    let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(interpreter) else {
506        return;
507    };
508
509    let Some(mut load) = host.load_account_delegated(to) else {
510        interpreter.instruction_result = InstructionResult::FatalExternalError;
511        return;
512    };
513    // set is_empty to false as we are not creating this account.
514    load.is_empty = false;
515    let Some(gas_limit) = calc_call_gas::<SPEC>(interpreter, load, false, local_gas_limit) else {
516        return;
517    };
518
519    gas!(interpreter, gas_limit);
520
521    // Call host to interact with target contract
522    interpreter.next_action = InterpreterAction::Call {
523        inputs: Box::new(CallInputs {
524            input,
525            gas_limit,
526            target_address: interpreter.contract.target_address,
527            caller: interpreter.contract.caller,
528            bytecode_address: to,
529            value: CallValue::Apparent(interpreter.contract.call_value),
530            scheme: CallScheme::DelegateCall,
531            is_static: interpreter.is_static,
532            is_eof: false,
533            return_memory_offset,
534        }),
535    };
536    interpreter.instruction_result = InstructionResult::CallOrCreate;
537}
538
539pub fn static_call<H: Host + ?Sized, SPEC: Spec>(interpreter: &mut Interpreter, host: &mut H) {
540    check!(interpreter, BYZANTIUM);
541    pop!(interpreter, local_gas_limit);
542    pop_address!(interpreter, to);
543    // max gas limit is not possible in real ethereum situation.
544    let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);
545
546    let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(interpreter) else {
547        return;
548    };
549
550    let Some(mut load) = host.load_account_delegated(to) else {
551        interpreter.instruction_result = InstructionResult::FatalExternalError;
552        return;
553    };
554    // set is_empty to false as we are not creating this account.
555    load.is_empty = false;
556    let Some(gas_limit) = calc_call_gas::<SPEC>(interpreter, load, false, local_gas_limit) else {
557        return;
558    };
559    gas!(interpreter, gas_limit);
560
561    // Call host to interact with target contract
562    interpreter.next_action = InterpreterAction::Call {
563        inputs: Box::new(CallInputs {
564            input,
565            gas_limit,
566            target_address: to,
567            caller: interpreter.contract.target_address,
568            bytecode_address: to,
569            value: CallValue::Transfer(U256::ZERO),
570            scheme: CallScheme::StaticCall,
571            is_static: true,
572            is_eof: false,
573            return_memory_offset,
574        }),
575    };
576    interpreter.instruction_result = InstructionResult::CallOrCreate;
577}