sui_json_rpc_types/
sui_event.rs

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