mod call_helpers;
pub use call_helpers::{calc_call_gas, get_memory_input_and_out_ranges, resize_memory};
use crate::{
gas::{self, cost_per_word, EOF_CREATE_GAS, KECCAK256WORD, MIN_CALLEE_GAS},
instructions::utility::IntoAddress,
interpreter::Interpreter,
interpreter_action::FrameInput,
interpreter_types::{
EofContainer, Immediates, InputsTrait, InterpreterTypes, Jumps, LoopControl, MemoryTrait,
ReturnData, RuntimeFlag, StackTrait,
},
CallInputs, CallScheme, CallValue, CreateInputs, EOFCreateInputs, Host, InstructionResult,
InterpreterAction, InterpreterResult,
};
use bytecode::eof::{Eof, EofHeader};
use context_interface::{Cfg, CreateScheme};
use core::cmp::max;
use primitives::{keccak256, Address, Bytes, B256, U256};
use specification::hardfork::SpecId;
use std::boxed::Box;
pub fn eofcreate<WIRE: InterpreterTypes, H: Host + ?Sized>(
interpreter: &mut Interpreter<WIRE>,
_host: &mut H,
) {
require_eof!(interpreter);
require_non_staticcall!(interpreter);
gas!(interpreter, EOF_CREATE_GAS);
let initcontainer_index = interpreter.bytecode.read_u8();
popn!([value, salt, data_offset, data_size], interpreter);
let container = interpreter
.bytecode
.eof_container(initcontainer_index as usize)
.expect("valid container")
.clone();
let Some(input_range) = resize_memory(interpreter, data_offset, data_size) else {
return;
};
let input = if !input_range.is_empty() {
interpreter.memory.slice(input_range).to_vec().into()
} else {
Bytes::new()
};
let eof = Eof::decode(container.clone()).expect("Subcontainer is verified");
if !eof.body.is_data_filled {
panic!("Panic if data section is not full");
}
gas_or_fail!(interpreter, cost_per_word(container.len(), KECCAK256WORD));
let created_address = interpreter
.input
.target_address()
.create2(salt.to_be_bytes(), keccak256(container));
let gas_limit = interpreter.control.gas().remaining_63_of_64_parts();
gas!(interpreter, gas_limit);
interpreter.control.set_next_action(
InterpreterAction::NewFrame(FrameInput::EOFCreate(Box::new(
EOFCreateInputs::new_opcode(
interpreter.input.target_address(),
created_address,
value,
eof,
gas_limit,
input,
),
))),
InstructionResult::CallOrCreate,
);
interpreter.bytecode.relative_jump(1);
}
pub fn return_contract<H: Host + ?Sized>(
interpreter: &mut Interpreter<impl InterpreterTypes>,
_host: &mut H,
) {
if !interpreter.runtime_flag.is_eof_init() {
interpreter
.control
.set_instruction_result(InstructionResult::ReturnContractInNotInitEOF);
return;
}
let deploy_container_index = interpreter.bytecode.read_u8();
popn!([aux_data_offset, aux_data_size], interpreter);
let aux_data_size = as_usize_or_fail!(interpreter, aux_data_size);
let container = interpreter
.bytecode
.eof_container(deploy_container_index as usize)
.expect("valid container")
.clone();
let (eof_header, _) = EofHeader::decode(&container).expect("valid EOF header");
let static_aux_size = eof_header.eof_size() - container.len();
let mut output = if aux_data_size != 0 {
let aux_data_offset = as_usize_or_fail!(interpreter, aux_data_offset);
resize_memory!(interpreter, aux_data_offset, aux_data_size);
let aux_slice = interpreter.memory.slice_len(aux_data_offset, aux_data_size);
[&container, aux_slice.as_ref()].concat()
} else {
container.to_vec()
};
let new_data_size = eof_header.data_size as usize - static_aux_size + aux_data_size;
if new_data_size > 0xFFFF {
interpreter
.control
.set_instruction_result(InstructionResult::EofAuxDataOverflow);
return;
}
if new_data_size < eof_header.data_size as usize {
interpreter
.control
.set_instruction_result(InstructionResult::EofAuxDataTooSmall);
return;
}
let new_data_size = (new_data_size as u16).to_be_bytes();
output[eof_header.data_size_raw_i()..][..2].clone_from_slice(&new_data_size);
let output: Bytes = output.into();
let result = InstructionResult::ReturnContract;
let gas = *interpreter.control.gas();
interpreter.control.set_next_action(
crate::InterpreterAction::Return {
result: InterpreterResult {
output,
gas,
result,
},
},
result,
);
}
pub fn extcall_input(interpreter: &mut Interpreter<impl InterpreterTypes>) -> Option<Bytes> {
popn!([input_offset, input_size], interpreter, None);
let return_memory_offset = resize_memory(interpreter, input_offset, input_size)?;
if return_memory_offset.is_empty() {
return Some(Bytes::new());
}
Some(Bytes::copy_from_slice(
interpreter
.memory
.slice(return_memory_offset.clone())
.as_ref(),
))
}
pub fn extcall_gas_calc<WIRE: InterpreterTypes, H: Host + ?Sized>(
interpreter: &mut Interpreter<WIRE>,
host: &mut H,
target: Address,
transfers_value: bool,
) -> Option<u64> {
let Some(account_load) = host.load_account_delegated(target) else {
interpreter
.control
.set_instruction_result(InstructionResult::FatalExternalError);
return None;
};
let call_cost = gas::call_cost(
interpreter.runtime_flag.spec_id(),
transfers_value,
account_load,
);
gas!(interpreter, call_cost, None);
let gas_reduce = max(interpreter.control.gas().remaining() / 64, 5000);
let gas_limit = interpreter
.control
.gas()
.remaining()
.saturating_sub(gas_reduce);
if gas_limit < MIN_CALLEE_GAS {
let _ = interpreter.stack.push(U256::from(1));
interpreter.return_data.buffer_mut().clear();
return None;
}
gas!(interpreter, gas_limit, None);
Some(gas_limit)
}
#[inline]
pub fn pop_extcall_target_address(
interpreter: &mut Interpreter<impl InterpreterTypes>,
) -> Option<Address> {
popn!([target_address], interpreter, None);
let target_address = B256::from(target_address);
if target_address[..12].iter().any(|i| *i != 0) {
interpreter
.control
.set_instruction_result(InstructionResult::InvalidEXTCALLTarget);
return None;
}
Some(Address::from_word(target_address))
}
pub fn extcall<WIRE: InterpreterTypes, H: Host + ?Sized>(
interpreter: &mut Interpreter<WIRE>,
host: &mut H,
) {
require_eof!(interpreter);
let Some(target_address) = pop_extcall_target_address(interpreter) else {
return;
};
let Some(input) = extcall_input(interpreter) else {
return;
};
popn!([value], interpreter);
let has_transfer = !value.is_zero();
if interpreter.runtime_flag.is_static() && has_transfer {
interpreter
.control
.set_instruction_result(InstructionResult::CallNotAllowedInsideStatic);
return;
}
let Some(gas_limit) = extcall_gas_calc(interpreter, host, target_address, has_transfer) else {
return;
};
interpreter.control.set_next_action(
InterpreterAction::NewFrame(FrameInput::Call(Box::new(CallInputs {
input,
gas_limit,
target_address,
caller: interpreter.input.target_address(),
bytecode_address: target_address,
value: CallValue::Transfer(value),
scheme: CallScheme::ExtCall,
is_static: interpreter.runtime_flag.is_static(),
is_eof: true,
return_memory_offset: 0..0,
}))),
InstructionResult::CallOrCreate,
);
}
pub fn extdelegatecall<WIRE: InterpreterTypes, H: Host + ?Sized>(
interpreter: &mut Interpreter<WIRE>,
host: &mut H,
) {
require_eof!(interpreter);
let Some(target_address) = pop_extcall_target_address(interpreter) else {
return;
};
let Some(input) = extcall_input(interpreter) else {
return;
};
let Some(gas_limit) = extcall_gas_calc(interpreter, host, target_address, false) else {
return;
};
interpreter.control.set_next_action(
InterpreterAction::NewFrame(FrameInput::Call(Box::new(CallInputs {
input,
gas_limit,
target_address: interpreter.input.target_address(),
caller: interpreter.input.caller_address(),
bytecode_address: target_address,
value: CallValue::Apparent(interpreter.input.call_value()),
scheme: CallScheme::ExtDelegateCall,
is_static: interpreter.runtime_flag.is_static(),
is_eof: true,
return_memory_offset: 0..0,
}))),
InstructionResult::CallOrCreate,
);
}
pub fn extstaticcall<WIRE: InterpreterTypes, H: Host + ?Sized>(
interpreter: &mut Interpreter<WIRE>,
host: &mut H,
) {
require_eof!(interpreter);
let Some(target_address) = pop_extcall_target_address(interpreter) else {
return;
};
let Some(input) = extcall_input(interpreter) else {
return;
};
let Some(gas_limit) = extcall_gas_calc(interpreter, host, target_address, false) else {
return;
};
interpreter.control.set_next_action(
InterpreterAction::NewFrame(FrameInput::Call(Box::new(CallInputs {
input,
gas_limit,
target_address,
caller: interpreter.input.target_address(),
bytecode_address: target_address,
value: CallValue::Transfer(U256::ZERO),
scheme: CallScheme::ExtStaticCall,
is_static: true,
is_eof: true,
return_memory_offset: 0..0,
}))),
InstructionResult::CallOrCreate,
);
}
pub fn create<WIRE: InterpreterTypes, const IS_CREATE2: bool, H: Host + ?Sized>(
interpreter: &mut Interpreter<WIRE>,
host: &mut H,
) {
require_non_staticcall!(interpreter);
if IS_CREATE2 {
check!(interpreter, PETERSBURG);
}
popn!([value, code_offset, len], interpreter);
let len = as_usize_or_fail!(interpreter, len);
let mut code = Bytes::new();
if len != 0 {
if interpreter
.runtime_flag
.spec_id()
.is_enabled_in(SpecId::SHANGHAI)
{
let max_initcode_size = host.cfg().max_code_size().saturating_mul(2);
if len > max_initcode_size {
interpreter
.control
.set_instruction_result(InstructionResult::CreateInitCodeSizeLimit);
return;
}
gas!(interpreter, gas::initcode_cost(len));
}
let code_offset = as_usize_or_fail!(interpreter, code_offset);
resize_memory!(interpreter, code_offset, len);
code = Bytes::copy_from_slice(interpreter.memory.slice_len(code_offset, len).as_ref());
}
let scheme = if IS_CREATE2 {
popn!([salt], interpreter);
gas_or_fail!(interpreter, gas::create2_cost(len));
CreateScheme::Create2 { salt }
} else {
gas!(interpreter, gas::CREATE);
CreateScheme::Create
};
let mut gas_limit = interpreter.control.gas().remaining();
if interpreter
.runtime_flag
.spec_id()
.is_enabled_in(SpecId::TANGERINE)
{
gas_limit -= gas_limit / 64
}
gas!(interpreter, gas_limit);
interpreter.control.set_next_action(
InterpreterAction::NewFrame(FrameInput::Create(Box::new(CreateInputs {
caller: interpreter.input.target_address(),
scheme,
value,
init_code: code,
gas_limit,
}))),
InstructionResult::CallOrCreate,
);
}
pub fn call<WIRE: InterpreterTypes, H: Host + ?Sized>(
interpreter: &mut Interpreter<WIRE>,
host: &mut H,
) {
popn!([local_gas_limit, to, value], interpreter);
let to = to.into_address();
let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);
let has_transfer = !value.is_zero();
if interpreter.runtime_flag.is_static() && has_transfer {
interpreter
.control
.set_instruction_result(InstructionResult::CallNotAllowedInsideStatic);
return;
}
let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(interpreter) else {
return;
};
let Some(account_load) = host.load_account_delegated(to) else {
interpreter
.control
.set_instruction_result(InstructionResult::FatalExternalError);
return;
};
let Some(mut gas_limit) =
calc_call_gas(interpreter, account_load, has_transfer, local_gas_limit)
else {
return;
};
gas!(interpreter, gas_limit);
if has_transfer {
gas_limit = gas_limit.saturating_add(gas::CALL_STIPEND);
}
interpreter.control.set_next_action(
InterpreterAction::NewFrame(FrameInput::Call(Box::new(CallInputs {
input,
gas_limit,
target_address: to,
caller: interpreter.input.target_address(),
bytecode_address: to,
value: CallValue::Transfer(value),
scheme: CallScheme::Call,
is_static: interpreter.runtime_flag.is_static(),
is_eof: false,
return_memory_offset,
}))),
InstructionResult::CallOrCreate,
);
}
pub fn call_code<WIRE: InterpreterTypes, H: Host + ?Sized>(
interpreter: &mut Interpreter<WIRE>,
host: &mut H,
) {
popn!([local_gas_limit, to, value], interpreter);
let to = Address::from_word(B256::from(to));
let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);
let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(interpreter) else {
return;
};
let Some(mut load) = host.load_account_delegated(to) else {
interpreter
.control
.set_instruction_result(InstructionResult::FatalExternalError);
return;
};
load.is_empty = false;
let Some(mut gas_limit) = calc_call_gas(interpreter, load, !value.is_zero(), local_gas_limit)
else {
return;
};
gas!(interpreter, gas_limit);
if !value.is_zero() {
gas_limit = gas_limit.saturating_add(gas::CALL_STIPEND);
}
interpreter.control.set_next_action(
InterpreterAction::NewFrame(FrameInput::Call(Box::new(CallInputs {
input,
gas_limit,
target_address: interpreter.input.target_address(),
caller: interpreter.input.target_address(),
bytecode_address: to,
value: CallValue::Transfer(value),
scheme: CallScheme::CallCode,
is_static: interpreter.runtime_flag.is_static(),
is_eof: false,
return_memory_offset,
}))),
InstructionResult::CallOrCreate,
);
}
pub fn delegate_call<WIRE: InterpreterTypes, H: Host + ?Sized>(
interpreter: &mut Interpreter<WIRE>,
host: &mut H,
) {
check!(interpreter, HOMESTEAD);
popn!([local_gas_limit, to], interpreter);
let to = Address::from_word(B256::from(to));
let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);
let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(interpreter) else {
return;
};
let Some(mut load) = host.load_account_delegated(to) else {
interpreter
.control
.set_instruction_result(InstructionResult::FatalExternalError);
return;
};
load.is_empty = false;
let Some(gas_limit) = calc_call_gas(interpreter, load, false, local_gas_limit) else {
return;
};
gas!(interpreter, gas_limit);
interpreter.control.set_next_action(
InterpreterAction::NewFrame(FrameInput::Call(Box::new(CallInputs {
input,
gas_limit,
target_address: interpreter.input.target_address(),
caller: interpreter.input.caller_address(),
bytecode_address: to,
value: CallValue::Apparent(interpreter.input.call_value()),
scheme: CallScheme::DelegateCall,
is_static: interpreter.runtime_flag.is_static(),
is_eof: false,
return_memory_offset,
}))),
InstructionResult::CallOrCreate,
);
}
pub fn static_call<WIRE: InterpreterTypes, H: Host + ?Sized>(
interpreter: &mut Interpreter<WIRE>,
host: &mut H,
) {
check!(interpreter, BYZANTIUM);
popn!([local_gas_limit, to], interpreter);
let to = Address::from_word(B256::from(to));
let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);
let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(interpreter) else {
return;
};
let Some(mut load) = host.load_account_delegated(to) else {
interpreter
.control
.set_instruction_result(InstructionResult::FatalExternalError);
return;
};
load.is_empty = false;
let Some(gas_limit) = calc_call_gas(interpreter, load, false, local_gas_limit) else {
return;
};
gas!(interpreter, gas_limit);
interpreter.control.set_next_action(
InterpreterAction::NewFrame(FrameInput::Call(Box::new(CallInputs {
input,
gas_limit,
target_address: to,
caller: interpreter.input.target_address(),
bytecode_address: to,
value: CallValue::Transfer(U256::ZERO),
scheme: CallScheme::StaticCall,
is_static: true,
is_eof: false,
return_memory_offset,
}))),
InstructionResult::CallOrCreate,
);
}