Skip to main content

iota_types/block/output/
mod.rs

1// Copyright 2020-2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4mod alias_id;
5mod chain_id;
6mod foundry_id;
7mod inputs_commitment;
8mod metadata;
9mod native_token;
10mod nft_id;
11mod output_id;
12mod rent;
13mod state_transition;
14mod token_id;
15mod token_scheme;
16mod treasury;
17
18///
19pub mod alias;
20///
21pub mod basic;
22///
23pub mod feature;
24///
25pub mod foundry;
26///
27pub mod nft;
28///
29pub mod unlock_condition;
30
31use core::ops::RangeInclusive;
32
33use derive_more::From;
34use packable::{
35    error::{UnpackError, UnpackErrorExt},
36    packer::Packer,
37    unpacker::Unpacker,
38    Packable, PackableExt,
39};
40
41pub(crate) use self::{
42    alias::StateMetadataLength,
43    feature::{MetadataFeatureLength, TagFeatureLength},
44    native_token::NativeTokenCount,
45    output_id::OutputIndex,
46    unlock_condition::AddressUnlockCondition,
47};
48pub use self::{
49    alias::{AliasOutput, AliasOutputBuilder, AliasTransition},
50    alias_id::AliasId,
51    basic::{BasicOutput, BasicOutputBuilder},
52    chain_id::ChainId,
53    feature::{Feature, Features},
54    foundry::{FoundryOutput, FoundryOutputBuilder},
55    foundry_id::FoundryId,
56    inputs_commitment::InputsCommitment,
57    metadata::OutputMetadata,
58    native_token::{NativeToken, NativeTokens, NativeTokensBuilder},
59    nft::{NftOutput, NftOutputBuilder},
60    nft_id::NftId,
61    output_id::OutputId,
62    rent::{Rent, RentStructure, RentStructureBuilder},
63    state_transition::{StateTransitionError, StateTransitionVerifier},
64    token_id::TokenId,
65    token_scheme::{SimpleTokenScheme, TokenScheme},
66    treasury::TreasuryOutput,
67    unlock_condition::{UnlockCondition, UnlockConditions},
68};
69use crate::block::{address::Address, protocol::ProtocolParameters, semantic::ValidationContext, Error};
70
71/// The maximum number of outputs of a transaction.
72pub const OUTPUT_COUNT_MAX: u16 = 128;
73/// The range of valid numbers of outputs of a transaction .
74pub const OUTPUT_COUNT_RANGE: RangeInclusive<u16> = 1..=OUTPUT_COUNT_MAX; // [1..128]
75/// The maximum index of outputs of a transaction.
76pub const OUTPUT_INDEX_MAX: u16 = OUTPUT_COUNT_MAX - 1; // 127
77/// The range of valid indices of outputs of a transaction .
78pub const OUTPUT_INDEX_RANGE: RangeInclusive<u16> = 0..=OUTPUT_INDEX_MAX; // [0..127]
79
80#[derive(Clone)]
81pub(crate) enum OutputBuilderAmount {
82    Amount(u64),
83    MinimumStorageDeposit(RentStructure),
84}
85
86/// A generic output that can represent different types defining the deposit of funds.
87#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From)]
88#[cfg_attr(
89    feature = "serde",
90    derive(serde::Serialize, serde::Deserialize),
91    serde(tag = "type", content = "data")
92)]
93pub enum Output {
94    /// A treasury output.
95    Treasury(TreasuryOutput),
96    /// A basic output.
97    Basic(BasicOutput),
98    /// An alias output.
99    Alias(AliasOutput),
100    /// A foundry output.
101    Foundry(FoundryOutput),
102    /// An NFT output.
103    Nft(NftOutput),
104}
105
106impl Output {
107    /// Minimum amount for an output.
108    pub const AMOUNT_MIN: u64 = 1;
109
110    /// Return the output kind of an [`Output`].
111    pub fn kind(&self) -> u8 {
112        match self {
113            Self::Treasury(_) => TreasuryOutput::KIND,
114            Self::Basic(_) => BasicOutput::KIND,
115            Self::Alias(_) => AliasOutput::KIND,
116            Self::Foundry(_) => FoundryOutput::KIND,
117            Self::Nft(_) => NftOutput::KIND,
118        }
119    }
120
121    /// Returns the amount of an [`Output`].
122    pub fn amount(&self) -> u64 {
123        match self {
124            Self::Treasury(output) => output.amount(),
125            Self::Basic(output) => output.amount(),
126            Self::Alias(output) => output.amount(),
127            Self::Foundry(output) => output.amount(),
128            Self::Nft(output) => output.amount(),
129        }
130    }
131
132    /// Returns the native tokens of an [`Output`], if any.
133    pub fn native_tokens(&self) -> Option<&NativeTokens> {
134        match self {
135            Self::Treasury(_) => None,
136            Self::Basic(output) => Some(output.native_tokens()),
137            Self::Alias(output) => Some(output.native_tokens()),
138            Self::Foundry(output) => Some(output.native_tokens()),
139            Self::Nft(output) => Some(output.native_tokens()),
140        }
141    }
142
143    /// Returns the unlock conditions of an [`Output`], if any.
144    pub fn unlock_conditions(&self) -> Option<&UnlockConditions> {
145        match self {
146            Self::Treasury(_) => None,
147            Self::Basic(output) => Some(output.unlock_conditions()),
148            Self::Alias(output) => Some(output.unlock_conditions()),
149            Self::Foundry(output) => Some(output.unlock_conditions()),
150            Self::Nft(output) => Some(output.unlock_conditions()),
151        }
152    }
153
154    /// Returns the features of an [`Output`], if any.
155    pub fn features(&self) -> Option<&Features> {
156        match self {
157            Self::Treasury(_) => None,
158            Self::Basic(output) => Some(output.features()),
159            Self::Alias(output) => Some(output.features()),
160            Self::Foundry(output) => Some(output.features()),
161            Self::Nft(output) => Some(output.features()),
162        }
163    }
164
165    /// Returns the immutable features of an [`Output`], if any.
166    pub fn immutable_features(&self) -> Option<&Features> {
167        match self {
168            Self::Treasury(_) => None,
169            Self::Basic(_) => None,
170            Self::Alias(output) => Some(output.immutable_features()),
171            Self::Foundry(output) => Some(output.immutable_features()),
172            Self::Nft(output) => Some(output.immutable_features()),
173        }
174    }
175
176    /// Returns the chain identifier of an [`Output`], if any.
177    pub fn chain_id(&self) -> Option<ChainId> {
178        match self {
179            Self::Treasury(_) => None,
180            Self::Basic(_) => None,
181            Self::Alias(output) => Some(output.chain_id()),
182            Self::Foundry(output) => Some(output.chain_id()),
183            Self::Nft(output) => Some(output.chain_id()),
184        }
185    }
186
187    /// Checks whether the output is a [`TreasuryOutput`].
188    pub fn is_treasury(&self) -> bool {
189        matches!(self, Self::Treasury(_))
190    }
191
192    /// Gets the output as an actual [`TreasuryOutput`].
193    /// PANIC: do not call on a non-treasury output.
194    pub fn as_treasury(&self) -> &TreasuryOutput {
195        if let Self::Treasury(output) = self {
196            output
197        } else {
198            panic!("as_treasury called on a non-treasury output");
199        }
200    }
201
202    /// Checks whether the output is a [`BasicOutput`].
203    pub fn is_basic(&self) -> bool {
204        matches!(self, Self::Basic(_))
205    }
206
207    /// Gets the output as an actual [`BasicOutput`].
208    /// PANIC: do not call on a non-basic output.
209    pub fn as_basic(&self) -> &BasicOutput {
210        if let Self::Basic(output) = self {
211            output
212        } else {
213            panic!("as_basic called on a non-basic output");
214        }
215    }
216
217    /// Checks whether the output is an [`AliasOutput`].
218    pub fn is_alias(&self) -> bool {
219        matches!(self, Self::Alias(_))
220    }
221
222    /// Gets the output as an actual [`AliasOutput`].
223    /// PANIC: do not call on a non-alias output.
224    pub fn as_alias(&self) -> &AliasOutput {
225        if let Self::Alias(output) = self {
226            output
227        } else {
228            panic!("as_alias called on a non-alias output");
229        }
230    }
231
232    /// Checks whether the output is a [`FoundryOutput`].
233    pub fn is_foundry(&self) -> bool {
234        matches!(self, Self::Foundry(_))
235    }
236
237    /// Gets the output as an actual [`FoundryOutput`].
238    /// PANIC: do not call on a non-foundry output.
239    pub fn as_foundry(&self) -> &FoundryOutput {
240        if let Self::Foundry(output) = self {
241            output
242        } else {
243            panic!("as_foundry called on a non-foundry output");
244        }
245    }
246
247    /// Checks whether the output is an [`NftOutput`].
248    pub fn is_nft(&self) -> bool {
249        matches!(self, Self::Nft(_))
250    }
251
252    /// Gets the output as an actual [`NftOutput`].
253    /// PANIC: do not call on a non-nft output.
254    pub fn as_nft(&self) -> &NftOutput {
255        if let Self::Nft(output) = self {
256            output
257        } else {
258            panic!("as_nft called on a non-nft output");
259        }
260    }
261
262    /// Returns the address that is required to unlock this [`Output`] and the alias or nft address that gets
263    /// unlocked by it, if it's an alias or nft.
264    /// If no `alias_transition` has been provided, assumes a state transition.
265    pub fn required_and_unlocked_address(
266        &self,
267        current_time: u32,
268        output_id: &OutputId,
269        alias_transition: Option<AliasTransition>,
270    ) -> Result<(Address, Option<Address>), Error> {
271        match self {
272            Self::Alias(output) => {
273                if alias_transition.unwrap_or(AliasTransition::State) == AliasTransition::State {
274                    // Alias address is only unlocked if it's a state transition
275                    Ok((
276                        *output.state_controller_address(),
277                        Some(Address::Alias(output.alias_address(output_id))),
278                    ))
279                } else {
280                    Ok((*output.governor_address(), None))
281                }
282            }
283            Self::Basic(output) => Ok((
284                *output
285                    .unlock_conditions()
286                    .locked_address(output.address(), current_time),
287                None,
288            )),
289            Self::Nft(output) => Ok((
290                *output
291                    .unlock_conditions()
292                    .locked_address(output.address(), current_time),
293                Some(Address::Nft(output.nft_address(output_id))),
294            )),
295            Self::Foundry(output) => Ok((Address::Alias(*output.alias_address()), None)),
296            Self::Treasury(_) => Err(Error::UnsupportedOutputKind(TreasuryOutput::KIND)),
297        }
298    }
299
300    ///
301    pub fn verify_state_transition(
302        current_state: Option<&Self>,
303        next_state: Option<&Self>,
304        context: &ValidationContext<'_>,
305    ) -> Result<(), StateTransitionError> {
306        match (current_state, next_state) {
307            // Creations.
308            (None, Some(Self::Alias(next_state))) => AliasOutput::creation(next_state, context),
309            (None, Some(Self::Foundry(next_state))) => FoundryOutput::creation(next_state, context),
310            (None, Some(Self::Nft(next_state))) => NftOutput::creation(next_state, context),
311
312            // Transitions.
313            (Some(Self::Alias(current_state)), Some(Self::Alias(next_state))) => {
314                AliasOutput::transition(current_state, next_state, context)
315            }
316            (Some(Self::Foundry(current_state)), Some(Self::Foundry(next_state))) => {
317                FoundryOutput::transition(current_state, next_state, context)
318            }
319            (Some(Self::Nft(current_state)), Some(Self::Nft(next_state))) => {
320                NftOutput::transition(current_state, next_state, context)
321            }
322
323            // Destructions.
324            (Some(Self::Alias(current_state)), None) => AliasOutput::destruction(current_state, context),
325            (Some(Self::Foundry(current_state)), None) => FoundryOutput::destruction(current_state, context),
326            (Some(Self::Nft(current_state)), None) => NftOutput::destruction(current_state, context),
327
328            // Unsupported.
329            _ => Err(StateTransitionError::UnsupportedStateTransition),
330        }
331    }
332
333    /// Verifies if a valid storage deposit was made. Each [`Output`] has to have an amount that covers its associated
334    /// byte cost, given by [`RentStructure`].
335    /// If there is a [`StorageDepositReturnUnlockCondition`](unlock_condition::StorageDepositReturnUnlockCondition),
336    /// its amount is also checked.
337    pub fn verify_storage_deposit(&self, rent_structure: RentStructure, token_supply: u64) -> Result<(), Error> {
338        let required_output_amount = self.rent_cost(&rent_structure);
339
340        if self.amount() < required_output_amount {
341            return Err(Error::InsufficientStorageDepositAmount {
342                amount: self.amount(),
343                required: required_output_amount,
344            });
345        }
346
347        if let Some(return_condition) = self
348            .unlock_conditions()
349            .and_then(UnlockConditions::storage_deposit_return)
350        {
351            // We can't return more tokens than were originally contained in the output.
352            // `Return Amount` ≤ `Amount`.
353            if return_condition.amount() > self.amount() {
354                return Err(Error::StorageDepositReturnExceedsOutputAmount {
355                    deposit: return_condition.amount(),
356                    amount: self.amount(),
357                });
358            }
359
360            let minimum_deposit =
361                minimum_storage_deposit(return_condition.return_address(), rent_structure, token_supply);
362
363            // `Minimum Storage Deposit` ≤ `Return Amount`
364            if return_condition.amount() < minimum_deposit {
365                return Err(Error::InsufficientStorageDepositReturnAmount {
366                    deposit: return_condition.amount(),
367                    required: minimum_deposit,
368                });
369            }
370        }
371
372        Ok(())
373    }
374}
375
376impl Packable for Output {
377    type UnpackError = Error;
378    type UnpackVisitor = ProtocolParameters;
379
380    fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error> {
381        match self {
382            Self::Treasury(output) => {
383                TreasuryOutput::KIND.pack(packer)?;
384                output.pack(packer)
385            }
386            Self::Basic(output) => {
387                BasicOutput::KIND.pack(packer)?;
388                output.pack(packer)
389            }
390            Self::Alias(output) => {
391                AliasOutput::KIND.pack(packer)?;
392                output.pack(packer)
393            }
394            Self::Foundry(output) => {
395                FoundryOutput::KIND.pack(packer)?;
396                output.pack(packer)
397            }
398            Self::Nft(output) => {
399                NftOutput::KIND.pack(packer)?;
400                output.pack(packer)
401            }
402        }?;
403
404        Ok(())
405    }
406
407    fn unpack<U: Unpacker, const VERIFY: bool>(
408        unpacker: &mut U,
409        visitor: &Self::UnpackVisitor,
410    ) -> Result<Self, UnpackError<Self::UnpackError, U::Error>> {
411        Ok(match u8::unpack::<_, VERIFY>(unpacker, &()).coerce()? {
412            TreasuryOutput::KIND => Self::from(TreasuryOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
413            BasicOutput::KIND => Self::from(BasicOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
414            AliasOutput::KIND => Self::from(AliasOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
415            FoundryOutput::KIND => Self::from(FoundryOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
416            NftOutput::KIND => Self::from(NftOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
417            k => return Err(Error::InvalidOutputKind(k)).map_err(UnpackError::Packable),
418        })
419    }
420}
421
422impl Rent for Output {
423    fn weighted_bytes(&self, rent_structure: &RentStructure) -> u64 {
424        self.packed_len() as u64 * rent_structure.byte_factor_data() as u64
425    }
426}
427
428pub(crate) fn verify_output_amount<const VERIFY: bool>(amount: &u64, token_supply: &u64) -> Result<(), Error> {
429    if VERIFY && (*amount < Output::AMOUNT_MIN || amount > token_supply) {
430        Err(Error::InvalidOutputAmount(*amount))
431    } else {
432        Ok(())
433    }
434}
435
436pub(crate) fn verify_output_amount_packable<const VERIFY: bool>(
437    amount: &u64,
438    protocol_parameters: &ProtocolParameters,
439) -> Result<(), Error> {
440    verify_output_amount::<VERIFY>(amount, &protocol_parameters.token_supply())
441}
442
443/// Computes the minimum amount that a storage deposit has to match to allow creating a return [`Output`] back to the
444/// sender [`Address`].
445fn minimum_storage_deposit(address: &Address, rent_structure: RentStructure, token_supply: u64) -> u64 {
446    let address_condition = UnlockCondition::Address(AddressUnlockCondition::new(*address));
447    // PANIC: This can never fail because the amount will always be within the valid range. Also, the actual value is
448    // not important, we are only interested in the storage requirements of the type.
449    BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure)
450        .unwrap()
451        .add_unlock_condition(address_condition)
452        .finish(token_supply)
453        .unwrap()
454        .amount()
455}
456
457#[cfg(feature = "dto")]
458#[allow(missing_docs)]
459pub mod dto {
460    use serde::{Deserialize, Serialize, Serializer};
461    use serde_json::Value;
462
463    use super::*;
464    pub use super::{
465        alias::dto::AliasOutputDto,
466        alias_id::dto::AliasIdDto,
467        basic::dto::BasicOutputDto,
468        foundry::dto::FoundryOutputDto,
469        metadata::dto::OutputMetadataDto,
470        native_token::dto::NativeTokenDto,
471        nft::dto::NftOutputDto,
472        nft_id::dto::NftIdDto,
473        rent::dto::RentStructureDto,
474        token_id::dto::TokenIdDto,
475        token_scheme::dto::{SimpleTokenSchemeDto, TokenSchemeDto},
476        treasury::dto::TreasuryOutputDto,
477    };
478    use crate::block::error::dto::DtoError;
479
480    #[derive(Clone, Debug, Deserialize, From)]
481    pub enum OutputBuilderAmountDto {
482        Amount(String),
483        MinimumStorageDeposit(RentStructure),
484    }
485
486    /// Describes all the different output types.
487    #[derive(Clone, Debug, Eq, PartialEq, From)]
488    pub enum OutputDto {
489        Treasury(TreasuryOutputDto),
490        Basic(BasicOutputDto),
491        Alias(AliasOutputDto),
492        Foundry(FoundryOutputDto),
493        Nft(NftOutputDto),
494    }
495
496    impl From<&Output> for OutputDto {
497        fn from(value: &Output) -> Self {
498            match value {
499                Output::Treasury(o) => Self::Treasury(o.into()),
500                Output::Basic(o) => Self::Basic(o.into()),
501                Output::Alias(o) => Self::Alias(o.into()),
502                Output::Foundry(o) => Self::Foundry(o.into()),
503                Output::Nft(o) => Self::Nft(o.into()),
504            }
505        }
506    }
507
508    impl Output {
509        pub fn try_from_dto(value: &OutputDto, token_supply: u64) -> Result<Self, DtoError> {
510            Ok(match value {
511                OutputDto::Treasury(o) => Self::Treasury(TreasuryOutput::try_from_dto(o, token_supply)?),
512                OutputDto::Basic(o) => Self::Basic(BasicOutput::try_from_dto(o, token_supply)?),
513                OutputDto::Alias(o) => Self::Alias(AliasOutput::try_from_dto(o, token_supply)?),
514                OutputDto::Foundry(o) => Self::Foundry(FoundryOutput::try_from_dto(o, token_supply)?),
515                OutputDto::Nft(o) => Self::Nft(NftOutput::try_from_dto(o, token_supply)?),
516            })
517        }
518
519        pub fn try_from_dto_unverified(value: &OutputDto) -> Result<Self, DtoError> {
520            Ok(match value {
521                OutputDto::Treasury(o) => Self::Treasury(TreasuryOutput::try_from_dto_unverified(o)?),
522                OutputDto::Basic(o) => Self::Basic(BasicOutput::try_from_dto_unverified(o)?),
523                OutputDto::Alias(o) => Self::Alias(AliasOutput::try_from_dto_unverified(o)?),
524                OutputDto::Foundry(o) => Self::Foundry(FoundryOutput::try_from_dto_unverified(o)?),
525                OutputDto::Nft(o) => Self::Nft(NftOutput::try_from_dto_unverified(o)?),
526            })
527        }
528    }
529
530    impl<'de> Deserialize<'de> for OutputDto {
531        fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
532            let value = Value::deserialize(d)?;
533            Ok(
534                match value
535                    .get("type")
536                    .and_then(Value::as_u64)
537                    .ok_or_else(|| serde::de::Error::custom("invalid output type"))? as u8
538                {
539                    TreasuryOutput::KIND => {
540                        Self::Treasury(TreasuryOutputDto::deserialize(value).map_err(|e| {
541                            serde::de::Error::custom(format!("cannot deserialize treasury output: {e}"))
542                        })?)
543                    }
544                    BasicOutput::KIND => Self::Basic(
545                        BasicOutputDto::deserialize(value)
546                            .map_err(|e| serde::de::Error::custom(format!("cannot deserialize basic output: {e}")))?,
547                    ),
548                    AliasOutput::KIND => Self::Alias(
549                        AliasOutputDto::deserialize(value)
550                            .map_err(|e| serde::de::Error::custom(format!("cannot deserialize alias output: {e}")))?,
551                    ),
552                    FoundryOutput::KIND => Self::Foundry(
553                        FoundryOutputDto::deserialize(value)
554                            .map_err(|e| serde::de::Error::custom(format!("cannot deserialize foundry output: {e}")))?,
555                    ),
556                    NftOutput::KIND => Self::Nft(
557                        NftOutputDto::deserialize(value)
558                            .map_err(|e| serde::de::Error::custom(format!("cannot deserialize NFT output: {e}")))?,
559                    ),
560                    _ => return Err(serde::de::Error::custom("invalid output type")),
561                },
562            )
563        }
564    }
565
566    impl Serialize for OutputDto {
567        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
568        where
569            S: Serializer,
570        {
571            #[derive(Serialize)]
572            #[serde(untagged)]
573            enum OutputDto_<'a> {
574                T1(&'a TreasuryOutputDto),
575                T2(&'a BasicOutputDto),
576                T3(&'a AliasOutputDto),
577                T4(&'a FoundryOutputDto),
578                T5(&'a NftOutputDto),
579            }
580            #[derive(Serialize)]
581            struct TypedOutput<'a> {
582                #[serde(flatten)]
583                output: OutputDto_<'a>,
584            }
585            let output = match self {
586                Self::Treasury(o) => TypedOutput {
587                    output: OutputDto_::T1(o),
588                },
589                Self::Basic(o) => TypedOutput {
590                    output: OutputDto_::T2(o),
591                },
592                Self::Alias(o) => TypedOutput {
593                    output: OutputDto_::T3(o),
594                },
595                Self::Foundry(o) => TypedOutput {
596                    output: OutputDto_::T4(o),
597                },
598                Self::Nft(o) => TypedOutput {
599                    output: OutputDto_::T5(o),
600                },
601            };
602            output.serialize(serializer)
603        }
604    }
605}