verbs_rs/env/
utils.rs

1//! EVM and data processing utilities
2//!
3
4use crate::contract::Event;
5use alloy_primitives::{Address, Bytes, Log, U256};
6use alloy_sol_types::{decode_revert_reason, SolCall, SolEvent};
7use revm::primitives::{ExecutionResult, Output, TransactTo, TxEnv};
8use std::fmt;
9
10/// Error raised when an EVM transaction is reverted
11#[derive(Debug, Clone)]
12pub struct RevertError {
13    /// Name of the function that was called
14    pub function_name: &'static str,
15    /// Address of the sender of the transaction
16    sender: Address,
17    /// Decoded revert error message
18    pub output: Option<String>,
19}
20
21impl fmt::Display for RevertError {
22    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
23        let out_str = match &self.output {
24            Some(x) => x.as_str(),
25            None => "No output",
26        };
27
28        write!(
29            f,
30            "Failed to call {} from {} due to revert: {}",
31            self.function_name, self.sender, out_str,
32        )
33    }
34}
35
36/// Process an [ExecutionResult] from a contract deployment
37///
38/// Process the result of a call to deploy a contract and
39/// decode the output or reason message.
40///
41/// # Arguments
42///
43/// - `contract_name` - Name of the contract being deployed
44/// - `execution_result` - Result returned from the EVM
45///
46/// # Panics
47///
48/// Panics if the deployment was reverted or halted.
49///
50pub fn deployment_output(contract_name: &str, execution_result: ExecutionResult) -> Output {
51    match execution_result {
52        ExecutionResult::Success { output, .. } => output,
53        ExecutionResult::Revert { output, .. } => {
54            panic!(
55                "Failed to deploy {} due to revert: {:?}",
56                contract_name,
57                decode_revert_reason(&output.0)
58            )
59        }
60        ExecutionResult::Halt { reason, .. } => {
61            panic!(
62                "Failed to deploy {} due to halt: {:?}",
63                contract_name, reason
64            )
65        }
66    }
67}
68
69/// Initialise a transaction for contract deployment
70///
71/// Helper function to initialise a transaction to
72/// deploy a contract.
73///
74/// # Arguments
75///
76/// - `caller` - Address of the account deploying the contract
77/// - `data` - ABI encoded deployment bytecode and arguments
78///
79pub fn init_create_transaction(caller: Address, data: Vec<u8>) -> TxEnv {
80    TxEnv {
81        caller,
82        gas_limit: u64::MAX,
83        gas_price: U256::ZERO,
84        gas_priority_fee: None,
85        transact_to: TransactTo::create(),
86        value: U256::ZERO,
87        data: Bytes::from(data),
88        chain_id: None,
89        nonce: None,
90        access_list: Vec::new(),
91        blob_hashes: Vec::default(),
92        max_fee_per_blob_gas: None,
93    }
94}
95
96/// Initialise a transaction calling a contract
97///
98/// Helper function to initialise a transaction
99/// calling a contract function.
100///
101/// # Arguments
102///
103/// - `caller` - Address of the contract caller
104/// - `contract` - Address of the contract to call
105/// - `data` - ABI encoded function arguments
106/// - `value` - Value attached to the transaction
107///
108pub fn init_call_transaction(
109    caller: Address,
110    contract: Address,
111    data: Vec<u8>,
112    value: U256,
113) -> TxEnv {
114    TxEnv {
115        caller,
116        gas_limit: u64::MAX,
117        gas_price: U256::ZERO,
118        gas_priority_fee: None,
119        transact_to: TransactTo::Call(contract),
120        value,
121        data: Bytes::from(data),
122        chain_id: None,
123        nonce: None,
124        access_list: Vec::new(),
125        blob_hashes: Vec::default(),
126        max_fee_per_blob_gas: None,
127    }
128}
129
130/// Handle an [ExecutionResult] returned from a transaction
131///
132/// # Arguments
133///
134/// - `sender` - Address of the transaction sender
135/// - `execution_result` - [ExecutionResult] returned from a transaction
136///
137/// # Raises
138///
139/// Raises a [RevertError] if the transaction is reverted.
140///
141/// # Panics
142///
143/// Panics if the transaction is halted.
144///
145pub fn result_to_raw_output(
146    sender: Address,
147    execution_result: ExecutionResult,
148) -> Result<ExecutionResult, RevertError> {
149    match execution_result {
150        ExecutionResult::Success { .. } => Ok(execution_result),
151        ExecutionResult::Revert { output, .. } => Err(RevertError {
152            function_name: "Direct execute raw",
153            sender,
154            output: decode_revert_reason(&output),
155        }),
156        ExecutionResult::Halt { reason, .. } => panic!("Failed due to halt: {:?}", reason),
157    }
158}
159
160/// Process an [ExecutionResult] returning an [Event]
161///
162/// Creates an [Event] from an [ExecutionResult], where
163/// events are stored over the course of the simulation
164/// allowing the history of the simulation to be recreated.
165///
166/// # Arguments
167///
168/// - `step` - Simulation step
169/// - `sequence` - Position in sequence the transaction was
170///   executed
171/// - `function_selector` - 4 byte function selector of the
172///   contract function that was called
173/// - `sender` - Address of the transaction sender
174/// - `execution_result` - [ExecutionResult] returned from
175///   the transaction
176/// - `checked` - Flag if `true` a reverted transaction will
177///   cause a panic and stop the simulation. Should be set
178///   to `false` if it's possible for a transaction to revert
179///   but the simulation should continue.
180///
181/// # Panics
182///
183/// Panics if the transaction halts and if `checked` is true
184/// and the transaction is reverted.
185///
186pub fn result_to_output_with_events(
187    step: usize,
188    sequence: usize,
189    function_selector: [u8; 4],
190    sender: Address,
191    execution_result: ExecutionResult,
192    checked: bool,
193) -> Event {
194    match execution_result {
195        ExecutionResult::Success { output, logs, .. } => match output {
196            Output::Call(_) => Event {
197                success: true,
198                function_selector,
199                logs,
200                step,
201                sequence,
202            },
203            Output::Create(..) => {
204                panic!("Unexpected call to create contract during simulation.")
205            }
206        },
207        ExecutionResult::Revert { output, .. } => match checked {
208            true => panic!(
209                "Failed to call {:?} from {} due to revert: {:?}",
210                function_selector,
211                sender,
212                decode_revert_reason(&output.0)
213            ),
214            false => Event {
215                success: true,
216                function_selector,
217                logs: Vec::default(),
218                step,
219                sequence,
220            },
221        },
222        ExecutionResult::Halt { reason, .. } => {
223            panic!(
224                "Failed to call {:?} from {} due to halt: {:?}",
225                function_selector, sender, reason
226            )
227        }
228    }
229}
230
231/// Convert an [ExecutionResult] to an [Output]
232///
233/// # Arguments
234///
235/// - `function_name` - Name of the function called
236/// - `sender` - Address of the transaction sender
237/// - `execution_result` - [ExecutionResult] returned
238///   from the transaction
239///
240/// # Panics
241///
242/// Panics if the transaction was halted
243///
244pub fn result_to_output(
245    function_name: &'static str,
246    sender: Address,
247    execution_result: ExecutionResult,
248) -> Result<(Output, Vec<Log>), RevertError> {
249    match execution_result {
250        ExecutionResult::Success { output, logs, .. } => Ok((output, logs)),
251        ExecutionResult::Revert { output, .. } => Err(RevertError {
252            function_name,
253            sender,
254            output: decode_revert_reason(&output),
255        }),
256        ExecutionResult::Halt { reason, .. } => {
257            panic!(
258                "Failed to call {} from {} due to halt: {:?}",
259                function_name, sender, reason
260            )
261        }
262    }
263}
264
265/// Decode data attached to an [Event]
266///
267/// # Arguments
268///
269/// - `event` - Simulation [Event]
270///
271/// # Panics
272///
273/// Panics if the data cannot be decoded for the given event
274///
275pub fn decode_event<T: SolEvent>(event: &Event) -> (usize, usize, Log<T>) {
276    let log = event.logs.last().unwrap();
277    let decoded_event = T::decode_log(log, false);
278
279    let decoded_event = match decoded_event {
280        Ok(e) => e,
281        Err(_) => panic!("Failed to decode event from {:?}", event.function_selector),
282    };
283
284    (event.step, event.sequence, decoded_event)
285}
286
287/// Filter and process a vector of simulation events
288///
289/// # Arguments
290///
291/// - events - Vector of simulation [Event]
292///
293pub fn process_events<S: SolCall, T: SolEvent>(events: &[Event]) -> Vec<(usize, usize, Log<T>)> {
294    let function_selector = S::SELECTOR;
295    events
296        .iter()
297        .filter(|x| x.function_selector == function_selector)
298        .map(decode_event)
299        .collect()
300}