simular_core/
abi.rs

1//!
2//! Parse contract ABIs to encode, decode contract calls
3//!
4use alloy_dyn_abi::{DynSolEvent, DynSolType, DynSolValue, Specifier};
5use alloy_json_abi::{ContractObject, Function, JsonAbi, StateMutability};
6use alloy_primitives::{Bytes, Log, LogData};
7use anyhow::{anyhow, bail, Result};
8use std::collections::BTreeMap;
9
10type EventMap = BTreeMap<std::string::String, Vec<alloy_json_abi::Event>>;
11
12///
13/// Wrapper around pre-processed Events to help extract log information.
14/// We flatten the structure of `events` in JsonAbi to make it easier to
15/// automatically decode Logs from a `transact/simulate`.
16///
17/// EventLog contains `DynSolEvent` to be used to decode log information
18#[derive(Debug)]
19pub struct EventLog {
20    /// the event name
21    pub name: String,
22    /// the decoder resolved from the original event
23    pub decoder: DynSolEvent,
24}
25
26impl EventLog {
27    /// Attempt to decode the log and return the event name and extracted values as
28    /// DynSolValues.
29    pub fn decode(&self, log: &LogData) -> Option<(String, DynSolValue)> {
30        if let Ok(r) = self.decoder.decode_log(log, true) {
31            let v = DynSolValue::Tuple([r.indexed, r.body].concat());
32            return Some((self.name.clone(), v));
33        }
34        None
35    }
36}
37
38pub struct ContractAbi {
39    /// alloy's json abi object
40    pub abi: JsonAbi,
41    /// optional contract bytecode
42    pub bytecode: Option<Bytes>,
43    /// Contract event information with a log decoder
44    pub events_logs: Vec<EventLog>,
45}
46
47// walk through the events in JsonAbi to flatten the
48// structure and convert to `EventLog`.
49fn convert_events(ev: &EventMap) -> Vec<EventLog> {
50    ev.iter()
51        .flat_map(|(k, v)| {
52            v.iter()
53                .map(|e| EventLog {
54                    name: k.clone(),
55                    decoder: e.resolve().unwrap(),
56                })
57                .collect::<Vec<EventLog>>()
58        })
59        .collect::<Vec<EventLog>>()
60}
61
62impl ContractAbi {
63    /// Parse the `abi` and `bytecode` from a compiled contract's json file.
64    /// Note: `raw` is un-parsed json.
65    pub fn from_full_json(raw: &str) -> Self {
66        let co =
67            serde_json::from_str::<ContractObject>(raw).expect("Abi: failed to parse abi to json");
68        if co.abi.is_none() {
69            panic!("Abi: ABI not found in file")
70        }
71        if co.bytecode.is_none() {
72            panic!("Abi: Bytecode not found in file")
73        }
74        let abi = co.abi.unwrap();
75        let evts = convert_events(&abi.events);
76        Self {
77            abi,
78            bytecode: co.bytecode,
79            events_logs: evts,
80        }
81    }
82
83    /// Parse the `abi` and `bytecode`
84    /// Note: `raw` is un-parsed json.
85    pub fn from_abi_bytecode(raw: &str, bytecode: Option<Vec<u8>>) -> Self {
86        let abi = serde_json::from_str::<JsonAbi>(raw).expect("Abi: failed to parse abi");
87        let evts = convert_events(&abi.events);
88        Self {
89            abi,
90            bytecode: bytecode.map(Bytes::from),
91            events_logs: evts,
92        }
93    }
94
95    /// Parse an ABI (without bytecode) from a `Vec` of contract function definitions.
96    /// See [human readable abi](https://docs.ethers.org/v5/api/utils/abi/formats/#abi-formats--human-readable-abi)
97    pub fn from_human_readable(input: Vec<&str>) -> Self {
98        let abi = JsonAbi::parse(input).expect("Abi: Invalid solidity function(s) format");
99        let evts = convert_events(&abi.events);
100        Self {
101            abi,
102            bytecode: None,
103            events_logs: evts,
104        }
105    }
106
107    /// Extract and decode logs from emitted events
108    pub fn extract_logs(&self, logs: Vec<Log>) -> Vec<(String, DynSolValue)> {
109        let mut results: Vec<(String, DynSolValue)> = Vec::new();
110        for log in logs {
111            for e in &self.events_logs {
112                if let Some(p) = e.decode(&log.data) {
113                    results.push(p);
114                }
115            }
116        }
117        results
118    }
119
120    /// Is there a function with the given name?
121    pub fn has_function(&self, name: &str) -> bool {
122        self.abi.functions.contains_key(name)
123    }
124
125    /// Does the ABI have a fallback?
126    pub fn has_fallback(&self) -> bool {
127        self.abi.fallback.is_some()
128    }
129
130    /// Does the ABI have a receive?
131    pub fn has_receive(&self) -> bool {
132        self.abi.receive.is_some()
133    }
134
135    /// Return the contract bytecode as a Vec
136    pub fn bytecode(&self) -> Option<Vec<u8>> {
137        self.bytecode.as_ref().map(|b| b.to_vec())
138    }
139
140    /// Encode the information needed to create a contract.  This will
141    /// concatenate the contract bytecode with any arguments required by
142    /// the constructor.  Note: `args` is a string of input arguments.  See
143    /// `encode_function` for more information.
144    pub fn encode_constructor(&self, args: &str) -> Result<(Vec<u8>, bool)> {
145        let bytecode = match self.bytecode() {
146            Some(b) => b,
147            _ => bail!("Abi: Missing contract bytecode!"),
148        };
149
150        let constructor = match &self.abi.constructor {
151            Some(c) => c,
152            _ => return Ok((bytecode, false)),
153        };
154
155        let types = constructor
156            .inputs
157            .iter()
158            .map(|i| i.resolve().unwrap())
159            .collect::<Vec<_>>();
160
161        let ty = DynSolType::Tuple(types);
162        let dynavalues = ty.coerce_str(args).map_err(|_| {
163            anyhow!("Abi: Error coercing the arguments for the constructor. Check the input argument(s)")
164        })?;
165        let encoded_args = dynavalues.abi_encode_params();
166        let is_payable = matches!(constructor.state_mutability, StateMutability::Payable);
167
168        Ok(([bytecode, encoded_args].concat(), is_payable))
169    }
170
171    fn extract(funcs: &Function, args: &str) -> Result<DynSolValue> {
172        let types = funcs
173            .inputs
174            .iter()
175            .map(|i| i.resolve().unwrap())
176            .collect::<Vec<_>>();
177        let ty = DynSolType::Tuple(types);
178        ty.coerce_str(args).map_err(|_| {
179            anyhow!(
180                "Abi: Error coercing the arguments for the function call. Check the input argument(s)"
181            )
182        })
183    }
184
185    /// Encode function information for use in a transaction. Note: `args` is a string
186    /// of input parameters that are parsed by alloy `DynSolType`'s  and converted into
187    /// `DynSolValue`s.   See [DynSolType.coerce_str()](https://docs.rs/alloy-dyn-abi/latest/alloy_dyn_abi/enum.DynSolType.html#method.coerce_str)
188    ///  
189    /// - `name` is the name of the function
190    /// - `args` string of input arguments
191    ///
192    /// ## Example
193    ///
194    /// `"(1, hello, (0x11111111111111111111111111111, 5))"`
195    ///
196    /// is parsed into an alloy `DynSolValue` ...tuple, U256, etc...
197    ///
198    /// Returns a tuple with:
199    /// - encoded function and args
200    /// - whether the function is payable
201    /// - and the output `DynSolType` that can be used to decode the result of the call
202    pub fn encode_function(
203        &self,
204        name: &str,
205        args: &str,
206    ) -> anyhow::Result<(Vec<u8>, bool, Option<DynSolType>)> {
207        let funcs = match self.abi.function(name) {
208            Some(funcs) => funcs,
209            _ => bail!("Abi: Function {} not found in the ABI!", name),
210        };
211
212        // find the first function that matches the input args
213        for f in funcs {
214            let result = Self::extract(f, args);
215            let is_payable = matches!(f.state_mutability, StateMutability::Payable);
216            if result.is_ok() {
217                // Get the return type decoder, if any...
218                let ty = match f.outputs.len() {
219                    0 => None,
220                    1 => f.outputs.first().unwrap().clone().resolve().ok(),
221                    _ => {
222                        let t = f
223                            .outputs
224                            .iter()
225                            .map(|i| i.resolve().unwrap())
226                            .collect::<Vec<_>>();
227                        Some(DynSolType::Tuple(t))
228                    }
229                };
230
231                let selector = f.selector().to_vec();
232                let encoded_args = result.unwrap().abi_encode_params();
233                let all = [selector, encoded_args].concat();
234
235                return Ok((all, is_payable, ty));
236            }
237        }
238
239        // if we get here, it means we didn't find a function that
240        // matched the input arguments
241        Err(anyhow::anyhow!(
242            "Abi: Arguments to the function do not match what is expected"
243        ))
244    }
245}
246
247#[cfg(test)]
248mod tests {
249
250    use super::*;
251    use alloy_primitives::{b256, bytes, Address, FixedBytes, LogData, U256};
252    use alloy_sol_types::{sol, SolCall};
253    use hex::FromHex;
254
255    sol! {
256
257        struct HelloInput {
258            uint256 value;
259            address owner;
260            uint160 beta;
261        }
262
263        contract HelloWorld {
264            address public owner;
265            function hello(HelloInput params) external returns (bool);
266        }
267    }
268
269    sol! {
270        contract MrOverLoads {
271            function one() public returns (bool);
272            function one(uint256);
273            function one(address, (uint64, uint64)) public returns (address);
274        }
275    }
276
277    sol! {
278        struct A {
279            uint256 value;
280            address owner;
281            bool isok;
282        }
283
284        struct B {
285            bytes data;
286        }
287
288        contract KitchenSink {
289            function check_types(uint256, bool, address, string, bytes32);
290            function check_both(A, B);
291            function check_blend(string, uint160, A);
292        }
293    }
294
295    #[test]
296    fn check_constructor_encoding() {
297        let input = vec!["constructor()"];
298        let mut abi = ContractAbi::from_human_readable(input);
299        // short-circuit internal check...
300        abi.bytecode = Some(b"hello".into());
301
302        assert!(abi.encode_constructor("()").is_ok());
303        assert!(abi.encode_constructor("(1234)").is_err());
304    }
305
306    #[test]
307    fn encoding_function_decoder_types() {
308        let tc = ContractAbi::from_human_readable(vec![
309            "function a()",
310            "function b() (uint256)",
311            "function c() (bool, address, uint256)",
312        ]);
313
314        let (_, _, r1) = tc.encode_function("a", "()").unwrap();
315        let (_, _, r2) = tc.encode_function("b", "()").unwrap();
316        let (_, _, r3) = tc.encode_function("c", "()").unwrap();
317
318        assert_eq!(None, r1);
319        assert_eq!(Some(DynSolType::Uint(256)), r2);
320        assert_eq!(
321            Some(DynSolType::Tuple(vec![
322                DynSolType::Bool,
323                DynSolType::Address,
324                DynSolType::Uint(256)
325            ])),
326            r3
327        );
328    }
329
330    #[test]
331    fn encoding_functions() {
332        let hello_world = vec!["function hello(tuple(uint256, address, uint160)) (bool)"];
333        let hw = ContractAbi::from_human_readable(hello_world);
334        assert!(hw.has_function("hello"));
335
336        let addy = Address::with_last_byte(24);
337        let solencoded = HelloWorld::helloCall {
338            params: HelloInput {
339                value: U256::from(10),
340                owner: addy,
341                beta: U256::from(1),
342            },
343        }
344        .abi_encode();
345
346        assert!(hw.encode_function("bob", "()").is_err());
347        assert!(hw.encode_function("hello", "(1,2").is_err());
348
349        let (cencoded, is_payable, dtype) = hw
350            .encode_function("hello", &format!("(({}, {}, {}))", 10, addy.to_string(), 1))
351            .unwrap();
352
353        assert!(!is_payable);
354        assert_eq!(solencoded, cencoded);
355        assert_eq!(dtype, Some(DynSolType::Bool));
356    }
357
358    #[test]
359    fn encoding_overloaded_functions() {
360        let overit = vec![
361            "function one() (bool)",
362            "function one(uint256)",
363            "function one(address, (uint64, uint64)) (address)",
364        ];
365        let abi = ContractAbi::from_human_readable(overit);
366        let addy = Address::with_last_byte(24);
367
368        let sa = MrOverLoads::one_0Call {}.abi_encode();
369        let (aa, _, _) = abi.encode_function("one", "()").unwrap();
370        assert_eq!(sa, aa);
371
372        let sb = MrOverLoads::one_1Call { _0: U256::from(1) }.abi_encode();
373        let (ab, _, _) = abi.encode_function("one", "(1)").unwrap();
374        assert_eq!(sb, ab);
375
376        let sc = MrOverLoads::one_2Call {
377            _0: addy,
378            _1: (10u64, 11u64),
379        }
380        .abi_encode();
381        let (ac, _, otype) = abi
382            .encode_function("one", &format!("({},({},{}))", addy.to_string(), 10, 11))
383            .unwrap();
384
385        assert_eq!(sc, ac);
386        assert_eq!(Some(DynSolType::Address), otype);
387    }
388
389    #[test]
390    fn encode_kitchen_sink() {
391        let addy = "0x023e09e337f5a6c82e62fe5ae4b6396d34930751";
392        // fixed bytes below
393        // "0101010101010101010101010101010101010101010101010101010101010101";
394
395        // encode with sol!
396        let expected_check_types = KitchenSink::check_typesCall {
397            _0: U256::from(1),
398            _1: true,
399            _2: Address::from_hex(addy).unwrap(),
400            _3: "bob".to_string(),
401            _4: FixedBytes::from_slice(&[1u8; 32]),
402        }
403        .abi_encode();
404
405        let abi = ContractAbi::from_human_readable(vec![
406            "function check_types(uint256, bool, address, string, bytes32)",
407            "function check_both(tuple(uint256, address, bool), tuple(bytes))",
408            "function check_blend(string, uint160, tuple(uint256, address, bool))",
409        ]);
410
411        // encode with abi: python input format
412        let input = "(1, true, 0x023e09e337f5a6c82e62fe5ae4b6396d34930751, 'bob', 0101010101010101010101010101010101010101010101010101010101010101)";
413        let (actual, _, _) = abi.encode_function("check_types", input).unwrap();
414        assert_eq!(expected_check_types, actual);
415
416        let expected_check_both = KitchenSink::check_bothCall {
417            _0: A {
418                value: U256::from(10),
419                owner: Address::from_hex(addy).unwrap(),
420                isok: false,
421            },
422            _1: B { data: Bytes::new() },
423        }
424        .abi_encode();
425
426        let input_both = "((10, 0x023e09e337f5a6c82e62fe5ae4b6396d34930751, false),(0x))";
427        let (actualboth, _, _) = abi.encode_function("check_both", input_both).unwrap();
428        assert_eq!(expected_check_both, actualboth);
429
430        let expected_check_blend = KitchenSink::check_blendCall {
431            _0: "bob".to_string(),
432            _1: U256::from(5),
433            _2: A {
434                value: U256::from(10),
435                owner: Address::from_hex(addy).unwrap(),
436                isok: false,
437            },
438        }
439        .abi_encode();
440
441        let input_blend = "(bob, 5,(10, 0x023e09e337f5a6c82e62fe5ae4b6396d34930751, false))";
442        let (actualblend, _, _) = abi.encode_function("check_blend", input_blend).unwrap();
443        assert_eq!(expected_check_blend, actualblend)
444    }
445
446    #[test]
447    fn test_flatten_event_structure() {
448        // mint signature: 0x0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d4121396885
449        // burn signature: 0xcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5
450        let sample = ContractAbi::from_human_readable(vec![
451            "event Transfer(address indexed from,address indexed to,uint256 amount)",
452            "event Transfer(address indexed from) anonymous",
453            "event Mint(address indexed recip,uint256 amount)",
454            "event Burn(address indexed recip,uint256 amount)",
455        ]);
456
457        assert_eq!(4, sample.events_logs.len());
458
459        let transfer = LogData::new_unchecked(
460            vec![
461                b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
462                b256!("000000000000000000000000c2e9f25be6257c210d7adf0d4cd6e3e881ba25f8"),
463                b256!("0000000000000000000000002b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b"),
464            ],
465            bytes!("0000000000000000000000000000000000000000000000000000000000000005"),
466        );
467
468        let burn = LogData::new_unchecked(
469            vec![
470                b256!("cc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5"),
471                b256!("000000000000000000000000c2e9f25be6257c210d7adf0d4cd6e3e881ba25f8"),
472            ],
473            bytes!("0000000000000000000000000000000000000000000000000000000000000005"),
474        );
475
476        let log_address = Address::repeat_byte(14);
477        let logs = vec![
478            Log {
479                address: log_address,
480                data: transfer,
481            },
482            Log {
483                address: log_address,
484                data: burn,
485            },
486        ];
487
488        let results = sample.extract_logs(logs);
489        assert_eq!(2, results.len());
490
491        //println!("{:?}", results);
492    }
493}