bee_block/output/
mod.rs

1// Copyright 2020-2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4mod alias_id;
5
6mod chain_id;
7
8mod foundry_id;
9mod inputs_commitment;
10mod native_token;
11mod nft_id;
12mod output_id;
13mod rent;
14mod state_transition;
15mod token_id;
16mod token_scheme;
17mod treasury;
18
19///
20pub mod alias;
21///
22pub mod basic;
23///
24pub mod feature;
25///
26pub mod foundry;
27///
28pub mod nft;
29///
30pub mod unlock_condition;
31
32use core::ops::RangeInclusive;
33
34use derive_more::From;
35use packable::{
36    error::{UnpackError, UnpackErrorExt},
37    packer::Packer,
38    unpacker::Unpacker,
39    Packable, PackableExt,
40};
41
42pub(crate) use self::{
43    alias::StateMetadataLength,
44    feature::{MetadataFeatureLength, TagFeatureLength},
45    native_token::NativeTokenCount,
46    output_id::OutputIndex,
47    unlock_condition::AddressUnlockCondition,
48};
49pub use self::{
50    alias::{AliasOutput, AliasOutputBuilder},
51    alias_id::AliasId,
52    basic::{BasicOutput, BasicOutputBuilder},
53    chain_id::ChainId,
54    feature::{Feature, Features},
55    foundry::{FoundryOutput, FoundryOutputBuilder},
56    foundry_id::FoundryId,
57    inputs_commitment::InputsCommitment,
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::{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, 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    ///
188    pub fn verify_state_transition(
189        current_state: Option<&Output>,
190        next_state: Option<&Output>,
191        context: &ValidationContext,
192    ) -> Result<(), StateTransitionError> {
193        match (current_state, next_state) {
194            // Creations.
195            (None, Some(Output::Alias(next_state))) => AliasOutput::creation(next_state, context),
196            (None, Some(Output::Foundry(next_state))) => FoundryOutput::creation(next_state, context),
197            (None, Some(Output::Nft(next_state))) => NftOutput::creation(next_state, context),
198
199            // Transitions.
200            (Some(Output::Alias(current_state)), Some(Output::Alias(next_state))) => {
201                AliasOutput::transition(current_state, next_state, context)
202            }
203            (Some(Output::Foundry(current_state)), Some(Output::Foundry(next_state))) => {
204                FoundryOutput::transition(current_state, next_state, context)
205            }
206            (Some(Output::Nft(current_state)), Some(Output::Nft(next_state))) => {
207                NftOutput::transition(current_state, next_state, context)
208            }
209
210            // Destructions.
211            (Some(Output::Alias(current_state)), None) => AliasOutput::destruction(current_state, context),
212            (Some(Output::Foundry(current_state)), None) => FoundryOutput::destruction(current_state, context),
213            (Some(Output::Nft(current_state)), None) => NftOutput::destruction(current_state, context),
214
215            // Unsupported.
216            _ => Err(StateTransitionError::UnsupportedStateTransition),
217        }
218    }
219
220    /// Verifies if a valid storage deposit was made. Each [`Output`] has to have an amount that covers its associated
221    /// byte cost, given by [`RentStructure`].
222    /// If there is a [`StorageDepositReturnUnlockCondition`](unlock_condition::StorageDepositReturnUnlockCondition),
223    /// its amount is also checked.
224    pub fn verify_storage_deposit(&self, rent_structure: RentStructure, token_supply: u64) -> Result<(), Error> {
225        let required_output_amount = self.rent_cost(&rent_structure);
226
227        if self.amount() < required_output_amount {
228            return Err(Error::InsufficientStorageDepositAmount {
229                amount: self.amount(),
230                required: required_output_amount,
231            });
232        }
233
234        if let Some(return_condition) = self
235            .unlock_conditions()
236            .and_then(UnlockConditions::storage_deposit_return)
237        {
238            // We can't return more tokens than were originally contained in the output.
239            // `Return Amount` ≤ `Amount`.
240            if return_condition.amount() > self.amount() {
241                return Err(Error::StorageDepositReturnExceedsOutputAmount {
242                    deposit: return_condition.amount(),
243                    amount: self.amount(),
244                });
245            }
246
247            let minimum_deposit =
248                minimum_storage_deposit(return_condition.return_address(), rent_structure, token_supply);
249
250            // `Minimum Storage Deposit` ≤  `Return Amount`
251            if return_condition.amount() < minimum_deposit {
252                return Err(Error::InsufficientStorageDepositReturnAmount {
253                    deposit: return_condition.amount(),
254                    required: minimum_deposit,
255                });
256            }
257        }
258
259        Ok(())
260    }
261}
262
263impl Packable for Output {
264    type UnpackError = Error;
265    type UnpackVisitor = ProtocolParameters;
266
267    fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error> {
268        match self {
269            Output::Treasury(output) => {
270                TreasuryOutput::KIND.pack(packer)?;
271                output.pack(packer)
272            }
273            Output::Basic(output) => {
274                BasicOutput::KIND.pack(packer)?;
275                output.pack(packer)
276            }
277            Output::Alias(output) => {
278                AliasOutput::KIND.pack(packer)?;
279                output.pack(packer)
280            }
281            Output::Foundry(output) => {
282                FoundryOutput::KIND.pack(packer)?;
283                output.pack(packer)
284            }
285            Output::Nft(output) => {
286                NftOutput::KIND.pack(packer)?;
287                output.pack(packer)
288            }
289        }?;
290
291        Ok(())
292    }
293
294    fn unpack<U: Unpacker, const VERIFY: bool>(
295        unpacker: &mut U,
296        visitor: &Self::UnpackVisitor,
297    ) -> Result<Self, UnpackError<Self::UnpackError, U::Error>> {
298        Ok(match u8::unpack::<_, VERIFY>(unpacker, &()).coerce()? {
299            TreasuryOutput::KIND => Output::from(TreasuryOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
300            BasicOutput::KIND => Output::from(BasicOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
301            AliasOutput::KIND => Output::from(AliasOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
302            FoundryOutput::KIND => Output::from(FoundryOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
303            NftOutput::KIND => Output::from(NftOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
304            k => return Err(Error::InvalidOutputKind(k)).map_err(UnpackError::Packable),
305        })
306    }
307}
308
309impl Rent for Output {
310    fn weighted_bytes(&self, rent_structure: &RentStructure) -> u64 {
311        self.packed_len() as u64 * rent_structure.v_byte_factor_data as u64
312    }
313}
314
315pub(crate) fn verify_output_amount<const VERIFY: bool>(amount: &u64, token_supply: &u64) -> Result<(), Error> {
316    if VERIFY && (*amount < Output::AMOUNT_MIN || amount > token_supply) {
317        Err(Error::InvalidOutputAmount(*amount))
318    } else {
319        Ok(())
320    }
321}
322
323pub(crate) fn verify_output_amount_packable<const VERIFY: bool>(
324    amount: &u64,
325    protocol_parameters: &ProtocolParameters,
326) -> Result<(), Error> {
327    verify_output_amount::<VERIFY>(amount, &protocol_parameters.token_supply())
328}
329
330/// Computes the minimum amount that a storage deposit has to match to allow creating a return [`Output`] back to the
331/// sender [`Address`].
332fn minimum_storage_deposit(address: &Address, rent_structure: RentStructure, token_supply: u64) -> u64 {
333    let address_condition = UnlockCondition::Address(AddressUnlockCondition::new(*address));
334    // PANIC: This can never fail because the amount will always be within the valid range. Also, the actual value is
335    // not important, we are only interested in the storage requirements of the type.
336    BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure)
337        .unwrap()
338        .add_unlock_condition(address_condition)
339        .finish(token_supply)
340        .unwrap()
341        .amount()
342}
343
344#[cfg(feature = "dto")]
345#[allow(missing_docs)]
346pub mod dto {
347    use serde::{Deserialize, Serialize, Serializer};
348    use serde_json::Value;
349
350    use super::*;
351    pub use super::{
352        alias::dto::AliasOutputDto,
353        alias_id::dto::AliasIdDto,
354        basic::dto::BasicOutputDto,
355        foundry::dto::FoundryOutputDto,
356        native_token::dto::NativeTokenDto,
357        nft::dto::NftOutputDto,
358        nft_id::dto::NftIdDto,
359        token_id::dto::TokenIdDto,
360        token_scheme::dto::{SimpleTokenSchemeDto, TokenSchemeDto},
361        treasury::dto::TreasuryOutputDto,
362    };
363    use crate::error::dto::DtoError;
364
365    #[derive(Clone, Debug, Deserialize, From)]
366    pub enum OutputBuilderAmountDto {
367        Amount(String),
368        MinimumStorageDeposit(RentStructure),
369    }
370
371    /// Describes all the different output types.
372    #[derive(Clone, Debug, Eq, PartialEq, From)]
373    pub enum OutputDto {
374        Treasury(TreasuryOutputDto),
375        Basic(BasicOutputDto),
376        Alias(AliasOutputDto),
377        Foundry(FoundryOutputDto),
378        Nft(NftOutputDto),
379    }
380
381    impl From<&Output> for OutputDto {
382        fn from(value: &Output) -> Self {
383            match value {
384                Output::Treasury(o) => OutputDto::Treasury(o.into()),
385                Output::Basic(o) => OutputDto::Basic(o.into()),
386                Output::Alias(o) => OutputDto::Alias(o.into()),
387                Output::Foundry(o) => OutputDto::Foundry(o.into()),
388                Output::Nft(o) => OutputDto::Nft(o.into()),
389            }
390        }
391    }
392
393    impl Output {
394        pub fn try_from_dto(value: &OutputDto, token_supply: u64) -> Result<Output, DtoError> {
395            Ok(match value {
396                OutputDto::Treasury(o) => Output::Treasury(TreasuryOutput::try_from_dto(o, token_supply)?),
397                OutputDto::Basic(o) => Output::Basic(BasicOutput::try_from_dto(o, token_supply)?),
398                OutputDto::Alias(o) => Output::Alias(AliasOutput::try_from_dto(o, token_supply)?),
399                OutputDto::Foundry(o) => Output::Foundry(FoundryOutput::try_from_dto(o, token_supply)?),
400                OutputDto::Nft(o) => Output::Nft(NftOutput::try_from_dto(o, token_supply)?),
401            })
402        }
403    }
404
405    impl<'de> Deserialize<'de> for OutputDto {
406        fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
407            let value = Value::deserialize(d)?;
408            Ok(
409                match value
410                    .get("type")
411                    .and_then(Value::as_u64)
412                    .ok_or_else(|| serde::de::Error::custom("invalid output type"))? as u8
413                {
414                    TreasuryOutput::KIND => {
415                        OutputDto::Treasury(TreasuryOutputDto::deserialize(value).map_err(|e| {
416                            serde::de::Error::custom(format!("cannot deserialize treasury output: {}", e))
417                        })?)
418                    }
419                    BasicOutput::KIND => OutputDto::Basic(
420                        BasicOutputDto::deserialize(value)
421                            .map_err(|e| serde::de::Error::custom(format!("cannot deserialize basic output: {}", e)))?,
422                    ),
423                    AliasOutput::KIND => OutputDto::Alias(
424                        AliasOutputDto::deserialize(value)
425                            .map_err(|e| serde::de::Error::custom(format!("cannot deserialize alias output: {}", e)))?,
426                    ),
427                    FoundryOutput::KIND => {
428                        OutputDto::Foundry(FoundryOutputDto::deserialize(value).map_err(|e| {
429                            serde::de::Error::custom(format!("cannot deserialize foundry output: {}", e))
430                        })?)
431                    }
432                    NftOutput::KIND => OutputDto::Nft(
433                        NftOutputDto::deserialize(value)
434                            .map_err(|e| serde::de::Error::custom(format!("cannot deserialize NFT output: {}", e)))?,
435                    ),
436                    _ => return Err(serde::de::Error::custom("invalid output type")),
437                },
438            )
439        }
440    }
441
442    impl Serialize for OutputDto {
443        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
444        where
445            S: Serializer,
446        {
447            #[derive(Serialize)]
448            #[serde(untagged)]
449            enum OutputDto_<'a> {
450                T1(&'a TreasuryOutputDto),
451                T2(&'a BasicOutputDto),
452                T3(&'a AliasOutputDto),
453                T4(&'a FoundryOutputDto),
454                T5(&'a NftOutputDto),
455            }
456            #[derive(Serialize)]
457            struct TypedOutput<'a> {
458                #[serde(flatten)]
459                output: OutputDto_<'a>,
460            }
461            let output = match self {
462                OutputDto::Treasury(o) => TypedOutput {
463                    output: OutputDto_::T1(o),
464                },
465                OutputDto::Basic(o) => TypedOutput {
466                    output: OutputDto_::T2(o),
467                },
468                OutputDto::Alias(o) => TypedOutput {
469                    output: OutputDto_::T3(o),
470                },
471                OutputDto::Foundry(o) => TypedOutput {
472                    output: OutputDto_::T4(o),
473                },
474                OutputDto::Nft(o) => TypedOutput {
475                    output: OutputDto_::T5(o),
476                },
477            };
478            output.serialize(serializer)
479        }
480    }
481}