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