1use 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#[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 #[schemars(with = "IotaEventID")]
77 pub id: EventID,
78 #[serde_as(as = "ObjectIdSchema")]
80 #[schemars(with = "ObjectIdSchema")]
81 pub package_id: ObjectId,
82 #[serde_as(as = "IdentifierSchema")]
83 #[schemars(with = "IdentifierSchema")]
84 pub transaction_module: Identifier,
86 #[serde_as(as = "AddressSchema")]
88 #[schemars(with = "AddressSchema")]
89 pub sender: Address,
90 #[schemars(with = "StructTagSchema")]
92 #[serde_as(as = "StructTagSchema")]
93 pub type_: StructTag,
94 pub parsed_json: Value,
96 #[serde(flatten)]
98 pub bcs: BcsEvent,
99 #[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 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
306fn 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
327fn 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 Sender(
338 #[serde_as(as = "AddressSchema")]
339 #[schemars(with = "AddressSchema")]
340 Address,
341 ),
342 Transaction(
344 #[serde_as(as = "Base58Schema")]
346 #[schemars(with = "Base58Schema")]
347 TransactionDigest,
348 ),
349 Package(
351 #[serde_as(as = "ObjectIdSchema")]
352 #[schemars(with = "ObjectIdSchema")]
353 ObjectId,
354 ),
355 MoveModule {
360 #[serde_as(as = "ObjectIdSchema")]
362 #[schemars(with = "ObjectIdSchema")]
363 package: ObjectId,
364 #[serde_as(as = "IdentifierSchema")]
366 #[schemars(with = "IdentifierSchema")]
367 module: Identifier,
368 },
369 MoveEventType(
373 #[schemars(with = "StructTagSchema")]
374 #[serde_as(as = "StructTagSchema")]
375 StructTag,
376 ),
377 MoveEventModule {
382 #[serde_as(as = "ObjectIdSchema")]
384 #[schemars(with = "ObjectIdSchema")]
385 package: ObjectId,
386 #[serde_as(as = "IdentifierSchema")]
388 #[schemars(with = "IdentifierSchema")]
389 module: Identifier,
390 },
391 MoveEventField {
392 path: String,
393 value: Value,
394 },
395 #[serde(rename_all = "camelCase")]
397 TimeRange {
398 #[serde_as(as = "DisplayFromStr")]
400 #[schemars(with = "String")]
401 start_time: u64,
402 #[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 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 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}