bee_block/output/
native_token.rs1use 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#[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 token_id: TokenId,
21 #[packable(verify_with = verify_amount)]
23 amount: U256,
24}
25
26impl NativeToken {
27 #[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 #[inline(always)]
37 pub fn token_id(&self) -> &TokenId {
38 &self.token_id
39 }
40
41 #[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#[derive(Clone, Default, Debug, Deref, DerefMut)]
59#[must_use]
60pub struct NativeTokensBuilder(HashMap<TokenId, U256>);
61
62impl NativeTokensBuilder {
63 #[inline(always)]
65 pub fn new() -> Self {
66 Self::default()
67 }
68
69 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 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 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 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 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 builder.add_native_tokens(native_tokens).unwrap();
122 builder
123 }
124}
125
126pub(crate) type NativeTokenCount = BoundedU8<0, { NativeTokens::COUNT_MAX }>;
127
128#[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 pub const COUNT_MAX: u8 = 64;
157
158 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 verify_unique_sorted::<true>(&native_tokens, &())?;
167
168 Ok(Self(native_tokens))
169 }
170
171 #[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 #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
197 pub struct NativeTokenDto {
198 #[serde(rename = "id")]
200 pub token_id: TokenIdDto,
201 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}