use crate::backend::Backend;
use crate::core::utils::{U256_ZERO, U64_MAX};
use crate::core::{ExitFatal, InterpreterHandler, Machine};
use crate::executor::stack::precompile::{
PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileSet,
};
use crate::executor::stack::tagged_runtime::{RuntimeKind, TaggedRuntime};
use crate::gasometer::{self, Gasometer, StorageTarget};
use crate::maybe_borrowed::MaybeBorrowed;
use crate::prelude::*;
use crate::runtime::Resolve;
use crate::{
Capture, Config, Context, CreateScheme, ExitError, ExitReason, Handler, Opcode, Runtime,
Transfer,
};
use core::{cmp::min, convert::Infallible};
use primitive_types::{H160, H256, U256};
use sha3::{Digest, Keccak256};
use smallvec::{smallvec, SmallVec};
macro_rules! emit_exit {
($reason:expr) => {{
let reason = $reason;
event!(Exit {
reason: &reason,
return_value: &Vec::new(),
});
reason
}};
($reason:expr, $return_value:expr) => {{
let reason = $reason;
let return_value = $return_value;
event!(Exit {
reason: &reason,
return_value: &return_value,
});
(reason, return_value)
}};
}
macro_rules! try_or_fail {
( $e:expr ) => {
match $e {
Ok(v) => v,
Err(e) => return Capture::Exit((e.into(), Vec::new())),
}
};
}
const DEFAULT_CALL_STACK_CAPACITY: usize = 4;
const fn l64(gas: u64) -> u64 {
gas - gas / 64
}
pub enum StackExitKind {
Succeeded,
Reverted,
Failed,
}
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct Authorization {
pub authority: H160,
pub address: H160,
pub nonce: u64,
pub is_valid: bool,
}
impl Authorization {
#[must_use]
pub const fn new(authority: H160, address: H160, nonce: u64, is_valid: bool) -> Self {
Self {
authority,
address,
nonce,
is_valid,
}
}
#[must_use]
pub fn is_delegated(code: &[u8]) -> bool {
code.len() == 23 && code.starts_with(&[0xEF, 0x01, 0x00])
}
#[must_use]
pub fn get_delegated_address(code: &[u8]) -> Option<H160> {
if Self::is_delegated(code) {
Some(H160::from_slice(&code[3..]))
} else {
None
}
}
#[must_use]
pub fn delegation_code(&self) -> Vec<u8> {
let mut code = Vec::with_capacity(23);
code.extend(&[0xEF, 0x01, 0x00]);
code.extend(self.address.as_bytes());
code
}
}
#[derive(Default, Clone, Debug)]
pub struct Accessed {
pub accessed_addresses: BTreeSet<H160>,
pub accessed_storage: BTreeSet<(H160, H256)>,
pub authority: BTreeMap<H160, H160>,
}
impl Accessed {
pub fn access_address(&mut self, address: H160) {
self.accessed_addresses.insert(address);
}
pub fn access_addresses<I>(&mut self, addresses: I)
where
I: Iterator<Item = H160>,
{
self.accessed_addresses.extend(addresses);
}
pub fn access_storages<I>(&mut self, storages: I)
where
I: Iterator<Item = (H160, H256)>,
{
for storage in storages {
self.accessed_storage.insert((storage.0, storage.1));
}
}
pub fn add_authority(&mut self, authority: H160, address: H160) {
self.authority.insert(authority, address);
}
pub fn remove_authority(&mut self, authority: H160) {
self.authority.remove(&authority);
}
#[must_use]
pub fn get_authority_target(&self, authority: H160) -> Option<H160> {
self.authority.get(&authority).copied()
}
#[must_use]
pub fn is_authority(&self, authority: H160) -> bool {
self.authority.contains_key(&authority)
}
}
#[derive(Clone, Debug)]
pub struct StackSubstateMetadata<'config> {
gasometer: Gasometer<'config>,
is_static: bool,
depth: Option<usize>,
accessed: Option<Accessed>,
}
impl<'config> StackSubstateMetadata<'config> {
#[must_use]
pub fn new(gas_limit: u64, config: &'config Config) -> Self {
let accessed = if config.increase_state_access_gas {
Some(Accessed::default())
} else {
None
};
Self {
gasometer: Gasometer::new(gas_limit, config),
is_static: false,
depth: None,
accessed,
}
}
pub fn swallow_commit(&mut self, other: Self) -> Result<(), ExitError> {
self.gasometer.record_stipend(other.gasometer.gas())?;
self.gasometer
.record_refund(other.gasometer.refunded_gas())?;
if let (Some(mut other_accessed), Some(self_accessed)) =
(other.accessed, self.accessed.as_mut())
{
self_accessed
.accessed_addresses
.append(&mut other_accessed.accessed_addresses);
self_accessed
.accessed_storage
.append(&mut other_accessed.accessed_storage);
self_accessed
.authority
.append(&mut other_accessed.authority);
}
Ok(())
}
pub fn swallow_revert(&mut self, other: &Self) -> Result<(), ExitError> {
self.gasometer.record_stipend(other.gasometer.gas())
}
pub const fn swallow_discard(&self, _other: &Self) {}
#[must_use]
pub fn spit_child(&self, gas_limit: u64, is_static: bool) -> Self {
Self {
gasometer: Gasometer::new(gas_limit, self.gasometer.config()),
is_static: is_static || self.is_static,
depth: self.depth.map_or(Some(0), |n| Some(n + 1)),
accessed: self.accessed.as_ref().map(|_| Accessed::default()),
}
}
#[must_use]
pub const fn gasometer(&self) -> &Gasometer<'config> {
&self.gasometer
}
pub const fn gasometer_mut(&mut self) -> &mut Gasometer<'config> {
&mut self.gasometer
}
#[must_use]
pub const fn is_static(&self) -> bool {
self.is_static
}
#[must_use]
pub const fn depth(&self) -> Option<usize> {
self.depth
}
pub fn access_address(&mut self, address: H160) {
if let Some(accessed) = &mut self.accessed {
accessed.access_address(address);
}
}
pub fn access_addresses<I>(&mut self, addresses: I)
where
I: Iterator<Item = H160>,
{
if let Some(accessed) = &mut self.accessed {
accessed.access_addresses(addresses);
}
}
pub fn access_storage(&mut self, address: H160, key: H256) {
if let Some(accessed) = &mut self.accessed {
accessed.accessed_storage.insert((address, key));
}
}
pub fn access_storages<I>(&mut self, storages: I)
where
I: Iterator<Item = (H160, H256)>,
{
if let Some(accessed) = &mut self.accessed {
accessed.access_storages(storages);
}
}
#[must_use]
pub const fn accessed(&self) -> &Option<Accessed> {
&self.accessed
}
pub fn add_authority(&mut self, authority: H160, address: H160) {
if let Some(accessed) = &mut self.accessed {
accessed.add_authority(authority, address);
}
}
pub fn remove_authority(&mut self, authority: H160) {
if let Some(accessed) = &mut self.accessed {
accessed.remove_authority(authority);
}
}
}
#[auto_impl::auto_impl(& mut, Box)]
pub trait StackState<'config>: Backend {
fn metadata(&self) -> &StackSubstateMetadata<'config>;
fn metadata_mut(&mut self) -> &mut StackSubstateMetadata<'config>;
fn enter(&mut self, gas_limit: u64, is_static: bool);
fn exit_commit(&mut self) -> Result<(), ExitError>;
fn exit_revert(&mut self) -> Result<(), ExitError>;
fn exit_discard(&mut self) -> Result<(), ExitError>;
fn is_empty(&self, address: H160) -> bool;
fn deleted(&self, address: H160) -> bool;
fn is_created(&self, address: H160) -> bool;
fn is_cold(&self, address: H160) -> bool;
fn is_storage_cold(&self, address: H160, key: H256) -> bool;
fn inc_nonce(&mut self, address: H160) -> Result<(), ExitError>;
fn set_storage(&mut self, address: H160, key: H256, value: H256);
fn reset_storage(&mut self, address: H160);
fn log(&mut self, address: H160, topics: Vec<H256>, data: Vec<u8>);
fn set_deleted(&mut self, address: H160);
fn set_created(&mut self, address: H160);
fn set_code(&mut self, address: H160, code: Vec<u8>);
fn transfer(&mut self, transfer: Transfer) -> Result<(), ExitError>;
fn reset_balance(&mut self, address: H160);
fn touch(&mut self, address: H160);
fn record_external_operation(
&mut self,
#[allow(clippy::used_underscore_binding)] _op: crate::ExternalOperation,
) -> Result<(), ExitError> {
Ok(())
}
fn record_external_dynamic_opcode_cost(
&mut self,
#[allow(clippy::used_underscore_binding)] _opcode: Opcode,
#[allow(clippy::used_underscore_binding)] _gas_cost: gasometer::GasCost,
#[allow(clippy::used_underscore_binding)] _target: StorageTarget,
) -> Result<(), ExitError> {
Ok(())
}
fn record_external_cost(
&mut self,
#[allow(clippy::used_underscore_binding)] _ref_time: Option<u64>,
#[allow(clippy::used_underscore_binding)] _proof_size: Option<u64>,
#[allow(clippy::used_underscore_binding)] _storage_growth: Option<u64>,
) -> Result<(), ExitError> {
Ok(())
}
fn refund_external_cost(
&mut self,
#[allow(clippy::used_underscore_binding)] _ref_time: Option<u64>,
#[allow(clippy::used_underscore_binding)] _proof_size: Option<u64>,
) {
}
fn tstore(&mut self, address: H160, index: H256, value: U256) -> Result<(), ExitError>;
fn tload(&mut self, address: H160, index: H256) -> Result<U256, ExitError>;
fn is_authority_cold(&mut self, address: H160) -> Option<bool>;
fn get_authority_target(&mut self, address: H160) -> Option<H160>;
}
pub struct StackExecutor<'config, 'precompiles, S, P> {
config: &'config Config,
state: S,
precompile_set: &'precompiles P,
}
impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet>
StackExecutor<'config, 'precompiles, S, P>
{
pub const fn config(&self) -> &'config Config {
self.config
}
pub const fn precompiles(&self) -> &'precompiles P {
self.precompile_set
}
pub const fn new_with_precompiles(
state: S,
config: &'config Config,
precompile_set: &'precompiles P,
) -> Self {
Self {
config,
state,
precompile_set,
}
}
pub const fn state(&self) -> &S {
&self.state
}
pub const fn state_mut(&mut self) -> &mut S {
&mut self.state
}
#[allow(clippy::missing_const_for_fn)]
pub fn into_state(self) -> S {
self.state
}
pub fn enter_substate(&mut self, gas_limit: u64, is_static: bool) {
self.state.enter(gas_limit, is_static);
}
pub fn exit_substate(&mut self, kind: &StackExitKind) -> Result<(), ExitError> {
match kind {
StackExitKind::Succeeded => self.state.exit_commit(),
StackExitKind::Reverted => self.state.exit_revert(),
StackExitKind::Failed => self.state.exit_discard(),
}
}
pub fn execute(&mut self, runtime: &mut Runtime) -> ExitReason {
let mut call_stack: SmallVec<[TaggedRuntime; DEFAULT_CALL_STACK_CAPACITY]> =
smallvec!(TaggedRuntime {
kind: RuntimeKind::Execute,
inner: MaybeBorrowed::Borrowed(runtime),
});
let (reason, _, _) = self.execute_with_call_stack(&mut call_stack);
reason
}
fn execute_with_call_stack(
&mut self,
call_stack: &mut SmallVec<[TaggedRuntime<'_>; DEFAULT_CALL_STACK_CAPACITY]>,
) -> (ExitReason, Option<H160>, Vec<u8>) {
let mut interrupt_runtime = None;
loop {
if let Some(rt) = interrupt_runtime.take() {
call_stack.push(rt);
}
let Some(runtime) = call_stack.last_mut() else {
return (
ExitReason::Fatal(ExitFatal::UnhandledInterrupt),
None,
Vec::new(),
);
};
let reason = {
let inner_runtime = &mut runtime.inner;
match inner_runtime.run(self) {
Capture::Exit(reason) => reason,
Capture::Trap(Resolve::Call(rt, _)) => {
interrupt_runtime = Some(rt.0);
continue;
}
Capture::Trap(Resolve::Create(rt, _)) => {
interrupt_runtime = Some(rt.0);
continue;
}
}
};
let runtime_kind = runtime.kind;
let (reason, maybe_address, return_data) = match runtime_kind {
RuntimeKind::Create(created_address) => {
let (reason, maybe_address, return_data) = self.exit_substate_for_create(
created_address,
reason,
runtime.inner.machine().return_value(),
);
(reason, maybe_address, return_data)
}
RuntimeKind::Call(code_address) => {
let return_data = self.exit_substate_for_call(
code_address,
&reason,
runtime.inner.machine().return_value(),
);
(reason, None, return_data)
}
RuntimeKind::Execute => (reason, None, runtime.inner.machine().return_value()),
};
call_stack.pop();
let Some(runtime) = call_stack.last_mut() else {
return (reason, None, return_data);
};
emit_exit!(&reason, &return_data);
let inner_runtime = &mut runtime.inner;
let maybe_error = match runtime_kind {
RuntimeKind::Create(_) => {
inner_runtime.finish_create(reason, maybe_address, return_data)
}
RuntimeKind::Call(_) | RuntimeKind::Execute => {
inner_runtime.finish_call(reason, return_data)
}
};
if let Err(e) = maybe_error {
return (e, None, Vec::new());
}
}
}
pub fn gas(&self) -> u64 {
self.state.metadata().gasometer.gas()
}
fn record_create_transaction_cost(
&mut self,
init_code: &[u8],
access_list: &[(H160, Vec<H256>)],
) -> Result<(), ExitError> {
let transaction_cost = gasometer::create_transaction_cost(init_code, access_list);
let gasometer = &mut self.state.metadata_mut().gasometer;
gasometer.record_transaction(transaction_cost)
}
fn maybe_record_init_code_cost(&mut self, init_code: &[u8]) -> Result<(), ExitError> {
if let Some(limit) = self.config.max_initcode_size {
if init_code.len() > limit {
self.state.metadata_mut().gasometer.fail();
return Err(ExitError::CreateContractLimit);
}
return self
.state
.metadata_mut()
.gasometer
.record_cost(gasometer::init_code_cost(init_code));
}
Ok(())
}
pub fn transact_create(
&mut self,
caller: H160,
value: U256,
init_code: Vec<u8>,
gas_limit: u64,
access_list: Vec<(H160, Vec<H256>)>, ) -> (ExitReason, Vec<u8>) {
if self.nonce(caller) >= U64_MAX {
return (ExitError::MaxNonce.into(), Vec::new());
}
let address = self.create_address(CreateScheme::Legacy { caller });
event!(TransactCreate {
caller,
value,
init_code: &init_code,
gas_limit,
address,
});
if let Some(limit) = self.config.max_initcode_size {
if init_code.len() > limit {
self.state.metadata_mut().gasometer.fail();
return emit_exit!(ExitError::CreateContractLimit.into(), Vec::new());
}
}
if let Err(e) = self.record_create_transaction_cost(&init_code, &access_list) {
return emit_exit!(e.into(), Vec::new());
}
self.warm_addresses_and_storage(caller, address, access_list);
match self.create_inner(
caller,
CreateScheme::Legacy { caller },
value,
init_code,
Some(gas_limit),
false,
) {
Capture::Exit((s, v)) => emit_exit!(s, v),
Capture::Trap(rt) => {
let mut cs: SmallVec<[TaggedRuntime<'_>; DEFAULT_CALL_STACK_CAPACITY]> =
smallvec!(rt.0);
let (s, _, v) = self.execute_with_call_stack(&mut cs);
emit_exit!(s, v)
}
}
}
#[cfg(feature = "create-fixed")]
pub fn transact_create_fixed(
&mut self,
caller: H160,
address: H160,
value: U256,
init_code: Vec<u8>,
gas_limit: u64,
access_list: Vec<(H160, Vec<H256>)>, ) -> (ExitReason, Vec<u8>) {
let address = self.create_address(CreateScheme::Fixed(address));
event!(TransactCreate {
caller,
value,
init_code: &init_code,
gas_limit,
address
});
if let Err(e) = self.record_create_transaction_cost(&init_code, &access_list) {
return emit_exit!(e.into(), Vec::new());
}
self.warm_addresses_and_storage(caller, address, access_list);
match self.create_inner(
caller,
CreateScheme::Fixed(address),
value,
init_code,
Some(gas_limit),
false,
) {
Capture::Exit((s, v)) => emit_exit!(s, v),
Capture::Trap(rt) => {
let mut cs: SmallVec<[TaggedRuntime<'_>; DEFAULT_CALL_STACK_CAPACITY]> =
smallvec!(rt.0);
let (s, _, v) = self.execute_with_call_stack(&mut cs);
emit_exit!(s, v)
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn transact_create2(
&mut self,
caller: H160,
value: U256,
init_code: Vec<u8>,
salt: H256,
gas_limit: u64,
access_list: Vec<(H160, Vec<H256>)>, ) -> (ExitReason, Vec<u8>) {
if let Some(limit) = self.config.max_initcode_size {
if init_code.len() > limit {
self.state.metadata_mut().gasometer.fail();
return emit_exit!(ExitError::CreateContractLimit.into(), Vec::new());
}
}
let code_hash =
H256::from_slice(<[u8; 32]>::from(Keccak256::digest(&init_code)).as_slice());
let address = self.create_address(CreateScheme::Create2 {
caller,
code_hash,
salt,
});
event!(TransactCreate2 {
caller,
value,
init_code: &init_code,
salt,
gas_limit,
address,
});
if let Err(e) = self.record_create_transaction_cost(&init_code, &access_list) {
return emit_exit!(e.into(), Vec::new());
}
self.warm_addresses_and_storage(caller, address, access_list);
match self.create_inner(
caller,
CreateScheme::Create2 {
caller,
code_hash,
salt,
},
value,
init_code,
Some(gas_limit),
false,
) {
Capture::Exit((s, v)) => emit_exit!(s, v),
Capture::Trap(rt) => {
let mut cs: SmallVec<[TaggedRuntime<'_>; DEFAULT_CALL_STACK_CAPACITY]> =
smallvec!(rt.0);
let (s, _, v) = self.execute_with_call_stack(&mut cs);
emit_exit!(s, v)
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn transact_call(
&mut self,
caller: H160,
address: H160,
value: U256,
data: Vec<u8>,
gas_limit: u64,
access_list: Vec<(H160, Vec<H256>)>,
authorization_list: Vec<Authorization>,
) -> (ExitReason, Vec<u8>) {
event!(TransactCall {
caller,
address,
value,
data: &data,
gas_limit,
});
if self.nonce(caller) >= U64_MAX {
return (ExitError::MaxNonce.into(), Vec::new());
}
let transaction_cost =
gasometer::call_transaction_cost(&data, &access_list, authorization_list.len());
let gasometer = &mut self.state.metadata_mut().gasometer;
match gasometer.record_transaction(transaction_cost) {
Ok(()) => (),
Err(e) => return emit_exit!(e.into(), Vec::new()),
}
if let Err(e) = self.state.inc_nonce(caller) {
return (e.into(), Vec::new());
}
self.warm_addresses_and_storage(caller, address, access_list);
if let Err(e) = self.authorized_accounts(authorization_list) {
return (e.into(), Vec::new());
}
let context = Context {
caller,
address,
apparent_value: value,
};
match self.call_inner(
address,
Some(Transfer {
source: caller,
target: address,
value,
}),
data,
Some(gas_limit),
false,
false,
false,
context,
) {
Capture::Exit((s, v)) => emit_exit!(s, v),
Capture::Trap(rt) => {
let mut cs: SmallVec<[TaggedRuntime<'_>; DEFAULT_CALL_STACK_CAPACITY]> =
smallvec!(rt.0);
let (s, _, v) = self.execute_with_call_stack(&mut cs);
emit_exit!(s, v)
}
}
}
pub fn used_gas(&self) -> u64 {
let refunded_gas =
u64::try_from(self.state.metadata().gasometer.refunded_gas()).unwrap_or_default();
let total_used_gas = self.state.metadata().gasometer.total_used_gas();
let total_used_gas_refunded = self.state.metadata().gasometer.total_used_gas()
- min(
total_used_gas / self.config.max_refund_quotient,
refunded_gas,
);
if self.config.has_floor_gas
&& total_used_gas_refunded < self.state.metadata().gasometer.floor_gas()
{
self.state.metadata().gasometer.floor_gas()
} else {
total_used_gas_refunded
}
}
pub fn fee(&self, price: U256) -> U256 {
let used_gas = self.used_gas();
U256::from(used_gas).saturating_mul(price)
}
pub fn nonce(&self, address: H160) -> U256 {
self.state.basic(address).nonce
}
pub fn is_create_collision(&self, address: H160) -> bool {
!self.code(address).is_empty()
|| self.nonce(address) > U256_ZERO
|| !self.state.is_empty_storage(address)
}
pub fn create_address(&self, scheme: CreateScheme) -> H160 {
match scheme {
CreateScheme::Create2 {
caller,
code_hash,
salt,
} => {
let mut hasher = Keccak256::new();
hasher.update([0xff]);
hasher.update(&caller[..]);
hasher.update(&salt[..]);
hasher.update(&code_hash[..]);
H256::from_slice(<[u8; 32]>::from(hasher.finalize()).as_slice()).into()
}
CreateScheme::Legacy { caller } => {
let nonce = self.nonce(caller);
let mut stream = rlp::RlpStream::new_list(2);
stream.append(&caller);
stream.append(&nonce);
H256::from_slice(<[u8; 32]>::from(Keccak256::digest(stream.out())).as_slice())
.into()
}
CreateScheme::Fixed(address) => address,
}
}
pub fn warm_access_list(&mut self, access_list: Vec<(H160, Vec<H256>)>) {
let addresses = access_list.iter().map(|a| a.0);
self.state.metadata_mut().access_addresses(addresses);
let storage_keys = access_list
.into_iter()
.flat_map(|(address, keys)| keys.into_iter().map(move |key| (address, key)));
self.state.metadata_mut().access_storages(storage_keys);
}
fn warm_addresses_and_storage(
&mut self,
caller: H160,
address: H160,
access_list: Vec<(H160, Vec<H256>)>,
) {
if self.config.increase_state_access_gas {
if self.config.warm_coinbase_address {
let coinbase = self.block_coinbase();
self.state
.metadata_mut()
.access_addresses([caller, address, coinbase].iter().copied());
} else {
self.state
.metadata_mut()
.access_addresses([caller, address].iter().copied());
}
self.warm_access_list(access_list);
}
}
fn authorized_accounts(
&mut self,
authorization_list: Vec<Authorization>,
) -> Result<(), ExitError> {
if !self.config.has_authorization_list {
return Ok(());
}
let mut refunded_accounts = 0;
let state = self.state_mut();
let mut warm_authority: Vec<H160> = Vec::with_capacity(authorization_list.len());
for authority in authorization_list {
if !authority.is_valid {
continue;
}
if U256::from(authority.nonce) >= U64_MAX {
continue;
}
warm_authority.push(authority.authority);
let authority_code = state.code(authority.authority);
if !authority_code.is_empty() && !Authorization::is_delegated(&authority_code) {
continue;
}
if state.basic(authority.authority).nonce != U256::from(authority.nonce) {
continue;
}
if !state.is_empty(authority.authority) {
refunded_accounts += 1;
}
let delegation_clearing = if authority.address.is_zero() {
state.set_code(authority.authority, Vec::new());
true
} else {
state.set_code(authority.authority, authority.delegation_code());
false
};
state.inc_nonce(authority.authority)?;
if delegation_clearing {
state.metadata_mut().remove_authority(authority.authority);
} else {
state
.metadata_mut()
.add_authority(authority.authority, authority.address);
}
}
self.state
.metadata_mut()
.access_addresses(warm_authority.into_iter());
self.state
.metadata_mut()
.gasometer
.record_authority_refund(refunded_accounts)
}
fn calc_gas_limit_and_record(
&mut self,
target_gas: Option<u64>,
take_l64: bool,
) -> Result<u64, ExitError> {
let initial_after_gas = self.state.metadata().gasometer.gas();
let after_gas = if take_l64 && self.config.call_l64_after_gas {
if self.config.estimate {
let diff = initial_after_gas - l64(initial_after_gas);
self.state.metadata_mut().gasometer.record_cost(diff)?;
initial_after_gas
} else {
l64(initial_after_gas)
}
} else {
initial_after_gas
};
let target_gas = target_gas.unwrap_or(after_gas);
let gas_limit = min(target_gas, after_gas);
self.state.metadata_mut().gasometer.record_cost(gas_limit)?;
Ok(gas_limit)
}
fn create_inner(
&mut self,
caller: H160,
scheme: CreateScheme,
value: U256,
init_code: Vec<u8>,
target_gas: Option<u64>,
take_l64: bool,
) -> Capture<(ExitReason, Vec<u8>), StackExecutorCreateInterrupt<'static>> {
if self.nonce(caller) >= U64_MAX {
return Capture::Exit((ExitError::MaxNonce.into(), Vec::new()));
}
let address = self.create_address(scheme);
self.state
.metadata_mut()
.access_addresses([caller, address].iter().copied());
event!(Create {
caller,
address,
scheme,
value,
init_code: &init_code,
target_gas
});
if let Some(depth) = self.state.metadata().depth {
if depth + 1 > self.config.call_stack_limit {
return Capture::Exit((ExitError::CallTooDeep.into(), Vec::new()));
}
}
if self.balance(caller) < value {
return Capture::Exit((ExitError::OutOfFund.into(), Vec::new()));
}
let gas_limit = try_or_fail!(self.calc_gas_limit_and_record(target_gas, take_l64));
try_or_fail!(self.state.inc_nonce(caller));
if self.is_create_collision(address) {
return Capture::Exit((ExitError::CreateCollision.into(), Vec::new()));
}
self.enter_substate(gas_limit, false);
if self.config.create_increase_nonce {
try_or_fail!(self.state.inc_nonce(address));
}
let transfer = Transfer {
source: caller,
target: address,
value,
};
match self.state.transfer(transfer) {
Ok(()) => (),
Err(e) => {
let _ = self.exit_substate(&StackExitKind::Reverted);
return Capture::Exit((ExitReason::Error(e), Vec::new()));
}
}
self.state.set_created(address);
let context = Context {
address,
caller,
apparent_value: value,
};
let runtime = Runtime::new(
Rc::new(init_code),
Rc::new(Vec::new()),
context,
self.config.stack_limit,
self.config.memory_limit,
);
Capture::Trap(StackExecutorCreateInterrupt(TaggedRuntime {
kind: RuntimeKind::Create(address),
inner: MaybeBorrowed::Owned(runtime),
}))
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
fn call_inner(
&mut self,
code_address: H160,
transfer: Option<Transfer>,
input: Vec<u8>,
target_gas: Option<u64>,
is_static: bool,
take_l64: bool,
take_stipend: bool,
context: Context,
) -> Capture<(ExitReason, Vec<u8>), StackExecutorCallInterrupt<'static>> {
event!(Call {
code_address,
transfer: &transfer,
input: &input,
target_gas,
is_static,
context: &context,
});
let mut gas_limit = try_or_fail!(self.calc_gas_limit_and_record(target_gas, take_l64));
if let Some(transfer) = transfer.as_ref() {
if take_stipend && transfer.value != U256_ZERO {
gas_limit = gas_limit.saturating_add(self.config.call_stipend);
}
}
let code = self.authority_code(code_address);
if let Some(target_address) = self.get_authority_target(code_address) {
self.warm_target((target_address, None));
}
self.enter_substate(gas_limit, is_static);
self.state.touch(context.address);
if let Some(depth) = self.state.metadata().depth {
if depth > self.config.call_stack_limit {
let _ = self.exit_substate(&StackExitKind::Reverted);
return Capture::Exit((ExitError::CallTooDeep.into(), Vec::new()));
}
}
if let Some(transfer) = transfer {
match self.state.transfer(transfer) {
Ok(()) => (),
Err(e) => {
let _ = self.exit_substate(&StackExitKind::Reverted);
return Capture::Exit((ExitReason::Error(e), Vec::new()));
}
}
}
let precompile_is_static = self.state.metadata().is_static();
if let Some(result) = self.precompile_set.execute(&mut StackExecutorHandle {
executor: self,
code_address,
input: &input,
gas_limit: Some(gas_limit),
context: &context,
is_static: precompile_is_static,
}) {
return match result {
Ok(PrecompileOutput {
exit_status,
output,
}) => {
let _ = self.exit_substate(&StackExitKind::Succeeded);
Capture::Exit((ExitReason::Succeed(exit_status), output))
}
Err(PrecompileFailure::Error { exit_status }) => {
let _ = self.exit_substate(&StackExitKind::Failed);
Capture::Exit((ExitReason::Error(exit_status), Vec::new()))
}
Err(PrecompileFailure::Revert {
exit_status,
output,
}) => {
let _ = self.exit_substate(&StackExitKind::Reverted);
Capture::Exit((ExitReason::Revert(exit_status), output))
}
Err(PrecompileFailure::Fatal { exit_status }) => {
self.state.metadata_mut().gasometer.fail();
let _ = self.exit_substate(&StackExitKind::Failed);
Capture::Exit((ExitReason::Fatal(exit_status), Vec::new()))
}
};
}
let runtime = Runtime::new(
Rc::new(code),
Rc::new(input),
context,
self.config.stack_limit,
self.config.memory_limit,
);
Capture::Trap(StackExecutorCallInterrupt(TaggedRuntime {
kind: RuntimeKind::Call(code_address),
inner: MaybeBorrowed::Owned(runtime),
}))
}
fn exit_substate_for_create(
&mut self,
created_address: H160,
reason: ExitReason,
return_data: Vec<u8>,
) -> (ExitReason, Option<H160>, Vec<u8>) {
fn check_first_byte_eof_magic(config: &Config, code: &[u8]) -> Result<(), ExitError> {
if config.disallow_executable_format && Some(&0xEF) == code.first() {
return Err(ExitError::CreateContractStartingWithEF);
}
Ok(())
}
log::debug!(target: "evm", "Create execution using address {created_address}: {reason:?}");
match reason {
ExitReason::Succeed(s) => {
let out = return_data;
let address = created_address;
if let Err(e) = check_first_byte_eof_magic(self.config, &out) {
self.state.metadata_mut().gasometer.fail();
let _ = self.exit_substate(&StackExitKind::Failed);
return (e.into(), None, Vec::new());
}
if let Some(limit) = self.config.create_contract_limit {
if out.len() > limit {
self.state.metadata_mut().gasometer.fail();
let _ = self.exit_substate(&StackExitKind::Failed);
return (ExitError::CreateContractLimit.into(), None, Vec::new());
}
}
match self
.state
.metadata_mut()
.gasometer
.record_deposit(out.len())
{
Ok(()) => {
let exit_result = self.exit_substate(&StackExitKind::Succeeded);
event!(CreateOutput {
address,
code: &out,
});
self.state.set_code(address, out);
if let Err(e) = exit_result {
return (e.into(), None, Vec::new());
}
(ExitReason::Succeed(s), Some(address), Vec::new())
}
Err(e) => {
let _ = self.exit_substate(&StackExitKind::Failed);
(ExitReason::Error(e), None, Vec::new())
}
}
}
ExitReason::Error(e) => {
self.state.metadata_mut().gasometer.fail();
let _ = self.exit_substate(&StackExitKind::Failed);
(ExitReason::Error(e), None, Vec::new())
}
ExitReason::Revert(e) => {
let _ = self.exit_substate(&StackExitKind::Reverted);
(ExitReason::Revert(e), None, return_data)
}
ExitReason::Fatal(e) => {
self.state.metadata_mut().gasometer.fail();
let _ = self.exit_substate(&StackExitKind::Failed);
(ExitReason::Fatal(e), None, Vec::new())
}
}
}
fn exit_substate_for_call(
&mut self,
code_address: H160,
reason: &ExitReason,
return_data: Vec<u8>,
) -> Vec<u8> {
log::debug!(target: "evm", "Call execution using address {code_address}: {reason:?}");
match reason {
ExitReason::Succeed(_) => {
let _ = self.exit_substate(&StackExitKind::Succeeded);
return_data
}
ExitReason::Error(_) => {
let _ = self.exit_substate(&StackExitKind::Failed);
Vec::new()
}
ExitReason::Revert(_) => {
let _ = self.exit_substate(&StackExitKind::Reverted);
return_data
}
ExitReason::Fatal(_) => {
self.state.metadata_mut().gasometer.fail();
let _ = self.exit_substate(&StackExitKind::Failed);
Vec::new()
}
}
}
fn is_created(&self, address: H160) -> bool {
self.state.is_created(address)
}
}
impl<'config, S: StackState<'config>, P: PrecompileSet> InterpreterHandler
for StackExecutor<'config, '_, S, P>
{
#[inline]
fn before_bytecode(
&mut self,
opcode: Opcode,
_pc: usize,
machine: &Machine,
address: &H160,
) -> Result<(), ExitError> {
#[cfg(feature = "tracing")]
{
use crate::runtime::tracing::Event::Step;
crate::runtime::tracing::with(|listener| {
#[allow(clippy::used_underscore_binding)]
listener.event(Step {
address: *address,
opcode,
position: &Ok(_pc),
stack: machine.stack(),
memory: machine.memory(),
});
});
}
#[cfg(feature = "print-debug")]
println!("### {opcode}");
if let Some(cost) = gasometer::static_opcode_cost(opcode) {
self.state
.metadata_mut()
.gasometer
.record_cost(u64::from(cost))?;
} else {
let is_static = self.state.metadata().is_static;
let (gas_cost, memory_cost) = gasometer::dynamic_opcode_cost(
*address,
opcode,
machine.stack(),
is_static,
self.config,
self,
)?;
self.state
.metadata_mut()
.gasometer
.record_dynamic_cost(gas_cost, memory_cost)?;
}
Ok(())
}
#[cfg(feature = "tracing")]
#[inline]
fn after_bytecode(
&mut self,
result: &Result<(), Capture<ExitReason, crate::core::Trap>>,
machine: &Machine,
) {
use crate::runtime::tracing::Event::StepResult;
crate::runtime::tracing::with(|listener| {
listener.event(StepResult {
result,
return_value: machine.return_value().as_slice(),
});
});
}
}
pub struct StackExecutorCallInterrupt<'borrow>(TaggedRuntime<'borrow>);
pub struct StackExecutorCreateInterrupt<'borrow>(TaggedRuntime<'borrow>);
impl<'config, S: StackState<'config>, P: PrecompileSet> Handler
for StackExecutor<'config, '_, S, P>
{
type CreateInterrupt = StackExecutorCreateInterrupt<'static>;
type CreateFeedback = Infallible;
type CallInterrupt = StackExecutorCallInterrupt<'static>;
type CallFeedback = Infallible;
fn balance(&self, address: H160) -> U256 {
self.state.basic(address).balance
}
fn code_size(&mut self, address: H160) -> U256 {
let target_code = self.code(address);
U256::from(target_code.len())
}
fn code_hash(&mut self, address: H160) -> H256 {
if !self.exists(address) {
return H256::default();
}
let code = self.code(address);
H256::from_slice(<[u8; 32]>::from(Keccak256::digest(code)).as_slice())
}
fn code(&self, address: H160) -> Vec<u8> {
self.state.code(address)
}
fn storage(&self, address: H160, index: H256) -> H256 {
self.state.storage(address, index)
}
fn is_empty_storage(&self, address: H160) -> bool {
self.state.is_empty(address)
}
fn original_storage(&self, address: H160, index: H256) -> H256 {
self.state
.original_storage(address, index)
.unwrap_or_default()
}
fn exists(&self, address: H160) -> bool {
if self.config.empty_considered_exists {
self.state.exists(address)
} else {
self.state.exists(address) && !self.state.is_empty(address)
}
}
fn is_cold(&mut self, address: H160, maybe_index: Option<H256>) -> bool {
match maybe_index {
None => !self.precompile_set.is_precompile(address) && self.state.is_cold(address),
Some(index) => self.state.is_storage_cold(address, index),
}
}
fn gas_left(&self) -> U256 {
U256::from(self.state.metadata().gasometer.gas())
}
fn gas_price(&self) -> U256 {
self.state.gas_price()
}
fn origin(&self) -> H160 {
self.state.origin()
}
fn block_hash(&self, number: U256) -> H256 {
self.state.block_hash(number)
}
fn block_number(&self) -> U256 {
self.state.block_number()
}
fn block_coinbase(&self) -> H160 {
self.state.block_coinbase()
}
fn block_timestamp(&self) -> U256 {
self.state.block_timestamp()
}
fn block_difficulty(&self) -> U256 {
self.state.block_difficulty()
}
fn block_randomness(&self) -> Option<H256> {
self.state.block_randomness()
}
fn block_gas_limit(&self) -> U256 {
self.state.block_gas_limit()
}
fn block_base_fee_per_gas(&self) -> U256 {
self.state.block_base_fee_per_gas()
}
fn chain_id(&self) -> U256 {
self.state.chain_id()
}
fn deleted(&self, address: H160) -> bool {
self.state.deleted(address)
}
fn set_storage(&mut self, address: H160, index: H256, value: H256) -> Result<(), ExitError> {
self.state.set_storage(address, index, value);
Ok(())
}
fn log(&mut self, address: H160, topics: Vec<H256>, data: Vec<u8>) -> Result<(), ExitError> {
self.state.log(address, topics, data);
Ok(())
}
fn mark_delete(&mut self, address: H160, target: H160) -> Result<(), ExitError> {
let is_created = self.is_created(address);
if self.config.has_restricted_selfdestruct && !is_created && address == target {
return Ok(());
}
let balance = self.balance(address);
event!(Suicide {
target,
address,
balance,
});
self.state.transfer(Transfer {
source: address,
target,
value: balance,
})?;
self.state.reset_balance(address);
if !self.config.has_restricted_selfdestruct || self.is_created(address) {
self.state.set_deleted(address);
}
Ok(())
}
#[cfg(not(feature = "tracing"))]
fn create(
&mut self,
caller: H160,
scheme: CreateScheme,
value: U256,
init_code: Vec<u8>,
target_gas: Option<u64>,
) -> Capture<(ExitReason, Vec<u8>), Self::CreateInterrupt> {
if let Err(e) = self.maybe_record_init_code_cost(&init_code) {
let reason: ExitReason = e.into();
emit_exit!(reason.clone());
return Capture::Exit((reason, Vec::new()));
}
self.create_inner(caller, scheme, value, init_code, target_gas, true)
}
#[cfg(feature = "tracing")]
fn create(
&mut self,
caller: H160,
scheme: CreateScheme,
value: U256,
init_code: Vec<u8>,
target_gas: Option<u64>,
) -> Capture<(ExitReason, Vec<u8>), Self::CreateInterrupt> {
if let Err(e) = self.maybe_record_init_code_cost(&init_code) {
let reason: ExitReason = e.into();
emit_exit!(reason.clone());
return Capture::Exit((reason, Vec::new()));
}
let capture = self.create_inner(caller, scheme, value, init_code, target_gas, true);
if let Capture::Exit((ref reason, ref return_value)) = capture {
emit_exit!(reason, return_value);
}
capture
}
#[cfg(not(feature = "tracing"))]
fn call(
&mut self,
code_address: H160,
transfer: Option<Transfer>,
input: Vec<u8>,
target_gas: Option<u64>,
is_static: bool,
context: Context,
) -> Capture<(ExitReason, Vec<u8>), Self::CallInterrupt> {
self.call_inner(
code_address,
transfer,
input,
target_gas,
is_static,
true,
true,
context,
)
}
#[cfg(feature = "tracing")]
fn call(
&mut self,
code_address: H160,
transfer: Option<Transfer>,
input: Vec<u8>,
target_gas: Option<u64>,
is_static: bool,
context: Context,
) -> Capture<(ExitReason, Vec<u8>), Self::CallInterrupt> {
let capture = self.call_inner(
code_address,
transfer,
input,
target_gas,
is_static,
true,
true,
context,
);
if let Capture::Exit((ref reason, ref return_value)) = capture {
emit_exit!(reason, return_value);
}
capture
}
fn record_external_operation(&mut self, op: crate::ExternalOperation) -> Result<(), ExitError> {
self.state.record_external_operation(op)
}
fn blob_base_fee(&self) -> Option<u128> {
if self.config.has_blob_base_fee {
self.state.blob_gas_price()
} else {
None
}
}
fn get_blob_hash(&self, index: usize) -> Option<U256> {
if self.config.has_shard_blob_transactions {
self.state.get_blob_hash(index)
} else {
None
}
}
fn tstore(&mut self, address: H160, index: H256, value: U256) -> Result<(), ExitError> {
if self.config.has_transient_storage {
self.state.tstore(address, index, value)
} else {
Err(ExitError::InvalidCode(Opcode::TSTORE))
}
}
fn tload(&mut self, address: H160, index: H256) -> Result<U256, ExitError> {
if self.config.has_transient_storage {
self.state.tload(address, index)
} else {
Err(ExitError::InvalidCode(Opcode::TLOAD))
}
}
fn get_authority_target(&mut self, address: H160) -> Option<H160> {
if self.config.has_authorization_list {
self.state.get_authority_target(address)
} else {
None
}
}
fn authority_code(&mut self, authority: H160) -> Vec<u8> {
if !self.config.has_authorization_list {
return self.code(authority);
}
self.get_authority_target(authority).map_or_else(
|| self.code(authority),
|target_address| self.code(target_address),
)
}
fn warm_target(&mut self, target: (H160, Option<H256>)) {
match target {
(address, None) => self.state.metadata_mut().access_address(address),
(address, Some(key)) => self.state.metadata_mut().access_storage(address, key),
}
}
}
struct StackExecutorHandle<'inner, 'config, 'precompiles, S, P> {
executor: &'inner mut StackExecutor<'config, 'precompiles, S, P>,
code_address: H160,
input: &'inner [u8],
gas_limit: Option<u64>,
context: &'inner Context,
is_static: bool,
}
impl<'config, S: StackState<'config>, P: PrecompileSet> PrecompileHandle
for StackExecutorHandle<'_, 'config, '_, S, P>
{
fn call(
&mut self,
code_address: H160,
transfer: Option<Transfer>,
input: Vec<u8>,
gas_limit: Option<u64>,
is_static: bool,
context: &Context,
) -> (ExitReason, Vec<u8>) {
let target_is_cold = self.executor.is_cold(code_address, None);
let delegated_designator_is_cold = self
.executor
.get_authority_target(code_address)
.map(|target| self.executor.is_cold(target, None));
let gas_cost = gasometer::GasCost::Call {
value: transfer.clone().map_or(U256_ZERO, |x| x.value),
gas: U256::from(gas_limit.unwrap_or(u64::MAX)),
target_is_cold,
delegated_designator_is_cold,
target_exists: self.executor.exists(code_address),
};
let memory_cost = Some(gasometer::MemoryCost {
offset: 0,
len: input.len(),
});
if let Err(error) = self
.executor
.state
.metadata_mut()
.gasometer
.record_dynamic_cost(gas_cost, memory_cost)
{
return (ExitReason::Error(error), Vec::new());
}
event!(PrecompileSubcall {
code_address,
transfer: &transfer,
input: &input,
target_gas: gas_limit,
is_static,
context
});
match Handler::call(
self.executor,
code_address,
transfer,
input,
gas_limit,
is_static,
context.clone(),
) {
Capture::Exit((s, v)) => (s, v),
Capture::Trap(rt) => {
let mut call_stack: SmallVec<[TaggedRuntime; DEFAULT_CALL_STACK_CAPACITY]> =
smallvec!(rt.0);
let (reason, _, return_data) =
self.executor.execute_with_call_stack(&mut call_stack);
emit_exit!(reason, return_data)
}
}
}
fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> {
self.executor
.state
.metadata_mut()
.gasometer
.record_cost(cost)
}
fn record_external_cost(
&mut self,
ref_time: Option<u64>,
proof_size: Option<u64>,
storage_growth: Option<u64>,
) -> Result<(), ExitError> {
self.executor
.state
.record_external_cost(ref_time, proof_size, storage_growth)
}
fn refund_external_cost(&mut self, ref_time: Option<u64>, proof_size: Option<u64>) {
self.executor
.state
.refund_external_cost(ref_time, proof_size);
}
fn remaining_gas(&self) -> u64 {
self.executor.state.metadata().gasometer.gas()
}
fn log(&mut self, address: H160, topics: Vec<H256>, data: Vec<u8>) -> Result<(), ExitError> {
Handler::log(self.executor, address, topics, data)
}
fn code_address(&self) -> H160 {
self.code_address
}
fn input(&self) -> &[u8] {
self.input
}
fn context(&self) -> &Context {
self.context
}
fn is_static(&self) -> bool {
self.is_static
}
fn gas_limit(&self) -> Option<u64> {
self.gas_limit
}
}