zint/
evm.rs

1//! Wrapper of revm
2
3use 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
14/// Transaction gas limit.
15const GAS_LIMIT: u64 = 1_000_000_000;
16
17/// Alice account address.
18pub const ALICE: [u8; 20] = [0; 20];
19
20/// Contract address if any.
21pub const CONTRACT: [u8; 20] = [1; 20];
22
23/// Wrapper of full REVM
24pub struct EVM<'e> {
25    inner: Revm<'e, (), InMemoryDB>,
26    /// Caller for the execution
27    pub caller: [u8; 20],
28    /// Blob hashes
29    pub blob_hashes: Option<Vec<B256>>,
30    /// The gas limit of the transaction
31    pub tx_gas_limit: u64,
32    /// If commit changes
33    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    /// Interpret runtime bytecode with provided arguments
54    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    /// Get storage from address and storage index
62    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    /// If commit changes
70    pub fn commit(mut self, flag: bool) -> Self {
71        self.commit = flag;
72        self
73    }
74
75    /// Set caller for the execution
76    pub fn caller(mut self, caller: [u8; 20]) -> Self {
77        self.caller = caller;
78        self
79    }
80
81    /// Set chain id
82    pub fn chain_id(mut self, id: u64) -> Self {
83        self.inner.tx_mut().chain_id = Some(id);
84        self
85    }
86
87    /// Set block number
88    pub fn block_number(mut self, number: u64) -> Self {
89        self.inner.block_mut().number = U256::from(number);
90        self
91    }
92
93    /// Set block hash
94    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    /// Set blob hashes
103    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    /// Set block basefee
110    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    /// Set block’s blob basefee
117    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    /// Get block’s blob basefee
125    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    /// Set block’s coinbase
137    pub fn coinbase(mut self, coinbase: [u8; 20]) -> Self {
138        self.inner.block_mut().coinbase = coinbase.into();
139        self
140    }
141
142    /// Set block’s prevrandao
143    pub fn prevrandao(mut self, prevrandao: [u8; 32]) -> Self {
144        self.inner.block_mut().prevrandao = Some(B256::from(prevrandao));
145        self
146    }
147
148    /// Set block’s timestamp
149    pub fn timestamp(mut self, timestamp: u64) -> Self {
150        self.inner.block_mut().timestamp = U256::from(timestamp);
151        self
152    }
153
154    /// Set tx’s gaslimit
155    pub fn tx_gas_limit(mut self, gaslimit: u64) -> Self {
156        self.tx_gas_limit = gaslimit;
157        self
158    }
159
160    /// Send transaction to the provided address.
161    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    /// Interpret runtime bytecode with provided arguments
180    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    /// Fill the calldata of the present transaction.
187    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    /// Override the present contract
193    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/// Interp execution result info.
213#[derive(Debug, Default)]
214pub struct Info {
215    /// the created contract address if any.
216    pub address: [u8; 20],
217    /// Gas spent.
218    pub gas: u64,
219    /// Return value.
220    pub ret: Vec<u8>,
221    /// The storage.
222    pub storage: HashMap<U256, U256>,
223    /// Execution logs.
224    pub logs: Vec<Log>,
225    /// Transaction halt reason.
226    pub halt: Option<HaltReason>,
227    /// The revert message.
228    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}