use crate::{
builder::{EvmBuilder, HandlerStage, SetGenericStage},
db::{Database, DatabaseCommit, EmptyDB},
handler::Handler,
interpreter::{
CallInputs, CreateInputs, EOFCreateInputs, Host, InterpreterAction, SharedMemory,
},
primitives::{
specification::SpecId, BlockEnv, CfgEnv, EVMError, EVMResult, EnvWithHandlerCfg,
ExecutionResult, HandlerCfg, ResultAndState, TxEnv, TxKind, EOF_MAGIC_BYTES,
},
Context, ContextWithHandlerCfg, Frame, FrameOrResult, FrameResult,
};
use core::fmt;
use std::{boxed::Box, vec::Vec};
pub const CALL_STACK_LIMIT: u64 = 1024;
pub struct Evm<'a, EXT, DB: Database> {
pub context: Context<EXT, DB>,
pub handler: Handler<'a, Context<EXT, DB>, EXT, DB>,
}
impl<EXT, DB> fmt::Debug for Evm<'_, EXT, DB>
where
EXT: fmt::Debug,
DB: Database + fmt::Debug,
DB::Error: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Evm")
.field("evm context", &self.context.evm)
.finish_non_exhaustive()
}
}
impl<EXT, DB: Database + DatabaseCommit> Evm<'_, EXT, DB> {
pub fn transact_commit(&mut self) -> Result<ExecutionResult, EVMError<DB::Error>> {
let ResultAndState { result, state } = self.transact()?;
self.context.evm.db.commit(state);
Ok(result)
}
}
impl<'a> Evm<'a, (), EmptyDB> {
pub fn builder() -> EvmBuilder<'a, SetGenericStage, (), EmptyDB> {
EvmBuilder::default()
}
}
impl<'a, EXT, DB: Database> Evm<'a, EXT, DB> {
pub fn new(
mut context: Context<EXT, DB>,
handler: Handler<'a, Context<EXT, DB>, EXT, DB>,
) -> Evm<'a, EXT, DB> {
context.evm.journaled_state.set_spec_id(handler.cfg.spec_id);
Evm { context, handler }
}
pub fn modify(self) -> EvmBuilder<'a, HandlerStage, EXT, DB> {
EvmBuilder::new(self)
}
#[inline]
pub fn run_the_loop(&mut self, first_frame: Frame) -> Result<FrameResult, EVMError<DB::Error>> {
let mut call_stack: Vec<Frame> = Vec::with_capacity(1025);
call_stack.push(first_frame);
#[cfg(feature = "memory_limit")]
let mut shared_memory =
SharedMemory::new_with_memory_limit(self.context.evm.env.cfg.memory_limit);
#[cfg(not(feature = "memory_limit"))]
let mut shared_memory = SharedMemory::new();
shared_memory.new_context();
let mut stack_frame = call_stack.last_mut().unwrap();
loop {
let next_action =
self.handler
.execute_frame(stack_frame, &mut shared_memory, &mut self.context)?;
self.context.evm.take_error()?;
let exec = &mut self.handler.execution;
let frame_or_result = match next_action {
InterpreterAction::Call { inputs } => exec.call(&mut self.context, inputs)?,
InterpreterAction::Create { inputs } => exec.create(&mut self.context, inputs)?,
InterpreterAction::EOFCreate { inputs } => {
exec.eofcreate(&mut self.context, inputs)?
}
InterpreterAction::Return { result } => {
shared_memory.free_context();
let returned_frame = call_stack
.pop()
.expect("We just returned from Interpreter frame");
let ctx = &mut self.context;
FrameOrResult::Result(match returned_frame {
Frame::Call(frame) => {
FrameResult::Call(exec.call_return(ctx, frame, result)?)
}
Frame::Create(frame) => {
FrameResult::Create(exec.create_return(ctx, frame, result)?)
}
Frame::EOFCreate(frame) => {
FrameResult::EOFCreate(exec.eofcreate_return(ctx, frame, result)?)
}
})
}
InterpreterAction::None => unreachable!("InterpreterAction::None is not expected"),
};
match frame_or_result {
FrameOrResult::Frame(frame) => {
shared_memory.new_context();
call_stack.push(frame);
stack_frame = call_stack.last_mut().unwrap();
}
FrameOrResult::Result(result) => {
let Some(top_frame) = call_stack.last_mut() else {
return Ok(result);
};
stack_frame = top_frame;
let ctx = &mut self.context;
match result {
FrameResult::Call(outcome) => {
exec.insert_call_outcome(ctx, stack_frame, &mut shared_memory, outcome)?
}
FrameResult::Create(outcome) => {
exec.insert_create_outcome(ctx, stack_frame, outcome)?
}
FrameResult::EOFCreate(outcome) => {
exec.insert_eofcreate_outcome(ctx, stack_frame, outcome)?
}
}
}
}
}
}
}
impl<EXT, DB: Database> Evm<'_, EXT, DB> {
pub fn spec_id(&self) -> SpecId {
self.handler.cfg.spec_id
}
#[inline]
pub fn preverify_transaction(&mut self) -> Result<(), EVMError<DB::Error>> {
let output = self.preverify_transaction_inner().map(|_| ());
self.clear();
output
}
fn clear(&mut self) {
self.handler.post_execution().clear(&mut self.context);
}
#[inline]
pub fn transact_preverified(&mut self) -> EVMResult<DB::Error> {
let initial_gas_spend = self
.handler
.validation()
.initial_tx_gas(&self.context.evm.env)
.inspect_err(|_e| self.clear())?;
let output = self.transact_preverified_inner(initial_gas_spend);
let output = self.handler.post_execution().end(&mut self.context, output);
self.clear();
output
}
#[inline]
fn preverify_transaction_inner(&mut self) -> Result<u64, EVMError<DB::Error>> {
self.handler.validation().env(&self.context.evm.env)?;
let initial_gas_spend = self
.handler
.validation()
.initial_tx_gas(&self.context.evm.env)?;
self.handler
.validation()
.tx_against_state(&mut self.context)?;
Ok(initial_gas_spend)
}
#[inline]
pub fn transact(&mut self) -> EVMResult<DB::Error> {
let initial_gas_spend = self
.preverify_transaction_inner()
.inspect_err(|_e| self.clear())?;
let output = self.transact_preverified_inner(initial_gas_spend);
let output = self.handler.post_execution().end(&mut self.context, output);
self.clear();
output
}
#[inline]
pub fn handler_cfg(&self) -> &HandlerCfg {
&self.handler.cfg
}
#[inline]
pub fn cfg(&self) -> &CfgEnv {
&self.context.env().cfg
}
#[inline]
pub fn cfg_mut(&mut self) -> &mut CfgEnv {
&mut self.context.evm.env.cfg
}
#[inline]
pub fn tx(&self) -> &TxEnv {
&self.context.evm.env.tx
}
#[inline]
pub fn tx_mut(&mut self) -> &mut TxEnv {
&mut self.context.evm.env.tx
}
#[inline]
pub fn db(&self) -> &DB {
&self.context.evm.db
}
#[inline]
pub fn db_mut(&mut self) -> &mut DB {
&mut self.context.evm.db
}
#[inline]
pub fn block(&self) -> &BlockEnv {
&self.context.evm.env.block
}
#[inline]
pub fn block_mut(&mut self) -> &mut BlockEnv {
&mut self.context.evm.env.block
}
pub fn modify_spec_id(&mut self, spec_id: SpecId) {
self.handler.modify_spec_id(spec_id);
}
#[inline]
pub fn into_context(self) -> Context<EXT, DB> {
self.context
}
#[inline]
pub fn into_db_and_env_with_handler_cfg(self) -> (DB, EnvWithHandlerCfg) {
(
self.context.evm.inner.db,
EnvWithHandlerCfg {
env: self.context.evm.inner.env,
handler_cfg: self.handler.cfg,
},
)
}
#[inline]
pub fn into_context_with_handler_cfg(self) -> ContextWithHandlerCfg<EXT, DB> {
ContextWithHandlerCfg::new(self.context, self.handler.cfg)
}
fn transact_preverified_inner(&mut self, initial_gas_spend: u64) -> EVMResult<DB::Error> {
let spec_id = self.spec_id();
let ctx = &mut self.context;
let pre_exec = self.handler.pre_execution();
pre_exec.load_accounts(ctx)?;
let precompiles = pre_exec.load_precompiles();
ctx.evm.set_precompiles(precompiles);
pre_exec.deduct_caller(ctx)?;
let gas_limit = ctx.evm.env.tx.gas_limit - initial_gas_spend;
let eip7702_gas_refund = pre_exec.apply_eip7702_auth_list(ctx)? as i64;
let exec = self.handler.execution();
let first_frame_or_result = match ctx.evm.env.tx.transact_to {
TxKind::Call(_) => exec.call(
ctx,
CallInputs::new_boxed(&ctx.evm.env.tx, gas_limit).unwrap(),
)?,
TxKind::Create => {
if spec_id.is_enabled_in(SpecId::OSAKA)
&& ctx.env().tx.data.starts_with(&EOF_MAGIC_BYTES)
{
exec.eofcreate(
ctx,
Box::new(EOFCreateInputs::new_tx(&ctx.evm.env.tx, gas_limit)),
)?
} else {
exec.create(
ctx,
CreateInputs::new_boxed(&ctx.evm.env.tx, gas_limit).unwrap(),
)?
}
}
};
let mut result = match first_frame_or_result {
FrameOrResult::Frame(first_frame) => self.run_the_loop(first_frame)?,
FrameOrResult::Result(result) => result,
};
let ctx = &mut self.context;
self.handler
.execution()
.last_frame_return(ctx, &mut result)?;
let post_exec = self.handler.post_execution();
post_exec.refund(ctx, result.gas_mut(), eip7702_gas_refund);
post_exec.reimburse_caller(ctx, result.gas())?;
post_exec.reward_beneficiary(ctx, result.gas())?;
post_exec.output(ctx, result)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
db::BenchmarkDB,
interpreter::opcode::{PUSH1, SSTORE},
primitives::{
address, Authorization, Bytecode, RecoveredAuthority, RecoveredAuthorization, U256,
},
};
#[test]
fn sanity_eip7702_tx() {
let caller = address!("0000000000000000000000000000000000000001");
let delegate = address!("0000000000000000000000000000000000000002");
let auth = address!("0000000000000000000000000000000000000100");
let bytecode = Bytecode::new_legacy([PUSH1, 0x01, PUSH1, 0x01, SSTORE].into());
let mut evm = Evm::builder()
.with_spec_id(SpecId::PRAGUE)
.with_db(BenchmarkDB::new_bytecode(bytecode).with_target(delegate))
.modify_tx_env(|tx| {
tx.authorization_list = Some(
vec![RecoveredAuthorization::new_unchecked(
Authorization {
chain_id: 1,
address: delegate,
nonce: 0,
},
RecoveredAuthority::Valid(auth),
)]
.into(),
);
tx.caller = caller;
tx.transact_to = TxKind::Call(auth);
})
.build();
let ok = evm.transact().unwrap();
let auth_acc = ok.state.get(&auth).unwrap();
assert_eq!(auth_acc.info.code, Some(Bytecode::new_eip7702(delegate)));
assert_eq!(auth_acc.info.nonce, 1);
assert_eq!(
auth_acc.storage.get(&U256::from(1)).unwrap().present_value,
U256::from(1)
);
}
}