1use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt, JsonAbiExt};
4use alloy_json_abi::{Error, Event, Function, 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 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_raw<I, S>(func: &Function, args: I) -> Result<Vec<u8>>
36where
37 I: IntoIterator<Item = S>,
38 S: AsRef<str>,
39{
40 Ok(func.abi_encode_input_raw(&encode_args(&func.inputs, args)?)?)
41}
42
43pub fn encode_function_args_packed<I, S>(func: &Function, args: I) -> Result<Vec<u8>>
46where
47 I: IntoIterator<Item = S>,
48 S: AsRef<str>,
49{
50 let params: Vec<Vec<u8>> = std::iter::zip(&func.inputs, args)
51 .map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref()))
52 .collect::<Result<Vec<_>>>()?
53 .into_iter()
54 .map(|v| v.abi_encode_packed())
55 .collect();
56
57 Ok(params.concat())
58}
59
60pub fn abi_decode_calldata(
62 sig: &str,
63 calldata: &str,
64 input: bool,
65 fn_selector: bool,
66) -> Result<Vec<DynSolValue>> {
67 let func = get_func(sig)?;
68 let calldata = hex::decode(calldata)?;
69
70 let mut calldata = calldata.as_slice();
71 if input && fn_selector && calldata.len() >= 4 {
73 calldata = &calldata[4..];
74 }
75
76 let res =
77 if input { func.abi_decode_input(calldata) } else { func.abi_decode_output(calldata) }?;
78
79 if res.is_empty() {
81 eyre::bail!("no data was decoded")
82 }
83
84 Ok(res)
85}
86
87pub fn get_func(sig: &str) -> Result<Function> {
89 Function::parse(sig).wrap_err("could not parse function signature")
90}
91
92pub fn get_event(sig: &str) -> Result<Event> {
94 Event::parse(sig).wrap_err("could not parse event signature")
95}
96
97pub fn get_error(sig: &str) -> Result<Error> {
99 Error::parse(sig).wrap_err("could not parse event signature")
100}
101
102pub fn get_indexed_event(mut event: Event, raw_log: &LogData) -> Event {
105 if !event.anonymous && raw_log.topics().len() > 1 {
106 let indexed_params = raw_log.topics().len() - 1;
107 let num_inputs = event.inputs.len();
108 let num_address_params = event.inputs.iter().filter(|p| p.ty == "address").count();
109
110 event.inputs.iter_mut().enumerate().for_each(|(index, param)| {
111 if param.name.is_empty() {
112 param.name = format!("param{index}");
113 }
114 if num_inputs == indexed_params
115 || (num_address_params == indexed_params && param.ty == "address")
116 {
117 param.indexed = true;
118 }
119 })
120 }
121 event
122}
123
124pub async fn get_func_etherscan(
127 function_name: &str,
128 contract: Address,
129 args: &[String],
130 chain: Chain,
131 etherscan_api_key: &str,
132 etherscan_api_version: EtherscanApiVersion,
133) -> Result<Function> {
134 let client = Client::new_with_api_version(chain, etherscan_api_key, etherscan_api_version)?;
135 let source = find_source(client, contract).await?;
136 let metadata = source.items.first().wrap_err("etherscan returned empty metadata")?;
137
138 let mut abi = metadata.abi()?;
139 let funcs = abi.functions.remove(function_name).unwrap_or_default();
140
141 for func in funcs {
142 let res = encode_function_args(&func, args);
143 if res.is_ok() {
144 return Ok(func);
145 }
146 }
147
148 Err(eyre::eyre!("Function not found in abi"))
149}
150
151pub fn find_source(
153 client: Client,
154 address: Address,
155) -> Pin<Box<dyn Future<Output = Result<ContractMetadata>>>> {
156 Box::pin(async move {
157 trace!(%address, "find Etherscan source");
158 let source = client.contract_source_code(address).await?;
159 let metadata = source.items.first().wrap_err("Etherscan returned no data")?;
160 if metadata.proxy == 0 {
161 Ok(source)
162 } else {
163 let implementation = metadata.implementation.unwrap();
164 sh_println!(
165 "Contract at {address} is a proxy, trying to fetch source at {implementation}..."
166 )?;
167 match find_source(client, implementation).await {
168 impl_source @ Ok(_) => impl_source,
169 Err(e) => {
170 let err = EtherscanError::ContractCodeNotVerified(address).to_string();
171 if e.to_string() == err {
172 error!(%err);
173 Ok(source)
174 } else {
175 Err(e)
176 }
177 }
178 }
179 }
180 })
181}
182
183pub fn coerce_value(ty: &str, arg: &str) -> Result<DynSolValue> {
185 let ty = DynSolType::parse(ty)?;
186 Ok(DynSolType::coerce_str(&ty, arg)?)
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use alloy_dyn_abi::EventExt;
193 use alloy_primitives::{B256, U256};
194
195 #[test]
196 fn test_get_func() {
197 let func = get_func("function foo(uint256 a, uint256 b) returns (uint256)");
198 assert!(func.is_ok());
199 let func = func.unwrap();
200 assert_eq!(func.name, "foo");
201 assert_eq!(func.inputs.len(), 2);
202 assert_eq!(func.inputs[0].ty, "uint256");
203 assert_eq!(func.inputs[1].ty, "uint256");
204
205 let func = get_func("foo(bytes4 a, uint8 b)(bytes4)");
207 assert!(func.is_ok());
208 let func = func.unwrap();
209 assert_eq!(func.name, "foo");
210 assert_eq!(func.inputs.len(), 2);
211 assert_eq!(func.inputs[0].ty, "bytes4");
212 assert_eq!(func.inputs[1].ty, "uint8");
213 assert_eq!(func.outputs[0].ty, "bytes4");
214 }
215
216 #[test]
217 fn test_indexed_only_address() {
218 let event = get_event("event Ev(address,uint256,address)").unwrap();
219
220 let param0 = B256::random();
221 let param1 = vec![3; 32];
222 let param2 = B256::random();
223 let log = LogData::new_unchecked(vec![event.selector(), param0, param2], param1.into());
224 let event = get_indexed_event(event, &log);
225
226 assert_eq!(event.inputs.len(), 3);
227
228 let parsed = event.decode_log(&log).unwrap();
230
231 assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 2);
232 assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0)));
233 assert_eq!(parsed.body[0], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256));
234 assert_eq!(parsed.indexed[1], DynSolValue::Address(Address::from_word(param2)));
235 }
236
237 #[test]
238 fn test_indexed_all() {
239 let event = get_event("event Ev(address,uint256,address)").unwrap();
240
241 let param0 = B256::random();
242 let param1 = vec![3; 32];
243 let param2 = B256::random();
244 let log = LogData::new_unchecked(
245 vec![event.selector(), param0, B256::from_slice(¶m1), param2],
246 vec![].into(),
247 );
248 let event = get_indexed_event(event, &log);
249
250 assert_eq!(event.inputs.len(), 3);
251
252 assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 3);
254 let parsed = event.decode_log(&log).unwrap();
255
256 assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0)));
257 assert_eq!(parsed.indexed[1], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256));
258 assert_eq!(parsed.indexed[2], DynSolValue::Address(Address::from_word(param2)));
259 }
260}