iota_stardust_types/block/output/
foundry.rs1use alloc::collections::BTreeSet;
5
6use packable::Packable;
7
8use crate::block::{
9 Error,
10 address::AliasAddress,
11 output::{
12 ChainId, FoundryId, NativeToken, NativeTokens, OutputBuilderAmount, TokenId, TokenScheme,
13 feature::{Feature, FeatureFlags, Features, verify_allowed_features},
14 unlock_condition::{
15 UnlockCondition, UnlockConditionFlags, UnlockConditions,
16 verify_allowed_unlock_conditions,
17 },
18 },
19};
20
21#[derive(Clone)]
23#[must_use]
24pub struct FoundryOutputBuilder {
25 amount: OutputBuilderAmount,
26 native_tokens: BTreeSet<NativeToken>,
27 serial_number: u32,
28 token_scheme: TokenScheme,
29 unlock_conditions: BTreeSet<UnlockCondition>,
30 features: BTreeSet<Feature>,
31 immutable_features: BTreeSet<Feature>,
32}
33
34impl FoundryOutputBuilder {
35 pub fn new_with_amount(amount: u64, serial_number: u32, token_scheme: TokenScheme) -> Self {
37 Self::new(
38 OutputBuilderAmount::Amount(amount),
39 serial_number,
40 token_scheme,
41 )
42 }
43
44 fn new(amount: OutputBuilderAmount, serial_number: u32, token_scheme: TokenScheme) -> Self {
45 Self {
46 amount,
47 native_tokens: BTreeSet::new(),
48 serial_number,
49 token_scheme,
50 unlock_conditions: BTreeSet::new(),
51 features: BTreeSet::new(),
52 immutable_features: BTreeSet::new(),
53 }
54 }
55
56 #[inline(always)]
58 pub fn with_amount(mut self, amount: u64) -> Self {
59 self.amount = OutputBuilderAmount::Amount(amount);
60 self
61 }
62
63 #[inline(always)]
65 pub fn add_native_token(mut self, native_token: NativeToken) -> Self {
66 self.native_tokens.insert(native_token);
67 self
68 }
69
70 #[inline(always)]
72 pub fn with_native_tokens(
73 mut self,
74 native_tokens: impl IntoIterator<Item = NativeToken>,
75 ) -> Self {
76 self.native_tokens = native_tokens.into_iter().collect();
77 self
78 }
79
80 #[inline(always)]
82 pub fn with_serial_number(mut self, serial_number: u32) -> Self {
83 self.serial_number = serial_number;
84 self
85 }
86
87 #[inline(always)]
89 pub fn with_token_scheme(mut self, token_scheme: TokenScheme) -> Self {
90 self.token_scheme = token_scheme;
91 self
92 }
93
94 #[inline(always)]
97 pub fn add_unlock_condition(mut self, unlock_condition: impl Into<UnlockCondition>) -> Self {
98 self.unlock_conditions.insert(unlock_condition.into());
99 self
100 }
101
102 #[inline(always)]
105 pub fn with_unlock_conditions(
106 mut self,
107 unlock_conditions: impl IntoIterator<Item = impl Into<UnlockCondition>>,
108 ) -> Self {
109 self.unlock_conditions = unlock_conditions.into_iter().map(Into::into).collect();
110 self
111 }
112
113 pub fn replace_unlock_condition(
116 mut self,
117 unlock_condition: impl Into<UnlockCondition>,
118 ) -> Self {
119 self.unlock_conditions.replace(unlock_condition.into());
120 self
121 }
122
123 #[inline(always)]
125 pub fn clear_unlock_conditions(mut self) -> Self {
126 self.unlock_conditions.clear();
127 self
128 }
129
130 #[inline(always)]
133 pub fn add_feature(mut self, feature: impl Into<Feature>) -> Self {
134 self.features.insert(feature.into());
135 self
136 }
137
138 #[inline(always)]
140 pub fn with_features(mut self, features: impl IntoIterator<Item = impl Into<Feature>>) -> Self {
141 self.features = features.into_iter().map(Into::into).collect();
142 self
143 }
144
145 pub fn replace_feature(mut self, feature: impl Into<Feature>) -> Self {
147 self.features.replace(feature.into());
148 self
149 }
150
151 #[inline(always)]
153 pub fn clear_features(mut self) -> Self {
154 self.features.clear();
155 self
156 }
157
158 #[inline(always)]
161 pub fn add_immutable_feature(mut self, immutable_feature: impl Into<Feature>) -> Self {
162 self.immutable_features.insert(immutable_feature.into());
163 self
164 }
165
166 #[inline(always)]
169 pub fn with_immutable_features(
170 mut self,
171 immutable_features: impl IntoIterator<Item = impl Into<Feature>>,
172 ) -> Self {
173 self.immutable_features = immutable_features.into_iter().map(Into::into).collect();
174 self
175 }
176
177 pub fn replace_immutable_feature(mut self, immutable_feature: impl Into<Feature>) -> Self {
180 self.immutable_features.replace(immutable_feature.into());
181 self
182 }
183
184 #[inline(always)]
186 pub fn clear_immutable_features(mut self) -> Self {
187 self.immutable_features.clear();
188 self
189 }
190
191 pub fn finish(self) -> Result<FoundryOutput, Error> {
193 if self.serial_number == 0 {
194 return Err(Error::InvalidFoundryZeroSerialNumber);
195 }
196
197 let unlock_conditions = UnlockConditions::from_set(self.unlock_conditions)?;
198
199 verify_unlock_conditions(&unlock_conditions)?;
200
201 let features = Features::from_set(self.features)?;
202
203 verify_allowed_features(&features, FoundryOutput::ALLOWED_FEATURES)?;
204
205 let immutable_features = Features::from_set(self.immutable_features)?;
206
207 verify_allowed_features(
208 &immutable_features,
209 FoundryOutput::ALLOWED_IMMUTABLE_FEATURES,
210 )?;
211
212 let mut output = FoundryOutput {
213 amount: 1u64,
214 native_tokens: NativeTokens::from_set(self.native_tokens)?,
215 serial_number: self.serial_number,
216 token_scheme: self.token_scheme,
217 unlock_conditions,
218 features,
219 immutable_features,
220 };
221
222 output.amount = match self.amount {
223 OutputBuilderAmount::Amount(amount) => amount,
224 };
225
226 Ok(output)
227 }
228}
229
230impl From<&FoundryOutput> for FoundryOutputBuilder {
231 fn from(output: &FoundryOutput) -> Self {
232 Self {
233 amount: OutputBuilderAmount::Amount(output.amount),
234 native_tokens: output.native_tokens.iter().copied().collect(),
235 serial_number: output.serial_number,
236 token_scheme: output.token_scheme.clone(),
237 unlock_conditions: output.unlock_conditions.iter().cloned().collect(),
238 features: output.features.iter().cloned().collect(),
239 immutable_features: output.immutable_features.iter().cloned().collect(),
240 }
241 }
242}
243
244#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)]
246#[packable(unpack_error = Error)]
247pub struct FoundryOutput {
248 amount: u64,
250 native_tokens: NativeTokens,
252 serial_number: u32,
254 token_scheme: TokenScheme,
255 unlock_conditions: UnlockConditions,
256 features: Features,
257 immutable_features: Features,
258}
259
260impl FoundryOutput {
261 pub const KIND: u8 = 5;
263 pub const ALLOWED_UNLOCK_CONDITIONS: UnlockConditionFlags =
265 UnlockConditionFlags::IMMUTABLE_ALIAS_ADDRESS;
266 pub const ALLOWED_FEATURES: FeatureFlags = FeatureFlags::METADATA;
268 pub const ALLOWED_IMMUTABLE_FEATURES: FeatureFlags = FeatureFlags::METADATA;
270
271 #[inline(always)]
273 pub fn build_with_amount(
274 amount: u64,
275 serial_number: u32,
276 token_scheme: TokenScheme,
277 ) -> FoundryOutputBuilder {
278 FoundryOutputBuilder::new_with_amount(amount, serial_number, token_scheme)
279 }
280
281 #[inline(always)]
283 pub fn amount(&self) -> u64 {
284 self.amount
285 }
286
287 #[inline(always)]
289 pub fn native_tokens(&self) -> &NativeTokens {
290 &self.native_tokens
291 }
292
293 #[inline(always)]
295 pub fn serial_number(&self) -> u32 {
296 self.serial_number
297 }
298
299 #[inline(always)]
301 pub fn token_scheme(&self) -> &TokenScheme {
302 &self.token_scheme
303 }
304
305 #[inline(always)]
307 pub fn unlock_conditions(&self) -> &UnlockConditions {
308 &self.unlock_conditions
309 }
310
311 #[inline(always)]
313 pub fn features(&self) -> &Features {
314 &self.features
315 }
316
317 #[inline(always)]
319 pub fn immutable_features(&self) -> &Features {
320 &self.immutable_features
321 }
322
323 #[inline(always)]
325 pub fn alias_address(&self) -> &AliasAddress {
326 self.unlock_conditions
328 .immutable_alias_address()
329 .map(|unlock_condition| unlock_condition.alias_address())
330 .unwrap()
331 }
332
333 pub fn id(&self) -> FoundryId {
335 FoundryId::build(
336 self.alias_address(),
337 self.serial_number,
338 self.token_scheme.kind(),
339 )
340 }
341
342 pub fn token_id(&self) -> TokenId {
344 TokenId::from(self.id())
345 }
346
347 #[inline(always)]
349 pub fn chain_id(&self) -> ChainId {
350 ChainId::Foundry(self.id())
351 }
352}
353
354fn verify_unlock_conditions(unlock_conditions: &UnlockConditions) -> Result<(), Error> {
355 if unlock_conditions.immutable_alias_address().is_none() {
356 Err(Error::MissingAddressUnlockCondition)
357 } else {
358 verify_allowed_unlock_conditions(
359 unlock_conditions,
360 FoundryOutput::ALLOWED_UNLOCK_CONDITIONS,
361 )
362 }
363}