bee_block/output/
foundry.rs

1// Copyright 2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use alloc::vec::Vec;
5use core::cmp::Ordering;
6
7use packable::{
8    error::{UnpackError, UnpackErrorExt},
9    packer::Packer,
10    unpacker::Unpacker,
11    Packable,
12};
13
14use crate::{
15    address::{Address, AliasAddress},
16    output::{
17        feature::{verify_allowed_features, Feature, FeatureFlags, Features},
18        unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions},
19        verify_output_amount, ChainId, FoundryId, NativeToken, NativeTokens, Output, OutputBuilderAmount, OutputId,
20        Rent, RentStructure, StateTransitionError, StateTransitionVerifier, TokenId, TokenScheme,
21    },
22    protocol::ProtocolParameters,
23    semantic::{ConflictReason, ValidationContext},
24    unlock::Unlock,
25    Error,
26};
27
28///
29#[derive(Clone)]
30#[must_use]
31pub struct FoundryOutputBuilder {
32    amount: OutputBuilderAmount,
33    native_tokens: Vec<NativeToken>,
34    serial_number: u32,
35    token_scheme: TokenScheme,
36    unlock_conditions: Vec<UnlockCondition>,
37    features: Vec<Feature>,
38    immutable_features: Vec<Feature>,
39}
40
41impl FoundryOutputBuilder {
42    /// Creates a [`FoundryOutputBuilder`] with a provided amount.
43    pub fn new_with_amount(
44        amount: u64,
45        serial_number: u32,
46        token_scheme: TokenScheme,
47    ) -> Result<FoundryOutputBuilder, Error> {
48        Self::new(OutputBuilderAmount::Amount(amount), serial_number, token_scheme)
49    }
50
51    /// Creates a [`FoundryOutputBuilder`] with a provided rent structure.
52    /// The amount will be set to the minimum storage deposit.
53    pub fn new_with_minimum_storage_deposit(
54        rent_structure: RentStructure,
55        serial_number: u32,
56        token_scheme: TokenScheme,
57    ) -> Result<FoundryOutputBuilder, Error> {
58        Self::new(
59            OutputBuilderAmount::MinimumStorageDeposit(rent_structure),
60            serial_number,
61            token_scheme,
62        )
63    }
64
65    fn new(
66        amount: OutputBuilderAmount,
67        serial_number: u32,
68        token_scheme: TokenScheme,
69    ) -> Result<FoundryOutputBuilder, Error> {
70        Ok(Self {
71            amount,
72            native_tokens: Vec::new(),
73            serial_number,
74            token_scheme,
75            unlock_conditions: Vec::new(),
76            features: Vec::new(),
77            immutable_features: Vec::new(),
78        })
79    }
80
81    /// Sets the amount to the provided value.
82    #[inline(always)]
83    pub fn with_amount(mut self, amount: u64) -> Result<Self, Error> {
84        self.amount = OutputBuilderAmount::Amount(amount);
85        Ok(self)
86    }
87
88    /// Sets the amount to the minimum storage deposit.
89    #[inline(always)]
90    pub fn with_minimum_storage_deposit(mut self, rent_structure: RentStructure) -> Self {
91        self.amount = OutputBuilderAmount::MinimumStorageDeposit(rent_structure);
92        self
93    }
94
95    ///
96    #[inline(always)]
97    pub fn add_native_token(mut self, native_token: NativeToken) -> Self {
98        self.native_tokens.push(native_token);
99        self
100    }
101
102    ///
103    #[inline(always)]
104    pub fn with_native_tokens(mut self, native_tokens: impl IntoIterator<Item = NativeToken>) -> Self {
105        self.native_tokens = native_tokens.into_iter().collect();
106        self
107    }
108
109    /// Sets the serial number to the provided value.
110    #[inline(always)]
111    pub fn with_serial_number(mut self, serial_number: u32) -> Self {
112        self.serial_number = serial_number;
113        self
114    }
115
116    /// Sets the token scheme to the provided value.
117    #[inline(always)]
118    pub fn with_token_scheme(mut self, token_scheme: TokenScheme) -> Self {
119        self.token_scheme = token_scheme;
120        self
121    }
122
123    ///
124    #[inline(always)]
125    pub fn add_unlock_condition(mut self, unlock_condition: UnlockCondition) -> Self {
126        self.unlock_conditions.push(unlock_condition);
127        self
128    }
129
130    ///
131    #[inline(always)]
132    pub fn with_unlock_conditions(mut self, unlock_conditions: impl IntoIterator<Item = UnlockCondition>) -> Self {
133        self.unlock_conditions = unlock_conditions.into_iter().collect();
134        self
135    }
136
137    ///
138    pub fn replace_unlock_condition(mut self, unlock_condition: UnlockCondition) -> Result<Self, Error> {
139        match self
140            .unlock_conditions
141            .iter_mut()
142            .find(|u| u.kind() == unlock_condition.kind())
143        {
144            Some(u) => *u = unlock_condition,
145            None => return Err(Error::CannotReplaceMissingField),
146        }
147        Ok(self)
148    }
149
150    ///
151    #[inline(always)]
152    pub fn add_feature(mut self, feature: Feature) -> Self {
153        self.features.push(feature);
154        self
155    }
156
157    ///
158    #[inline(always)]
159    pub fn with_features(mut self, features: impl IntoIterator<Item = Feature>) -> Self {
160        self.features = features.into_iter().collect();
161        self
162    }
163
164    ///
165    pub fn replace_feature(mut self, feature: Feature) -> Result<Self, Error> {
166        match self.features.iter_mut().find(|f| f.kind() == feature.kind()) {
167            Some(f) => *f = feature,
168            None => return Err(Error::CannotReplaceMissingField),
169        }
170        Ok(self)
171    }
172
173    ///
174    #[inline(always)]
175    pub fn add_immutable_feature(mut self, immutable_feature: Feature) -> Self {
176        self.immutable_features.push(immutable_feature);
177        self
178    }
179
180    ///
181    #[inline(always)]
182    pub fn with_immutable_features(mut self, immutable_features: impl IntoIterator<Item = Feature>) -> Self {
183        self.immutable_features = immutable_features.into_iter().collect();
184        self
185    }
186
187    ///
188    pub fn replace_immutable_feature(mut self, immutable_feature: Feature) -> Result<Self, Error> {
189        match self
190            .immutable_features
191            .iter_mut()
192            .find(|f| f.kind() == immutable_feature.kind())
193        {
194            Some(f) => *f = immutable_feature,
195            None => return Err(Error::CannotReplaceMissingField),
196        }
197        Ok(self)
198    }
199
200    ///
201    pub fn finish(self, token_supply: u64) -> Result<FoundryOutput, Error> {
202        let unlock_conditions = UnlockConditions::new(self.unlock_conditions)?;
203
204        verify_unlock_conditions(&unlock_conditions)?;
205
206        let features = Features::new(self.features)?;
207
208        verify_allowed_features(&features, FoundryOutput::ALLOWED_FEATURES)?;
209
210        let immutable_features = Features::new(self.immutable_features)?;
211
212        verify_allowed_features(&immutable_features, FoundryOutput::ALLOWED_IMMUTABLE_FEATURES)?;
213
214        let mut output = FoundryOutput {
215            amount: 1u64,
216            native_tokens: NativeTokens::new(self.native_tokens)?,
217            serial_number: self.serial_number,
218            token_scheme: self.token_scheme,
219            unlock_conditions,
220            features,
221            immutable_features,
222        };
223
224        output.amount = match self.amount {
225            OutputBuilderAmount::Amount(amount) => amount,
226            OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => {
227                Output::Foundry(output.clone()).rent_cost(&rent_structure)
228            }
229        };
230
231        verify_output_amount::<true>(&output.amount, &token_supply)?;
232
233        Ok(output)
234    }
235
236    /// Finishes the [`FoundryOutputBuilder`] into an [`Output`].
237    pub fn finish_output(self, token_supply: u64) -> Result<Output, Error> {
238        Ok(Output::Foundry(self.finish(token_supply)?))
239    }
240}
241
242impl From<&FoundryOutput> for FoundryOutputBuilder {
243    fn from(output: &FoundryOutput) -> Self {
244        FoundryOutputBuilder {
245            amount: OutputBuilderAmount::Amount(output.amount),
246            native_tokens: output.native_tokens.to_vec(),
247            serial_number: output.serial_number,
248            token_scheme: output.token_scheme.clone(),
249            unlock_conditions: output.unlock_conditions.to_vec(),
250            features: output.features.to_vec(),
251            immutable_features: output.immutable_features.to_vec(),
252        }
253    }
254}
255
256/// Describes a foundry output that is controlled by an alias.
257#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
258#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
259pub struct FoundryOutput {
260    // Amount of IOTA tokens held by the output.
261    amount: u64,
262    // Native tokens held by the output.
263    native_tokens: NativeTokens,
264    // The serial number of the foundry with respect to the controlling alias.
265    serial_number: u32,
266    token_scheme: TokenScheme,
267    unlock_conditions: UnlockConditions,
268    features: Features,
269    immutable_features: Features,
270}
271
272impl FoundryOutput {
273    /// The [`Output`](crate::output::Output) kind of a [`FoundryOutput`].
274    pub const KIND: u8 = 5;
275    /// The set of allowed [`UnlockCondition`]s for a [`FoundryOutput`].
276    pub const ALLOWED_UNLOCK_CONDITIONS: UnlockConditionFlags = UnlockConditionFlags::IMMUTABLE_ALIAS_ADDRESS;
277    /// The set of allowed [`Feature`]s for a [`FoundryOutput`].
278    pub const ALLOWED_FEATURES: FeatureFlags = FeatureFlags::METADATA;
279    /// The set of allowed immutable [`Feature`]s for a [`FoundryOutput`].
280    pub const ALLOWED_IMMUTABLE_FEATURES: FeatureFlags = FeatureFlags::METADATA;
281
282    /// Creates a new [`FoundryOutput`] with a provided amount.
283    #[inline(always)]
284    pub fn new_with_amount(
285        amount: u64,
286        serial_number: u32,
287        token_scheme: TokenScheme,
288        token_supply: u64,
289    ) -> Result<Self, Error> {
290        FoundryOutputBuilder::new_with_amount(amount, serial_number, token_scheme)?.finish(token_supply)
291    }
292
293    /// Creates a new [`FoundryOutput`] with a provided rent structure.
294    /// The amount will be set to the minimum storage deposit.
295    #[inline(always)]
296    pub fn new_with_minimum_storage_deposit(
297        serial_number: u32,
298        token_scheme: TokenScheme,
299        rent_structure: RentStructure,
300        token_supply: u64,
301    ) -> Result<Self, Error> {
302        FoundryOutputBuilder::new_with_minimum_storage_deposit(rent_structure, serial_number, token_scheme)?
303            .finish(token_supply)
304    }
305
306    /// Creates a new [`FoundryOutputBuilder`] with a provided amount.
307    #[inline(always)]
308    pub fn build_with_amount(
309        amount: u64,
310        serial_number: u32,
311        token_scheme: TokenScheme,
312    ) -> Result<FoundryOutputBuilder, Error> {
313        FoundryOutputBuilder::new_with_amount(amount, serial_number, token_scheme)
314    }
315
316    /// Creates a new [`FoundryOutputBuilder`] with a provided rent structure.
317    /// The amount will be set to the minimum storage deposit.
318    #[inline(always)]
319    pub fn build_with_minimum_storage_deposit(
320        rent_structure: RentStructure,
321        serial_number: u32,
322        token_scheme: TokenScheme,
323    ) -> Result<FoundryOutputBuilder, Error> {
324        FoundryOutputBuilder::new_with_minimum_storage_deposit(rent_structure, serial_number, token_scheme)
325    }
326
327    ///
328    #[inline(always)]
329    pub fn amount(&self) -> u64 {
330        self.amount
331    }
332
333    ///
334    #[inline(always)]
335    pub fn native_tokens(&self) -> &NativeTokens {
336        &self.native_tokens
337    }
338
339    ///
340    #[inline(always)]
341    pub fn serial_number(&self) -> u32 {
342        self.serial_number
343    }
344
345    ///
346    #[inline(always)]
347    pub fn token_scheme(&self) -> &TokenScheme {
348        &self.token_scheme
349    }
350
351    ///
352    #[inline(always)]
353    pub fn unlock_conditions(&self) -> &UnlockConditions {
354        &self.unlock_conditions
355    }
356
357    ///
358    #[inline(always)]
359    pub fn features(&self) -> &Features {
360        &self.features
361    }
362
363    ///
364    #[inline(always)]
365    pub fn immutable_features(&self) -> &Features {
366        &self.immutable_features
367    }
368
369    ///
370    #[inline(always)]
371    pub fn alias_address(&self) -> &AliasAddress {
372        // A FoundryOutput must have an ImmutableAliasAddressUnlockCondition.
373        self.unlock_conditions
374            .immutable_alias_address()
375            .map(|unlock_condition| unlock_condition.alias_address())
376            .unwrap()
377    }
378
379    /// Returns the [`FoundryId`] of the [`FoundryOutput`].
380    pub fn id(&self) -> FoundryId {
381        FoundryId::build(self.alias_address(), self.serial_number, self.token_scheme.kind())
382    }
383
384    /// Returns the [`TokenId`] of the [`FoundryOutput`].
385    pub fn token_id(&self) -> TokenId {
386        TokenId::from(self.id())
387    }
388
389    ///
390    #[inline(always)]
391    pub fn chain_id(&self) -> ChainId {
392        ChainId::Foundry(self.id())
393    }
394
395    ///
396    pub fn unlock(
397        &self,
398        _output_id: &OutputId,
399        unlock: &Unlock,
400        inputs: &[(OutputId, &Output)],
401        context: &mut ValidationContext,
402    ) -> Result<(), ConflictReason> {
403        Address::from(*self.alias_address()).unlock(unlock, inputs, context)
404    }
405}
406
407impl StateTransitionVerifier for FoundryOutput {
408    fn creation(next_state: &Self, context: &ValidationContext) -> Result<(), StateTransitionError> {
409        let alias_chain_id = ChainId::from(*next_state.alias_address().alias_id());
410
411        if let (Some(Output::Alias(input_alias)), Some(Output::Alias(output_alias))) = (
412            context.input_chains.get(&alias_chain_id),
413            context.output_chains.get(&alias_chain_id),
414        ) {
415            if input_alias.foundry_counter() >= next_state.serial_number()
416                || next_state.serial_number() > output_alias.foundry_counter()
417            {
418                return Err(StateTransitionError::InconsistentFoundrySerialNumber);
419            }
420        } else {
421            return Err(StateTransitionError::MissingAliasForFoundry);
422        }
423
424        let token_id = next_state.token_id();
425        let output_tokens = context.output_native_tokens.get(&token_id).copied().unwrap_or_default();
426        let TokenScheme::Simple(ref next_token_scheme) = next_state.token_scheme;
427
428        // No native tokens should be referenced prior to the foundry creation.
429        if context.input_native_tokens.contains_key(&token_id) {
430            return Err(StateTransitionError::InconsistentNativeTokensFoundryCreation);
431        }
432
433        if output_tokens != next_token_scheme.minted_tokens() || !next_token_scheme.melted_tokens().is_zero() {
434            return Err(StateTransitionError::InconsistentNativeTokensFoundryCreation);
435        }
436
437        Ok(())
438    }
439
440    fn transition(
441        current_state: &Self,
442        next_state: &Self,
443        context: &ValidationContext,
444    ) -> Result<(), StateTransitionError> {
445        if current_state.alias_address() != next_state.alias_address()
446            || current_state.serial_number != next_state.serial_number
447            || current_state.immutable_features != next_state.immutable_features
448        {
449            return Err(StateTransitionError::MutatedImmutableField);
450        }
451
452        let token_id = next_state.token_id();
453        let input_tokens = context.input_native_tokens.get(&token_id).copied().unwrap_or_default();
454        let output_tokens = context.output_native_tokens.get(&token_id).copied().unwrap_or_default();
455        let TokenScheme::Simple(ref current_token_scheme) = current_state.token_scheme;
456        let TokenScheme::Simple(ref next_token_scheme) = next_state.token_scheme;
457
458        if current_token_scheme.maximum_supply() != next_token_scheme.maximum_supply() {
459            return Err(StateTransitionError::MutatedImmutableField);
460        }
461
462        if current_token_scheme.minted_tokens() > next_token_scheme.minted_tokens()
463            || current_token_scheme.melted_tokens() > next_token_scheme.melted_tokens()
464        {
465            return Err(StateTransitionError::NonMonotonicallyIncreasingNativeTokens);
466        }
467
468        match input_tokens.cmp(&output_tokens) {
469            Ordering::Less => {
470                // Mint
471
472                // This can't underflow as it is known that current_minted_tokens <= next_minted_tokens.
473                let minted_diff = next_token_scheme.minted_tokens() - current_token_scheme.minted_tokens();
474                // This can't underflow as it is known that input_tokens < output_tokens (Ordering::Less).
475                let token_diff = output_tokens - input_tokens;
476
477                if minted_diff != token_diff {
478                    return Err(StateTransitionError::InconsistentNativeTokensMint);
479                }
480
481                if current_token_scheme.melted_tokens() != next_token_scheme.melted_tokens() {
482                    return Err(StateTransitionError::InconsistentNativeTokensMint);
483                }
484            }
485            Ordering::Equal => {
486                // Transition
487
488                if current_token_scheme.minted_tokens() != next_token_scheme.minted_tokens()
489                    || current_token_scheme.melted_tokens() != next_token_scheme.melted_tokens()
490                {
491                    return Err(StateTransitionError::InconsistentNativeTokensTransition);
492                }
493            }
494            Ordering::Greater => {
495                // Melt / Burn
496
497                if current_token_scheme.melted_tokens() != next_token_scheme.melted_tokens()
498                    && current_token_scheme.minted_tokens() != next_token_scheme.minted_tokens()
499                {
500                    return Err(StateTransitionError::InconsistentNativeTokensMeltBurn);
501                }
502
503                // This can't underflow as it is known that current_melted_tokens <= next_melted_tokens.
504                let melted_diff = next_token_scheme.melted_tokens() - current_token_scheme.melted_tokens();
505                // This can't underflow as it is known that input_tokens > output_tokens (Ordering::Greater).
506                let token_diff = input_tokens - output_tokens;
507
508                if melted_diff > token_diff {
509                    return Err(StateTransitionError::InconsistentNativeTokensMeltBurn);
510                }
511            }
512        }
513
514        Ok(())
515    }
516
517    fn destruction(current_state: &Self, context: &ValidationContext) -> Result<(), StateTransitionError> {
518        let token_id = current_state.token_id();
519        let input_tokens = context.input_native_tokens.get(&token_id).copied().unwrap_or_default();
520        let TokenScheme::Simple(ref current_token_scheme) = current_state.token_scheme;
521
522        // No native tokens should be referenced after the foundry destruction.
523        if context.output_native_tokens.contains_key(&token_id) {
524            return Err(StateTransitionError::InconsistentNativeTokensFoundryDestruction);
525        }
526
527        // This can't underflow as it is known that minted_tokens >= melted_tokens (syntactic rule).
528        let minted_melted_diff = current_token_scheme.minted_tokens() - current_token_scheme.melted_tokens();
529
530        if minted_melted_diff != input_tokens {
531            return Err(StateTransitionError::InconsistentNativeTokensFoundryDestruction);
532        }
533
534        Ok(())
535    }
536}
537
538impl Packable for FoundryOutput {
539    type UnpackError = Error;
540    type UnpackVisitor = ProtocolParameters;
541
542    fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error> {
543        self.amount.pack(packer)?;
544        self.native_tokens.pack(packer)?;
545        self.serial_number.pack(packer)?;
546        self.token_scheme.pack(packer)?;
547        self.unlock_conditions.pack(packer)?;
548        self.features.pack(packer)?;
549        self.immutable_features.pack(packer)?;
550
551        Ok(())
552    }
553
554    fn unpack<U: Unpacker, const VERIFY: bool>(
555        unpacker: &mut U,
556        visitor: &Self::UnpackVisitor,
557    ) -> Result<Self, UnpackError<Self::UnpackError, U::Error>> {
558        let amount = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?;
559
560        verify_output_amount::<VERIFY>(&amount, &visitor.token_supply()).map_err(UnpackError::Packable)?;
561
562        let native_tokens = NativeTokens::unpack::<_, VERIFY>(unpacker, &())?;
563        let serial_number = u32::unpack::<_, VERIFY>(unpacker, &()).coerce()?;
564        let token_scheme = TokenScheme::unpack::<_, VERIFY>(unpacker, &())?;
565
566        let unlock_conditions = UnlockConditions::unpack::<_, VERIFY>(unpacker, visitor)?;
567
568        if VERIFY {
569            verify_unlock_conditions(&unlock_conditions).map_err(UnpackError::Packable)?;
570        }
571
572        let features = Features::unpack::<_, VERIFY>(unpacker, &())?;
573
574        if VERIFY {
575            verify_allowed_features(&features, FoundryOutput::ALLOWED_FEATURES).map_err(UnpackError::Packable)?;
576        }
577
578        let immutable_features = Features::unpack::<_, VERIFY>(unpacker, &())?;
579
580        if VERIFY {
581            verify_allowed_features(&immutable_features, FoundryOutput::ALLOWED_IMMUTABLE_FEATURES)
582                .map_err(UnpackError::Packable)?;
583        }
584
585        Ok(Self {
586            amount,
587            native_tokens,
588            serial_number,
589            token_scheme,
590            unlock_conditions,
591            features,
592            immutable_features,
593        })
594    }
595}
596
597fn verify_unlock_conditions(unlock_conditions: &UnlockConditions) -> Result<(), Error> {
598    if unlock_conditions.immutable_alias_address().is_none() {
599        Err(Error::MissingAddressUnlockCondition)
600    } else {
601        verify_allowed_unlock_conditions(unlock_conditions, FoundryOutput::ALLOWED_UNLOCK_CONDITIONS)
602    }
603}
604
605#[cfg(feature = "dto")]
606#[allow(missing_docs)]
607pub mod dto {
608    use serde::{Deserialize, Serialize};
609
610    use super::*;
611    use crate::{
612        error::dto::DtoError,
613        output::{
614            dto::OutputBuilderAmountDto, feature::dto::FeatureDto, native_token::dto::NativeTokenDto,
615            token_scheme::dto::TokenSchemeDto, unlock_condition::dto::UnlockConditionDto,
616        },
617    };
618
619    /// Describes a foundry output that is controlled by an alias.
620    #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
621    pub struct FoundryOutputDto {
622        #[serde(rename = "type")]
623        pub kind: u8,
624        // Amount of IOTA tokens held by the output.
625        pub amount: String,
626        // Native tokens held by the output.
627        #[serde(rename = "nativeTokens", skip_serializing_if = "Vec::is_empty", default)]
628        pub native_tokens: Vec<NativeTokenDto>,
629        // The serial number of the foundry with respect to the controlling alias.
630        #[serde(rename = "serialNumber")]
631        pub serial_number: u32,
632        #[serde(rename = "tokenScheme")]
633        pub token_scheme: TokenSchemeDto,
634        #[serde(rename = "unlockConditions")]
635        pub unlock_conditions: Vec<UnlockConditionDto>,
636        #[serde(skip_serializing_if = "Vec::is_empty", default)]
637        pub features: Vec<FeatureDto>,
638        #[serde(rename = "immutableFeatures", skip_serializing_if = "Vec::is_empty", default)]
639        pub immutable_features: Vec<FeatureDto>,
640    }
641
642    impl From<&FoundryOutput> for FoundryOutputDto {
643        fn from(value: &FoundryOutput) -> Self {
644            Self {
645                kind: FoundryOutput::KIND,
646                amount: value.amount().to_string(),
647                native_tokens: value.native_tokens().iter().map(Into::into).collect::<_>(),
648                serial_number: value.serial_number(),
649                token_scheme: value.token_scheme().into(),
650                unlock_conditions: value.unlock_conditions().iter().map(Into::into).collect::<_>(),
651                features: value.features().iter().map(Into::into).collect::<_>(),
652                immutable_features: value.immutable_features().iter().map(Into::into).collect::<_>(),
653            }
654        }
655    }
656
657    impl FoundryOutput {
658        pub fn try_from_dto(value: &FoundryOutputDto, token_supply: u64) -> Result<FoundryOutput, DtoError> {
659            let mut builder = FoundryOutputBuilder::new_with_amount(
660                value
661                    .amount
662                    .parse::<u64>()
663                    .map_err(|_| DtoError::InvalidField("amount"))?,
664                value.serial_number,
665                (&value.token_scheme).try_into()?,
666            )?;
667
668            for t in &value.native_tokens {
669                builder = builder.add_native_token(t.try_into()?);
670            }
671
672            for u in &value.unlock_conditions {
673                builder = builder.add_unlock_condition(UnlockCondition::try_from_dto(u, token_supply)?);
674            }
675
676            for b in &value.features {
677                builder = builder.add_feature(b.try_into()?);
678            }
679
680            for b in &value.immutable_features {
681                builder = builder.add_immutable_feature(b.try_into()?);
682            }
683
684            Ok(builder.finish(token_supply)?)
685        }
686
687        #[allow(clippy::too_many_arguments)]
688        pub fn try_from_dtos(
689            amount: OutputBuilderAmountDto,
690            native_tokens: Option<Vec<NativeTokenDto>>,
691            serial_number: u32,
692            token_scheme: &TokenSchemeDto,
693            unlock_conditions: Vec<UnlockConditionDto>,
694            features: Option<Vec<FeatureDto>>,
695            immutable_features: Option<Vec<FeatureDto>>,
696            token_supply: u64,
697        ) -> Result<FoundryOutput, DtoError> {
698            let token_scheme = TokenScheme::try_from(token_scheme)?;
699
700            let mut builder = match amount {
701                OutputBuilderAmountDto::Amount(amount) => FoundryOutputBuilder::new_with_amount(
702                    amount.parse().map_err(|_| DtoError::InvalidField("amount"))?,
703                    serial_number,
704                    token_scheme,
705                )?,
706                OutputBuilderAmountDto::MinimumStorageDeposit(rent_structure) => {
707                    FoundryOutputBuilder::new_with_minimum_storage_deposit(rent_structure, serial_number, token_scheme)?
708                }
709            };
710
711            if let Some(native_tokens) = native_tokens {
712                let native_tokens = native_tokens
713                    .iter()
714                    .map(NativeToken::try_from)
715                    .collect::<Result<Vec<NativeToken>, DtoError>>()?;
716                builder = builder.with_native_tokens(native_tokens);
717            }
718
719            let unlock_conditions = unlock_conditions
720                .iter()
721                .map(|u| UnlockCondition::try_from_dto(u, token_supply))
722                .collect::<Result<Vec<UnlockCondition>, DtoError>>()?;
723            builder = builder.with_unlock_conditions(unlock_conditions);
724
725            if let Some(features) = features {
726                let features = features
727                    .iter()
728                    .map(Feature::try_from)
729                    .collect::<Result<Vec<Feature>, DtoError>>()?;
730                builder = builder.with_features(features);
731            }
732
733            if let Some(immutable_features) = immutable_features {
734                let immutable_features = immutable_features
735                    .iter()
736                    .map(Feature::try_from)
737                    .collect::<Result<Vec<Feature>, DtoError>>()?;
738                builder = builder.with_immutable_features(immutable_features);
739            }
740
741            Ok(builder.finish(token_supply)?)
742        }
743    }
744}