use crate::{
evm::FrameTr, item_or_result::FrameInitOrResult, precompile_provider::PrecompileProvider,
CallFrame, CreateFrame, FrameData, FrameResult, ItemOrResult,
};
use context::result::FromStringError;
use context_interface::{
context::{take_error, ContextError},
journaled_state::{account::JournaledAccountTr, JournalCheckpoint, JournalTr},
local::{FrameToken, OutFrame},
Cfg, ContextTr, Database,
};
use core::cmp::min;
use derive_where::derive_where;
use interpreter::{
interpreter::{EthInterpreter, ExtBytecode},
interpreter_action::FrameInit,
interpreter_types::ReturnData,
CallInput, CallInputs, CallOutcome, CallValue, CreateInputs, CreateOutcome, CreateScheme,
FrameInput, Gas, InputsImpl, InstructionResult, Interpreter, InterpreterAction,
InterpreterResult, InterpreterTypes, SharedMemory,
};
use primitives::{
constants::CALL_STACK_LIMIT,
hardfork::SpecId::{self, HOMESTEAD, LONDON, SPURIOUS_DRAGON},
keccak256, Address, Bytes, U256,
};
use state::Bytecode;
use std::{borrow::ToOwned, boxed::Box, vec::Vec};
#[derive_where(Clone, Debug; IW,
<IW as InterpreterTypes>::Stack,
<IW as InterpreterTypes>::Memory,
<IW as InterpreterTypes>::Bytecode,
<IW as InterpreterTypes>::ReturnData,
<IW as InterpreterTypes>::Input,
<IW as InterpreterTypes>::RuntimeFlag,
<IW as InterpreterTypes>::Extend,
)]
pub struct EthFrame<IW: InterpreterTypes = EthInterpreter> {
pub data: FrameData,
pub input: FrameInput,
pub depth: usize,
pub checkpoint: JournalCheckpoint,
pub interpreter: Interpreter<IW>,
pub is_finished: bool,
}
impl<IT: InterpreterTypes> FrameTr for EthFrame<IT> {
type FrameResult = FrameResult;
type FrameInit = FrameInit;
}
impl Default for EthFrame<EthInterpreter> {
fn default() -> Self {
Self::do_default(Interpreter::default())
}
}
impl EthFrame<EthInterpreter> {
pub fn invalid() -> Self {
Self::do_default(Interpreter::invalid())
}
fn do_default(interpreter: Interpreter<EthInterpreter>) -> Self {
Self {
data: FrameData::Call(CallFrame {
return_memory_range: 0..0,
}),
input: FrameInput::Empty,
depth: 0,
checkpoint: JournalCheckpoint::default(),
interpreter,
is_finished: false,
}
}
pub fn is_finished(&self) -> bool {
self.is_finished
}
pub fn set_finished(&mut self, finished: bool) {
self.is_finished = finished;
}
}
pub type ContextTrDbError<CTX> = <<CTX as ContextTr>::Db as Database>::Error;
impl EthFrame<EthInterpreter> {
#[allow(clippy::too_many_arguments)]
#[inline(always)]
pub fn clear(
&mut self,
data: FrameData,
input: FrameInput,
depth: usize,
memory: SharedMemory,
bytecode: ExtBytecode,
inputs: InputsImpl,
is_static: bool,
spec_id: SpecId,
gas_limit: u64,
reservoir_remaining_gas: u64,
checkpoint: JournalCheckpoint,
) {
let Self {
data: data_ref,
input: input_ref,
depth: depth_ref,
interpreter,
checkpoint: checkpoint_ref,
is_finished: is_finished_ref,
} = self;
*data_ref = data;
*input_ref = input;
*depth_ref = depth;
*is_finished_ref = false;
interpreter.clear(
memory,
bytecode,
inputs,
is_static,
spec_id,
gas_limit,
reservoir_remaining_gas,
);
*checkpoint_ref = checkpoint;
}
#[inline]
pub fn make_call_frame<
CTX: ContextTr,
PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
ERROR: From<ContextTrDbError<CTX>> + FromStringError,
>(
mut this: OutFrame<'_, Self>,
ctx: &mut CTX,
precompiles: &mut PRECOMPILES,
depth: usize,
memory: SharedMemory,
inputs: Box<CallInputs>,
) -> Result<ItemOrResult<FrameToken, FrameResult>, ERROR> {
let reservoir_remaining_gas = inputs.reservoir;
let gas =
Gas::new_with_regular_gas_and_reservoir(inputs.gas_limit, reservoir_remaining_gas);
let return_result = |instruction_result: InstructionResult| {
Ok(ItemOrResult::Result(FrameResult::Call(CallOutcome {
result: InterpreterResult {
result: instruction_result,
gas,
output: Bytes::new(),
},
memory_offset: inputs.return_memory_offset.clone(),
was_precompile_called: false,
precompile_call_logs: Vec::new(),
})))
};
if depth > CALL_STACK_LIMIT as usize {
return return_result(InstructionResult::CallTooDeep);
}
let checkpoint = ctx.journal_mut().checkpoint();
if let CallValue::Transfer(value) = inputs.value {
if let Some(i) =
ctx.journal_mut()
.transfer_loaded(inputs.caller, inputs.target_address, value)
{
ctx.journal_mut().checkpoint_revert(checkpoint);
return return_result(i.into());
}
}
let interpreter_input = InputsImpl {
target_address: inputs.target_address,
caller_address: inputs.caller,
bytecode_address: Some(inputs.bytecode_address),
input: inputs.input.clone(),
call_value: inputs.value.get(),
};
let is_static = inputs.is_static;
let gas_limit = inputs.gas_limit;
if let Some(result) = precompiles.run(ctx, &inputs).map_err(ERROR::from_string)? {
let mut logs = Vec::new();
if result.result.is_ok() {
ctx.journal_mut().checkpoint_commit();
} else {
logs = ctx.journal_mut().logs()[checkpoint.log_i..].to_vec();
ctx.journal_mut().checkpoint_revert(checkpoint);
}
return Ok(ItemOrResult::Result(FrameResult::Call(CallOutcome {
result,
memory_offset: inputs.return_memory_offset.clone(),
was_precompile_called: true,
precompile_call_logs: logs,
})));
}
let (bytecode_hash, bytecode) = inputs.known_bytecode.clone();
if bytecode.is_empty() {
ctx.journal_mut().checkpoint_commit();
return return_result(InstructionResult::Stop);
}
this.get(EthFrame::invalid).clear(
FrameData::Call(CallFrame {
return_memory_range: inputs.return_memory_offset.clone(),
}),
FrameInput::Call(inputs),
depth,
memory,
ExtBytecode::new_with_hash(bytecode, bytecode_hash),
interpreter_input,
is_static,
ctx.cfg().spec().into(),
gas_limit,
reservoir_remaining_gas,
checkpoint,
);
Ok(ItemOrResult::Item(this.consume()))
}
#[inline]
pub fn make_create_frame<
CTX: ContextTr,
ERROR: From<ContextTrDbError<CTX>> + FromStringError,
>(
mut this: OutFrame<'_, Self>,
context: &mut CTX,
depth: usize,
memory: SharedMemory,
inputs: Box<CreateInputs>,
) -> Result<ItemOrResult<FrameToken, FrameResult>, ERROR> {
let reservoir_remaining_gas = inputs.reservoir();
let spec = context.cfg().spec().into();
let return_error = |e| {
Ok(ItemOrResult::Result(FrameResult::Create(CreateOutcome {
result: InterpreterResult {
result: e,
gas: Gas::new_with_regular_gas_and_reservoir(
inputs.gas_limit(),
reservoir_remaining_gas,
),
output: Bytes::new(),
},
address: None,
})))
};
if depth > CALL_STACK_LIMIT as usize {
return return_error(InstructionResult::CallTooDeep);
}
let journal = context.journal_mut();
let mut caller_info = journal.load_account_mut(inputs.caller())?;
if *caller_info.balance() < inputs.value() {
return return_error(InstructionResult::OutOfFunds);
}
let old_nonce = caller_info.nonce();
if !caller_info.bump_nonce() {
return return_error(InstructionResult::Return);
};
let mut init_code_hash = None;
let created_address = match inputs.scheme() {
CreateScheme::Create => inputs.caller().create(old_nonce),
CreateScheme::Create2 { salt } => {
let init_code_hash = *init_code_hash.insert(keccak256(inputs.init_code()));
inputs.caller().create2(salt.to_be_bytes(), init_code_hash)
}
CreateScheme::Custom { address } => address,
};
drop(caller_info);
journal.load_account(created_address)?;
let checkpoint = match context.journal_mut().create_account_checkpoint(
inputs.caller(),
created_address,
inputs.value(),
spec,
) {
Ok(checkpoint) => checkpoint,
Err(e) => return return_error(e.into()),
};
let bytecode = ExtBytecode::new_with_optional_hash(
Bytecode::new_legacy(inputs.init_code().clone()),
init_code_hash,
);
let interpreter_input = InputsImpl {
target_address: created_address,
caller_address: inputs.caller(),
bytecode_address: None,
input: CallInput::Bytes(Bytes::new()),
call_value: inputs.value(),
};
let gas_limit = inputs.gas_limit();
this.get(EthFrame::invalid).clear(
FrameData::Create(CreateFrame { created_address }),
FrameInput::Create(inputs),
depth,
memory,
bytecode,
interpreter_input,
false,
spec,
gas_limit,
reservoir_remaining_gas,
checkpoint,
);
Ok(ItemOrResult::Item(this.consume()))
}
pub fn init_with_context<
CTX: ContextTr,
PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
>(
this: OutFrame<'_, Self>,
ctx: &mut CTX,
precompiles: &mut PRECOMPILES,
frame_init: FrameInit,
) -> Result<
ItemOrResult<FrameToken, FrameResult>,
ContextError<<<CTX as ContextTr>::Db as Database>::Error>,
> {
let FrameInit {
depth,
memory,
frame_input,
} = frame_init;
match frame_input {
FrameInput::Call(inputs) => {
Self::make_call_frame(this, ctx, precompiles, depth, memory, inputs)
}
FrameInput::Create(inputs) => Self::make_create_frame(this, ctx, depth, memory, inputs),
FrameInput::Empty => unreachable!(),
}
}
}
impl EthFrame<EthInterpreter> {
pub fn process_next_action<
CTX: ContextTr,
ERROR: From<ContextTrDbError<CTX>> + FromStringError,
>(
&mut self,
context: &mut CTX,
next_action: InterpreterAction,
) -> Result<FrameInitOrResult<Self>, ERROR> {
let mut interpreter_result = match next_action {
InterpreterAction::NewFrame(frame_input) => {
let depth = self.depth + 1;
return Ok(ItemOrResult::Item(FrameInit {
frame_input,
depth,
memory: self.interpreter.memory.new_child_context(),
}));
}
InterpreterAction::Return(result) => result,
};
let result = match &self.data {
FrameData::Call(frame) => {
if interpreter_result.result.is_ok() {
context.journal_mut().checkpoint_commit();
} else {
context.journal_mut().checkpoint_revert(self.checkpoint);
}
ItemOrResult::Result(FrameResult::Call(CallOutcome::new(
interpreter_result,
frame.return_memory_range.clone(),
)))
}
FrameData::Create(frame) => {
let (cfg, journal) = context.cfg_journal_mut();
return_create(
journal,
cfg,
self.checkpoint,
&mut interpreter_result,
frame.created_address,
);
ItemOrResult::Result(FrameResult::Create(CreateOutcome::new(
interpreter_result,
Some(frame.created_address),
)))
}
};
Ok(result)
}
pub fn return_result<CTX: ContextTr, ERROR: From<ContextTrDbError<CTX>> + FromStringError>(
&mut self,
ctx: &mut CTX,
result: FrameResult,
) -> Result<(), ERROR> {
self.interpreter.memory.free_child_context();
take_error::<ERROR, _>(ctx.error())?;
match result {
FrameResult::Call(outcome) => {
let out_gas = outcome.gas();
let ins_result = *outcome.instruction_result();
let returned_len = outcome.result.output.len();
let interpreter = &mut self.interpreter;
let mem_length = outcome.memory_length();
let mem_start = outcome.memory_start();
interpreter.return_data.set_buffer(outcome.result.output);
let target_len = min(mem_length, returned_len);
if ins_result == InstructionResult::FatalExternalError {
panic!("Fatal external error in insert_call_outcome");
}
let item = if ins_result.is_ok() {
U256::from(1)
} else {
U256::ZERO
};
let _ = interpreter.stack.push(item);
if ins_result.is_ok_or_revert() {
interpreter.gas.erase_cost(out_gas.remaining());
interpreter
.memory
.set(mem_start, &interpreter.return_data.buffer()[..target_len]);
}
handle_reservoir_remaining_gas(&mut interpreter.gas, &out_gas, ins_result);
if ins_result.is_ok() {
interpreter.gas.record_refund(out_gas.refunded());
}
}
FrameResult::Create(outcome) => {
let instruction_result = *outcome.instruction_result();
let interpreter = &mut self.interpreter;
if instruction_result == InstructionResult::Revert {
interpreter
.return_data
.set_buffer(outcome.output().to_owned());
} else {
interpreter.return_data.clear();
};
assert_ne!(
instruction_result,
InstructionResult::FatalExternalError,
"Fatal external error in insert_eofcreate_outcome"
);
let this_gas = &mut interpreter.gas;
if instruction_result.is_ok_or_revert() {
this_gas.erase_cost(outcome.gas().remaining());
}
handle_reservoir_remaining_gas(this_gas, outcome.gas(), instruction_result);
let stack_item = if instruction_result.is_ok() {
this_gas.record_refund(outcome.gas().refunded());
outcome.address.unwrap_or_default().into_word().into()
} else {
U256::ZERO
};
let _ = interpreter.stack.push(stack_item);
}
}
Ok(())
}
}
#[inline]
pub fn handle_reservoir_remaining_gas(
parent_gas: &mut Gas,
child_gas: &Gas,
result: InstructionResult,
) {
if result.is_ok() {
parent_gas.set_reservoir(child_gas.reservoir());
parent_gas.set_state_gas_spent(parent_gas.state_gas_spent() + child_gas.state_gas_spent());
} else {
parent_gas.set_reservoir(child_gas.state_gas_spent() + child_gas.reservoir());
}
}
pub fn return_create<JOURNAL: JournalTr, CFG: Cfg>(
journal: &mut JOURNAL,
cfg: CFG,
checkpoint: JournalCheckpoint,
interpreter_result: &mut InterpreterResult,
address: Address,
) {
let max_code_size = cfg.max_code_size();
let is_eip3541_disabled = cfg.is_eip3541_disabled();
let spec_id = cfg.spec().into();
if !interpreter_result.result.is_ok() {
journal.checkpoint_revert(checkpoint);
return;
}
if spec_id.is_enabled_in(SPURIOUS_DRAGON) && interpreter_result.output.len() > max_code_size {
journal.checkpoint_revert(checkpoint);
interpreter_result.result = InstructionResult::CreateContractSizeLimit;
return;
}
if !is_eip3541_disabled
&& spec_id.is_enabled_in(LONDON)
&& interpreter_result.output.first() == Some(&0xEF)
{
journal.checkpoint_revert(checkpoint);
interpreter_result.result = InstructionResult::CreateContractStartingWithEF;
return;
}
let gas_for_code = cfg
.gas_params()
.code_deposit_cost(interpreter_result.output.len());
if !interpreter_result.gas.record_regular_cost(gas_for_code) {
if spec_id.is_enabled_in(HOMESTEAD) {
journal.checkpoint_revert(checkpoint);
interpreter_result.result = InstructionResult::OutOfGas;
return;
} else {
interpreter_result.output = Bytes::new();
}
}
if cfg.is_amsterdam_eip8037_enabled() {
let hash_cost = cfg
.gas_params()
.keccak256_cost(interpreter_result.output.len());
if !interpreter_result.gas.record_regular_cost(hash_cost) {
journal.checkpoint_revert(checkpoint);
interpreter_result.result = InstructionResult::OutOfGas;
return;
}
let state_gas_for_code = cfg
.gas_params()
.code_deposit_state_gas(interpreter_result.output.len());
if state_gas_for_code > 0 && !interpreter_result.gas.record_state_cost(state_gas_for_code) {
journal.checkpoint_revert(checkpoint);
interpreter_result.result = InstructionResult::OutOfGas;
return;
}
}
journal.checkpoint_commit();
let bytecode = Bytecode::new_legacy(interpreter_result.output.clone());
journal.set_code(address, bytecode);
interpreter_result.result = InstructionResult::Return;
}