use alloy_primitives::{
b256, keccak256, Address, BlockNumber, Bytes, Sealable, Sealed, TxNumber, B256, U256,
};
use alloy_rlp_derive::{RlpDecodable, RlpEncodable};
use alloy_sol_types::{sol, SolCall, SolType};
use revm::{
primitives::{
db::Database, AccountInfo, BlockEnv, Bytecode, CfgEnvWithHandlerCfg, ExecutionResult,
HashMap, ResultAndState, SpecId, SuccessReason, TransactTo,
},
Evm,
};
use serde::{Deserialize, Serialize};
use std::{convert::Infallible, fmt::Debug, rc::Rc};
pub mod config;
pub mod db;
pub mod ethereum;
#[cfg(feature = "host")]
pub mod host;
mod mpt;
pub use mpt::MerkleTrie;
#[derive(Debug, Serialize, Deserialize)]
pub struct ViewCallInput<H> {
pub header: H,
pub state_trie: MerkleTrie,
pub storage_tries: Vec<MerkleTrie>,
pub contracts: Vec<Bytes>,
pub ancestors: Vec<H>,
}
impl<H: EvmHeader> ViewCallInput<H> {
pub fn into_env(self) -> ViewCallEnv<StateDB, H> {
let state_root = self.state_trie.hash_slow();
assert_eq!(self.header.state_root(), &state_root, "State root mismatch");
let header = self.header.seal_slow();
let mut block_hashes = HashMap::with_capacity(self.ancestors.len() + 1);
block_hashes.insert(header.number(), header.seal());
let mut previous_header = header.inner();
for ancestor in &self.ancestors {
let ancestor_hash = ancestor.hash_slow();
assert_eq!(
previous_header.parent_hash(),
&ancestor_hash,
"Invalid chain: block {} is not the parent of block {}",
ancestor.number(),
previous_header.number()
);
block_hashes.insert(ancestor.number(), ancestor_hash);
previous_header = ancestor;
}
let db = StateDB::new(
self.state_trie,
self.storage_tries,
self.contracts,
block_hashes,
);
ViewCallEnv::new(db, header)
}
}
sol! {
struct BlockCommitment {
bytes32 blockHash;
uint blockNumber;
}
}
pub struct ViewCallEnv<D: Database, H: EvmHeader> {
db: D,
cfg_env: CfgEnvWithHandlerCfg,
header: Sealed<H>,
}
impl<D: Database, H: EvmHeader> ViewCallEnv<D, H> {
pub fn new(db: D, header: Sealed<H>) -> Self {
let cfg_env = CfgEnvWithHandlerCfg::new_with_spec_id(Default::default(), SpecId::LATEST);
Self {
db,
cfg_env,
header,
}
}
pub fn with_chain_spec(mut self, chain_spec: &config::ChainSpec) -> Self {
self.cfg_env.chain_id = chain_spec.chain_id();
self.cfg_env.handler_cfg.spec_id = chain_spec
.active_fork(self.header.number(), self.header.timestamp())
.unwrap();
self
}
pub fn block_commitment(&self) -> BlockCommitment {
BlockCommitment {
blockHash: self.header.seal(),
blockNumber: U256::from(self.header.number()),
}
}
pub fn header(&self) -> &H {
self.header.inner()
}
}
pub struct ViewCall<C: SolCall> {
call: C,
contract: Address,
caller: Address,
}
impl<C: SolCall> ViewCall<C> {
const GAS_LIMIT: u64 = 30_000_000;
pub fn new(call: C, contract: Address) -> Self {
Self {
call,
contract,
caller: contract,
}
}
pub fn with_caller(mut self, caller: Address) -> Self {
self.caller = caller;
self
}
#[inline]
pub fn execute<D: Database, H: EvmHeader>(self, env: ViewCallEnv<D, H>) -> C::Return
where
<D as Database>::Error: Debug,
{
self.transact(env.db, env.cfg_env, env.header.inner())
.unwrap()
}
fn transact<D: Database, H: EvmHeader>(
&self,
db: D,
cfg_env: CfgEnvWithHandlerCfg,
header: &H,
) -> Result<C::Return, String>
where
<D as Database>::Error: Debug,
{
let mut evm = Evm::builder()
.with_db(db)
.with_cfg_env_with_handler_cfg(cfg_env)
.modify_block_env(|blk_env| header.fill_block_env(blk_env))
.build();
let tx_env = evm.tx_mut();
tx_env.caller = self.caller;
tx_env.gas_limit = Self::GAS_LIMIT;
tx_env.transact_to = TransactTo::call(self.contract);
tx_env.value = U256::ZERO;
tx_env.data = self.call.abi_encode().into();
let ResultAndState { result, .. } = evm
.transact_preverified()
.map_err(|err| format!("Call '{}' failed: {:?}", C::SIGNATURE, err))?;
let ExecutionResult::Success { reason, output, .. } = result else {
return Err(format!("Call '{}' failed", C::SIGNATURE));
};
if reason != SuccessReason::Return {
return Err(format!(
"Call '{}' did not return: {:?}",
C::SIGNATURE,
reason
));
}
let returns = C::abi_decode_returns(&output.into_data(), true).map_err(|err| {
format!(
"Call '{}' returned invalid type; expected '{}': {:?}",
C::SIGNATURE,
<C::ReturnTuple<'_> as SolType>::SOL_NAME,
err
)
})?;
Ok(returns)
}
}
pub struct StateDB {
state_trie: MerkleTrie,
storage_tries: HashMap<B256, Rc<MerkleTrie>>,
contracts: HashMap<B256, Bytes>,
block_hashes: HashMap<u64, B256>,
account_storage: HashMap<Address, Option<Rc<MerkleTrie>>>,
}
impl StateDB {
pub fn new(
state_trie: MerkleTrie,
storage_tries: impl IntoIterator<Item = MerkleTrie>,
contracts: impl IntoIterator<Item = Bytes>,
block_hashes: HashMap<u64, B256>,
) -> Self {
let contracts = contracts
.into_iter()
.map(|code| (keccak256(&code), code))
.collect();
let storage_tries = storage_tries
.into_iter()
.map(|trie| (trie.hash_slow(), Rc::new(trie)))
.collect();
Self {
state_trie,
contracts,
storage_tries,
block_hashes,
account_storage: HashMap::new(),
}
}
}
impl Database for StateDB {
type Error = Infallible;
#[inline]
fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
let account = self
.state_trie
.get_rlp::<StateAccount>(keccak256(address))
.expect("invalid state value");
match account {
Some(account) => {
if let Some(storage_trie) = self.storage_tries.get(&account.storage_root) {
self.account_storage
.insert(address, Some(storage_trie.clone()));
}
Ok(Some(AccountInfo {
balance: account.balance,
nonce: account.nonce,
code_hash: account.code_hash,
code: None, }))
}
None => {
self.account_storage.insert(address, None);
Ok(None)
}
}
}
#[inline]
fn code_by_hash(&mut self, hash: B256) -> Result<Bytecode, Self::Error> {
let code = self
.contracts
.get(&hash)
.unwrap_or_else(|| panic!("code not found: {}", hash));
Ok(Bytecode::new_raw(code.clone()))
}
#[inline]
fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
let storage = self
.account_storage
.get(&address)
.unwrap_or_else(|| panic!("storage not found: {:?}", address));
match storage {
Some(storage) => {
let val = storage
.get_rlp(keccak256(index.to_be_bytes::<32>()))
.expect("invalid storage value");
Ok(val.unwrap_or_default())
}
None => Ok(U256::ZERO),
}
}
#[inline]
fn block_hash(&mut self, number: U256) -> Result<B256, Self::Error> {
let number: u64 = number.to();
let hash = self
.block_hashes
.get(&number)
.unwrap_or_else(|| panic!("block not found: {}", number));
Ok(*hash)
}
}
pub const KECCAK_EMPTY: B256 =
b256!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
struct StateAccount {
pub nonce: TxNumber,
pub balance: U256,
pub storage_root: B256,
pub code_hash: B256,
}
impl Default for StateAccount {
fn default() -> Self {
Self {
nonce: 0,
balance: U256::ZERO,
storage_root: mpt::EMPTY_ROOT_HASH,
code_hash: KECCAK_EMPTY,
}
}
}
pub trait EvmHeader: Sealable {
fn parent_hash(&self) -> &B256;
fn number(&self) -> BlockNumber;
fn timestamp(&self) -> u64;
fn state_root(&self) -> &B256;
fn fill_block_env(&self, blk_env: &mut BlockEnv);
}