aztec_contract/
events.rs

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// ---------------------------------------------------------------------------
13// Public event types
14// ---------------------------------------------------------------------------
15
16/// Metadata attached to a decoded public event.
17#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub struct PublicEventMetadata {
20    /// Address of the contract that emitted the event.
21    pub contract_address: AztecAddress,
22    /// Hash of the transaction that emitted the event.
23    pub tx_hash: Option<TxHash>,
24    /// Block number containing the event.
25    pub block_number: u64,
26    /// Log index within the block.
27    pub log_index: u64,
28}
29
30/// A decoded public event with metadata.
31#[derive(Clone, Debug, Serialize, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub struct PublicEvent<T> {
34    /// Decoded event data.
35    pub event: T,
36    /// Event metadata (contract, block, index).
37    pub metadata: PublicEventMetadata,
38}
39
40/// Filter for querying public events from the node.
41///
42/// This wraps the underlying [`PublicLogFilter`] but omits the `selector`
43/// field which is provided automatically from the [`EventMetadataDefinition`].
44#[derive(Clone, Debug, Default, Serialize, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct PublicEventFilter {
47    /// Filter by transaction hash.
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub tx_hash: Option<TxHash>,
50    /// Start block (inclusive).
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub from_block: Option<u64>,
53    /// End block (inclusive).
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub to_block: Option<u64>,
56    /// Filter by emitting contract address.
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub contract_address: Option<AztecAddress>,
59    /// Cursor for pagination.
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub after_log: Option<LogId>,
62}
63
64/// Result of a public events query.
65#[derive(Clone, Debug, Serialize, Deserialize)]
66#[serde(rename_all = "camelCase")]
67pub struct GetPublicEventsResult<T> {
68    /// Decoded events.
69    pub events: Vec<PublicEvent<T>>,
70    /// Whether the response was truncated due to the log limit.
71    pub max_logs_hit: bool,
72}
73
74// ---------------------------------------------------------------------------
75// Decoding helpers
76// ---------------------------------------------------------------------------
77
78/// Decode a single public log's field-element data into named fields.
79///
80/// Noir's `emit_public_log` places the event selector at the **last** position:
81/// `[serialized_event_fields..., event_type_id]`.
82/// The field elements before the selector are mapped positionally to field names.
83fn 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    // Event selector is the last field element.
92    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    // Data fields are everything before the selector.
101    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
135// ---------------------------------------------------------------------------
136// Public event query
137// ---------------------------------------------------------------------------
138
139/// Query and decode public events from the node.
140///
141/// Fetches all public logs matching the filter, then performs **client-side**
142/// filtering by event selector (matching the last field of each log against
143/// the selector from `event_metadata`).  This mirrors the upstream TS SDK
144/// behaviour where the node returns all logs and the client skips non-matching
145/// selectors.
146pub 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, // client-side filtering, not node-side
157        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        // Client-side selector matching: skip logs whose last field doesn't
165        // match the expected event selector.
166        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
191// ---------------------------------------------------------------------------
192// Re-exports for convenience
193// ---------------------------------------------------------------------------
194
195// Private event types live in `wallet` but are re-exported here so that
196// consumers can find all event-related types in one place.
197pub use crate::wallet::{
198    EventMetadataDefinition as EventMetadata, PrivateEvent, PrivateEventFilter,
199    PrivateEventMetadata,
200};
201
202// ---------------------------------------------------------------------------
203// Tests
204// ---------------------------------------------------------------------------
205
206#[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    // -- Mock node that returns configurable public logs --
219
220    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    // -- decode_log_fields tests --
422
423    #[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), // extra
441            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)]; // only 1 field, need 2
464
465        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    // -- get_public_events tests --
528
529    #[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        // Log with matching selector but insufficient fields.
647        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    // -- PublicEventFilter serde --
660
661    #[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    // -- PublicEventMetadata serde --
688
689    #[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    // -- PublicEvent serde --
703
704    #[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    // -- GetPublicEventsResult serde --
725
726    #[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}