Skip to main content

foundry_common/
abi.rs

1//! ABI related helper functions.
2
3use alloy_chains::Chain;
4use alloy_dyn_abi::{DecodedEvent, DynSolType, DynSolValue, FunctionExt, JsonAbiExt, Specifier};
5use alloy_json_abi::{Error, Event, Function, Param};
6use alloy_primitives::{Address, LogData, hex};
7use eyre::{Context, ContextCompat, Result};
8use foundry_block_explorers::{Client, contract::ContractMetadata, errors::EtherscanError};
9use std::pin::Pin;
10
11pub fn encode_args<I, S>(inputs: &[Param], args: I) -> Result<Vec<DynSolValue>>
12where
13    I: IntoIterator<Item = S>,
14    S: AsRef<str>,
15{
16    let args: Vec<S> = args.into_iter().collect();
17
18    if inputs.len() != args.len() {
19        eyre::bail!("encode length mismatch: expected {} types, got {}", inputs.len(), args.len());
20    }
21
22    std::iter::zip(inputs, args)
23        .map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref()))
24        .collect()
25}
26
27/// Given a function and a vector of string arguments, it proceeds to convert the args to alloy
28/// [DynSolValue]s and then ABI encode them, prefixes the encoded data with the function selector.
29pub fn encode_function_args<I, S>(func: &Function, args: I) -> Result<Vec<u8>>
30where
31    I: IntoIterator<Item = S>,
32    S: AsRef<str>,
33{
34    Ok(func.abi_encode_input(&encode_args(&func.inputs, args)?)?)
35}
36
37/// Given a function and a vector of string arguments, it proceeds to convert the args to alloy
38/// [DynSolValue]s and then ABI encode them. Doesn't prefix the function selector.
39pub fn encode_function_args_raw<I, S>(func: &Function, args: I) -> Result<Vec<u8>>
40where
41    I: IntoIterator<Item = S>,
42    S: AsRef<str>,
43{
44    Ok(func.abi_encode_input_raw(&encode_args(&func.inputs, args)?)?)
45}
46
47/// Given a function and a vector of string arguments, it proceeds to convert the args to alloy
48/// [DynSolValue]s and encode them using the packed encoding.
49pub fn encode_function_args_packed<I, S>(func: &Function, args: I) -> Result<Vec<u8>>
50where
51    I: IntoIterator<Item = S>,
52    S: AsRef<str>,
53{
54    let args: Vec<S> = args.into_iter().collect();
55
56    if func.inputs.len() != args.len() {
57        eyre::bail!(
58            "encode length mismatch: expected {} types, got {}",
59            func.inputs.len(),
60            args.len(),
61        );
62    }
63
64    let params: Vec<Vec<u8>> = std::iter::zip(&func.inputs, args)
65        .map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref()))
66        .collect::<Result<Vec<_>>>()?
67        .into_iter()
68        .map(|v| v.abi_encode_packed())
69        .collect();
70
71    Ok(params.concat())
72}
73
74/// Decodes the calldata of the function
75pub fn abi_decode_calldata(
76    sig: &str,
77    calldata: &str,
78    input: bool,
79    fn_selector: bool,
80) -> Result<Vec<DynSolValue>> {
81    let func = get_func(sig)?;
82    let calldata = hex::decode(calldata)?;
83
84    let mut calldata = calldata.as_slice();
85    // If function selector is prefixed in "calldata", remove it (first 4 bytes)
86    if input && fn_selector && calldata.len() >= 4 {
87        calldata = &calldata[4..];
88    }
89
90    let res =
91        if input { func.abi_decode_input(calldata) } else { func.abi_decode_output(calldata) }?;
92
93    // in case the decoding worked but nothing was decoded
94    if res.is_empty() {
95        eyre::bail!("no data was decoded");
96    }
97
98    Ok(res)
99}
100
101/// Given a function signature string, it tries to parse it as a `Function`
102pub fn get_func(sig: &str) -> Result<Function> {
103    Function::parse(sig).wrap_err("could not parse function signature")
104}
105
106/// Given an event signature string, it tries to parse it as a `Event`
107pub fn get_event(sig: &str) -> Result<Event> {
108    Event::parse(sig).wrap_err("could not parse event signature")
109}
110
111/// ABI-decodes the non-indexed parameters (the log data) of an event.
112///
113/// Indexed parameters are stored in the log topics rather than in the data, so they are skipped.
114/// This allows decoding the data of a log without knowing its topics.
115pub fn abi_decode_event_data(event: &Event, data: &[u8]) -> Result<Vec<DynSolValue>> {
116    let body = DynSolType::Tuple(
117        event
118            .inputs
119            .iter()
120            .filter(|input| !input.indexed)
121            .map(|input| input.resolve())
122            .collect::<Result<_, _>>()?,
123    );
124    Ok(match body.abi_decode_sequence(data)? {
125        DynSolValue::Tuple(values) => values,
126        _ => unreachable!("body is a tuple"),
127    })
128}
129
130/// Given an error signature string, it tries to parse it as a `Error`
131pub fn get_error(sig: &str) -> Result<Error> {
132    Error::parse(sig).wrap_err("could not parse error signature")
133}
134
135/// Given an event without indexed parameters and a rawlog, it tries to return the event with the
136/// proper indexed parameters. Otherwise, it returns the original event.
137pub fn get_indexed_event(mut event: Event, raw_log: &LogData) -> Event {
138    if !event.anonymous && raw_log.topics().len() > 1 {
139        let indexed_params = raw_log.topics().len() - 1;
140        let num_inputs = event.inputs.len();
141        let num_address_params = event.inputs.iter().filter(|p| p.ty == "address").count();
142
143        event.inputs.iter_mut().enumerate().for_each(|(index, param)| {
144            if param.name.is_empty() {
145                param.name = format!("param{index}");
146            }
147            if num_inputs == indexed_params
148                || (num_address_params == indexed_params && param.ty == "address")
149            {
150                param.indexed = true;
151            }
152        })
153    }
154    event
155}
156
157/// Restore the order of the params of a decoded event,
158/// as Alloy returns the indexed and unindexed params separately.
159pub fn reconstruct_event_params(event: &Event, decoded: &DecodedEvent) -> Vec<DynSolValue> {
160    let mut indexed = 0;
161    let mut unindexed = 0;
162    let mut inputs = vec![];
163    for input in &event.inputs {
164        // Prevent panic of event `Transfer(from, to)` decoded with a signature
165        // `Transfer(address indexed from, address indexed to, uint256 indexed tokenId)` by making
166        // sure the event inputs is not higher than decoded indexed / un-indexed values.
167        if input.indexed && indexed < decoded.indexed.len() {
168            inputs.push(decoded.indexed[indexed].clone());
169            indexed += 1;
170        } else if unindexed < decoded.body.len() {
171            inputs.push(decoded.body[unindexed].clone());
172            unindexed += 1;
173        }
174    }
175
176    inputs
177}
178
179/// Given a function name, address, and args, tries to parse it as a `Function` by fetching the
180/// abi from etherscan. If the address is a proxy, fetches the ABI of the implementation contract.
181pub async fn get_func_etherscan(
182    function_name: &str,
183    contract: Address,
184    args: &[String],
185    chain: Chain,
186    etherscan_api_key: &str,
187    etherscan_api_url: Option<&str>,
188) -> Result<Function> {
189    let client = if let Some(api_url) = etherscan_api_url {
190        Client::builder()
191            .with_api_key(etherscan_api_key)
192            .with_api_url(api_url)?
193            .with_url(api_url)?
194            .build()?
195    } else {
196        Client::new(chain, etherscan_api_key)?
197    };
198    let source = find_source(client, contract).await?;
199    let metadata = source.items.first().wrap_err("etherscan returned empty metadata")?;
200
201    let mut abi = metadata.abi()?;
202    let funcs = abi.functions.remove(function_name).unwrap_or_default();
203
204    for func in funcs {
205        let res = encode_function_args(&func, args);
206        if res.is_ok() {
207            return Ok(func);
208        }
209    }
210
211    Err(eyre::eyre!("Function not found in abi"))
212}
213
214/// If the code at `address` is a proxy, recurse until we find the implementation.
215pub fn find_source(
216    client: Client,
217    address: Address,
218) -> Pin<Box<dyn Future<Output = Result<ContractMetadata>>>> {
219    Box::pin(async move {
220        trace!(%address, "find Etherscan source");
221        let source = client.contract_source_code(address).await?;
222        let metadata = source.items.first().wrap_err("Etherscan returned no data")?;
223        // `Proxy: 1` can still come with an empty `Implementation` (unresolved/unverified);
224        // treat that like "not a proxy" instead of panicking on `.unwrap()`.
225        let implementation = if metadata.proxy == 0 { None } else { metadata.implementation };
226        if let Some(implementation) = implementation {
227            sh_println!(
228                "Contract at {address} is a proxy, trying to fetch source at {implementation}..."
229            )?;
230            match find_source(client, implementation).await {
231                impl_source @ Ok(_) => impl_source,
232                Err(e)
233                    if matches!(
234                        e.downcast_ref::<EtherscanError>(),
235                        Some(EtherscanError::ContractCodeNotVerified(address)) if *address == implementation
236                    ) =>
237                {
238                    error!(%e);
239                    Ok(source)
240                }
241                Err(e) => Err(e),
242            }
243        } else {
244            if metadata.proxy != 0 {
245                error!(%address, "Etherscan reports this contract as a proxy but returned no implementation address");
246            }
247            Ok(source)
248        }
249    })
250}
251
252/// Helper function to coerce a value to a [DynSolValue] given a type string
253pub fn coerce_value(ty: &str, arg: &str) -> Result<DynSolValue> {
254    let ty = DynSolType::parse(ty)?;
255    Ok(DynSolType::coerce_str(&ty, arg)?)
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261    use alloy_dyn_abi::EventExt;
262    use alloy_primitives::{B256, U256};
263
264    /// `Proxy: 1` with an empty `Implementation` used to panic on `.unwrap()`
265    /// (real-world shape, see `foundry_block_explorers`' own `can_deserialize_address_opt` test).
266    #[test]
267    fn test_proxy_without_implementation_does_not_panic() {
268        use foundry_block_explorers::contract::Metadata;
269
270        let json = serde_json::json!({
271            "SourceCode": "// dummy",
272            "ABI": "[]",
273            "ContractName": "Dummy",
274            "CompilerVersion": "v0.8.0+commit.c7dfd78e",
275            "OptimizationUsed": "0",
276            "Runs": "200",
277            "ConstructorArguments": "",
278            "EVMVersion": "Default",
279            "Library": "",
280            "LicenseType": "None",
281            "Proxy": "1",
282            "Implementation": "",
283            "SwarmSource": ""
284        });
285
286        let metadata: Metadata =
287            serde_json::from_value(json).expect("realistic Etherscan payload must deserialize");
288
289        // This is exactly the combination that used to reach `.unwrap()` on `None`.
290        assert_eq!(metadata.proxy, 1, "Proxy: 1 must deserialize to a nonzero proxy flag");
291        assert_eq!(
292            metadata.implementation, None,
293            "an empty Implementation string must deserialize to None, not a parsed address"
294        );
295
296        // Must not panic: this is the exact decision `find_source` makes.
297        let implementation = if metadata.proxy == 0 { None } else { metadata.implementation };
298        assert_eq!(implementation, None);
299    }
300
301    #[test]
302    fn proxy_implementation_decision_follows_real_implementation() {
303        use alloy_primitives::address;
304        use foundry_block_explorers::contract::Metadata;
305
306        let json = serde_json::json!({
307            "SourceCode": "// dummy",
308            "ABI": "[]",
309            "ContractName": "Dummy",
310            "CompilerVersion": "v0.8.0+commit.c7dfd78e",
311            "OptimizationUsed": "0",
312            "Runs": "200",
313            "ConstructorArguments": "",
314            "EVMVersion": "Default",
315            "Library": "",
316            "LicenseType": "None",
317            "Proxy": "1",
318            "Implementation": "0x1F98431c8aD98523631AE4a59f267346ea31F984",
319            "SwarmSource": ""
320        });
321        let metadata: Metadata = serde_json::from_value(json).unwrap();
322
323        let implementation = if metadata.proxy == 0 { None } else { metadata.implementation };
324        assert_eq!(implementation, Some(address!("0x1F98431c8aD98523631AE4a59f267346ea31F984")));
325    }
326
327    #[test]
328    fn test_get_func() {
329        let func = get_func("function foo(uint256 a, uint256 b) returns (uint256)");
330        assert!(func.is_ok());
331        let func = func.unwrap();
332        assert_eq!(func.name, "foo");
333        assert_eq!(func.inputs.len(), 2);
334        assert_eq!(func.inputs[0].ty, "uint256");
335        assert_eq!(func.inputs[1].ty, "uint256");
336
337        // Stripped down function, which [Function] can parse.
338        let func = get_func("foo(bytes4 a, uint8 b)(bytes4)");
339        assert!(func.is_ok());
340        let func = func.unwrap();
341        assert_eq!(func.name, "foo");
342        assert_eq!(func.inputs.len(), 2);
343        assert_eq!(func.inputs[0].ty, "bytes4");
344        assert_eq!(func.inputs[1].ty, "uint8");
345        assert_eq!(func.outputs[0].ty, "bytes4");
346    }
347
348    #[test]
349    fn test_indexed_only_address() {
350        let event = get_event("event Ev(address,uint256,address)").unwrap();
351
352        let param0 = B256::random();
353        let param1 = vec![3; 32];
354        let param2 = B256::random();
355        let log = LogData::new_unchecked(vec![event.selector(), param0, param2], param1.into());
356        let event = get_indexed_event(event, &log);
357
358        assert_eq!(event.inputs.len(), 3);
359
360        // Only the address fields get indexed since total_params > num_indexed_params
361        let parsed = event.decode_log(&log).unwrap();
362
363        assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 2);
364        assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0)));
365        assert_eq!(parsed.body[0], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256));
366        assert_eq!(parsed.indexed[1], DynSolValue::Address(Address::from_word(param2)));
367    }
368
369    #[test]
370    fn test_indexed_all() {
371        let event = get_event("event Ev(address,uint256,address)").unwrap();
372
373        let param0 = B256::random();
374        let param1 = vec![3; 32];
375        let param2 = B256::random();
376        let log = LogData::new_unchecked(
377            vec![event.selector(), param0, B256::from_slice(&param1), param2],
378            vec![].into(),
379        );
380        let event = get_indexed_event(event, &log);
381
382        assert_eq!(event.inputs.len(), 3);
383
384        // All parameters get indexed since num_indexed_params == total_params
385        assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 3);
386        let parsed = event.decode_log(&log).unwrap();
387
388        assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0)));
389        assert_eq!(parsed.indexed[1], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256));
390        assert_eq!(parsed.indexed[2], DynSolValue::Address(Address::from_word(param2)));
391    }
392
393    #[test]
394    fn test_abi_decode_event_data_ignores_indexed() {
395        let event =
396            get_event("event Ev(uint256 indexed a, string b, address indexed c, uint256 d)")
397                .unwrap();
398        let data = DynSolValue::Tuple(vec![
399            DynSolValue::String("hello".into()),
400            DynSolValue::Uint(U256::from(42), 256),
401        ])
402        .abi_encode_params();
403
404        let decoded = abi_decode_event_data(&event, &data).unwrap();
405        assert_eq!(
406            decoded,
407            vec![DynSolValue::String("hello".into()), DynSolValue::Uint(U256::from(42), 256)]
408        );
409    }
410
411    #[test]
412    fn test_abi_decode_event_data_without_indexed() {
413        let event = get_event("event Ev(uint256 a, address b)").unwrap();
414        let addr = Address::random();
415        let data =
416            DynSolValue::Tuple(vec![DynSolValue::Uint(U256::ONE, 256), DynSolValue::Address(addr)])
417                .abi_encode_params();
418
419        let decoded = abi_decode_event_data(&event, &data).unwrap();
420        assert_eq!(decoded, vec![DynSolValue::Uint(U256::ONE, 256), DynSolValue::Address(addr)]);
421    }
422
423    #[test]
424    fn test_encode_args_length_validation() {
425        use alloy_json_abi::Param;
426
427        let params = vec![
428            Param {
429                name: "a".to_string(),
430                ty: "uint256".to_string(),
431                internal_type: None,
432                components: vec![],
433            },
434            Param {
435                name: "b".to_string(),
436                ty: "address".to_string(),
437                internal_type: None,
438                components: vec![],
439            },
440        ];
441
442        // Less arguments than parameters
443        let args = vec!["1"];
444        let res = encode_args(&params, &args);
445        assert!(res.is_err());
446        assert!(format!("{}", res.unwrap_err()).contains("encode length mismatch"));
447
448        // Exact number of arguments and parameters
449        let args = vec!["1", "0x0000000000000000000000000000000000000001"];
450        let res = encode_args(&params, &args);
451        assert!(res.is_ok());
452        let values = res.unwrap();
453        assert_eq!(values.len(), 2);
454
455        // More arguments than parameters
456        let args = vec!["1", "0x0000000000000000000000000000000000000001", "extra"];
457        let res = encode_args(&params, &args);
458        assert!(res.is_err());
459        assert!(format!("{}", res.unwrap_err()).contains("encode length mismatch"));
460    }
461}