diff --git a/sdk/src/types/block/output/account.rs b/sdk/src/types/block/output/account.rs index dc6bcbe39d..977d432973 100644 --- a/sdk/src/types/block/output/account.rs +++ b/sdk/src/types/block/output/account.rs @@ -16,12 +16,10 @@ use crate::types::block::{ output::{ feature::{verify_allowed_features, Feature, FeatureFlags, Features}, unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions}, - ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StateTransitionError, - StateTransitionVerifier, StorageScore, StorageScoreParameters, + ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters, }, - payload::signed_transaction::TransactionCapabilityFlag, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, - semantic::{SemanticValidationContext, TransactionFailureReason}, + semantic::{SemanticValidationContext, StateTransitionError, TransactionFailureReason}, unlock::Unlock, Error, }; @@ -464,45 +462,6 @@ impl AccountOutput { } } -impl StateTransitionVerifier for AccountOutput { - fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { - if !next_state.account_id.is_null() { - return Err(StateTransitionError::NonZeroCreatedId); - } - - if let Some(issuer) = next_state.immutable_features().issuer() { - if !context.unlocked_addresses.contains(issuer.address()) { - return Err(StateTransitionError::IssuerNotUnlocked); - } - } - - Ok(()) - } - - fn transition( - current_state: &Self, - next_state: &Self, - context: &SemanticValidationContext<'_>, - ) -> Result<(), StateTransitionError> { - Self::transition_inner( - current_state, - next_state, - &context.input_chains, - context.transaction.outputs(), - ) - } - - fn destruction(_current_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { - if !context - .transaction - .has_capability(TransactionCapabilityFlag::DestroyAccountOutputs) - { - return Err(TransactionFailureReason::TransactionCapabilityAccountDestructionNotAllowed)?; - } - Ok(()) - } -} - impl StorageScore for AccountOutput { fn storage_score(&self, params: StorageScoreParameters) -> u64 { params.output_offset() diff --git a/sdk/src/types/block/output/anchor.rs b/sdk/src/types/block/output/anchor.rs index de5db1b0ab..dc1270983b 100644 --- a/sdk/src/types/block/output/anchor.rs +++ b/sdk/src/types/block/output/anchor.rs @@ -16,12 +16,10 @@ use crate::types::block::{ output::{ feature::{verify_allowed_features, Feature, FeatureFlags, Features}, unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions}, - ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StateTransitionError, - StateTransitionVerifier, StorageScore, StorageScoreParameters, + ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters, }, - payload::signed_transaction::TransactionCapabilityFlag, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, - semantic::{SemanticValidationContext, TransactionFailureReason}, + semantic::{SemanticValidationContext, StateTransitionError, TransactionFailureReason}, unlock::Unlock, Error, }; @@ -517,46 +515,6 @@ impl WorkScore for AnchorOutput { impl MinimumOutputAmount for AnchorOutput {} -impl StateTransitionVerifier for AnchorOutput { - fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { - if !next_state.anchor_id.is_null() { - return Err(StateTransitionError::NonZeroCreatedId); - } - - if let Some(issuer) = next_state.immutable_features().issuer() { - if !context.unlocked_addresses.contains(issuer.address()) { - return Err(StateTransitionError::IssuerNotUnlocked); - } - } - - Ok(()) - } - - fn transition( - current_state: &Self, - next_state: &Self, - context: &SemanticValidationContext<'_>, - ) -> Result<(), StateTransitionError> { - Self::transition_inner( - current_state, - next_state, - &context.input_chains, - context.transaction.outputs(), - ) - } - - fn destruction(_current_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { - if !context - .transaction - .capabilities() - .has_capability(TransactionCapabilityFlag::DestroyAnchorOutputs) - { - return Err(TransactionFailureReason::TransactionCapabilityAccountDestructionNotAllowed)?; - } - Ok(()) - } -} - impl Packable for AnchorOutput { type UnpackError = Error; type UnpackVisitor = ProtocolParameters; diff --git a/sdk/src/types/block/output/delegation.rs b/sdk/src/types/block/output/delegation.rs index 885a5afba5..fa64224726 100644 --- a/sdk/src/types/block/output/delegation.rs +++ b/sdk/src/types/block/output/delegation.rs @@ -10,11 +10,10 @@ use crate::types::block::{ output::{ chain_id::ChainId, unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions}, - MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StateTransitionError, StateTransitionVerifier, - StorageScore, StorageScoreParameters, + MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters, }, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, - semantic::{SemanticValidationContext, TransactionFailureReason}, + semantic::{SemanticValidationContext, StateTransitionError, TransactionFailureReason}, slot::EpochIndex, unlock::Unlock, Error, @@ -353,40 +352,6 @@ impl DelegationOutput { } } -impl StateTransitionVerifier for DelegationOutput { - fn creation(next_state: &Self, _context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { - if !next_state.delegation_id.is_null() { - return Err(StateTransitionError::NonZeroCreatedId); - } - - if next_state.amount != next_state.delegated_amount { - return Err(StateTransitionError::InvalidDelegatedAmount); - } - - if next_state.end_epoch != 0 { - return Err(StateTransitionError::NonZeroDelegationEndEpoch); - } - - Ok(()) - } - - fn transition( - current_state: &Self, - next_state: &Self, - _context: &SemanticValidationContext<'_>, - ) -> Result<(), StateTransitionError> { - Self::transition_inner(current_state, next_state) - } - - fn destruction( - _current_state: &Self, - _context: &SemanticValidationContext<'_>, - ) -> Result<(), StateTransitionError> { - // TODO handle mana rewards - Ok(()) - } -} - impl StorageScore for DelegationOutput { fn storage_score(&self, params: StorageScoreParameters) -> u64 { params.output_offset() diff --git a/sdk/src/types/block/output/foundry.rs b/sdk/src/types/block/output/foundry.rs index 497d459969..20bf4656fe 100644 --- a/sdk/src/types/block/output/foundry.rs +++ b/sdk/src/types/block/output/foundry.rs @@ -18,12 +18,12 @@ use crate::types::block::{ account::AccountId, feature::{verify_allowed_features, Feature, FeatureFlags, Features, NativeTokenFeature}, unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions}, - ChainId, MinimumOutputAmount, NativeToken, Output, OutputBuilderAmount, OutputId, StateTransitionError, - StateTransitionVerifier, StorageScore, StorageScoreParameters, TokenId, TokenScheme, + ChainId, MinimumOutputAmount, NativeToken, Output, OutputBuilderAmount, OutputId, StorageScore, + StorageScoreParameters, TokenId, TokenScheme, }, payload::signed_transaction::{TransactionCapabilities, TransactionCapabilityFlag}, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, - semantic::{SemanticValidationContext, TransactionFailureReason}, + semantic::{SemanticValidationContext, StateTransitionError, TransactionFailureReason}, unlock::Unlock, Error, }; @@ -522,81 +522,6 @@ impl WorkScore for FoundryOutput { impl MinimumOutputAmount for FoundryOutput {} -impl StateTransitionVerifier for FoundryOutput { - fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { - let account_chain_id = ChainId::from(*next_state.account_address().account_id()); - - if let (Some(Output::Account(input_account)), Some(Output::Account(output_account))) = ( - context.input_chains.get(&account_chain_id), - context.output_chains.get(&account_chain_id), - ) { - if input_account.foundry_counter() >= next_state.serial_number() - || next_state.serial_number() > output_account.foundry_counter() - { - return Err(StateTransitionError::InconsistentFoundrySerialNumber); - } - } else { - return Err(StateTransitionError::MissingAccountForFoundry); - } - - let token_id = next_state.token_id(); - let output_tokens = context.output_native_tokens.get(&token_id).copied().unwrap_or_default(); - let TokenScheme::Simple(ref next_token_scheme) = next_state.token_scheme; - - // No native tokens should be referenced prior to the foundry creation. - if context.input_native_tokens.contains_key(&token_id) { - return Err(StateTransitionError::InconsistentNativeTokensFoundryCreation); - } - - if output_tokens != next_token_scheme.minted_tokens() || !next_token_scheme.melted_tokens().is_zero() { - return Err(StateTransitionError::InconsistentNativeTokensFoundryCreation); - } - - Ok(()) - } - - fn transition( - current_state: &Self, - next_state: &Self, - context: &SemanticValidationContext<'_>, - ) -> Result<(), StateTransitionError> { - Self::transition_inner( - current_state, - next_state, - &context.input_native_tokens, - &context.output_native_tokens, - context.transaction.capabilities(), - ) - } - - fn destruction(current_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { - if !context - .transaction - .has_capability(TransactionCapabilityFlag::DestroyFoundryOutputs) - { - return Err(TransactionFailureReason::TransactionCapabilityFoundryDestructionNotAllowed)?; - } - - let token_id = current_state.token_id(); - let input_tokens = context.input_native_tokens.get(&token_id).copied().unwrap_or_default(); - let TokenScheme::Simple(ref current_token_scheme) = current_state.token_scheme; - - // No native tokens should be referenced after the foundry destruction. - if context.output_native_tokens.contains_key(&token_id) { - return Err(StateTransitionError::InconsistentNativeTokensFoundryDestruction); - } - - // This can't underflow as it is known that minted_tokens >= melted_tokens (syntactic rule). - let minted_melted_diff = current_token_scheme.minted_tokens() - current_token_scheme.melted_tokens(); - - if minted_melted_diff != input_tokens { - return Err(StateTransitionError::InconsistentNativeTokensFoundryDestruction); - } - - Ok(()) - } -} - impl Packable for FoundryOutput { type UnpackError = Error; type UnpackVisitor = ProtocolParameters; diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index 501a2b93d1..e2ec4241a5 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -8,7 +8,6 @@ mod metadata; mod native_token; mod output_id; mod output_id_proof; -mod state_transition; mod storage_score; mod token_scheme; @@ -43,7 +42,6 @@ pub use self::{ nft::{NftId, NftOutput, NftOutputBuilder}, output_id::OutputId, output_id_proof::{HashableNode, LeafHash, OutputCommitmentProof, OutputIdProof, ValueHash}, - state_transition::{StateTransitionError, StateTransitionVerifier}, storage_score::{StorageScore, StorageScoreParameters}, token_scheme::{SimpleTokenScheme, TokenScheme}, unlock_condition::{UnlockCondition, UnlockConditions}, @@ -57,7 +55,6 @@ pub(crate) use self::{ use crate::types::block::{ address::Address, protocol::{CommittableAgeRange, ProtocolParameters, WorkScore, WorkScoreParameters}, - semantic::SemanticValidationContext, slot::SlotIndex, Error, }; @@ -313,52 +310,6 @@ impl Output { }) } - /// - pub fn verify_state_transition( - current_state: Option<&Self>, - next_state: Option<&Self>, - context: &SemanticValidationContext<'_>, - ) -> Result<(), StateTransitionError> { - match (current_state, next_state) { - // Creations. - (None, Some(Self::Account(next_state))) => AccountOutput::creation(next_state, context), - (None, Some(Self::Foundry(next_state))) => FoundryOutput::creation(next_state, context), - (None, Some(Self::Nft(next_state))) => NftOutput::creation(next_state, context), - (None, Some(Self::Delegation(next_state))) => DelegationOutput::creation(next_state, context), - - // Transitions. - (Some(Self::Basic(current_state)), Some(Self::Account(_next_state))) => { - if !current_state.is_implicit_account() { - Err(StateTransitionError::UnsupportedStateTransition) - } else { - // TODO https://github.com/iotaledger/iota-sdk/issues/1664 - Ok(()) - } - } - (Some(Self::Account(current_state)), Some(Self::Account(next_state))) => { - AccountOutput::transition(current_state, next_state, context) - } - (Some(Self::Foundry(current_state)), Some(Self::Foundry(next_state))) => { - FoundryOutput::transition(current_state, next_state, context) - } - (Some(Self::Nft(current_state)), Some(Self::Nft(next_state))) => { - NftOutput::transition(current_state, next_state, context) - } - (Some(Self::Delegation(current_state)), Some(Self::Delegation(next_state))) => { - DelegationOutput::transition(current_state, next_state, context) - } - - // Destructions. - (Some(Self::Account(current_state)), None) => AccountOutput::destruction(current_state, context), - (Some(Self::Foundry(current_state)), None) => FoundryOutput::destruction(current_state, context), - (Some(Self::Nft(current_state)), None) => NftOutput::destruction(current_state, context), - (Some(Self::Delegation(current_state)), None) => DelegationOutput::destruction(current_state, context), - - // Unsupported. - _ => Err(StateTransitionError::UnsupportedStateTransition), - } - } - /// Verifies if a valid storage deposit was made. Each [`Output`] has to have an amount that covers its associated /// byte cost, given by [`StorageScoreParameters`]. /// If there is a [`StorageDepositReturnUnlockCondition`](unlock_condition::StorageDepositReturnUnlockCondition), diff --git a/sdk/src/types/block/output/nft.rs b/sdk/src/types/block/output/nft.rs index b580db81fd..3398b5a6af 100644 --- a/sdk/src/types/block/output/nft.rs +++ b/sdk/src/types/block/output/nft.rs @@ -18,12 +18,11 @@ use crate::types::block::{ verify_allowed_unlock_conditions, AddressUnlockCondition, StorageDepositReturnUnlockCondition, UnlockCondition, UnlockConditionFlags, UnlockConditions, }, - BasicOutputBuilder, ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StateTransitionError, - StateTransitionVerifier, StorageScore, StorageScoreParameters, + BasicOutputBuilder, ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, + StorageScoreParameters, }, - payload::signed_transaction::TransactionCapabilityFlag, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, - semantic::{SemanticValidationContext, TransactionFailureReason}, + semantic::{SemanticValidationContext, StateTransitionError, TransactionFailureReason}, unlock::Unlock, Error, }; @@ -475,40 +474,6 @@ impl WorkScore for NftOutput { impl MinimumOutputAmount for NftOutput {} -impl StateTransitionVerifier for NftOutput { - fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { - if !next_state.nft_id.is_null() { - return Err(StateTransitionError::NonZeroCreatedId); - } - - if let Some(issuer) = next_state.immutable_features().issuer() { - if !context.unlocked_addresses.contains(issuer.address()) { - return Err(StateTransitionError::IssuerNotUnlocked); - } - } - - Ok(()) - } - - fn transition( - current_state: &Self, - next_state: &Self, - _context: &SemanticValidationContext<'_>, - ) -> Result<(), StateTransitionError> { - Self::transition_inner(current_state, next_state) - } - - fn destruction(_current_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { - if !context - .transaction - .has_capability(TransactionCapabilityFlag::DestroyNftOutputs) - { - return Err(TransactionFailureReason::TransactionCapabilityNftDestructionNotAllowed)?; - } - Ok(()) - } -} - impl Packable for NftOutput { type UnpackError = Error; type UnpackVisitor = ProtocolParameters; diff --git a/sdk/src/types/block/output/state_transition.rs b/sdk/src/types/block/output/state_transition.rs deleted file mode 100644 index bc1ca6fc36..0000000000 --- a/sdk/src/types/block/output/state_transition.rs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use crate::types::block::semantic::{SemanticValidationContext, TransactionFailureReason}; - -/// -#[allow(missing_docs)] -#[derive(Debug, Eq, PartialEq)] -pub enum StateTransitionError { - InconsistentCreatedFoundriesCount, - InconsistentFoundrySerialNumber, - InconsistentNativeTokensFoundryCreation, - InconsistentNativeTokensFoundryDestruction, - InconsistentNativeTokensMint, - InconsistentNativeTokensTransition, - InconsistentNativeTokensMeltBurn, - InvalidDelegatedAmount, - IssuerNotUnlocked, - MissingAccountForFoundry, - MutatedFieldWithoutRights, - MutatedImmutableField, - NonDelayedClaimingTransition, - NonMonotonicallyIncreasingNativeTokens, - NonZeroCreatedId, - NonZeroCreatedFoundryCounter, - NonZeroCreatedStateIndex, - NonZeroDelegationEndEpoch, - UnsortedCreatedFoundries, - UnsupportedStateIndexOperation { current_state: u32, next_state: u32 }, - UnsupportedStateTransition, - TransactionFailure(TransactionFailureReason), -} - -impl From for StateTransitionError { - fn from(error: TransactionFailureReason) -> Self { - Self::TransactionFailure(error) - } -} - -/// -pub trait StateTransitionVerifier { - /// - fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError>; - - /// - fn transition( - current_state: &Self, - next_state: &Self, - context: &SemanticValidationContext<'_>, - ) -> Result<(), StateTransitionError>; - - /// - fn destruction(current_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError>; -} diff --git a/sdk/src/types/block/semantic.rs b/sdk/src/types/block/semantic/mod.rs similarity index 98% rename from sdk/src/types/block/semantic.rs rename to sdk/src/types/block/semantic/mod.rs index bdc896f8cc..beea9713a6 100644 --- a/sdk/src/types/block/semantic.rs +++ b/sdk/src/types/block/semantic/mod.rs @@ -1,18 +1,18 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +mod state_transition; + use alloc::collections::BTreeMap; use core::fmt; use hashbrown::{HashMap, HashSet}; use primitive_types::U256; +pub use self::state_transition::{StateTransitionError, StateTransitionVerifier}; use crate::types::block::{ address::{Address, AddressCapabilityFlag}, - output::{ - AccountId, AnchorOutput, ChainId, FoundryId, NativeTokens, Output, OutputId, StateTransitionError, TokenId, - UnlockCondition, - }, + output::{AccountId, AnchorOutput, ChainId, FoundryId, NativeTokens, Output, OutputId, TokenId, UnlockCondition}, payload::signed_transaction::{Transaction, TransactionCapabilityFlag, TransactionSigningHash}, protocol::ProtocolParameters, unlock::Unlock, @@ -516,10 +516,9 @@ impl<'a> SemanticValidationContext<'a> { // Validation of state transitions and destructions. for (chain_id, current_state) in self.input_chains.iter() { - match Output::verify_state_transition( + match self.verify_state_transition( Some(current_state), self.output_chains.get(chain_id).map(core::ops::Deref::deref), - &self, ) { Err(StateTransitionError::TransactionFailure(f)) => return Ok(Some(f)), Err(_) => { @@ -532,7 +531,7 @@ impl<'a> SemanticValidationContext<'a> { // Validation of state creations. for (chain_id, next_state) in self.output_chains.iter() { if self.input_chains.get(chain_id).is_none() { - match Output::verify_state_transition(None, Some(next_state), &self) { + match self.verify_state_transition(None, Some(next_state)) { Err(StateTransitionError::TransactionFailure(f)) => return Ok(Some(f)), Err(_) => { return Ok(Some(TransactionFailureReason::InvalidChainStateTransition)); diff --git a/sdk/src/types/block/semantic/state_transition.rs b/sdk/src/types/block/semantic/state_transition.rs new file mode 100644 index 0000000000..22afecc78c --- /dev/null +++ b/sdk/src/types/block/semantic/state_transition.rs @@ -0,0 +1,328 @@ +// Copyright 2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::types::block::{ + output::{AccountOutput, AnchorOutput, ChainId, DelegationOutput, FoundryOutput, NftOutput, Output, TokenScheme}, + payload::signed_transaction::TransactionCapabilityFlag, + semantic::{SemanticValidationContext, TransactionFailureReason}, +}; + +/// +#[allow(missing_docs)] +#[derive(Debug, Eq, PartialEq)] +pub enum StateTransitionError { + InconsistentCreatedFoundriesCount, + InconsistentFoundrySerialNumber, + InconsistentNativeTokensFoundryCreation, + InconsistentNativeTokensFoundryDestruction, + InconsistentNativeTokensMint, + InconsistentNativeTokensTransition, + InconsistentNativeTokensMeltBurn, + InvalidDelegatedAmount, + IssuerNotUnlocked, + MissingAccountForFoundry, + MutatedFieldWithoutRights, + MutatedImmutableField, + NonDelayedClaimingTransition, + NonMonotonicallyIncreasingNativeTokens, + NonZeroCreatedId, + NonZeroCreatedFoundryCounter, + NonZeroCreatedStateIndex, + NonZeroDelegationEndEpoch, + UnsortedCreatedFoundries, + UnsupportedStateIndexOperation { current_state: u32, next_state: u32 }, + UnsupportedStateTransition, + TransactionFailure(TransactionFailureReason), +} + +impl From for StateTransitionError { + fn from(error: TransactionFailureReason) -> Self { + Self::TransactionFailure(error) + } +} + +/// +pub trait StateTransitionVerifier { + /// + fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError>; + + /// + fn transition( + current_state: &Self, + next_state: &Self, + context: &SemanticValidationContext<'_>, + ) -> Result<(), StateTransitionError>; + + /// + fn destruction(current_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError>; +} + +impl SemanticValidationContext<'_> { + /// + pub fn verify_state_transition( + &self, + current_state: Option<&Output>, + next_state: Option<&Output>, + ) -> Result<(), StateTransitionError> { + match (current_state, next_state) { + // Creations. + (None, Some(Output::Account(next_state))) => AccountOutput::creation(next_state, self), + (None, Some(Output::Foundry(next_state))) => FoundryOutput::creation(next_state, self), + (None, Some(Output::Nft(next_state))) => NftOutput::creation(next_state, self), + (None, Some(Output::Delegation(next_state))) => DelegationOutput::creation(next_state, self), + + // Transitions. + (Some(Output::Basic(current_state)), Some(Output::Account(_next_state))) => { + if !current_state.is_implicit_account() { + Err(StateTransitionError::UnsupportedStateTransition) + } else { + // TODO https://github.com/iotaledger/iota-sdk/issues/1664 + Ok(()) + } + } + (Some(Output::Account(current_state)), Some(Output::Account(next_state))) => { + AccountOutput::transition(current_state, next_state, self) + } + (Some(Output::Foundry(current_state)), Some(Output::Foundry(next_state))) => { + FoundryOutput::transition(current_state, next_state, self) + } + (Some(Output::Nft(current_state)), Some(Output::Nft(next_state))) => { + NftOutput::transition(current_state, next_state, self) + } + (Some(Output::Delegation(current_state)), Some(Output::Delegation(next_state))) => { + DelegationOutput::transition(current_state, next_state, self) + } + + // Destructions. + (Some(Output::Account(current_state)), None) => AccountOutput::destruction(current_state, self), + (Some(Output::Foundry(current_state)), None) => FoundryOutput::destruction(current_state, self), + (Some(Output::Nft(current_state)), None) => NftOutput::destruction(current_state, self), + (Some(Output::Delegation(current_state)), None) => DelegationOutput::destruction(current_state, self), + + // Unsupported. + _ => Err(StateTransitionError::UnsupportedStateTransition), + } + } +} + +impl StateTransitionVerifier for AccountOutput { + fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { + if !next_state.account_id().is_null() { + return Err(StateTransitionError::NonZeroCreatedId); + } + + if let Some(issuer) = next_state.immutable_features().issuer() { + if !context.unlocked_addresses.contains(issuer.address()) { + return Err(StateTransitionError::IssuerNotUnlocked); + } + } + + Ok(()) + } + + fn transition( + current_state: &Self, + next_state: &Self, + context: &SemanticValidationContext<'_>, + ) -> Result<(), StateTransitionError> { + Self::transition_inner( + current_state, + next_state, + &context.input_chains, + context.transaction.outputs(), + ) + } + + fn destruction(_current_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { + if !context + .transaction + .has_capability(TransactionCapabilityFlag::DestroyAccountOutputs) + { + return Err(TransactionFailureReason::TransactionCapabilityAccountDestructionNotAllowed)?; + } + Ok(()) + } +} + +impl StateTransitionVerifier for AnchorOutput { + fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { + if !next_state.anchor_id().is_null() { + return Err(StateTransitionError::NonZeroCreatedId); + } + + if let Some(issuer) = next_state.immutable_features().issuer() { + if !context.unlocked_addresses.contains(issuer.address()) { + return Err(StateTransitionError::IssuerNotUnlocked); + } + } + + Ok(()) + } + + fn transition( + current_state: &Self, + next_state: &Self, + context: &SemanticValidationContext<'_>, + ) -> Result<(), StateTransitionError> { + Self::transition_inner( + current_state, + next_state, + &context.input_chains, + context.transaction.outputs(), + ) + } + + fn destruction(_current_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { + if !context + .transaction + .capabilities() + .has_capability(TransactionCapabilityFlag::DestroyAnchorOutputs) + { + return Err(TransactionFailureReason::TransactionCapabilityAccountDestructionNotAllowed)?; + } + Ok(()) + } +} + +impl StateTransitionVerifier for FoundryOutput { + fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { + let account_chain_id = ChainId::from(*next_state.account_address().account_id()); + + if let (Some(Output::Account(input_account)), Some(Output::Account(output_account))) = ( + context.input_chains.get(&account_chain_id), + context.output_chains.get(&account_chain_id), + ) { + if input_account.foundry_counter() >= next_state.serial_number() + || next_state.serial_number() > output_account.foundry_counter() + { + return Err(StateTransitionError::InconsistentFoundrySerialNumber); + } + } else { + return Err(StateTransitionError::MissingAccountForFoundry); + } + + let token_id = next_state.token_id(); + let output_tokens = context.output_native_tokens.get(&token_id).copied().unwrap_or_default(); + let TokenScheme::Simple(ref next_token_scheme) = next_state.token_scheme(); + + // No native tokens should be referenced prior to the foundry creation. + if context.input_native_tokens.contains_key(&token_id) { + return Err(StateTransitionError::InconsistentNativeTokensFoundryCreation); + } + + if output_tokens != next_token_scheme.minted_tokens() || !next_token_scheme.melted_tokens().is_zero() { + return Err(StateTransitionError::InconsistentNativeTokensFoundryCreation); + } + + Ok(()) + } + + fn transition( + current_state: &Self, + next_state: &Self, + context: &SemanticValidationContext<'_>, + ) -> Result<(), StateTransitionError> { + Self::transition_inner( + current_state, + next_state, + &context.input_native_tokens, + &context.output_native_tokens, + context.transaction.capabilities(), + ) + } + + fn destruction(current_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { + if !context + .transaction + .has_capability(TransactionCapabilityFlag::DestroyFoundryOutputs) + { + return Err(TransactionFailureReason::TransactionCapabilityFoundryDestructionNotAllowed)?; + } + + let token_id = current_state.token_id(); + let input_tokens = context.input_native_tokens.get(&token_id).copied().unwrap_or_default(); + let TokenScheme::Simple(ref current_token_scheme) = current_state.token_scheme(); + + // No native tokens should be referenced after the foundry destruction. + if context.output_native_tokens.contains_key(&token_id) { + return Err(StateTransitionError::InconsistentNativeTokensFoundryDestruction); + } + + // This can't underflow as it is known that minted_tokens >= melted_tokens (syntactic rule). + let minted_melted_diff = current_token_scheme.minted_tokens() - current_token_scheme.melted_tokens(); + + if minted_melted_diff != input_tokens { + return Err(StateTransitionError::InconsistentNativeTokensFoundryDestruction); + } + + Ok(()) + } +} + +impl StateTransitionVerifier for NftOutput { + fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { + if !next_state.nft_id().is_null() { + return Err(StateTransitionError::NonZeroCreatedId); + } + + if let Some(issuer) = next_state.immutable_features().issuer() { + if !context.unlocked_addresses.contains(issuer.address()) { + return Err(StateTransitionError::IssuerNotUnlocked); + } + } + + Ok(()) + } + + fn transition( + current_state: &Self, + next_state: &Self, + _context: &SemanticValidationContext<'_>, + ) -> Result<(), StateTransitionError> { + Self::transition_inner(current_state, next_state) + } + + fn destruction(_current_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { + if !context + .transaction + .has_capability(TransactionCapabilityFlag::DestroyNftOutputs) + { + return Err(TransactionFailureReason::TransactionCapabilityNftDestructionNotAllowed)?; + } + Ok(()) + } +} + +impl StateTransitionVerifier for DelegationOutput { + fn creation(next_state: &Self, _context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { + if !next_state.delegation_id().is_null() { + return Err(StateTransitionError::NonZeroCreatedId); + } + + if next_state.amount() != next_state.delegated_amount() { + return Err(StateTransitionError::InvalidDelegatedAmount); + } + + if next_state.end_epoch() != 0 { + return Err(StateTransitionError::NonZeroDelegationEndEpoch); + } + + Ok(()) + } + + fn transition( + current_state: &Self, + next_state: &Self, + _context: &SemanticValidationContext<'_>, + ) -> Result<(), StateTransitionError> { + Self::transition_inner(current_state, next_state) + } + + fn destruction( + _current_state: &Self, + _context: &SemanticValidationContext<'_>, + ) -> Result<(), StateTransitionError> { + // TODO handle mana rewards + Ok(()) + } +}