1use 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#[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 #[schemars(with = "IotaEventID")]
78 pub id: EventID,
79 #[serde_as(as = "ObjectIdSchema")]
81 #[schemars(with = "ObjectIdSchema")]
82 pub package_id: ObjectId,
83 #[serde_as(as = "IdentifierSchema")]
84 #[schemars(with = "IdentifierSchema")]
85 pub transaction_module: Identifier,
87 #[serde_as(as = "IotaAddressSchema")]
89 #[schemars(with = "IotaAddressSchema")]
90 pub sender: IotaAddress,
91 #[schemars(with = "StructTagSchema")]
93 #[serde_as(as = "StructTagSchema")]
94 pub type_: StructTag,
95 pub parsed_json: Value,
97 #[serde(flatten)]
99 pub bcs: BcsEvent,
100 #[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 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
307fn 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
328fn 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 Sender(
339 #[serde_as(as = "IotaAddressSchema")]
340 #[schemars(with = "IotaAddressSchema")]
341 IotaAddress,
342 ),
343 Transaction(
345 #[serde_as(as = "Base58Schema")]
347 #[schemars(with = "Base58Schema")]
348 TransactionDigest,
349 ),
350 Package(
352 #[serde_as(as = "ObjectIdSchema")]
353 #[schemars(with = "ObjectIdSchema")]
354 ObjectId,
355 ),
356 MoveModule {
361 #[serde_as(as = "ObjectIdSchema")]
363 #[schemars(with = "ObjectIdSchema")]
364 package: ObjectId,
365 #[serde_as(as = "IdentifierSchema")]
367 #[schemars(with = "IdentifierSchema")]
368 module: Identifier,
369 },
370 MoveEventType(
374 #[schemars(with = "StructTagSchema")]
375 #[serde_as(as = "StructTagSchema")]
376 StructTag,
377 ),
378 MoveEventModule {
383 #[serde_as(as = "ObjectIdSchema")]
385 #[schemars(with = "ObjectIdSchema")]
386 package: ObjectId,
387 #[serde_as(as = "IdentifierSchema")]
389 #[schemars(with = "IdentifierSchema")]
390 module: Identifier,
391 },
392 MoveEventField {
393 path: String,
394 value: Value,
395 },
396 #[serde(rename_all = "camelCase")]
398 TimeRange {
399 #[serde_as(as = "DisplayFromStr")]
401 #[schemars(with = "String")]
402 start_time: u64,
403 #[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 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 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}