diff --git a/sdk/src/client/api/block_builder/transaction.rs b/sdk/src/client/api/block_builder/transaction.rs index 9f3c08cce9..307e0ce910 100644 --- a/sdk/src/client/api/block_builder/transaction.rs +++ b/sdk/src/client/api/block_builder/transaction.rs @@ -10,7 +10,7 @@ use crate::{ types::block::{ output::{Output, OutputId}, payload::signed_transaction::{SignedTransactionPayload, Transaction}, - semantic::{semantic_validation, TransactionFailureReason, ValidationContext}, + semantic::{SemanticValidationContext, TransactionFailureReason}, signature::Ed25519Signature, BlockId, BlockWrapper, }, @@ -36,18 +36,14 @@ pub fn verify_semantic( .map(|input| (input.output_id(), &input.output)) .collect::>(); - let context = ValidationContext::new( - &transaction_id, + let context = SemanticValidationContext::new( transaction_payload.transaction(), - inputs.iter().map(|(id, input)| (*id, *input)), + &transaction_id, + &inputs, transaction_payload.unlocks(), ); - Ok(semantic_validation( - context, - inputs.as_slice(), - transaction_payload.unlocks(), - )?) + Ok(context.validate()?) } /// Verifies that the signed transaction payload doesn't exceed the block size limit with 8 parents. diff --git a/sdk/src/types/block/address/mod.rs b/sdk/src/types/block/address/mod.rs index 856215d37c..d12487d675 100644 --- a/sdk/src/types/block/address/mod.rs +++ b/sdk/src/types/block/address/mod.rs @@ -22,8 +22,8 @@ pub use self::{ restricted::{AddressCapabilities, AddressCapabilityFlag, RestrictedAddress}, }; use crate::types::block::{ - output::{Output, OutputId}, - semantic::{TransactionFailureReason, ValidationContext}, + output::Output, + semantic::{SemanticValidationContext, TransactionFailureReason}, signature::Signature, unlock::Unlock, ConvertTo, Error, @@ -100,8 +100,7 @@ impl Address { pub fn unlock( &self, unlock: &Unlock, - inputs: &[(&OutputId, &Output)], - context: &mut ValidationContext<'_>, + context: &mut SemanticValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { match (self, unlock) { (Self::Ed25519(ed25519_address), Unlock::Signature(unlock)) => { @@ -128,7 +127,7 @@ impl Address { } (Self::Account(account_address), Unlock::Account(unlock)) => { // PANIC: indexing is fine as it is already syntactically verified that indexes reference below. - if let (output_id, Output::Account(account_output)) = inputs[unlock.index() as usize] { + if let (output_id, Output::Account(account_output)) = context.inputs[unlock.index() as usize] { if &account_output.account_id_non_null(output_id) != account_address.account_id() { return Err(TransactionFailureReason::InvalidInputUnlock); } @@ -141,7 +140,7 @@ impl Address { } (Self::Nft(nft_address), Unlock::Nft(unlock)) => { // PANIC: indexing is fine as it is already syntactically verified that indexes reference below. - if let (output_id, Output::Nft(nft_output)) = inputs[unlock.index() as usize] { + if let (output_id, Output::Nft(nft_output)) = context.inputs[unlock.index() as usize] { if &nft_output.nft_id_non_null(output_id) != nft_address.nft_id() { return Err(TransactionFailureReason::InvalidInputUnlock); } diff --git a/sdk/src/types/block/output/account.rs b/sdk/src/types/block/output/account.rs index 3aace3b38b..16714f7557 100644 --- a/sdk/src/types/block/output/account.rs +++ b/sdk/src/types/block/output/account.rs @@ -27,7 +27,7 @@ use crate::types::{ }, payload::signed_transaction::TransactionCapabilityFlag, protocol::ProtocolParameters, - semantic::{TransactionFailureReason, ValidationContext}, + semantic::{SemanticValidationContext, TransactionFailureReason}, unlock::Unlock, Error, }, @@ -523,8 +523,7 @@ impl AccountOutput { &self, output_id: &OutputId, unlock: &Unlock, - inputs: &[(&OutputId, &Output)], - context: &mut ValidationContext<'_>, + context: &mut SemanticValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { let account_id = if self.account_id().is_null() { AccountId::from(output_id) @@ -536,9 +535,9 @@ impl AccountOutput { match next_state { Some(Output::Account(next_state)) => { if self.state_index() == next_state.state_index() { - self.governor_address().unlock(unlock, inputs, context)?; + self.governor_address().unlock(unlock, context)?; } else { - self.state_controller_address().unlock(unlock, inputs, context)?; + self.state_controller_address().unlock(unlock, context)?; // Only a state transition can be used to consider the account address for output unlocks and // sender/issuer validations. context @@ -546,7 +545,7 @@ impl AccountOutput { .insert(Address::from(AccountAddress::from(account_id))); } } - None => self.governor_address().unlock(unlock, inputs, context)?, + None => self.governor_address().unlock(unlock, context)?, // The next state can only be an account output since it is identified by an account chain identifier. Some(_) => unreachable!(), }; @@ -554,7 +553,7 @@ impl AccountOutput { Ok(()) } - // Transition, just without full ValidationContext + // Transition, just without full SemanticValidationContext pub(crate) fn transition_inner( current_state: &Self, next_state: &Self, @@ -622,7 +621,7 @@ impl AccountOutput { } impl StateTransitionVerifier for AccountOutput { - fn creation(next_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> { + fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { if !next_state.account_id.is_null() { return Err(StateTransitionError::NonZeroCreatedId); } @@ -639,7 +638,7 @@ impl StateTransitionVerifier for AccountOutput { fn transition( current_state: &Self, next_state: &Self, - context: &ValidationContext<'_>, + context: &SemanticValidationContext<'_>, ) -> Result<(), StateTransitionError> { Self::transition_inner( current_state, @@ -649,7 +648,7 @@ impl StateTransitionVerifier for AccountOutput { ) } - fn destruction(_current_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> { + fn destruction(_current_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { if !context .transaction .has_capability(TransactionCapabilityFlag::DestroyAccountOutputs) diff --git a/sdk/src/types/block/output/basic.rs b/sdk/src/types/block/output/basic.rs index c0b4a4ab74..93da725189 100644 --- a/sdk/src/types/block/output/basic.rs +++ b/sdk/src/types/block/output/basic.rs @@ -17,7 +17,7 @@ use crate::types::{ NativeTokens, Output, OutputBuilderAmount, OutputId, Rent, RentStructure, }, protocol::ProtocolParameters, - semantic::{TransactionFailureReason, ValidationContext}, + semantic::{SemanticValidationContext, TransactionFailureReason}, unlock::Unlock, Error, }, @@ -301,12 +301,11 @@ impl BasicOutput { &self, _output_id: &OutputId, unlock: &Unlock, - inputs: &[(&OutputId, &Output)], - context: &mut ValidationContext<'_>, + context: &mut SemanticValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { self.unlock_conditions() .locked_address(self.address(), context.transaction.creation_slot()) - .unlock(unlock, inputs, context) + .unlock(unlock, context) } /// Returns the address of the unlock conditions if the output is a simple deposit. diff --git a/sdk/src/types/block/output/delegation.rs b/sdk/src/types/block/output/delegation.rs index 815c93243c..0e40245870 100644 --- a/sdk/src/types/block/output/delegation.rs +++ b/sdk/src/types/block/output/delegation.rs @@ -22,7 +22,7 @@ use crate::types::{ OutputBuilderAmount, OutputId, Rent, RentStructure, StateTransitionError, StateTransitionVerifier, }, protocol::ProtocolParameters, - semantic::{TransactionFailureReason, ValidationContext}, + semantic::{SemanticValidationContext, TransactionFailureReason}, slot::EpochIndex, unlock::Unlock, Error, @@ -352,15 +352,14 @@ impl DelegationOutput { &self, _output_id: &OutputId, unlock: &Unlock, - inputs: &[(&OutputId, &Output)], - context: &mut ValidationContext<'_>, + context: &mut SemanticValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { self.unlock_conditions() .locked_address(self.address(), context.transaction.creation_slot()) - .unlock(unlock, inputs, context) + .unlock(unlock, context) } - // Transition, just without full ValidationContext. + // Transition, just without full SemanticValidationContext. pub(crate) fn transition_inner(current_state: &Self, next_state: &Self) -> Result<(), StateTransitionError> { #[allow(clippy::nonminimal_bool)] if !(current_state.delegation_id.is_null() && !next_state.delegation_id().is_null()) { @@ -379,7 +378,7 @@ impl DelegationOutput { } impl StateTransitionVerifier for DelegationOutput { - fn creation(next_state: &Self, _context: &ValidationContext<'_>) -> Result<(), StateTransitionError> { + fn creation(next_state: &Self, _context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { if !next_state.delegation_id.is_null() { return Err(StateTransitionError::NonZeroCreatedId); } @@ -398,12 +397,15 @@ impl StateTransitionVerifier for DelegationOutput { fn transition( current_state: &Self, next_state: &Self, - _context: &ValidationContext<'_>, + _context: &SemanticValidationContext<'_>, ) -> Result<(), StateTransitionError> { Self::transition_inner(current_state, next_state) } - fn destruction(_current_state: &Self, _context: &ValidationContext<'_>) -> Result<(), StateTransitionError> { + fn destruction( + _current_state: &Self, + _context: &SemanticValidationContext<'_>, + ) -> Result<(), StateTransitionError> { // TODO handle mana rewards Ok(()) } diff --git a/sdk/src/types/block/output/foundry.rs b/sdk/src/types/block/output/foundry.rs index 3cb3c8dbad..2799fd9ac6 100644 --- a/sdk/src/types/block/output/foundry.rs +++ b/sdk/src/types/block/output/foundry.rs @@ -27,7 +27,7 @@ use crate::types::{ }, payload::signed_transaction::{TransactionCapabilities, TransactionCapabilityFlag}, protocol::ProtocolParameters, - semantic::{TransactionFailureReason, ValidationContext}, + semantic::{SemanticValidationContext, TransactionFailureReason}, unlock::Unlock, Error, }, @@ -449,13 +449,12 @@ impl FoundryOutput { &self, _output_id: &OutputId, unlock: &Unlock, - inputs: &[(&OutputId, &Output)], - context: &mut ValidationContext<'_>, + context: &mut SemanticValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { - Address::from(*self.account_address()).unlock(unlock, inputs, context) + Address::from(*self.account_address()).unlock(unlock, context) } - // Transition, just without full ValidationContext + // Transition, just without full SemanticValidationContext pub(crate) fn transition_inner( current_state: &Self, next_state: &Self, @@ -544,7 +543,7 @@ impl FoundryOutput { } impl StateTransitionVerifier for FoundryOutput { - fn creation(next_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> { + 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))) = ( @@ -579,7 +578,7 @@ impl StateTransitionVerifier for FoundryOutput { fn transition( current_state: &Self, next_state: &Self, - context: &ValidationContext<'_>, + context: &SemanticValidationContext<'_>, ) -> Result<(), StateTransitionError> { Self::transition_inner( current_state, @@ -590,7 +589,7 @@ impl StateTransitionVerifier for FoundryOutput { ) } - fn destruction(current_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> { + fn destruction(current_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { if !context .transaction .has_capability(TransactionCapabilityFlag::DestroyFoundryOutputs) diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index bbf424256a..01dff5900f 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -57,7 +57,7 @@ pub use self::{ unlock_condition::{UnlockCondition, UnlockConditions}, }; use super::protocol::ProtocolParameters; -use crate::types::block::{address::Address, semantic::ValidationContext, slot::SlotIndex, Error}; +use crate::types::block::{address::Address, semantic::SemanticValidationContext, slot::SlotIndex, Error}; /// The maximum number of outputs of a transaction. pub const OUTPUT_COUNT_MAX: u16 = 128; @@ -290,7 +290,7 @@ impl Output { pub fn verify_state_transition( current_state: Option<&Self>, next_state: Option<&Self>, - context: &ValidationContext<'_>, + context: &SemanticValidationContext<'_>, ) -> Result<(), StateTransitionError> { match (current_state, next_state) { // Creations. diff --git a/sdk/src/types/block/output/nft.rs b/sdk/src/types/block/output/nft.rs index b162f15c7e..973a67df1a 100644 --- a/sdk/src/types/block/output/nft.rs +++ b/sdk/src/types/block/output/nft.rs @@ -24,7 +24,7 @@ use crate::types::{ }, payload::signed_transaction::TransactionCapabilityFlag, protocol::ProtocolParameters, - semantic::{TransactionFailureReason, ValidationContext}, + semantic::{SemanticValidationContext, TransactionFailureReason}, unlock::Unlock, Error, }, @@ -408,12 +408,11 @@ impl NftOutput { &self, output_id: &OutputId, unlock: &Unlock, - inputs: &[(&OutputId, &Output)], - context: &mut ValidationContext<'_>, + context: &mut SemanticValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { self.unlock_conditions() .locked_address(self.address(), context.transaction.creation_slot()) - .unlock(unlock, inputs, context)?; + .unlock(unlock, context)?; let nft_id = if self.nft_id().is_null() { NftId::from(output_id) @@ -428,7 +427,7 @@ impl NftOutput { Ok(()) } - // Transition, just without full ValidationContext + // Transition, just without full SemanticValidationContext pub(crate) fn transition_inner(current_state: &Self, next_state: &Self) -> Result<(), StateTransitionError> { if current_state.immutable_features != next_state.immutable_features { return Err(StateTransitionError::MutatedImmutableField); @@ -438,7 +437,7 @@ impl NftOutput { } impl StateTransitionVerifier for NftOutput { - fn creation(next_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> { + fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { if !next_state.nft_id.is_null() { return Err(StateTransitionError::NonZeroCreatedId); } @@ -455,12 +454,12 @@ impl StateTransitionVerifier for NftOutput { fn transition( current_state: &Self, next_state: &Self, - _context: &ValidationContext<'_>, + _context: &SemanticValidationContext<'_>, ) -> Result<(), StateTransitionError> { Self::transition_inner(current_state, next_state) } - fn destruction(_current_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> { + fn destruction(_current_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { if !context .transaction .has_capability(TransactionCapabilityFlag::DestroyNftOutputs) diff --git a/sdk/src/types/block/output/state_transition.rs b/sdk/src/types/block/output/state_transition.rs index 55dae28e86..08ddbc78ff 100644 --- a/sdk/src/types/block/output/state_transition.rs +++ b/sdk/src/types/block/output/state_transition.rs @@ -1,7 +1,7 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::types::block::semantic::ValidationContext; +use crate::types::block::semantic::SemanticValidationContext; /// #[allow(missing_docs)] @@ -33,15 +33,15 @@ pub enum StateTransitionError { /// pub trait StateTransitionVerifier { /// - fn creation(next_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError>; + fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError>; /// fn transition( current_state: &Self, next_state: &Self, - context: &ValidationContext<'_>, + context: &SemanticValidationContext<'_>, ) -> Result<(), StateTransitionError>; /// - fn destruction(current_state: &Self, context: &ValidationContext<'_>) -> 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.rs index 728ff701c2..d6f6240501 100644 --- a/sdk/src/types/block/semantic.rs +++ b/sdk/src/types/block/semantic.rs @@ -147,11 +147,10 @@ impl TryFrom for TransactionFailureReason { } /// -pub struct ValidationContext<'a> { +pub struct SemanticValidationContext<'a> { pub(crate) transaction: &'a Transaction, pub(crate) transaction_signing_hash: TransactionSigningHash, - // TODO - #[allow(dead_code)] + pub(crate) inputs: &'a [(&'a OutputId, &'a Output)], pub(crate) unlocks: &'a Unlocks, pub(crate) input_amount: u64, pub(crate) input_mana: u64, @@ -166,26 +165,28 @@ pub struct ValidationContext<'a> { pub(crate) simple_deposits: HashMap, } -impl<'a> ValidationContext<'a> { +impl<'a> SemanticValidationContext<'a> { /// pub fn new( - transaction_id: &TransactionId, transaction: &'a Transaction, - inputs: impl Iterator + Clone, + transaction_id: &TransactionId, + inputs: &'a [(&'a OutputId, &'a Output)], unlocks: &'a Unlocks, ) -> Self { Self { transaction, - unlocks, transaction_signing_hash: transaction.signing_hash(), + inputs, + unlocks, input_amount: 0, input_mana: 0, input_native_tokens: BTreeMap::::new(), input_chains: inputs + .iter() .filter_map(|(output_id, input)| { input .chain_id() - .map(|chain_id| (chain_id.or_from_output_id(output_id), input)) + .map(|chain_id| (chain_id.or_from_output_id(output_id), *input)) }) .collect(), output_amount: 0, @@ -209,294 +210,275 @@ impl<'a> ValidationContext<'a> { simple_deposits: HashMap::new(), } } -} - -/// -pub fn semantic_validation( - mut context: ValidationContext<'_>, - inputs: &[(&OutputId, &Output)], - unlocks: &Unlocks, -) -> Result, Error> { - // Validation of inputs. - for ((output_id, consumed_output), unlock) in inputs.iter().zip(unlocks.iter()) { - let (conflict, amount, mana, consumed_native_tokens, unlock_conditions) = match consumed_output { - Output::Basic(output) => ( - output.unlock(output_id, unlock, inputs, &mut context), - output.amount(), - output.mana(), - Some(output.native_tokens()), - output.unlock_conditions(), - ), - Output::Account(output) => ( - output.unlock(output_id, unlock, inputs, &mut context), - output.amount(), - output.mana(), - Some(output.native_tokens()), - output.unlock_conditions(), - ), - Output::Foundry(output) => ( - output.unlock(output_id, unlock, inputs, &mut context), - output.amount(), - 0, - Some(output.native_tokens()), - output.unlock_conditions(), - ), - Output::Nft(output) => ( - output.unlock(output_id, unlock, inputs, &mut context), - output.amount(), - output.mana(), - Some(output.native_tokens()), - output.unlock_conditions(), - ), - Output::Delegation(output) => ( - output.unlock(output_id, unlock, inputs, &mut context), - output.amount(), - 0, - None, - output.unlock_conditions(), - ), - }; - if let Err(conflict) = conflict { - return Ok(Some(conflict)); - } + /// + pub fn validate(mut self) -> Result, Error> { + // Validation of inputs. + for ((output_id, consumed_output), unlock) in self.inputs.iter().zip(self.unlocks.iter()) { + let (conflict, amount, mana, consumed_native_tokens, unlock_conditions) = match consumed_output { + Output::Basic(output) => ( + output.unlock(output_id, unlock, &mut self), + output.amount(), + output.mana(), + Some(output.native_tokens()), + output.unlock_conditions(), + ), + Output::Account(output) => ( + output.unlock(output_id, unlock, &mut self), + output.amount(), + output.mana(), + Some(output.native_tokens()), + output.unlock_conditions(), + ), + Output::Foundry(output) => ( + output.unlock(output_id, unlock, &mut self), + output.amount(), + 0, + Some(output.native_tokens()), + output.unlock_conditions(), + ), + Output::Nft(output) => ( + output.unlock(output_id, unlock, &mut self), + output.amount(), + output.mana(), + Some(output.native_tokens()), + output.unlock_conditions(), + ), + Output::Delegation(output) => ( + output.unlock(output_id, unlock, &mut self), + output.amount(), + 0, + None, + output.unlock_conditions(), + ), + }; + + if let Err(conflict) = conflict { + return Ok(Some(conflict)); + } - if unlock_conditions.is_time_locked(context.transaction.creation_slot()) { - return Ok(Some(TransactionFailureReason::TimelockNotExpired)); - } + if unlock_conditions.is_time_locked(self.transaction.creation_slot()) { + return Ok(Some(TransactionFailureReason::TimelockNotExpired)); + } - if !unlock_conditions.is_expired(context.transaction.creation_slot()) { - if let Some(storage_deposit_return) = unlock_conditions.storage_deposit_return() { - let amount = context - .storage_deposit_returns - .entry(storage_deposit_return.return_address().clone()) - .or_default(); + if !unlock_conditions.is_expired(self.transaction.creation_slot()) { + if let Some(storage_deposit_return) = unlock_conditions.storage_deposit_return() { + let amount = self + .storage_deposit_returns + .entry(storage_deposit_return.return_address().clone()) + .or_default(); - *amount = amount - .checked_add(storage_deposit_return.amount()) - .ok_or(Error::StorageDepositReturnOverflow)?; + *amount = amount + .checked_add(storage_deposit_return.amount()) + .ok_or(Error::StorageDepositReturnOverflow)?; + } } - } - context.input_amount = context - .input_amount - .checked_add(amount) - .ok_or(Error::ConsumedAmountOverflow)?; + self.input_amount = self + .input_amount + .checked_add(amount) + .ok_or(Error::ConsumedAmountOverflow)?; - context.input_mana = context - .input_mana - .checked_add(mana) - .ok_or(Error::ConsumedManaOverflow)?; + self.input_mana = self.input_mana.checked_add(mana).ok_or(Error::ConsumedManaOverflow)?; - if let Some(consumed_native_tokens) = consumed_native_tokens { - for native_token in consumed_native_tokens.iter() { - let native_token_amount = context.input_native_tokens.entry(*native_token.token_id()).or_default(); + if let Some(consumed_native_tokens) = consumed_native_tokens { + for native_token in consumed_native_tokens.iter() { + let native_token_amount = self.input_native_tokens.entry(*native_token.token_id()).or_default(); - *native_token_amount = native_token_amount - .checked_add(native_token.amount()) - .ok_or(Error::ConsumedNativeTokensAmountOverflow)?; + *native_token_amount = native_token_amount + .checked_add(native_token.amount()) + .ok_or(Error::ConsumedNativeTokensAmountOverflow)?; + } } } - } - // Validation of outputs. - for created_output in context.transaction.outputs() { - let (amount, mana, created_native_tokens, features) = match created_output { - Output::Basic(output) => { - if let Some(address) = output.simple_deposit_address() { - let amount = context.simple_deposits.entry(address.clone()).or_default(); - - *amount = amount - .checked_add(output.amount()) - .ok_or(Error::CreatedAmountOverflow)?; + // Validation of outputs. + for created_output in self.transaction.outputs() { + let (amount, mana, created_native_tokens, features) = match created_output { + Output::Basic(output) => { + if let Some(address) = output.simple_deposit_address() { + let amount = self.simple_deposits.entry(address.clone()).or_default(); + + *amount = amount + .checked_add(output.amount()) + .ok_or(Error::CreatedAmountOverflow)?; + } + + ( + output.amount(), + output.mana(), + Some(output.native_tokens()), + Some(output.features()), + ) } - - ( + Output::Account(output) => ( output.amount(), output.mana(), Some(output.native_tokens()), Some(output.features()), - ) - } - Output::Account(output) => ( - output.amount(), - output.mana(), - Some(output.native_tokens()), - Some(output.features()), - ), - Output::Foundry(output) => ( - output.amount(), - 0, - Some(output.native_tokens()), - Some(output.features()), - ), - Output::Nft(output) => ( - output.amount(), - output.mana(), - Some(output.native_tokens()), - Some(output.features()), - ), - Output::Delegation(output) => (output.amount(), 0, None, None), - }; - - if let Some(unlock_conditions) = created_output.unlock_conditions() { - // Check the possibly restricted address-containing conditions - let addresses = unlock_conditions - .iter() - .filter_map(|uc| match uc { - UnlockCondition::Address(uc) => Some(uc.address()), - UnlockCondition::Expiration(uc) => Some(uc.return_address()), - UnlockCondition::StateControllerAddress(uc) => Some(uc.address()), - UnlockCondition::GovernorAddress(uc) => Some(uc.address()), - _ => None, - }) - .filter_map(Address::as_restricted_opt); - for address in addresses { - if created_output.native_tokens().map(|t| t.len()).unwrap_or_default() > 0 - && !address.has_capability(AddressCapabilityFlag::OutputsWithNativeTokens) - { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + ), + Output::Foundry(output) => ( + output.amount(), + 0, + Some(output.native_tokens()), + Some(output.features()), + ), + Output::Nft(output) => ( + output.amount(), + output.mana(), + Some(output.native_tokens()), + Some(output.features()), + ), + Output::Delegation(output) => (output.amount(), 0, None, None), + }; + + if let Some(unlock_conditions) = created_output.unlock_conditions() { + // Check the possibly restricted address-containing conditions + let addresses = unlock_conditions + .iter() + .filter_map(|uc| match uc { + UnlockCondition::Address(uc) => Some(uc.address()), + UnlockCondition::Expiration(uc) => Some(uc.return_address()), + UnlockCondition::StateControllerAddress(uc) => Some(uc.address()), + UnlockCondition::GovernorAddress(uc) => Some(uc.address()), + _ => None, + }) + .filter_map(Address::as_restricted_opt); + for address in addresses { + if created_output.native_tokens().map(|t| t.len()).unwrap_or_default() > 0 + && !address.has_capability(AddressCapabilityFlag::OutputsWithNativeTokens) + { + // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 + return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + } + + if created_output.mana() > 0 && !address.has_capability(AddressCapabilityFlag::OutputsWithMana) { + // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 + return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + } + + if unlock_conditions.timelock().is_some() + && !address.has_capability(AddressCapabilityFlag::OutputsWithTimelock) + { + // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 + return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + } + + if unlock_conditions.expiration().is_some() + && !address.has_capability(AddressCapabilityFlag::OutputsWithExpiration) + { + // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 + return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + } + + if unlock_conditions.storage_deposit_return().is_some() + && !address.has_capability(AddressCapabilityFlag::OutputsWithStorageDepositReturn) + { + // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 + return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + } + + if match &created_output { + Output::Account(_) => !address.has_capability(AddressCapabilityFlag::AccountOutputs), + Output::Nft(_) => !address.has_capability(AddressCapabilityFlag::NftOutputs), + Output::Delegation(_) => !address.has_capability(AddressCapabilityFlag::DelegationOutputs), + _ => false, + } { + // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 + return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + } } + } - if created_output.mana() > 0 && !address.has_capability(AddressCapabilityFlag::OutputsWithMana) { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + if let Some(sender) = features.and_then(|f| f.sender()) { + if !self.unlocked_addresses.contains(sender.address()) { + return Ok(Some(TransactionFailureReason::SenderNotUnlocked)); } + } - if unlock_conditions.timelock().is_some() - && !address.has_capability(AddressCapabilityFlag::OutputsWithTimelock) - { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); - } + self.output_amount = self + .output_amount + .checked_add(amount) + .ok_or(Error::CreatedAmountOverflow)?; - if unlock_conditions.expiration().is_some() - && !address.has_capability(AddressCapabilityFlag::OutputsWithExpiration) - { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); - } + self.output_mana = self.output_mana.checked_add(mana).ok_or(Error::CreatedManaOverflow)?; - if unlock_conditions.storage_deposit_return().is_some() - && !address.has_capability(AddressCapabilityFlag::OutputsWithStorageDepositReturn) - { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); - } + if let Some(created_native_tokens) = created_native_tokens { + for native_token in created_native_tokens.iter() { + let native_token_amount = self.output_native_tokens.entry(*native_token.token_id()).or_default(); - if match &created_output { - Output::Account(_) => !address.has_capability(AddressCapabilityFlag::AccountOutputs), - Output::Nft(_) => !address.has_capability(AddressCapabilityFlag::NftOutputs), - Output::Delegation(_) => !address.has_capability(AddressCapabilityFlag::DelegationOutputs), - _ => false, - } { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + *native_token_amount = native_token_amount + .checked_add(native_token.amount()) + .ok_or(Error::CreatedNativeTokensAmountOverflow)?; } } } - if let Some(sender) = features.and_then(|f| f.sender()) { - if !context.unlocked_addresses.contains(sender.address()) { - return Ok(Some(TransactionFailureReason::SenderNotUnlocked)); + // Validation of storage deposit returns. + for (return_address, return_amount) in self.storage_deposit_returns.iter() { + if let Some(deposit_amount) = self.simple_deposits.get(return_address) { + if deposit_amount < return_amount { + return Ok(Some(TransactionFailureReason::StorageDepositReturnUnfulfilled)); + } + } else { + return Ok(Some(TransactionFailureReason::StorageDepositReturnUnfulfilled)); } } - context.output_amount = context - .output_amount - .checked_add(amount) - .ok_or(Error::CreatedAmountOverflow)?; - - context.output_mana = context - .output_mana - .checked_add(mana) - .ok_or(Error::CreatedManaOverflow)?; - - if let Some(created_native_tokens) = created_native_tokens { - for native_token in created_native_tokens.iter() { - let native_token_amount = context - .output_native_tokens - .entry(*native_token.token_id()) - .or_default(); - - *native_token_amount = native_token_amount - .checked_add(native_token.amount()) - .ok_or(Error::CreatedNativeTokensAmountOverflow)?; - } + // Validation of amounts. + if self.input_amount != self.output_amount { + return Ok(Some(TransactionFailureReason::SumInputsOutputsAmountMismatch)); } - } - // Validation of storage deposit returns. - for (return_address, return_amount) in context.storage_deposit_returns.iter() { - if let Some(deposit_amount) = context.simple_deposits.get(return_address) { - if deposit_amount < return_amount { - return Ok(Some(TransactionFailureReason::StorageDepositReturnUnfulfilled)); - } - } else { - return Ok(Some(TransactionFailureReason::StorageDepositReturnUnfulfilled)); + if self.input_mana > self.output_mana && !self.transaction.has_capability(TransactionCapabilityFlag::BurnMana) { + // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 + return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); } - } - // Validation of amounts. - if context.input_amount != context.output_amount { - return Ok(Some(TransactionFailureReason::SumInputsOutputsAmountMismatch)); - } - - if context.input_mana > context.output_mana - && !context.transaction.has_capability(TransactionCapabilityFlag::BurnMana) - { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); - } + // Validation of input native tokens. + let mut native_token_ids = self.input_native_tokens.keys().collect::>(); - let mut native_token_ids = HashSet::new(); + // Validation of output native tokens. + for (token_id, output_amount) in self.output_native_tokens.iter() { + let input_amount = self.input_native_tokens.get(token_id).copied().unwrap_or_default(); - // Validation of input native tokens. - for (token_id, _input_amount) in context.input_native_tokens.iter() { - native_token_ids.insert(token_id); - } + if output_amount > &input_amount + && !self + .output_chains + .contains_key(&ChainId::from(FoundryId::from(*token_id))) + { + return Ok(Some(TransactionFailureReason::InvalidNativeTokens)); + } - // Validation of output native tokens. - for (token_id, output_amount) in context.output_native_tokens.iter() { - let input_amount = context.input_native_tokens.get(token_id).copied().unwrap_or_default(); + native_token_ids.insert(token_id); + } - if output_amount > &input_amount - && !context - .output_chains - .contains_key(&ChainId::from(FoundryId::from(*token_id))) - { + if native_token_ids.len() > NativeTokens::COUNT_MAX as usize { return Ok(Some(TransactionFailureReason::InvalidNativeTokens)); } - native_token_ids.insert(token_id); - } - - if native_token_ids.len() > NativeTokens::COUNT_MAX as usize { - return Ok(Some(TransactionFailureReason::InvalidNativeTokens)); - } - - // Validation of state transitions and destructions. - for (chain_id, current_state) in context.input_chains.iter() { - if Output::verify_state_transition( - Some(current_state), - context.output_chains.get(chain_id).map(core::ops::Deref::deref), - &context, - ) - .is_err() - { - return Ok(Some(TransactionFailureReason::InvalidChainStateTransition)); + // Validation of state transitions and destructions. + for (chain_id, current_state) in self.input_chains.iter() { + if Output::verify_state_transition( + Some(current_state), + self.output_chains.get(chain_id).map(core::ops::Deref::deref), + &self, + ) + .is_err() + { + return Ok(Some(TransactionFailureReason::InvalidChainStateTransition)); + } } - } - // Validation of state creations. - for (chain_id, next_state) in context.output_chains.iter() { - if context.input_chains.get(chain_id).is_none() - && Output::verify_state_transition(None, Some(next_state), &context).is_err() - { - return Ok(Some(TransactionFailureReason::InvalidChainStateTransition)); + // Validation of state creations. + for (chain_id, next_state) in self.output_chains.iter() { + if self.input_chains.get(chain_id).is_none() + && Output::verify_state_transition(None, Some(next_state), &self).is_err() + { + return Ok(Some(TransactionFailureReason::InvalidChainStateTransition)); + } } - } - Ok(None) + Ok(None) + } }