1use alloy_chains::Chain;
4use alloy_dyn_abi::{DecodedEvent, 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
27pub 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
37pub 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
47pub 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
74pub 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 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 if res.is_empty() {
95 eyre::bail!("no data was decoded");
96 }
97
98 Ok(res)
99}
100
101pub fn get_func(sig: &str) -> Result<Function> {
103 Function::parse(sig).wrap_err("could not parse function signature")
104}
105
106pub fn get_event(sig: &str) -> Result<Event> {
108 Event::parse(sig).wrap_err("could not parse event signature")
109}
110
111pub 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
130pub fn get_error(sig: &str) -> Result<Error> {
132 Error::parse(sig).wrap_err("could not parse error signature")
133}
134
135pub 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
157pub fn reconstruct_event_params(event: &Event, decoded: &DecodedEvent) -> Vec<DynSolValue> {
160 let mut indexed = 0;
161 let mut unindexed = 0;
162 let mut inputs = vec![];
163 for input in &event.inputs {
164 if input.indexed && indexed < decoded.indexed.len() {
168 inputs.push(decoded.indexed[indexed].clone());
169 indexed += 1;
170 } else if unindexed < decoded.body.len() {
171 inputs.push(decoded.body[unindexed].clone());
172 unindexed += 1;
173 }
174 }
175
176 inputs
177}
178
179pub async fn get_func_etherscan(
182 function_name: &str,
183 contract: Address,
184 args: &[String],
185 chain: Chain,
186 etherscan_api_key: &str,
187 etherscan_api_url: Option<&str>,
188) -> Result<Function> {
189 let client = if let Some(api_url) = etherscan_api_url {
190 Client::builder()
191 .with_api_key(etherscan_api_key)
192 .with_api_url(api_url)?
193 .with_url(api_url)?
194 .build()?
195 } else {
196 Client::new(chain, etherscan_api_key)?
197 };
198 let source = find_source(client, contract).await?;
199 let metadata = source.items.first().wrap_err("etherscan returned empty metadata")?;
200
201 let mut abi = metadata.abi()?;
202 let funcs = abi.functions.remove(function_name).unwrap_or_default();
203
204 for func in funcs {
205 let res = encode_function_args(&func, args);
206 if res.is_ok() {
207 return Ok(func);
208 }
209 }
210
211 Err(eyre::eyre!("Function not found in abi"))
212}
213
214pub fn find_source(
216 client: Client,
217 address: Address,
218) -> Pin<Box<dyn Future<Output = Result<ContractMetadata>>>> {
219 Box::pin(async move {
220 trace!(%address, "find Etherscan source");
221 let source = client.contract_source_code(address).await?;
222 let metadata = source.items.first().wrap_err("Etherscan returned no data")?;
223 let implementation = if metadata.proxy == 0 { None } else { metadata.implementation };
226 if let Some(implementation) = implementation {
227 sh_println!(
228 "Contract at {address} is a proxy, trying to fetch source at {implementation}..."
229 )?;
230 match find_source(client, implementation).await {
231 impl_source @ Ok(_) => impl_source,
232 Err(e)
233 if matches!(
234 e.downcast_ref::<EtherscanError>(),
235 Some(EtherscanError::ContractCodeNotVerified(address)) if *address == implementation
236 ) =>
237 {
238 error!(%e);
239 Ok(source)
240 }
241 Err(e) => Err(e),
242 }
243 } else {
244 if metadata.proxy != 0 {
245 error!(%address, "Etherscan reports this contract as a proxy but returned no implementation address");
246 }
247 Ok(source)
248 }
249 })
250}
251
252pub fn coerce_value(ty: &str, arg: &str) -> Result<DynSolValue> {
254 let ty = DynSolType::parse(ty)?;
255 Ok(DynSolType::coerce_str(&ty, arg)?)
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use alloy_dyn_abi::EventExt;
262 use alloy_primitives::{B256, U256};
263
264 #[test]
267 fn test_proxy_without_implementation_does_not_panic() {
268 use foundry_block_explorers::contract::Metadata;
269
270 let json = serde_json::json!({
271 "SourceCode": "// dummy",
272 "ABI": "[]",
273 "ContractName": "Dummy",
274 "CompilerVersion": "v0.8.0+commit.c7dfd78e",
275 "OptimizationUsed": "0",
276 "Runs": "200",
277 "ConstructorArguments": "",
278 "EVMVersion": "Default",
279 "Library": "",
280 "LicenseType": "None",
281 "Proxy": "1",
282 "Implementation": "",
283 "SwarmSource": ""
284 });
285
286 let metadata: Metadata =
287 serde_json::from_value(json).expect("realistic Etherscan payload must deserialize");
288
289 assert_eq!(metadata.proxy, 1, "Proxy: 1 must deserialize to a nonzero proxy flag");
291 assert_eq!(
292 metadata.implementation, None,
293 "an empty Implementation string must deserialize to None, not a parsed address"
294 );
295
296 let implementation = if metadata.proxy == 0 { None } else { metadata.implementation };
298 assert_eq!(implementation, None);
299 }
300
301 #[test]
302 fn proxy_implementation_decision_follows_real_implementation() {
303 use alloy_primitives::address;
304 use foundry_block_explorers::contract::Metadata;
305
306 let json = serde_json::json!({
307 "SourceCode": "// dummy",
308 "ABI": "[]",
309 "ContractName": "Dummy",
310 "CompilerVersion": "v0.8.0+commit.c7dfd78e",
311 "OptimizationUsed": "0",
312 "Runs": "200",
313 "ConstructorArguments": "",
314 "EVMVersion": "Default",
315 "Library": "",
316 "LicenseType": "None",
317 "Proxy": "1",
318 "Implementation": "0x1F98431c8aD98523631AE4a59f267346ea31F984",
319 "SwarmSource": ""
320 });
321 let metadata: Metadata = serde_json::from_value(json).unwrap();
322
323 let implementation = if metadata.proxy == 0 { None } else { metadata.implementation };
324 assert_eq!(implementation, Some(address!("0x1F98431c8aD98523631AE4a59f267346ea31F984")));
325 }
326
327 #[test]
328 fn test_get_func() {
329 let func = get_func("function foo(uint256 a, uint256 b) returns (uint256)");
330 assert!(func.is_ok());
331 let func = func.unwrap();
332 assert_eq!(func.name, "foo");
333 assert_eq!(func.inputs.len(), 2);
334 assert_eq!(func.inputs[0].ty, "uint256");
335 assert_eq!(func.inputs[1].ty, "uint256");
336
337 let func = get_func("foo(bytes4 a, uint8 b)(bytes4)");
339 assert!(func.is_ok());
340 let func = func.unwrap();
341 assert_eq!(func.name, "foo");
342 assert_eq!(func.inputs.len(), 2);
343 assert_eq!(func.inputs[0].ty, "bytes4");
344 assert_eq!(func.inputs[1].ty, "uint8");
345 assert_eq!(func.outputs[0].ty, "bytes4");
346 }
347
348 #[test]
349 fn test_indexed_only_address() {
350 let event = get_event("event Ev(address,uint256,address)").unwrap();
351
352 let param0 = B256::random();
353 let param1 = vec![3; 32];
354 let param2 = B256::random();
355 let log = LogData::new_unchecked(vec![event.selector(), param0, param2], param1.into());
356 let event = get_indexed_event(event, &log);
357
358 assert_eq!(event.inputs.len(), 3);
359
360 let parsed = event.decode_log(&log).unwrap();
362
363 assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 2);
364 assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0)));
365 assert_eq!(parsed.body[0], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256));
366 assert_eq!(parsed.indexed[1], DynSolValue::Address(Address::from_word(param2)));
367 }
368
369 #[test]
370 fn test_indexed_all() {
371 let event = get_event("event Ev(address,uint256,address)").unwrap();
372
373 let param0 = B256::random();
374 let param1 = vec![3; 32];
375 let param2 = B256::random();
376 let log = LogData::new_unchecked(
377 vec![event.selector(), param0, B256::from_slice(¶m1), param2],
378 vec![].into(),
379 );
380 let event = get_indexed_event(event, &log);
381
382 assert_eq!(event.inputs.len(), 3);
383
384 assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 3);
386 let parsed = event.decode_log(&log).unwrap();
387
388 assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0)));
389 assert_eq!(parsed.indexed[1], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256));
390 assert_eq!(parsed.indexed[2], DynSolValue::Address(Address::from_word(param2)));
391 }
392
393 #[test]
394 fn test_abi_decode_event_data_ignores_indexed() {
395 let event =
396 get_event("event Ev(uint256 indexed a, string b, address indexed c, uint256 d)")
397 .unwrap();
398 let data = DynSolValue::Tuple(vec![
399 DynSolValue::String("hello".into()),
400 DynSolValue::Uint(U256::from(42), 256),
401 ])
402 .abi_encode_params();
403
404 let decoded = abi_decode_event_data(&event, &data).unwrap();
405 assert_eq!(
406 decoded,
407 vec![DynSolValue::String("hello".into()), DynSolValue::Uint(U256::from(42), 256)]
408 );
409 }
410
411 #[test]
412 fn test_abi_decode_event_data_without_indexed() {
413 let event = get_event("event Ev(uint256 a, address b)").unwrap();
414 let addr = Address::random();
415 let data =
416 DynSolValue::Tuple(vec![DynSolValue::Uint(U256::ONE, 256), DynSolValue::Address(addr)])
417 .abi_encode_params();
418
419 let decoded = abi_decode_event_data(&event, &data).unwrap();
420 assert_eq!(decoded, vec![DynSolValue::Uint(U256::ONE, 256), DynSolValue::Address(addr)]);
421 }
422
423 #[test]
424 fn test_encode_args_length_validation() {
425 use alloy_json_abi::Param;
426
427 let params = vec![
428 Param {
429 name: "a".to_string(),
430 ty: "uint256".to_string(),
431 internal_type: None,
432 components: vec![],
433 },
434 Param {
435 name: "b".to_string(),
436 ty: "address".to_string(),
437 internal_type: None,
438 components: vec![],
439 },
440 ];
441
442 let args = vec!["1"];
444 let res = encode_args(¶ms, &args);
445 assert!(res.is_err());
446 assert!(format!("{}", res.unwrap_err()).contains("encode length mismatch"));
447
448 let args = vec!["1", "0x0000000000000000000000000000000000000001"];
450 let res = encode_args(¶ms, &args);
451 assert!(res.is_ok());
452 let values = res.unwrap();
453 assert_eq!(values.len(), 2);
454
455 let args = vec!["1", "0x0000000000000000000000000000000000000001", "extra"];
457 let res = encode_args(¶ms, &args);
458 assert!(res.is_err());
459 assert!(format!("{}", res.unwrap_err()).contains("encode length mismatch"));
460 }
461}