Skip to main content

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