Skip to main content

foundry_common/
abi.rs

1//! ABI related helper functions.
2
3use alloy_chains::Chain;
4use alloy_dyn_abi::{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/// Given a function name, address, and args, tries to parse it as a `Function` by fetching the
158/// abi from etherscan. If the address is a proxy, fetches the ABI of the implementation contract.
159pub async fn get_func_etherscan(
160    function_name: &str,
161    contract: Address,
162    args: &[String],
163    chain: Chain,
164    etherscan_api_key: &str,
165    etherscan_api_url: Option<&str>,
166) -> Result<Function> {
167    let client = if let Some(api_url) = etherscan_api_url {
168        Client::builder()
169            .with_api_key(etherscan_api_key)
170            .with_api_url(api_url)?
171            .with_url(api_url)?
172            .build()?
173    } else {
174        Client::new(chain, etherscan_api_key)?
175    };
176    let source = find_source(client, contract).await?;
177    let metadata = source.items.first().wrap_err("etherscan returned empty metadata")?;
178
179    let mut abi = metadata.abi()?;
180    let funcs = abi.functions.remove(function_name).unwrap_or_default();
181
182    for func in funcs {
183        let res = encode_function_args(&func, args);
184        if res.is_ok() {
185            return Ok(func);
186        }
187    }
188
189    Err(eyre::eyre!("Function not found in abi"))
190}
191
192/// If the code at `address` is a proxy, recurse until we find the implementation.
193pub fn find_source(
194    client: Client,
195    address: Address,
196) -> Pin<Box<dyn Future<Output = Result<ContractMetadata>>>> {
197    Box::pin(async move {
198        trace!(%address, "find Etherscan source");
199        let source = client.contract_source_code(address).await?;
200        let metadata = source.items.first().wrap_err("Etherscan returned no data")?;
201        // `Proxy: 1` can still come with an empty `Implementation` (unresolved/unverified);
202        // treat that like "not a proxy" instead of panicking on `.unwrap()`.
203        let implementation = if metadata.proxy == 0 { None } else { metadata.implementation };
204        if let Some(implementation) = implementation {
205            sh_println!(
206                "Contract at {address} is a proxy, trying to fetch source at {implementation}..."
207            )?;
208            match find_source(client, implementation).await {
209                impl_source @ Ok(_) => impl_source,
210                Err(e)
211                    if matches!(
212                        e.downcast_ref::<EtherscanError>(),
213                        Some(EtherscanError::ContractCodeNotVerified(address)) if *address == implementation
214                    ) =>
215                {
216                    error!(%e);
217                    Ok(source)
218                }
219                Err(e) => Err(e),
220            }
221        } else {
222            if metadata.proxy != 0 {
223                error!(%address, "Etherscan reports this contract as a proxy but returned no implementation address");
224            }
225            Ok(source)
226        }
227    })
228}
229
230/// Helper function to coerce a value to a [DynSolValue] given a type string
231pub fn coerce_value(ty: &str, arg: &str) -> Result<DynSolValue> {
232    let ty = DynSolType::parse(ty)?;
233    Ok(DynSolType::coerce_str(&ty, arg)?)
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239    use alloy_dyn_abi::EventExt;
240    use alloy_primitives::{B256, U256};
241
242    /// `Proxy: 1` with an empty `Implementation` used to panic on `.unwrap()`
243    /// (real-world shape, see `foundry_block_explorers`' own `can_deserialize_address_opt` test).
244    #[test]
245    fn test_proxy_without_implementation_does_not_panic() {
246        use foundry_block_explorers::contract::Metadata;
247
248        let json = serde_json::json!({
249            "SourceCode": "// dummy",
250            "ABI": "[]",
251            "ContractName": "Dummy",
252            "CompilerVersion": "v0.8.0+commit.c7dfd78e",
253            "OptimizationUsed": "0",
254            "Runs": "200",
255            "ConstructorArguments": "",
256            "EVMVersion": "Default",
257            "Library": "",
258            "LicenseType": "None",
259            "Proxy": "1",
260            "Implementation": "",
261            "SwarmSource": ""
262        });
263
264        let metadata: Metadata =
265            serde_json::from_value(json).expect("realistic Etherscan payload must deserialize");
266
267        // This is exactly the combination that used to reach `.unwrap()` on `None`.
268        assert_eq!(metadata.proxy, 1, "Proxy: 1 must deserialize to a nonzero proxy flag");
269        assert_eq!(
270            metadata.implementation, None,
271            "an empty Implementation string must deserialize to None, not a parsed address"
272        );
273
274        // Must not panic: this is the exact decision `find_source` makes.
275        let implementation = if metadata.proxy == 0 { None } else { metadata.implementation };
276        assert_eq!(implementation, None);
277    }
278
279    #[test]
280    fn proxy_implementation_decision_follows_real_implementation() {
281        use alloy_primitives::address;
282        use foundry_block_explorers::contract::Metadata;
283
284        let json = serde_json::json!({
285            "SourceCode": "// dummy",
286            "ABI": "[]",
287            "ContractName": "Dummy",
288            "CompilerVersion": "v0.8.0+commit.c7dfd78e",
289            "OptimizationUsed": "0",
290            "Runs": "200",
291            "ConstructorArguments": "",
292            "EVMVersion": "Default",
293            "Library": "",
294            "LicenseType": "None",
295            "Proxy": "1",
296            "Implementation": "0x1F98431c8aD98523631AE4a59f267346ea31F984",
297            "SwarmSource": ""
298        });
299        let metadata: Metadata = serde_json::from_value(json).unwrap();
300
301        let implementation = if metadata.proxy == 0 { None } else { metadata.implementation };
302        assert_eq!(implementation, Some(address!("0x1F98431c8aD98523631AE4a59f267346ea31F984")));
303    }
304
305    #[test]
306    fn test_get_func() {
307        let func = get_func("function foo(uint256 a, uint256 b) returns (uint256)");
308        assert!(func.is_ok());
309        let func = func.unwrap();
310        assert_eq!(func.name, "foo");
311        assert_eq!(func.inputs.len(), 2);
312        assert_eq!(func.inputs[0].ty, "uint256");
313        assert_eq!(func.inputs[1].ty, "uint256");
314
315        // Stripped down function, which [Function] can parse.
316        let func = get_func("foo(bytes4 a, uint8 b)(bytes4)");
317        assert!(func.is_ok());
318        let func = func.unwrap();
319        assert_eq!(func.name, "foo");
320        assert_eq!(func.inputs.len(), 2);
321        assert_eq!(func.inputs[0].ty, "bytes4");
322        assert_eq!(func.inputs[1].ty, "uint8");
323        assert_eq!(func.outputs[0].ty, "bytes4");
324    }
325
326    #[test]
327    fn test_indexed_only_address() {
328        let event = get_event("event Ev(address,uint256,address)").unwrap();
329
330        let param0 = B256::random();
331        let param1 = vec![3; 32];
332        let param2 = B256::random();
333        let log = LogData::new_unchecked(vec![event.selector(), param0, param2], param1.into());
334        let event = get_indexed_event(event, &log);
335
336        assert_eq!(event.inputs.len(), 3);
337
338        // Only the address fields get indexed since total_params > num_indexed_params
339        let parsed = event.decode_log(&log).unwrap();
340
341        assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 2);
342        assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0)));
343        assert_eq!(parsed.body[0], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256));
344        assert_eq!(parsed.indexed[1], DynSolValue::Address(Address::from_word(param2)));
345    }
346
347    #[test]
348    fn test_indexed_all() {
349        let event = get_event("event Ev(address,uint256,address)").unwrap();
350
351        let param0 = B256::random();
352        let param1 = vec![3; 32];
353        let param2 = B256::random();
354        let log = LogData::new_unchecked(
355            vec![event.selector(), param0, B256::from_slice(&param1), param2],
356            vec![].into(),
357        );
358        let event = get_indexed_event(event, &log);
359
360        assert_eq!(event.inputs.len(), 3);
361
362        // All parameters get indexed since num_indexed_params == total_params
363        assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 3);
364        let parsed = event.decode_log(&log).unwrap();
365
366        assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0)));
367        assert_eq!(parsed.indexed[1], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256));
368        assert_eq!(parsed.indexed[2], DynSolValue::Address(Address::from_word(param2)));
369    }
370
371    #[test]
372    fn test_abi_decode_event_data_ignores_indexed() {
373        let event =
374            get_event("event Ev(uint256 indexed a, string b, address indexed c, uint256 d)")
375                .unwrap();
376        let data = DynSolValue::Tuple(vec![
377            DynSolValue::String("hello".into()),
378            DynSolValue::Uint(U256::from(42), 256),
379        ])
380        .abi_encode_params();
381
382        let decoded = abi_decode_event_data(&event, &data).unwrap();
383        assert_eq!(
384            decoded,
385            vec![DynSolValue::String("hello".into()), DynSolValue::Uint(U256::from(42), 256)]
386        );
387    }
388
389    #[test]
390    fn test_abi_decode_event_data_without_indexed() {
391        let event = get_event("event Ev(uint256 a, address b)").unwrap();
392        let addr = Address::random();
393        let data =
394            DynSolValue::Tuple(vec![DynSolValue::Uint(U256::ONE, 256), DynSolValue::Address(addr)])
395                .abi_encode_params();
396
397        let decoded = abi_decode_event_data(&event, &data).unwrap();
398        assert_eq!(decoded, vec![DynSolValue::Uint(U256::ONE, 256), DynSolValue::Address(addr)]);
399    }
400
401    #[test]
402    fn test_encode_args_length_validation() {
403        use alloy_json_abi::Param;
404
405        let params = vec![
406            Param {
407                name: "a".to_string(),
408                ty: "uint256".to_string(),
409                internal_type: None,
410                components: vec![],
411            },
412            Param {
413                name: "b".to_string(),
414                ty: "address".to_string(),
415                internal_type: None,
416                components: vec![],
417            },
418        ];
419
420        // Less arguments than parameters
421        let args = vec!["1"];
422        let res = encode_args(&params, &args);
423        assert!(res.is_err());
424        assert!(format!("{}", res.unwrap_err()).contains("encode length mismatch"));
425
426        // Exact number of arguments and parameters
427        let args = vec!["1", "0x0000000000000000000000000000000000000001"];
428        let res = encode_args(&params, &args);
429        assert!(res.is_ok());
430        let values = res.unwrap();
431        assert_eq!(values.len(), 2);
432
433        // More arguments than parameters
434        let args = vec!["1", "0x0000000000000000000000000000000000000001", "extra"];
435        let res = encode_args(&params, &args);
436        assert!(res.is_err());
437        assert!(format!("{}", res.unwrap_err()).contains("encode length mismatch"));
438    }
439}