1use 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
21pub 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
31pub 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
48pub 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 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 if res.is_empty() {
72 eyre::bail!("no data was decoded")
73 }
74
75 Ok(res)
76}
77
78pub fn get_func(sig: &str) -> Result<Function> {
80 Function::parse(sig).wrap_err("could not parse function signature")
81}
82
83pub fn get_event(sig: &str) -> Result<Event> {
85 Event::parse(sig).wrap_err("could not parse event signature")
86}
87
88pub fn get_error(sig: &str) -> Result<Error> {
90 Error::parse(sig).wrap_err("could not parse event signature")
91}
92
93pub 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
115pub 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
141pub 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
173pub 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 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 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(¶m1), param2],
236 vec![].into(),
237 );
238 let event = get_indexed_event(event, &log);
239
240 assert_eq!(event.inputs.len(), 3);
241
242 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}