iota_types/stardust/output/
alias.rs

1// Copyright (c) 2024 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use iota_protocol_config::ProtocolConfig;
5use iota_stardust_sdk::types::block::output::AliasOutput as StardustAlias;
6use move_core_types::{ident_str, identifier::IdentStr, language_storage::StructTag};
7use serde::{Deserialize, Serialize};
8use serde_with::serde_as;
9
10use crate::{
11    STARDUST_ADDRESS, TypeTag,
12    balance::Balance,
13    base_types::{IotaAddress, ObjectID, SequenceNumber, TxContext},
14    collection_types::Bag,
15    error::IotaError,
16    id::UID,
17    object::{Data, MoveObject, Object, Owner},
18    stardust::{coin_type::CoinType, stardust_to_iota_address},
19};
20
21pub const ALIAS_MODULE_NAME: &IdentStr = ident_str!("alias");
22pub const ALIAS_OUTPUT_MODULE_NAME: &IdentStr = ident_str!("alias_output");
23pub const ALIAS_OUTPUT_STRUCT_NAME: &IdentStr = ident_str!("AliasOutput");
24pub const ALIAS_STRUCT_NAME: &IdentStr = ident_str!("Alias");
25pub const ALIAS_DYNAMIC_OBJECT_FIELD_KEY: &[u8] = b"alias";
26pub const ALIAS_DYNAMIC_OBJECT_FIELD_KEY_TYPE: &str = "vector<u8>";
27
28#[serde_as]
29#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
30pub struct Alias {
31    /// The ID of the Alias = hash of the Output ID that created the Alias
32    /// Output in Stardust. This is the AliasID from Stardust.
33    pub id: UID,
34
35    /// The last State Controller address assigned before the migration.
36    pub legacy_state_controller: IotaAddress,
37    /// A counter increased by 1 every time the alias was state transitioned.
38    pub state_index: u32,
39    /// State metadata that can be used to store additional information.
40    pub state_metadata: Option<Vec<u8>>,
41
42    /// The sender feature.
43    pub sender: Option<IotaAddress>,
44    /// The metadata feature.
45    pub metadata: Option<Vec<u8>>,
46
47    /// The immutable issuer feature.
48    pub immutable_issuer: Option<IotaAddress>,
49    /// The immutable metadata feature.
50    pub immutable_metadata: Option<Vec<u8>>,
51}
52
53impl Alias {
54    /// Returns the struct tag that represents the fully qualified path of an
55    /// [`Alias`] in its move package.
56    pub fn tag() -> StructTag {
57        StructTag {
58            address: STARDUST_ADDRESS,
59            module: ALIAS_MODULE_NAME.to_owned(),
60            name: ALIAS_STRUCT_NAME.to_owned(),
61            type_params: Vec::new(),
62        }
63    }
64
65    /// Creates the Move-based Alias model from a Stardust-based Alias Output.
66    pub fn try_from_stardust(
67        alias_id: ObjectID,
68        alias: &StardustAlias,
69    ) -> Result<Self, anyhow::Error> {
70        if alias_id.as_ref() == [0; 32] {
71            anyhow::bail!("alias_id must be non-zeroed");
72        }
73
74        let state_metadata: Option<Vec<u8>> = if alias.state_metadata().is_empty() {
75            None
76        } else {
77            Some(alias.state_metadata().to_vec())
78        };
79        let sender: Option<IotaAddress> = alias
80            .features()
81            .sender()
82            .map(|sender_feat| stardust_to_iota_address(sender_feat.address()))
83            .transpose()?;
84        let metadata: Option<Vec<u8>> = alias
85            .features()
86            .metadata()
87            .map(|metadata_feat| metadata_feat.data().to_vec());
88        let immutable_issuer: Option<IotaAddress> = alias
89            .immutable_features()
90            .issuer()
91            .map(|issuer_feat| stardust_to_iota_address(issuer_feat.address()))
92            .transpose()?;
93        let immutable_metadata: Option<Vec<u8>> = alias
94            .immutable_features()
95            .metadata()
96            .map(|metadata_feat| metadata_feat.data().to_vec());
97
98        Ok(Alias {
99            id: UID::new(alias_id),
100            legacy_state_controller: stardust_to_iota_address(alias.state_controller_address())?,
101            state_index: alias.state_index(),
102            state_metadata,
103            sender,
104            metadata,
105            immutable_issuer,
106            immutable_metadata,
107        })
108    }
109
110    pub fn to_genesis_object(
111        &self,
112        owner: Owner,
113        protocol_config: &ProtocolConfig,
114        tx_context: &TxContext,
115        version: SequenceNumber,
116    ) -> anyhow::Result<Object> {
117        // Construct the Alias object.
118        let move_alias_object = {
119            MoveObject::new_from_execution(
120                Self::tag().into(),
121                version,
122                bcs::to_bytes(&self)?,
123                protocol_config,
124            )?
125        };
126
127        let move_alias_object = Object::new_from_genesis(
128            Data::Move(move_alias_object),
129            // We will later overwrite the owner we set here since this object will be added
130            // as a dynamic field on the alias output object.
131            owner,
132            tx_context.digest(),
133        );
134
135        Ok(move_alias_object)
136    }
137}
138
139#[serde_as]
140#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
141pub struct AliasOutput {
142    /// This is a "random" UID, not the AliasID from Stardust.
143    pub id: UID,
144
145    /// The amount of coins held by the output.
146    pub balance: Balance,
147    /// The `Bag` holds native tokens, key-ed by the stringified type of the
148    /// asset. Example: key: "0xabcded::soon::SOON", value:
149    /// Balance<0xabcded::soon::SOON>.
150    pub native_tokens: Bag,
151}
152
153impl AliasOutput {
154    /// Returns the struct tag that represents the fully qualified path of an
155    /// [`AliasOutput`] in its move package.
156    pub fn tag(type_param: TypeTag) -> StructTag {
157        StructTag {
158            address: STARDUST_ADDRESS,
159            module: ALIAS_OUTPUT_MODULE_NAME.to_owned(),
160            name: ALIAS_OUTPUT_STRUCT_NAME.to_owned(),
161            type_params: vec![type_param],
162        }
163    }
164
165    /// Creates the Move-based Alias Output model from a Stardust-based Alias
166    /// Output.
167    pub fn try_from_stardust(
168        object_id: ObjectID,
169        alias: &StardustAlias,
170        native_tokens: Bag,
171    ) -> Result<Self, anyhow::Error> {
172        Ok(AliasOutput {
173            id: UID::new(object_id),
174            balance: Balance::new(alias.amount()),
175            native_tokens,
176        })
177    }
178
179    pub fn to_genesis_object(
180        &self,
181        owner: Owner,
182        protocol_config: &ProtocolConfig,
183        tx_context: &TxContext,
184        version: SequenceNumber,
185        coin_type: CoinType,
186    ) -> anyhow::Result<Object> {
187        // Construct the Alias Output object.
188        let move_alias_output_object = {
189            MoveObject::new_from_execution(
190                AliasOutput::tag(coin_type.to_type_tag()).into(),
191                version,
192                bcs::to_bytes(&self)?,
193                protocol_config,
194            )?
195        };
196
197        let move_alias_output_object = Object::new_from_genesis(
198            Data::Move(move_alias_output_object),
199            owner,
200            tx_context.digest(),
201        );
202
203        Ok(move_alias_output_object)
204    }
205
206    /// Create an `AliasOutput` from BCS bytes.
207    pub fn from_bcs_bytes(content: &[u8]) -> Result<Self, IotaError> {
208        bcs::from_bytes(content).map_err(|err| IotaError::ObjectDeserialization {
209            error: format!("Unable to deserialize AliasOutput object: {:?}", err),
210        })
211    }
212
213    pub fn is_alias_output(s: &StructTag) -> bool {
214        s.address == STARDUST_ADDRESS
215            && s.module.as_ident_str() == ALIAS_OUTPUT_MODULE_NAME
216            && s.name.as_ident_str() == ALIAS_OUTPUT_STRUCT_NAME
217    }
218}
219
220impl TryFrom<&Object> for AliasOutput {
221    type Error = IotaError;
222    fn try_from(object: &Object) -> Result<Self, Self::Error> {
223        match &object.data {
224            Data::Move(o) => {
225                if o.type_().is_alias_output() {
226                    return AliasOutput::from_bcs_bytes(o.contents());
227                }
228            }
229            Data::Package(_) => {}
230        }
231
232        Err(IotaError::Type {
233            error: format!("Object type is not an AliasOutput: {:?}", object),
234        })
235    }
236}