mod call_helpers;
use super::utility::IntoAddress;
use crate::{
exec::CallResources,
vm::{
evm::{interpreter::Halt, util::as_usize_or_halt, Interpreter},
Ext, RuntimeCosts,
},
Code, DebugSettings, Error, Pallet, ReentrancyProtection, H160, LOG_TARGET, U256,
};
use alloc::{vec, vec::Vec};
pub use call_helpers::{charge_call_gas, get_memory_in_and_out_ranges};
use core::{
cmp::min,
ops::{ControlFlow, Range},
};
use revm::interpreter::{gas::CALL_STIPEND, interpreter_action::CallScheme};
pub fn create<const IS_CREATE2: bool, E: Ext>(
interpreter: &mut Interpreter<E>,
) -> ControlFlow<Halt> {
if interpreter.ext.is_read_only() {
return ControlFlow::Break(Error::<E::T>::StateChangeDenied.into());
}
let [value, code_offset, len] = interpreter.stack.popn()?;
let len = as_usize_or_halt::<E::T>(len)?;
interpreter.ext.charge_or_halt(RuntimeCosts::Create {
init_code_len: len as u32,
balance_transfer: Pallet::<E::T>::has_balance(value),
dust_transfer: Pallet::<E::T>::has_dust(value),
})?;
let mut code = Vec::new();
if len != 0 {
if len > revm::primitives::eip3860::MAX_INITCODE_SIZE &&
!DebugSettings::is_unlimited_contract_size_allowed::<E::T>()
{
return ControlFlow::Break(Error::<E::T>::BlobTooLarge.into());
}
let code_offset = as_usize_or_halt::<E::T>(code_offset)?;
interpreter.memory.resize(code_offset, len)?;
code = interpreter.memory.slice_len(code_offset, len).to_vec();
}
let salt = if IS_CREATE2 {
let [salt] = interpreter.stack.popn()?;
Some(salt.to_big_endian())
} else {
None
};
let call_result = interpreter.ext.instantiate(
&CallResources::NoLimits,
Code::Upload(code),
value,
vec![],
salt.as_ref(),
);
match call_result {
Ok(address) => {
let return_value = interpreter.ext.last_frame_output();
if return_value.did_revert() {
interpreter.stack.push(U256::zero())
} else {
*interpreter.ext.last_frame_output_mut() = Default::default();
interpreter.stack.push(address)
}
},
Err(err) => {
log::debug!(target: LOG_TARGET, "Create failed: {err:?}");
interpreter.stack.push(U256::zero())?;
ControlFlow::Continue(())
},
}
}
pub fn call<E: Ext>(interpreter: &mut Interpreter<E>) -> ControlFlow<Halt> {
let [gas_limit, to, value] = interpreter.stack.popn()?;
let to = to.into_address();
let has_transfer = !value.is_zero();
if interpreter.ext.is_read_only() && has_transfer {
return ControlFlow::Break(Error::<E::T>::StateChangeDenied.into());
}
let (input, return_memory_range) = get_memory_in_and_out_ranges(interpreter)?;
let scheme = CallScheme::Call;
charge_call_gas(interpreter, to, scheme, input.len(), value)?;
run_call(
interpreter,
to,
gas_limit,
interpreter.memory.slice(input).to_vec(),
scheme,
value,
return_memory_range,
)
}
pub fn call_code<E: Ext>(_interpreter: &mut Interpreter<E>) -> ControlFlow<Halt> {
ControlFlow::Break(Error::<E::T>::InvalidInstruction.into())
}
pub fn delegate_call<E: Ext>(interpreter: &mut Interpreter<E>) -> ControlFlow<Halt> {
let [gas_limit, to] = interpreter.stack.popn()?;
let to = to.into_address();
let (input, return_memory_range) = get_memory_in_and_out_ranges(interpreter)?;
let scheme = CallScheme::DelegateCall;
let value = U256::zero();
charge_call_gas(interpreter, to, scheme, input.len(), value)?;
run_call(
interpreter,
to,
gas_limit,
interpreter.memory.slice(input).to_vec(),
scheme,
value,
return_memory_range,
)
}
pub fn static_call<E: Ext>(interpreter: &mut Interpreter<E>) -> ControlFlow<Halt> {
let [gas_limit, to] = interpreter.stack.popn()?;
let to = to.into_address();
let (input, return_memory_range) = get_memory_in_and_out_ranges(interpreter)?;
let scheme = CallScheme::StaticCall;
let value = U256::zero();
charge_call_gas(interpreter, to, scheme, input.len(), value)?;
run_call(
interpreter,
to,
gas_limit,
interpreter.memory.slice(input).to_vec(),
scheme,
value,
return_memory_range,
)
}
fn run_call<'a, E: Ext>(
interpreter: &mut Interpreter<'a, E>,
callee: H160,
gas_limit: U256,
input: Vec<u8>,
scheme: CallScheme,
value: U256,
return_memory_range: Range<usize>,
) -> ControlFlow<Halt> {
let (add_stipend, reentracy) =
match (value.is_zero(), gas_limit.try_into().is_ok_and(|limit: u64| limit == CALL_STIPEND))
{
(false, _) => (true, ReentrancyProtection::AllowReentry),
(_, true) => (true, ReentrancyProtection::AllowNext),
(_, _) => (false, ReentrancyProtection::AllowReentry),
};
let call_result = match scheme {
CallScheme::Call | CallScheme::StaticCall => interpreter.ext.call(
&CallResources::from_ethereum_gas(gas_limit, add_stipend),
&callee,
value,
input,
reentracy,
scheme.is_static_call(),
),
CallScheme::DelegateCall => interpreter.ext.delegate_call(
&CallResources::from_ethereum_gas(gas_limit, add_stipend),
callee,
input,
),
CallScheme::CallCode => {
unreachable!()
},
};
match call_result {
Ok(()) => {
let mem_start = return_memory_range.start;
let mem_length = return_memory_range.len();
let returned_len = interpreter.ext.last_frame_output().data.len();
let target_len = min(mem_length, returned_len);
interpreter
.ext
.frame_meter_mut()
.charge_or_halt(RuntimeCosts::CopyToContract(target_len as u32))?;
let return_value = interpreter.ext.last_frame_output();
let return_data = &return_value.data;
let did_revert = return_value.did_revert();
interpreter.memory.set(mem_start, &return_data[..target_len]);
interpreter.stack.push(U256::from(!did_revert as u8))
},
Err(err) => {
log::debug!(target: LOG_TARGET, "Call failed: {err:?}");
interpreter.stack.push(U256::zero())?;
ControlFlow::Continue(())
},
}
}