1mod alias_id;
5mod chain_id;
6mod foundry_id;
7mod inputs_commitment;
8mod metadata;
9mod native_token;
10mod nft_id;
11mod output_id;
12mod rent;
13mod state_transition;
14mod token_id;
15mod token_scheme;
16mod treasury;
17
18pub mod alias;
20pub mod basic;
22pub mod feature;
24pub mod foundry;
26pub mod nft;
28pub mod unlock_condition;
30
31use core::ops::RangeInclusive;
32
33use derive_more::From;
34use packable::{
35 error::{UnpackError, UnpackErrorExt},
36 packer::Packer,
37 unpacker::Unpacker,
38 Packable, PackableExt,
39};
40
41pub(crate) use self::{
42 alias::StateMetadataLength,
43 feature::{MetadataFeatureLength, TagFeatureLength},
44 native_token::NativeTokenCount,
45 output_id::OutputIndex,
46 unlock_condition::AddressUnlockCondition,
47};
48pub use self::{
49 alias::{AliasOutput, AliasOutputBuilder, AliasTransition},
50 alias_id::AliasId,
51 basic::{BasicOutput, BasicOutputBuilder},
52 chain_id::ChainId,
53 feature::{Feature, Features},
54 foundry::{FoundryOutput, FoundryOutputBuilder},
55 foundry_id::FoundryId,
56 inputs_commitment::InputsCommitment,
57 metadata::OutputMetadata,
58 native_token::{NativeToken, NativeTokens, NativeTokensBuilder},
59 nft::{NftOutput, NftOutputBuilder},
60 nft_id::NftId,
61 output_id::OutputId,
62 rent::{Rent, RentStructure, RentStructureBuilder},
63 state_transition::{StateTransitionError, StateTransitionVerifier},
64 token_id::TokenId,
65 token_scheme::{SimpleTokenScheme, TokenScheme},
66 treasury::TreasuryOutput,
67 unlock_condition::{UnlockCondition, UnlockConditions},
68};
69use crate::block::{address::Address, protocol::ProtocolParameters, semantic::ValidationContext, Error};
70
71pub const OUTPUT_COUNT_MAX: u16 = 128;
73pub const OUTPUT_COUNT_RANGE: RangeInclusive<u16> = 1..=OUTPUT_COUNT_MAX; pub const OUTPUT_INDEX_MAX: u16 = OUTPUT_COUNT_MAX - 1; pub const OUTPUT_INDEX_RANGE: RangeInclusive<u16> = 0..=OUTPUT_INDEX_MAX; #[derive(Clone)]
81pub(crate) enum OutputBuilderAmount {
82 Amount(u64),
83 MinimumStorageDeposit(RentStructure),
84}
85
86#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From)]
88#[cfg_attr(
89 feature = "serde",
90 derive(serde::Serialize, serde::Deserialize),
91 serde(tag = "type", content = "data")
92)]
93pub enum Output {
94 Treasury(TreasuryOutput),
96 Basic(BasicOutput),
98 Alias(AliasOutput),
100 Foundry(FoundryOutput),
102 Nft(NftOutput),
104}
105
106impl Output {
107 pub const AMOUNT_MIN: u64 = 1;
109
110 pub fn kind(&self) -> u8 {
112 match self {
113 Self::Treasury(_) => TreasuryOutput::KIND,
114 Self::Basic(_) => BasicOutput::KIND,
115 Self::Alias(_) => AliasOutput::KIND,
116 Self::Foundry(_) => FoundryOutput::KIND,
117 Self::Nft(_) => NftOutput::KIND,
118 }
119 }
120
121 pub fn amount(&self) -> u64 {
123 match self {
124 Self::Treasury(output) => output.amount(),
125 Self::Basic(output) => output.amount(),
126 Self::Alias(output) => output.amount(),
127 Self::Foundry(output) => output.amount(),
128 Self::Nft(output) => output.amount(),
129 }
130 }
131
132 pub fn native_tokens(&self) -> Option<&NativeTokens> {
134 match self {
135 Self::Treasury(_) => None,
136 Self::Basic(output) => Some(output.native_tokens()),
137 Self::Alias(output) => Some(output.native_tokens()),
138 Self::Foundry(output) => Some(output.native_tokens()),
139 Self::Nft(output) => Some(output.native_tokens()),
140 }
141 }
142
143 pub fn unlock_conditions(&self) -> Option<&UnlockConditions> {
145 match self {
146 Self::Treasury(_) => None,
147 Self::Basic(output) => Some(output.unlock_conditions()),
148 Self::Alias(output) => Some(output.unlock_conditions()),
149 Self::Foundry(output) => Some(output.unlock_conditions()),
150 Self::Nft(output) => Some(output.unlock_conditions()),
151 }
152 }
153
154 pub fn features(&self) -> Option<&Features> {
156 match self {
157 Self::Treasury(_) => None,
158 Self::Basic(output) => Some(output.features()),
159 Self::Alias(output) => Some(output.features()),
160 Self::Foundry(output) => Some(output.features()),
161 Self::Nft(output) => Some(output.features()),
162 }
163 }
164
165 pub fn immutable_features(&self) -> Option<&Features> {
167 match self {
168 Self::Treasury(_) => None,
169 Self::Basic(_) => None,
170 Self::Alias(output) => Some(output.immutable_features()),
171 Self::Foundry(output) => Some(output.immutable_features()),
172 Self::Nft(output) => Some(output.immutable_features()),
173 }
174 }
175
176 pub fn chain_id(&self) -> Option<ChainId> {
178 match self {
179 Self::Treasury(_) => None,
180 Self::Basic(_) => None,
181 Self::Alias(output) => Some(output.chain_id()),
182 Self::Foundry(output) => Some(output.chain_id()),
183 Self::Nft(output) => Some(output.chain_id()),
184 }
185 }
186
187 pub fn is_treasury(&self) -> bool {
189 matches!(self, Self::Treasury(_))
190 }
191
192 pub fn as_treasury(&self) -> &TreasuryOutput {
195 if let Self::Treasury(output) = self {
196 output
197 } else {
198 panic!("as_treasury called on a non-treasury output");
199 }
200 }
201
202 pub fn is_basic(&self) -> bool {
204 matches!(self, Self::Basic(_))
205 }
206
207 pub fn as_basic(&self) -> &BasicOutput {
210 if let Self::Basic(output) = self {
211 output
212 } else {
213 panic!("as_basic called on a non-basic output");
214 }
215 }
216
217 pub fn is_alias(&self) -> bool {
219 matches!(self, Self::Alias(_))
220 }
221
222 pub fn as_alias(&self) -> &AliasOutput {
225 if let Self::Alias(output) = self {
226 output
227 } else {
228 panic!("as_alias called on a non-alias output");
229 }
230 }
231
232 pub fn is_foundry(&self) -> bool {
234 matches!(self, Self::Foundry(_))
235 }
236
237 pub fn as_foundry(&self) -> &FoundryOutput {
240 if let Self::Foundry(output) = self {
241 output
242 } else {
243 panic!("as_foundry called on a non-foundry output");
244 }
245 }
246
247 pub fn is_nft(&self) -> bool {
249 matches!(self, Self::Nft(_))
250 }
251
252 pub fn as_nft(&self) -> &NftOutput {
255 if let Self::Nft(output) = self {
256 output
257 } else {
258 panic!("as_nft called on a non-nft output");
259 }
260 }
261
262 pub fn required_and_unlocked_address(
266 &self,
267 current_time: u32,
268 output_id: &OutputId,
269 alias_transition: Option<AliasTransition>,
270 ) -> Result<(Address, Option<Address>), Error> {
271 match self {
272 Self::Alias(output) => {
273 if alias_transition.unwrap_or(AliasTransition::State) == AliasTransition::State {
274 Ok((
276 *output.state_controller_address(),
277 Some(Address::Alias(output.alias_address(output_id))),
278 ))
279 } else {
280 Ok((*output.governor_address(), None))
281 }
282 }
283 Self::Basic(output) => Ok((
284 *output
285 .unlock_conditions()
286 .locked_address(output.address(), current_time),
287 None,
288 )),
289 Self::Nft(output) => Ok((
290 *output
291 .unlock_conditions()
292 .locked_address(output.address(), current_time),
293 Some(Address::Nft(output.nft_address(output_id))),
294 )),
295 Self::Foundry(output) => Ok((Address::Alias(*output.alias_address()), None)),
296 Self::Treasury(_) => Err(Error::UnsupportedOutputKind(TreasuryOutput::KIND)),
297 }
298 }
299
300 pub fn verify_state_transition(
302 current_state: Option<&Self>,
303 next_state: Option<&Self>,
304 context: &ValidationContext<'_>,
305 ) -> Result<(), StateTransitionError> {
306 match (current_state, next_state) {
307 (None, Some(Self::Alias(next_state))) => AliasOutput::creation(next_state, context),
309 (None, Some(Self::Foundry(next_state))) => FoundryOutput::creation(next_state, context),
310 (None, Some(Self::Nft(next_state))) => NftOutput::creation(next_state, context),
311
312 (Some(Self::Alias(current_state)), Some(Self::Alias(next_state))) => {
314 AliasOutput::transition(current_state, next_state, context)
315 }
316 (Some(Self::Foundry(current_state)), Some(Self::Foundry(next_state))) => {
317 FoundryOutput::transition(current_state, next_state, context)
318 }
319 (Some(Self::Nft(current_state)), Some(Self::Nft(next_state))) => {
320 NftOutput::transition(current_state, next_state, context)
321 }
322
323 (Some(Self::Alias(current_state)), None) => AliasOutput::destruction(current_state, context),
325 (Some(Self::Foundry(current_state)), None) => FoundryOutput::destruction(current_state, context),
326 (Some(Self::Nft(current_state)), None) => NftOutput::destruction(current_state, context),
327
328 _ => Err(StateTransitionError::UnsupportedStateTransition),
330 }
331 }
332
333 pub fn verify_storage_deposit(&self, rent_structure: RentStructure, token_supply: u64) -> Result<(), Error> {
338 let required_output_amount = self.rent_cost(&rent_structure);
339
340 if self.amount() < required_output_amount {
341 return Err(Error::InsufficientStorageDepositAmount {
342 amount: self.amount(),
343 required: required_output_amount,
344 });
345 }
346
347 if let Some(return_condition) = self
348 .unlock_conditions()
349 .and_then(UnlockConditions::storage_deposit_return)
350 {
351 if return_condition.amount() > self.amount() {
354 return Err(Error::StorageDepositReturnExceedsOutputAmount {
355 deposit: return_condition.amount(),
356 amount: self.amount(),
357 });
358 }
359
360 let minimum_deposit =
361 minimum_storage_deposit(return_condition.return_address(), rent_structure, token_supply);
362
363 if return_condition.amount() < minimum_deposit {
365 return Err(Error::InsufficientStorageDepositReturnAmount {
366 deposit: return_condition.amount(),
367 required: minimum_deposit,
368 });
369 }
370 }
371
372 Ok(())
373 }
374}
375
376impl Packable for Output {
377 type UnpackError = Error;
378 type UnpackVisitor = ProtocolParameters;
379
380 fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error> {
381 match self {
382 Self::Treasury(output) => {
383 TreasuryOutput::KIND.pack(packer)?;
384 output.pack(packer)
385 }
386 Self::Basic(output) => {
387 BasicOutput::KIND.pack(packer)?;
388 output.pack(packer)
389 }
390 Self::Alias(output) => {
391 AliasOutput::KIND.pack(packer)?;
392 output.pack(packer)
393 }
394 Self::Foundry(output) => {
395 FoundryOutput::KIND.pack(packer)?;
396 output.pack(packer)
397 }
398 Self::Nft(output) => {
399 NftOutput::KIND.pack(packer)?;
400 output.pack(packer)
401 }
402 }?;
403
404 Ok(())
405 }
406
407 fn unpack<U: Unpacker, const VERIFY: bool>(
408 unpacker: &mut U,
409 visitor: &Self::UnpackVisitor,
410 ) -> Result<Self, UnpackError<Self::UnpackError, U::Error>> {
411 Ok(match u8::unpack::<_, VERIFY>(unpacker, &()).coerce()? {
412 TreasuryOutput::KIND => Self::from(TreasuryOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
413 BasicOutput::KIND => Self::from(BasicOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
414 AliasOutput::KIND => Self::from(AliasOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
415 FoundryOutput::KIND => Self::from(FoundryOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
416 NftOutput::KIND => Self::from(NftOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
417 k => return Err(Error::InvalidOutputKind(k)).map_err(UnpackError::Packable),
418 })
419 }
420}
421
422impl Rent for Output {
423 fn weighted_bytes(&self, rent_structure: &RentStructure) -> u64 {
424 self.packed_len() as u64 * rent_structure.byte_factor_data() as u64
425 }
426}
427
428pub(crate) fn verify_output_amount<const VERIFY: bool>(amount: &u64, token_supply: &u64) -> Result<(), Error> {
429 if VERIFY && (*amount < Output::AMOUNT_MIN || amount > token_supply) {
430 Err(Error::InvalidOutputAmount(*amount))
431 } else {
432 Ok(())
433 }
434}
435
436pub(crate) fn verify_output_amount_packable<const VERIFY: bool>(
437 amount: &u64,
438 protocol_parameters: &ProtocolParameters,
439) -> Result<(), Error> {
440 verify_output_amount::<VERIFY>(amount, &protocol_parameters.token_supply())
441}
442
443fn minimum_storage_deposit(address: &Address, rent_structure: RentStructure, token_supply: u64) -> u64 {
446 let address_condition = UnlockCondition::Address(AddressUnlockCondition::new(*address));
447 BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure)
450 .unwrap()
451 .add_unlock_condition(address_condition)
452 .finish(token_supply)
453 .unwrap()
454 .amount()
455}
456
457#[cfg(feature = "dto")]
458#[allow(missing_docs)]
459pub mod dto {
460 use serde::{Deserialize, Serialize, Serializer};
461 use serde_json::Value;
462
463 use super::*;
464 pub use super::{
465 alias::dto::AliasOutputDto,
466 alias_id::dto::AliasIdDto,
467 basic::dto::BasicOutputDto,
468 foundry::dto::FoundryOutputDto,
469 metadata::dto::OutputMetadataDto,
470 native_token::dto::NativeTokenDto,
471 nft::dto::NftOutputDto,
472 nft_id::dto::NftIdDto,
473 rent::dto::RentStructureDto,
474 token_id::dto::TokenIdDto,
475 token_scheme::dto::{SimpleTokenSchemeDto, TokenSchemeDto},
476 treasury::dto::TreasuryOutputDto,
477 };
478 use crate::block::error::dto::DtoError;
479
480 #[derive(Clone, Debug, Deserialize, From)]
481 pub enum OutputBuilderAmountDto {
482 Amount(String),
483 MinimumStorageDeposit(RentStructure),
484 }
485
486 #[derive(Clone, Debug, Eq, PartialEq, From)]
488 pub enum OutputDto {
489 Treasury(TreasuryOutputDto),
490 Basic(BasicOutputDto),
491 Alias(AliasOutputDto),
492 Foundry(FoundryOutputDto),
493 Nft(NftOutputDto),
494 }
495
496 impl From<&Output> for OutputDto {
497 fn from(value: &Output) -> Self {
498 match value {
499 Output::Treasury(o) => Self::Treasury(o.into()),
500 Output::Basic(o) => Self::Basic(o.into()),
501 Output::Alias(o) => Self::Alias(o.into()),
502 Output::Foundry(o) => Self::Foundry(o.into()),
503 Output::Nft(o) => Self::Nft(o.into()),
504 }
505 }
506 }
507
508 impl Output {
509 pub fn try_from_dto(value: &OutputDto, token_supply: u64) -> Result<Self, DtoError> {
510 Ok(match value {
511 OutputDto::Treasury(o) => Self::Treasury(TreasuryOutput::try_from_dto(o, token_supply)?),
512 OutputDto::Basic(o) => Self::Basic(BasicOutput::try_from_dto(o, token_supply)?),
513 OutputDto::Alias(o) => Self::Alias(AliasOutput::try_from_dto(o, token_supply)?),
514 OutputDto::Foundry(o) => Self::Foundry(FoundryOutput::try_from_dto(o, token_supply)?),
515 OutputDto::Nft(o) => Self::Nft(NftOutput::try_from_dto(o, token_supply)?),
516 })
517 }
518
519 pub fn try_from_dto_unverified(value: &OutputDto) -> Result<Self, DtoError> {
520 Ok(match value {
521 OutputDto::Treasury(o) => Self::Treasury(TreasuryOutput::try_from_dto_unverified(o)?),
522 OutputDto::Basic(o) => Self::Basic(BasicOutput::try_from_dto_unverified(o)?),
523 OutputDto::Alias(o) => Self::Alias(AliasOutput::try_from_dto_unverified(o)?),
524 OutputDto::Foundry(o) => Self::Foundry(FoundryOutput::try_from_dto_unverified(o)?),
525 OutputDto::Nft(o) => Self::Nft(NftOutput::try_from_dto_unverified(o)?),
526 })
527 }
528 }
529
530 impl<'de> Deserialize<'de> for OutputDto {
531 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
532 let value = Value::deserialize(d)?;
533 Ok(
534 match value
535 .get("type")
536 .and_then(Value::as_u64)
537 .ok_or_else(|| serde::de::Error::custom("invalid output type"))? as u8
538 {
539 TreasuryOutput::KIND => {
540 Self::Treasury(TreasuryOutputDto::deserialize(value).map_err(|e| {
541 serde::de::Error::custom(format!("cannot deserialize treasury output: {e}"))
542 })?)
543 }
544 BasicOutput::KIND => Self::Basic(
545 BasicOutputDto::deserialize(value)
546 .map_err(|e| serde::de::Error::custom(format!("cannot deserialize basic output: {e}")))?,
547 ),
548 AliasOutput::KIND => Self::Alias(
549 AliasOutputDto::deserialize(value)
550 .map_err(|e| serde::de::Error::custom(format!("cannot deserialize alias output: {e}")))?,
551 ),
552 FoundryOutput::KIND => Self::Foundry(
553 FoundryOutputDto::deserialize(value)
554 .map_err(|e| serde::de::Error::custom(format!("cannot deserialize foundry output: {e}")))?,
555 ),
556 NftOutput::KIND => Self::Nft(
557 NftOutputDto::deserialize(value)
558 .map_err(|e| serde::de::Error::custom(format!("cannot deserialize NFT output: {e}")))?,
559 ),
560 _ => return Err(serde::de::Error::custom("invalid output type")),
561 },
562 )
563 }
564 }
565
566 impl Serialize for OutputDto {
567 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
568 where
569 S: Serializer,
570 {
571 #[derive(Serialize)]
572 #[serde(untagged)]
573 enum OutputDto_<'a> {
574 T1(&'a TreasuryOutputDto),
575 T2(&'a BasicOutputDto),
576 T3(&'a AliasOutputDto),
577 T4(&'a FoundryOutputDto),
578 T5(&'a NftOutputDto),
579 }
580 #[derive(Serialize)]
581 struct TypedOutput<'a> {
582 #[serde(flatten)]
583 output: OutputDto_<'a>,
584 }
585 let output = match self {
586 Self::Treasury(o) => TypedOutput {
587 output: OutputDto_::T1(o),
588 },
589 Self::Basic(o) => TypedOutput {
590 output: OutputDto_::T2(o),
591 },
592 Self::Alias(o) => TypedOutput {
593 output: OutputDto_::T3(o),
594 },
595 Self::Foundry(o) => TypedOutput {
596 output: OutputDto_::T4(o),
597 },
598 Self::Nft(o) => TypedOutput {
599 output: OutputDto_::T5(o),
600 },
601 };
602 output.serialize(serializer)
603 }
604 }
605}