rtvm_interpreter/instructions/
contract.rs

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