iota_stardust_types/block/output/
basic.rs1use alloc::collections::BTreeSet;
5
6use packable::Packable;
7
8use crate::block::{
9 Error,
10 address::Address,
11 output::{
12 NativeToken, NativeTokens, OutputBuilderAmount,
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 BasicOutputBuilder {
25 amount: OutputBuilderAmount,
26 native_tokens: BTreeSet<NativeToken>,
27 unlock_conditions: BTreeSet<UnlockCondition>,
28 features: BTreeSet<Feature>,
29}
30
31impl BasicOutputBuilder {
32 #[inline(always)]
34 pub fn new_with_amount(amount: u64) -> Self {
35 Self::new(OutputBuilderAmount::Amount(amount))
36 }
37
38 fn new(amount: OutputBuilderAmount) -> Self {
39 Self {
40 amount,
41 native_tokens: BTreeSet::new(),
42 unlock_conditions: BTreeSet::new(),
43 features: BTreeSet::new(),
44 }
45 }
46
47 #[inline(always)]
49 pub fn with_amount(mut self, amount: u64) -> Self {
50 self.amount = OutputBuilderAmount::Amount(amount);
51 self
52 }
53
54 #[inline(always)]
56 pub fn add_native_token(mut self, native_token: NativeToken) -> Self {
57 self.native_tokens.insert(native_token);
58 self
59 }
60
61 #[inline(always)]
63 pub fn with_native_tokens(
64 mut self,
65 native_tokens: impl IntoIterator<Item = NativeToken>,
66 ) -> Self {
67 self.native_tokens = native_tokens.into_iter().collect();
68 self
69 }
70
71 #[inline(always)]
74 pub fn add_unlock_condition(mut self, unlock_condition: impl Into<UnlockCondition>) -> Self {
75 self.unlock_conditions.insert(unlock_condition.into());
76 self
77 }
78
79 #[inline(always)]
82 pub fn with_unlock_conditions(
83 mut self,
84 unlock_conditions: impl IntoIterator<Item = impl Into<UnlockCondition>>,
85 ) -> Self {
86 self.unlock_conditions = unlock_conditions.into_iter().map(Into::into).collect();
87 self
88 }
89
90 pub fn replace_unlock_condition(
93 mut self,
94 unlock_condition: impl Into<UnlockCondition>,
95 ) -> Self {
96 self.unlock_conditions.replace(unlock_condition.into());
97 self
98 }
99
100 #[inline(always)]
102 pub fn clear_unlock_conditions(mut self) -> Self {
103 self.unlock_conditions.clear();
104 self
105 }
106
107 #[inline(always)]
110 pub fn add_feature(mut self, feature: impl Into<Feature>) -> Self {
111 self.features.insert(feature.into());
112 self
113 }
114
115 #[inline(always)]
117 pub fn with_features(mut self, features: impl IntoIterator<Item = impl Into<Feature>>) -> Self {
118 self.features = features.into_iter().map(Into::into).collect();
119 self
120 }
121
122 pub fn replace_feature(mut self, feature: impl Into<Feature>) -> Self {
124 self.features.replace(feature.into());
125 self
126 }
127
128 #[inline(always)]
130 pub fn clear_features(mut self) -> Self {
131 self.features.clear();
132 self
133 }
134
135 pub fn finish(self) -> Result<BasicOutput, Error> {
137 let unlock_conditions = UnlockConditions::from_set(self.unlock_conditions)?;
138
139 verify_unlock_conditions::<true>(&unlock_conditions)?;
140
141 let features = Features::from_set(self.features)?;
142
143 verify_features::<true>(&features)?;
144
145 let mut output = BasicOutput {
146 amount: 1u64,
147 native_tokens: NativeTokens::from_set(self.native_tokens)?,
148 unlock_conditions,
149 features,
150 };
151
152 output.amount = match self.amount {
153 OutputBuilderAmount::Amount(amount) => amount,
154 };
155
156 Ok(output)
157 }
158}
159
160impl From<&BasicOutput> for BasicOutputBuilder {
161 fn from(output: &BasicOutput) -> Self {
162 Self {
163 amount: OutputBuilderAmount::Amount(output.amount),
164 native_tokens: output.native_tokens.iter().copied().collect(),
165 unlock_conditions: output.unlock_conditions.iter().cloned().collect(),
166 features: output.features.iter().cloned().collect(),
167 }
168 }
169}
170
171#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)]
173#[packable(unpack_error = Error)]
174pub struct BasicOutput {
175 amount: u64,
177 native_tokens: NativeTokens,
179 unlock_conditions: UnlockConditions,
180 features: Features,
181}
182
183impl BasicOutput {
184 pub const KIND: u8 = 3;
186
187 const ALLOWED_UNLOCK_CONDITIONS: UnlockConditionFlags = UnlockConditionFlags::ADDRESS
189 .union(UnlockConditionFlags::STORAGE_DEPOSIT_RETURN)
190 .union(UnlockConditionFlags::TIMELOCK)
191 .union(UnlockConditionFlags::EXPIRATION);
192 pub const ALLOWED_FEATURES: FeatureFlags = FeatureFlags::SENDER
194 .union(FeatureFlags::METADATA)
195 .union(FeatureFlags::TAG);
196
197 #[inline(always)]
199 pub fn build_with_amount(amount: u64) -> BasicOutputBuilder {
200 BasicOutputBuilder::new_with_amount(amount)
201 }
202
203 #[inline(always)]
205 pub fn amount(&self) -> u64 {
206 self.amount
207 }
208
209 #[inline(always)]
211 pub fn native_tokens(&self) -> &NativeTokens {
212 &self.native_tokens
213 }
214
215 #[inline(always)]
217 pub fn unlock_conditions(&self) -> &UnlockConditions {
218 &self.unlock_conditions
219 }
220
221 #[inline(always)]
223 pub fn features(&self) -> &Features {
224 &self.features
225 }
226
227 #[inline(always)]
229 pub fn address(&self) -> &Address {
230 self.unlock_conditions
232 .address()
233 .map(|unlock_condition| unlock_condition.address())
234 .unwrap()
235 }
236
237 pub fn simple_deposit_address(&self) -> Option<&Address> {
242 if let [UnlockCondition::Address(address)] = self.unlock_conditions().as_ref() {
243 if self.native_tokens.is_empty() && self.features.is_empty() {
244 return Some(address.address());
245 }
246 }
247
248 None
249 }
250}
251
252fn verify_unlock_conditions<const VERIFY: bool>(
253 unlock_conditions: &UnlockConditions,
254) -> Result<(), Error> {
255 if VERIFY {
256 if unlock_conditions.address().is_none() {
257 Err(Error::MissingAddressUnlockCondition)
258 } else {
259 verify_allowed_unlock_conditions(
260 unlock_conditions,
261 BasicOutput::ALLOWED_UNLOCK_CONDITIONS,
262 )
263 }
264 } else {
265 Ok(())
266 }
267}
268
269fn verify_features<const VERIFY: bool>(blocks: &Features) -> Result<(), Error> {
270 if VERIFY {
271 verify_allowed_features(blocks, BasicOutput::ALLOWED_FEATURES)
272 } else {
273 Ok(())
274 }
275}