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::{Address, Event, Identifier, ObjectId, StructTag, TransactionDigest};
10use iota_types::{
11    error::IotaResult,
12    event::{EventEnvelope, EventID},
13    object::bounded_visitor::BoundedVisitor,
14};
15use json_to_table::json_to_table;
16use move_core_types::annotated_value::MoveDatatypeLayout;
17use schemars::JsonSchema;
18use serde::{Deserialize, Serialize};
19use serde_json::{Value, json};
20use serde_with::{DisplayFromStr, serde_as};
21use tabled::settings::Style as TableStyle;
22
23use crate::{
24    Page,
25    iota_primitives::{
26        Address as AddressSchema, Base58 as Base58Schema, Base64 as Base64Schema,
27        Identifier as IdentifierSchema, ObjectId as ObjectIdSchema, StructTag as StructTagSchema,
28    },
29    type_and_fields_from_move_event_data,
30};
31
32pub type EventPage = Page<IotaEvent, EventID>;
33
34/// Unique ID of an IOTA Event, the ID is a combination of transaction digest
35/// and event seq number.
36#[serde_as]
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)]
38#[serde(rename_all = "camelCase")]
39#[schemars(rename = "EventID")]
40pub struct IotaEventID {
41    #[serde_as(as = "Base58Schema")]
42    #[schemars(with = "Base58Schema")]
43    pub tx_digest: TransactionDigest,
44    #[schemars(with = "String")]
45    #[serde_as(as = "DisplayFromStr")]
46    pub event_seq: u64,
47}
48
49impl From<EventID> for IotaEventID {
50    fn from(id: EventID) -> Self {
51        Self {
52            tx_digest: id.tx_digest,
53            event_seq: id.event_seq,
54        }
55    }
56}
57
58impl From<IotaEventID> for EventID {
59    fn from(id: IotaEventID) -> Self {
60        Self {
61            tx_digest: id.tx_digest,
62            event_seq: id.event_seq,
63        }
64    }
65}
66
67#[serde_as]
68#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
69#[serde(rename = "Event", rename_all = "camelCase")]
70pub struct IotaEvent {
71    /// Sequential event ID, ie (transaction seq number, event seq number).
72    /// 1) Serves as a unique event ID for each fullnode
73    /// 2) Also serves to sequence events for the purposes of pagination and
74    ///    querying. A higher id is an event seen later by that fullnode.
75    /// This ID is the "cursor" for event querying.
76    #[schemars(with = "IotaEventID")]
77    pub id: EventID,
78    /// Move package where this event was emitted.
79    #[serde_as(as = "ObjectIdSchema")]
80    #[schemars(with = "ObjectIdSchema")]
81    pub package_id: ObjectId,
82    #[serde_as(as = "IdentifierSchema")]
83    #[schemars(with = "IdentifierSchema")]
84    /// Move module where this event was emitted.
85    pub transaction_module: Identifier,
86    /// Sender's IOTA address.
87    #[serde_as(as = "AddressSchema")]
88    #[schemars(with = "AddressSchema")]
89    pub sender: Address,
90    /// Move event type.
91    #[schemars(with = "StructTagSchema")]
92    #[serde_as(as = "StructTagSchema")]
93    pub type_: 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            type_: ev.event.type_,
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            type_: val.type_,
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            type_: _,
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 (type_, 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            type_,
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.type_
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            type_: StructTag::from_str("0x6666::random_for_testing::RandomForTesting").unwrap(),
299            parsed_json: json!({}),
300            bcs: BcsEvent::new(vec![]),
301            timestamp_ms: None,
302        }
303    }
304}
305
306/// Convert a json array of bytes to Base64
307fn bytes_array_to_base64(v: &mut Value) {
308    match v {
309        Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => (),
310        Value::Array(vals) => {
311            if let Some(vals) = vals.iter().map(try_into_byte).collect::<Option<Vec<_>>>() {
312                *v = json!(Base64::from_bytes(&vals).encoded())
313            } else {
314                for val in vals {
315                    bytes_array_to_base64(val)
316                }
317            }
318        }
319        Value::Object(map) => {
320            for val in map.values_mut() {
321                bytes_array_to_base64(val)
322            }
323        }
324    }
325}
326
327/// Try to convert a json Value object into an u8.
328fn try_into_byte(v: &Value) -> Option<u8> {
329    let num = v.as_u64()?;
330    (num <= 255).then_some(num as u8)
331}
332
333#[serde_as]
334#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
335pub enum EventFilter {
336    /// Query by sender address.
337    Sender(
338        #[serde_as(as = "AddressSchema")]
339        #[schemars(with = "AddressSchema")]
340        Address,
341    ),
342    /// Return events emitted by the given transaction.
343    Transaction(
344        /// digest of the transaction, as base-64 encoded string
345        #[serde_as(as = "Base58Schema")]
346        #[schemars(with = "Base58Schema")]
347        TransactionDigest,
348    ),
349    /// Return events emitted in a specified Package.
350    Package(
351        #[serde_as(as = "ObjectIdSchema")]
352        #[schemars(with = "ObjectIdSchema")]
353        ObjectId,
354    ),
355    /// Return events emitted in a specified Move module.
356    /// If the event is defined in Module A but emitted in a tx with Module B,
357    /// query `MoveModule` by module B returns the event.
358    /// Query `MoveEventModule` by module A returns the event too.
359    MoveModule {
360        /// the Move package ID
361        #[serde_as(as = "ObjectIdSchema")]
362        #[schemars(with = "ObjectIdSchema")]
363        package: ObjectId,
364        /// the module name
365        #[serde_as(as = "IdentifierSchema")]
366        #[schemars(with = "IdentifierSchema")]
367        module: Identifier,
368    },
369    /// Return events with the given Move event struct name (struct tag).
370    /// For example, if the event is defined in `0xabcd::MyModule`, and named
371    /// `Foo`, then the struct tag is `0xabcd::MyModule::Foo`.
372    MoveEventType(
373        #[schemars(with = "StructTagSchema")]
374        #[serde_as(as = "StructTagSchema")]
375        StructTag,
376    ),
377    /// Return events with the given Move module name where the event struct is
378    /// defined. If the event is defined in Module A but emitted in a tx
379    /// with Module B, query `MoveEventModule` by module A returns the
380    /// event. Query `MoveModule` by module B returns the event too.
381    MoveEventModule {
382        /// the Move package ID
383        #[serde_as(as = "ObjectIdSchema")]
384        #[schemars(with = "ObjectIdSchema")]
385        package: ObjectId,
386        /// the module name
387        #[serde_as(as = "IdentifierSchema")]
388        #[schemars(with = "IdentifierSchema")]
389        module: Identifier,
390    },
391    MoveEventField {
392        path: String,
393        value: Value,
394    },
395    /// Return events emitted in [start_time, end_time] interval
396    #[serde(rename_all = "camelCase")]
397    TimeRange {
398        /// left endpoint of time interval, milliseconds since epoch, inclusive
399        #[serde_as(as = "DisplayFromStr")]
400        #[schemars(with = "String")]
401        start_time: u64,
402        /// right endpoint of time interval, milliseconds since epoch, exclusive
403        #[serde_as(as = "DisplayFromStr")]
404        #[schemars(with = "String")]
405        end_time: u64,
406    },
407
408    All(Vec<EventFilter>),
409    Any(Vec<EventFilter>),
410    And(Box<EventFilter>, Box<EventFilter>),
411    Or(Box<EventFilter>, Box<EventFilter>),
412}
413
414impl EventFilter {
415    fn try_matches(&self, item: &IotaEvent) -> IotaResult<bool> {
416        Ok(match self {
417            EventFilter::MoveEventType(event_type) => &item.type_ == event_type,
418            EventFilter::MoveEventField { path, value } => {
419                matches!(item.parsed_json.pointer(path), Some(v) if v == value)
420            }
421            EventFilter::Sender(sender) => &item.sender == sender,
422            EventFilter::Package(object_id) => &item.package_id == object_id,
423            EventFilter::MoveModule { package, module } => {
424                &item.transaction_module == module && &item.package_id == package
425            }
426            EventFilter::All(filters) => filters.iter().all(|f| f.matches(item)),
427            EventFilter::Any(filters) => filters.iter().any(|f| f.matches(item)),
428            EventFilter::And(f1, f2) => {
429                EventFilter::All(vec![*(*f1).clone(), *(*f2).clone()]).matches(item)
430            }
431            EventFilter::Or(f1, f2) => {
432                EventFilter::Any(vec![*(*f1).clone(), *(*f2).clone()]).matches(item)
433            }
434            EventFilter::Transaction(digest) => digest == &item.id.tx_digest,
435
436            EventFilter::TimeRange {
437                start_time,
438                end_time,
439            } => {
440                if let Some(timestamp) = &item.timestamp_ms {
441                    start_time <= timestamp && end_time > timestamp
442                } else {
443                    false
444                }
445            }
446            EventFilter::MoveEventModule { package, module } => {
447                item.type_.module() == module && &item.type_.address() == package.as_address()
448            }
449        })
450    }
451
452    pub fn and(self, other_filter: EventFilter) -> Self {
453        Self::All(vec![self, other_filter])
454    }
455    pub fn or(self, other_filter: EventFilter) -> Self {
456        Self::Any(vec![self, other_filter])
457    }
458}
459
460impl Filter<IotaEvent> for EventFilter {
461    fn matches(&self, item: &IotaEvent) -> bool {
462        let _scope = monitored_scope("EventFilter::matches");
463        self.try_matches(item).unwrap_or_default()
464    }
465}
466
467pub trait Filter<T> {
468    fn matches(&self, item: &T) -> bool;
469}
470
471#[cfg(test)]
472mod test {
473    use super::*;
474
475    #[test]
476    fn bcs_event_test() {
477        let bytes = vec![0, 1, 2, 3, 4];
478        let untagged_base58 = r#"{"bcs":"12VfUX"}"#;
479        let tagged_base58 = r#"{"bcsEncoding":"base58","bcs":"12VfUX"}"#;
480        let tagged_base64 = r#"{"bcsEncoding":"base64","bcs":"AAECAwQ="}"#;
481
482        assert_eq!(
483            bytes,
484            serde_json::from_str::<BcsEvent>(untagged_base58)
485                .unwrap()
486                .into_bytes()
487        );
488        assert_eq!(
489            bytes,
490            serde_json::from_str::<BcsEvent>(tagged_base58)
491                .unwrap()
492                .into_bytes()
493        );
494        assert_eq!(
495            bytes,
496            serde_json::from_str::<BcsEvent>(tagged_base64)
497                .unwrap()
498                .into_bytes()
499        );
500
501        // Roundtrip base64
502        let event = serde_json::from_str::<BcsEvent>(tagged_base64).unwrap();
503        let json = serde_json::to_string(&event).unwrap();
504        let from_json = serde_json::from_str::<BcsEvent>(&json).unwrap();
505        assert_eq!(event, from_json);
506
507        // Roundtrip base58
508        let event = serde_json::from_str::<BcsEvent>(tagged_base58).unwrap();
509        let json = serde_json::to_string(&event).unwrap();
510        let from_json = serde_json::from_str::<BcsEvent>(&json).unwrap();
511        assert_eq!(event, from_json);
512    }
513}