1use crate::models::{
2 EVMReceiptLog, EVMTransaction, EVMTransactionReceipt, MatchConditions, Monitor,
3};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Deserialize, Serialize)]
8pub struct MonitorMatch {
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#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
133pub struct MonitorConfig {}
134
135#[cfg(test)]
136mod tests {
137 use crate::{
138 models::{ContractSpec as ModelsContractSpec, FunctionCondition, StellarContractSpec},
139 utils::tests::evm::{
140 monitor::MonitorBuilder, receipt::ReceiptBuilder, transaction::TransactionBuilder,
141 },
142 };
143
144 use super::*;
145 use alloy::primitives::{Address, B256, U256, U64};
146
147 #[test]
148 fn test_evm_monitor_match() {
149 let monitor = MonitorBuilder::new()
150 .name("TestMonitor")
151 .function("transfer(address,uint256)", None)
152 .build();
153
154 let transaction = TransactionBuilder::new()
155 .hash(B256::with_last_byte(1))
156 .nonce(U256::from(1))
157 .from(Address::ZERO)
158 .to(Address::ZERO)
159 .value(U256::ZERO)
160 .gas_price(U256::from(20))
161 .gas_limit(U256::from(21000))
162 .build();
163
164 let receipt = ReceiptBuilder::new()
165 .transaction_hash(B256::with_last_byte(1))
166 .transaction_index(0)
167 .from(Address::ZERO)
168 .to(Address::ZERO)
169 .gas_used(U256::from(21000))
170 .status(true)
171 .build();
172
173 let match_params = MatchParamsMap {
174 signature: "transfer(address,uint256)".to_string(),
175 args: Some(vec![
176 MatchParamEntry {
177 name: "to".to_string(),
178 value: "0x0000000000000000000000000000000000000000".to_string(),
179 kind: "address".to_string(),
180 indexed: false,
181 },
182 MatchParamEntry {
183 name: "amount".to_string(),
184 value: "1000000000000000000".to_string(),
185 kind: "uint256".to_string(),
186 indexed: false,
187 },
188 ]),
189 hex_signature: Some("0xa9059cbb".to_string()),
190 };
191
192 let monitor_match = MonitorMatch {
193 monitor: monitor.clone(),
194 transaction: transaction.clone(),
195 receipt: Some(receipt.clone()),
196 logs: Some(receipt.logs.clone()),
197 network_slug: "ethereum_mainnet".to_string(),
198 matched_on: MatchConditions {
199 functions: vec![FunctionCondition {
200 signature: "transfer(address,uint256)".to_string(),
201 expression: None,
202 }],
203 events: vec![],
204 transactions: vec![],
205 },
206 matched_on_args: Some(MatchArguments {
207 functions: Some(vec![match_params]),
208 events: None,
209 }),
210 };
211
212 assert_eq!(monitor_match.monitor.name, "TestMonitor");
213 assert_eq!(monitor_match.transaction.hash, B256::with_last_byte(1));
214 assert_eq!(
215 monitor_match.receipt.as_ref().unwrap().status,
216 Some(U64::from(1))
217 );
218 assert_eq!(monitor_match.network_slug, "ethereum_mainnet");
219 assert_eq!(monitor_match.matched_on.functions.len(), 1);
220 assert_eq!(
221 monitor_match.matched_on.functions[0].signature,
222 "transfer(address,uint256)"
223 );
224
225 let matched_args = monitor_match.matched_on_args.unwrap();
226 let function_args = matched_args.functions.unwrap();
227 assert_eq!(function_args.len(), 1);
228 assert_eq!(function_args[0].signature, "transfer(address,uint256)");
229 assert_eq!(
230 function_args[0].hex_signature,
231 Some("0xa9059cbb".to_string())
232 );
233
234 let args = function_args[0].args.as_ref().unwrap();
235 assert_eq!(args.len(), 2);
236 assert_eq!(args[0].name, "to");
237 assert_eq!(args[0].kind, "address");
238 assert_eq!(args[1].name, "amount");
239 assert_eq!(args[1].kind, "uint256");
240 }
241
242 #[test]
243 fn test_match_arguments() {
244 let from_addr = Address::ZERO;
245 let to_addr = Address::with_last_byte(1);
246 let amount = U256::from(1000000000000000000u64);
247
248 let match_args = MatchArguments {
249 functions: Some(vec![MatchParamsMap {
250 signature: "transfer(address,uint256)".to_string(),
251 args: Some(vec![
252 MatchParamEntry {
253 name: "to".to_string(),
254 value: format!("{:#x}", to_addr),
255 kind: "address".to_string(),
256 indexed: false,
257 },
258 MatchParamEntry {
259 name: "amount".to_string(),
260 value: amount.to_string(),
261 kind: "uint256".to_string(),
262 indexed: false,
263 },
264 ]),
265 hex_signature: Some("0xa9059cbb".to_string()),
266 }]),
267 events: Some(vec![MatchParamsMap {
268 signature: "Transfer(address,address,uint256)".to_string(),
269 args: Some(vec![
270 MatchParamEntry {
271 name: "from".to_string(),
272 value: format!("{:#x}", from_addr),
273 kind: "address".to_string(),
274 indexed: true,
275 },
276 MatchParamEntry {
277 name: "to".to_string(),
278 value: format!("{:#x}", to_addr),
279 kind: "address".to_string(),
280 indexed: true,
281 },
282 MatchParamEntry {
283 name: "amount".to_string(),
284 value: amount.to_string(),
285 kind: "uint256".to_string(),
286 indexed: false,
287 },
288 ]),
289 hex_signature: Some(
290 "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
291 .to_string(),
292 ),
293 }]),
294 };
295
296 assert!(match_args.functions.is_some());
297 let functions = match_args.functions.unwrap();
298 assert_eq!(functions.len(), 1);
299 assert_eq!(functions[0].signature, "transfer(address,uint256)");
300 assert_eq!(functions[0].hex_signature, Some("0xa9059cbb".to_string()));
301
302 let function_args = functions[0].args.as_ref().unwrap();
303 assert_eq!(function_args.len(), 2);
304 assert_eq!(function_args[0].name, "to");
305 assert_eq!(function_args[0].kind, "address");
306 assert_eq!(function_args[1].name, "amount");
307 assert_eq!(function_args[1].kind, "uint256");
308
309 assert!(match_args.events.is_some());
310 let events = match_args.events.unwrap();
311 assert_eq!(events.len(), 1);
312 assert_eq!(events[0].signature, "Transfer(address,address,uint256)");
313 assert_eq!(
314 events[0].hex_signature,
315 Some("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef".to_string())
316 );
317
318 let event_args = events[0].args.as_ref().unwrap();
319 assert_eq!(event_args.len(), 3);
320 assert_eq!(event_args[0].name, "from");
321 assert!(event_args[0].indexed);
322 assert_eq!(event_args[1].name, "to");
323 assert!(event_args[1].indexed);
324 assert_eq!(event_args[2].name, "amount");
325 assert!(!event_args[2].indexed);
326 }
327
328 #[test]
329 fn test_contract_spec_from_json() {
330 let json_value = serde_json::json!([{
331 "type": "function",
332 "name": "transfer",
333 "inputs": [
334 {
335 "name": "to",
336 "type": "address",
337 "internalType": "address"
338 },
339 {
340 "name": "amount",
341 "type": "uint256",
342 "internalType": "uint256"
343 }
344 ],
345 "outputs": [],
346 "stateMutability": "nonpayable"
347 }]);
348
349 let contract_spec = ContractSpec::from(json_value);
350 let functions: Vec<_> = contract_spec.0.functions().collect();
351 assert!(!functions.is_empty());
352
353 let function = &functions[0];
354 assert_eq!(function.name, "transfer");
355 assert_eq!(function.inputs.len(), 2);
356 assert_eq!(function.inputs[0].name, "to");
357 assert_eq!(function.inputs[0].ty, "address");
358 assert_eq!(function.inputs[1].name, "amount");
359 assert_eq!(function.inputs[1].ty, "uint256");
360 }
361
362 #[test]
363 fn test_contract_spec_from_invalid_json() {
364 let invalid_json = serde_json::json!({
365 "invalid": "data"
366 });
367
368 let contract_spec = ContractSpec::from(invalid_json);
369 assert!(contract_spec.0.functions.is_empty());
370 }
371
372 #[test]
373 fn test_contract_spec_display() {
374 let json_value = serde_json::json!([{
375 "type": "function",
376 "name": "transfer",
377 "inputs": [
378 {
379 "name": "to",
380 "type": "address",
381 "internalType": "address"
382 }
383 ],
384 "outputs": [],
385 "stateMutability": "nonpayable"
386 }]);
387
388 let contract_spec = ContractSpec::from(json_value);
389 let display_str = format!("{}", contract_spec);
390 assert!(!display_str.is_empty());
391 assert!(display_str.contains("transfer"));
392 assert!(display_str.contains("address"));
393 }
394
395 #[test]
396 fn test_contract_spec_deref() {
397 let json_value = serde_json::json!([{
398 "type": "function",
399 "name": "transfer",
400 "inputs": [
401 {
402 "name": "to",
403 "type": "address",
404 "internalType": "address"
405 }
406 ],
407 "outputs": [],
408 "stateMutability": "nonpayable"
409 }]);
410
411 let contract_spec = ContractSpec::from(json_value);
412 let functions: Vec<_> = contract_spec.functions().collect();
413 assert!(!functions.is_empty());
414 assert_eq!(functions[0].name, "transfer");
415 }
416
417 #[test]
418 fn test_contract_spec_from_models() {
419 let json_value = serde_json::json!([{
420 "type": "function",
421 "name": "transfer",
422 "inputs": [
423 {
424 "name": "to",
425 "type": "address",
426 "internalType": "address"
427 }
428 ],
429 "outputs": [],
430 "stateMutability": "nonpayable"
431 }]);
432
433 let evm_spec = ContractSpec::from(json_value.clone());
434 let models_spec = ModelsContractSpec::EVM(evm_spec);
435 let converted_spec = ContractSpec::from(models_spec);
436
437 let functions: Vec<_> = converted_spec.functions().collect();
438 assert!(!functions.is_empty());
439 assert_eq!(functions[0].name, "transfer");
440 assert_eq!(functions[0].inputs.len(), 1);
441 assert_eq!(functions[0].inputs[0].name, "to");
442 assert_eq!(functions[0].inputs[0].ty, "address");
443
444 let stellar_spec = StellarContractSpec::from(vec![]);
445 let models_spec = ModelsContractSpec::Stellar(stellar_spec);
446 let converted_spec = ContractSpec::from(models_spec);
447 assert!(converted_spec.is_empty());
448 }
449}