iota_json_rpc_types/
iota_event.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{fmt, fmt::Display, str::FromStr};
6
7use fastcrypto::encoding::{Base58, Base64};
8use iota_metrics::monitored_scope;
9use iota_types::{
10    base_types::{IotaAddress, ObjectID, TransactionDigest},
11    error::IotaResult,
12    event::{Event, EventEnvelope, EventID},
13    iota_serde::{BigInt, IotaStructTag},
14};
15use json_to_table::json_to_table;
16use move_core_types::{
17    annotated_value::MoveDatatypeLayout, identifier::Identifier, language_storage::StructTag,
18};
19use schemars::JsonSchema;
20use serde::{Deserialize, Serialize};
21use serde_json::{Value, json};
22use serde_with::{DisplayFromStr, serde_as};
23use tabled::settings::Style as TableStyle;
24
25use crate::{Page, type_and_fields_from_move_event_data};
26
27pub type EventPage = Page<IotaEvent, EventID>;
28
29#[serde_as]
30#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
31#[serde(rename = "Event", rename_all = "camelCase")]
32pub struct IotaEvent {
33    /// Sequential event ID, ie (transaction seq number, event seq number).
34    /// 1) Serves as a unique event ID for each fullnode
35    /// 2) Also serves to sequence events for the purposes of pagination and
36    ///    querying. A higher id is an event seen later by that fullnode.
37    /// This ID is the "cursor" for event querying.
38    pub id: EventID,
39    /// Move package where this event was emitted.
40    pub package_id: ObjectID,
41    #[schemars(with = "String")]
42    #[serde_as(as = "DisplayFromStr")]
43    /// Move module where this event was emitted.
44    pub transaction_module: Identifier,
45    /// Sender's IOTA address.
46    pub sender: IotaAddress,
47    #[schemars(with = "String")]
48    #[serde_as(as = "IotaStructTag")]
49    /// Move event type.
50    pub type_: StructTag,
51    /// Parsed json value of the event
52    pub parsed_json: Value,
53    /// Base64 encoded bcs bytes of the move event
54    #[serde(flatten)]
55    pub bcs: BcsEvent,
56    /// UTC timestamp in milliseconds since epoch (1/1/1970)
57    #[serde(skip_serializing_if = "Option::is_none")]
58    #[schemars(with = "Option<BigInt<u64>>")]
59    #[serde_as(as = "Option<BigInt<u64>>")]
60    pub timestamp_ms: Option<u64>,
61}
62
63#[serde_as]
64#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
65#[serde(rename_all = "camelCase", tag = "bcsEncoding")]
66#[serde(from = "MaybeTaggedBcsEvent")]
67pub enum BcsEvent {
68    Base64 {
69        #[serde_as(as = "Base64")]
70        #[schemars(with = "Base64")]
71        bcs: Vec<u8>,
72    },
73    Base58 {
74        #[serde_as(as = "Base58")]
75        #[schemars(with = "Base58")]
76        bcs: Vec<u8>,
77    },
78}
79
80impl BcsEvent {
81    pub fn new(bytes: Vec<u8>) -> Self {
82        Self::Base64 { bcs: bytes }
83    }
84
85    pub fn bytes(&self) -> &[u8] {
86        match self {
87            BcsEvent::Base64 { bcs } => bcs.as_ref(),
88            BcsEvent::Base58 { bcs } => bcs.as_ref(),
89        }
90    }
91
92    pub fn into_bytes(self) -> Vec<u8> {
93        match self {
94            BcsEvent::Base64 { bcs } => bcs,
95            BcsEvent::Base58 { bcs } => bcs,
96        }
97    }
98}
99
100#[allow(unused)]
101#[serde_as]
102#[derive(Serialize, Deserialize)]
103#[serde(rename_all = "camelCase", untagged)]
104enum MaybeTaggedBcsEvent {
105    Tagged(TaggedBcsEvent),
106    Base58 {
107        #[serde_as(as = "Base58")]
108        bcs: Vec<u8>,
109    },
110}
111
112#[serde_as]
113#[derive(Serialize, Deserialize)]
114#[serde(rename_all = "camelCase", tag = "bcsEncoding")]
115enum TaggedBcsEvent {
116    Base64 {
117        #[serde_as(as = "Base64")]
118        bcs: Vec<u8>,
119    },
120    Base58 {
121        #[serde_as(as = "Base58")]
122        bcs: Vec<u8>,
123    },
124}
125
126impl From<MaybeTaggedBcsEvent> for BcsEvent {
127    fn from(event: MaybeTaggedBcsEvent) -> BcsEvent {
128        let bcs = match event {
129            MaybeTaggedBcsEvent::Tagged(TaggedBcsEvent::Base58 { bcs })
130            | MaybeTaggedBcsEvent::Base58 { bcs } => bcs,
131            MaybeTaggedBcsEvent::Tagged(TaggedBcsEvent::Base64 { bcs }) => bcs,
132        };
133
134        // Bytes are already decoded, force into Base64 variant to avoid serializing to
135        // base58
136        Self::Base64 { bcs }
137    }
138}
139
140impl From<EventEnvelope> for IotaEvent {
141    fn from(ev: EventEnvelope) -> Self {
142        Self {
143            id: EventID {
144                tx_digest: ev.tx_digest,
145                event_seq: ev.event_num,
146            },
147            package_id: ev.event.package_id,
148            transaction_module: ev.event.transaction_module,
149            sender: ev.event.sender,
150            type_: ev.event.type_,
151            parsed_json: ev.parsed_json,
152            bcs: BcsEvent::Base64 {
153                bcs: ev.event.contents,
154            },
155            timestamp_ms: Some(ev.timestamp),
156        }
157    }
158}
159
160impl From<IotaEvent> for Event {
161    fn from(val: IotaEvent) -> Self {
162        Event {
163            package_id: val.package_id,
164            transaction_module: val.transaction_module,
165            sender: val.sender,
166            type_: val.type_,
167            contents: val.bcs.into_bytes(),
168        }
169    }
170}
171
172impl IotaEvent {
173    pub fn try_from(
174        event: Event,
175        tx_digest: TransactionDigest,
176        event_seq: u64,
177        timestamp_ms: Option<u64>,
178        layout: MoveDatatypeLayout,
179    ) -> IotaResult<Self> {
180        let Event {
181            package_id,
182            transaction_module,
183            sender,
184            type_: _,
185            contents,
186        } = event;
187
188        let bcs = BcsEvent::Base64 {
189            bcs: contents.to_vec(),
190        };
191
192        let move_value = Event::move_event_to_move_value(&contents, layout)?;
193        let (type_, fields) = type_and_fields_from_move_event_data(move_value)?;
194
195        Ok(IotaEvent {
196            id: EventID {
197                tx_digest,
198                event_seq,
199            },
200            package_id,
201            transaction_module,
202            sender,
203            type_,
204            parsed_json: fields,
205            bcs,
206            timestamp_ms,
207        })
208    }
209}
210
211impl Display for IotaEvent {
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        let parsed_json = &mut self.parsed_json.clone();
214        bytes_array_to_base64(parsed_json);
215        let mut table = json_to_table(parsed_json);
216        let style = TableStyle::modern();
217        table.collapse().with(style);
218        write!(
219            f,
220            " ┌──\n │ EventID: {}:{}\n │ PackageID: {}\n │ Transaction Module: {}\n │ Sender: {}\n │ EventType: {}\n",
221            self.id.tx_digest,
222            self.id.event_seq,
223            self.package_id,
224            self.transaction_module,
225            self.sender,
226            self.type_
227        )?;
228        if let Some(ts) = self.timestamp_ms {
229            writeln!(f, " │ Timestamp: {ts}\n └──")?;
230        }
231        writeln!(f, " │ ParsedJSON:")?;
232        let table_string = table.to_string();
233        let table_rows = table_string.split_inclusive('\n');
234        for r in table_rows {
235            write!(f, " │   {r}")?;
236        }
237
238        write!(f, "\n └──")
239    }
240}
241
242impl IotaEvent {
243    pub fn random_for_testing() -> Self {
244        Self {
245            id: EventID {
246                tx_digest: TransactionDigest::random(),
247                event_seq: 0,
248            },
249            package_id: ObjectID::random(),
250            transaction_module: Identifier::from_str("random_for_testing").unwrap(),
251            sender: IotaAddress::random_for_testing_only(),
252            type_: StructTag::from_str("0x6666::random_for_testing::RandomForTesting").unwrap(),
253            parsed_json: json!({}),
254            bcs: BcsEvent::new(vec![]),
255            timestamp_ms: None,
256        }
257    }
258}
259
260/// Convert a json array of bytes to Base64
261fn bytes_array_to_base64(v: &mut Value) {
262    match v {
263        Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => (),
264        Value::Array(vals) => {
265            if let Some(vals) = vals.iter().map(try_into_byte).collect::<Option<Vec<_>>>() {
266                *v = json!(Base64::from_bytes(&vals).encoded())
267            } else {
268                for val in vals {
269                    bytes_array_to_base64(val)
270                }
271            }
272        }
273        Value::Object(map) => {
274            for val in map.values_mut() {
275                bytes_array_to_base64(val)
276            }
277        }
278    }
279}
280
281/// Try to convert a json Value object into an u8.
282fn try_into_byte(v: &Value) -> Option<u8> {
283    let num = v.as_u64()?;
284    (num <= 255).then_some(num as u8)
285}
286
287#[serde_as]
288#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
289pub enum EventFilter {
290    /// Query by sender address.
291    Sender(IotaAddress),
292    /// Return events emitted by the given transaction.
293    Transaction(
294        /// digest of the transaction, as base-64 encoded string
295        TransactionDigest,
296    ),
297    /// Return events emitted in a specified Package.
298    Package(ObjectID),
299    /// Return events emitted in a specified Move module.
300    /// If the event is defined in Module A but emitted in a tx with Module B,
301    /// query `MoveModule` by module B returns the event.
302    /// Query `MoveEventModule` by module A returns the event too.
303    MoveModule {
304        /// the Move package ID
305        package: ObjectID,
306        /// the module name
307        #[schemars(with = "String")]
308        #[serde_as(as = "DisplayFromStr")]
309        module: Identifier,
310    },
311    /// Return events with the given Move event struct name (struct tag).
312    /// For example, if the event is defined in `0xabcd::MyModule`, and named
313    /// `Foo`, then the struct tag is `0xabcd::MyModule::Foo`.
314    MoveEventType(
315        #[schemars(with = "String")]
316        #[serde_as(as = "IotaStructTag")]
317        StructTag,
318    ),
319    /// Return events with the given Move module name where the event struct is
320    /// defined. If the event is defined in Module A but emitted in a tx
321    /// with Module B, query `MoveEventModule` by module A returns the
322    /// event. Query `MoveModule` by module B returns the event too.
323    MoveEventModule {
324        /// the Move package ID
325        package: ObjectID,
326        /// the module name
327        #[schemars(with = "String")]
328        #[serde_as(as = "DisplayFromStr")]
329        module: Identifier,
330    },
331    MoveEventField {
332        path: String,
333        value: Value,
334    },
335    /// Return events emitted in [start_time, end_time] interval
336    #[serde(rename_all = "camelCase")]
337    TimeRange {
338        /// left endpoint of time interval, milliseconds since epoch, inclusive
339        #[schemars(with = "BigInt<u64>")]
340        #[serde_as(as = "BigInt<u64>")]
341        start_time: u64,
342        /// right endpoint of time interval, milliseconds since epoch, exclusive
343        #[schemars(with = "BigInt<u64>")]
344        #[serde_as(as = "BigInt<u64>")]
345        end_time: u64,
346    },
347
348    All(Vec<EventFilter>),
349    Any(Vec<EventFilter>),
350    And(Box<EventFilter>, Box<EventFilter>),
351    Or(Box<EventFilter>, Box<EventFilter>),
352}
353
354impl EventFilter {
355    fn try_matches(&self, item: &IotaEvent) -> IotaResult<bool> {
356        Ok(match self {
357            EventFilter::MoveEventType(event_type) => &item.type_ == event_type,
358            EventFilter::MoveEventField { path, value } => {
359                matches!(item.parsed_json.pointer(path), Some(v) if v == value)
360            }
361            EventFilter::Sender(sender) => &item.sender == sender,
362            EventFilter::Package(object_id) => &item.package_id == object_id,
363            EventFilter::MoveModule { package, module } => {
364                &item.transaction_module == module && &item.package_id == package
365            }
366            EventFilter::All(filters) => filters.iter().all(|f| f.matches(item)),
367            EventFilter::Any(filters) => filters.iter().any(|f| f.matches(item)),
368            EventFilter::And(f1, f2) => {
369                EventFilter::All(vec![*(*f1).clone(), *(*f2).clone()]).matches(item)
370            }
371            EventFilter::Or(f1, f2) => {
372                EventFilter::Any(vec![*(*f1).clone(), *(*f2).clone()]).matches(item)
373            }
374            EventFilter::Transaction(digest) => digest == &item.id.tx_digest,
375
376            EventFilter::TimeRange {
377                start_time,
378                end_time,
379            } => {
380                if let Some(timestamp) = &item.timestamp_ms {
381                    start_time <= timestamp && end_time > timestamp
382                } else {
383                    false
384                }
385            }
386            EventFilter::MoveEventModule { package, module } => {
387                &item.type_.module == module && &ObjectID::from(item.type_.address) == package
388            }
389        })
390    }
391
392    pub fn and(self, other_filter: EventFilter) -> Self {
393        Self::All(vec![self, other_filter])
394    }
395    pub fn or(self, other_filter: EventFilter) -> Self {
396        Self::Any(vec![self, other_filter])
397    }
398}
399
400impl Filter<IotaEvent> for EventFilter {
401    fn matches(&self, item: &IotaEvent) -> bool {
402        let _scope = monitored_scope("EventFilter::matches");
403        self.try_matches(item).unwrap_or_default()
404    }
405}
406
407pub trait Filter<T> {
408    fn matches(&self, item: &T) -> bool;
409}
410
411#[cfg(test)]
412mod test {
413    use super::*;
414
415    #[test]
416    fn bcs_event_test() {
417        let bytes = vec![0, 1, 2, 3, 4];
418        let untagged_base58 = r#"{"bcs":"12VfUX"}"#;
419        let tagged_base58 = r#"{"bcsEncoding":"base58","bcs":"12VfUX"}"#;
420        let tagged_base64 = r#"{"bcsEncoding":"base64","bcs":"AAECAwQ="}"#;
421
422        assert_eq!(
423            bytes,
424            serde_json::from_str::<BcsEvent>(untagged_base58)
425                .unwrap()
426                .into_bytes()
427        );
428        assert_eq!(
429            bytes,
430            serde_json::from_str::<BcsEvent>(tagged_base58)
431                .unwrap()
432                .into_bytes()
433        );
434        assert_eq!(
435            bytes,
436            serde_json::from_str::<BcsEvent>(tagged_base64)
437                .unwrap()
438                .into_bytes()
439        );
440
441        // Roundtrip base64
442        let event = serde_json::from_str::<BcsEvent>(tagged_base64).unwrap();
443        let json = serde_json::to_string(&event).unwrap();
444        let from_json = serde_json::from_str::<BcsEvent>(&json).unwrap();
445        assert_eq!(event, from_json);
446
447        // Roundtrip base58
448        let event = serde_json::from_str::<BcsEvent>(tagged_base58).unwrap();
449        let json = serde_json::to_string(&event).unwrap();
450        let from_json = serde_json::from_str::<BcsEvent>(&json).unwrap();
451        assert_eq!(event, from_json);
452    }
453}