1use 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
29pub 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
39pub 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
49pub 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
76pub 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 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 if res.is_empty() {
97 eyre::bail!("no data was decoded")
98 }
99
100 Ok(res)
101}
102
103pub fn get_func(sig: &str) -> Result<Function> {
105 Function::parse(sig).wrap_err("could not parse function signature")
106}
107
108pub fn get_event(sig: &str) -> Result<Event> {
110 Event::parse(sig).wrap_err("could not parse event signature")
111}
112
113pub fn get_error(sig: &str) -> Result<Error> {
115 Error::parse(sig).wrap_err("could not parse event signature")
116}
117
118pub 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
140pub 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
153pub 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
180pub 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
212pub 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 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 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(¶m1), param2],
275 vec![].into(),
276 );
277 let event = get_indexed_event(event, &log);
278
279 assert_eq!(event.inputs.len(), 3);
280
281 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 let args = vec!["1"];
311 let res = encode_args(¶ms, &args);
312 assert!(res.is_err());
313 assert!(format!("{}", res.unwrap_err()).contains("encode length mismatch"));
314
315 let args = vec!["1", "0x0000000000000000000000000000000000000001"];
317 let res = encode_args(¶ms, &args);
318 assert!(res.is_ok());
319 let values = res.unwrap();
320 assert_eq!(values.len(), 2);
321
322 let args = vec!["1", "0x0000000000000000000000000000000000000001", "extra"];
324 let res = encode_args(¶ms, &args);
325 assert!(res.is_err());
326 assert!(format!("{}", res.unwrap_err()).contains("encode length mismatch"));
327 }
328}