1use alloy_json_abi::JsonAbi;
2use anyhow::{Context, Result};
3
4#[derive(Debug, Clone)]
6pub struct EvmAbiEvent {
7 pub name: String,
9 pub name_snake_case: String,
11 pub signature: String,
15 pub selector_signature: String,
18 pub topic0: String,
20 pub abi_json: String,
22}
23
24#[derive(Debug, Clone)]
26pub struct EvmAbiFunction {
27 pub name: String,
29 pub name_snake_case: String,
31 pub signature: String,
34 pub selector_signature: String,
37 pub selector: String,
39 pub abi_json: String,
41}
42
43fn to_snake_case(name: &str) -> String {
45 let mut out = String::with_capacity(name.len() + 4);
46 for (i, ch) in name.char_indices() {
47 if ch.is_uppercase() && i != 0 {
48 out.push('_');
49 }
50 out.extend(ch.to_lowercase());
51 }
52 out
53}
54
55fn param_type_str(ty: &str, components: &[alloy_json_abi::Param]) -> String {
58 if !components.is_empty() && ty.starts_with("tuple") {
59 let suffix = &ty["tuple".len()..]; let inner = components
61 .iter()
62 .map(|c| param_type_str(&c.ty, &c.components))
63 .collect::<Vec<_>>()
64 .join(",");
65 format!("tuple({inner}){suffix}")
66 } else {
67 ty.to_string()
68 }
69}
70
71fn event_decode_signature(event: &alloy_json_abi::Event) -> String {
76 let params = event
77 .inputs
78 .iter()
79 .map(|input| {
80 let ty = param_type_str(&input.ty, &input.components);
81 match (input.indexed, input.name.is_empty()) {
82 (true, false) => format!("{ty} indexed {}", input.name),
83 (true, true) => format!("{ty} indexed"),
84 (false, false) => format!("{ty} {}", input.name),
85 (false, true) => ty,
86 }
87 })
88 .collect::<Vec<_>>()
89 .join(", ");
90 format!("{}({params})", event.name)
91}
92
93fn func_decode_signature(func: &alloy_json_abi::Function) -> String {
97 let fmt_params = |params: &[alloy_json_abi::Param]| {
98 params
99 .iter()
100 .map(|p| {
101 let ty = param_type_str(&p.ty, &p.components);
102 if p.name.is_empty() {
103 ty
104 } else {
105 format!("{ty} {}", p.name)
106 }
107 })
108 .collect::<Vec<_>>()
109 .join(", ")
110 };
111 format!(
112 "{}({})({})",
113 func.name,
114 fmt_params(&func.inputs),
115 fmt_params(&func.outputs)
116 )
117}
118
119pub fn abi_events(json_str: &str) -> Result<Vec<EvmAbiEvent>> {
121 let abi: JsonAbi = serde_json::from_str(json_str).context("parse JSON ABI")?;
122 let mut events = Vec::new();
123 for event in abi.events() {
124 let selector = event.selector();
125 events.push(EvmAbiEvent {
126 name_snake_case: to_snake_case(&event.name),
127 name: event.name.clone(),
128 signature: event_decode_signature(event),
129 selector_signature: event.signature(),
130 topic0: format!("0x{}", faster_hex::hex_string(selector.as_slice())),
131 abi_json: serde_json::to_string(event)
132 .context("alloy_json_abi::Event serialization")?,
133 });
134 }
135 Ok(events)
136}
137
138pub fn abi_functions(json_str: &str) -> Result<Vec<EvmAbiFunction>> {
140 let abi: JsonAbi = serde_json::from_str(json_str).context("parse JSON ABI")?;
141 let mut functions = Vec::new();
142 for func in abi.functions() {
143 let selector = func.selector();
144 functions.push(EvmAbiFunction {
145 name_snake_case: to_snake_case(&func.name),
146 name: func.name.clone(),
147 signature: func_decode_signature(func),
148 selector_signature: func.signature(),
149 selector: format!("0x{}", faster_hex::hex_string(selector.as_slice())),
150 abi_json: serde_json::to_string(func)
151 .context("alloy_json_abi::Function serialization")?,
152 });
153 }
154 Ok(functions)
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use crate::signature_to_topic0;
161
162 #[test]
163 fn test_abi_events() {
164 let abi_json = r#"[
165 {
166 "anonymous": false,
167 "inputs": [
168 {"indexed": true, "internalType": "address", "name": "sender", "type": "address"},
169 {"indexed": true, "internalType": "address", "name": "recipient", "type": "address"},
170 {"indexed": false, "internalType": "int256", "name": "amount0", "type": "int256"},
171 {"indexed": false, "internalType": "int256", "name": "amount1", "type": "int256"},
172 {"indexed": false, "internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160"},
173 {"indexed": false, "internalType": "uint128", "name": "liquidity", "type": "uint128"},
174 {"indexed": false, "internalType": "int24", "name": "tick", "type": "int24"}
175 ],
176 "name": "Swap",
177 "type": "event"
178 },
179 {
180 "inputs": [{"internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160"}],
181 "name": "initialize",
182 "outputs": [],
183 "stateMutability": "nonpayable",
184 "type": "function"
185 }
186 ]"#;
187
188 let events = abi_events(abi_json);
189 assert!(events.is_ok());
190 let events = events.unwrap_or_default();
191 assert_eq!(events.len(), 1);
192 assert_eq!(events[0].name, "Swap");
193 assert_eq!(
194 events[0].signature,
195 "Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick)"
196 );
197 assert_eq!(
198 events[0].selector_signature,
199 "Swap(address,address,int256,int256,uint160,uint128,int24)"
200 );
201 assert!(events[0].topic0.starts_with("0x"));
202 let topic0_from_sig = signature_to_topic0(&events[0].signature);
204 assert!(topic0_from_sig.is_ok());
205 }
206
207 #[test]
208 fn test_abi_functions() {
209 let abi_json = r#"[
210 {
211 "inputs": [
212 {"internalType": "address", "name": "recipient", "type": "address"},
213 {"internalType": "bool", "name": "zeroForOne", "type": "bool"},
214 {"internalType": "int256", "name": "amountSpecified", "type": "int256"},
215 {"internalType": "uint160", "name": "sqrtPriceLimitX96", "type": "uint160"},
216 {"internalType": "bytes", "name": "data", "type": "bytes"}
217 ],
218 "name": "swap",
219 "outputs": [
220 {"internalType": "int256", "name": "amount0", "type": "int256"},
221 {"internalType": "int256", "name": "amount1", "type": "int256"}
222 ],
223 "stateMutability": "nonpayable",
224 "type": "function"
225 },
226 {
227 "anonymous": false,
228 "inputs": [
229 {"indexed": true, "internalType": "address", "name": "sender", "type": "address"}
230 ],
231 "name": "Swap",
232 "type": "event"
233 }
234 ]"#;
235
236 let functions = abi_functions(abi_json);
237 assert!(functions.is_ok());
238 let functions = functions.unwrap_or_default();
239 assert_eq!(functions.len(), 1);
240 assert_eq!(functions[0].name, "swap");
241 assert_eq!(
242 functions[0].selector_signature,
243 "swap(address,bool,int256,uint160,bytes)"
244 );
245 assert!(functions[0].selector.starts_with("0x"));
246 assert_eq!(functions[0].selector.len(), 10); }
248
249 #[test]
250 fn test_abi_events_empty_abi() {
251 let events = abi_events("[]");
252 assert!(events.is_ok());
253 assert!(events.unwrap_or_default().is_empty());
254 }
255}