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::{
8 contract::ContractMetadata, errors::EtherscanError, Client, EtherscanApiVersion,
9};
10use foundry_config::Chain;
11use std::{future::Future, 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 std::iter::zip(inputs, args)
19 .map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref()))
20 .collect()
21}
22
23pub fn encode_function_args<I, S>(func: &Function, args: I) -> Result<Vec<u8>>
26where
27 I: IntoIterator<Item = S>,
28 S: AsRef<str>,
29{
30 Ok(func.abi_encode_input(&encode_args(&func.inputs, args)?)?)
31}
32
33pub fn encode_function_args_packed<I, S>(func: &Function, args: I) -> Result<Vec<u8>>
36where
37 I: IntoIterator<Item = S>,
38 S: AsRef<str>,
39{
40 let params: Vec<Vec<u8>> = std::iter::zip(&func.inputs, args)
41 .map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref()))
42 .collect::<Result<Vec<_>>>()?
43 .into_iter()
44 .map(|v| v.abi_encode_packed())
45 .collect();
46
47 Ok(params.concat())
48}
49
50pub fn abi_decode_calldata(
52 sig: &str,
53 calldata: &str,
54 input: bool,
55 fn_selector: bool,
56) -> Result<Vec<DynSolValue>> {
57 let func = get_func(sig)?;
58 let calldata = hex::decode(calldata)?;
59
60 let mut calldata = calldata.as_slice();
61 if input && fn_selector && calldata.len() >= 4 {
63 calldata = &calldata[4..];
64 }
65
66 let res =
67 if input { func.abi_decode_input(calldata) } else { func.abi_decode_output(calldata) }?;
68
69 if res.is_empty() {
71 eyre::bail!("no data was decoded")
72 }
73
74 Ok(res)
75}
76
77pub fn get_func(sig: &str) -> Result<Function> {
79 Function::parse(sig).wrap_err("could not parse function signature")
80}
81
82pub fn get_event(sig: &str) -> Result<Event> {
84 Event::parse(sig).wrap_err("could not parse event signature")
85}
86
87pub fn get_error(sig: &str) -> Result<Error> {
89 Error::parse(sig).wrap_err("could not parse event signature")
90}
91
92pub fn get_indexed_event(mut event: Event, raw_log: &LogData) -> Event {
95 if !event.anonymous && raw_log.topics().len() > 1 {
96 let indexed_params = raw_log.topics().len() - 1;
97 let num_inputs = event.inputs.len();
98 let num_address_params = event.inputs.iter().filter(|p| p.ty == "address").count();
99
100 event.inputs.iter_mut().enumerate().for_each(|(index, param)| {
101 if param.name.is_empty() {
102 param.name = format!("param{index}");
103 }
104 if num_inputs == indexed_params ||
105 (num_address_params == indexed_params && param.ty == "address")
106 {
107 param.indexed = true;
108 }
109 })
110 }
111 event
112}
113
114pub async fn get_func_etherscan(
117 function_name: &str,
118 contract: Address,
119 args: &[String],
120 chain: Chain,
121 etherscan_api_key: &str,
122 etherscan_api_version: EtherscanApiVersion,
123) -> Result<Function> {
124 let client = Client::new_with_api_version(chain, etherscan_api_key, etherscan_api_version)?;
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).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).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}