//! Wrapper of revm
use anyhow::{anyhow, Result};
use revm::{
db::EmptyDB,
primitives::{
AccountInfo, Bytecode, Bytes, ExecutionResult, HaltReason, Log, Output, ResultAndState,
SuccessReason, TransactTo, TxKind, B256, U256,
},
Database, Evm as Revm, InMemoryDB,
};
use std::collections::HashMap;
/// Transaction gas limit.
const GAS_LIMIT: u64 = 1_000_000_000;
/// Alice account address.
pub const ALICE: [u8; 20] = [0; 20];
/// Contract address if any.
pub const CONTRACT: [u8; 20] = [1; 20];
/// Wrapper of full REVM
pub struct EVM<'e> {
inner: Revm<'e, (), InMemoryDB>,
/// Caller for the execution
pub caller: [u8; 20],
/// Blob hashes
pub blob_hashes: Option<Vec<B256>>,
/// The gas limit of the transaction
pub tx_gas_limit: u64,
/// If commit changes
commit: bool,
}
impl<'e> Default for EVM<'e> {
fn default() -> Self {
let mut db = InMemoryDB::default();
db.insert_account_info(ALICE.into(), AccountInfo::from_balance(U256::MAX));
let evm = Revm::<'e, (), EmptyDB>::builder().with_db(db).build();
Self {
inner: evm,
caller: [0; 20],
blob_hashes: None,
tx_gas_limit: GAS_LIMIT,
commit: false,
}
}
}
impl EVM<'_> {
/// Interpret runtime bytecode with provided arguments
pub fn interp(runtime_bytecode: &[u8], input: &[u8]) -> Result<Info> {
Self::default()
.contract(runtime_bytecode)
.calldata(input)
.call(CONTRACT)
}
/// Get storage from address and storage index
pub fn storage(&mut self, address: [u8; 20], key: [u8; 32]) -> Result<[u8; 32]> {
let db = self.inner.db_mut();
Ok(db
.storage(address.into(), U256::from_be_bytes(key))?
.to_be_bytes())
}
/// If commit changes
pub fn commit(mut self, flag: bool) -> Self {
self.commit = flag;
self
}
/// Set caller for the execution
pub fn caller(mut self, caller: [u8; 20]) -> Self {
self.caller = caller;
self
}
/// Set chain id
pub fn chain_id(mut self, id: u64) -> Self {
self.inner.tx_mut().chain_id = Some(id);
self
}
/// Set block number
pub fn block_number(mut self, number: u64) -> Self {
self.inner.block_mut().number = U256::from(number);
self
}
/// Set block hash
pub fn block_hash(mut self, number: u64, hash: [u8; 32]) -> Self {
self.inner
.db_mut()
.block_hashes
.insert(U256::from(number), hash.into());
self
}
/// Set blob hashes
pub fn blob_hashes(mut self, blob_hashes: Vec<[u8; 32]>) -> Self {
let blob_hashes = blob_hashes.into_iter().map(Into::into).collect();
self.blob_hashes = Some(blob_hashes);
self
}
/// Set block basefee
pub fn basefee(mut self, basefee: u64, gas_price: u64) -> Self {
self.inner.block_mut().basefee = U256::from(basefee);
self.inner.tx_mut().gas_price = U256::from(gas_price);
self
}
/// Set block’s blob basefee
pub fn blob_basefee(mut self, excess_blob_gas: u64) -> Self {
self.inner
.block_mut()
.set_blob_excess_gas_and_price(excess_blob_gas);
self
}
/// Get block’s blob basefee
pub fn get_blob_basefee(&self) -> [u8; 32] {
let basefee = self.inner.block().get_blob_gasprice();
let basefee = match basefee {
Some(fee) => fee.to_be_bytes(),
None => [0; 16],
};
let mut blob_basefee = [0; 32];
blob_basefee[16..].copy_from_slice(&basefee);
blob_basefee
}
/// Set block’s coinbase
pub fn coinbase(mut self, coinbase: [u8; 20]) -> Self {
self.inner.block_mut().coinbase = coinbase.into();
self
}
/// Set block’s prevrandao
pub fn prevrandao(mut self, prevrandao: [u8; 32]) -> Self {
self.inner.block_mut().prevrandao = Some(B256::from(prevrandao));
self
}
/// Set block’s timestamp
pub fn timestamp(mut self, timestamp: u64) -> Self {
self.inner.block_mut().timestamp = U256::from(timestamp);
self
}
/// Set tx’s gaslimit
pub fn tx_gas_limit(mut self, gaslimit: u64) -> Self {
self.tx_gas_limit = gaslimit;
self
}
/// Send transaction to the provided address.
pub fn call(&mut self, to: [u8; 20]) -> Result<Info> {
let to = TransactTo::Call(to.into());
self.inner.tx_mut().gas_limit = self.tx_gas_limit;
self.inner.tx_mut().transact_to = to;
self.inner.tx_mut().caller = self.caller.into();
if let Some(hashes) = &self.blob_hashes {
self.inner.tx_mut().max_fee_per_blob_gas = Some(U256::from(1));
self.inner.tx_mut().blob_hashes = hashes.clone();
}
if self.commit {
self.inner.transact_commit()?.try_into()
} else {
let result = self.inner.transact().map_err(|e| anyhow!(e))?;
(result, to).try_into()
}
}
/// Interpret runtime bytecode with provided arguments
pub fn deploy(&mut self, bytecode: &[u8]) -> Result<Info> {
self.calldata(bytecode);
self.inner.tx_mut().transact_to = TxKind::Create;
self.inner.transact_commit()?.try_into()
}
/// Fill the calldata of the present transaction.
pub fn calldata(&mut self, input: &[u8]) -> &mut Self {
self.inner.tx_mut().data = Bytes::copy_from_slice(input);
self
}
/// Override the present contract
pub fn contract(mut self, runtime_bytecode: &[u8]) -> Self {
self.db().insert_account_info(
CONTRACT.into(),
AccountInfo::new(
Default::default(),
0,
Default::default(),
Bytecode::new_raw(Bytes::copy_from_slice(runtime_bytecode)),
),
);
self
}
fn db(&mut self) -> &mut InMemoryDB {
self.inner.db_mut()
}
}
/// Interp execution result info.
#[derive(Debug, Default)]
pub struct Info {
/// the created contract address if any.
pub address: [u8; 20],
/// Gas spent.
pub gas: u64,
/// Return value.
pub ret: Vec<u8>,
/// The storage.
pub storage: HashMap<U256, U256>,
/// Execution logs.
pub logs: Vec<Log>,
/// Transaction halt reason.
pub halt: Option<HaltReason>,
/// The revert message.
pub revert: Option<String>,
}
impl TryFrom<ExecutionResult> for Info {
type Error = anyhow::Error;
fn try_from(result: ExecutionResult) -> Result<Self> {
let mut info = Info {
gas: result.gas_used(),
..Default::default()
};
match result {
ExecutionResult::Success {
logs,
reason,
output,
..
} => {
if reason != SuccessReason::Return {
return Err(anyhow!("Transaction is not returned: {reason:?}"));
}
info.logs = logs;
let ret = match output {
Output::Call(bytes) => bytes,
Output::Create(bytes, maybe_address) => {
let Some(address) = maybe_address else {
return Err(anyhow!(
"No contract created after the creation transaction."
));
};
info.address = *address.as_ref();
bytes
}
};
info.ret = ret.into();
}
ExecutionResult::Halt { reason, .. } => {
info.halt = Some(reason);
}
ExecutionResult::Revert { gas_used, output } => {
info.gas = gas_used;
info.revert = Some(
String::from_utf8_lossy(&output)
.trim_start_matches("\0")
.to_string(),
);
}
}
Ok(info)
}
}
impl TryFrom<(ResultAndState, TransactTo)> for Info {
type Error = anyhow::Error;
fn try_from((res, to): (ResultAndState, TransactTo)) -> Result<Self> {
let ResultAndState { result, state } = res;
let mut info = Self::try_from(result)?;
if let TransactTo::Call(address) = to {
info.storage = state
.get(&address)
.ok_or_else(|| anyhow!("no state found for account 0x{}", hex::encode(address)))?
.storage
.iter()
.map(|(k, v)| (*k, v.present_value))
.collect();
}
Ok(info)
}
}