diff --git a/Cargo.lock b/Cargo.lock index c099d23da8..9f773525b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1615,6 +1615,7 @@ dependencies = [ "num_cpus", "once_cell", "packable", + "paste", "prefix-hex", "primitive-types", "rand", diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 7261ec6f52..3b4d9aa64f 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -47,6 +47,7 @@ iterator-sorted = { version = "0.1.0", default-features = false } packable = { version = "0.8.3", default-features = false, features = [ "primitive-types", ] } +paste = { version = "1.0.14", default-features = false } prefix-hex = { version = "0.7.1", default-features = false, features = [ "primitive-types", ] } diff --git a/sdk/src/types/block/address/mod.rs b/sdk/src/types/block/address/mod.rs index 91a2f357de..0c10dda516 100644 --- a/sdk/src/types/block/address/mod.rs +++ b/sdk/src/types/block/address/mod.rs @@ -83,50 +83,7 @@ impl Address { } } - /// Checks whether the address is an [`Ed25519Address`]. - pub fn is_ed25519(&self) -> bool { - matches!(self, Self::Ed25519(_)) - } - - /// Gets the address as an actual [`Ed25519Address`]. - /// PANIC: do not call on a non-ed25519 address. - pub fn as_ed25519(&self) -> &Ed25519Address { - if let Self::Ed25519(address) = self { - address - } else { - panic!("as_ed25519 called on a non-ed25519 address"); - } - } - - /// Checks whether the address is an [`AccountAddress`]. - pub fn is_account(&self) -> bool { - matches!(self, Self::Account(_)) - } - - /// Gets the address as an actual [`AccountAddress`]. - /// PANIC: do not call on a non-account address. - pub fn as_account(&self) -> &AccountAddress { - if let Self::Account(address) = self { - address - } else { - panic!("as_account called on a non-account address"); - } - } - - /// Checks whether the address is an [`NftAddress`]. - pub fn is_nft(&self) -> bool { - matches!(self, Self::Nft(_)) - } - - /// Gets the address as an actual [`NftAddress`]. - /// PANIC: do not call on a non-nft address. - pub fn as_nft(&self) -> &NftAddress { - if let Self::Nft(address) = self { - address - } else { - panic!("as_nft called on a non-nft address"); - } - } + def_is_as_opt!(Address: Ed25519, Account, Nft, ImplicitAccountCreation, Restricted); /// Tries to create an [`Address`] from a bech32 encoded string. pub fn try_from_bech32(address: impl AsRef) -> Result { diff --git a/sdk/src/types/block/address/restricted.rs b/sdk/src/types/block/address/restricted.rs index e23523a17b..2b20223d7c 100644 --- a/sdk/src/types/block/address/restricted.rs +++ b/sdk/src/types/block/address/restricted.rs @@ -47,6 +47,11 @@ impl RestrictedAddress { self.allowed_capabilities = allowed_capabilities.into(); self } + + /// Returns whether a given [`AddressCapabilityFlag`] is enabled. + pub fn has_capability(&self, flag: AddressCapabilityFlag) -> bool { + self.allowed_capabilities.has_capability(flag) + } } impl TryFrom
for RestrictedAddress { diff --git a/sdk/src/types/block/context_input/mod.rs b/sdk/src/types/block/context_input/mod.rs index 09bfeaab55..c1b1855b58 100644 --- a/sdk/src/types/block/context_input/mod.rs +++ b/sdk/src/types/block/context_input/mod.rs @@ -59,50 +59,7 @@ impl ContextInput { } } - /// Checks whether the context input is a [`CommitmentContextInput`]. - pub fn is_commitment(&self) -> bool { - matches!(self, Self::Commitment(_)) - } - - /// Gets the input as an actual [`CommitmentContextInput`]. - /// PANIC: do not call on a non-commitment context input. - pub fn as_commitment(&self) -> &CommitmentContextInput { - if let Self::Commitment(input) = self { - input - } else { - panic!("invalid downcast of non-CommitmentContextInput"); - } - } - - /// Checks whether the context input is a [`BlockIssuanceCreditContextInput`]. - pub fn is_block_issuance_credit(&self) -> bool { - matches!(self, Self::BlockIssuanceCredit(_)) - } - - /// Gets the input as an actual [`BlockIssuanceCreditContextInput`]. - /// PANIC: do not call on a non-block-issuance-credit context input. - pub fn as_block_issuance_credit(&self) -> &BlockIssuanceCreditContextInput { - if let Self::BlockIssuanceCredit(input) = self { - input - } else { - panic!("invalid downcast of non-BlockIssuanceCreditContextInput"); - } - } - - /// Checks whether the context input is a [`RewardContextInput`]. - pub fn is_reward(&self) -> bool { - matches!(self, Self::Reward(_)) - } - - /// Gets the input as an actual [`RewardContextInput`]. - /// PANIC: do not call on a non-reward context input. - pub fn as_reward(&self) -> &RewardContextInput { - if let Self::Reward(input) = self { - input - } else { - panic!("invalid downcast of non-RewardContextInput"); - } - } + def_is_as_opt!(ContextInput: Commitment, BlockIssuanceCredit, Reward); } #[cfg(test)] diff --git a/sdk/src/types/block/core/mod.rs b/sdk/src/types/block/core/mod.rs index bd3c3fcd38..3b1b9b40bb 100644 --- a/sdk/src/types/block/core/mod.rs +++ b/sdk/src/types/block/core/mod.rs @@ -95,35 +95,7 @@ impl Block { ValidationBlockBuilder::new(strong_parents, highest_supported_version, protocol_parameters_hash) } - /// Checks whether the block is a [`BasicBlock`]. - pub fn is_basic(&self) -> bool { - matches!(self, Self::Basic(_)) - } - - /// Gets the block as an actual [`BasicBlock`]. - /// NOTE: Will panic if the block is not a [`BasicBlock`]. - pub fn as_basic(&self) -> &BasicBlock { - if let Self::Basic(block) = self { - block - } else { - panic!("invalid downcast of non-BasicBlock"); - } - } - - /// Checks whether the block is a [`ValidationBlock`]. - pub fn is_validation(&self) -> bool { - matches!(self, Self::Validation(_)) - } - - /// Gets the block as an actual [`ValidationBlock`]. - /// NOTE: Will panic if the block is not a [`ValidationBlock`]. - pub fn as_validation(&self) -> &ValidationBlock { - if let Self::Validation(block) = self { - block - } else { - panic!("invalid downcast of non-ValidationBlock"); - } - } + def_is_as_opt!(Block: Basic, Validation); pub(crate) fn hash(&self) -> [u8; 32] { Blake2b256::digest(self.pack_to_vec()).into() diff --git a/sdk/src/types/block/input/mod.rs b/sdk/src/types/block/input/mod.rs index d0757d8554..e203669c9e 100644 --- a/sdk/src/types/block/input/mod.rs +++ b/sdk/src/types/block/input/mod.rs @@ -46,15 +46,5 @@ impl Input { } } - /// Checks whether the input is a [`UtxoInput`]. - pub fn is_utxo(&self) -> bool { - matches!(self, Self::Utxo(_)) - } - - /// Gets the input as an actual [`UtxoInput`]. - /// PANIC: do not call on a non-utxo input. - pub fn as_utxo(&self) -> &UtxoInput { - let Self::Utxo(input) = self; - input - } + def_is_as_opt!(Input: Utxo); } diff --git a/sdk/src/types/block/macro.rs b/sdk/src/types/block/macro.rs index 1f7d5cec28..99f8d737a9 100644 --- a/sdk/src/types/block/macro.rs +++ b/sdk/src/types/block/macro.rs @@ -200,3 +200,38 @@ macro_rules! impl_serde_typed_dto { } }; } + +#[macro_export] +macro_rules! def_is_as_opt { + ($type:ty: $($name:ident),+$(,)?) => { + paste::paste! { + $( + #[doc = "Checks whether the " [<$type:snake>] " is a(n) [`" [<$name $type>] "`]."] + pub fn [](&self) -> bool { + matches!(self, Self::$name(_)) + } + + #[doc = "Gets the " [<$type:snake>] " as an actual [`" [<$name $type>] "`]."] + #[doc = "PANIC: do not call on a non-" [<$name>] " " [<$type:snake>] "."] + pub fn [](&self) -> &[<$name $type>] { + #[allow(irrefutable_let_patterns)] + if let Self::$name(v) = self { + v + } else { + panic!("{} called on a non-{} {}", stringify!([]), stringify!([<$name>]), stringify!($type:snake)); + } + } + + #[doc = "Gets the " [<$type:snake>] " as an actual [`" [<$name $type>] "`], if it is one."] + pub fn [](&self) -> Option<&[<$name $type>]> { + #[allow(irrefutable_let_patterns)] + if let Self::$name(v) = self { + Some(v) + } else { + None + } + } + )+ + } + }; +} diff --git a/sdk/src/types/block/output/account.rs b/sdk/src/types/block/output/account.rs index 14a23bfe1a..90dd7b3cde 100644 --- a/sdk/src/types/block/output/account.rs +++ b/sdk/src/types/block/output/account.rs @@ -650,7 +650,6 @@ impl StateTransitionVerifier for AccountOutput { fn destruction(_current_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> { if !context .essence - .capabilities() .has_capability(TransactionCapabilityFlag::DestroyAccountOutputs) { // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 diff --git a/sdk/src/types/block/output/feature/block_issuer.rs b/sdk/src/types/block/output/feature/block_issuer.rs index 5f86e3931f..334d4240ac 100644 --- a/sdk/src/types/block/output/feature/block_issuer.rs +++ b/sdk/src/types/block/output/feature/block_issuer.rs @@ -44,17 +44,7 @@ impl BlockIssuerKey { } } - /// Checks whether the block issuer key is an [`Ed25519BlockIssuerKey`]. - pub fn is_ed25519(&self) -> bool { - matches!(self, Self::Ed25519(_)) - } - - /// Gets the block issuer key as an actual [`Ed25519BlockIssuerKey`]. - /// NOTE: Will panic if the block issuer key is not a [`Ed25519BlockIssuerKey`]. - pub fn as_ed25519(&self) -> &Ed25519BlockIssuerKey { - let Self::Ed25519(key) = self; - key - } + def_is_as_opt!(BlockIssuerKey: Ed25519); } /// An Ed25519 block issuer key. diff --git a/sdk/src/types/block/output/feature/mod.rs b/sdk/src/types/block/output/feature/mod.rs index 33904fe62e..0143179fc0 100644 --- a/sdk/src/types/block/output/feature/mod.rs +++ b/sdk/src/types/block/output/feature/mod.rs @@ -106,95 +106,7 @@ impl Feature { } } - /// Checks whether the feature is a [`SenderFeature`]. - pub fn is_sender(&self) -> bool { - matches!(self, Self::Sender(_)) - } - - /// Gets the feature as an actual [`SenderFeature`]. - /// NOTE: Will panic if the feature is not a [`SenderFeature`]. - pub fn as_sender(&self) -> &SenderFeature { - if let Self::Sender(feature) = self { - feature - } else { - panic!("invalid downcast of non-SenderFeature"); - } - } - - /// Checks whether the feature is an [`IssuerFeature`]. - pub fn is_issuer(&self) -> bool { - matches!(self, Self::Issuer(_)) - } - - /// Gets the feature as an actual [`IssuerFeature`]. - /// NOTE: Will panic if the feature is not an [`IssuerFeature`]. - pub fn as_issuer(&self) -> &IssuerFeature { - if let Self::Issuer(feature) = self { - feature - } else { - panic!("invalid downcast of non-IssuerFeature"); - } - } - - /// Checks whether the feature is a [`MetadataFeature`]. - pub fn is_metadata(&self) -> bool { - matches!(self, Self::Metadata(_)) - } - - /// Gets the feature as an actual [`MetadataFeature`]. - /// NOTE: Will panic if the feature is not a [`MetadataFeature`]. - pub fn as_metadata(&self) -> &MetadataFeature { - if let Self::Metadata(feature) = self { - feature - } else { - panic!("invalid downcast of non-MetadataFeature"); - } - } - - /// Checks whether the feature is a [`TagFeature`]. - pub fn is_tag(&self) -> bool { - matches!(self, Self::Tag(_)) - } - - /// Gets the feature as an actual [`TagFeature`]. - /// NOTE: Will panic if the feature is not a [`TagFeature`]. - pub fn as_tag(&self) -> &TagFeature { - if let Self::Tag(feature) = self { - feature - } else { - panic!("invalid downcast of non-TagFeature"); - } - } - - /// Checks whether the feature is a [`BlockIssuerFeature`]. - pub fn is_block_issuer(&self) -> bool { - matches!(self, Self::BlockIssuer(_)) - } - - /// Gets the feature as an actual [`BlockIssuerFeature`]. - /// NOTE: Will panic if the feature is not a [`BlockIssuerFeature`]. - pub fn as_block_issuer(&self) -> &BlockIssuerFeature { - if let Self::BlockIssuer(feature) = self { - feature - } else { - panic!("invalid downcast of non-BlockIssuerFeature"); - } - } - - /// Checks whether the feature is a [`StakingFeature`]. - pub fn is_staking(&self) -> bool { - matches!(self, Self::Staking(_)) - } - - /// Gets the feature as an actual [`StakingFeature`]. - /// NOTE: Will panic if the feature is not a [`StakingFeature`]. - pub fn as_staking(&self) -> &StakingFeature { - if let Self::Staking(feature) = self { - feature - } else { - panic!("invalid downcast of non-StakingFeature"); - } - } + def_is_as_opt!(Feature: Sender, Issuer, Metadata, Tag, BlockIssuer, Staking); } create_bitflags!( diff --git a/sdk/src/types/block/output/foundry.rs b/sdk/src/types/block/output/foundry.rs index 3f6ff5fcb3..691fe5809e 100644 --- a/sdk/src/types/block/output/foundry.rs +++ b/sdk/src/types/block/output/foundry.rs @@ -590,7 +590,6 @@ impl StateTransitionVerifier for FoundryOutput { fn destruction(current_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> { if !context .essence - .capabilities() .has_capability(TransactionCapabilityFlag::DestroyFoundryOutputs) { // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index 17549a2a24..a2e123bc21 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -174,6 +174,17 @@ impl Output { } } + /// Returns the mana of an [`Output`]. + pub fn mana(&self) -> u64 { + match self { + Self::Basic(output) => output.mana(), + Self::Account(output) => output.mana(), + Self::Foundry(_) => 0, + Self::Nft(output) => output.mana(), + Self::Delegation(_) => 0, + } + } + /// Returns the native tokens of an [`Output`], if any. pub fn native_tokens(&self) -> Option<&NativeTokens> { match self { @@ -229,80 +240,7 @@ impl Output { } } - /// Checks whether the output is a [`BasicOutput`]. - pub fn is_basic(&self) -> bool { - matches!(self, Self::Basic(_)) - } - - /// Gets the output as an actual [`BasicOutput`]. - /// NOTE: Will panic if the output is not a [`BasicOutput`]. - pub fn as_basic(&self) -> &BasicOutput { - if let Self::Basic(output) = self { - output - } else { - panic!("invalid downcast of non-BasicOutput"); - } - } - - /// Checks whether the output is an [`AccountOutput`]. - pub fn is_account(&self) -> bool { - matches!(self, Self::Account(_)) - } - - /// Gets the output as an actual [`AccountOutput`]. - /// NOTE: Will panic if the output is not a [`AccountOutput`]. - pub fn as_account(&self) -> &AccountOutput { - if let Self::Account(output) = self { - output - } else { - panic!("invalid downcast of non-AccountOutput"); - } - } - - /// Checks whether the output is a [`FoundryOutput`]. - pub fn is_foundry(&self) -> bool { - matches!(self, Self::Foundry(_)) - } - - /// Gets the output as an actual [`FoundryOutput`]. - /// NOTE: Will panic if the output is not a [`FoundryOutput`]. - pub fn as_foundry(&self) -> &FoundryOutput { - if let Self::Foundry(output) = self { - output - } else { - panic!("invalid downcast of non-FoundryOutput"); - } - } - - /// Checks whether the output is an [`NftOutput`]. - pub fn is_nft(&self) -> bool { - matches!(self, Self::Nft(_)) - } - - /// Gets the output as an actual [`NftOutput`]. - /// NOTE: Will panic if the output is not a [`NftOutput`]. - pub fn as_nft(&self) -> &NftOutput { - if let Self::Nft(output) = self { - output - } else { - panic!("invalid downcast of non-NftOutput"); - } - } - - /// Checks whether the output is a [`DelegationOutput`]. - pub fn is_delegation(&self) -> bool { - matches!(self, Self::Delegation(_)) - } - - /// Gets the output as an actual [`DelegationOutput`]. - /// NOTE: Will panic if the output is not a [`DelegationOutput`]. - pub fn as_delegation(&self) -> &DelegationOutput { - if let Self::Delegation(output) = self { - output - } else { - panic!("invalid downcast of non-DelegationOutput"); - } - } + def_is_as_opt!(Output: Basic, Account, Foundry, Nft, Delegation); /// Returns the address that is required to unlock this [`Output`] and the account or nft address that gets /// unlocked by it, if it's an account or nft. diff --git a/sdk/src/types/block/output/nft.rs b/sdk/src/types/block/output/nft.rs index 7f459c30ce..3c0f649f39 100644 --- a/sdk/src/types/block/output/nft.rs +++ b/sdk/src/types/block/output/nft.rs @@ -460,7 +460,6 @@ impl StateTransitionVerifier for NftOutput { fn destruction(_current_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> { if !context .essence - .capabilities() .has_capability(TransactionCapabilityFlag::DestroyNftOutputs) { // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 diff --git a/sdk/src/types/block/output/token_scheme/mod.rs b/sdk/src/types/block/output/token_scheme/mod.rs index 3ae41b98d4..d8912d7c6b 100644 --- a/sdk/src/types/block/output/token_scheme/mod.rs +++ b/sdk/src/types/block/output/token_scheme/mod.rs @@ -33,15 +33,5 @@ impl TokenScheme { } } - /// Checks whether the token scheme is a [`SimpleTokenScheme`]. - pub fn is_simple(&self) -> bool { - matches!(self, Self::Simple(_)) - } - - /// Gets the token scheme as an actual [`SimpleTokenScheme`]. - /// PANIC: do not call on a non-simple token scheme. - pub fn as_simple(&self) -> &SimpleTokenScheme { - let Self::Simple(scheme) = self; - scheme - } + def_is_as_opt!(TokenScheme: Simple); } diff --git a/sdk/src/types/block/output/unlock_condition/mod.rs b/sdk/src/types/block/output/unlock_condition/mod.rs index 28c7a00fa6..3ba443efad 100644 --- a/sdk/src/types/block/output/unlock_condition/mod.rs +++ b/sdk/src/types/block/output/unlock_condition/mod.rs @@ -103,110 +103,15 @@ impl UnlockCondition { } } - /// Checks whether the unlock condition is an [`AddressUnlockCondition`]. - pub fn is_address(&self) -> bool { - matches!(self, Self::Address(_)) - } - - /// Gets the unlock condition as an actual [`AddressUnlockCondition`]. - /// NOTE: Will panic if the unlock condition is not an [`AddressUnlockCondition`]. - pub fn as_address(&self) -> &AddressUnlockCondition { - if let Self::Address(unlock_condition) = self { - unlock_condition - } else { - panic!("invalid downcast of non-AddressUnlockCondition"); - } - } - - /// Checks whether the unlock condition is a [`StorageDepositReturnUnlockCondition`]. - pub fn is_storage_deposit_return(&self) -> bool { - matches!(self, Self::StorageDepositReturn(_)) - } - - /// Gets the unlock condition as an actual [`StorageDepositReturnUnlockCondition`]. - /// NOTE: Will panic if the unlock condition is not a [`StorageDepositReturnUnlockCondition`]. - pub fn as_storage_deposit_return(&self) -> &StorageDepositReturnUnlockCondition { - if let Self::StorageDepositReturn(unlock_condition) = self { - unlock_condition - } else { - panic!("invalid downcast of non-StorageDepositReturnUnlockCondition"); - } - } - - /// Checks whether the unlock condition is a [`TimelockUnlockCondition`]. - pub fn is_timelock(&self) -> bool { - matches!(self, Self::Timelock(_)) - } - - /// Gets the unlock condition as an actual [`TimelockUnlockCondition`]. - /// NOTE: Will panic if the unlock condition is not a [`TimelockUnlockCondition`]. - pub fn as_timelock(&self) -> &TimelockUnlockCondition { - if let Self::Timelock(unlock_condition) = self { - unlock_condition - } else { - panic!("invalid downcast of non-TimelockUnlockCondition"); - } - } - - /// Checks whether the unlock condition is an [`ExpirationUnlockCondition`]. - pub fn is_expiration(&self) -> bool { - matches!(self, Self::Expiration(_)) - } - - /// Gets the unlock condition as an actual [`ExpirationUnlockCondition`]. - /// NOTE: Will panic if the unlock condition is not an [`ExpirationUnlockCondition`]. - pub fn as_expiration(&self) -> &ExpirationUnlockCondition { - if let Self::Expiration(unlock_condition) = self { - unlock_condition - } else { - panic!("invalid downcast of non-ExpirationUnlockCondition"); - } - } - - /// Checks whether the unlock condition is a [`StateControllerAddressUnlockCondition`]. - pub fn is_state_controller_address(&self) -> bool { - matches!(self, Self::StateControllerAddress(_)) - } - - /// Gets the unlock condition as an actual [`StateControllerAddressUnlockCondition`]. - /// NOTE: Will panic if the unlock condition is not a [`StateControllerAddressUnlockCondition`]. - pub fn as_state_controller_address(&self) -> &StateControllerAddressUnlockCondition { - if let Self::StateControllerAddress(unlock_condition) = self { - unlock_condition - } else { - panic!("invalid downcast of non-StateControllerAddressUnlockCondition"); - } - } - - /// Checks whether the unlock condition is a [`GovernorAddressUnlockCondition`]. - pub fn is_governor_address(&self) -> bool { - matches!(self, Self::GovernorAddress(_)) - } - - /// Gets the unlock condition as an actual [`GovernorAddressUnlockCondition`]. - /// NOTE: Will panic if the unlock condition is not a [`GovernorAddressUnlockCondition`]. - pub fn as_governor_address(&self) -> &GovernorAddressUnlockCondition { - if let Self::GovernorAddress(unlock_condition) = self { - unlock_condition - } else { - panic!("invalid downcast of non-GovernorAddressUnlockCondition"); - } - } - - /// Checks whether the unlock condition is an [`ImmutableAccountAddressUnlockCondition`]. - pub fn is_immutable_account_address(&self) -> bool { - matches!(self, Self::ImmutableAccountAddress(_)) - } - - /// Gets the unlock condition as an actual [`ImmutableAccountAddressUnlockCondition`]. - /// NOTE: Will panic if the unlock condition is not an [`ImmutableAccountAddressUnlockCondition`]. - pub fn as_immutable_account_address(&self) -> &ImmutableAccountAddressUnlockCondition { - if let Self::ImmutableAccountAddress(unlock_condition) = self { - unlock_condition - } else { - panic!("invalid downcast of non-ImmutableAccountAddressUnlockCondition"); - } - } + def_is_as_opt!(UnlockCondition: + Address, + StorageDepositReturn, + Timelock, + Expiration, + StateControllerAddress, + GovernorAddress, + ImmutableAccountAddress + ); } create_bitflags!( diff --git a/sdk/src/types/block/payload/mod.rs b/sdk/src/types/block/payload/mod.rs index 1f386b016b..f70becd37b 100644 --- a/sdk/src/types/block/payload/mod.rs +++ b/sdk/src/types/block/payload/mod.rs @@ -61,6 +61,8 @@ impl Payload { Self::TaggedData(_) => TaggedDataPayload::KIND, } } + + def_is_as_opt!(Payload: Transaction, TaggedData); } impl Packable for Payload { diff --git a/sdk/src/types/block/payload/transaction/essence/mod.rs b/sdk/src/types/block/payload/transaction/essence/mod.rs index 19afeef77e..c877f51cf1 100644 --- a/sdk/src/types/block/payload/transaction/essence/mod.rs +++ b/sdk/src/types/block/payload/transaction/essence/mod.rs @@ -36,17 +36,7 @@ impl TransactionEssence { Blake2b256::digest(self.pack_to_vec()).into() } - /// Checks whether the essence is a [`RegularTransactionEssence`]. - pub fn is_regular(&self) -> bool { - matches!(self, Self::Regular(_)) - } - - /// Gets the essence as an actual [`RegularTransactionEssence`]. - /// PANIC: do not call on a non-regular essence. - pub fn as_regular(&self) -> &RegularTransactionEssence { - let Self::Regular(essence) = self; - essence - } + def_is_as_opt!(TransactionEssence: Regular); } #[cfg(feature = "serde")] diff --git a/sdk/src/types/block/payload/transaction/essence/regular.rs b/sdk/src/types/block/payload/transaction/essence/regular.rs index b6926d9e41..c6698790a4 100644 --- a/sdk/src/types/block/payload/transaction/essence/regular.rs +++ b/sdk/src/types/block/payload/transaction/essence/regular.rs @@ -282,6 +282,11 @@ impl RegularTransactionEssence { &self.capabilities } + /// Returns whether a given [`TransactionCapabilityFlag`] is enabled. + pub fn has_capability(&self, flag: TransactionCapabilityFlag) -> bool { + self.capabilities.has_capability(flag) + } + /// Returns the optional payload of a [`RegularTransactionEssence`]. pub fn payload(&self) -> Option<&Payload> { self.payload.as_ref() diff --git a/sdk/src/types/block/semantic.rs b/sdk/src/types/block/semantic.rs index b7feb09e8c..d64919bb3c 100644 --- a/sdk/src/types/block/semantic.rs +++ b/sdk/src/types/block/semantic.rs @@ -8,8 +8,8 @@ use hashbrown::{HashMap, HashSet}; use primitive_types::U256; use crate::types::block::{ - address::Address, - output::{ChainId, FoundryId, InputsCommitment, NativeTokens, Output, OutputId, TokenId}, + address::{Address, AddressCapabilityFlag}, + output::{ChainId, FoundryId, InputsCommitment, NativeTokens, Output, OutputId, TokenId, UnlockCondition}, payload::transaction::{RegularTransactionEssence, TransactionCapabilityFlag, TransactionEssence, TransactionId}, unlock::Unlocks, Error, @@ -350,6 +350,64 @@ pub fn semantic_validation( 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 let Some(sender) = features.and_then(|f| f.sender()) { if !context.unlocked_addresses.contains(sender.address()) { return Ok(Some(TransactionFailureReason::SenderNotUnlocked)); @@ -396,11 +454,7 @@ pub fn semantic_validation( return Ok(Some(TransactionFailureReason::SumInputsOutputsAmountMismatch)); } - if context.input_mana > context.output_mana - && !context - .essence - .capabilities() - .has_capability(TransactionCapabilityFlag::BurnMana) + if context.input_mana > context.output_mana && !context.essence.has_capability(TransactionCapabilityFlag::BurnMana) { // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); diff --git a/sdk/src/types/block/signature/mod.rs b/sdk/src/types/block/signature/mod.rs index c8086fe668..ad0a8e7b45 100644 --- a/sdk/src/types/block/signature/mod.rs +++ b/sdk/src/types/block/signature/mod.rs @@ -39,15 +39,5 @@ impl Signature { } } - /// Checks whether the signature is an [`Ed25519Signature`]. - pub fn is_ed25519(&self) -> bool { - matches!(self, Self::Ed25519(_)) - } - - /// Gets the signature as an actual [`Ed25519Signature`]. - /// PANIC: do not call on a non-ed25519 signature. - pub fn as_ed25519(&self) -> &Ed25519Signature { - let Self::Ed25519(sig) = self; - sig - } + def_is_as_opt!(Signature: Ed25519); } diff --git a/sdk/src/types/block/unlock/mod.rs b/sdk/src/types/block/unlock/mod.rs index bbd74b8f56..e3a39c6ab7 100644 --- a/sdk/src/types/block/unlock/mod.rs +++ b/sdk/src/types/block/unlock/mod.rs @@ -78,6 +78,8 @@ impl Unlock { Self::Nft(_) => NftUnlock::KIND, } } + + def_is_as_opt!(Unlock: Signature, Reference, Account, Nft); } pub(crate) type UnlockCount = BoundedU16<{ *UNLOCK_COUNT_RANGE.start() }, { *UNLOCK_COUNT_RANGE.end() }>;