use anyhow::{anyhow, Result};
use revm::{
db::EmptyDB,
primitives::{
AccountInfo, Bytecode, Bytes, ExecutionResult, HaltReason, Log, Output, ResultAndState,
SuccessReason, TransactTo, TxKind, U256,
},
Database, Evm as Revm, InMemoryDB,
};
use std::collections::HashMap;
const GAS_LIMIT: u64 = 1_000_000_000;
pub const ALICE: [u8; 20] = [0; 20];
pub const CONTRACT: [u8; 20] = [1; 20];
pub struct EVM<'e> {
inner: Revm<'e, (), InMemoryDB>,
pub caller: [u8; 20],
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],
commit: false,
}
}
}
impl<'e> EVM<'e> {
pub fn interp(runtime_bytecode: &[u8], input: &[u8]) -> Result<Info> {
Self::default()
.contract(runtime_bytecode)
.calldata(input)
.call(CONTRACT)
}
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())
}
pub fn commit(mut self, flag: bool) -> Self {
self.commit = flag;
self
}
pub fn caller(mut self, caller: [u8; 20]) -> Self {
self.caller = caller;
self
}
pub fn call(&mut self, to: [u8; 20]) -> Result<Info> {
let to = TransactTo::Call(to.into());
self.inner.tx_mut().gas_limit = GAS_LIMIT;
self.inner.tx_mut().transact_to = to;
self.inner.tx_mut().caller = self.caller.into();
if self.commit {
self.inner.transact_commit()?.try_into()
} else {
let result = self.inner.transact().map_err(|e| anyhow!(e))?;
(result, to).try_into()
}
}
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()
}
pub fn calldata(&mut self, input: &[u8]) -> &mut Self {
self.inner.tx_mut().data = Bytes::copy_from_slice(input);
self
}
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()
}
}
#[derive(Debug, Default)]
pub struct Info {
pub address: [u8; 20],
pub gas: u64,
pub ret: Vec<u8>,
pub storage: HashMap<U256, U256>,
pub logs: Vec<Log>,
pub halt: Option<HaltReason>,
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)
}
}