1use 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    pub id: EventID,
41    pub package_id: ObjectID,
43    #[schemars(with = "String")]
44    #[serde_as(as = "DisplayFromStr")]
45    pub transaction_module: Identifier,
47    pub sender: SuiAddress,
49    #[schemars(with = "String")]
50    #[serde_as(as = "SuiStructTag")]
51    pub type_: StructTag,
53    pub parsed_json: Value,
55    #[serde(flatten)]
57    pub bcs: BcsEvent,
58    #[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        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
261fn 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
282fn 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    All([Box<EventFilter>; 0]),
293
294    Any(Vec<EventFilter>),
296
297    Sender(SuiAddress),
299    Transaction(
301        TransactionDigest,
303    ),
304    MoveModule {
309        package: ObjectID,
311        #[schemars(with = "String")]
313        #[serde_as(as = "DisplayFromStr")]
314        module: Identifier,
315    },
316    MoveEventType(
320        #[schemars(with = "String")]
321        #[serde_as(as = "SuiStructTag")]
322        StructTag,
323    ),
324    MoveEventModule {
329        package: ObjectID,
331        #[schemars(with = "String")]
333        #[serde_as(as = "DisplayFromStr")]
334        module: Identifier,
335    },
336    #[serde(rename_all = "camelCase")]
338    TimeRange {
339        #[schemars(with = "BigInt<u64>")]
341        #[serde_as(as = "BigInt<u64>")]
342        start_time: u64,
343        #[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        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        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}