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, Param};
5use alloy_primitives::{hex, Address, LogData};
6use eyre::{Context, ContextCompat, Result};
7use foundry_block_explorers::{contract::ContractMetadata, errors::EtherscanError, Client};
8use foundry_config::Chain;
9use std::{future::Future, 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    std::iter::zip(inputs, args)
17        .map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref()))
18        .collect()
19}
20
21/// Given a function and a vector of string arguments, it proceeds to convert the args to alloy
22/// [DynSolValue]s and then ABI encode them.
23pub fn encode_function_args<I, S>(func: &Function, args: I) -> Result<Vec<u8>>
24where
25    I: IntoIterator<Item = S>,
26    S: AsRef<str>,
27{
28    Ok(func.abi_encode_input(&encode_args(&func.inputs, args)?)?)
29}
30
31/// Given a function and a vector of string arguments, it proceeds to convert the args to alloy
32/// [DynSolValue]s and encode them using the packed encoding.
33pub fn encode_function_args_packed<I, S>(func: &Function, args: I) -> Result<Vec<u8>>
34where
35    I: IntoIterator<Item = S>,
36    S: AsRef<str>,
37{
38    let params: Vec<Vec<u8>> = std::iter::zip(&func.inputs, args)
39        .map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref()))
40        .collect::<Result<Vec<_>>>()?
41        .into_iter()
42        .map(|v| v.abi_encode_packed())
43        .collect();
44
45    Ok(params.concat())
46}
47
48/// Decodes the calldata of the function
49pub fn abi_decode_calldata(
50    sig: &str,
51    calldata: &str,
52    input: bool,
53    fn_selector: bool,
54) -> Result<Vec<DynSolValue>> {
55    let func = get_func(sig)?;
56    let calldata = hex::decode(calldata)?;
57
58    let mut calldata = calldata.as_slice();
59    // If function selector is prefixed in "calldata", remove it (first 4 bytes)
60    if input && fn_selector && calldata.len() >= 4 {
61        calldata = &calldata[4..];
62    }
63
64    let res = if input {
65        func.abi_decode_input(calldata, false)
66    } else {
67        func.abi_decode_output(calldata, false)
68    }?;
69
70    // in case the decoding worked but nothing was decoded
71    if res.is_empty() {
72        eyre::bail!("no data was decoded")
73    }
74
75    Ok(res)
76}
77
78/// Given a function signature string, it tries to parse it as a `Function`
79pub fn get_func(sig: &str) -> Result<Function> {
80    Function::parse(sig).wrap_err("could not parse function signature")
81}
82
83/// Given an event signature string, it tries to parse it as a `Event`
84pub fn get_event(sig: &str) -> Result<Event> {
85    Event::parse(sig).wrap_err("could not parse event signature")
86}
87
88/// Given an error signature string, it tries to parse it as a `Error`
89pub fn get_error(sig: &str) -> Result<Error> {
90    Error::parse(sig).wrap_err("could not parse event signature")
91}
92
93/// Given an event without indexed parameters and a rawlog, it tries to return the event with the
94/// proper indexed parameters. Otherwise, it returns the original event.
95pub fn get_indexed_event(mut event: Event, raw_log: &LogData) -> Event {
96    if !event.anonymous && raw_log.topics().len() > 1 {
97        let indexed_params = raw_log.topics().len() - 1;
98        let num_inputs = event.inputs.len();
99        let num_address_params = event.inputs.iter().filter(|p| p.ty == "address").count();
100
101        event.inputs.iter_mut().enumerate().for_each(|(index, param)| {
102            if param.name.is_empty() {
103                param.name = format!("param{index}");
104            }
105            if num_inputs == indexed_params ||
106                (num_address_params == indexed_params && param.ty == "address")
107            {
108                param.indexed = true;
109            }
110        })
111    }
112    event
113}
114
115/// Given a function name, address, and args, tries to parse it as a `Function` by fetching the
116/// abi from etherscan. If the address is a proxy, fetches the ABI of the implementation contract.
117pub async fn get_func_etherscan(
118    function_name: &str,
119    contract: Address,
120    args: &[String],
121    chain: Chain,
122    etherscan_api_key: &str,
123) -> Result<Function> {
124    let client = Client::new(chain, etherscan_api_key)?;
125    let source = find_source(client, contract).await?;
126    let metadata = source.items.first().wrap_err("etherscan returned empty metadata")?;
127
128    let mut abi = metadata.abi()?;
129    let funcs = abi.functions.remove(function_name).unwrap_or_default();
130
131    for func in funcs {
132        let res = encode_function_args(&func, args);
133        if res.is_ok() {
134            return Ok(func)
135        }
136    }
137
138    Err(eyre::eyre!("Function not found in abi"))
139}
140
141/// If the code at `address` is a proxy, recurse until we find the implementation.
142pub fn find_source(
143    client: Client,
144    address: Address,
145) -> Pin<Box<dyn Future<Output = Result<ContractMetadata>>>> {
146    Box::pin(async move {
147        trace!(%address, "find Etherscan source");
148        let source = client.contract_source_code(address).await?;
149        let metadata = source.items.first().wrap_err("Etherscan returned no data")?;
150        if metadata.proxy == 0 {
151            Ok(source)
152        } else {
153            let implementation = metadata.implementation.unwrap();
154            sh_println!(
155                "Contract at {address} is a proxy, trying to fetch source at {implementation}..."
156            )?;
157            match find_source(client, implementation).await {
158                impl_source @ Ok(_) => impl_source,
159                Err(e) => {
160                    let err = EtherscanError::ContractCodeNotVerified(address).to_string();
161                    if e.to_string() == err {
162                        error!(%err);
163                        Ok(source)
164                    } else {
165                        Err(e)
166                    }
167                }
168            }
169        }
170    })
171}
172
173/// Helper function to coerce a value to a [DynSolValue] given a type string
174pub fn coerce_value(ty: &str, arg: &str) -> Result<DynSolValue> {
175    let ty = DynSolType::parse(ty)?;
176    Ok(DynSolType::coerce_str(&ty, arg)?)
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182    use alloy_dyn_abi::EventExt;
183    use alloy_primitives::{B256, U256};
184
185    #[test]
186    fn test_get_func() {
187        let func = get_func("function foo(uint256 a, uint256 b) returns (uint256)");
188        assert!(func.is_ok());
189        let func = func.unwrap();
190        assert_eq!(func.name, "foo");
191        assert_eq!(func.inputs.len(), 2);
192        assert_eq!(func.inputs[0].ty, "uint256");
193        assert_eq!(func.inputs[1].ty, "uint256");
194
195        // Stripped down function, which [Function] can parse.
196        let func = get_func("foo(bytes4 a, uint8 b)(bytes4)");
197        assert!(func.is_ok());
198        let func = func.unwrap();
199        assert_eq!(func.name, "foo");
200        assert_eq!(func.inputs.len(), 2);
201        assert_eq!(func.inputs[0].ty, "bytes4");
202        assert_eq!(func.inputs[1].ty, "uint8");
203        assert_eq!(func.outputs[0].ty, "bytes4");
204    }
205
206    #[test]
207    fn test_indexed_only_address() {
208        let event = get_event("event Ev(address,uint256,address)").unwrap();
209
210        let param0 = B256::random();
211        let param1 = vec![3; 32];
212        let param2 = B256::random();
213        let log = LogData::new_unchecked(vec![event.selector(), param0, param2], param1.into());
214        let event = get_indexed_event(event, &log);
215
216        assert_eq!(event.inputs.len(), 3);
217
218        // Only the address fields get indexed since total_params > num_indexed_params
219        let parsed = event.decode_log(&log, false).unwrap();
220
221        assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 2);
222        assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0)));
223        assert_eq!(parsed.body[0], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256));
224        assert_eq!(parsed.indexed[1], DynSolValue::Address(Address::from_word(param2)));
225    }
226
227    #[test]
228    fn test_indexed_all() {
229        let event = get_event("event Ev(address,uint256,address)").unwrap();
230
231        let param0 = B256::random();
232        let param1 = vec![3; 32];
233        let param2 = B256::random();
234        let log = LogData::new_unchecked(
235            vec![event.selector(), param0, B256::from_slice(&param1), param2],
236            vec![].into(),
237        );
238        let event = get_indexed_event(event, &log);
239
240        assert_eq!(event.inputs.len(), 3);
241
242        // All parameters get indexed since num_indexed_params == total_params
243        assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 3);
244        let parsed = event.decode_log(&log, false).unwrap();
245
246        assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0)));
247        assert_eq!(parsed.indexed[1], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256));
248        assert_eq!(parsed.indexed[2], DynSolValue::Address(Address::from_word(param2)));
249    }
250}