iota_types/stardust/output/
basic.rs1use anyhow::Result;
8use iota_protocol_config::ProtocolConfig;
9use move_core_types::{ident_str, identifier::IdentStr, language_storage::StructTag};
10use serde::{Deserialize, Serialize};
11use serde_with::serde_as;
12
13use super::unlock_conditions::{
14 ExpirationUnlockCondition, StorageDepositReturnUnlockCondition, TimelockUnlockCondition,
15};
16use crate::{
17 STARDUST_ADDRESS, TypeTag,
18 balance::Balance,
19 base_types::{IotaAddress, MoveObjectType, ObjectID, SequenceNumber, TxContext},
20 coin::Coin,
21 collection_types::Bag,
22 error::IotaError,
23 id::UID,
24 object::{Data, MoveObject, Object, Owner},
25 stardust::{coin_type::CoinType, stardust_to_iota_address},
26};
27
28pub const BASIC_OUTPUT_MODULE_NAME: &IdentStr = ident_str!("basic_output");
29pub const BASIC_OUTPUT_STRUCT_NAME: &IdentStr = ident_str!("BasicOutput");
30
31#[serde_as]
33#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
34pub struct BasicOutput {
35 pub id: UID,
37
38 pub balance: Balance,
40
41 pub native_tokens: Bag,
45
46 pub storage_deposit_return: Option<StorageDepositReturnUnlockCondition>,
48 pub timelock: Option<TimelockUnlockCondition>,
50 pub expiration: Option<ExpirationUnlockCondition>,
52
53 pub metadata: Option<Vec<u8>>,
57 pub tag: Option<Vec<u8>>,
59 pub sender: Option<IotaAddress>,
61}
62
63impl BasicOutput {
64 pub fn new(
68 header_object_id: ObjectID,
69 output: &iota_stardust_sdk::types::block::output::BasicOutput,
70 ) -> Result<Self> {
71 let id = UID::new(header_object_id);
72 let balance = Balance::new(output.amount());
73 let native_tokens = Default::default();
74 let unlock_conditions = output.unlock_conditions();
75 let storage_deposit_return = unlock_conditions
76 .storage_deposit_return()
77 .map(|unlock| unlock.try_into())
78 .transpose()?;
79 let timelock = unlock_conditions.timelock().map(|unlock| unlock.into());
80 let expiration = output
81 .unlock_conditions()
82 .expiration()
83 .map(|expiration| ExpirationUnlockCondition::new(output.address(), expiration))
84 .transpose()?;
85 let metadata = output
86 .features()
87 .metadata()
88 .map(|metadata| metadata.data().to_vec());
89 let tag = output.features().tag().map(|tag| tag.tag().to_vec());
90 let sender = output
91 .features()
92 .sender()
93 .map(|sender| stardust_to_iota_address(sender.address()))
94 .transpose()?;
95
96 Ok(BasicOutput {
97 id,
98 balance,
99 native_tokens,
100 storage_deposit_return,
101 timelock,
102 expiration,
103 metadata,
104 tag,
105 sender,
106 })
107 }
108
109 pub fn tag(type_param: TypeTag) -> StructTag {
111 StructTag {
112 address: STARDUST_ADDRESS,
113 module: BASIC_OUTPUT_MODULE_NAME.to_owned(),
114 name: BASIC_OUTPUT_STRUCT_NAME.to_owned(),
115 type_params: vec![type_param],
116 }
117 }
118
119 pub fn is_simple_coin(&self, target_milestone_timestamp_sec: u32) -> bool {
125 !(self.expiration.is_some()
126 || self.storage_deposit_return.is_some()
127 || self
128 .timelock
129 .as_ref()
130 .is_some_and(|timelock| target_milestone_timestamp_sec < timelock.unix_time)
131 || self.metadata.is_some()
132 || self.tag.is_some()
133 || self.sender.is_some())
134 }
135
136 pub fn to_genesis_object(
137 &self,
138 owner: IotaAddress,
139 protocol_config: &ProtocolConfig,
140 tx_context: &TxContext,
141 version: SequenceNumber,
142 coin_type: &CoinType,
143 ) -> Result<Object> {
144 let move_object = {
145 MoveObject::new_from_execution(
146 BasicOutput::tag(coin_type.to_type_tag()).into(),
147 version,
148 bcs::to_bytes(self)?,
149 protocol_config,
150 )?
151 };
152 let owner = if self.expiration.is_some() {
154 Owner::Shared {
155 initial_shared_version: version,
156 }
157 } else {
158 Owner::AddressOwner(owner)
159 };
160 Ok(Object::new_from_genesis(
161 Data::Move(move_object),
162 owner,
163 tx_context.digest(),
164 ))
165 }
166
167 pub fn into_genesis_coin_object(
168 self,
169 owner: IotaAddress,
170 protocol_config: &ProtocolConfig,
171 tx_context: &TxContext,
172 version: SequenceNumber,
173 coin_type: &CoinType,
174 ) -> Result<Object> {
175 create_coin(
176 self.id,
177 owner,
178 self.balance.value(),
179 tx_context,
180 version,
181 protocol_config,
182 coin_type,
183 )
184 }
185
186 pub fn from_bcs_bytes(content: &[u8]) -> Result<Self, IotaError> {
188 bcs::from_bytes(content).map_err(|err| IotaError::ObjectDeserialization {
189 error: format!("Unable to deserialize BasicOutput object: {:?}", err),
190 })
191 }
192
193 pub fn is_basic_output(s: &StructTag) -> bool {
195 s.address == STARDUST_ADDRESS
196 && s.module.as_ident_str() == BASIC_OUTPUT_MODULE_NAME
197 && s.name.as_ident_str() == BASIC_OUTPUT_STRUCT_NAME
198 }
199}
200
201pub(crate) fn create_coin(
202 object_id: UID,
203 owner: IotaAddress,
204 amount: u64,
205 tx_context: &TxContext,
206 version: SequenceNumber,
207 protocol_config: &ProtocolConfig,
208 coin_type: &CoinType,
209) -> Result<Object> {
210 let coin = Coin::new(object_id, amount);
211 let move_object = {
212 MoveObject::new_from_execution(
213 MoveObjectType::from(Coin::type_(coin_type.to_type_tag())),
214 version,
215 bcs::to_bytes(&coin)?,
216 protocol_config,
217 )?
218 };
219 let owner = Owner::AddressOwner(owner);
221 Ok(Object::new_from_genesis(
222 Data::Move(move_object),
223 owner,
224 tx_context.digest(),
225 ))
226}
227
228impl TryFrom<&Object> for BasicOutput {
229 type Error = IotaError;
230 fn try_from(object: &Object) -> Result<Self, Self::Error> {
231 match &object.data {
232 Data::Move(o) => {
233 if o.type_().is_basic_output() {
234 return BasicOutput::from_bcs_bytes(o.contents());
235 }
236 }
237 Data::Package(_) => {}
238 }
239
240 Err(IotaError::Type {
241 error: format!("Object type is not a BasicOutput: {:?}", object),
242 })
243 }
244}