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, EOF_CREATE_GAS, MIN_CALLEE_GAS},
7    instructions::utility::IntoAddress,
8    interpreter::Interpreter,
9    interpreter_action::FrameInput,
10    interpreter_types::{
11        EofContainer, Immediates, InputsTr, InterpreterTypes, Jumps, LoopControl, MemoryTr,
12        ReturnData, RuntimeFlag, StackTr,
13    },
14    CallInput, CallInputs, CallScheme, CallValue, CreateInputs, EOFCreateInputs, Host,
15    InstructionResult, InterpreterAction, InterpreterResult,
16};
17use bytecode::eof::{Eof, EofHeader};
18use context_interface::CreateScheme;
19use core::cmp::max;
20use primitives::{eof::new_eof_address, hardfork::SpecId, Address, Bytes, B256, U256};
21use std::boxed::Box;
22
23use crate::InstructionContext;
24
25/// EOF Create instruction
26pub fn eofcreate<WIRE: InterpreterTypes, H: Host + ?Sized>(
27    context: InstructionContext<'_, H, WIRE>,
28) {
29    require_eof!(context.interpreter);
30    require_non_staticcall!(context.interpreter);
31    gas!(context.interpreter, EOF_CREATE_GAS);
32    let initcontainer_index = context.interpreter.bytecode.read_u8();
33
34    popn!([salt, input_offset, input_size, value], context.interpreter);
35
36    let container = context
37        .interpreter
38        .bytecode
39        .eof_container(initcontainer_index as usize)
40        .expect("valid container")
41        .clone();
42
43    // Resize memory and get return range.
44    let Some(input_range) = resize_memory(context.interpreter, input_offset, input_size) else {
45        return;
46    };
47
48    let input = if !input_range.is_empty() {
49        context
50            .interpreter
51            .memory
52            .slice(input_range)
53            .to_vec()
54            .into()
55    } else {
56        Bytes::new()
57    };
58
59    let eof = Eof::decode(container.clone()).expect("Subcontainer is verified");
60
61    if !eof.body.is_data_filled {
62        // Should be always false as it is verified by eof verification.
63        panic!("Panic if data section is not full");
64    }
65
66    // Calculate new address
67    let created_address = new_eof_address(
68        context.interpreter.input.target_address(),
69        salt.to_be_bytes().into(),
70    );
71
72    let gas_limit = context.interpreter.gas.remaining_63_of_64_parts();
73    gas!(context.interpreter, gas_limit);
74
75    // Send container for execution as all deployed containers are preverified to be valid EOF.
76    context
77        .interpreter
78        .bytecode
79        .set_action(InterpreterAction::NewFrame(FrameInput::EOFCreate(
80            Box::new(EOFCreateInputs::new_opcode(
81                context.interpreter.input.target_address(),
82                created_address,
83                value,
84                eof,
85                gas_limit,
86                CallInput::Bytes(input),
87            )),
88        )));
89
90    // jump over initcontainer index.
91    context.interpreter.bytecode.relative_jump(1);
92}
93
94/// Instruction to create a new EOF contract from a transaction initcode.
95pub fn txcreate<WIRE: InterpreterTypes, H: Host + ?Sized>(
96    context: InstructionContext<'_, H, WIRE>,
97) {
98    // TODO(EOF) only accepted in EOF.
99    require_eof!(context.interpreter);
100    require_non_staticcall!(context.interpreter);
101    gas!(context.interpreter, EOF_CREATE_GAS);
102
103    // pop tx_initcode_hash, salt, input_offset, input_size, value from the operand stack
104    popn!(
105        [tx_initcode_hash, salt, input_offset, input_size, value],
106        context.interpreter
107    );
108    let tx_initcode_hash = B256::from(tx_initcode_hash);
109
110    // perform (and charge for) memory expansion using [input_offset, input_size]
111    let Some(input_range) = resize_memory(context.interpreter, input_offset, input_size) else {
112        return;
113    };
114
115    // Get validated initcode with all its subcontainers validated recursively.
116    let Some(initcode) = context.host.initcode_by_hash(tx_initcode_hash) else {
117        // If initcode is not found or not valid, push 0 on the stack.
118        push!(context.interpreter, U256::ZERO);
119        return;
120    };
121
122    // caller’s memory slice [input_offset:input_size] is used as calldata
123    let input = if !input_range.is_empty() {
124        context
125            .interpreter
126            .memory
127            .slice(input_range)
128            .to_vec()
129            .into()
130    } else {
131        Bytes::new()
132    };
133
134    // Decode initcode as EOF.
135    let eof = Eof::decode(initcode).expect("Subcontainer is verified");
136
137    // Calculate new address
138    let created_address = new_eof_address(
139        context.interpreter.input.target_address(),
140        salt.to_be_bytes().into(),
141    );
142
143    let gas_limit = context.interpreter.gas.remaining_63_of_64_parts();
144    gas!(context.interpreter, gas_limit);
145
146    // Send container for execution as all deployed containers are preverified to be valid EOF.
147    context
148        .interpreter
149        .bytecode
150        .set_action(InterpreterAction::NewFrame(FrameInput::EOFCreate(
151            Box::new(EOFCreateInputs::new_opcode(
152                context.interpreter.input.target_address(),
153                created_address,
154                value,
155                eof,
156                gas_limit,
157                CallInput::Bytes(input),
158            )),
159        )));
160}
161
162pub fn return_contract<H: Host + ?Sized>(
163    context: InstructionContext<'_, H, impl InterpreterTypes>,
164) {
165    if !context.interpreter.runtime_flag.is_eof_init() {
166        context
167            .interpreter
168            .halt(InstructionResult::ReturnContractInNotInitEOF);
169        return;
170    }
171    let deploy_container_index = context.interpreter.bytecode.read_u8();
172    popn!([aux_data_offset, aux_data_size], context.interpreter);
173    let aux_data_size = as_usize_or_fail!(context.interpreter, aux_data_size);
174    let container = context
175        .interpreter
176        .bytecode
177        .eof_container(deploy_container_index as usize)
178        .expect("valid container")
179        .clone();
180
181    // Convert to EOF so we can check data section size.
182    let (eof_header, _) = EofHeader::decode(&container).expect("valid EOF header");
183
184    let static_aux_size = eof_header.eof_size() - container.len();
185
186    // Important: Offset must be ignored if len is zeros
187    let mut output = if aux_data_size != 0 {
188        let aux_data_offset = as_usize_or_fail!(context.interpreter, aux_data_offset);
189        resize_memory!(context.interpreter, aux_data_offset, aux_data_size);
190
191        let aux_slice = context
192            .interpreter
193            .memory
194            .slice_len(aux_data_offset, aux_data_size);
195
196        [&container, aux_slice.as_ref()].concat()
197    } else {
198        container.to_vec()
199    };
200
201    // `data_size - static_aux_size` give us current data `container` size.
202    // And with `aux_slice` len we can calculate new data size.
203    let new_data_size = eof_header.data_size as usize - static_aux_size + aux_data_size;
204    if new_data_size > 0xFFFF {
205        // Aux data is too big
206        context
207            .interpreter
208            .halt(InstructionResult::EofAuxDataOverflow);
209        return;
210    }
211    if new_data_size < eof_header.data_size as usize {
212        // Aux data is too small
213        context
214            .interpreter
215            .halt(InstructionResult::EofAuxDataTooSmall);
216        return;
217    }
218    let new_data_size = (new_data_size as u16).to_be_bytes();
219
220    // Set new data size in eof bytes as we know exact index.
221    output[eof_header.data_size_raw_i()..][..2].clone_from_slice(&new_data_size);
222    let output: Bytes = output.into();
223
224    let result = InstructionResult::ReturnContract;
225    let gas = context.interpreter.gas;
226    context
227        .interpreter
228        .bytecode
229        .set_action(InterpreterAction::Return(InterpreterResult {
230            output,
231            gas,
232            result,
233        }));
234}
235
236pub fn extcall_input(interpreter: &mut Interpreter<impl InterpreterTypes>) -> Option<Bytes> {
237    popn!([input_offset, input_size], interpreter, None);
238    let return_memory_offset = resize_memory(interpreter, input_offset, input_size)?;
239
240    if return_memory_offset.is_empty() {
241        return Some(Bytes::new());
242    }
243
244    Some(Bytes::copy_from_slice(
245        interpreter.memory.slice(return_memory_offset).as_ref(),
246    ))
247}
248
249#[inline]
250pub fn extcall_gas_calc<WIRE: InterpreterTypes, H: Host + ?Sized>(
251    context: &mut InstructionContext<'_, H, WIRE>,
252    target: Address,
253    transfers_value: bool,
254) -> Option<u64> {
255    let Some(account_load) = context.host.load_account_delegated(target) else {
256        context
257            .interpreter
258            .halt(InstructionResult::FatalExternalError);
259        return None;
260    };
261
262    // account_load.is_empty will be accounted if there is transfer value
263    // Berlin can be hardcoded as extcall came after berlin.
264    let call_cost = gas::call_cost(
265        context.interpreter.runtime_flag.spec_id(),
266        transfers_value,
267        account_load,
268    );
269    gas!(context.interpreter, call_cost, None);
270
271    // Calculate the gas available to callee as caller’s
272    // remaining gas reduced by max(ceil(gas/64), MIN_RETAINED_GAS) (MIN_RETAINED_GAS is 5000).
273    let gas_reduce = max(context.interpreter.gas.remaining() / 64, 5000);
274    let gas_limit = context
275        .interpreter
276        .gas
277        .remaining()
278        .saturating_sub(gas_reduce);
279
280    // The MIN_CALLEE_GAS rule is a replacement for stipend:
281    // it simplifies the reasoning about the gas costs and is
282    // applied uniformly for all introduced EXT*CALL instructions.
283    //
284    // If Gas available to callee is less than MIN_CALLEE_GAS trigger light failure (Same as Revert).
285    if gas_limit < MIN_CALLEE_GAS {
286        // Push 1 to stack to indicate that call light failed.
287        // It is safe to ignore stack overflow error as we already popped multiple values from stack.
288        let _ = context.interpreter.stack.push(U256::from(1));
289        context.interpreter.return_data.clear();
290        // Return none to continue execution.
291        return None;
292    }
293
294    gas!(context.interpreter, gas_limit, None);
295    Some(gas_limit)
296}
297
298/// Pop target address from stack and check if it is valid.
299///
300/// Valid address has first 12 bytes as zeroes.
301#[inline]
302pub fn pop_extcall_target_address(
303    interpreter: &mut Interpreter<impl InterpreterTypes>,
304) -> Option<Address> {
305    popn!([target_address], interpreter, None);
306    let target_address = B256::from(target_address);
307    // Check if target is left padded with zeroes.
308    if target_address[..12].iter().any(|i| *i != 0) {
309        interpreter.halt(InstructionResult::InvalidEXTCALLTarget);
310        return None;
311    }
312    // Discard first 12 bytes.
313    Some(Address::from_word(target_address))
314}
315
316pub fn extcall<WIRE: InterpreterTypes, H: Host + ?Sized>(
317    mut context: InstructionContext<'_, H, WIRE>,
318) {
319    require_eof!(context.interpreter);
320
321    // Pop target address
322    let Some(target_address) = pop_extcall_target_address(context.interpreter) else {
323        return;
324    };
325
326    // Input call
327    let Some(input) = extcall_input(context.interpreter) else {
328        return;
329    };
330
331    popn!([value], context.interpreter);
332    let has_transfer = !value.is_zero();
333    if context.interpreter.runtime_flag.is_static() && has_transfer {
334        context
335            .interpreter
336            .halt(InstructionResult::CallNotAllowedInsideStatic);
337        return;
338    }
339
340    let Some(gas_limit) = extcall_gas_calc(&mut context, target_address, has_transfer) else {
341        return;
342    };
343
344    // Call host to interact with target contract
345    context
346        .interpreter
347        .bytecode
348        .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(
349            CallInputs {
350                input: CallInput::Bytes(input),
351                gas_limit,
352                target_address,
353                caller: context.interpreter.input.target_address(),
354                bytecode_address: target_address,
355                value: CallValue::Transfer(value),
356                scheme: CallScheme::ExtCall,
357                is_static: context.interpreter.runtime_flag.is_static(),
358                is_eof: true,
359                return_memory_offset: 0..0,
360            },
361        ))));
362}
363
364pub fn extdelegatecall<WIRE: InterpreterTypes, H: Host + ?Sized>(
365    mut context: InstructionContext<'_, H, WIRE>,
366) {
367    require_eof!(context.interpreter);
368
369    // Pop target address
370    let Some(target_address) = pop_extcall_target_address(context.interpreter) else {
371        return;
372    };
373
374    // Input call
375    let Some(input) = extcall_input(context.interpreter) else {
376        return;
377    };
378
379    let Some(gas_limit) = extcall_gas_calc(&mut context, target_address, false) else {
380        return;
381    };
382
383    // Call host to interact with target contract
384    context
385        .interpreter
386        .bytecode
387        .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(
388            CallInputs {
389                input: CallInput::Bytes(input),
390                gas_limit,
391                target_address: context.interpreter.input.target_address(),
392                caller: context.interpreter.input.caller_address(),
393                bytecode_address: target_address,
394                value: CallValue::Apparent(context.interpreter.input.call_value()),
395                scheme: CallScheme::ExtDelegateCall,
396                is_static: context.interpreter.runtime_flag.is_static(),
397                is_eof: true,
398                return_memory_offset: 0..0,
399            },
400        ))));
401}
402
403pub fn extstaticcall<WIRE: InterpreterTypes, H: Host + ?Sized>(
404    mut context: InstructionContext<'_, H, WIRE>,
405) {
406    require_eof!(context.interpreter);
407
408    // Pop target address
409    let Some(target_address) = pop_extcall_target_address(context.interpreter) else {
410        return;
411    };
412
413    // Input call
414    let Some(input) = extcall_input(context.interpreter) else {
415        return;
416    };
417
418    let Some(gas_limit) = extcall_gas_calc(&mut context, target_address, false) else {
419        return;
420    };
421
422    // Call host to interact with target contract
423    context
424        .interpreter
425        .bytecode
426        .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(
427            CallInputs {
428                input: CallInput::Bytes(input),
429                gas_limit,
430                target_address,
431                caller: context.interpreter.input.target_address(),
432                bytecode_address: target_address,
433                value: CallValue::Transfer(U256::ZERO),
434                scheme: CallScheme::ExtStaticCall,
435                is_static: true,
436                is_eof: true,
437                return_memory_offset: 0..0,
438            },
439        ))));
440}
441
442pub fn create<WIRE: InterpreterTypes, const IS_CREATE2: bool, H: Host + ?Sized>(
443    context: InstructionContext<'_, H, WIRE>,
444) {
445    require_non_staticcall!(context.interpreter);
446
447    // EIP-1014: Skinny CREATE2
448    if IS_CREATE2 {
449        check!(context.interpreter, PETERSBURG);
450    }
451
452    popn!([value, code_offset, len], context.interpreter);
453    let len = as_usize_or_fail!(context.interpreter, len);
454
455    let mut code = Bytes::new();
456    if len != 0 {
457        // EIP-3860: Limit and meter initcode
458        if context
459            .interpreter
460            .runtime_flag
461            .spec_id()
462            .is_enabled_in(SpecId::SHANGHAI)
463        {
464            // Limit is set as double of max contract bytecode size
465            if len > context.host.max_initcode_size() {
466                context
467                    .interpreter
468                    .halt(InstructionResult::CreateInitCodeSizeLimit);
469                return;
470            }
471            gas!(context.interpreter, gas::initcode_cost(len));
472        }
473
474        let code_offset = as_usize_or_fail!(context.interpreter, code_offset);
475        resize_memory!(context.interpreter, code_offset, len);
476        code = Bytes::copy_from_slice(
477            context
478                .interpreter
479                .memory
480                .slice_len(code_offset, len)
481                .as_ref(),
482        );
483    }
484
485    // EIP-1014: Skinny CREATE2
486    let scheme = if IS_CREATE2 {
487        popn!([salt], context.interpreter);
488        // SAFETY: `len` is reasonable in size as gas for it is already deducted.
489        gas_or_fail!(context.interpreter, gas::create2_cost(len));
490        CreateScheme::Create2 { salt }
491    } else {
492        gas!(context.interpreter, gas::CREATE);
493        CreateScheme::Create
494    };
495
496    let mut gas_limit = context.interpreter.gas.remaining();
497
498    // EIP-150: Gas cost changes for IO-heavy operations
499    if context
500        .interpreter
501        .runtime_flag
502        .spec_id()
503        .is_enabled_in(SpecId::TANGERINE)
504    {
505        // Take remaining gas and deduce l64 part of it.
506        gas_limit -= gas_limit / 64
507    }
508    gas!(context.interpreter, gas_limit);
509
510    // Call host to interact with target contract
511    context
512        .interpreter
513        .bytecode
514        .set_action(InterpreterAction::NewFrame(FrameInput::Create(Box::new(
515            CreateInputs {
516                caller: context.interpreter.input.target_address(),
517                scheme,
518                value,
519                init_code: code,
520                gas_limit,
521            },
522        ))));
523}
524
525pub fn call<WIRE: InterpreterTypes, H: Host + ?Sized>(context: InstructionContext<'_, H, WIRE>) {
526    popn!([local_gas_limit, to, value], context.interpreter);
527    let to = to.into_address();
528    // Max gas limit is not possible in real ethereum situation.
529    let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);
530
531    let has_transfer = !value.is_zero();
532    if context.interpreter.runtime_flag.is_static() && has_transfer {
533        context
534            .interpreter
535            .halt(InstructionResult::CallNotAllowedInsideStatic);
536        return;
537    }
538
539    let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter)
540    else {
541        return;
542    };
543
544    let Some(account_load) = context.host.load_account_delegated(to) else {
545        context
546            .interpreter
547            .halt(InstructionResult::FatalExternalError);
548        return;
549    };
550
551    let Some(mut gas_limit) = calc_call_gas(
552        context.interpreter,
553        account_load,
554        has_transfer,
555        local_gas_limit,
556    ) else {
557        return;
558    };
559
560    gas!(context.interpreter, gas_limit);
561
562    // Add call stipend if there is value to be transferred.
563    if has_transfer {
564        gas_limit = gas_limit.saturating_add(gas::CALL_STIPEND);
565    }
566
567    // Call host to interact with target contract
568    context
569        .interpreter
570        .bytecode
571        .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(
572            CallInputs {
573                input: CallInput::SharedBuffer(input),
574                gas_limit,
575                target_address: to,
576                caller: context.interpreter.input.target_address(),
577                bytecode_address: to,
578                value: CallValue::Transfer(value),
579                scheme: CallScheme::Call,
580                is_static: context.interpreter.runtime_flag.is_static(),
581                is_eof: false,
582                return_memory_offset,
583            },
584        ))));
585}
586
587pub fn call_code<WIRE: InterpreterTypes, H: Host + ?Sized>(
588    context: InstructionContext<'_, H, WIRE>,
589) {
590    popn!([local_gas_limit, to, value], context.interpreter);
591    let to = Address::from_word(B256::from(to));
592    // Max gas limit is not possible in real ethereum situation.
593    let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);
594
595    //pop!(context.interpreter, value);
596    let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter)
597    else {
598        return;
599    };
600
601    let Some(mut load) = context.host.load_account_delegated(to) else {
602        context
603            .interpreter
604            .halt(InstructionResult::FatalExternalError);
605        return;
606    };
607
608    // Set `is_empty` to false as we are not creating this account.
609    load.is_empty = false;
610    let Some(mut gas_limit) =
611        calc_call_gas(context.interpreter, load, !value.is_zero(), local_gas_limit)
612    else {
613        return;
614    };
615
616    gas!(context.interpreter, gas_limit);
617
618    // Add call stipend if there is value to be transferred.
619    if !value.is_zero() {
620        gas_limit = gas_limit.saturating_add(gas::CALL_STIPEND);
621    }
622
623    // Call host to interact with target contract
624    context
625        .interpreter
626        .bytecode
627        .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(
628            CallInputs {
629                input: CallInput::SharedBuffer(input),
630                gas_limit,
631                target_address: context.interpreter.input.target_address(),
632                caller: context.interpreter.input.target_address(),
633                bytecode_address: to,
634                value: CallValue::Transfer(value),
635                scheme: CallScheme::CallCode,
636                is_static: context.interpreter.runtime_flag.is_static(),
637                is_eof: false,
638                return_memory_offset,
639            },
640        ))));
641}
642
643pub fn delegate_call<WIRE: InterpreterTypes, H: Host + ?Sized>(
644    context: InstructionContext<'_, H, WIRE>,
645) {
646    check!(context.interpreter, HOMESTEAD);
647    popn!([local_gas_limit, to], context.interpreter);
648    let to = Address::from_word(B256::from(to));
649    // Max gas limit is not possible in real ethereum situation.
650    let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);
651
652    let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter)
653    else {
654        return;
655    };
656
657    let Some(mut load) = context.host.load_account_delegated(to) else {
658        context
659            .interpreter
660            .halt(InstructionResult::FatalExternalError);
661        return;
662    };
663
664    // Set is_empty to false as we are not creating this account.
665    load.is_empty = false;
666    let Some(gas_limit) = calc_call_gas(context.interpreter, load, false, local_gas_limit) else {
667        return;
668    };
669
670    gas!(context.interpreter, gas_limit);
671
672    // Call host to interact with target contract
673    context
674        .interpreter
675        .bytecode
676        .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(
677            CallInputs {
678                input: CallInput::SharedBuffer(input),
679                gas_limit,
680                target_address: context.interpreter.input.target_address(),
681                caller: context.interpreter.input.caller_address(),
682                bytecode_address: to,
683                value: CallValue::Apparent(context.interpreter.input.call_value()),
684                scheme: CallScheme::DelegateCall,
685                is_static: context.interpreter.runtime_flag.is_static(),
686                is_eof: false,
687                return_memory_offset,
688            },
689        ))));
690}
691
692pub fn static_call<WIRE: InterpreterTypes, H: Host + ?Sized>(
693    context: InstructionContext<'_, H, WIRE>,
694) {
695    check!(context.interpreter, BYZANTIUM);
696    popn!([local_gas_limit, to], context.interpreter);
697    let to = Address::from_word(B256::from(to));
698    // Max gas limit is not possible in real ethereum situation.
699    let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);
700
701    let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter)
702    else {
703        return;
704    };
705
706    let Some(mut load) = context.host.load_account_delegated(to) else {
707        context
708            .interpreter
709            .halt(InstructionResult::FatalExternalError);
710        return;
711    };
712    // Set `is_empty` to false as we are not creating this account.
713    load.is_empty = false;
714    let Some(gas_limit) = calc_call_gas(context.interpreter, load, false, local_gas_limit) else {
715        return;
716    };
717    gas!(context.interpreter, gas_limit);
718
719    // Call host to interact with target contract
720    context
721        .interpreter
722        .bytecode
723        .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(
724            CallInputs {
725                input: CallInput::SharedBuffer(input),
726                gas_limit,
727                target_address: to,
728                caller: context.interpreter.input.target_address(),
729                bytecode_address: to,
730                value: CallValue::Transfer(U256::ZERO),
731                scheme: CallScheme::StaticCall,
732                is_static: true,
733                is_eof: false,
734                return_memory_offset,
735            },
736        ))));
737}