1use std::{
6 convert::{TryFrom, TryInto},
7 fmt::{Display, Formatter},
8};
9
10use iota_sdk_types::ObjectId;
11use move_core_types::annotated_value::MoveStructLayout;
12use serde::{Deserialize, Serialize};
13
14use crate::{
15 balance::Supply,
16 base_types::SequenceNumber,
17 coin::{Coin, TreasuryCap},
18 error::{ExecutionError, ExecutionErrorKind},
19 object::{Data, MoveObject, MoveObjectExt, Object},
20};
21
22pub const NANOS_PER_IOTA: u64 = 1_000_000_000;
24
25pub const STARDUST_TOTAL_SUPPLY_IOTA: u64 = 4_600_000_000;
28
29pub const STARDUST_TOTAL_SUPPLY_NANOS: u64 = STARDUST_TOTAL_SUPPLY_IOTA * NANOS_PER_IOTA;
34
35pub use checked::*;
36
37#[iota_macros::with_checked_arithmetic]
38mod checked {
39 use iota_sdk_types::{StructTag, TypeTag};
40
41 use super::*;
42
43 pub struct GAS {}
44 impl GAS {
45 pub fn type_tag() -> TypeTag {
46 StructTag::new_gas().into()
47 }
48
49 pub fn is_gas_type(other: &TypeTag) -> bool {
50 match other {
51 TypeTag::Struct(s) => s.is_gas(),
52 _ => false,
53 }
54 }
55 }
56
57 #[derive(Clone, Debug, Serialize, Deserialize)]
59 pub struct GasCoin(pub Coin);
60
61 impl GasCoin {
62 pub fn new(id: ObjectId, value: u64) -> Self {
63 Self(Coin::new(id, value))
64 }
65
66 pub fn value(&self) -> u64 {
67 self.0.value()
68 }
69
70 pub fn is_gas_balance(s: &StructTag) -> bool {
73 s.is_balance() && GAS::is_gas_type(&s.type_params()[0])
74 }
75
76 pub fn id(&self) -> &ObjectId {
77 self.0.id()
78 }
79
80 pub fn to_bcs_bytes(&self) -> Vec<u8> {
81 bcs::to_bytes(&self).unwrap()
82 }
83
84 pub fn to_object(&self, version: SequenceNumber) -> MoveObject {
85 MoveObject::new_gas_coin(version, *self.id(), self.value())
86 }
87
88 pub fn layout() -> MoveStructLayout {
89 Coin::layout(TypeTag::Struct(Box::new(StructTag::new_gas())))
90 }
91
92 pub fn new_for_testing(value: u64) -> Self {
93 Self::new(ObjectId::random(), value)
94 }
95
96 pub fn new_for_testing_with_id(id: ObjectId, value: u64) -> Self {
97 Self::new(id, value)
98 }
99 }
100
101 impl TryFrom<&MoveObject> for GasCoin {
102 type Error = ExecutionError;
103
104 fn try_from(value: &MoveObject) -> Result<GasCoin, ExecutionError> {
105 if !value.struct_tag().is_gas_coin() {
106 return Err(ExecutionError::new_with_source(
107 ExecutionErrorKind::InvalidGasObject,
108 format!("Gas object type is not a gas coin: {}", value.struct_tag()),
109 ));
110 }
111 let gas_coin: GasCoin = bcs::from_bytes(value.contents()).map_err(|err| {
112 ExecutionError::new_with_source(
113 ExecutionErrorKind::InvalidGasObject,
114 format!("Unable to deserialize gas object: {err:?}"),
115 )
116 })?;
117 Ok(gas_coin)
118 }
119 }
120
121 impl TryFrom<&Object> for GasCoin {
122 type Error = ExecutionError;
123
124 fn try_from(value: &Object) -> Result<GasCoin, ExecutionError> {
125 match &value.data {
126 Data::Struct(obj) => obj.try_into(),
127 Data::Package(_) => Err(ExecutionError::new_with_source(
128 ExecutionErrorKind::InvalidGasObject,
129 format!("Gas object type is not a gas coin: {value:?}"),
130 )),
131 }
132 }
133 }
134
135 impl Display for GasCoin {
136 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
137 write!(f, "Coin {{ id: {}, value: {} }}", self.id(), self.value())
138 }
139 }
140
141 #[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
143 pub struct IotaTreasuryCap {
144 pub inner: TreasuryCap,
145 }
146
147 impl IotaTreasuryCap {
148 pub fn id(&self) -> &ObjectId {
150 self.inner.id.object_id()
151 }
152
153 pub fn total_supply(&self) -> &Supply {
155 &self.inner.total_supply
156 }
157 }
158}