1use crate::models::{
2 EVMReceiptLog, EVMTransaction, EVMTransactionReceipt, MatchConditions, Monitor,
3};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Deserialize, Serialize)]
8pub struct EVMMonitorMatch {
9 pub monitor: Monitor,
11
12 pub transaction: EVMTransaction,
14
15 pub receipt: Option<EVMTransactionReceipt>,
17
18 pub logs: Option<Vec<EVMReceiptLog>>,
20
21 pub network_slug: String,
23
24 pub matched_on: MatchConditions,
26
27 pub matched_on_args: Option<MatchArguments>,
29}
30
31#[derive(Debug, Clone, Deserialize, Serialize)]
33pub struct MatchParamsMap {
34 pub signature: String,
36
37 pub args: Option<Vec<MatchParamEntry>>,
39
40 pub hex_signature: Option<String>,
42}
43
44#[derive(Debug, Clone, Deserialize, Serialize)]
46pub struct MatchParamEntry {
47 pub name: String,
49
50 pub value: String,
52
53 pub indexed: bool,
55
56 pub kind: String,
58}
59
60#[derive(Debug, Clone, Deserialize, Serialize)]
62pub struct MatchArguments {
63 pub functions: Option<Vec<MatchParamsMap>>,
65
66 pub events: Option<Vec<MatchParamsMap>>,
68}
69
70#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
76pub struct ContractSpec(alloy::json_abi::JsonAbi);
77
78impl From<crate::models::ContractSpec> for ContractSpec {
80 fn from(spec: crate::models::ContractSpec) -> Self {
81 match spec {
82 crate::models::ContractSpec::EVM(evm_spec) => Self(evm_spec.0),
83 _ => Self(alloy::json_abi::JsonAbi::new()),
84 }
85 }
86}
87
88impl From<alloy::json_abi::JsonAbi> for ContractSpec {
90 fn from(spec: alloy::json_abi::JsonAbi) -> Self {
91 Self(spec)
92 }
93}
94
95impl From<serde_json::Value> for ContractSpec {
97 fn from(spec: serde_json::Value) -> Self {
98 let spec = serde_json::from_value(spec).unwrap_or_else(|e| {
99 tracing::error!("Error parsing contract spec: {:?}", e);
100 alloy::json_abi::JsonAbi::new()
101 });
102 Self(spec)
103 }
104}
105
106impl std::fmt::Display for ContractSpec {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 match serde_json::to_string(self) {
110 Ok(s) => write!(f, "{}", s),
111 Err(e) => {
112 tracing::error!("Error serializing contract spec: {:?}", e);
113 write!(f, "")
114 }
115 }
116 }
117}
118
119impl std::ops::Deref for ContractSpec {
121 type Target = alloy::json_abi::JsonAbi;
122
123 fn deref(&self) -> &Self::Target {
124 &self.0
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use crate::{
131 models::{ContractSpec as ModelsContractSpec, FunctionCondition, StellarContractSpec},
132 utils::tests::evm::{
133 monitor::MonitorBuilder, receipt::ReceiptBuilder, transaction::TransactionBuilder,
134 },
135 };
136
137 use super::*;
138 use alloy::primitives::{Address, B256, U256, U64};
139
140 #[test]
141 fn test_evm_monitor_match() {
142 let monitor = MonitorBuilder::new()
143 .name("TestMonitor")
144 .function("transfer(address,uint256)", None)
145 .build();
146
147 let transaction = TransactionBuilder::new()
148 .hash(B256::with_last_byte(1))
149 .nonce(U256::from(1))
150 .from(Address::ZERO)
151 .to(Address::ZERO)
152 .value(U256::ZERO)
153 .gas_price(U256::from(20))
154 .gas_limit(U256::from(21000))
155 .build();
156
157 let receipt = ReceiptBuilder::new()
158 .transaction_hash(B256::with_last_byte(1))
159 .transaction_index(0)
160 .from(Address::ZERO)
161 .to(Address::ZERO)
162 .gas_used(U256::from(21000))
163 .status(true)
164 .build();
165
166 let match_params = MatchParamsMap {
167 signature: "transfer(address,uint256)".to_string(),
168 args: Some(vec![
169 MatchParamEntry {
170 name: "to".to_string(),
171 value: "0x0000000000000000000000000000000000000000".to_string(),
172 kind: "address".to_string(),
173 indexed: false,
174 },
175 MatchParamEntry {
176 name: "amount".to_string(),
177 value: "1000000000000000000".to_string(),
178 kind: "uint256".to_string(),
179 indexed: false,
180 },
181 ]),
182 hex_signature: Some("0xa9059cbb".to_string()),
183 };
184
185 let monitor_match = EVMMonitorMatch {
186 monitor: monitor.clone(),
187 transaction: transaction.clone(),
188 receipt: Some(receipt.clone()),
189 logs: Some(receipt.logs.clone()),
190 network_slug: "ethereum_mainnet".to_string(),
191 matched_on: MatchConditions {
192 functions: vec![FunctionCondition {
193 signature: "transfer(address,uint256)".to_string(),
194 expression: None,
195 }],
196 events: vec![],
197 transactions: vec![],
198 },
199 matched_on_args: Some(MatchArguments {
200 functions: Some(vec![match_params]),
201 events: None,
202 }),
203 };
204
205 assert_eq!(monitor_match.monitor.name, "TestMonitor");
206 assert_eq!(monitor_match.transaction.hash, B256::with_last_byte(1));
207 assert_eq!(
208 monitor_match.receipt.as_ref().unwrap().status,
209 Some(U64::from(1))
210 );
211 assert_eq!(monitor_match.network_slug, "ethereum_mainnet");
212 assert_eq!(monitor_match.matched_on.functions.len(), 1);
213 assert_eq!(
214 monitor_match.matched_on.functions[0].signature,
215 "transfer(address,uint256)"
216 );
217
218 let matched_args = monitor_match.matched_on_args.unwrap();
219 let function_args = matched_args.functions.unwrap();
220 assert_eq!(function_args.len(), 1);
221 assert_eq!(function_args[0].signature, "transfer(address,uint256)");
222 assert_eq!(
223 function_args[0].hex_signature,
224 Some("0xa9059cbb".to_string())
225 );
226
227 let args = function_args[0].args.as_ref().unwrap();
228 assert_eq!(args.len(), 2);
229 assert_eq!(args[0].name, "to");
230 assert_eq!(args[0].kind, "address");
231 assert_eq!(args[1].name, "amount");
232 assert_eq!(args[1].kind, "uint256");
233 }
234
235 #[test]
236 fn test_match_arguments() {
237 let from_addr = Address::ZERO;
238 let to_addr = Address::with_last_byte(1);
239 let amount = U256::from(1000000000000000000u64);
240
241 let match_args = MatchArguments {
242 functions: Some(vec![MatchParamsMap {
243 signature: "transfer(address,uint256)".to_string(),
244 args: Some(vec![
245 MatchParamEntry {
246 name: "to".to_string(),
247 value: format!("{:#x}", to_addr),
248 kind: "address".to_string(),
249 indexed: false,
250 },
251 MatchParamEntry {
252 name: "amount".to_string(),
253 value: amount.to_string(),
254 kind: "uint256".to_string(),
255 indexed: false,
256 },
257 ]),
258 hex_signature: Some("0xa9059cbb".to_string()),
259 }]),
260 events: Some(vec![MatchParamsMap {
261 signature: "Transfer(address,address,uint256)".to_string(),
262 args: Some(vec![
263 MatchParamEntry {
264 name: "from".to_string(),
265 value: format!("{:#x}", from_addr),
266 kind: "address".to_string(),
267 indexed: true,
268 },
269 MatchParamEntry {
270 name: "to".to_string(),
271 value: format!("{:#x}", to_addr),
272 kind: "address".to_string(),
273 indexed: true,
274 },
275 MatchParamEntry {
276 name: "amount".to_string(),
277 value: amount.to_string(),
278 kind: "uint256".to_string(),
279 indexed: false,
280 },
281 ]),
282 hex_signature: Some(
283 "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
284 .to_string(),
285 ),
286 }]),
287 };
288
289 assert!(match_args.functions.is_some());
290 let functions = match_args.functions.unwrap();
291 assert_eq!(functions.len(), 1);
292 assert_eq!(functions[0].signature, "transfer(address,uint256)");
293 assert_eq!(functions[0].hex_signature, Some("0xa9059cbb".to_string()));
294
295 let function_args = functions[0].args.as_ref().unwrap();
296 assert_eq!(function_args.len(), 2);
297 assert_eq!(function_args[0].name, "to");
298 assert_eq!(function_args[0].kind, "address");
299 assert_eq!(function_args[1].name, "amount");
300 assert_eq!(function_args[1].kind, "uint256");
301
302 assert!(match_args.events.is_some());
303 let events = match_args.events.unwrap();
304 assert_eq!(events.len(), 1);
305 assert_eq!(events[0].signature, "Transfer(address,address,uint256)");
306 assert_eq!(
307 events[0].hex_signature,
308 Some("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef".to_string())
309 );
310
311 let event_args = events[0].args.as_ref().unwrap();
312 assert_eq!(event_args.len(), 3);
313 assert_eq!(event_args[0].name, "from");
314 assert!(event_args[0].indexed);
315 assert_eq!(event_args[1].name, "to");
316 assert!(event_args[1].indexed);
317 assert_eq!(event_args[2].name, "amount");
318 assert!(!event_args[2].indexed);
319 }
320
321 #[test]
322 fn test_contract_spec_from_json() {
323 let json_value = serde_json::json!([{
324 "type": "function",
325 "name": "transfer",
326 "inputs": [
327 {
328 "name": "to",
329 "type": "address",
330 "internalType": "address"
331 },
332 {
333 "name": "amount",
334 "type": "uint256",
335 "internalType": "uint256"
336 }
337 ],
338 "outputs": [],
339 "stateMutability": "nonpayable"
340 }]);
341
342 let contract_spec = ContractSpec::from(json_value);
343 let functions: Vec<_> = contract_spec.0.functions().collect();
344 assert!(!functions.is_empty());
345
346 let function = &functions[0];
347 assert_eq!(function.name, "transfer");
348 assert_eq!(function.inputs.len(), 2);
349 assert_eq!(function.inputs[0].name, "to");
350 assert_eq!(function.inputs[0].ty, "address");
351 assert_eq!(function.inputs[1].name, "amount");
352 assert_eq!(function.inputs[1].ty, "uint256");
353 }
354
355 #[test]
356 fn test_contract_spec_from_invalid_json() {
357 let invalid_json = serde_json::json!({
358 "invalid": "data"
359 });
360
361 let contract_spec = ContractSpec::from(invalid_json);
362 assert!(contract_spec.0.functions.is_empty());
363 }
364
365 #[test]
366 fn test_contract_spec_display() {
367 let json_value = serde_json::json!([{
368 "type": "function",
369 "name": "transfer",
370 "inputs": [
371 {
372 "name": "to",
373 "type": "address",
374 "internalType": "address"
375 }
376 ],
377 "outputs": [],
378 "stateMutability": "nonpayable"
379 }]);
380
381 let contract_spec = ContractSpec::from(json_value);
382 let display_str = format!("{}", contract_spec);
383 assert!(!display_str.is_empty());
384 assert!(display_str.contains("transfer"));
385 assert!(display_str.contains("address"));
386 }
387
388 #[test]
389 fn test_contract_spec_deref() {
390 let json_value = serde_json::json!([{
391 "type": "function",
392 "name": "transfer",
393 "inputs": [
394 {
395 "name": "to",
396 "type": "address",
397 "internalType": "address"
398 }
399 ],
400 "outputs": [],
401 "stateMutability": "nonpayable"
402 }]);
403
404 let contract_spec = ContractSpec::from(json_value);
405 let functions: Vec<_> = contract_spec.functions().collect();
406 assert!(!functions.is_empty());
407 assert_eq!(functions[0].name, "transfer");
408 }
409
410 #[test]
411 fn test_contract_spec_from_models() {
412 let json_value = serde_json::json!([{
413 "type": "function",
414 "name": "transfer",
415 "inputs": [
416 {
417 "name": "to",
418 "type": "address",
419 "internalType": "address"
420 }
421 ],
422 "outputs": [],
423 "stateMutability": "nonpayable"
424 }]);
425
426 let evm_spec = ContractSpec::from(json_value.clone());
427 let models_spec = ModelsContractSpec::EVM(evm_spec);
428 let converted_spec = ContractSpec::from(models_spec);
429
430 let functions: Vec<_> = converted_spec.functions().collect();
431 assert!(!functions.is_empty());
432 assert_eq!(functions[0].name, "transfer");
433 assert_eq!(functions[0].inputs.len(), 1);
434 assert_eq!(functions[0].inputs[0].name, "to");
435 assert_eq!(functions[0].inputs[0].ty, "address");
436
437 let stellar_spec = StellarContractSpec::from(vec![]);
438 let models_spec = ModelsContractSpec::Stellar(stellar_spec);
439 let converted_spec = ContractSpec::from(models_spec);
440 assert!(converted_spec.is_empty());
441 }
442}