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 transaction digest
43/// and event seq number.
44#[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/// Specific type of event
102#[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/// Event emitted in move code `fun advance_epoch` in protocol versions 1 to 3
210#[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/// Event emitted in move code `fun advance_epoch` in protocol versions 5 and
226/// later.
227/// This second version of the event includes the tips amount to show how much
228/// of the gas fees go to the validators when protocol_defined_base_fee is
229/// enabled in the protocol config.
230#[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}