1use anyhow::{anyhow, Result};
4use revm::{
5 db::EmptyDB,
6 primitives::{
7 AccountInfo, Bytecode, Bytes, ExecutionResult, HaltReason, Log, Output, ResultAndState,
8 SuccessReason, TransactTo, TxKind, B256, 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 pub blob_hashes: Option<Vec<B256>>,
30 pub tx_gas_limit: u64,
32 commit: bool,
34}
35
36impl<'e> Default for EVM<'e> {
37 fn default() -> Self {
38 let mut db = InMemoryDB::default();
39 db.insert_account_info(ALICE.into(), AccountInfo::from_balance(U256::MAX));
40
41 let evm = Revm::<'e, (), EmptyDB>::builder().with_db(db).build();
42 Self {
43 inner: evm,
44 caller: [0; 20],
45 blob_hashes: None,
46 tx_gas_limit: GAS_LIMIT,
47 commit: false,
48 }
49 }
50}
51
52impl EVM<'_> {
53 pub fn interp(runtime_bytecode: &[u8], input: &[u8]) -> Result<Info> {
55 Self::default()
56 .contract(runtime_bytecode)
57 .calldata(input)
58 .call(CONTRACT)
59 }
60
61 pub fn storage(&mut self, address: [u8; 20], key: [u8; 32]) -> Result<[u8; 32]> {
63 let db = self.inner.db_mut();
64 Ok(db
65 .storage(address.into(), U256::from_be_bytes(key))?
66 .to_be_bytes())
67 }
68
69 pub fn commit(mut self, flag: bool) -> Self {
71 self.commit = flag;
72 self
73 }
74
75 pub fn caller(mut self, caller: [u8; 20]) -> Self {
77 self.caller = caller;
78 self
79 }
80
81 pub fn chain_id(mut self, id: u64) -> Self {
83 self.inner.tx_mut().chain_id = Some(id);
84 self
85 }
86
87 pub fn block_number(mut self, number: u64) -> Self {
89 self.inner.block_mut().number = U256::from(number);
90 self
91 }
92
93 pub fn block_hash(mut self, number: u64, hash: [u8; 32]) -> Self {
95 self.inner
96 .db_mut()
97 .block_hashes
98 .insert(U256::from(number), hash.into());
99 self
100 }
101
102 pub fn blob_hashes(mut self, blob_hashes: Vec<[u8; 32]>) -> Self {
104 let blob_hashes = blob_hashes.into_iter().map(Into::into).collect();
105 self.blob_hashes = Some(blob_hashes);
106 self
107 }
108
109 pub fn basefee(mut self, basefee: u64, gas_price: u64) -> Self {
111 self.inner.block_mut().basefee = U256::from(basefee);
112 self.inner.tx_mut().gas_price = U256::from(gas_price);
113 self
114 }
115
116 pub fn blob_basefee(mut self, excess_blob_gas: u64) -> Self {
118 self.inner
119 .block_mut()
120 .set_blob_excess_gas_and_price(excess_blob_gas);
121 self
122 }
123
124 pub fn get_blob_basefee(&self) -> [u8; 32] {
126 let basefee = self.inner.block().get_blob_gasprice();
127 let basefee = match basefee {
128 Some(fee) => fee.to_be_bytes(),
129 None => [0; 16],
130 };
131 let mut blob_basefee = [0; 32];
132 blob_basefee[16..].copy_from_slice(&basefee);
133 blob_basefee
134 }
135
136 pub fn coinbase(mut self, coinbase: [u8; 20]) -> Self {
138 self.inner.block_mut().coinbase = coinbase.into();
139 self
140 }
141
142 pub fn prevrandao(mut self, prevrandao: [u8; 32]) -> Self {
144 self.inner.block_mut().prevrandao = Some(B256::from(prevrandao));
145 self
146 }
147
148 pub fn timestamp(mut self, timestamp: u64) -> Self {
150 self.inner.block_mut().timestamp = U256::from(timestamp);
151 self
152 }
153
154 pub fn tx_gas_limit(mut self, gaslimit: u64) -> Self {
156 self.tx_gas_limit = gaslimit;
157 self
158 }
159
160 pub fn call(&mut self, to: [u8; 20]) -> Result<Info> {
162 let to = TransactTo::Call(to.into());
163 self.inner.tx_mut().gas_limit = self.tx_gas_limit;
164 self.inner.tx_mut().transact_to = to;
165 self.inner.tx_mut().caller = self.caller.into();
166 if let Some(hashes) = &self.blob_hashes {
167 self.inner.tx_mut().max_fee_per_blob_gas = Some(U256::from(1));
168 self.inner.tx_mut().blob_hashes = hashes.clone();
169 }
170
171 if self.commit {
172 self.inner.transact_commit()?.try_into()
173 } else {
174 let result = self.inner.transact().map_err(|e| anyhow!(e))?;
175 (result, to).try_into()
176 }
177 }
178
179 pub fn deploy(&mut self, bytecode: &[u8]) -> Result<Info> {
181 self.calldata(bytecode);
182 self.inner.tx_mut().transact_to = TxKind::Create;
183 self.inner.transact_commit()?.try_into()
184 }
185
186 pub fn calldata(&mut self, input: &[u8]) -> &mut Self {
188 self.inner.tx_mut().data = Bytes::copy_from_slice(input);
189 self
190 }
191
192 pub fn contract(mut self, runtime_bytecode: &[u8]) -> Self {
194 self.db().insert_account_info(
195 CONTRACT.into(),
196 AccountInfo::new(
197 Default::default(),
198 0,
199 Default::default(),
200 Bytecode::new_raw(Bytes::copy_from_slice(runtime_bytecode)),
201 ),
202 );
203
204 self
205 }
206
207 fn db(&mut self) -> &mut InMemoryDB {
208 self.inner.db_mut()
209 }
210}
211
212#[derive(Debug, Default)]
214pub struct Info {
215 pub address: [u8; 20],
217 pub gas: u64,
219 pub ret: Vec<u8>,
221 pub storage: HashMap<U256, U256>,
223 pub logs: Vec<Log>,
225 pub halt: Option<HaltReason>,
227 pub revert: Option<String>,
229}
230
231impl TryFrom<ExecutionResult> for Info {
232 type Error = anyhow::Error;
233
234 fn try_from(result: ExecutionResult) -> Result<Self> {
235 let mut info = Info {
236 gas: result.gas_used(),
237 ..Default::default()
238 };
239
240 match result {
241 ExecutionResult::Success {
242 logs,
243 reason,
244 output,
245 ..
246 } => {
247 if reason != SuccessReason::Return {
248 return Err(anyhow!("Transaction is not returned: {reason:?}"));
249 }
250 info.logs = logs;
251
252 let ret = match output {
253 Output::Call(bytes) => bytes,
254 Output::Create(bytes, maybe_address) => {
255 let Some(address) = maybe_address else {
256 return Err(anyhow!(
257 "No contract created after the creation transaction."
258 ));
259 };
260
261 info.address = *address.as_ref();
262 bytes
263 }
264 };
265
266 info.ret = ret.into();
267 }
268 ExecutionResult::Halt { reason, .. } => {
269 info.halt = Some(reason);
270 }
271 ExecutionResult::Revert { gas_used, output } => {
272 info.gas = gas_used;
273 info.revert = Some(
274 String::from_utf8_lossy(&output)
275 .trim_start_matches("\0")
276 .to_string(),
277 );
278 }
279 }
280
281 Ok(info)
282 }
283}
284
285impl TryFrom<(ResultAndState, TransactTo)> for Info {
286 type Error = anyhow::Error;
287
288 fn try_from((res, to): (ResultAndState, TransactTo)) -> Result<Self> {
289 let ResultAndState { result, state } = res;
290 let mut info = Self::try_from(result)?;
291
292 if let TransactTo::Call(address) = to {
293 info.storage = state
294 .get(&address)
295 .ok_or_else(|| anyhow!("no state found for account 0x{}", hex::encode(address)))?
296 .storage
297 .iter()
298 .map(|(k, v)| (*k, v.present_value))
299 .collect();
300 }
301
302 Ok(info)
303 }
304}