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, 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    /// If commit changes
29    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    /// Interpret runtime bytecode with provided arguments
48    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    /// Get storage from address and storage index
56    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    /// If commit changes
64    pub fn commit(mut self, flag: bool) -> Self {
65        self.commit = flag;
66        self
67    }
68
69    /// Set caller for the execution
70    pub fn caller(mut self, caller: [u8; 20]) -> Self {
71        self.caller = caller;
72        self
73    }
74
75    /// Send transaction to the provided address.
76    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    /// Interpret runtime bytecode with provided arguments
91    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    /// Fill the calldata of the present transaction.
98    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    /// Override the present contract
104    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/// Interp execution result info.
124#[derive(Debug, Default)]
125pub struct Info {
126    /// the created contract address if any.
127    pub address: [u8; 20],
128    /// Gas spent.
129    pub gas: u64,
130    /// Return value.
131    pub ret: Vec<u8>,
132    /// The storage.
133    pub storage: HashMap<U256, U256>,
134    /// Execution logs.
135    pub logs: Vec<Log>,
136    /// Transaction halt reason.
137    pub halt: Option<HaltReason>,
138    /// The revert message.
139    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}