Skip to main content

iota_types/block/output/
alias.rs

1// Copyright 2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use alloc::vec::Vec;
5
6use packable::{
7    bounded::BoundedU16,
8    error::{UnpackError, UnpackErrorExt},
9    packer::Packer,
10    prefix::BoxedSlicePrefix,
11    unpacker::Unpacker,
12    Packable,
13};
14
15use crate::block::{
16    address::{Address, AliasAddress},
17    output::{
18        feature::{verify_allowed_features, Feature, FeatureFlags, Features},
19        unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions},
20        verify_output_amount, AliasId, ChainId, NativeToken, NativeTokens, Output, OutputBuilderAmount, OutputId, Rent,
21        RentStructure, StateTransitionError, StateTransitionVerifier,
22    },
23    protocol::ProtocolParameters,
24    semantic::{ConflictReason, ValidationContext},
25    unlock::Unlock,
26    Error,
27};
28
29/// Types of alias transition.
30#[derive(Copy, Clone, Debug, Eq, PartialEq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub enum AliasTransition {
33    /// State transition.
34    State,
35    /// Governance transition.
36    Governance,
37}
38
39impl AliasTransition {
40    /// Checks whether the alias transition is a state one.
41    pub fn is_state(&self) -> bool {
42        matches!(self, Self::State)
43    }
44
45    /// Checks whether the alias transition is a governance one.
46    pub fn is_governance(&self) -> bool {
47        matches!(self, Self::Governance)
48    }
49}
50
51impl std::fmt::Display for AliasTransition {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        match self {
54            Self::State => write!(f, "state"),
55            Self::Governance => write!(f, "governance"),
56        }
57    }
58}
59
60///
61#[derive(Clone)]
62#[must_use]
63pub struct AliasOutputBuilder {
64    amount: OutputBuilderAmount,
65    native_tokens: Vec<NativeToken>,
66    alias_id: AliasId,
67    state_index: Option<u32>,
68    state_metadata: Vec<u8>,
69    foundry_counter: Option<u32>,
70    unlock_conditions: Vec<UnlockCondition>,
71    features: Vec<Feature>,
72    immutable_features: Vec<Feature>,
73}
74
75impl AliasOutputBuilder {
76    /// Creates an [`AliasOutputBuilder`] with a provided amount.
77    pub fn new_with_amount(amount: u64, alias_id: AliasId) -> Result<Self, Error> {
78        Self::new(OutputBuilderAmount::Amount(amount), alias_id)
79    }
80
81    /// Creates an [`AliasOutputBuilder`] with a provided rent structure.
82    /// The amount will be set to the minimum storage deposit.
83    pub fn new_with_minimum_storage_deposit(rent_structure: RentStructure, alias_id: AliasId) -> Result<Self, Error> {
84        Self::new(OutputBuilderAmount::MinimumStorageDeposit(rent_structure), alias_id)
85    }
86
87    fn new(amount: OutputBuilderAmount, alias_id: AliasId) -> Result<Self, Error> {
88        Ok(Self {
89            amount,
90            native_tokens: Vec::new(),
91            alias_id,
92            state_index: None,
93            state_metadata: Vec::new(),
94            foundry_counter: None,
95            unlock_conditions: Vec::new(),
96            features: Vec::new(),
97            immutable_features: Vec::new(),
98        })
99    }
100
101    /// Sets the amount to the provided value.
102    #[inline(always)]
103    pub fn with_amount(mut self, amount: u64) -> Result<Self, Error> {
104        self.amount = OutputBuilderAmount::Amount(amount);
105        Ok(self)
106    }
107
108    /// Sets the amount to the minimum storage deposit.
109    #[inline(always)]
110    pub fn with_minimum_storage_deposit(mut self, rent_structure: RentStructure) -> Self {
111        self.amount = OutputBuilderAmount::MinimumStorageDeposit(rent_structure);
112        self
113    }
114
115    ///
116    #[inline(always)]
117    pub fn add_native_token(mut self, native_token: NativeToken) -> Self {
118        self.native_tokens.push(native_token);
119        self
120    }
121
122    ///
123    #[inline(always)]
124    pub fn with_native_tokens(mut self, native_tokens: impl IntoIterator<Item = NativeToken>) -> Self {
125        self.native_tokens = native_tokens.into_iter().collect();
126        self
127    }
128
129    /// Sets the alias ID to the provided value.
130    #[inline(always)]
131    pub fn with_alias_id(mut self, alias_id: AliasId) -> Self {
132        self.alias_id = alias_id;
133        self
134    }
135
136    ///
137    #[inline(always)]
138    pub fn with_state_index(mut self, state_index: u32) -> Self {
139        self.state_index.replace(state_index);
140        self
141    }
142
143    ///
144    #[inline(always)]
145    pub fn with_state_metadata(mut self, state_metadata: Vec<u8>) -> Self {
146        self.state_metadata = state_metadata;
147        self
148    }
149
150    ///
151    #[inline(always)]
152    pub fn with_foundry_counter(mut self, foundry_counter: u32) -> Self {
153        self.foundry_counter.replace(foundry_counter);
154        self
155    }
156
157    ///
158    #[inline(always)]
159    pub fn add_unlock_condition(mut self, unlock_condition: UnlockCondition) -> Self {
160        self.unlock_conditions.push(unlock_condition);
161        self
162    }
163
164    ///
165    #[inline(always)]
166    pub fn with_unlock_conditions(mut self, unlock_conditions: impl IntoIterator<Item = UnlockCondition>) -> Self {
167        self.unlock_conditions = unlock_conditions.into_iter().collect();
168        self
169    }
170
171    ///
172    pub fn replace_unlock_condition(mut self, unlock_condition: UnlockCondition) -> Self {
173        match self
174            .unlock_conditions
175            .iter_mut()
176            .find(|u| u.kind() == unlock_condition.kind())
177        {
178            Some(u) => *u = unlock_condition,
179            None => self.unlock_conditions.push(unlock_condition),
180        }
181        self
182    }
183
184    ///
185    #[inline(always)]
186    pub fn add_feature(mut self, feature: Feature) -> Self {
187        self.features.push(feature);
188        self
189    }
190
191    ///
192    #[inline(always)]
193    pub fn with_features(mut self, features: impl IntoIterator<Item = Feature>) -> Self {
194        self.features = features.into_iter().collect();
195        self
196    }
197
198    ///
199    pub fn replace_feature(mut self, feature: Feature) -> Self {
200        match self.features.iter_mut().find(|f| f.kind() == feature.kind()) {
201            Some(f) => *f = feature,
202            None => self.features.push(feature),
203        }
204        self
205    }
206
207    ///
208    #[inline(always)]
209    pub fn add_immutable_feature(mut self, immutable_feature: Feature) -> Self {
210        self.immutable_features.push(immutable_feature);
211        self
212    }
213
214    ///
215    #[inline(always)]
216    pub fn with_immutable_features(mut self, immutable_features: impl IntoIterator<Item = Feature>) -> Self {
217        self.immutable_features = immutable_features.into_iter().collect();
218        self
219    }
220
221    ///
222    pub fn replace_immutable_feature(mut self, immutable_feature: Feature) -> Self {
223        match self
224            .immutable_features
225            .iter_mut()
226            .find(|f| f.kind() == immutable_feature.kind())
227        {
228            Some(f) => *f = immutable_feature,
229            None => self.immutable_features.push(immutable_feature),
230        }
231        self
232    }
233
234    ///
235    pub fn finish_unverified(self) -> Result<AliasOutput, Error> {
236        let state_index = self.state_index.unwrap_or(0);
237        let foundry_counter = self.foundry_counter.unwrap_or(0);
238
239        let state_metadata = self
240            .state_metadata
241            .into_boxed_slice()
242            .try_into()
243            .map_err(Error::InvalidStateMetadataLength)?;
244
245        verify_index_counter(&self.alias_id, state_index, foundry_counter)?;
246
247        let unlock_conditions = UnlockConditions::new(self.unlock_conditions)?;
248
249        verify_unlock_conditions(&unlock_conditions, &self.alias_id)?;
250
251        let features = Features::new(self.features)?;
252
253        verify_allowed_features(&features, AliasOutput::ALLOWED_FEATURES)?;
254
255        let immutable_features = Features::new(self.immutable_features)?;
256
257        verify_allowed_features(&immutable_features, AliasOutput::ALLOWED_IMMUTABLE_FEATURES)?;
258
259        let mut output = AliasOutput {
260            amount: 1,
261            native_tokens: NativeTokens::new(self.native_tokens)?,
262            alias_id: self.alias_id,
263            state_index,
264            state_metadata,
265            foundry_counter,
266            unlock_conditions,
267            features,
268            immutable_features,
269        };
270
271        output.amount = match self.amount {
272            OutputBuilderAmount::Amount(amount) => amount,
273            OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => {
274                Output::Alias(output.clone()).rent_cost(&rent_structure)
275            }
276        };
277
278        Ok(output)
279    }
280
281    ///
282    pub fn finish(self, token_supply: u64) -> Result<AliasOutput, Error> {
283        let output = self.finish_unverified()?;
284
285        verify_output_amount::<true>(&output.amount, &token_supply)?;
286
287        Ok(output)
288    }
289
290    /// Finishes the [`AliasOutputBuilder`] into an [`Output`].
291    pub fn finish_output(self, token_supply: u64) -> Result<Output, Error> {
292        Ok(Output::Alias(self.finish(token_supply)?))
293    }
294}
295
296impl From<&AliasOutput> for AliasOutputBuilder {
297    fn from(output: &AliasOutput) -> Self {
298        Self {
299            amount: OutputBuilderAmount::Amount(output.amount),
300            native_tokens: output.native_tokens.to_vec(),
301            alias_id: output.alias_id,
302            state_index: Some(output.state_index),
303            state_metadata: output.state_metadata.to_vec(),
304            foundry_counter: Some(output.foundry_counter),
305            unlock_conditions: output.unlock_conditions.to_vec(),
306            features: output.features.to_vec(),
307            immutable_features: output.immutable_features.to_vec(),
308        }
309    }
310}
311
312pub(crate) type StateMetadataLength = BoundedU16<0, { AliasOutput::STATE_METADATA_LENGTH_MAX }>;
313
314/// Describes an alias account in the ledger that can be controlled by the state and governance controllers.
315#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
316#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
317pub struct AliasOutput {
318    // Amount of IOTA tokens held by the output.
319    amount: u64,
320    // Native tokens held by the output.
321    native_tokens: NativeTokens,
322    // Unique identifier of the alias.
323    alias_id: AliasId,
324    // A counter that must increase by 1 every time the alias is state transitioned.
325    state_index: u32,
326    // Metadata that can only be changed by the state controller.
327    state_metadata: BoxedSlicePrefix<u8, StateMetadataLength>,
328    // A counter that denotes the number of foundries created by this alias account.
329    foundry_counter: u32,
330    unlock_conditions: UnlockConditions,
331    //
332    features: Features,
333    //
334    immutable_features: Features,
335}
336
337impl AliasOutput {
338    /// The [`Output`](crate::block::output::Output) kind of an [`AliasOutput`].
339    pub const KIND: u8 = 4;
340    /// Maximum possible length in bytes of the state metadata.
341    pub const STATE_METADATA_LENGTH_MAX: u16 = 8192;
342    /// The set of allowed [`UnlockCondition`]s for an [`AliasOutput`].
343    pub const ALLOWED_UNLOCK_CONDITIONS: UnlockConditionFlags =
344        UnlockConditionFlags::STATE_CONTROLLER_ADDRESS.union(UnlockConditionFlags::GOVERNOR_ADDRESS);
345    /// The set of allowed [`Feature`]s for an [`AliasOutput`].
346    pub const ALLOWED_FEATURES: FeatureFlags = FeatureFlags::SENDER.union(FeatureFlags::METADATA);
347    /// The set of allowed immutable [`Feature`]s for an [`AliasOutput`].
348    pub const ALLOWED_IMMUTABLE_FEATURES: FeatureFlags = FeatureFlags::ISSUER.union(FeatureFlags::METADATA);
349
350    /// Creates a new [`AliasOutput`] with a provided amount.
351    #[inline(always)]
352    pub fn new_with_amount(amount: u64, alias_id: AliasId, token_supply: u64) -> Result<Self, Error> {
353        AliasOutputBuilder::new_with_amount(amount, alias_id)?.finish(token_supply)
354    }
355
356    /// Creates a new [`AliasOutput`] with a provided rent structure.
357    /// The amount will be set to the minimum storage deposit.
358    #[inline(always)]
359    pub fn new_with_minimum_storage_deposit(
360        alias_id: AliasId,
361        rent_structure: RentStructure,
362        token_supply: u64,
363    ) -> Result<Self, Error> {
364        AliasOutputBuilder::new_with_minimum_storage_deposit(rent_structure, alias_id)?.finish(token_supply)
365    }
366
367    /// Creates a new [`AliasOutputBuilder`] with a provided amount.
368    #[inline(always)]
369    pub fn build_with_amount(amount: u64, alias_id: AliasId) -> Result<AliasOutputBuilder, Error> {
370        AliasOutputBuilder::new_with_amount(amount, alias_id)
371    }
372
373    /// Creates a new [`AliasOutputBuilder`] with a provided rent structure.
374    /// The amount will be set to the minimum storage deposit.
375    #[inline(always)]
376    pub fn build_with_minimum_storage_deposit(
377        rent_structure: RentStructure,
378        alias_id: AliasId,
379    ) -> Result<AliasOutputBuilder, Error> {
380        AliasOutputBuilder::new_with_minimum_storage_deposit(rent_structure, alias_id)
381    }
382
383    ///
384    #[inline(always)]
385    pub fn amount(&self) -> u64 {
386        self.amount
387    }
388
389    ///
390    #[inline(always)]
391    pub fn native_tokens(&self) -> &NativeTokens {
392        &self.native_tokens
393    }
394
395    ///
396    #[inline(always)]
397    pub fn alias_id(&self) -> &AliasId {
398        &self.alias_id
399    }
400
401    /// Returns the alias ID if not null, or creates it from the output ID.
402    #[inline(always)]
403    pub fn alias_id_non_null(&self, output_id: &OutputId) -> AliasId {
404        self.alias_id.or_from_output_id(output_id)
405    }
406
407    ///
408    #[inline(always)]
409    pub fn state_index(&self) -> u32 {
410        self.state_index
411    }
412
413    ///
414    #[inline(always)]
415    pub fn state_metadata(&self) -> &[u8] {
416        &self.state_metadata
417    }
418
419    ///
420    #[inline(always)]
421    pub fn foundry_counter(&self) -> u32 {
422        self.foundry_counter
423    }
424
425    ///
426    #[inline(always)]
427    pub fn unlock_conditions(&self) -> &UnlockConditions {
428        &self.unlock_conditions
429    }
430
431    ///
432    #[inline(always)]
433    pub fn features(&self) -> &Features {
434        &self.features
435    }
436
437    ///
438    #[inline(always)]
439    pub fn immutable_features(&self) -> &Features {
440        &self.immutable_features
441    }
442
443    ///
444    #[inline(always)]
445    pub fn state_controller_address(&self) -> &Address {
446        // An AliasOutput must have a StateControllerAddressUnlockCondition.
447        self.unlock_conditions
448            .state_controller_address()
449            .map(|unlock_condition| unlock_condition.address())
450            .unwrap()
451    }
452
453    ///
454    #[inline(always)]
455    pub fn governor_address(&self) -> &Address {
456        // An AliasOutput must have a GovernorAddressUnlockCondition.
457        self.unlock_conditions
458            .governor_address()
459            .map(|unlock_condition| unlock_condition.address())
460            .unwrap()
461    }
462
463    ///
464    #[inline(always)]
465    pub fn chain_id(&self) -> ChainId {
466        ChainId::Alias(self.alias_id)
467    }
468
469    /// Returns the alias address for this output.
470    pub fn alias_address(&self, output_id: &OutputId) -> AliasAddress {
471        AliasAddress::new(self.alias_id_non_null(output_id))
472    }
473
474    ///
475    pub fn unlock(
476        &self,
477        output_id: &OutputId,
478        unlock: &Unlock,
479        inputs: &[(OutputId, &Output)],
480        context: &mut ValidationContext<'_>,
481    ) -> Result<(), ConflictReason> {
482        let alias_id = if self.alias_id().is_null() {
483            AliasId::from(output_id)
484        } else {
485            *self.alias_id()
486        };
487        let next_state = context.output_chains.get(&ChainId::from(alias_id));
488
489        match next_state {
490            Some(Output::Alias(next_state)) => {
491                if self.state_index() == next_state.state_index() {
492                    self.governor_address().unlock(unlock, inputs, context)?;
493                } else {
494                    self.state_controller_address().unlock(unlock, inputs, context)?;
495                    // Only a state transition can be used to consider the alias address for output unlocks and
496                    // sender/issuer validations.
497                    context
498                        .unlocked_addresses
499                        .insert(Address::from(AliasAddress::from(alias_id)));
500                }
501            }
502            None => self.governor_address().unlock(unlock, inputs, context)?,
503            // The next state can only be an alias output since it is identified by an alias chain identifier.
504            Some(_) => unreachable!(),
505        };
506
507        Ok(())
508    }
509}
510
511impl StateTransitionVerifier for AliasOutput {
512    fn creation(next_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> {
513        if !next_state.alias_id.is_null() {
514            return Err(StateTransitionError::NonZeroCreatedId);
515        }
516
517        if let Some(issuer) = next_state.immutable_features().issuer() {
518            if !context.unlocked_addresses.contains(issuer.address()) {
519                return Err(StateTransitionError::IssuerNotUnlocked);
520            }
521        }
522
523        Ok(())
524    }
525
526    fn transition(
527        current_state: &Self,
528        next_state: &Self,
529        context: &ValidationContext<'_>,
530    ) -> Result<(), StateTransitionError> {
531        if current_state.immutable_features != next_state.immutable_features {
532            return Err(StateTransitionError::MutatedImmutableField);
533        }
534
535        if next_state.state_index == current_state.state_index + 1 {
536            // State transition.
537            if current_state.state_controller_address() != next_state.state_controller_address()
538                || current_state.governor_address() != next_state.governor_address()
539                || current_state.features.metadata() != next_state.features.metadata()
540            {
541                return Err(StateTransitionError::MutatedFieldWithoutRights);
542            }
543
544            let created_foundries = context.essence.outputs().iter().filter_map(|output| {
545                if let Output::Foundry(foundry) = output {
546                    if foundry.alias_address().alias_id() == &next_state.alias_id
547                        && !context.input_chains.contains_key(&foundry.chain_id())
548                    {
549                        Some(foundry)
550                    } else {
551                        None
552                    }
553                } else {
554                    None
555                }
556            });
557
558            let mut created_foundries_count = 0;
559
560            for foundry in created_foundries {
561                created_foundries_count += 1;
562
563                if foundry.serial_number() != current_state.foundry_counter + created_foundries_count {
564                    return Err(StateTransitionError::UnsortedCreatedFoundries);
565                }
566            }
567
568            if current_state.foundry_counter + created_foundries_count != next_state.foundry_counter {
569                return Err(StateTransitionError::InconsistentCreatedFoundriesCount);
570            }
571        } else if next_state.state_index == current_state.state_index {
572            // Governance transition.
573            if current_state.amount != next_state.amount
574                || current_state.native_tokens != next_state.native_tokens
575                || current_state.state_metadata != next_state.state_metadata
576                || current_state.foundry_counter != next_state.foundry_counter
577            {
578                return Err(StateTransitionError::MutatedFieldWithoutRights);
579            }
580        } else {
581            return Err(StateTransitionError::UnsupportedStateIndexOperation {
582                current_state: current_state.state_index,
583                next_state: next_state.state_index,
584            });
585        }
586
587        Ok(())
588    }
589
590    fn destruction(_current_state: &Self, _context: &ValidationContext<'_>) -> Result<(), StateTransitionError> {
591        Ok(())
592    }
593}
594
595impl Packable for AliasOutput {
596    type UnpackError = Error;
597    type UnpackVisitor = ProtocolParameters;
598
599    fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error> {
600        self.amount.pack(packer)?;
601        self.native_tokens.pack(packer)?;
602        self.alias_id.pack(packer)?;
603        self.state_index.pack(packer)?;
604        self.state_metadata.pack(packer)?;
605        self.foundry_counter.pack(packer)?;
606        self.unlock_conditions.pack(packer)?;
607        self.features.pack(packer)?;
608        self.immutable_features.pack(packer)?;
609
610        Ok(())
611    }
612
613    fn unpack<U: Unpacker, const VERIFY: bool>(
614        unpacker: &mut U,
615        visitor: &Self::UnpackVisitor,
616    ) -> Result<Self, UnpackError<Self::UnpackError, U::Error>> {
617        let amount = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?;
618
619        verify_output_amount::<VERIFY>(&amount, &visitor.token_supply()).map_err(UnpackError::Packable)?;
620
621        let native_tokens = NativeTokens::unpack::<_, VERIFY>(unpacker, &())?;
622        let alias_id = AliasId::unpack::<_, VERIFY>(unpacker, &()).coerce()?;
623        let state_index = u32::unpack::<_, VERIFY>(unpacker, &()).coerce()?;
624        let state_metadata = BoxedSlicePrefix::<u8, StateMetadataLength>::unpack::<_, VERIFY>(unpacker, &())
625            .map_packable_err(|err| Error::InvalidStateMetadataLength(err.into_prefix_err().into()))?;
626
627        let foundry_counter = u32::unpack::<_, VERIFY>(unpacker, &()).coerce()?;
628
629        if VERIFY {
630            verify_index_counter(&alias_id, state_index, foundry_counter).map_err(UnpackError::Packable)?;
631        }
632
633        let unlock_conditions = UnlockConditions::unpack::<_, VERIFY>(unpacker, visitor)?;
634
635        if VERIFY {
636            verify_unlock_conditions(&unlock_conditions, &alias_id).map_err(UnpackError::Packable)?;
637        }
638
639        let features = Features::unpack::<_, VERIFY>(unpacker, &())?;
640
641        if VERIFY {
642            verify_allowed_features(&features, Self::ALLOWED_FEATURES).map_err(UnpackError::Packable)?;
643        }
644
645        let immutable_features = Features::unpack::<_, VERIFY>(unpacker, &())?;
646
647        if VERIFY {
648            verify_allowed_features(&immutable_features, Self::ALLOWED_IMMUTABLE_FEATURES)
649                .map_err(UnpackError::Packable)?;
650        }
651
652        Ok(Self {
653            amount,
654            native_tokens,
655            alias_id,
656            state_index,
657            state_metadata,
658            foundry_counter,
659            unlock_conditions,
660            features,
661            immutable_features,
662        })
663    }
664}
665
666#[inline]
667fn verify_index_counter(alias_id: &AliasId, state_index: u32, foundry_counter: u32) -> Result<(), Error> {
668    if alias_id.is_null() && (state_index != 0 || foundry_counter != 0) {
669        Err(Error::NonZeroStateIndexOrFoundryCounter)
670    } else {
671        Ok(())
672    }
673}
674
675fn verify_unlock_conditions(unlock_conditions: &UnlockConditions, alias_id: &AliasId) -> Result<(), Error> {
676    if let Some(unlock_condition) = unlock_conditions.state_controller_address() {
677        if let Address::Alias(alias_address) = unlock_condition.address() {
678            if alias_address.alias_id() == alias_id {
679                return Err(Error::SelfControlledAliasOutput(*alias_id));
680            }
681        }
682    } else {
683        return Err(Error::MissingStateControllerUnlockCondition);
684    }
685
686    if let Some(unlock_condition) = unlock_conditions.governor_address() {
687        if let Address::Alias(alias_address) = unlock_condition.address() {
688            if alias_address.alias_id() == alias_id {
689                return Err(Error::SelfControlledAliasOutput(*alias_id));
690            }
691        }
692    } else {
693        return Err(Error::MissingGovernorUnlockCondition);
694    }
695
696    verify_allowed_unlock_conditions(unlock_conditions, AliasOutput::ALLOWED_UNLOCK_CONDITIONS)
697}
698
699#[cfg(feature = "dto")]
700#[allow(missing_docs)]
701pub mod dto {
702    use serde::{Deserialize, Serialize};
703
704    use super::*;
705    use crate::block::{
706        error::dto::DtoError,
707        output::{
708            alias_id::dto::AliasIdDto, dto::OutputBuilderAmountDto, feature::dto::FeatureDto,
709            native_token::dto::NativeTokenDto, unlock_condition::dto::UnlockConditionDto,
710        },
711    };
712
713    /// Describes an alias account in the ledger that can be controlled by the state and governance controllers.
714    #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
715    pub struct AliasOutputDto {
716        #[serde(rename = "type")]
717        pub kind: u8,
718        // Amount of IOTA tokens held by the output.
719        pub amount: String,
720        // Native tokens held by the output.
721        #[serde(rename = "nativeTokens", skip_serializing_if = "Vec::is_empty", default)]
722        pub native_tokens: Vec<NativeTokenDto>,
723        // Unique identifier of the alias.
724        #[serde(rename = "aliasId")]
725        pub alias_id: AliasIdDto,
726        // A counter that must increase by 1 every time the alias is state transitioned.
727        #[serde(rename = "stateIndex")]
728        pub state_index: u32,
729        // Metadata that can only be changed by the state controller.
730        #[serde(rename = "stateMetadata", skip_serializing_if = "String::is_empty", default)]
731        pub state_metadata: String,
732        // A counter that denotes the number of foundries created by this alias account.
733        #[serde(rename = "foundryCounter")]
734        pub foundry_counter: u32,
735        //
736        #[serde(rename = "unlockConditions")]
737        pub unlock_conditions: Vec<UnlockConditionDto>,
738        //
739        #[serde(skip_serializing_if = "Vec::is_empty", default)]
740        pub features: Vec<FeatureDto>,
741        //
742        #[serde(rename = "immutableFeatures", skip_serializing_if = "Vec::is_empty", default)]
743        pub immutable_features: Vec<FeatureDto>,
744    }
745
746    impl From<&AliasOutput> for AliasOutputDto {
747        fn from(value: &AliasOutput) -> Self {
748            Self {
749                kind: AliasOutput::KIND,
750                amount: value.amount().to_string(),
751                native_tokens: value.native_tokens().iter().map(Into::into).collect::<_>(),
752                alias_id: AliasIdDto(value.alias_id().to_string()),
753                state_index: value.state_index(),
754                state_metadata: prefix_hex::encode(value.state_metadata()),
755                foundry_counter: value.foundry_counter(),
756                unlock_conditions: value.unlock_conditions().iter().map(Into::into).collect::<_>(),
757                features: value.features().iter().map(Into::into).collect::<_>(),
758                immutable_features: value.immutable_features().iter().map(Into::into).collect::<_>(),
759            }
760        }
761    }
762
763    impl AliasOutput {
764        fn _try_from_dto(value: &AliasOutputDto) -> Result<AliasOutputBuilder, DtoError> {
765            let mut builder = AliasOutputBuilder::new_with_amount(
766                value
767                    .amount
768                    .parse::<u64>()
769                    .map_err(|_| DtoError::InvalidField("amount"))?,
770                (&value.alias_id).try_into()?,
771            )?;
772
773            builder = builder.with_state_index(value.state_index);
774
775            if !value.state_metadata.is_empty() {
776                builder = builder.with_state_metadata(
777                    prefix_hex::decode(&value.state_metadata).map_err(|_| DtoError::InvalidField("state_metadata"))?,
778                );
779            }
780
781            builder = builder.with_foundry_counter(value.foundry_counter);
782
783            for t in &value.native_tokens {
784                builder = builder.add_native_token(t.try_into()?);
785            }
786
787            for b in &value.features {
788                builder = builder.add_feature(b.try_into()?);
789            }
790
791            for b in &value.immutable_features {
792                builder = builder.add_immutable_feature(b.try_into()?);
793            }
794
795            Ok(builder)
796        }
797
798        pub fn try_from_dto(value: &AliasOutputDto, token_supply: u64) -> Result<Self, DtoError> {
799            let mut builder = Self::_try_from_dto(value)?;
800
801            for u in &value.unlock_conditions {
802                builder = builder.add_unlock_condition(UnlockCondition::try_from_dto(u, token_supply)?);
803            }
804
805            Ok(builder.finish(token_supply)?)
806        }
807
808        pub fn try_from_dto_unverified(value: &AliasOutputDto) -> Result<Self, DtoError> {
809            let mut builder = Self::_try_from_dto(value)?;
810
811            for u in &value.unlock_conditions {
812                builder = builder.add_unlock_condition(UnlockCondition::try_from_dto_unverified(u)?);
813            }
814
815            Ok(builder.finish_unverified()?)
816        }
817
818        #[allow(clippy::too_many_arguments)]
819        pub fn try_from_dtos(
820            amount: OutputBuilderAmountDto,
821            native_tokens: Option<Vec<NativeTokenDto>>,
822            alias_id: &AliasIdDto,
823            state_index: Option<u32>,
824            state_metadata: Option<Vec<u8>>,
825            foundry_counter: Option<u32>,
826            unlock_conditions: Vec<UnlockConditionDto>,
827            features: Option<Vec<FeatureDto>>,
828            immutable_features: Option<Vec<FeatureDto>>,
829            token_supply: u64,
830        ) -> Result<Self, DtoError> {
831            let alias_id = AliasId::try_from(alias_id)?;
832
833            let mut builder = match amount {
834                OutputBuilderAmountDto::Amount(amount) => AliasOutputBuilder::new_with_amount(
835                    amount.parse().map_err(|_| DtoError::InvalidField("amount"))?,
836                    alias_id,
837                )?,
838                OutputBuilderAmountDto::MinimumStorageDeposit(rent_structure) => {
839                    AliasOutputBuilder::new_with_minimum_storage_deposit(rent_structure, alias_id)?
840                }
841            };
842
843            if let Some(native_tokens) = native_tokens {
844                let native_tokens = native_tokens
845                    .iter()
846                    .map(NativeToken::try_from)
847                    .collect::<Result<Vec<NativeToken>, DtoError>>()?;
848                builder = builder.with_native_tokens(native_tokens);
849            }
850
851            if let Some(state_index) = state_index {
852                builder = builder.with_state_index(state_index);
853            }
854
855            if let Some(state_metadata) = state_metadata {
856                builder = builder.with_state_metadata(state_metadata);
857            }
858
859            if let Some(foundry_counter) = foundry_counter {
860                builder = builder.with_foundry_counter(foundry_counter);
861            }
862
863            let unlock_conditions = unlock_conditions
864                .iter()
865                .map(|u| UnlockCondition::try_from_dto(u, token_supply))
866                .collect::<Result<Vec<UnlockCondition>, DtoError>>()?;
867            builder = builder.with_unlock_conditions(unlock_conditions);
868
869            if let Some(features) = features {
870                let features = features
871                    .iter()
872                    .map(Feature::try_from)
873                    .collect::<Result<Vec<Feature>, DtoError>>()?;
874                builder = builder.with_features(features);
875            }
876
877            if let Some(immutable_features) = immutable_features {
878                let immutable_features = immutable_features
879                    .iter()
880                    .map(Feature::try_from)
881                    .collect::<Result<Vec<Feature>, DtoError>>()?;
882                builder = builder.with_immutable_features(immutable_features);
883            }
884
885            Ok(builder.finish(token_supply)?)
886        }
887    }
888}