bee_block/output/
native_token.rs

1// Copyright 2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use alloc::{boxed::Box, vec::Vec};
5
6use derive_more::{Deref, DerefMut};
7use hashbrown::HashMap;
8use iterator_sorted::is_unique_sorted;
9use packable::{bounded::BoundedU8, prefix::BoxedSlicePrefix, Packable};
10use primitive_types::U256;
11
12use crate::{output::TokenId, Error};
13
14///
15#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[packable(unpack_error = Error)]
18pub struct NativeToken {
19    // Identifier of the native token.
20    token_id: TokenId,
21    // Amount of native tokens.
22    #[packable(verify_with = verify_amount)]
23    amount: U256,
24}
25
26impl NativeToken {
27    /// Creates a new [`NativeToken`].
28    #[inline(always)]
29    pub fn new(token_id: TokenId, amount: U256) -> Result<Self, Error> {
30        verify_amount::<true>(&amount, &())?;
31
32        Ok(Self { token_id, amount })
33    }
34
35    /// Returns the token ID of the [`NativeToken`].
36    #[inline(always)]
37    pub fn token_id(&self) -> &TokenId {
38        &self.token_id
39    }
40
41    /// Returns the amount of the [`NativeToken`].
42    #[inline(always)]
43    pub fn amount(&self) -> U256 {
44        self.amount
45    }
46}
47
48#[inline]
49fn verify_amount<const VERIFY: bool>(amount: &U256, _: &()) -> Result<(), Error> {
50    if VERIFY && amount.is_zero() {
51        Err(Error::NativeTokensNullAmount)
52    } else {
53        Ok(())
54    }
55}
56
57/// A builder for [`NativeTokens`].
58#[derive(Clone, Default, Debug, Deref, DerefMut)]
59#[must_use]
60pub struct NativeTokensBuilder(HashMap<TokenId, U256>);
61
62impl NativeTokensBuilder {
63    /// Creates a new [`NativeTokensBuilder`].
64    #[inline(always)]
65    pub fn new() -> Self {
66        Self::default()
67    }
68
69    /// Adds the given [`NativeToken`].
70    pub fn add_native_token(&mut self, native_token: NativeToken) -> Result<(), Error> {
71        let entry = self.0.entry(*native_token.token_id()).or_default();
72        *entry = entry
73            .checked_add(native_token.amount())
74            .ok_or(Error::NativeTokensOverflow)?;
75
76        Ok(())
77    }
78
79    /// Adds the given [`NativeTokens`].
80    pub fn add_native_tokens(&mut self, native_tokens: NativeTokens) -> Result<(), Error> {
81        for native_token in native_tokens {
82            self.add_native_token(native_token)?;
83        }
84
85        Ok(())
86    }
87
88    /// Merges another [`NativeTokensBuilder`] into this one.
89    pub fn merge(&mut self, other: NativeTokensBuilder) -> Result<(), Error> {
90        for (token_id, amount) in other.0.into_iter() {
91            self.add_native_token(NativeToken::new(token_id, amount)?)?;
92        }
93
94        Ok(())
95    }
96
97    /// Finishes the [`NativeTokensBuilder`] into [`NativeTokens`].
98    pub fn finish(self) -> Result<NativeTokens, Error> {
99        NativeTokens::try_from(
100            self.0
101                .into_iter()
102                .map(|(token_id, amount)| NativeToken::new(token_id, amount))
103                .collect::<Result<Vec<_>, _>>()?,
104        )
105    }
106
107    /// Finishes the [`NativeTokensBuilder`] into a [`Vec<NativeToken>`].
108    pub fn finish_vec(self) -> Result<Vec<NativeToken>, Error> {
109        self.0
110            .into_iter()
111            .map(|(token_id, amount)| NativeToken::new(token_id, amount))
112            .collect::<Result<_, _>>()
113    }
114}
115
116impl From<NativeTokens> for NativeTokensBuilder {
117    fn from(native_tokens: NativeTokens) -> Self {
118        let mut builder = NativeTokensBuilder::new();
119
120        // PANIC: safe as `native_tokens` was already built and then valid.
121        builder.add_native_tokens(native_tokens).unwrap();
122        builder
123    }
124}
125
126pub(crate) type NativeTokenCount = BoundedU8<0, { NativeTokens::COUNT_MAX }>;
127
128///
129#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Deref, Packable)]
130#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
131#[packable(unpack_error = Error, with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidNativeTokenCount(p.into())))]
132pub struct NativeTokens(
133    #[packable(verify_with = verify_unique_sorted)] BoxedSlicePrefix<NativeToken, NativeTokenCount>,
134);
135
136impl TryFrom<Vec<NativeToken>> for NativeTokens {
137    type Error = Error;
138
139    #[inline(always)]
140    fn try_from(native_tokens: Vec<NativeToken>) -> Result<Self, Self::Error> {
141        Self::new(native_tokens)
142    }
143}
144
145impl IntoIterator for NativeTokens {
146    type Item = NativeToken;
147    type IntoIter = alloc::vec::IntoIter<Self::Item>;
148
149    fn into_iter(self) -> Self::IntoIter {
150        Vec::from(Into::<Box<[NativeToken]>>::into(self.0)).into_iter()
151    }
152}
153
154impl NativeTokens {
155    /// Maximum number of different native tokens that can be referenced in one transaction.
156    pub const COUNT_MAX: u8 = 64;
157
158    /// Creates a new [`NativeTokens`].
159    pub fn new(native_tokens: Vec<NativeToken>) -> Result<Self, Error> {
160        let mut native_tokens =
161            BoxedSlicePrefix::<NativeToken, NativeTokenCount>::try_from(native_tokens.into_boxed_slice())
162                .map_err(Error::InvalidNativeTokenCount)?;
163
164        native_tokens.sort_by(|a, b| a.token_id().cmp(b.token_id()));
165        // Sort is obviously fine now but uniqueness still needs to be checked.
166        verify_unique_sorted::<true>(&native_tokens, &())?;
167
168        Ok(Self(native_tokens))
169    }
170
171    /// Creates a new [`NativeTokensBuilder`].
172    #[inline(always)]
173    pub fn build() -> NativeTokensBuilder {
174        NativeTokensBuilder::new()
175    }
176}
177
178#[inline]
179fn verify_unique_sorted<const VERIFY: bool>(native_tokens: &[NativeToken], _: &()) -> Result<(), Error> {
180    if VERIFY && !is_unique_sorted(native_tokens.iter().map(NativeToken::token_id)) {
181        Err(Error::NativeTokensNotUniqueSorted)
182    } else {
183        Ok(())
184    }
185}
186
187#[cfg(feature = "dto")]
188#[allow(missing_docs)]
189pub mod dto {
190    use serde::{Deserialize, Serialize};
191
192    use super::*;
193    use crate::{dto::U256Dto, error::dto::DtoError, output::token_id::dto::TokenIdDto};
194
195    /// Describes a native token.
196    #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
197    pub struct NativeTokenDto {
198        // Identifier of the native token.
199        #[serde(rename = "id")]
200        pub token_id: TokenIdDto,
201        // Amount of native tokens hex encoded.
202        pub amount: U256Dto,
203    }
204
205    impl From<&NativeToken> for NativeTokenDto {
206        fn from(value: &NativeToken) -> Self {
207            Self {
208                token_id: TokenIdDto(value.token_id().to_string()),
209                amount: (&value.amount()).into(),
210            }
211        }
212    }
213
214    impl TryFrom<&NativeTokenDto> for NativeToken {
215        type Error = DtoError;
216
217        fn try_from(value: &NativeTokenDto) -> Result<Self, Self::Error> {
218            Self::new(
219                (&value.token_id).try_into()?,
220                U256::try_from(&value.amount).map_err(|_| DtoError::InvalidField("amount"))?,
221            )
222            .map_err(|_| DtoError::InvalidField("nativeTokens"))
223        }
224    }
225}