iota_types/
event.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use 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/// A universal IOTA event type encapsulating different types of events
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct EventEnvelope {
31    /// UTC timestamp in milliseconds since epoch (1/1/1970)
32    pub timestamp: u64,
33    /// Transaction digest of associated transaction
34    pub tx_digest: TransactionDigest,
35    /// Consecutive per-tx counter assigned to this event.
36    pub event_num: u64,
37    /// Specific event type
38    pub event: Event,
39    /// Move event's json value
40    pub parsed_json: Value,
41}
42/// Unique ID of an IOTA Event, the ID is a combination of tx seq number and
43/// event seq number, the ID is local to this particular fullnode and will be
44/// different from other fullnode.
45#[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/// Specific type of event
103#[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/// Event emitted in move code `fun advance_epoch` in protocol versions 1 to 3
211#[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/// Event emitted in move code `fun advance_epoch` in protocol versions 5 and
227/// later.
228/// This second version of the event includes the tips amount to show how much
229/// of the gas fees go to the validators when protocol_defined_base_fee is
230/// enabled in the protocol config.
231#[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}