iota_types/
gas_coin.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{
6    convert::{TryFrom, TryInto},
7    fmt::{Display, Formatter},
8};
9
10use move_core_types::annotated_value::MoveStructLayout;
11use serde::{Deserialize, Serialize};
12
13use crate::{
14    balance::Supply,
15    base_types::{ObjectID, SequenceNumber},
16    coin::{Coin, TreasuryCap},
17    error::{ExecutionError, ExecutionErrorKind},
18    object::{Data, MoveObject, MoveObjectExt, Object},
19};
20
21/// The number of Nanos per IOTA token
22pub const NANOS_PER_IOTA: u64 = 1_000_000_000;
23
24/// Total supply in IOTA at genesis, after the migration from a Stardust ledger,
25/// before any inflation mechanism
26pub const STARDUST_TOTAL_SUPPLY_IOTA: u64 = 4_600_000_000;
27
28// Note: cannot use checked arithmetic here since `const unwrap` is still
29// unstable.
30/// Total supply at genesis denominated in Nanos, after the migration from a
31/// Stardust ledger, before any inflation mechanism
32pub const STARDUST_TOTAL_SUPPLY_NANOS: u64 = STARDUST_TOTAL_SUPPLY_IOTA * NANOS_PER_IOTA;
33
34pub use checked::*;
35
36#[iota_macros::with_checked_arithmetic]
37mod checked {
38    use iota_sdk_types::{StructTag, TypeTag};
39
40    use super::*;
41
42    pub struct GAS {}
43    impl GAS {
44        pub fn type_tag() -> TypeTag {
45            StructTag::new_gas().into()
46        }
47
48        pub fn is_gas_type(other: &TypeTag) -> bool {
49            match other {
50                TypeTag::Struct(s) => s.is_gas(),
51                _ => false,
52            }
53        }
54    }
55
56    /// Rust version of the Move iota::coin::Coin<Iota::iota::IOTA> type
57    #[derive(Clone, Debug, Serialize, Deserialize)]
58    pub struct GasCoin(pub Coin);
59
60    impl GasCoin {
61        pub fn new(id: ObjectID, value: u64) -> Self {
62            Self(Coin::new(id, value))
63        }
64
65        pub fn value(&self) -> u64 {
66            self.0.value()
67        }
68
69        /// Return `true` if `s` is the type of a gas balance (i.e.,
70        /// 0x2::balance::Balance<0x2::iota::IOTA>)
71        pub fn is_gas_balance(s: &StructTag) -> bool {
72            s.is_balance() && GAS::is_gas_type(&s.type_params()[0])
73        }
74
75        pub fn id(&self) -> &ObjectID {
76            self.0.id()
77        }
78
79        pub fn to_bcs_bytes(&self) -> Vec<u8> {
80            bcs::to_bytes(&self).unwrap()
81        }
82
83        pub fn to_object(&self, version: SequenceNumber) -> MoveObject {
84            MoveObject::new_gas_coin(version, *self.id(), self.value())
85        }
86
87        pub fn layout() -> MoveStructLayout {
88            Coin::layout(TypeTag::Struct(Box::new(StructTag::new_gas())))
89        }
90
91        pub fn new_for_testing(value: u64) -> Self {
92            Self::new(ObjectID::random(), value)
93        }
94
95        pub fn new_for_testing_with_id(id: ObjectID, value: u64) -> Self {
96            Self::new(id, value)
97        }
98    }
99
100    impl TryFrom<&MoveObject> for GasCoin {
101        type Error = ExecutionError;
102
103        fn try_from(value: &MoveObject) -> Result<GasCoin, ExecutionError> {
104            if !value.struct_tag().is_gas_coin() {
105                return Err(ExecutionError::new_with_source(
106                    ExecutionErrorKind::InvalidGasObject,
107                    format!("Gas object type is not a gas coin: {}", value.struct_tag()),
108                ));
109            }
110            let gas_coin: GasCoin = bcs::from_bytes(value.contents()).map_err(|err| {
111                ExecutionError::new_with_source(
112                    ExecutionErrorKind::InvalidGasObject,
113                    format!("Unable to deserialize gas object: {err:?}"),
114                )
115            })?;
116            Ok(gas_coin)
117        }
118    }
119
120    impl TryFrom<&Object> for GasCoin {
121        type Error = ExecutionError;
122
123        fn try_from(value: &Object) -> Result<GasCoin, ExecutionError> {
124            match &value.data {
125                Data::Struct(obj) => obj.try_into(),
126                Data::Package(_) => Err(ExecutionError::new_with_source(
127                    ExecutionErrorKind::InvalidGasObject,
128                    format!("Gas object type is not a gas coin: {value:?}"),
129                )),
130            }
131        }
132    }
133
134    impl Display for GasCoin {
135        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
136            write!(f, "Coin {{ id: {}, value: {} }}", self.id(), self.value())
137        }
138    }
139
140    // Rust version of the IotaTreasuryCap type
141    #[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
142    pub struct IotaTreasuryCap {
143        pub inner: TreasuryCap,
144    }
145
146    impl IotaTreasuryCap {
147        /// Returns the `TreasuryCap<IOTA>` object ID.
148        pub fn id(&self) -> &ObjectID {
149            self.inner.id.object_id()
150        }
151
152        /// Returns the total `Supply` of `Coin<IOTA>`.
153        pub fn total_supply(&self) -> &Supply {
154            &self.inner.total_supply
155        }
156    }
157}