1use alloc::vec::Vec;
5
6use packable::Packable;
7
8use crate::{
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) -> Result<Self, Error> {
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 => return Err(Error::CannotReplaceMissingField),
106 }
107 Ok(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) -> Result<Self, Error> {
126 match self.features.iter_mut().find(|f| f.kind() == feature.kind()) {
127 Some(f) => *f = feature,
128 None => return Err(Error::CannotReplaceMissingField),
129 }
130 Ok(self)
131 }
132
133 pub fn finish(self, token_supply: u64) -> 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 verify_output_amount::<true>(&output.amount, &token_supply)?;
158
159 Ok(output)
160 }
161
162 pub fn finish_output(self, token_supply: u64) -> Result<Output, Error> {
164 Ok(Output::Basic(self.finish(token_supply)?))
165 }
166}
167
168impl From<&BasicOutput> for BasicOutputBuilder {
169 fn from(output: &BasicOutput) -> Self {
170 BasicOutputBuilder {
171 amount: OutputBuilderAmount::Amount(output.amount),
172 native_tokens: output.native_tokens.to_vec(),
173 unlock_conditions: output.unlock_conditions.to_vec(),
174 features: output.features.to_vec(),
175 }
176 }
177}
178
179#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable)]
181#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
182#[packable(unpack_error = Error)]
183#[packable(unpack_visitor = ProtocolParameters)]
184pub struct BasicOutput {
185 #[packable(verify_with = verify_output_amount_packable)]
187 amount: u64,
188 native_tokens: NativeTokens,
190 #[packable(verify_with = verify_unlock_conditions_packable)]
191 unlock_conditions: UnlockConditions,
192 #[packable(verify_with = verify_features_packable)]
193 features: Features,
194}
195
196impl BasicOutput {
197 pub const KIND: u8 = 3;
199
200 const ALLOWED_UNLOCK_CONDITIONS: UnlockConditionFlags = UnlockConditionFlags::ADDRESS
202 .union(UnlockConditionFlags::STORAGE_DEPOSIT_RETURN)
203 .union(UnlockConditionFlags::TIMELOCK)
204 .union(UnlockConditionFlags::EXPIRATION);
205 pub const ALLOWED_FEATURES: FeatureFlags = FeatureFlags::SENDER
207 .union(FeatureFlags::METADATA)
208 .union(FeatureFlags::TAG);
209
210 #[inline(always)]
212 pub fn new_with_amount(amount: u64, token_supply: u64) -> Result<Self, Error> {
213 BasicOutputBuilder::new_with_amount(amount)?.finish(token_supply)
214 }
215
216 #[inline(always)]
219 pub fn new_with_minimum_storage_deposit(rent_structure: RentStructure, token_supply: u64) -> Result<Self, Error> {
220 BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure)?.finish(token_supply)
221 }
222
223 #[inline(always)]
225 pub fn build_with_amount(amount: u64) -> Result<BasicOutputBuilder, Error> {
226 BasicOutputBuilder::new_with_amount(amount)
227 }
228
229 #[inline(always)]
232 pub fn build_with_minimum_storage_deposit(rent_structure: RentStructure) -> Result<BasicOutputBuilder, Error> {
233 BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure)
234 }
235
236 #[inline(always)]
238 pub fn amount(&self) -> u64 {
239 self.amount
240 }
241
242 #[inline(always)]
244 pub fn native_tokens(&self) -> &NativeTokens {
245 &self.native_tokens
246 }
247
248 #[inline(always)]
250 pub fn unlock_conditions(&self) -> &UnlockConditions {
251 &self.unlock_conditions
252 }
253
254 #[inline(always)]
256 pub fn features(&self) -> &Features {
257 &self.features
258 }
259
260 #[inline(always)]
262 pub fn address(&self) -> &Address {
263 self.unlock_conditions
265 .address()
266 .map(|unlock_condition| unlock_condition.address())
267 .unwrap()
268 }
269
270 pub fn unlock(
272 &self,
273 _output_id: &OutputId,
274 unlock: &Unlock,
275 inputs: &[(OutputId, &Output)],
276 context: &mut ValidationContext,
277 ) -> Result<(), ConflictReason> {
278 self.unlock_conditions()
279 .locked_address(self.address(), context.milestone_timestamp)
280 .unlock(unlock, inputs, context)
281 }
282
283 pub fn simple_deposit_address(&self) -> Option<&Address> {
287 if let [UnlockCondition::Address(address)] = self.unlock_conditions().as_ref() {
288 if self.native_tokens.is_empty() && self.features.is_empty() {
289 return Some(address.address());
290 }
291 }
292
293 None
294 }
295}
296
297fn verify_unlock_conditions<const VERIFY: bool>(unlock_conditions: &UnlockConditions) -> Result<(), Error> {
298 if VERIFY {
299 if unlock_conditions.address().is_none() {
300 Err(Error::MissingAddressUnlockCondition)
301 } else {
302 verify_allowed_unlock_conditions(unlock_conditions, BasicOutput::ALLOWED_UNLOCK_CONDITIONS)
303 }
304 } else {
305 Ok(())
306 }
307}
308
309fn verify_unlock_conditions_packable<const VERIFY: bool>(
310 unlock_conditions: &UnlockConditions,
311 _: &ProtocolParameters,
312) -> Result<(), Error> {
313 verify_unlock_conditions::<VERIFY>(unlock_conditions)
314}
315
316fn verify_features<const VERIFY: bool>(blocks: &Features) -> Result<(), Error> {
317 if VERIFY {
318 verify_allowed_features(blocks, BasicOutput::ALLOWED_FEATURES)
319 } else {
320 Ok(())
321 }
322}
323
324fn verify_features_packable<const VERIFY: bool>(blocks: &Features, _: &ProtocolParameters) -> Result<(), Error> {
325 verify_features::<VERIFY>(blocks)
326}
327
328#[cfg(feature = "dto")]
329#[allow(missing_docs)]
330pub mod dto {
331 use serde::{Deserialize, Serialize};
332
333 use super::*;
334 use crate::{
335 error::dto::DtoError,
336 output::{
337 dto::OutputBuilderAmountDto, feature::dto::FeatureDto, native_token::dto::NativeTokenDto,
338 unlock_condition::dto::UnlockConditionDto,
339 },
340 };
341
342 #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
344 pub struct BasicOutputDto {
345 #[serde(rename = "type")]
346 pub kind: u8,
347 pub amount: String,
349 #[serde(rename = "nativeTokens", skip_serializing_if = "Vec::is_empty", default)]
351 pub native_tokens: Vec<NativeTokenDto>,
352 #[serde(rename = "unlockConditions")]
353 pub unlock_conditions: Vec<UnlockConditionDto>,
354 #[serde(skip_serializing_if = "Vec::is_empty", default)]
355 pub features: Vec<FeatureDto>,
356 }
357
358 impl From<&BasicOutput> for BasicOutputDto {
359 fn from(value: &BasicOutput) -> Self {
360 Self {
361 kind: BasicOutput::KIND,
362 amount: value.amount().to_string(),
363 native_tokens: value.native_tokens().iter().map(Into::into).collect::<_>(),
364 unlock_conditions: value.unlock_conditions().iter().map(Into::into).collect::<_>(),
365 features: value.features().iter().map(Into::into).collect::<_>(),
366 }
367 }
368 }
369
370 impl BasicOutput {
371 pub fn try_from_dto(value: &BasicOutputDto, token_supply: u64) -> Result<BasicOutput, DtoError> {
372 let mut builder = BasicOutputBuilder::new_with_amount(
373 value.amount.parse().map_err(|_| DtoError::InvalidField("amount"))?,
374 )?;
375
376 for t in &value.native_tokens {
377 builder = builder.add_native_token(t.try_into()?);
378 }
379
380 for u in &value.unlock_conditions {
381 builder = builder.add_unlock_condition(UnlockCondition::try_from_dto(u, token_supply)?);
382 }
383
384 for b in &value.features {
385 builder = builder.add_feature(b.try_into()?);
386 }
387
388 Ok(builder.finish(token_supply)?)
389 }
390
391 pub fn try_from_dtos(
392 amount: OutputBuilderAmountDto,
393 native_tokens: Option<Vec<NativeTokenDto>>,
394 unlock_conditions: Vec<UnlockConditionDto>,
395 features: Option<Vec<FeatureDto>>,
396 token_supply: u64,
397 ) -> Result<BasicOutput, DtoError> {
398 let mut builder = match amount {
399 OutputBuilderAmountDto::Amount(amount) => {
400 BasicOutputBuilder::new_with_amount(amount.parse().map_err(|_| DtoError::InvalidField("amount"))?)?
401 }
402 OutputBuilderAmountDto::MinimumStorageDeposit(rent_structure) => {
403 BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure)?
404 }
405 };
406
407 if let Some(native_tokens) = native_tokens {
408 let native_tokens = native_tokens
409 .iter()
410 .map(NativeToken::try_from)
411 .collect::<Result<Vec<NativeToken>, DtoError>>()?;
412 builder = builder.with_native_tokens(native_tokens);
413 }
414
415 let unlock_conditions = unlock_conditions
416 .iter()
417 .map(|u| UnlockCondition::try_from_dto(u, token_supply))
418 .collect::<Result<Vec<UnlockCondition>, DtoError>>()?;
419 builder = builder.with_unlock_conditions(unlock_conditions);
420
421 if let Some(features) = features {
422 let features = features
423 .iter()
424 .map(Feature::try_from)
425 .collect::<Result<Vec<Feature>, DtoError>>()?;
426 builder = builder.with_features(features);
427 }
428
429 Ok(builder.finish(token_supply)?)
430 }
431 }
432}