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_metrics::monitored_scope;
9use iota_sdk_types::{Event, Identifier, ObjectId, StructTag};
10use iota_types::{
11    base_types::{IotaAddress, TransactionDigest},
12    error::IotaResult,
13    event::{EventEnvelope, EventID},
14    object::bounded_visitor::BoundedVisitor,
15};
16use json_to_table::json_to_table;
17use move_core_types::annotated_value::MoveDatatypeLayout;
18use schemars::JsonSchema;
19use serde::{Deserialize, Serialize};
20use serde_json::{Value, json};
21use serde_with::{DisplayFromStr, serde_as};
22use tabled::settings::Style as TableStyle;
23
24use crate::{
25    Page,
26    iota_primitives::{
27        Base58 as Base58Schema, Base64 as Base64Schema, Identifier as IdentifierSchema,
28        IotaAddress as IotaAddressSchema, ObjectId as ObjectIdSchema, StructTag as StructTagSchema,
29    },
30    type_and_fields_from_move_event_data,
31};
32
33pub type EventPage = Page<IotaEvent, EventID>;
34
35/// Unique ID of an IOTA Event, the ID is a combination of transaction digest
36/// and event seq number.
37#[serde_as]
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)]
39#[serde(rename_all = "camelCase")]
40#[schemars(rename = "EventID")]
41pub struct IotaEventID {
42    #[serde_as(as = "Base58Schema")]
43    #[schemars(with = "Base58Schema")]
44    pub tx_digest: TransactionDigest,
45    #[schemars(with = "String")]
46    #[serde_as(as = "DisplayFromStr")]
47    pub event_seq: u64,
48}
49
50impl From<EventID> for IotaEventID {
51    fn from(id: EventID) -> Self {
52        Self {
53            tx_digest: id.tx_digest,
54            event_seq: id.event_seq,
55        }
56    }
57}
58
59impl From<IotaEventID> for EventID {
60    fn from(id: IotaEventID) -> Self {
61        Self {
62            tx_digest: id.tx_digest,
63            event_seq: id.event_seq,
64        }
65    }
66}
67
68#[serde_as]
69#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
70#[serde(rename = "Event", rename_all = "camelCase")]
71pub struct IotaEvent {
72    /// Sequential event ID, ie (transaction seq number, event seq number).
73    /// 1) Serves as a unique event ID for each fullnode
74    /// 2) Also serves to sequence events for the purposes of pagination and
75    ///    querying. A higher id is an event seen later by that fullnode.
76    /// This ID is the "cursor" for event querying.
77    #[schemars(with = "IotaEventID")]
78    pub id: EventID,
79    /// Move package where this event was emitted.
80    #[serde_as(as = "ObjectIdSchema")]
81    #[schemars(with = "ObjectIdSchema")]
82    pub package_id: ObjectId,
83    #[serde_as(as = "IdentifierSchema")]
84    #[schemars(with = "IdentifierSchema")]
85    /// Move module where this event was emitted.
86    pub transaction_module: Identifier,
87    /// Sender's IOTA address.
88    #[serde_as(as = "IotaAddressSchema")]
89    #[schemars(with = "IotaAddressSchema")]
90    pub sender: IotaAddress,
91    /// Move event type.
92    #[schemars(with = "StructTagSchema")]
93    #[serde_as(as = "StructTagSchema")]
94    pub type_: StructTag,
95    /// Parsed json value of the event
96    pub parsed_json: Value,
97    /// Base64 encoded bcs bytes of the move event
98    #[serde(flatten)]
99    pub bcs: BcsEvent,
100    /// UTC timestamp in milliseconds since epoch (1/1/1970)
101    #[serde(skip_serializing_if = "Option::is_none")]
102    #[schemars(with = "Option<String>")]
103    #[serde_as(as = "Option<DisplayFromStr>")]
104    pub timestamp_ms: Option<u64>,
105}
106
107#[serde_as]
108#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
109#[serde(rename_all = "camelCase", tag = "bcsEncoding")]
110#[serde(from = "MaybeTaggedBcsEvent")]
111pub enum BcsEvent {
112    Base64 {
113        #[serde_as(as = "Base64")]
114        #[schemars(with = "Base64Schema")]
115        bcs: Vec<u8>,
116    },
117    Base58 {
118        #[serde_as(as = "Base58")]
119        #[schemars(with = "Base58Schema")]
120        bcs: Vec<u8>,
121    },
122}
123
124impl BcsEvent {
125    pub fn new(bytes: Vec<u8>) -> Self {
126        Self::Base64 { bcs: bytes }
127    }
128
129    pub fn bytes(&self) -> &[u8] {
130        match self {
131            BcsEvent::Base64 { bcs } => bcs.as_ref(),
132            BcsEvent::Base58 { bcs } => bcs.as_ref(),
133        }
134    }
135
136    pub fn into_bytes(self) -> Vec<u8> {
137        match self {
138            BcsEvent::Base64 { bcs } => bcs,
139            BcsEvent::Base58 { bcs } => bcs,
140        }
141    }
142}
143
144#[allow(unused)]
145#[serde_as]
146#[derive(Serialize, Deserialize)]
147#[serde(rename_all = "camelCase", untagged)]
148enum MaybeTaggedBcsEvent {
149    Tagged(TaggedBcsEvent),
150    Base58 {
151        #[serde_as(as = "Base58")]
152        bcs: Vec<u8>,
153    },
154}
155
156#[serde_as]
157#[derive(Serialize, Deserialize)]
158#[serde(rename_all = "camelCase", tag = "bcsEncoding")]
159enum TaggedBcsEvent {
160    Base64 {
161        #[serde_as(as = "Base64")]
162        bcs: Vec<u8>,
163    },
164    Base58 {
165        #[serde_as(as = "Base58")]
166        bcs: Vec<u8>,
167    },
168}
169
170impl From<MaybeTaggedBcsEvent> for BcsEvent {
171    fn from(event: MaybeTaggedBcsEvent) -> BcsEvent {
172        let bcs = match event {
173            MaybeTaggedBcsEvent::Tagged(TaggedBcsEvent::Base58 { bcs })
174            | MaybeTaggedBcsEvent::Base58 { bcs } => bcs,
175            MaybeTaggedBcsEvent::Tagged(TaggedBcsEvent::Base64 { bcs }) => bcs,
176        };
177
178        // Bytes are already decoded, force into Base64 variant to avoid serializing to
179        // base58
180        Self::Base64 { bcs }
181    }
182}
183
184impl From<EventEnvelope> for IotaEvent {
185    fn from(ev: EventEnvelope) -> Self {
186        Self {
187            id: EventID {
188                tx_digest: ev.tx_digest,
189                event_seq: ev.event_num,
190            },
191            package_id: ev.event.package_id,
192            transaction_module: ev.event.module,
193            sender: ev.event.sender,
194            type_: ev.event.type_,
195            parsed_json: ev.parsed_json,
196            bcs: BcsEvent::Base64 {
197                bcs: ev.event.contents,
198            },
199            timestamp_ms: Some(ev.timestamp),
200        }
201    }
202}
203
204impl From<IotaEvent> for Event {
205    fn from(val: IotaEvent) -> Self {
206        Event {
207            package_id: val.package_id,
208            module: val.transaction_module,
209            sender: val.sender,
210            type_: val.type_,
211            contents: val.bcs.into_bytes(),
212        }
213    }
214}
215
216impl IotaEvent {
217    pub fn try_from(
218        event: Event,
219        tx_digest: TransactionDigest,
220        event_seq: u64,
221        timestamp_ms: Option<u64>,
222        layout: MoveDatatypeLayout,
223    ) -> IotaResult<Self> {
224        let Event {
225            package_id,
226            module,
227            sender,
228            type_: _,
229            contents,
230        } = event;
231
232        let bcs = BcsEvent::Base64 {
233            bcs: contents.to_vec(),
234        };
235
236        let move_value = BoundedVisitor::deserialize_value(&contents, &layout.into_layout())
237            .map_err(|e| iota_types::error::IotaError::ObjectDeserialization {
238                error: e.to_string(),
239            })?;
240        let (type_, fields) = type_and_fields_from_move_event_data(move_value)?;
241
242        Ok(IotaEvent {
243            id: EventID {
244                tx_digest,
245                event_seq,
246            },
247            package_id,
248            transaction_module: module,
249            sender,
250            type_,
251            parsed_json: fields,
252            bcs,
253            timestamp_ms,
254        })
255    }
256}
257
258impl Display for IotaEvent {
259    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260        let parsed_json = &mut self.parsed_json.clone();
261        bytes_array_to_base64(parsed_json);
262        let mut table = json_to_table(parsed_json);
263        let style = TableStyle::modern();
264        table.collapse().with(style);
265        write!(
266            f,
267            " ┌──\n │ EventID: {}:{}\n │ PackageID: {}\n │ Transaction Module: {}\n │ Sender: {}\n │ EventType: {}\n",
268            self.id.tx_digest,
269            self.id.event_seq,
270            self.package_id,
271            self.transaction_module,
272            self.sender,
273            self.type_
274        )?;
275        if let Some(ts) = self.timestamp_ms {
276            writeln!(f, " │ Timestamp: {ts}\n └──")?;
277        }
278        writeln!(f, " │ ParsedJSON:")?;
279        let table_string = table.to_string();
280        let table_rows = table_string.split_inclusive('\n');
281        for r in table_rows {
282            write!(f, " │   {r}")?;
283        }
284
285        write!(f, "\n └──")
286    }
287}
288
289impl IotaEvent {
290    pub fn random_for_testing() -> Self {
291        Self {
292            id: EventID {
293                tx_digest: TransactionDigest::random(),
294                event_seq: 0,
295            },
296            package_id: ObjectId::random(),
297            transaction_module: Identifier::from_str("random_for_testing").unwrap(),
298            sender: IotaAddress::random(),
299            type_: StructTag::from_str("0x6666::random_for_testing::RandomForTesting").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 = "IotaAddressSchema")]
340        #[schemars(with = "IotaAddressSchema")]
341        IotaAddress,
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.type_ == 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.type_.module() == module && &item.type_.address() == package.as_address()
449            }
450        })
451    }
452
453    pub fn and(self, other_filter: EventFilter) -> Self {
454        Self::All(vec![self, other_filter])
455    }
456    pub fn or(self, other_filter: EventFilter) -> Self {
457        Self::Any(vec![self, other_filter])
458    }
459}
460
461impl Filter<IotaEvent> for EventFilter {
462    fn matches(&self, item: &IotaEvent) -> bool {
463        let _scope = monitored_scope("EventFilter::matches");
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}