From fe64b50968546310d850d2a5b5110e24ec60aa8c Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 21 Dec 2023 14:12:33 +0100 Subject: [PATCH 1/9] Move address caps verification to syntactic --- .../types/block/output/unlock_condition/mod.rs | 18 +++++++++++++++++- sdk/src/types/block/semantic/mod.rs | 14 +++----------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/sdk/src/types/block/output/unlock_condition/mod.rs b/sdk/src/types/block/output/unlock_condition/mod.rs index 6b2bc79ed5..e7e29c974e 100644 --- a/sdk/src/types/block/output/unlock_condition/mod.rs +++ b/sdk/src/types/block/output/unlock_condition/mod.rs @@ -24,7 +24,7 @@ pub use self::{ storage_deposit_return::StorageDepositReturnUnlockCondition, timelock::TimelockUnlockCondition, }; use crate::types::block::{ - address::Address, + address::{Address, RestrictedAddress}, output::{StorageScore, StorageScoreParameters}, protocol::{CommittableAgeRange, ProtocolParameters, WorkScore}, slot::SlotIndex, @@ -314,6 +314,22 @@ impl UnlockConditions { Ok(address) } + + /// Returns an iterator over all addresses. + #[inline(always)] + pub fn addresses(&self) -> impl Iterator { + self.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, + }) + } + + pub fn restricted_addresses(&self) -> impl Iterator { + self.addresses().filter_map(Address::as_restricted_opt) + } } impl StorageScore for UnlockConditions { diff --git a/sdk/src/types/block/semantic/mod.rs b/sdk/src/types/block/semantic/mod.rs index b7573384e2..8a3cbed64b 100644 --- a/sdk/src/types/block/semantic/mod.rs +++ b/sdk/src/types/block/semantic/mod.rs @@ -16,7 +16,7 @@ pub use self::{ }; use crate::types::block::{ address::{Address, AddressCapabilityFlag}, - output::{AccountId, AnchorOutput, ChainId, FoundryId, NativeTokens, Output, OutputId, TokenId, UnlockCondition}, + output::{AccountId, AnchorOutput, ChainId, FoundryId, NativeTokens, Output, OutputId, TokenId}, payload::signed_transaction::{Transaction, TransactionCapabilityFlag, TransactionSigningHash}, protocol::ProtocolParameters, unlock::Unlock, @@ -212,16 +212,8 @@ impl<'a> SemanticValidationContext<'a> { 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); + let addresses = unlock_conditions.restricted_addresses(); + for address in addresses { if created_native_token.is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithNativeTokens) From bfae6ff471acbd53ba8c1dd07807476e145fb87b Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Tue, 9 Jan 2024 18:01:16 +0100 Subject: [PATCH 2/9] verify_restricted_addresses --- sdk/src/types/block/error.rs | 2 + .../block/output/unlock_condition/mod.rs | 53 +++++++++++++++++-- sdk/src/types/block/semantic/mod.rs | 53 +------------------ 3 files changed, 53 insertions(+), 55 deletions(-) diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index ee8d5b44c8..a9e6d41f2c 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -201,6 +201,7 @@ pub enum Error { target: EpochIndex, }, TrailingCapabilityBytes, + RestrictedAddressCapability, } #[cfg(feature = "std")] @@ -432,6 +433,7 @@ impl fmt::Display for Error { write!(f, "invalid epoch delta: created {created}, target {target}") } Self::TrailingCapabilityBytes => write!(f, "capability bytes have trailing zeroes"), + Self::RestrictedAddressCapability => write!(f, "restricted address capability"), } } } diff --git a/sdk/src/types/block/output/unlock_condition/mod.rs b/sdk/src/types/block/output/unlock_condition/mod.rs index e7e29c974e..57ac082560 100644 --- a/sdk/src/types/block/output/unlock_condition/mod.rs +++ b/sdk/src/types/block/output/unlock_condition/mod.rs @@ -24,8 +24,11 @@ pub use self::{ storage_deposit_return::StorageDepositReturnUnlockCondition, timelock::TimelockUnlockCondition, }; use crate::types::block::{ - address::{Address, RestrictedAddress}, - output::{StorageScore, StorageScoreParameters}, + address::{Address, AddressCapabilityFlag, RestrictedAddress}, + output::{ + feature::NativeTokenFeature, AccountOutput, AnchorOutput, DelegationOutput, NftOutput, StorageScore, + StorageScoreParameters, + }, protocol::{CommittableAgeRange, ProtocolParameters, WorkScore}, slot::SlotIndex, Error, @@ -316,7 +319,6 @@ impl UnlockConditions { } /// Returns an iterator over all addresses. - #[inline(always)] pub fn addresses(&self) -> impl Iterator { self.iter().filter_map(|uc| match uc { UnlockCondition::Address(uc) => Some(uc.address()), @@ -327,9 +329,54 @@ impl UnlockConditions { }) } + /// Returns an iterator over all restricted addresses. pub fn restricted_addresses(&self) -> impl Iterator { self.addresses().filter_map(Address::as_restricted_opt) } + + pub(crate) fn verify_restricted_addresses( + &self, + output_kind: u8, + native_token: Option<&NativeTokenFeature>, + mana: u64, + ) -> Result<(), Error> { + let addresses = self.restricted_addresses(); + + for address in addresses { + if native_token.is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithNativeTokens) { + return Err(Error::RestrictedAddressCapability); + } + + if mana > 0 && !address.has_capability(AddressCapabilityFlag::OutputsWithMana) { + return Err(Error::RestrictedAddressCapability); + } + + if self.timelock().is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithTimelock) { + return Err(Error::RestrictedAddressCapability); + } + + if self.expiration().is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithExpiration) { + return Err(Error::RestrictedAddressCapability); + } + + if self.storage_deposit_return().is_some() + && !address.has_capability(AddressCapabilityFlag::OutputsWithStorageDepositReturn) + { + return Err(Error::RestrictedAddressCapability); + } + + if match output_kind { + AccountOutput::KIND => !address.has_capability(AddressCapabilityFlag::AccountOutputs), + AnchorOutput::KIND => !address.has_capability(AddressCapabilityFlag::AnchorOutputs), + NftOutput::KIND => !address.has_capability(AddressCapabilityFlag::NftOutputs), + DelegationOutput::KIND => !address.has_capability(AddressCapabilityFlag::DelegationOutputs), + _ => return Err(Error::UnsupportedOutputKind(output_kind)), + } { + return Err(Error::RestrictedAddressCapability); + } + } + Ok(()) + } } impl StorageScore for UnlockConditions { diff --git a/sdk/src/types/block/semantic/mod.rs b/sdk/src/types/block/semantic/mod.rs index 8a3cbed64b..f92e5bab2e 100644 --- a/sdk/src/types/block/semantic/mod.rs +++ b/sdk/src/types/block/semantic/mod.rs @@ -15,7 +15,7 @@ pub use self::{ state_transition::{StateTransitionError, StateTransitionVerifier}, }; use crate::types::block::{ - address::{Address, AddressCapabilityFlag}, + address::Address, output::{AccountId, AnchorOutput, ChainId, FoundryId, NativeTokens, Output, OutputId, TokenId}, payload::signed_transaction::{Transaction, TransactionCapabilityFlag, TransactionSigningHash}, protocol::ProtocolParameters, @@ -210,57 +210,6 @@ impl<'a> SemanticValidationContext<'a> { 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.restricted_addresses(); - - for address in addresses { - if created_native_token.is_some() - && !address.has_capability(AddressCapabilityFlag::OutputsWithNativeTokens) - { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); - } - - if 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::Anchor(_) => !address.has_capability(AddressCapabilityFlag::AnchorOutputs), - 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 let Some(sender) = features.and_then(|f| f.sender()) { if !self.unlocked_addresses.contains(sender.address()) { return Ok(Some(TransactionFailureReason::SenderNotUnlocked)); From 964642ddbc3464b5046d82141a95f154c8a48144 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Wed, 10 Jan 2024 09:33:47 +0100 Subject: [PATCH 3/9] Use verify_restricted_addresses in builders --- sdk/src/types/block/output/account.rs | 11 ++- sdk/src/types/block/output/anchor.rs | 11 ++- sdk/src/types/block/output/basic.rs | 10 +- sdk/src/types/block/output/delegation.rs | 6 +- sdk/src/types/block/output/nft.rs | 5 +- .../block/output/unlock_condition/mod.rs | 91 ++++++++++--------- 6 files changed, 83 insertions(+), 51 deletions(-) diff --git a/sdk/src/types/block/output/account.rs b/sdk/src/types/block/output/account.rs index 04f601d233..3b2303cc93 100644 --- a/sdk/src/types/block/output/account.rs +++ b/sdk/src/types/block/output/account.rs @@ -15,7 +15,10 @@ use crate::types::block::{ address::{AccountAddress, Address}, output::{ feature::{verify_allowed_features, Feature, FeatureFlags, Features}, - unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions}, + unlock_condition::{ + verify_allowed_unlock_conditions, verify_restricted_addresses, UnlockCondition, UnlockConditionFlags, + UnlockConditions, + }, ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters, }, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, @@ -217,6 +220,12 @@ impl AccountOutputBuilder { let features = Features::from_set(self.features)?; + verify_restricted_addresses( + &unlock_conditions, + AccountOutput::KIND, + features.native_token(), + self.mana, + )?; verify_allowed_features(&features, AccountOutput::ALLOWED_FEATURES)?; let immutable_features = Features::from_set(self.immutable_features)?; diff --git a/sdk/src/types/block/output/anchor.rs b/sdk/src/types/block/output/anchor.rs index 6f5c926934..cce2b3aacd 100644 --- a/sdk/src/types/block/output/anchor.rs +++ b/sdk/src/types/block/output/anchor.rs @@ -15,7 +15,10 @@ use crate::types::block::{ address::{Address, AnchorAddress}, output::{ feature::{verify_allowed_features, Feature, FeatureFlags, Features}, - unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions}, + unlock_condition::{ + verify_allowed_unlock_conditions, verify_restricted_addresses, UnlockCondition, UnlockConditionFlags, + UnlockConditions, + }, ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters, }, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, @@ -248,6 +251,12 @@ impl AnchorOutputBuilder { let features = Features::from_set(self.features)?; + verify_restricted_addresses( + &unlock_conditions, + AnchorOutput::KIND, + features.native_token(), + self.mana, + )?; verify_allowed_features(&features, AnchorOutput::ALLOWED_FEATURES)?; let immutable_features = Features::from_set(self.immutable_features)?; diff --git a/sdk/src/types/block/output/basic.rs b/sdk/src/types/block/output/basic.rs index 9b31c18782..5adb40a475 100644 --- a/sdk/src/types/block/output/basic.rs +++ b/sdk/src/types/block/output/basic.rs @@ -10,8 +10,8 @@ use crate::types::block::{ output::{ feature::{verify_allowed_features, Feature, FeatureFlags, Features, NativeTokenFeature}, unlock_condition::{ - verify_allowed_unlock_conditions, AddressUnlockCondition, StorageDepositReturnUnlockCondition, - UnlockCondition, UnlockConditionFlags, UnlockConditions, + verify_allowed_unlock_conditions, verify_restricted_addresses, AddressUnlockCondition, + StorageDepositReturnUnlockCondition, UnlockCondition, UnlockConditionFlags, UnlockConditions, }, MinimumOutputAmount, NativeToken, Output, OutputBuilderAmount, StorageScore, StorageScoreParameters, }, @@ -192,6 +192,12 @@ impl BasicOutputBuilder { let features = Features::from_set(self.features)?; + verify_restricted_addresses( + &unlock_conditions, + BasicOutput::KIND, + features.native_token(), + self.mana, + )?; verify_features::(&features)?; let mut output = BasicOutput { diff --git a/sdk/src/types/block/output/delegation.rs b/sdk/src/types/block/output/delegation.rs index e3d8b9ffd9..2fdbaa964b 100644 --- a/sdk/src/types/block/output/delegation.rs +++ b/sdk/src/types/block/output/delegation.rs @@ -9,7 +9,10 @@ use crate::types::block::{ address::{AccountAddress, Address}, output::{ chain_id::ChainId, - unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions}, + unlock_condition::{ + verify_allowed_unlock_conditions, verify_restricted_addresses, UnlockCondition, UnlockConditionFlags, + UnlockConditions, + }, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters, }, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, @@ -172,6 +175,7 @@ impl DelegationOutputBuilder { let unlock_conditions = UnlockConditions::from_set(self.unlock_conditions)?; verify_unlock_conditions::(&unlock_conditions)?; + verify_restricted_addresses(&unlock_conditions, DelegationOutput::KIND, None, 0)?; let mut output = DelegationOutput { amount: 0, diff --git a/sdk/src/types/block/output/nft.rs b/sdk/src/types/block/output/nft.rs index 9636399d5c..32267890f3 100644 --- a/sdk/src/types/block/output/nft.rs +++ b/sdk/src/types/block/output/nft.rs @@ -15,8 +15,8 @@ use crate::types::block::{ output::{ feature::{verify_allowed_features, Feature, FeatureFlags, Features}, unlock_condition::{ - verify_allowed_unlock_conditions, AddressUnlockCondition, StorageDepositReturnUnlockCondition, - UnlockCondition, UnlockConditionFlags, UnlockConditions, + verify_allowed_unlock_conditions, verify_restricted_addresses, AddressUnlockCondition, + StorageDepositReturnUnlockCondition, UnlockCondition, UnlockConditionFlags, UnlockConditions, }, BasicOutputBuilder, ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters, @@ -256,6 +256,7 @@ impl NftOutputBuilder { let features = Features::from_set(self.features)?; + verify_restricted_addresses(&unlock_conditions, NftOutput::KIND, features.native_token(), self.mana)?; verify_allowed_features(&features, NftOutput::ALLOWED_FEATURES)?; let immutable_features = Features::from_set(self.immutable_features)?; diff --git a/sdk/src/types/block/output/unlock_condition/mod.rs b/sdk/src/types/block/output/unlock_condition/mod.rs index 57ac082560..771ce84236 100644 --- a/sdk/src/types/block/output/unlock_condition/mod.rs +++ b/sdk/src/types/block/output/unlock_condition/mod.rs @@ -333,50 +333,6 @@ impl UnlockConditions { pub fn restricted_addresses(&self) -> impl Iterator { self.addresses().filter_map(Address::as_restricted_opt) } - - pub(crate) fn verify_restricted_addresses( - &self, - output_kind: u8, - native_token: Option<&NativeTokenFeature>, - mana: u64, - ) -> Result<(), Error> { - let addresses = self.restricted_addresses(); - - for address in addresses { - if native_token.is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithNativeTokens) { - return Err(Error::RestrictedAddressCapability); - } - - if mana > 0 && !address.has_capability(AddressCapabilityFlag::OutputsWithMana) { - return Err(Error::RestrictedAddressCapability); - } - - if self.timelock().is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithTimelock) { - return Err(Error::RestrictedAddressCapability); - } - - if self.expiration().is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithExpiration) { - return Err(Error::RestrictedAddressCapability); - } - - if self.storage_deposit_return().is_some() - && !address.has_capability(AddressCapabilityFlag::OutputsWithStorageDepositReturn) - { - return Err(Error::RestrictedAddressCapability); - } - - if match output_kind { - AccountOutput::KIND => !address.has_capability(AddressCapabilityFlag::AccountOutputs), - AnchorOutput::KIND => !address.has_capability(AddressCapabilityFlag::AnchorOutputs), - NftOutput::KIND => !address.has_capability(AddressCapabilityFlag::NftOutputs), - DelegationOutput::KIND => !address.has_capability(AddressCapabilityFlag::DelegationOutputs), - _ => return Err(Error::UnsupportedOutputKind(output_kind)), - } { - return Err(Error::RestrictedAddressCapability); - } - } - Ok(()) - } } impl StorageScore for UnlockConditions { @@ -418,6 +374,53 @@ pub(crate) fn verify_allowed_unlock_conditions( Ok(()) } +pub(crate) fn verify_restricted_addresses( + unlock_conditions: &UnlockConditions, + output_kind: u8, + native_token: Option<&NativeTokenFeature>, + mana: u64, +) -> Result<(), Error> { + let addresses = unlock_conditions.restricted_addresses(); + + for address in addresses { + if native_token.is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithNativeTokens) { + return Err(Error::RestrictedAddressCapability); + } + + if mana > 0 && !address.has_capability(AddressCapabilityFlag::OutputsWithMana) { + return Err(Error::RestrictedAddressCapability); + } + + if unlock_conditions.timelock().is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithTimelock) + { + return Err(Error::RestrictedAddressCapability); + } + + if unlock_conditions.expiration().is_some() + && !address.has_capability(AddressCapabilityFlag::OutputsWithExpiration) + { + return Err(Error::RestrictedAddressCapability); + } + + if unlock_conditions.storage_deposit_return().is_some() + && !address.has_capability(AddressCapabilityFlag::OutputsWithStorageDepositReturn) + { + return Err(Error::RestrictedAddressCapability); + } + + if match output_kind { + AccountOutput::KIND => !address.has_capability(AddressCapabilityFlag::AccountOutputs), + AnchorOutput::KIND => !address.has_capability(AddressCapabilityFlag::AnchorOutputs), + NftOutput::KIND => !address.has_capability(AddressCapabilityFlag::NftOutputs), + DelegationOutput::KIND => !address.has_capability(AddressCapabilityFlag::DelegationOutputs), + _ => return Err(Error::UnsupportedOutputKind(output_kind)), + } { + return Err(Error::RestrictedAddressCapability); + } + } + Ok(()) +} + #[cfg(test)] mod test { use pretty_assertions::assert_eq; From db4dddfb4473edad4d085246403c97b4bec98cd6 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Wed, 10 Jan 2024 09:41:30 +0100 Subject: [PATCH 4/9] Use it with Packable --- sdk/src/types/block/output/account.rs | 9 +++------ sdk/src/types/block/output/anchor.rs | 13 +++---------- sdk/src/types/block/output/basic.rs | 10 ++++++++++ sdk/src/types/block/output/delegation.rs | 8 ++++++++ sdk/src/types/block/output/nft.rs | 2 ++ 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/sdk/src/types/block/output/account.rs b/sdk/src/types/block/output/account.rs index 3b2303cc93..1c887190a5 100644 --- a/sdk/src/types/block/output/account.rs +++ b/sdk/src/types/block/output/account.rs @@ -502,6 +502,8 @@ impl Packable for AccountOutput { let features = Features::unpack::<_, VERIFY>(unpacker, &())?; if VERIFY { + verify_restricted_addresses(&unlock_conditions, AccountOutput::KIND, features.native_token(), mana) + .map_err(UnpackError::Packable)?; verify_allowed_features(&features, Self::ALLOWED_FEATURES).map_err(UnpackError::Packable)?; } @@ -620,12 +622,7 @@ mod tests { use super::*; use crate::types::block::{ - output::account::dto::AccountOutputDto, - protocol::protocol_parameters, - rand::output::{ - feature::rand_allowed_features, rand_account_id, rand_account_output, - unlock_condition::rand_address_unlock_condition_different_from_account_id, - }, + output::account::dto::AccountOutputDto, protocol::protocol_parameters, rand::output::rand_account_output, }; #[test] diff --git a/sdk/src/types/block/output/anchor.rs b/sdk/src/types/block/output/anchor.rs index cce2b3aacd..3f1f00f66e 100644 --- a/sdk/src/types/block/output/anchor.rs +++ b/sdk/src/types/block/output/anchor.rs @@ -558,6 +558,8 @@ impl Packable for AnchorOutput { let features = Features::unpack::<_, VERIFY>(unpacker, &())?; if VERIFY { + verify_restricted_addresses(&unlock_conditions, AnchorOutput::KIND, features.native_token(), mana) + .map_err(UnpackError::Packable)?; verify_allowed_features(&features, Self::ALLOWED_FEATURES).map_err(UnpackError::Packable)?; } @@ -684,16 +686,7 @@ mod dto { mod tests { use super::*; use crate::types::block::{ - output::anchor::dto::AnchorOutputDto, - protocol::protocol_parameters, - rand::output::{ - feature::rand_allowed_features, - rand_anchor_id, rand_anchor_output, - unlock_condition::{ - rand_governor_address_unlock_condition_different_from, - rand_state_controller_address_unlock_condition_different_from, - }, - }, + output::anchor::dto::AnchorOutputDto, protocol::protocol_parameters, rand::output::rand_anchor_output, }; #[test] diff --git a/sdk/src/types/block/output/basic.rs b/sdk/src/types/block/output/basic.rs index 5adb40a475..41f81ac911 100644 --- a/sdk/src/types/block/output/basic.rs +++ b/sdk/src/types/block/output/basic.rs @@ -236,6 +236,7 @@ impl From<&BasicOutput> for BasicOutputBuilder { #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)] #[packable(unpack_error = Error)] #[packable(unpack_visitor = ProtocolParameters)] +#[packable(verify_with = verify_basic_output)] pub struct BasicOutput { /// Amount of IOTA coins held by the output. amount: u64, @@ -398,6 +399,15 @@ fn verify_features_packable(features: &Features, _: &Protoco verify_features::(features) } +fn verify_basic_output(output: &BasicOutput, _: &ProtocolParameters) -> Result<(), Error> { + verify_restricted_addresses( + output.unlock_conditions(), + BasicOutput::KIND, + output.features.native_token(), + output.mana, + ) +} + #[cfg(feature = "serde")] mod dto { use alloc::vec::Vec; diff --git a/sdk/src/types/block/output/delegation.rs b/sdk/src/types/block/output/delegation.rs index 2fdbaa964b..d9e880019c 100644 --- a/sdk/src/types/block/output/delegation.rs +++ b/sdk/src/types/block/output/delegation.rs @@ -219,6 +219,7 @@ impl From<&DelegationOutput> for DelegationOutputBuilder { #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)] #[packable(unpack_error = Error)] #[packable(unpack_visitor = ProtocolParameters)] +#[packable(verify_with = verify_delegation_output)] pub struct DelegationOutput { /// Amount of IOTA coins held by the output. amount: u64, @@ -396,6 +397,13 @@ fn verify_unlock_conditions_packable( verify_unlock_conditions::(unlock_conditions) } +fn verify_delegation_output( + output: &DelegationOutput, + _: &ProtocolParameters, +) -> Result<(), Error> { + verify_restricted_addresses(output.unlock_conditions(), DelegationOutput::KIND, None, 0) +} + #[cfg(feature = "serde")] mod dto { use alloc::vec::Vec; diff --git a/sdk/src/types/block/output/nft.rs b/sdk/src/types/block/output/nft.rs index 32267890f3..54759a995b 100644 --- a/sdk/src/types/block/output/nft.rs +++ b/sdk/src/types/block/output/nft.rs @@ -470,6 +470,8 @@ impl Packable for NftOutput { let features = Features::unpack::<_, VERIFY>(unpacker, &())?; if VERIFY { + verify_restricted_addresses(&unlock_conditions, NftOutput::KIND, features.native_token(), mana) + .map_err(UnpackError::Packable)?; verify_allowed_features(&features, Self::ALLOWED_FEATURES).map_err(UnpackError::Packable)?; } From 6ea04f6fa77d5d2d2a47d292a97168a82b341c1e Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Wed, 10 Jan 2024 09:46:22 +0100 Subject: [PATCH 5/9] Fix match --- sdk/src/types/block/output/unlock_condition/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/types/block/output/unlock_condition/mod.rs b/sdk/src/types/block/output/unlock_condition/mod.rs index 771ce84236..e3998601ba 100644 --- a/sdk/src/types/block/output/unlock_condition/mod.rs +++ b/sdk/src/types/block/output/unlock_condition/mod.rs @@ -413,7 +413,7 @@ pub(crate) fn verify_restricted_addresses( AnchorOutput::KIND => !address.has_capability(AddressCapabilityFlag::AnchorOutputs), NftOutput::KIND => !address.has_capability(AddressCapabilityFlag::NftOutputs), DelegationOutput::KIND => !address.has_capability(AddressCapabilityFlag::DelegationOutputs), - _ => return Err(Error::UnsupportedOutputKind(output_kind)), + _ => false, } { return Err(Error::RestrictedAddressCapability); } From 10c99272f790e55fa21d1f5abd62a146b31719ed Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Wed, 10 Jan 2024 09:55:27 +0100 Subject: [PATCH 6/9] Update sdk/src/types/block/output/account.rs Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> --- sdk/src/types/block/output/account.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/types/block/output/account.rs b/sdk/src/types/block/output/account.rs index 1c887190a5..e2792c4362 100644 --- a/sdk/src/types/block/output/account.rs +++ b/sdk/src/types/block/output/account.rs @@ -502,7 +502,7 @@ impl Packable for AccountOutput { let features = Features::unpack::<_, VERIFY>(unpacker, &())?; if VERIFY { - verify_restricted_addresses(&unlock_conditions, AccountOutput::KIND, features.native_token(), mana) + verify_restricted_addresses(&unlock_conditions, Self::KIND, features.native_token(), mana) .map_err(UnpackError::Packable)?; verify_allowed_features(&features, Self::ALLOWED_FEATURES).map_err(UnpackError::Packable)?; } From 049b2f523c01492fde5fcfa27aa745fcc1fb70fd Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Wed, 10 Jan 2024 09:57:50 +0100 Subject: [PATCH 7/9] Self:: --- sdk/src/types/block/output/anchor.rs | 2 +- sdk/src/types/block/output/nft.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/types/block/output/anchor.rs b/sdk/src/types/block/output/anchor.rs index 3f1f00f66e..958b8eecda 100644 --- a/sdk/src/types/block/output/anchor.rs +++ b/sdk/src/types/block/output/anchor.rs @@ -558,7 +558,7 @@ impl Packable for AnchorOutput { let features = Features::unpack::<_, VERIFY>(unpacker, &())?; if VERIFY { - verify_restricted_addresses(&unlock_conditions, AnchorOutput::KIND, features.native_token(), mana) + verify_restricted_addresses(&unlock_conditions, Self::KIND, features.native_token(), mana) .map_err(UnpackError::Packable)?; verify_allowed_features(&features, Self::ALLOWED_FEATURES).map_err(UnpackError::Packable)?; } diff --git a/sdk/src/types/block/output/nft.rs b/sdk/src/types/block/output/nft.rs index 54759a995b..d641ee1f46 100644 --- a/sdk/src/types/block/output/nft.rs +++ b/sdk/src/types/block/output/nft.rs @@ -470,7 +470,7 @@ impl Packable for NftOutput { let features = Features::unpack::<_, VERIFY>(unpacker, &())?; if VERIFY { - verify_restricted_addresses(&unlock_conditions, NftOutput::KIND, features.native_token(), mana) + verify_restricted_addresses(&unlock_conditions, Self::KIND, features.native_token(), mana) .map_err(UnpackError::Packable)?; verify_allowed_features(&features, Self::ALLOWED_FEATURES).map_err(UnpackError::Packable)?; } From 448d0e7c5d956478abbadabb013260c23c50eb77 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Wed, 10 Jan 2024 10:09:30 +0100 Subject: [PATCH 8/9] Better error --- sdk/src/types/block/error.rs | 6 +-- .../block/output/unlock_condition/mod.rs | 46 +++++++++++++------ 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index a9e6d41f2c..6e293465ad 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -11,7 +11,7 @@ use primitive_types::U256; use super::slot::EpochIndex; use crate::types::block::{ - address::WeightedAddressCount, + address::{AddressCapabilityFlag, WeightedAddressCount}, context_input::RewardContextInputIndex, input::UtxoInput, mana::ManaAllotmentCount, @@ -201,7 +201,7 @@ pub enum Error { target: EpochIndex, }, TrailingCapabilityBytes, - RestrictedAddressCapability, + RestrictedAddressCapability(AddressCapabilityFlag), } #[cfg(feature = "std")] @@ -433,7 +433,7 @@ impl fmt::Display for Error { write!(f, "invalid epoch delta: created {created}, target {target}") } Self::TrailingCapabilityBytes => write!(f, "capability bytes have trailing zeroes"), - Self::RestrictedAddressCapability => write!(f, "restricted address capability"), + Self::RestrictedAddressCapability(cap) => write!(f, "restricted address capability: {cap:?}"), } } } diff --git a/sdk/src/types/block/output/unlock_condition/mod.rs b/sdk/src/types/block/output/unlock_condition/mod.rs index e3998601ba..e106ba8ca5 100644 --- a/sdk/src/types/block/output/unlock_condition/mod.rs +++ b/sdk/src/types/block/output/unlock_condition/mod.rs @@ -384,38 +384,58 @@ pub(crate) fn verify_restricted_addresses( for address in addresses { if native_token.is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithNativeTokens) { - return Err(Error::RestrictedAddressCapability); + return Err(Error::RestrictedAddressCapability( + AddressCapabilityFlag::OutputsWithNativeTokens, + )); } if mana > 0 && !address.has_capability(AddressCapabilityFlag::OutputsWithMana) { - return Err(Error::RestrictedAddressCapability); + return Err(Error::RestrictedAddressCapability( + AddressCapabilityFlag::OutputsWithMana, + )); } if unlock_conditions.timelock().is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithTimelock) { - return Err(Error::RestrictedAddressCapability); + return Err(Error::RestrictedAddressCapability( + AddressCapabilityFlag::OutputsWithTimelock, + )); } if unlock_conditions.expiration().is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithExpiration) { - return Err(Error::RestrictedAddressCapability); + return Err(Error::RestrictedAddressCapability( + AddressCapabilityFlag::OutputsWithExpiration, + )); } if unlock_conditions.storage_deposit_return().is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithStorageDepositReturn) { - return Err(Error::RestrictedAddressCapability); + return Err(Error::RestrictedAddressCapability( + AddressCapabilityFlag::OutputsWithStorageDepositReturn, + )); } - if match output_kind { - AccountOutput::KIND => !address.has_capability(AddressCapabilityFlag::AccountOutputs), - AnchorOutput::KIND => !address.has_capability(AddressCapabilityFlag::AnchorOutputs), - NftOutput::KIND => !address.has_capability(AddressCapabilityFlag::NftOutputs), - DelegationOutput::KIND => !address.has_capability(AddressCapabilityFlag::DelegationOutputs), - _ => false, - } { - return Err(Error::RestrictedAddressCapability); + match output_kind { + AccountOutput::KIND if !address.has_capability(AddressCapabilityFlag::AccountOutputs) => { + return Err(Error::RestrictedAddressCapability( + AddressCapabilityFlag::AccountOutputs, + )); + } + AnchorOutput::KIND if !address.has_capability(AddressCapabilityFlag::AnchorOutputs) => { + return Err(Error::RestrictedAddressCapability(AddressCapabilityFlag::AnchorOutputs)); + } + NftOutput::KIND if !address.has_capability(AddressCapabilityFlag::NftOutputs) => { + return Err(Error::RestrictedAddressCapability(AddressCapabilityFlag::NftOutputs)); + } + DelegationOutput::KIND if !address.has_capability(AddressCapabilityFlag::DelegationOutputs) => { + return Err(Error::RestrictedAddressCapability( + AddressCapabilityFlag::DelegationOutputs, + )); + } + _ => {} } } Ok(()) From cefad2a58fbdeb2f8f011d07b84494e6e49350fd Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Wed, 10 Jan 2024 10:49:35 +0100 Subject: [PATCH 9/9] Update sdk/src/types/block/output/unlock_condition/mod.rs Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> --- sdk/src/types/block/output/unlock_condition/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/types/block/output/unlock_condition/mod.rs b/sdk/src/types/block/output/unlock_condition/mod.rs index e106ba8ca5..b3506172c1 100644 --- a/sdk/src/types/block/output/unlock_condition/mod.rs +++ b/sdk/src/types/block/output/unlock_condition/mod.rs @@ -318,7 +318,7 @@ impl UnlockConditions { Ok(address) } - /// Returns an iterator over all addresses. + /// Returns an iterator over all addresses except StorageDepositReturn address. pub fn addresses(&self) -> impl Iterator { self.iter().filter_map(|uc| match uc { UnlockCondition::Address(uc) => Some(uc.address()),