anvil_zksync_traces/
abi_utils.rs

1//! ABI related helper functions.
2//////////////////////////////////////////////////////////////////////////////////////
3// Attribution: File adapted from the `foundry-common` crate                        //
4//                                                                                  //
5// Full credit goes to its authors. See the original implementation here:           //
6// https://github.com/foundry-rs/foundry/blob/master/crates/common/src/abi.rs.      //
7//                                                                                  //
8// Note: These methods are used under the terms of the original project's license.  //
9//////////////////////////////////////////////////////////////////////////////////////
10
11use alloy::dyn_abi::{DynSolType, DynSolValue, FunctionExt, JsonAbiExt};
12use alloy::json_abi::{Error, Event, Function, Param};
13use alloy::primitives::hex;
14use anvil_zksync_types::traces::{DecodedValue, LogData};
15use eyre::{Context, Result};
16
17use crate::decode::decode_value;
18
19pub fn encode_args<I, S>(inputs: &[Param], args: I) -> Result<Vec<DynSolValue>>
20where
21    I: IntoIterator<Item = S>,
22    S: AsRef<str>,
23{
24    std::iter::zip(inputs, args)
25        .map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref()))
26        .collect()
27}
28
29/// Given a function and a vector of string arguments, it proceeds to convert the args to alloy
30/// [DynSolValue]s and then ABI encode them.
31pub fn encode_function_args<I, S>(func: &Function, args: I) -> Result<Vec<u8>>
32where
33    I: IntoIterator<Item = S>,
34    S: AsRef<str>,
35{
36    Ok(func.abi_encode_input(&encode_args(&func.inputs, args)?)?)
37}
38
39/// Given a function and a vector of string arguments, it proceeds to convert the args to alloy
40/// [DynSolValue]s and encode them using the packed encoding.
41pub fn encode_function_args_packed<I, S>(func: &Function, args: I) -> Result<Vec<u8>>
42where
43    I: IntoIterator<Item = S>,
44    S: AsRef<str>,
45{
46    let params: Vec<Vec<u8>> = std::iter::zip(&func.inputs, args)
47        .map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref()))
48        .collect::<Result<Vec<_>>>()?
49        .into_iter()
50        .map(|v| v.abi_encode_packed())
51        .collect();
52
53    Ok(params.concat())
54}
55
56/// Decodes the calldata of the function
57pub fn abi_decode_calldata(
58    sig: &str,
59    calldata: &str,
60    input: bool,
61    fn_selector: bool,
62) -> Result<Vec<DecodedValue>> {
63    let func = get_func(sig)?;
64    let calldata = hex::decode(calldata)?;
65
66    let mut calldata = calldata.as_slice();
67    // If function selector is prefixed in "calldata", remove it (first 4 bytes)
68    if input && fn_selector && calldata.len() >= 4 {
69        calldata = &calldata[4..];
70    }
71
72    let res = if input {
73        func.abi_decode_input(calldata, false)
74    } else {
75        func.abi_decode_output(calldata, false)
76    }?;
77
78    // in case the decoding worked but nothing was decoded
79    if res.is_empty() {
80        eyre::bail!("no data was decoded")
81    } else {
82        Ok(res.into_iter().map(decode_value).collect())
83    }
84}
85
86/// Given a function signature string, it tries to parse it as a `Function`
87pub fn get_func(sig: &str) -> Result<Function> {
88    Function::parse(sig).wrap_err("could not parse function signature")
89}
90
91/// Given an event signature string, it tries to parse it as a `Event`
92pub fn get_event(sig: &str) -> Result<Event> {
93    Event::parse(sig).wrap_err("could not parse event signature")
94}
95
96/// Given an error signature string, it tries to parse it as a `Error`
97pub fn get_error(sig: &str) -> Result<Error> {
98    Error::parse(sig).wrap_err("could not parse event signature")
99}
100
101/// Given an event without indexed parameters and a rawlog, it tries to return the event with the
102/// proper indexed parameters. Otherwise, it returns the original event.
103pub fn get_indexed_event(mut event: Event, raw_log: &LogData) -> Event {
104    if !event.anonymous && raw_log.topics().len() > 1 {
105        let indexed_params = raw_log.topics().len() - 1;
106        let num_inputs = event.inputs.len();
107        let num_address_params = event.inputs.iter().filter(|p| p.ty == "address").count();
108
109        event
110            .inputs
111            .iter_mut()
112            .enumerate()
113            .for_each(|(index, param)| {
114                if param.name.is_empty() {
115                    param.name = format!("param{index}");
116                }
117                if num_inputs == indexed_params
118                    || (num_address_params == indexed_params && param.ty == "address")
119                {
120                    param.indexed = true;
121                }
122            })
123    }
124    event
125}
126
127/// Helper function to coerce a value to a [DynSolValue] given a type string
128pub fn coerce_value(ty: &str, arg: &str) -> Result<DynSolValue> {
129    let ty = DynSolType::parse(ty)?;
130    Ok(DynSolType::coerce_str(&ty, arg)?)
131}
132
133#[cfg(test)]
134mod tests {
135    use crate::decode::{get_indexed_event_from_vm_event, vm_event_to_log_data};
136
137    use super::*;
138    use alloy::dyn_abi::EventExt;
139    use alloy::primitives::{Address, B256, U256};
140    use zksync_multivm::interface::VmEvent;
141    use zksync_types::H256;
142
143    #[test]
144    fn test_get_func() {
145        let func = get_func("function foo(uint256 a, uint256 b) returns (uint256)");
146        assert!(func.is_ok());
147        let func = func.unwrap();
148        assert_eq!(func.name, "foo");
149        assert_eq!(func.inputs.len(), 2);
150        assert_eq!(func.inputs[0].ty, "uint256");
151        assert_eq!(func.inputs[1].ty, "uint256");
152
153        // Stripped down function, which [Function] can parse.
154        let func = get_func("foo(bytes4 a, uint8 b)(bytes4)");
155        assert!(func.is_ok());
156        let func = func.unwrap();
157        assert_eq!(func.name, "foo");
158        assert_eq!(func.inputs.len(), 2);
159        assert_eq!(func.inputs[0].ty, "bytes4");
160        assert_eq!(func.inputs[1].ty, "uint8");
161        assert_eq!(func.outputs[0].ty, "bytes4");
162    }
163
164    #[test]
165    fn test_indexed_only_address_vm() {
166        let event = get_event("event Ev(address,uint256,address)").unwrap();
167
168        let param0 = B256::new([0u8; 32]);
169        let param2 = B256::new([0u8; 32]);
170        let param1_data = vec![3u8; 32];
171
172        let vm_event = VmEvent {
173            indexed_topics: vec![
174                H256::from_slice(&event.selector().0),
175                H256::from_slice(&param0.0),
176                H256::from_slice(&param2.0),
177            ],
178            value: param1_data.clone(),
179            ..Default::default()
180        };
181
182        // Convert the `Event` into its indexed form, matching the number of topics in `VmEvent`.
183        let updated_event = get_indexed_event_from_vm_event(event, &vm_event);
184        assert_eq!(updated_event.inputs.len(), 3);
185
186        // Now convert the VmEvent into a `LogData`
187        let log_data = vm_event_to_log_data(&vm_event);
188        let decoded = updated_event.decode_log(&log_data, false).unwrap();
189
190        assert_eq!(
191            updated_event
192                .inputs
193                .iter()
194                .filter(|param| param.indexed)
195                .count(),
196            2
197        );
198        assert_eq!(
199            decoded.indexed[0],
200            DynSolValue::Address(Address::from_word(param0))
201        );
202        assert_eq!(
203            decoded.body[0],
204            DynSolValue::Uint(U256::from_be_bytes([3u8; 32]), 256)
205        );
206        assert_eq!(
207            decoded.indexed[1],
208            DynSolValue::Address(Address::from_word(param2))
209        );
210    }
211}