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