1use std::str::FromStr;
6
7use anyhow::ensure;
8use move_core_types::{
9 account_address::AccountAddress,
10 annotated_value::{MoveDatatypeLayout, MoveValue},
11 ident_str,
12 identifier::{IdentStr, Identifier},
13 language_storage::StructTag,
14};
15use schemars::JsonSchema;
16use serde::{Deserialize, Serialize};
17use serde_json::Value;
18use serde_with::{Bytes, serde_as};
19
20use crate::{
21 IOTA_SYSTEM_ADDRESS,
22 base_types::{IotaAddress, ObjectID, TransactionDigest},
23 error::{IotaError, IotaResult},
24 iota_serde::{BigInt, Readable},
25 object::bounded_visitor::BoundedVisitor,
26};
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct EventEnvelope {
31 pub timestamp: u64,
33 pub tx_digest: TransactionDigest,
35 pub event_num: u64,
37 pub event: Event,
39 pub parsed_json: Value,
41}
42#[serde_as]
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Hash)]
46#[serde(rename_all = "camelCase")]
47pub struct EventID {
48 pub tx_digest: TransactionDigest,
49 #[schemars(with = "BigInt<u64>")]
50 #[serde_as(as = "Readable<BigInt<u64>, _>")]
51 pub event_seq: u64,
52}
53
54impl From<(TransactionDigest, u64)> for EventID {
55 fn from((tx_digest_num, event_seq_number): (TransactionDigest, u64)) -> Self {
56 Self {
57 tx_digest: tx_digest_num as TransactionDigest,
58 event_seq: event_seq_number,
59 }
60 }
61}
62
63impl From<EventID> for String {
64 fn from(id: EventID) -> Self {
65 format!("{:?}:{}", id.tx_digest, id.event_seq)
66 }
67}
68
69impl TryFrom<String> for EventID {
70 type Error = anyhow::Error;
71
72 fn try_from(value: String) -> Result<Self, Self::Error> {
73 let values = value.split(':').collect::<Vec<_>>();
74 ensure!(values.len() == 2, "Malformed EventID : {value}");
75 Ok((
76 TransactionDigest::from_str(values[0])?,
77 u64::from_str(values[1])?,
78 )
79 .into())
80 }
81}
82
83impl EventEnvelope {
84 pub fn new(
85 timestamp: u64,
86 tx_digest: TransactionDigest,
87 event_num: u64,
88 event: Event,
89 move_struct_json_value: Value,
90 ) -> Self {
91 Self {
92 timestamp,
93 tx_digest,
94 event_num,
95 event,
96 parsed_json: move_struct_json_value,
97 }
98 }
99}
100
101#[serde_as]
103#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize, Hash)]
104pub struct Event {
105 pub package_id: ObjectID,
106 pub transaction_module: Identifier,
107 pub sender: IotaAddress,
108 pub type_: StructTag,
109 #[serde_as(as = "Bytes")]
110 pub contents: Vec<u8>,
111}
112
113impl Event {
114 pub fn new(
115 package_id: &AccountAddress,
116 module: &IdentStr,
117 sender: IotaAddress,
118 type_: StructTag,
119 contents: Vec<u8>,
120 ) -> Self {
121 Self {
122 package_id: ObjectID::from(*package_id),
123 transaction_module: Identifier::from(module),
124 sender,
125 type_,
126 contents,
127 }
128 }
129 pub fn move_event_to_move_value(
130 contents: &[u8],
131 layout: MoveDatatypeLayout,
132 ) -> IotaResult<MoveValue> {
133 BoundedVisitor::deserialize_value(contents, &layout.into_layout()).map_err(|e| {
134 IotaError::ObjectSerialization {
135 error: e.to_string(),
136 }
137 })
138 }
139
140 pub fn is_system_epoch_info_event_v1(&self) -> bool {
141 self.type_.address == IOTA_SYSTEM_ADDRESS
142 && self.type_.module.as_ident_str() == ident_str!("iota_system_state_inner")
143 && self.type_.name.as_ident_str() == ident_str!("SystemEpochInfoEventV1")
144 }
145
146 pub fn is_system_epoch_info_event_v2(&self) -> bool {
147 self.type_.address == IOTA_SYSTEM_ADDRESS
148 && self.type_.module.as_ident_str() == ident_str!("iota_system_state_inner")
149 && self.type_.name.as_ident_str() == ident_str!("SystemEpochInfoEventV2")
150 }
151
152 pub fn is_system_epoch_info_event(&self) -> bool {
153 self.is_system_epoch_info_event_v1() || self.is_system_epoch_info_event_v2()
154 }
155}
156
157impl Event {
158 pub fn random_for_testing() -> Self {
159 Self {
160 package_id: ObjectID::random(),
161 transaction_module: Identifier::new("test").unwrap(),
162 sender: AccountAddress::random().into(),
163 type_: StructTag {
164 address: AccountAddress::random(),
165 module: Identifier::new("test").unwrap(),
166 name: Identifier::new("test").unwrap(),
167 type_params: vec![],
168 },
169 contents: vec![],
170 }
171 }
172}
173
174#[derive(Deserialize)]
175pub enum SystemEpochInfoEvent {
176 V1(SystemEpochInfoEventV1),
177 V2(SystemEpochInfoEventV2),
178}
179
180impl SystemEpochInfoEvent {
181 pub fn supply_change(&self) -> i64 {
182 match self {
183 SystemEpochInfoEvent::V1(event) => {
184 event.minted_tokens_amount as i64 - event.burnt_tokens_amount as i64
185 }
186 SystemEpochInfoEvent::V2(event) => {
187 event.minted_tokens_amount as i64 - event.burnt_tokens_amount as i64
188 }
189 }
190 }
191}
192
193impl From<Event> for SystemEpochInfoEvent {
194 fn from(event: Event) -> Self {
195 if event.is_system_epoch_info_event_v2() {
196 SystemEpochInfoEvent::V2(
197 bcs::from_bytes::<SystemEpochInfoEventV2>(&event.contents)
198 .expect("event deserialization should succeed as type was pre-validated"),
199 )
200 } else {
201 SystemEpochInfoEvent::V1(
202 bcs::from_bytes::<SystemEpochInfoEventV1>(&event.contents)
203 .expect("event deserialization should succeed as type was pre-validated"),
204 )
205 }
206 }
207}
208
209#[derive(Deserialize)]
211pub struct SystemEpochInfoEventV1 {
212 pub epoch: u64,
213 pub protocol_version: u64,
214 pub reference_gas_price: u64,
215 pub total_stake: u64,
216 pub storage_charge: u64,
217 pub storage_rebate: u64,
218 pub storage_fund_balance: u64,
219 pub total_gas_fees: u64,
220 pub total_stake_rewards_distributed: u64,
221 pub burnt_tokens_amount: u64,
222 pub minted_tokens_amount: u64,
223}
224
225#[derive(Deserialize)]
231pub struct SystemEpochInfoEventV2 {
232 pub epoch: u64,
233 pub protocol_version: u64,
234 pub total_stake: u64,
235 pub storage_charge: u64,
236 pub storage_rebate: u64,
237 pub storage_fund_balance: u64,
238 pub total_gas_fees: u64,
239 pub total_stake_rewards_distributed: u64,
240 pub burnt_tokens_amount: u64,
241 pub minted_tokens_amount: u64,
242 pub tips_amount: u64,
243}