foundry_common/
abi.rs

1//! ABI related helper functions.
2
3use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt, JsonAbiExt};
4use alloy_json_abi::{Error, Event, Function, JsonAbi, Param};
5use alloy_primitives::{Address, LogData, hex};
6use eyre::{Context, ContextCompat, Result};
7use foundry_block_explorers::{
8    Client, EtherscanApiVersion, contract::ContractMetadata, errors::EtherscanError,
9};
10use foundry_config::Chain;
11use std::pin::Pin;
12
13pub fn encode_args<I, S>(inputs: &[Param], args: I) -> Result<Vec<DynSolValue>>
14where
15    I: IntoIterator<Item = S>,
16    S: AsRef<str>,
17{
18    let args: Vec<S> = args.into_iter().collect();
19
20    if inputs.len() != args.len() {
21        eyre::bail!("encode length mismatch: expected {} types, got {}", inputs.len(), args.len())
22    }
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, prefixes the encoded data with the function selector.
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 then ABI encode them. Doesn't prefix the function selector.
41pub fn encode_function_args_raw<I, S>(func: &Function, args: I) -> Result<Vec<u8>>
42where
43    I: IntoIterator<Item = S>,
44    S: AsRef<str>,
45{
46    Ok(func.abi_encode_input_raw(&encode_args(&func.inputs, args)?)?)
47}
48
49/// Given a function and a vector of string arguments, it proceeds to convert the args to alloy
50/// [DynSolValue]s and encode them using the packed encoding.
51pub fn encode_function_args_packed<I, S>(func: &Function, args: I) -> Result<Vec<u8>>
52where
53    I: IntoIterator<Item = S>,
54    S: AsRef<str>,
55{
56    let args: Vec<S> = args.into_iter().collect();
57
58    if func.inputs.len() != args.len() {
59        eyre::bail!(
60            "encode length mismatch: expected {} types, got {}",
61            func.inputs.len(),
62            args.len(),
63        );
64    }
65
66    let params: Vec<Vec<u8>> = std::iter::zip(&func.inputs, args)
67        .map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref()))
68        .collect::<Result<Vec<_>>>()?
69        .into_iter()
70        .map(|v| v.abi_encode_packed())
71        .collect();
72
73    Ok(params.concat())
74}
75
76/// Decodes the calldata of the function
77pub fn abi_decode_calldata(
78    sig: &str,
79    calldata: &str,
80    input: bool,
81    fn_selector: bool,
82) -> Result<Vec<DynSolValue>> {
83    let func = get_func(sig)?;
84    let calldata = hex::decode(calldata)?;
85
86    let mut calldata = calldata.as_slice();
87    // If function selector is prefixed in "calldata", remove it (first 4 bytes)
88    if input && fn_selector && calldata.len() >= 4 {
89        calldata = &calldata[4..];
90    }
91
92    let res =
93        if input { func.abi_decode_input(calldata) } else { func.abi_decode_output(calldata) }?;
94
95    // in case the decoding worked but nothing was decoded
96    if res.is_empty() {
97        eyre::bail!("no data was decoded")
98    }
99
100    Ok(res)
101}
102
103/// Given a function signature string, it tries to parse it as a `Function`
104pub fn get_func(sig: &str) -> Result<Function> {
105    Function::parse(sig).wrap_err("could not parse function signature")
106}
107
108/// Given an event signature string, it tries to parse it as a `Event`
109pub fn get_event(sig: &str) -> Result<Event> {
110    Event::parse(sig).wrap_err("could not parse event signature")
111}
112
113/// Given an error signature string, it tries to parse it as a `Error`
114pub fn get_error(sig: &str) -> Result<Error> {
115    Error::parse(sig).wrap_err("could not parse event signature")
116}
117
118/// Given an event without indexed parameters and a rawlog, it tries to return the event with the
119/// proper indexed parameters. Otherwise, it returns the original event.
120pub fn get_indexed_event(mut event: Event, raw_log: &LogData) -> Event {
121    if !event.anonymous && raw_log.topics().len() > 1 {
122        let indexed_params = raw_log.topics().len() - 1;
123        let num_inputs = event.inputs.len();
124        let num_address_params = event.inputs.iter().filter(|p| p.ty == "address").count();
125
126        event.inputs.iter_mut().enumerate().for_each(|(index, param)| {
127            if param.name.is_empty() {
128                param.name = format!("param{index}");
129            }
130            if num_inputs == indexed_params
131                || (num_address_params == indexed_params && param.ty == "address")
132            {
133                param.indexed = true;
134            }
135        })
136    }
137    event
138}
139
140/// Fetches the ABI of a contract from Etherscan.
141pub async fn fetch_abi_from_etherscan(
142    address: Address,
143    config: &foundry_config::Config,
144) -> Result<Vec<(JsonAbi, String)>> {
145    let chain = config.chain.unwrap_or_default();
146    let api_version = config.get_etherscan_api_version(Some(chain));
147    let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default();
148    let client = Client::new_with_api_version(chain, api_key, api_version)?;
149    let source = client.contract_source_code(address).await?;
150    source.items.into_iter().map(|item| Ok((item.abi()?, item.contract_name))).collect()
151}
152
153/// Given a function name, address, and args, tries to parse it as a `Function` by fetching the
154/// abi from etherscan. If the address is a proxy, fetches the ABI of the implementation contract.
155pub async fn get_func_etherscan(
156    function_name: &str,
157    contract: Address,
158    args: &[String],
159    chain: Chain,
160    etherscan_api_key: &str,
161    etherscan_api_version: EtherscanApiVersion,
162) -> Result<Function> {
163    let client = Client::new_with_api_version(chain, etherscan_api_key, etherscan_api_version)?;
164    let source = find_source(client, contract).await?;
165    let metadata = source.items.first().wrap_err("etherscan returned empty metadata")?;
166
167    let mut abi = metadata.abi()?;
168    let funcs = abi.functions.remove(function_name).unwrap_or_default();
169
170    for func in funcs {
171        let res = encode_function_args(&func, args);
172        if res.is_ok() {
173            return Ok(func);
174        }
175    }
176
177    Err(eyre::eyre!("Function not found in abi"))
178}
179
180/// If the code at `address` is a proxy, recurse until we find the implementation.
181pub fn find_source(
182    client: Client,
183    address: Address,
184) -> Pin<Box<dyn Future<Output = Result<ContractMetadata>>>> {
185    Box::pin(async move {
186        trace!(%address, "find Etherscan source");
187        let source = client.contract_source_code(address).await?;
188        let metadata = source.items.first().wrap_err("Etherscan returned no data")?;
189        if metadata.proxy == 0 {
190            Ok(source)
191        } else {
192            let implementation = metadata.implementation.unwrap();
193            sh_println!(
194                "Contract at {address} is a proxy, trying to fetch source at {implementation}..."
195            )?;
196            match find_source(client, implementation).await {
197                impl_source @ Ok(_) => impl_source,
198                Err(e) => {
199                    let err = EtherscanError::ContractCodeNotVerified(address).to_string();
200                    if e.to_string() == err {
201                        error!(%err);
202                        Ok(source)
203                    } else {
204                        Err(e)
205                    }
206                }
207            }
208        }
209    })
210}
211
212/// Helper function to coerce a value to a [DynSolValue] given a type string
213pub fn coerce_value(ty: &str, arg: &str) -> Result<DynSolValue> {
214    let ty = DynSolType::parse(ty)?;
215    Ok(DynSolType::coerce_str(&ty, arg)?)
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221    use alloy_dyn_abi::EventExt;
222    use alloy_primitives::{B256, U256};
223
224    #[test]
225    fn test_get_func() {
226        let func = get_func("function foo(uint256 a, uint256 b) returns (uint256)");
227        assert!(func.is_ok());
228        let func = func.unwrap();
229        assert_eq!(func.name, "foo");
230        assert_eq!(func.inputs.len(), 2);
231        assert_eq!(func.inputs[0].ty, "uint256");
232        assert_eq!(func.inputs[1].ty, "uint256");
233
234        // Stripped down function, which [Function] can parse.
235        let func = get_func("foo(bytes4 a, uint8 b)(bytes4)");
236        assert!(func.is_ok());
237        let func = func.unwrap();
238        assert_eq!(func.name, "foo");
239        assert_eq!(func.inputs.len(), 2);
240        assert_eq!(func.inputs[0].ty, "bytes4");
241        assert_eq!(func.inputs[1].ty, "uint8");
242        assert_eq!(func.outputs[0].ty, "bytes4");
243    }
244
245    #[test]
246    fn test_indexed_only_address() {
247        let event = get_event("event Ev(address,uint256,address)").unwrap();
248
249        let param0 = B256::random();
250        let param1 = vec![3; 32];
251        let param2 = B256::random();
252        let log = LogData::new_unchecked(vec![event.selector(), param0, param2], param1.into());
253        let event = get_indexed_event(event, &log);
254
255        assert_eq!(event.inputs.len(), 3);
256
257        // Only the address fields get indexed since total_params > num_indexed_params
258        let parsed = event.decode_log(&log).unwrap();
259
260        assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 2);
261        assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0)));
262        assert_eq!(parsed.body[0], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256));
263        assert_eq!(parsed.indexed[1], DynSolValue::Address(Address::from_word(param2)));
264    }
265
266    #[test]
267    fn test_indexed_all() {
268        let event = get_event("event Ev(address,uint256,address)").unwrap();
269
270        let param0 = B256::random();
271        let param1 = vec![3; 32];
272        let param2 = B256::random();
273        let log = LogData::new_unchecked(
274            vec![event.selector(), param0, B256::from_slice(&param1), param2],
275            vec![].into(),
276        );
277        let event = get_indexed_event(event, &log);
278
279        assert_eq!(event.inputs.len(), 3);
280
281        // All parameters get indexed since num_indexed_params == total_params
282        assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 3);
283        let parsed = event.decode_log(&log).unwrap();
284
285        assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0)));
286        assert_eq!(parsed.indexed[1], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256));
287        assert_eq!(parsed.indexed[2], DynSolValue::Address(Address::from_word(param2)));
288    }
289
290    #[test]
291    fn test_encode_args_length_validation() {
292        use alloy_json_abi::Param;
293
294        let params = vec![
295            Param {
296                name: "a".to_string(),
297                ty: "uint256".to_string(),
298                internal_type: None,
299                components: vec![],
300            },
301            Param {
302                name: "b".to_string(),
303                ty: "address".to_string(),
304                internal_type: None,
305                components: vec![],
306            },
307        ];
308
309        // Less arguments than parameters
310        let args = vec!["1"];
311        let res = encode_args(&params, &args);
312        assert!(res.is_err());
313        assert!(format!("{}", res.unwrap_err()).contains("encode length mismatch"));
314
315        // Exact number of arguments and parameters
316        let args = vec!["1", "0x0000000000000000000000000000000000000001"];
317        let res = encode_args(&params, &args);
318        assert!(res.is_ok());
319        let values = res.unwrap();
320        assert_eq!(values.len(), 2);
321
322        // More arguments than parameters
323        let args = vec!["1", "0x0000000000000000000000000000000000000001", "extra"];
324        let res = encode_args(&params, &args);
325        assert!(res.is_err());
326        assert!(format!("{}", res.unwrap_err()).contains("encode length mismatch"));
327    }
328}