mod alias_id;
mod chain_id;
mod foundry_id;
mod inputs_commitment;
mod native_token;
mod nft_id;
mod output_id;
mod rent;
mod state_transition;
mod token_id;
mod token_scheme;
mod treasury;
pub mod alias;
pub mod basic;
pub mod feature;
pub mod foundry;
pub mod nft;
pub mod unlock_condition;
use core::ops::RangeInclusive;
use derive_more::From;
use packable::{
error::{UnpackError, UnpackErrorExt},
packer::Packer,
unpacker::Unpacker,
Packable, PackableExt,
};
pub(crate) use self::{
alias::StateMetadataLength,
feature::{MetadataFeatureLength, TagFeatureLength},
native_token::NativeTokenCount,
output_id::OutputIndex,
unlock_condition::AddressUnlockCondition,
};
pub use self::{
alias::{AliasOutput, AliasOutputBuilder},
alias_id::AliasId,
basic::{BasicOutput, BasicOutputBuilder},
chain_id::ChainId,
feature::{Feature, Features},
foundry::{FoundryOutput, FoundryOutputBuilder},
foundry_id::FoundryId,
inputs_commitment::InputsCommitment,
native_token::{NativeToken, NativeTokens, NativeTokensBuilder},
nft::{NftOutput, NftOutputBuilder},
nft_id::NftId,
output_id::OutputId,
rent::{Rent, RentStructure, RentStructureBuilder},
state_transition::{StateTransitionError, StateTransitionVerifier},
token_id::TokenId,
token_scheme::{SimpleTokenScheme, TokenScheme},
treasury::TreasuryOutput,
unlock_condition::{UnlockCondition, UnlockConditions},
};
use crate::{address::Address, protocol::ProtocolParameters, semantic::ValidationContext, Error};
pub const OUTPUT_COUNT_MAX: u16 = 128;
pub 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)]
pub(crate) enum OutputBuilderAmount {
Amount(u64),
MinimumStorageDeposit(RentStructure),
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, From)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", content = "data")
)]
pub enum Output {
Treasury(TreasuryOutput),
Basic(BasicOutput),
Alias(AliasOutput),
Foundry(FoundryOutput),
Nft(NftOutput),
}
impl Output {
pub const AMOUNT_MIN: u64 = 1;
pub fn kind(&self) -> u8 {
match self {
Self::Treasury(_) => TreasuryOutput::KIND,
Self::Basic(_) => BasicOutput::KIND,
Self::Alias(_) => AliasOutput::KIND,
Self::Foundry(_) => FoundryOutput::KIND,
Self::Nft(_) => NftOutput::KIND,
}
}
pub fn amount(&self) -> u64 {
match self {
Self::Treasury(output) => output.amount(),
Self::Basic(output) => output.amount(),
Self::Alias(output) => output.amount(),
Self::Foundry(output) => output.amount(),
Self::Nft(output) => output.amount(),
}
}
pub fn native_tokens(&self) -> Option<&NativeTokens> {
match self {
Self::Treasury(_) => None,
Self::Basic(output) => Some(output.native_tokens()),
Self::Alias(output) => Some(output.native_tokens()),
Self::Foundry(output) => Some(output.native_tokens()),
Self::Nft(output) => Some(output.native_tokens()),
}
}
pub fn unlock_conditions(&self) -> Option<&UnlockConditions> {
match self {
Self::Treasury(_) => None,
Self::Basic(output) => Some(output.unlock_conditions()),
Self::Alias(output) => Some(output.unlock_conditions()),
Self::Foundry(output) => Some(output.unlock_conditions()),
Self::Nft(output) => Some(output.unlock_conditions()),
}
}
pub fn features(&self) -> Option<&Features> {
match self {
Self::Treasury(_) => None,
Self::Basic(output) => Some(output.features()),
Self::Alias(output) => Some(output.features()),
Self::Foundry(output) => Some(output.features()),
Self::Nft(output) => Some(output.features()),
}
}
pub fn immutable_features(&self) -> Option<&Features> {
match self {
Self::Treasury(_) => None,
Self::Basic(_) => None,
Self::Alias(output) => Some(output.immutable_features()),
Self::Foundry(output) => Some(output.immutable_features()),
Self::Nft(output) => Some(output.immutable_features()),
}
}
pub fn chain_id(&self) -> Option<ChainId> {
match self {
Self::Treasury(_) => None,
Self::Basic(_) => None,
Self::Alias(output) => Some(output.chain_id()),
Self::Foundry(output) => Some(output.chain_id()),
Self::Nft(output) => Some(output.chain_id()),
}
}
pub fn verify_state_transition(
current_state: Option<&Output>,
next_state: Option<&Output>,
context: &ValidationContext,
) -> Result<(), StateTransitionError> {
match (current_state, next_state) {
(None, Some(Output::Alias(next_state))) => AliasOutput::creation(next_state, context),
(None, Some(Output::Foundry(next_state))) => FoundryOutput::creation(next_state, context),
(None, Some(Output::Nft(next_state))) => NftOutput::creation(next_state, context),
(Some(Output::Alias(current_state)), Some(Output::Alias(next_state))) => {
AliasOutput::transition(current_state, next_state, context)
}
(Some(Output::Foundry(current_state)), Some(Output::Foundry(next_state))) => {
FoundryOutput::transition(current_state, next_state, context)
}
(Some(Output::Nft(current_state)), Some(Output::Nft(next_state))) => {
NftOutput::transition(current_state, next_state, context)
}
(Some(Output::Alias(current_state)), None) => AliasOutput::destruction(current_state, context),
(Some(Output::Foundry(current_state)), None) => FoundryOutput::destruction(current_state, context),
(Some(Output::Nft(current_state)), None) => NftOutput::destruction(current_state, context),
_ => Err(StateTransitionError::UnsupportedStateTransition),
}
}
pub fn verify_storage_deposit(&self, rent_structure: RentStructure, token_supply: u64) -> Result<(), Error> {
let required_output_amount = self.rent_cost(&rent_structure);
if self.amount() < required_output_amount {
return Err(Error::InsufficientStorageDepositAmount {
amount: self.amount(),
required: required_output_amount,
});
}
if let Some(return_condition) = self
.unlock_conditions()
.and_then(UnlockConditions::storage_deposit_return)
{
if return_condition.amount() > self.amount() {
return Err(Error::StorageDepositReturnExceedsOutputAmount {
deposit: return_condition.amount(),
amount: self.amount(),
});
}
let minimum_deposit =
minimum_storage_deposit(return_condition.return_address(), rent_structure, token_supply);
if return_condition.amount() < minimum_deposit {
return Err(Error::InsufficientStorageDepositReturnAmount {
deposit: return_condition.amount(),
required: minimum_deposit,
});
}
}
Ok(())
}
}
impl Packable for Output {
type UnpackError = Error;
type UnpackVisitor = ProtocolParameters;
fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error> {
match self {
Output::Treasury(output) => {
TreasuryOutput::KIND.pack(packer)?;
output.pack(packer)
}
Output::Basic(output) => {
BasicOutput::KIND.pack(packer)?;
output.pack(packer)
}
Output::Alias(output) => {
AliasOutput::KIND.pack(packer)?;
output.pack(packer)
}
Output::Foundry(output) => {
FoundryOutput::KIND.pack(packer)?;
output.pack(packer)
}
Output::Nft(output) => {
NftOutput::KIND.pack(packer)?;
output.pack(packer)
}
}?;
Ok(())
}
fn unpack<U: Unpacker, const VERIFY: bool>(
unpacker: &mut U,
visitor: &Self::UnpackVisitor,
) -> Result<Self, UnpackError<Self::UnpackError, U::Error>> {
Ok(match u8::unpack::<_, VERIFY>(unpacker, &()).coerce()? {
TreasuryOutput::KIND => Output::from(TreasuryOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
BasicOutput::KIND => Output::from(BasicOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
AliasOutput::KIND => Output::from(AliasOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
FoundryOutput::KIND => Output::from(FoundryOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
NftOutput::KIND => Output::from(NftOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
k => return Err(Error::InvalidOutputKind(k)).map_err(UnpackError::Packable),
})
}
}
impl Rent for Output {
fn weighted_bytes(&self, rent_structure: &RentStructure) -> u64 {
self.packed_len() as u64 * rent_structure.v_byte_factor_data as u64
}
}
pub(crate) fn verify_output_amount<const VERIFY: bool>(amount: &u64, token_supply: &u64) -> Result<(), Error> {
if VERIFY && (*amount < Output::AMOUNT_MIN || amount > token_supply) {
Err(Error::InvalidOutputAmount(*amount))
} else {
Ok(())
}
}
pub(crate) fn verify_output_amount_packable<const VERIFY: bool>(
amount: &u64,
protocol_parameters: &ProtocolParameters,
) -> Result<(), Error> {
verify_output_amount::<VERIFY>(amount, &protocol_parameters.token_supply())
}
fn minimum_storage_deposit(address: &Address, rent_structure: RentStructure, token_supply: u64) -> u64 {
let address_condition = UnlockCondition::Address(AddressUnlockCondition::new(*address));
BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure)
.unwrap()
.add_unlock_condition(address_condition)
.finish(token_supply)
.unwrap()
.amount()
}
#[cfg(feature = "dto")]
#[allow(missing_docs)]
pub mod dto {
use serde::{Deserialize, Serialize, Serializer};
use serde_json::Value;
use super::*;
pub use super::{
alias::dto::AliasOutputDto,
alias_id::dto::AliasIdDto,
basic::dto::BasicOutputDto,
foundry::dto::FoundryOutputDto,
native_token::dto::NativeTokenDto,
nft::dto::NftOutputDto,
nft_id::dto::NftIdDto,
token_id::dto::TokenIdDto,
token_scheme::dto::{SimpleTokenSchemeDto, TokenSchemeDto},
treasury::dto::TreasuryOutputDto,
};
use crate::error::dto::DtoError;
#[derive(Clone, Debug, Deserialize, From)]
pub enum OutputBuilderAmountDto {
Amount(String),
MinimumStorageDeposit(RentStructure),
}
#[derive(Clone, Debug, Eq, PartialEq, From)]
pub enum OutputDto {
Treasury(TreasuryOutputDto),
Basic(BasicOutputDto),
Alias(AliasOutputDto),
Foundry(FoundryOutputDto),
Nft(NftOutputDto),
}
impl From<&Output> for OutputDto {
fn from(value: &Output) -> Self {
match value {
Output::Treasury(o) => OutputDto::Treasury(o.into()),
Output::Basic(o) => OutputDto::Basic(o.into()),
Output::Alias(o) => OutputDto::Alias(o.into()),
Output::Foundry(o) => OutputDto::Foundry(o.into()),
Output::Nft(o) => OutputDto::Nft(o.into()),
}
}
}
impl Output {
pub fn try_from_dto(value: &OutputDto, token_supply: u64) -> Result<Output, DtoError> {
Ok(match value {
OutputDto::Treasury(o) => Output::Treasury(TreasuryOutput::try_from_dto(o, token_supply)?),
OutputDto::Basic(o) => Output::Basic(BasicOutput::try_from_dto(o, token_supply)?),
OutputDto::Alias(o) => Output::Alias(AliasOutput::try_from_dto(o, token_supply)?),
OutputDto::Foundry(o) => Output::Foundry(FoundryOutput::try_from_dto(o, token_supply)?),
OutputDto::Nft(o) => Output::Nft(NftOutput::try_from_dto(o, token_supply)?),
})
}
}
impl<'de> Deserialize<'de> for OutputDto {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let value = Value::deserialize(d)?;
Ok(
match value
.get("type")
.and_then(Value::as_u64)
.ok_or_else(|| serde::de::Error::custom("invalid output type"))? as u8
{
TreasuryOutput::KIND => {
OutputDto::Treasury(TreasuryOutputDto::deserialize(value).map_err(|e| {
serde::de::Error::custom(format!("cannot deserialize treasury output: {}", e))
})?)
}
BasicOutput::KIND => OutputDto::Basic(
BasicOutputDto::deserialize(value)
.map_err(|e| serde::de::Error::custom(format!("cannot deserialize basic output: {}", e)))?,
),
AliasOutput::KIND => OutputDto::Alias(
AliasOutputDto::deserialize(value)
.map_err(|e| serde::de::Error::custom(format!("cannot deserialize alias output: {}", e)))?,
),
FoundryOutput::KIND => {
OutputDto::Foundry(FoundryOutputDto::deserialize(value).map_err(|e| {
serde::de::Error::custom(format!("cannot deserialize foundry output: {}", e))
})?)
}
NftOutput::KIND => OutputDto::Nft(
NftOutputDto::deserialize(value)
.map_err(|e| serde::de::Error::custom(format!("cannot deserialize NFT output: {}", e)))?,
),
_ => return Err(serde::de::Error::custom("invalid output type")),
},
)
}
}
impl Serialize for OutputDto {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
#[serde(untagged)]
enum OutputDto_<'a> {
T1(&'a TreasuryOutputDto),
T2(&'a BasicOutputDto),
T3(&'a AliasOutputDto),
T4(&'a FoundryOutputDto),
T5(&'a NftOutputDto),
}
#[derive(Serialize)]
struct TypedOutput<'a> {
#[serde(flatten)]
output: OutputDto_<'a>,
}
let output = match self {
OutputDto::Treasury(o) => TypedOutput {
output: OutputDto_::T1(o),
},
OutputDto::Basic(o) => TypedOutput {
output: OutputDto_::T2(o),
},
OutputDto::Alias(o) => TypedOutput {
output: OutputDto_::T3(o),
},
OutputDto::Foundry(o) => TypedOutput {
output: OutputDto_::T4(o),
},
OutputDto::Nft(o) => TypedOutput {
output: OutputDto_::T5(o),
},
};
output.serialize(serializer)
}
}
}