1use alloc::vec::Vec;
5
6use packable::Packable;
7
8use crate::block::{
9 address::Address,
10 output::{
11 feature::{verify_allowed_features, Feature, FeatureFlags, Features},
12 unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions},
13 verify_output_amount, verify_output_amount_packable, NativeToken, NativeTokens, Output, OutputBuilderAmount,
14 OutputId, Rent, RentStructure,
15 },
16 protocol::ProtocolParameters,
17 semantic::{ConflictReason, ValidationContext},
18 unlock::Unlock,
19 Error,
20};
21
22#[derive(Clone)]
24#[must_use]
25pub struct BasicOutputBuilder {
26 amount: OutputBuilderAmount,
27 native_tokens: Vec<NativeToken>,
28 unlock_conditions: Vec<UnlockCondition>,
29 features: Vec<Feature>,
30}
31
32impl BasicOutputBuilder {
33 #[inline(always)]
35 pub fn new_with_amount(amount: u64) -> Result<Self, Error> {
36 Self::new(OutputBuilderAmount::Amount(amount))
37 }
38
39 #[inline(always)]
42 pub fn new_with_minimum_storage_deposit(rent_structure: RentStructure) -> Result<Self, Error> {
43 Self::new(OutputBuilderAmount::MinimumStorageDeposit(rent_structure))
44 }
45
46 fn new(amount: OutputBuilderAmount) -> Result<Self, Error> {
47 Ok(Self {
48 amount,
49 native_tokens: Vec::new(),
50 unlock_conditions: Vec::new(),
51 features: Vec::new(),
52 })
53 }
54
55 #[inline(always)]
57 pub fn with_amount(mut self, amount: u64) -> Result<Self, Error> {
58 self.amount = OutputBuilderAmount::Amount(amount);
59 Ok(self)
60 }
61
62 #[inline(always)]
64 pub fn with_minimum_storage_deposit(mut self, rent_structure: RentStructure) -> Self {
65 self.amount = OutputBuilderAmount::MinimumStorageDeposit(rent_structure);
66 self
67 }
68
69 #[inline(always)]
71 pub fn add_native_token(mut self, native_token: NativeToken) -> Self {
72 self.native_tokens.push(native_token);
73 self
74 }
75
76 #[inline(always)]
78 pub fn with_native_tokens(mut self, native_tokens: impl IntoIterator<Item = NativeToken>) -> Self {
79 self.native_tokens = native_tokens.into_iter().collect();
80 self
81 }
82
83 #[inline(always)]
85 pub fn add_unlock_condition(mut self, unlock_condition: UnlockCondition) -> Self {
86 self.unlock_conditions.push(unlock_condition);
87 self
88 }
89
90 #[inline(always)]
92 pub fn with_unlock_conditions(mut self, unlock_conditions: impl IntoIterator<Item = UnlockCondition>) -> Self {
93 self.unlock_conditions = unlock_conditions.into_iter().collect();
94 self
95 }
96
97 pub fn replace_unlock_condition(mut self, unlock_condition: UnlockCondition) -> Self {
99 match self
100 .unlock_conditions
101 .iter_mut()
102 .find(|u| u.kind() == unlock_condition.kind())
103 {
104 Some(u) => *u = unlock_condition,
105 None => self.unlock_conditions.push(unlock_condition),
106 }
107 self
108 }
109
110 #[inline(always)]
112 pub fn add_feature(mut self, feature: Feature) -> Self {
113 self.features.push(feature);
114 self
115 }
116
117 #[inline(always)]
119 pub fn with_features(mut self, features: impl IntoIterator<Item = Feature>) -> Self {
120 self.features = features.into_iter().collect();
121 self
122 }
123
124 pub fn replace_feature(mut self, feature: Feature) -> Self {
126 match self.features.iter_mut().find(|f| f.kind() == feature.kind()) {
127 Some(f) => *f = feature,
128 None => self.features.push(feature),
129 }
130 self
131 }
132
133 pub fn finish_unverified(self) -> Result<BasicOutput, Error> {
135 let unlock_conditions = UnlockConditions::new(self.unlock_conditions)?;
136
137 verify_unlock_conditions::<true>(&unlock_conditions)?;
138
139 let features = Features::new(self.features)?;
140
141 verify_features::<true>(&features)?;
142
143 let mut output = BasicOutput {
144 amount: 1u64,
145 native_tokens: NativeTokens::new(self.native_tokens)?,
146 unlock_conditions,
147 features,
148 };
149
150 output.amount = match self.amount {
151 OutputBuilderAmount::Amount(amount) => amount,
152 OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => {
153 Output::Basic(output.clone()).rent_cost(&rent_structure)
154 }
155 };
156
157 Ok(output)
158 }
159
160 pub fn finish(self, token_supply: u64) -> Result<BasicOutput, Error> {
162 let output = self.finish_unverified()?;
163
164 verify_output_amount::<true>(&output.amount, &token_supply)?;
165
166 Ok(output)
167 }
168
169 pub fn finish_output(self, token_supply: u64) -> Result<Output, Error> {
171 Ok(Output::Basic(self.finish(token_supply)?))
172 }
173}
174
175impl From<&BasicOutput> for BasicOutputBuilder {
176 fn from(output: &BasicOutput) -> Self {
177 Self {
178 amount: OutputBuilderAmount::Amount(output.amount),
179 native_tokens: output.native_tokens.to_vec(),
180 unlock_conditions: output.unlock_conditions.to_vec(),
181 features: output.features.to_vec(),
182 }
183 }
184}
185
186#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)]
188#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
189#[packable(unpack_error = Error)]
190#[packable(unpack_visitor = ProtocolParameters)]
191pub struct BasicOutput {
192 #[packable(verify_with = verify_output_amount_packable)]
194 amount: u64,
195 native_tokens: NativeTokens,
197 #[packable(verify_with = verify_unlock_conditions_packable)]
198 unlock_conditions: UnlockConditions,
199 #[packable(verify_with = verify_features_packable)]
200 features: Features,
201}
202
203impl BasicOutput {
204 pub const KIND: u8 = 3;
206
207 const ALLOWED_UNLOCK_CONDITIONS: UnlockConditionFlags = UnlockConditionFlags::ADDRESS
209 .union(UnlockConditionFlags::STORAGE_DEPOSIT_RETURN)
210 .union(UnlockConditionFlags::TIMELOCK)
211 .union(UnlockConditionFlags::EXPIRATION);
212 pub const ALLOWED_FEATURES: FeatureFlags = FeatureFlags::SENDER
214 .union(FeatureFlags::METADATA)
215 .union(FeatureFlags::TAG);
216
217 #[inline(always)]
219 pub fn new_with_amount(amount: u64, token_supply: u64) -> Result<Self, Error> {
220 BasicOutputBuilder::new_with_amount(amount)?.finish(token_supply)
221 }
222
223 #[inline(always)]
226 pub fn new_with_minimum_storage_deposit(rent_structure: RentStructure, token_supply: u64) -> Result<Self, Error> {
227 BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure)?.finish(token_supply)
228 }
229
230 #[inline(always)]
232 pub fn build_with_amount(amount: u64) -> Result<BasicOutputBuilder, Error> {
233 BasicOutputBuilder::new_with_amount(amount)
234 }
235
236 #[inline(always)]
239 pub fn build_with_minimum_storage_deposit(rent_structure: RentStructure) -> Result<BasicOutputBuilder, Error> {
240 BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure)
241 }
242
243 #[inline(always)]
245 pub fn amount(&self) -> u64 {
246 self.amount
247 }
248
249 #[inline(always)]
251 pub fn native_tokens(&self) -> &NativeTokens {
252 &self.native_tokens
253 }
254
255 #[inline(always)]
257 pub fn unlock_conditions(&self) -> &UnlockConditions {
258 &self.unlock_conditions
259 }
260
261 #[inline(always)]
263 pub fn features(&self) -> &Features {
264 &self.features
265 }
266
267 #[inline(always)]
269 pub fn address(&self) -> &Address {
270 self.unlock_conditions
272 .address()
273 .map(|unlock_condition| unlock_condition.address())
274 .unwrap()
275 }
276
277 pub fn unlock(
279 &self,
280 _output_id: &OutputId,
281 unlock: &Unlock,
282 inputs: &[(OutputId, &Output)],
283 context: &mut ValidationContext<'_>,
284 ) -> Result<(), ConflictReason> {
285 self.unlock_conditions()
286 .locked_address(self.address(), context.milestone_timestamp)
287 .unlock(unlock, inputs, context)
288 }
289
290 pub fn simple_deposit_address(&self) -> Option<&Address> {
294 if let [UnlockCondition::Address(address)] = self.unlock_conditions().as_ref() {
295 if self.native_tokens.is_empty() && self.features.is_empty() {
296 return Some(address.address());
297 }
298 }
299
300 None
301 }
302}
303
304fn verify_unlock_conditions<const VERIFY: bool>(unlock_conditions: &UnlockConditions) -> Result<(), Error> {
305 if VERIFY {
306 if unlock_conditions.address().is_none() {
307 Err(Error::MissingAddressUnlockCondition)
308 } else {
309 verify_allowed_unlock_conditions(unlock_conditions, BasicOutput::ALLOWED_UNLOCK_CONDITIONS)
310 }
311 } else {
312 Ok(())
313 }
314}
315
316fn verify_unlock_conditions_packable<const VERIFY: bool>(
317 unlock_conditions: &UnlockConditions,
318 _: &ProtocolParameters,
319) -> Result<(), Error> {
320 verify_unlock_conditions::<VERIFY>(unlock_conditions)
321}
322
323fn verify_features<const VERIFY: bool>(blocks: &Features) -> Result<(), Error> {
324 if VERIFY {
325 verify_allowed_features(blocks, BasicOutput::ALLOWED_FEATURES)
326 } else {
327 Ok(())
328 }
329}
330
331fn verify_features_packable<const VERIFY: bool>(blocks: &Features, _: &ProtocolParameters) -> Result<(), Error> {
332 verify_features::<VERIFY>(blocks)
333}
334
335#[cfg(feature = "dto")]
336#[allow(missing_docs)]
337pub mod dto {
338 use serde::{Deserialize, Serialize};
339
340 use super::*;
341 use crate::block::{
342 error::dto::DtoError,
343 output::{
344 dto::OutputBuilderAmountDto, feature::dto::FeatureDto, native_token::dto::NativeTokenDto,
345 unlock_condition::dto::UnlockConditionDto,
346 },
347 };
348
349 #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
351 pub struct BasicOutputDto {
352 #[serde(rename = "type")]
353 pub kind: u8,
354 pub amount: String,
356 #[serde(rename = "nativeTokens", skip_serializing_if = "Vec::is_empty", default)]
358 pub native_tokens: Vec<NativeTokenDto>,
359 #[serde(rename = "unlockConditions")]
360 pub unlock_conditions: Vec<UnlockConditionDto>,
361 #[serde(skip_serializing_if = "Vec::is_empty", default)]
362 pub features: Vec<FeatureDto>,
363 }
364
365 impl From<&BasicOutput> for BasicOutputDto {
366 fn from(value: &BasicOutput) -> Self {
367 Self {
368 kind: BasicOutput::KIND,
369 amount: value.amount().to_string(),
370 native_tokens: value.native_tokens().iter().map(Into::into).collect::<_>(),
371 unlock_conditions: value.unlock_conditions().iter().map(Into::into).collect::<_>(),
372 features: value.features().iter().map(Into::into).collect::<_>(),
373 }
374 }
375 }
376
377 impl BasicOutput {
378 fn _try_from_dto(value: &BasicOutputDto) -> Result<BasicOutputBuilder, DtoError> {
379 let mut builder = BasicOutputBuilder::new_with_amount(
380 value.amount.parse().map_err(|_| DtoError::InvalidField("amount"))?,
381 )?;
382
383 for t in &value.native_tokens {
384 builder = builder.add_native_token(t.try_into()?);
385 }
386
387 for b in &value.features {
388 builder = builder.add_feature(b.try_into()?);
389 }
390
391 Ok(builder)
392 }
393
394 pub fn try_from_dto(value: &BasicOutputDto, token_supply: u64) -> Result<Self, DtoError> {
395 let mut builder = Self::_try_from_dto(value)?;
396
397 for u in &value.unlock_conditions {
398 builder = builder.add_unlock_condition(UnlockCondition::try_from_dto(u, token_supply)?);
399 }
400
401 Ok(builder.finish(token_supply)?)
402 }
403
404 pub fn try_from_dto_unverified(value: &BasicOutputDto) -> Result<Self, DtoError> {
405 let mut builder = Self::_try_from_dto(value)?;
406
407 for u in &value.unlock_conditions {
408 builder = builder.add_unlock_condition(UnlockCondition::try_from_dto_unverified(u)?);
409 }
410
411 Ok(builder.finish_unverified()?)
412 }
413
414 pub fn try_from_dtos(
415 amount: OutputBuilderAmountDto,
416 native_tokens: Option<Vec<NativeTokenDto>>,
417 unlock_conditions: Vec<UnlockConditionDto>,
418 features: Option<Vec<FeatureDto>>,
419 token_supply: u64,
420 ) -> Result<Self, DtoError> {
421 let mut builder = match amount {
422 OutputBuilderAmountDto::Amount(amount) => {
423 BasicOutputBuilder::new_with_amount(amount.parse().map_err(|_| DtoError::InvalidField("amount"))?)?
424 }
425 OutputBuilderAmountDto::MinimumStorageDeposit(rent_structure) => {
426 BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure)?
427 }
428 };
429
430 if let Some(native_tokens) = native_tokens {
431 let native_tokens = native_tokens
432 .iter()
433 .map(NativeToken::try_from)
434 .collect::<Result<Vec<NativeToken>, DtoError>>()?;
435 builder = builder.with_native_tokens(native_tokens);
436 }
437
438 let unlock_conditions = unlock_conditions
439 .iter()
440 .map(|u| UnlockCondition::try_from_dto(u, token_supply))
441 .collect::<Result<Vec<UnlockCondition>, DtoError>>()?;
442 builder = builder.with_unlock_conditions(unlock_conditions);
443
444 if let Some(features) = features {
445 let features = features
446 .iter()
447 .map(Feature::try_from)
448 .collect::<Result<Vec<Feature>, DtoError>>()?;
449 builder = builder.with_features(features);
450 }
451
452 Ok(builder.finish(token_supply)?)
453 }
454 }
455}