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}
21
22#[derive(Debug, Clone)]
24pub struct EvmAbiFunction {
25 pub name: String,
27 pub name_snake_case: String,
29 pub signature: String,
32 pub selector_signature: String,
35 pub selector: String,
37}
38
39fn to_snake_case(name: &str) -> String {
41 let mut out = String::with_capacity(name.len() + 4);
42 for (i, ch) in name.char_indices() {
43 if ch.is_uppercase() && i != 0 {
44 out.push('_');
45 }
46 out.extend(ch.to_lowercase());
47 }
48 out
49}
50
51fn event_decode_signature(event: &alloy_json_abi::Event) -> String {
54 let full = event.full_signature();
55 full.strip_prefix("event ").unwrap_or(&full).to_string()
57}
58
59pub fn abi_events(json_str: &str) -> Result<Vec<EvmAbiEvent>> {
61 let abi: JsonAbi = serde_json::from_str(json_str).context("parse JSON ABI")?;
62 let mut events = Vec::new();
63 for event in abi.events() {
64 let selector = event.selector();
65 events.push(EvmAbiEvent {
66 name_snake_case: to_snake_case(&event.name),
67 name: event.name.clone(),
68 signature: event_decode_signature(event),
69 selector_signature: event.signature(),
70 topic0: format!("0x{}", faster_hex::hex_string(selector.as_slice())),
71 });
72 }
73 Ok(events)
74}
75
76pub fn abi_functions(json_str: &str) -> Result<Vec<EvmAbiFunction>> {
78 let abi: JsonAbi = serde_json::from_str(json_str).context("parse JSON ABI")?;
79 let mut functions = Vec::new();
80 for func in abi.functions() {
81 let selector = func.selector();
82 functions.push(EvmAbiFunction {
83 name_snake_case: to_snake_case(&func.name),
84 name: func.name.clone(),
85 signature: func.full_signature(),
86 selector_signature: func.signature(),
87 selector: format!("0x{}", faster_hex::hex_string(selector.as_slice())),
88 });
89 }
90 Ok(functions)
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96 use crate::signature_to_topic0;
97
98 #[test]
99 fn test_abi_events() {
100 let abi_json = r#"[
101 {
102 "anonymous": false,
103 "inputs": [
104 {"indexed": true, "internalType": "address", "name": "sender", "type": "address"},
105 {"indexed": true, "internalType": "address", "name": "recipient", "type": "address"},
106 {"indexed": false, "internalType": "int256", "name": "amount0", "type": "int256"},
107 {"indexed": false, "internalType": "int256", "name": "amount1", "type": "int256"},
108 {"indexed": false, "internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160"},
109 {"indexed": false, "internalType": "uint128", "name": "liquidity", "type": "uint128"},
110 {"indexed": false, "internalType": "int24", "name": "tick", "type": "int24"}
111 ],
112 "name": "Swap",
113 "type": "event"
114 },
115 {
116 "inputs": [{"internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160"}],
117 "name": "initialize",
118 "outputs": [],
119 "stateMutability": "nonpayable",
120 "type": "function"
121 }
122 ]"#;
123
124 let events = abi_events(abi_json);
125 assert!(events.is_ok());
126 let events = events.unwrap_or_default();
127 assert_eq!(events.len(), 1);
128 assert_eq!(events[0].name, "Swap");
129 assert_eq!(
130 events[0].signature,
131 "Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick)"
132 );
133 assert_eq!(
134 events[0].selector_signature,
135 "Swap(address,address,int256,int256,uint160,uint128,int24)"
136 );
137 assert!(events[0].topic0.starts_with("0x"));
138 let topic0_from_sig = signature_to_topic0(&events[0].signature);
140 assert!(topic0_from_sig.is_ok());
141 }
142
143 #[test]
144 fn test_abi_functions() {
145 let abi_json = r#"[
146 {
147 "inputs": [
148 {"internalType": "address", "name": "recipient", "type": "address"},
149 {"internalType": "bool", "name": "zeroForOne", "type": "bool"},
150 {"internalType": "int256", "name": "amountSpecified", "type": "int256"},
151 {"internalType": "uint160", "name": "sqrtPriceLimitX96", "type": "uint160"},
152 {"internalType": "bytes", "name": "data", "type": "bytes"}
153 ],
154 "name": "swap",
155 "outputs": [
156 {"internalType": "int256", "name": "amount0", "type": "int256"},
157 {"internalType": "int256", "name": "amount1", "type": "int256"}
158 ],
159 "stateMutability": "nonpayable",
160 "type": "function"
161 },
162 {
163 "anonymous": false,
164 "inputs": [
165 {"indexed": true, "internalType": "address", "name": "sender", "type": "address"}
166 ],
167 "name": "Swap",
168 "type": "event"
169 }
170 ]"#;
171
172 let functions = abi_functions(abi_json);
173 assert!(functions.is_ok());
174 let functions = functions.unwrap_or_default();
175 assert_eq!(functions.len(), 1);
176 assert_eq!(functions[0].name, "swap");
177 assert_eq!(
178 functions[0].selector_signature,
179 "swap(address,bool,int256,uint160,bytes)"
180 );
181 assert!(functions[0].selector.starts_with("0x"));
182 assert_eq!(functions[0].selector.len(), 10); }
184
185 #[test]
186 fn test_abi_events_empty_abi() {
187 let events = abi_events("[]");
188 assert!(events.is_ok());
189 assert!(events.unwrap_or_default().is_empty());
190 }
191}