1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::abi::AbiType;
6use crate::error::Error;
7use crate::node::{AztecNode, LogId, PublicLogFilter};
8use crate::tx::TxHash;
9use crate::types::{AztecAddress, Fr};
10use crate::wallet::EventMetadataDefinition;
11
12#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub struct PublicEventMetadata {
20 pub contract_address: AztecAddress,
22 pub tx_hash: Option<TxHash>,
24 pub block_number: u64,
26 pub log_index: u64,
28}
29
30#[derive(Clone, Debug, Serialize, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub struct PublicEvent<T> {
34 pub event: T,
36 pub metadata: PublicEventMetadata,
38}
39
40#[derive(Clone, Debug, Default, Serialize, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct PublicEventFilter {
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub tx_hash: Option<TxHash>,
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub from_block: Option<u64>,
53 #[serde(skip_serializing_if = "Option::is_none")]
55 pub to_block: Option<u64>,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub contract_address: Option<AztecAddress>,
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub after_log: Option<LogId>,
62}
63
64#[derive(Clone, Debug, Serialize, Deserialize)]
66#[serde(rename_all = "camelCase")]
67pub struct GetPublicEventsResult<T> {
68 pub events: Vec<PublicEvent<T>>,
70 pub max_logs_hit: bool,
72}
73
74fn decode_log_fields(
84 data: &[Fr],
85 event_metadata: &EventMetadataDefinition,
86) -> Result<BTreeMap<String, Fr>, Error> {
87 if data.is_empty() {
88 return Err(Error::Abi("log data is empty".into()));
89 }
90
91 let selector = *data.last().expect("non-empty");
93 if selector != event_metadata.event_selector.0 {
94 return Err(Error::Abi(format!(
95 "event selector mismatch: expected {}, got {}",
96 event_metadata.event_selector.0, selector
97 )));
98 }
99
100 let field_data = &data[..data.len() - 1];
102 let names: Vec<String> = match &event_metadata.abi_type {
103 AbiType::Struct { fields, .. } => {
104 if event_metadata.field_names.is_empty() {
105 fields.iter().map(|field| field.name.clone()).collect()
106 } else {
107 if event_metadata.field_names.len() != fields.len() {
108 return Err(Error::Abi(format!(
109 "event metadata field name count {} does not match abi struct field count {}",
110 event_metadata.field_names.len(),
111 fields.len()
112 )));
113 }
114 event_metadata.field_names.clone()
115 }
116 }
117 _ => event_metadata.field_names.clone(),
118 };
119
120 if field_data.len() < names.len() {
121 return Err(Error::Abi(format!(
122 "not enough fields in log data: expected at least {}, got {}",
123 names.len(),
124 field_data.len()
125 )));
126 }
127
128 let mut result = BTreeMap::new();
129 for (i, name) in names.iter().enumerate() {
130 result.insert(name.clone(), field_data[i]);
131 }
132 Ok(result)
133}
134
135pub async fn get_public_events(
147 node: &(impl AztecNode + ?Sized),
148 event_metadata: &EventMetadataDefinition,
149 filter: PublicEventFilter,
150) -> Result<GetPublicEventsResult<BTreeMap<String, Fr>>, Error> {
151 let log_filter = PublicLogFilter {
152 tx_hash: filter.tx_hash,
153 from_block: filter.from_block,
154 to_block: filter.to_block,
155 contract_address: filter.contract_address,
156 selector: None, after_log: filter.after_log,
158 };
159
160 let response = node.get_public_logs(log_filter).await?;
161
162 let mut events = Vec::with_capacity(response.logs.len());
163 for log in &response.logs {
164 if let Some(last) = log.data.last() {
167 if *last != event_metadata.event_selector.0 {
168 continue;
169 }
170 } else {
171 continue;
172 }
173 let decoded = decode_log_fields(&log.data, event_metadata)?;
174 events.push(PublicEvent {
175 event: decoded,
176 metadata: PublicEventMetadata {
177 contract_address: log.contract_address,
178 tx_hash: log.tx_hash,
179 block_number: log.block_number,
180 log_index: log.log_index,
181 },
182 });
183 }
184
185 Ok(GetPublicEventsResult {
186 events,
187 max_logs_hit: response.max_logs_hit,
188 })
189}
190
191pub use crate::wallet::{
198 EventMetadataDefinition as EventMetadata, PrivateEvent, PrivateEventFilter,
199 PrivateEventMetadata,
200};
201
202#[cfg(test)]
207#[allow(clippy::unwrap_used, clippy::expect_used, clippy::unimplemented)]
208mod tests {
209 use super::*;
210 use crate::abi::{AbiParameter, EventSelector};
211 use crate::node::{PublicLog, PublicLogsResponse};
212 use async_trait::async_trait;
213 use std::sync::Mutex;
214
215 use crate::node::{AztecNode, NodeInfo, PublicLogFilter};
216 use crate::tx::{TxHash, TxReceipt};
217
218 struct MockEventNode {
221 logs_response: Mutex<PublicLogsResponse>,
222 captured_filter: Mutex<Option<PublicLogFilter>>,
223 }
224
225 impl MockEventNode {
226 fn new(response: PublicLogsResponse) -> Self {
227 Self {
228 logs_response: Mutex::new(response),
229 captured_filter: Mutex::new(None),
230 }
231 }
232
233 fn captured_filter(&self) -> Option<PublicLogFilter> {
234 self.captured_filter.lock().unwrap().clone()
235 }
236 }
237
238 #[async_trait]
239 impl AztecNode for MockEventNode {
240 async fn get_node_info(&self) -> Result<NodeInfo, Error> {
241 unimplemented!("not needed for event tests")
242 }
243
244 async fn get_block_number(&self) -> Result<u64, Error> {
245 unimplemented!("not needed for event tests")
246 }
247
248 async fn get_proven_block_number(&self) -> Result<u64, Error> {
249 unimplemented!("not needed for event tests")
250 }
251
252 async fn get_tx_receipt(&self, _tx_hash: &TxHash) -> Result<TxReceipt, Error> {
253 unimplemented!("not needed for event tests")
254 }
255
256 async fn get_tx_effect(
257 &self,
258 _tx_hash: &TxHash,
259 ) -> Result<Option<serde_json::Value>, Error> {
260 unimplemented!("not needed for event tests")
261 }
262
263 async fn get_public_logs(
264 &self,
265 filter: PublicLogFilter,
266 ) -> Result<PublicLogsResponse, Error> {
267 *self.captured_filter.lock().unwrap() = Some(filter);
268 Ok(self.logs_response.lock().unwrap().clone())
269 }
270
271 async fn send_tx(&self, _tx: &serde_json::Value) -> Result<(), Error> {
272 unimplemented!("not needed for event tests")
273 }
274
275 async fn get_contract(
276 &self,
277 _address: &AztecAddress,
278 ) -> Result<Option<aztec_core::types::ContractInstanceWithAddress>, Error> {
279 unimplemented!("not needed for event tests")
280 }
281
282 async fn get_contract_class(&self, _id: &Fr) -> Result<Option<serde_json::Value>, Error> {
283 unimplemented!("not needed for event tests")
284 }
285
286 async fn get_block_header(&self, _block_number: u64) -> Result<serde_json::Value, Error> {
287 unimplemented!("not needed for event tests")
288 }
289 async fn get_block(&self, _block_number: u64) -> Result<Option<serde_json::Value>, Error> {
290 unimplemented!("not needed for event tests")
291 }
292 async fn get_note_hash_membership_witness(
293 &self,
294 _block_number: u64,
295 _note_hash: &Fr,
296 ) -> Result<Option<serde_json::Value>, Error> {
297 unimplemented!("not needed for event tests")
298 }
299 async fn get_nullifier_membership_witness(
300 &self,
301 _block_number: u64,
302 _nullifier: &Fr,
303 ) -> Result<Option<serde_json::Value>, Error> {
304 unimplemented!("not needed for event tests")
305 }
306 async fn get_low_nullifier_membership_witness(
307 &self,
308 _block_number: u64,
309 _nullifier: &Fr,
310 ) -> Result<Option<serde_json::Value>, Error> {
311 unimplemented!("not needed for event tests")
312 }
313 async fn get_public_storage_at(
314 &self,
315 _block_number: u64,
316 _contract: &AztecAddress,
317 _slot: &Fr,
318 ) -> Result<Fr, Error> {
319 unimplemented!("not needed for event tests")
320 }
321 async fn get_public_data_witness(
322 &self,
323 _block_number: u64,
324 _leaf_slot: &Fr,
325 ) -> Result<Option<serde_json::Value>, Error> {
326 unimplemented!("not needed for event tests")
327 }
328 async fn get_l1_to_l2_message_membership_witness(
329 &self,
330 _block_number: u64,
331 _entry_key: &Fr,
332 ) -> Result<Option<serde_json::Value>, Error> {
333 unimplemented!("not needed for event tests")
334 }
335 async fn simulate_public_calls(
336 &self,
337 _tx: &serde_json::Value,
338 _skip_fee_enforcement: bool,
339 ) -> Result<serde_json::Value, Error> {
340 unimplemented!("not needed for event tests")
341 }
342 async fn is_valid_tx(
343 &self,
344 _tx: &serde_json::Value,
345 ) -> Result<aztec_node_client::TxValidationResult, Error> {
346 unimplemented!("not needed for event tests")
347 }
348 async fn get_private_logs_by_tags(&self, _tags: &[Fr]) -> Result<serde_json::Value, Error> {
349 unimplemented!("not needed for event tests")
350 }
351 async fn get_public_logs_by_tags_from_contract(
352 &self,
353 _contract: &AztecAddress,
354 _tags: &[Fr],
355 ) -> Result<serde_json::Value, Error> {
356 unimplemented!("not needed for event tests")
357 }
358 async fn register_contract_function_signatures(
359 &self,
360 _signatures: &[String],
361 ) -> Result<(), Error> {
362 unimplemented!("not needed for event tests")
363 }
364 async fn get_block_hash_membership_witness(
365 &self,
366 _block_number: u64,
367 _block_hash: &Fr,
368 ) -> Result<Option<serde_json::Value>, Error> {
369 unimplemented!("not needed for event tests")
370 }
371 async fn find_leaves_indexes(
372 &self,
373 _block_number: u64,
374 _tree_id: &str,
375 _leaves: &[Fr],
376 ) -> Result<Vec<Option<u64>>, Error> {
377 unimplemented!("not needed for event tests")
378 }
379 async fn get_tx_by_hash(
380 &self,
381 _tx_hash: &TxHash,
382 ) -> Result<Option<serde_json::Value>, Error> {
383 unimplemented!("not needed for event tests")
384 }
385 }
386
387 fn sample_event_metadata() -> EventMetadataDefinition {
388 EventMetadataDefinition {
389 event_selector: EventSelector(Fr::from(42u64)),
390 abi_type: AbiType::Struct {
391 name: "Transfer".to_owned(),
392 fields: vec![
393 AbiParameter {
394 name: "amount".to_owned(),
395 typ: AbiType::Field,
396 visibility: None,
397 },
398 AbiParameter {
399 name: "sender".to_owned(),
400 typ: AbiType::Field,
401 visibility: None,
402 },
403 ],
404 },
405 field_names: vec!["amount".to_owned(), "sender".to_owned()],
406 }
407 }
408
409 fn make_log(selector: Fr, fields: Vec<Fr>, block: u64, index: u64) -> PublicLog {
410 let mut data = fields;
411 data.push(selector);
412 PublicLog {
413 contract_address: AztecAddress(Fr::from(1u64)),
414 data,
415 tx_hash: Some(TxHash::zero()),
416 block_number: block,
417 log_index: index,
418 }
419 }
420
421 #[test]
424 fn decode_log_fields_success() {
425 let meta = sample_event_metadata();
426 let data = vec![Fr::from(100u64), Fr::from(200u64), Fr::from(42u64)];
427
428 let decoded = decode_log_fields(&data, &meta).expect("decode");
429 assert_eq!(decoded.len(), 2);
430 assert_eq!(decoded["amount"], Fr::from(100u64));
431 assert_eq!(decoded["sender"], Fr::from(200u64));
432 }
433
434 #[test]
435 fn decode_log_fields_extra_fields_ignored() {
436 let meta = sample_event_metadata();
437 let data = vec![
438 Fr::from(100u64),
439 Fr::from(200u64),
440 Fr::from(300u64), Fr::from(42u64),
442 ];
443
444 let decoded = decode_log_fields(&data, &meta).expect("decode");
445 assert_eq!(decoded.len(), 2);
446 assert_eq!(decoded["amount"], Fr::from(100u64));
447 assert_eq!(decoded["sender"], Fr::from(200u64));
448 }
449
450 #[test]
451 fn decode_log_fields_selector_mismatch() {
452 let meta = sample_event_metadata();
453 let data = vec![Fr::from(100u64), Fr::from(200u64), Fr::from(99u64)];
454
455 let err = decode_log_fields(&data, &meta).unwrap_err();
456 assert!(matches!(err, Error::Abi(_)));
457 assert!(err.to_string().contains("selector mismatch"));
458 }
459
460 #[test]
461 fn decode_log_fields_insufficient_fields() {
462 let meta = sample_event_metadata();
463 let data = vec![Fr::from(100u64), Fr::from(42u64)]; let err = decode_log_fields(&data, &meta).unwrap_err();
466 assert!(matches!(err, Error::Abi(_)));
467 assert!(err.to_string().contains("not enough fields"));
468 }
469
470 #[test]
471 fn decode_log_fields_empty_data() {
472 let meta = sample_event_metadata();
473 let err = decode_log_fields(&[], &meta).unwrap_err();
474 assert!(matches!(err, Error::Abi(_)));
475 assert!(err.to_string().contains("empty"));
476 }
477
478 #[test]
479 fn decode_log_fields_no_field_names() {
480 let meta = EventMetadataDefinition {
481 event_selector: EventSelector(Fr::from(42u64)),
482 abi_type: AbiType::Struct {
483 name: "Transfer".to_owned(),
484 fields: vec![
485 AbiParameter {
486 name: "amount".to_owned(),
487 typ: AbiType::Field,
488 visibility: None,
489 },
490 AbiParameter {
491 name: "sender".to_owned(),
492 typ: AbiType::Field,
493 visibility: None,
494 },
495 ],
496 },
497 field_names: vec![],
498 };
499 let data = vec![Fr::from(100u64), Fr::from(200u64), Fr::from(42u64)];
500
501 let decoded = decode_log_fields(&data, &meta).expect("decode");
502 assert_eq!(decoded["amount"], Fr::from(100u64));
503 assert_eq!(decoded["sender"], Fr::from(200u64));
504 }
505
506 #[test]
507 fn decode_log_fields_mismatched_field_names_and_abi_fails() {
508 let meta = EventMetadataDefinition {
509 event_selector: EventSelector(Fr::from(42u64)),
510 abi_type: AbiType::Struct {
511 name: "Transfer".to_owned(),
512 fields: vec![AbiParameter {
513 name: "amount".to_owned(),
514 typ: AbiType::Field,
515 visibility: None,
516 }],
517 },
518 field_names: vec!["amount".to_owned(), "sender".to_owned()],
519 };
520 let data = vec![Fr::from(100u64), Fr::from(42u64)];
521
522 let err = decode_log_fields(&data, &meta).unwrap_err();
523 assert!(matches!(err, Error::Abi(_)));
524 assert!(err.to_string().contains("field name count"));
525 }
526
527 #[tokio::test]
530 async fn get_public_events_decodes_logs() {
531 let meta = sample_event_metadata();
532 let logs = vec![
533 make_log(
534 Fr::from(42u64),
535 vec![Fr::from(100u64), Fr::from(200u64)],
536 10,
537 0,
538 ),
539 make_log(
540 Fr::from(42u64),
541 vec![Fr::from(300u64), Fr::from(400u64)],
542 10,
543 1,
544 ),
545 ];
546 let node = MockEventNode::new(PublicLogsResponse {
547 logs,
548 max_logs_hit: false,
549 });
550
551 let result = get_public_events(&node, &meta, PublicEventFilter::default())
552 .await
553 .expect("get events");
554
555 assert_eq!(result.events.len(), 2);
556 assert!(!result.max_logs_hit);
557
558 assert_eq!(result.events[0].event["amount"], Fr::from(100u64));
559 assert_eq!(result.events[0].event["sender"], Fr::from(200u64));
560 assert_eq!(result.events[0].metadata.block_number, 10);
561 assert_eq!(result.events[0].metadata.log_index, 0);
562
563 assert_eq!(result.events[1].event["amount"], Fr::from(300u64));
564 assert_eq!(result.events[1].event["sender"], Fr::from(400u64));
565 assert_eq!(result.events[1].metadata.log_index, 1);
566 }
567
568 #[tokio::test]
569 async fn get_public_events_empty_response() {
570 let meta = sample_event_metadata();
571 let node = MockEventNode::new(PublicLogsResponse {
572 logs: vec![],
573 max_logs_hit: false,
574 });
575
576 let result = get_public_events(&node, &meta, PublicEventFilter::default())
577 .await
578 .expect("get events");
579
580 assert!(result.events.is_empty());
581 assert!(!result.max_logs_hit);
582 }
583
584 #[tokio::test]
585 async fn get_public_events_propagates_max_logs_hit() {
586 let meta = sample_event_metadata();
587 let node = MockEventNode::new(PublicLogsResponse {
588 logs: vec![make_log(
589 Fr::from(42u64),
590 vec![Fr::from(1u64), Fr::from(2u64)],
591 5,
592 0,
593 )],
594 max_logs_hit: true,
595 });
596
597 let result = get_public_events(&node, &meta, PublicEventFilter::default())
598 .await
599 .expect("get events");
600
601 assert_eq!(result.events.len(), 1);
602 assert!(result.max_logs_hit);
603 }
604
605 #[tokio::test]
606 async fn get_public_events_passes_filter_without_selector() {
607 let meta = sample_event_metadata();
608 let contract = AztecAddress(Fr::from(99u64));
609 let after = LogId {
610 block_number: 5,
611 log_index: 3,
612 };
613 let tx_hash =
614 TxHash::from_hex("0x0000000000000000000000000000000000000000000000000000000000000001")
615 .expect("valid tx hash");
616
617 let node = MockEventNode::new(PublicLogsResponse {
618 logs: vec![],
619 max_logs_hit: false,
620 });
621
622 let filter = PublicEventFilter {
623 tx_hash: Some(tx_hash),
624 from_block: Some(1),
625 to_block: Some(100),
626 contract_address: Some(contract),
627 after_log: Some(after.clone()),
628 };
629
630 let _result = get_public_events(&node, &meta, filter)
631 .await
632 .expect("get events");
633
634 let captured = node.captured_filter().expect("filter was captured");
635 assert_eq!(captured.tx_hash, Some(tx_hash));
636 assert_eq!(captured.from_block, Some(1));
637 assert_eq!(captured.to_block, Some(100));
638 assert_eq!(captured.contract_address, Some(contract));
639 assert_eq!(captured.after_log, Some(after));
640 assert_eq!(captured.selector, None);
641 }
642
643 #[tokio::test]
644 async fn get_public_events_decode_error_propagates() {
645 let meta = sample_event_metadata();
646 let logs = vec![make_log(Fr::from(42u64), vec![Fr::from(1u64)], 1, 0)];
648 let node = MockEventNode::new(PublicLogsResponse {
649 logs,
650 max_logs_hit: false,
651 });
652
653 let err = get_public_events(&node, &meta, PublicEventFilter::default())
654 .await
655 .unwrap_err();
656 assert!(matches!(err, Error::Abi(_)));
657 }
658
659 #[test]
662 fn public_event_filter_default_serializes_empty() {
663 let filter = PublicEventFilter::default();
664 let json = serde_json::to_value(&filter).unwrap();
665 assert_eq!(json, serde_json::json!({}));
666 }
667
668 #[test]
669 fn public_event_filter_with_fields() {
670 let tx_hash =
671 TxHash::from_hex("0x0000000000000000000000000000000000000000000000000000000000000002")
672 .expect("valid tx hash");
673 let filter = PublicEventFilter {
674 tx_hash: Some(tx_hash),
675 from_block: Some(10),
676 to_block: Some(20),
677 ..Default::default()
678 };
679 let json = serde_json::to_value(&filter).unwrap();
680 assert_eq!(json["txHash"], tx_hash.to_string());
681 assert_eq!(json["fromBlock"], 10);
682 assert_eq!(json["toBlock"], 20);
683 assert!(json.get("contractAddress").is_none());
684 assert!(json.get("afterLog").is_none());
685 }
686
687 #[test]
690 fn public_event_metadata_roundtrip() {
691 let meta = PublicEventMetadata {
692 contract_address: AztecAddress(Fr::from(1u64)),
693 tx_hash: Some(TxHash::zero()),
694 block_number: 42,
695 log_index: 7,
696 };
697 let json = serde_json::to_string(&meta).expect("serialize");
698 let decoded: PublicEventMetadata = serde_json::from_str(&json).expect("deserialize");
699 assert_eq!(decoded, meta);
700 }
701
702 #[test]
705 fn public_event_roundtrip() {
706 let mut fields = BTreeMap::new();
707 fields.insert("amount".to_owned(), Fr::from(100u64));
708 let event = PublicEvent {
709 event: fields,
710 metadata: PublicEventMetadata {
711 contract_address: AztecAddress(Fr::from(1u64)),
712 tx_hash: Some(TxHash::zero()),
713 block_number: 10,
714 log_index: 0,
715 },
716 };
717 let json = serde_json::to_string(&event).expect("serialize");
718 let decoded: PublicEvent<BTreeMap<String, Fr>> =
719 serde_json::from_str(&json).expect("deserialize");
720 assert_eq!(decoded.event["amount"], Fr::from(100u64));
721 assert_eq!(decoded.metadata.block_number, 10);
722 }
723
724 #[test]
727 fn get_public_events_result_roundtrip() {
728 let result: GetPublicEventsResult<BTreeMap<String, Fr>> = GetPublicEventsResult {
729 events: vec![],
730 max_logs_hit: false,
731 };
732 let json = serde_json::to_string(&result).expect("serialize");
733 let decoded: GetPublicEventsResult<BTreeMap<String, Fr>> =
734 serde_json::from_str(&json).expect("deserialize");
735 assert!(decoded.events.is_empty());
736 assert!(!decoded.max_logs_hit);
737 }
738}