1use std::{fmt, fmt::Display, str::FromStr};
6
7use fastcrypto::encoding::{Base58, Base64};
8use iota_metrics::monitored_scope;
9use iota_types::{
10 base_types::{IotaAddress, ObjectID, TransactionDigest},
11 error::IotaResult,
12 event::{Event, EventEnvelope, EventID},
13 iota_serde::{BigInt, IotaStructTag},
14};
15use json_to_table::json_to_table;
16use move_core_types::{
17 annotated_value::MoveDatatypeLayout, identifier::Identifier, language_storage::StructTag,
18};
19use schemars::JsonSchema;
20use serde::{Deserialize, Serialize};
21use serde_json::{Value, json};
22use serde_with::{DisplayFromStr, serde_as};
23use tabled::settings::Style as TableStyle;
24
25use crate::{Page, type_and_fields_from_move_event_data};
26
27pub type EventPage = Page<IotaEvent, EventID>;
28
29#[serde_as]
30#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
31#[serde(rename = "Event", rename_all = "camelCase")]
32pub struct IotaEvent {
33 pub id: EventID,
39 pub package_id: ObjectID,
41 #[schemars(with = "String")]
42 #[serde_as(as = "DisplayFromStr")]
43 pub transaction_module: Identifier,
45 pub sender: IotaAddress,
47 #[schemars(with = "String")]
48 #[serde_as(as = "IotaStructTag")]
49 pub type_: StructTag,
51 pub parsed_json: Value,
53 #[serde(flatten)]
55 pub bcs: BcsEvent,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 #[schemars(with = "Option<BigInt<u64>>")]
59 #[serde_as(as = "Option<BigInt<u64>>")]
60 pub timestamp_ms: Option<u64>,
61}
62
63#[serde_as]
64#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
65#[serde(rename_all = "camelCase", tag = "bcsEncoding")]
66#[serde(from = "MaybeTaggedBcsEvent")]
67pub enum BcsEvent {
68 Base64 {
69 #[serde_as(as = "Base64")]
70 #[schemars(with = "Base64")]
71 bcs: Vec<u8>,
72 },
73 Base58 {
74 #[serde_as(as = "Base58")]
75 #[schemars(with = "Base58")]
76 bcs: Vec<u8>,
77 },
78}
79
80impl BcsEvent {
81 pub fn new(bytes: Vec<u8>) -> Self {
82 Self::Base64 { bcs: bytes }
83 }
84
85 pub fn bytes(&self) -> &[u8] {
86 match self {
87 BcsEvent::Base64 { bcs } => bcs.as_ref(),
88 BcsEvent::Base58 { bcs } => bcs.as_ref(),
89 }
90 }
91
92 pub fn into_bytes(self) -> Vec<u8> {
93 match self {
94 BcsEvent::Base64 { bcs } => bcs,
95 BcsEvent::Base58 { bcs } => bcs,
96 }
97 }
98}
99
100#[allow(unused)]
101#[serde_as]
102#[derive(Serialize, Deserialize)]
103#[serde(rename_all = "camelCase", untagged)]
104enum MaybeTaggedBcsEvent {
105 Tagged(TaggedBcsEvent),
106 Base58 {
107 #[serde_as(as = "Base58")]
108 bcs: Vec<u8>,
109 },
110}
111
112#[serde_as]
113#[derive(Serialize, Deserialize)]
114#[serde(rename_all = "camelCase", tag = "bcsEncoding")]
115enum TaggedBcsEvent {
116 Base64 {
117 #[serde_as(as = "Base64")]
118 bcs: Vec<u8>,
119 },
120 Base58 {
121 #[serde_as(as = "Base58")]
122 bcs: Vec<u8>,
123 },
124}
125
126impl From<MaybeTaggedBcsEvent> for BcsEvent {
127 fn from(event: MaybeTaggedBcsEvent) -> BcsEvent {
128 let bcs = match event {
129 MaybeTaggedBcsEvent::Tagged(TaggedBcsEvent::Base58 { bcs })
130 | MaybeTaggedBcsEvent::Base58 { bcs } => bcs,
131 MaybeTaggedBcsEvent::Tagged(TaggedBcsEvent::Base64 { bcs }) => bcs,
132 };
133
134 Self::Base64 { bcs }
137 }
138}
139
140impl From<EventEnvelope> for IotaEvent {
141 fn from(ev: EventEnvelope) -> Self {
142 Self {
143 id: EventID {
144 tx_digest: ev.tx_digest,
145 event_seq: ev.event_num,
146 },
147 package_id: ev.event.package_id,
148 transaction_module: ev.event.transaction_module,
149 sender: ev.event.sender,
150 type_: ev.event.type_,
151 parsed_json: ev.parsed_json,
152 bcs: BcsEvent::Base64 {
153 bcs: ev.event.contents,
154 },
155 timestamp_ms: Some(ev.timestamp),
156 }
157 }
158}
159
160impl From<IotaEvent> for Event {
161 fn from(val: IotaEvent) -> Self {
162 Event {
163 package_id: val.package_id,
164 transaction_module: val.transaction_module,
165 sender: val.sender,
166 type_: val.type_,
167 contents: val.bcs.into_bytes(),
168 }
169 }
170}
171
172impl IotaEvent {
173 pub fn try_from(
174 event: Event,
175 tx_digest: TransactionDigest,
176 event_seq: u64,
177 timestamp_ms: Option<u64>,
178 layout: MoveDatatypeLayout,
179 ) -> IotaResult<Self> {
180 let Event {
181 package_id,
182 transaction_module,
183 sender,
184 type_: _,
185 contents,
186 } = event;
187
188 let bcs = BcsEvent::Base64 {
189 bcs: contents.to_vec(),
190 };
191
192 let move_value = Event::move_event_to_move_value(&contents, layout)?;
193 let (type_, fields) = type_and_fields_from_move_event_data(move_value)?;
194
195 Ok(IotaEvent {
196 id: EventID {
197 tx_digest,
198 event_seq,
199 },
200 package_id,
201 transaction_module,
202 sender,
203 type_,
204 parsed_json: fields,
205 bcs,
206 timestamp_ms,
207 })
208 }
209}
210
211impl Display for IotaEvent {
212 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213 let parsed_json = &mut self.parsed_json.clone();
214 bytes_array_to_base64(parsed_json);
215 let mut table = json_to_table(parsed_json);
216 let style = TableStyle::modern();
217 table.collapse().with(style);
218 write!(
219 f,
220 " ┌──\n │ EventID: {}:{}\n │ PackageID: {}\n │ Transaction Module: {}\n │ Sender: {}\n │ EventType: {}\n",
221 self.id.tx_digest,
222 self.id.event_seq,
223 self.package_id,
224 self.transaction_module,
225 self.sender,
226 self.type_
227 )?;
228 if let Some(ts) = self.timestamp_ms {
229 writeln!(f, " │ Timestamp: {ts}\n └──")?;
230 }
231 writeln!(f, " │ ParsedJSON:")?;
232 let table_string = table.to_string();
233 let table_rows = table_string.split_inclusive('\n');
234 for r in table_rows {
235 write!(f, " │ {r}")?;
236 }
237
238 write!(f, "\n └──")
239 }
240}
241
242impl IotaEvent {
243 pub fn random_for_testing() -> Self {
244 Self {
245 id: EventID {
246 tx_digest: TransactionDigest::random(),
247 event_seq: 0,
248 },
249 package_id: ObjectID::random(),
250 transaction_module: Identifier::from_str("random_for_testing").unwrap(),
251 sender: IotaAddress::random_for_testing_only(),
252 type_: StructTag::from_str("0x6666::random_for_testing::RandomForTesting").unwrap(),
253 parsed_json: json!({}),
254 bcs: BcsEvent::new(vec![]),
255 timestamp_ms: None,
256 }
257 }
258}
259
260fn bytes_array_to_base64(v: &mut Value) {
262 match v {
263 Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => (),
264 Value::Array(vals) => {
265 if let Some(vals) = vals.iter().map(try_into_byte).collect::<Option<Vec<_>>>() {
266 *v = json!(Base64::from_bytes(&vals).encoded())
267 } else {
268 for val in vals {
269 bytes_array_to_base64(val)
270 }
271 }
272 }
273 Value::Object(map) => {
274 for val in map.values_mut() {
275 bytes_array_to_base64(val)
276 }
277 }
278 }
279}
280
281fn try_into_byte(v: &Value) -> Option<u8> {
283 let num = v.as_u64()?;
284 (num <= 255).then_some(num as u8)
285}
286
287#[serde_as]
288#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
289pub enum EventFilter {
290 Sender(IotaAddress),
292 Transaction(
294 TransactionDigest,
296 ),
297 Package(ObjectID),
299 MoveModule {
304 package: ObjectID,
306 #[schemars(with = "String")]
308 #[serde_as(as = "DisplayFromStr")]
309 module: Identifier,
310 },
311 MoveEventType(
315 #[schemars(with = "String")]
316 #[serde_as(as = "IotaStructTag")]
317 StructTag,
318 ),
319 MoveEventModule {
324 package: ObjectID,
326 #[schemars(with = "String")]
328 #[serde_as(as = "DisplayFromStr")]
329 module: Identifier,
330 },
331 MoveEventField {
332 path: String,
333 value: Value,
334 },
335 #[serde(rename_all = "camelCase")]
337 TimeRange {
338 #[schemars(with = "BigInt<u64>")]
340 #[serde_as(as = "BigInt<u64>")]
341 start_time: u64,
342 #[schemars(with = "BigInt<u64>")]
344 #[serde_as(as = "BigInt<u64>")]
345 end_time: u64,
346 },
347
348 All(Vec<EventFilter>),
349 Any(Vec<EventFilter>),
350 And(Box<EventFilter>, Box<EventFilter>),
351 Or(Box<EventFilter>, Box<EventFilter>),
352}
353
354impl EventFilter {
355 fn try_matches(&self, item: &IotaEvent) -> IotaResult<bool> {
356 Ok(match self {
357 EventFilter::MoveEventType(event_type) => &item.type_ == event_type,
358 EventFilter::MoveEventField { path, value } => {
359 matches!(item.parsed_json.pointer(path), Some(v) if v == value)
360 }
361 EventFilter::Sender(sender) => &item.sender == sender,
362 EventFilter::Package(object_id) => &item.package_id == object_id,
363 EventFilter::MoveModule { package, module } => {
364 &item.transaction_module == module && &item.package_id == package
365 }
366 EventFilter::All(filters) => filters.iter().all(|f| f.matches(item)),
367 EventFilter::Any(filters) => filters.iter().any(|f| f.matches(item)),
368 EventFilter::And(f1, f2) => {
369 EventFilter::All(vec![*(*f1).clone(), *(*f2).clone()]).matches(item)
370 }
371 EventFilter::Or(f1, f2) => {
372 EventFilter::Any(vec![*(*f1).clone(), *(*f2).clone()]).matches(item)
373 }
374 EventFilter::Transaction(digest) => digest == &item.id.tx_digest,
375
376 EventFilter::TimeRange {
377 start_time,
378 end_time,
379 } => {
380 if let Some(timestamp) = &item.timestamp_ms {
381 start_time <= timestamp && end_time > timestamp
382 } else {
383 false
384 }
385 }
386 EventFilter::MoveEventModule { package, module } => {
387 &item.type_.module == module && &ObjectID::from(item.type_.address) == package
388 }
389 })
390 }
391
392 pub fn and(self, other_filter: EventFilter) -> Self {
393 Self::All(vec![self, other_filter])
394 }
395 pub fn or(self, other_filter: EventFilter) -> Self {
396 Self::Any(vec![self, other_filter])
397 }
398}
399
400impl Filter<IotaEvent> for EventFilter {
401 fn matches(&self, item: &IotaEvent) -> bool {
402 let _scope = monitored_scope("EventFilter::matches");
403 self.try_matches(item).unwrap_or_default()
404 }
405}
406
407pub trait Filter<T> {
408 fn matches(&self, item: &T) -> bool;
409}
410
411#[cfg(test)]
412mod test {
413 use super::*;
414
415 #[test]
416 fn bcs_event_test() {
417 let bytes = vec![0, 1, 2, 3, 4];
418 let untagged_base58 = r#"{"bcs":"12VfUX"}"#;
419 let tagged_base58 = r#"{"bcsEncoding":"base58","bcs":"12VfUX"}"#;
420 let tagged_base64 = r#"{"bcsEncoding":"base64","bcs":"AAECAwQ="}"#;
421
422 assert_eq!(
423 bytes,
424 serde_json::from_str::<BcsEvent>(untagged_base58)
425 .unwrap()
426 .into_bytes()
427 );
428 assert_eq!(
429 bytes,
430 serde_json::from_str::<BcsEvent>(tagged_base58)
431 .unwrap()
432 .into_bytes()
433 );
434 assert_eq!(
435 bytes,
436 serde_json::from_str::<BcsEvent>(tagged_base64)
437 .unwrap()
438 .into_bytes()
439 );
440
441 let event = serde_json::from_str::<BcsEvent>(tagged_base64).unwrap();
443 let json = serde_json::to_string(&event).unwrap();
444 let from_json = serde_json::from_str::<BcsEvent>(&json).unwrap();
445 assert_eq!(event, from_json);
446
447 let event = serde_json::from_str::<BcsEvent>(tagged_base58).unwrap();
449 let json = serde_json::to_string(&event).unwrap();
450 let from_json = serde_json::from_str::<BcsEvent>(&json).unwrap();
451 assert_eq!(event, from_json);
452 }
453}