1use anyhow::{anyhow, Result};
4use revm::{
5 db::EmptyDB,
6 primitives::{
7 AccountInfo, Bytecode, Bytes, ExecutionResult, HaltReason, Log, Output, ResultAndState,
8 SuccessReason, TransactTo, TxKind, U256,
9 },
10 Database, Evm as Revm, InMemoryDB,
11};
12use std::collections::HashMap;
13
14const GAS_LIMIT: u64 = 1_000_000_000;
16
17pub const ALICE: [u8; 20] = [0; 20];
19
20pub const CONTRACT: [u8; 20] = [1; 20];
22
23pub struct EVM<'e> {
25 inner: Revm<'e, (), InMemoryDB>,
26 pub caller: [u8; 20],
28 commit: bool,
30}
31
32impl<'e> Default for EVM<'e> {
33 fn default() -> Self {
34 let mut db = InMemoryDB::default();
35 db.insert_account_info(ALICE.into(), AccountInfo::from_balance(U256::MAX));
36
37 let evm = Revm::<'e, (), EmptyDB>::builder().with_db(db).build();
38 Self {
39 inner: evm,
40 caller: [0; 20],
41 commit: false,
42 }
43 }
44}
45
46impl EVM<'_> {
47 pub fn interp(runtime_bytecode: &[u8], input: &[u8]) -> Result<Info> {
49 Self::default()
50 .contract(runtime_bytecode)
51 .calldata(input)
52 .call(CONTRACT)
53 }
54
55 pub fn storage(&mut self, address: [u8; 20], key: [u8; 32]) -> Result<[u8; 32]> {
57 let db = self.inner.db_mut();
58 Ok(db
59 .storage(address.into(), U256::from_be_bytes(key))?
60 .to_be_bytes())
61 }
62
63 pub fn commit(mut self, flag: bool) -> Self {
65 self.commit = flag;
66 self
67 }
68
69 pub fn caller(mut self, caller: [u8; 20]) -> Self {
71 self.caller = caller;
72 self
73 }
74
75 pub fn call(&mut self, to: [u8; 20]) -> Result<Info> {
77 let to = TransactTo::Call(to.into());
78 self.inner.tx_mut().gas_limit = GAS_LIMIT;
79 self.inner.tx_mut().transact_to = to;
80 self.inner.tx_mut().caller = self.caller.into();
81
82 if self.commit {
83 self.inner.transact_commit()?.try_into()
84 } else {
85 let result = self.inner.transact().map_err(|e| anyhow!(e))?;
86 (result, to).try_into()
87 }
88 }
89
90 pub fn deploy(&mut self, bytecode: &[u8]) -> Result<Info> {
92 self.calldata(bytecode);
93 self.inner.tx_mut().transact_to = TxKind::Create;
94 self.inner.transact_commit()?.try_into()
95 }
96
97 pub fn calldata(&mut self, input: &[u8]) -> &mut Self {
99 self.inner.tx_mut().data = Bytes::copy_from_slice(input);
100 self
101 }
102
103 pub fn contract(mut self, runtime_bytecode: &[u8]) -> Self {
105 self.db().insert_account_info(
106 CONTRACT.into(),
107 AccountInfo::new(
108 Default::default(),
109 0,
110 Default::default(),
111 Bytecode::new_raw(Bytes::copy_from_slice(runtime_bytecode)),
112 ),
113 );
114
115 self
116 }
117
118 fn db(&mut self) -> &mut InMemoryDB {
119 self.inner.db_mut()
120 }
121}
122
123#[derive(Debug, Default)]
125pub struct Info {
126 pub address: [u8; 20],
128 pub gas: u64,
130 pub ret: Vec<u8>,
132 pub storage: HashMap<U256, U256>,
134 pub logs: Vec<Log>,
136 pub halt: Option<HaltReason>,
138 pub revert: Option<String>,
140}
141
142impl TryFrom<ExecutionResult> for Info {
143 type Error = anyhow::Error;
144
145 fn try_from(result: ExecutionResult) -> Result<Self> {
146 let mut info = Info {
147 gas: result.gas_used(),
148 ..Default::default()
149 };
150
151 match result {
152 ExecutionResult::Success {
153 logs,
154 reason,
155 output,
156 ..
157 } => {
158 if reason != SuccessReason::Return {
159 return Err(anyhow!("Transaction is not returned: {reason:?}"));
160 }
161 info.logs = logs;
162
163 let ret = match output {
164 Output::Call(bytes) => bytes,
165 Output::Create(bytes, maybe_address) => {
166 let Some(address) = maybe_address else {
167 return Err(anyhow!(
168 "No contract created after the creation transaction."
169 ));
170 };
171
172 info.address = *address.as_ref();
173 bytes
174 }
175 };
176
177 info.ret = ret.into();
178 }
179 ExecutionResult::Halt { reason, .. } => {
180 info.halt = Some(reason);
181 }
182 ExecutionResult::Revert { gas_used, output } => {
183 info.gas = gas_used;
184 info.revert = Some(
185 String::from_utf8_lossy(&output)
186 .trim_start_matches("\0")
187 .to_string(),
188 );
189 }
190 }
191
192 Ok(info)
193 }
194}
195
196impl TryFrom<(ResultAndState, TransactTo)> for Info {
197 type Error = anyhow::Error;
198
199 fn try_from((res, to): (ResultAndState, TransactTo)) -> Result<Self> {
200 let ResultAndState { result, state } = res;
201 let mut info = Self::try_from(result)?;
202
203 if let TransactTo::Call(address) = to {
204 info.storage = state
205 .get(&address)
206 .ok_or_else(|| anyhow!("no state found for account 0x{}", hex::encode(address)))?
207 .storage
208 .iter()
209 .map(|(k, v)| (*k, v.present_value))
210 .collect();
211 }
212
213 Ok(info)
214 }
215}