diff --git a/bindings/nodejs/lib/types/block/output/output.ts b/bindings/nodejs/lib/types/block/output/output.ts index 2f5ce7b90d..18535f2668 100644 --- a/bindings/nodejs/lib/types/block/output/output.ts +++ b/bindings/nodejs/lib/types/block/output/output.ts @@ -22,15 +22,15 @@ export type OutputId = string; */ enum OutputType { /** A Basic output. */ - Basic = 3, + Basic = 0, /** An Account output. */ - Account = 4, + Account = 1, /** A Foundry output. */ - Foundry = 5, + Foundry = 2, /** An NFT output. */ - Nft = 6, + Nft = 3, /** A Delegation output. */ - Delegation = 7, + Delegation = 4, } /** diff --git a/bindings/nodejs/lib/types/block/payload/payload.ts b/bindings/nodejs/lib/types/block/payload/payload.ts index aa43f87f82..a087aad976 100644 --- a/bindings/nodejs/lib/types/block/payload/payload.ts +++ b/bindings/nodejs/lib/types/block/payload/payload.ts @@ -6,9 +6,9 @@ */ enum PayloadType { /** A tagged data payload. */ - TaggedData = 5, + TaggedData = 0, /** A transaction payload. */ - Transaction = 6, + Transaction = 1, } /** diff --git a/bindings/python/iota_sdk/types/output.py b/bindings/python/iota_sdk/types/output.py index 6a6ea8add0..5876265944 100644 --- a/bindings/python/iota_sdk/types/output.py +++ b/bindings/python/iota_sdk/types/output.py @@ -17,17 +17,17 @@ class OutputType(IntEnum): """Output types. Attributes: - Basic (3): A basic output. - Account (4): An account output. - Foundry (5): A foundry output. - Nft (6): An NFT output. - Delegation (7): A delegation output. + Basic (0): A basic output. + Account (1): An account output. + Foundry (2): A foundry output. + Nft (3): An NFT output. + Delegation (4): A delegation output. """ - Basic = 3 - Account = 4 - Foundry = 5 - Nft = 6 - Delegation = 7 + Basic = 0 + Account = 1 + Foundry = 2 + Nft = 3 + Delegation = 4 @json diff --git a/bindings/python/iota_sdk/types/payload.py b/bindings/python/iota_sdk/types/payload.py index f2db60d367..5b254a5673 100644 --- a/bindings/python/iota_sdk/types/payload.py +++ b/bindings/python/iota_sdk/types/payload.py @@ -14,11 +14,11 @@ class PayloadType(IntEnum): """Block payload types. Attributes: - TaggedData (5): A tagged data payload. - Transaction (6): A transaction payload. + TaggedData (0): A tagged data payload. + Transaction (1): A transaction payload. """ - TaggedData = 5 - Transaction = 6 + TaggedData = 0 + Transaction = 1 @json diff --git a/bindings/python/tests/test_block.py b/bindings/python/tests/test_block.py index a02c54279d..ac4a8c0c99 100644 --- a/bindings/python/tests/test_block.py +++ b/bindings/python/tests/test_block.py @@ -16,7 +16,7 @@ def test_basic_block_with_tagged_data_payload(): "shallowLikeParents": [], "maxBurnedMana": "180500", "payload": { - "type": 5, + "type": 0, "tag": "0x484f524e4554205370616d6d6572", "data": "0x57652061726520616c6c206d616465206f662073746172647573742e0a436f756e743a20353436333730330a54696d657374616d703a20323032332d30372d31395430373a32323a32385a0a54697073656c656374696f6e3a20343732c2b573"}} block = BasicBlock.from_dict(block_dict) @@ -52,7 +52,7 @@ def test_block_wrapper_with_tagged_data_payload(): ], "maxBurnedMana": "180500", "payload": { - "type": 5, + "type": 0, "tag": "0x68656c6c6f20776f726c64", "data": "0x01020304" } @@ -84,7 +84,7 @@ def test_basic_block_with_tx_payload(): "weakParents": [], "shallowLikeParents": [], "maxBurnedMana": "180500", - "payload": {"type": 6, + "payload": {"type": 1, "essence": {"type": 1, "networkId": "1856588631910923207", "inputs": [{"type": 0, @@ -117,7 +117,7 @@ def test_basic_block_with_tx_payload_all_output_types(): "type": 0, "strongParents": [ "0x053296e7434e8a4d602f8db30a5aaf16c01140212fe79d8132137cda1c38a60a", "0x559ec1d9a31c55bd27588ada2ade70fb5b13764ddd600e29c3b018761ba30e15", "0xe78e8cdbbeda89e3408eed51b77e0db5ba035f5f3bf79a8365435bba40697693", "0xee9d6e45dbc080694e6c827fecbc31ad9f654cf57404bc98f4cbca033f8e3139"], "weakParents": [], "shallowLikeParents": [], "payload": { - "type": 6, "essence": { + "type": 1, "essence": { "type": 1, "networkId": "1856588631910923207", "inputs": [ { "type": 0, "transactionId": "0xa49f5a764c3fe22f702b5b238a75a648faae1863f61c14fac51ba58d26acb823", "transactionOutputIndex": 9}, { @@ -263,7 +263,7 @@ def test_basic_block_with_tx_payload_with_tagged_data_payload(): "weakParents": [], "shallowLikeParents": [], "maxBurnedMana": "180500", - "payload": {"type": 6, + "payload": {"type": 1, "essence": {"type": 1, "networkId": "1856588631910923207", "inputs": [{"type": 0, diff --git a/bindings/python/tests/test_output.py b/bindings/python/tests/test_output.py index 1d1ff0d4ad..3bac03889b 100644 --- a/bindings/python/tests/test_output.py +++ b/bindings/python/tests/test_output.py @@ -26,7 +26,7 @@ def test_feature(): def test_output(): basic_output_dict = { - "type": 3, + "type": 0, "mana": "999500700", "amount": "999500700", "unlockConditions": [ @@ -43,7 +43,7 @@ def test_output(): assert basic_output.to_dict() == basic_output_dict basic_output_dict = { - "type": 3, + "type": 0, "mana": "57600", "amount": "57600", "nativeTokens": [ @@ -82,7 +82,7 @@ def test_output(): assert basic_output.to_dict() == basic_output_dict basic_output_dict = { - "type": 3, + "type": 0, "mana": "50100", "amount": "50100", "nativeTokens": [ @@ -109,7 +109,7 @@ def test_output(): assert basic_output.to_dict() == basic_output_dict account_output_dict = { - "type": 4, + "type": 1, "mana": "168200", "amount": "168200", "accountId": "0x8d073d15074834785046d9cacec7ac4d672dcb6dad342624a936f3c4334520f1", @@ -146,7 +146,7 @@ def test_output(): assert account_output.to_dict() == account_output_dict account_output_dict = { - "type": 4, + "type": 1, "mana": "55100", "amount": "55100", "accountId": "0x5380cce0ac342b8fa3e9c4f46d5b473ee9e824f0017fe43682dca77e6b875354", @@ -187,7 +187,7 @@ def test_output(): assert account_output.to_dict() == account_output_dict foundry_output_dict = { - "type": 5, + "type": 2, "amount": "54700", "serialNumber": 1, "tokenScheme": { @@ -216,7 +216,7 @@ def test_output(): assert foundry_output.to_dict() == foundry_output_dict nft_output_dict = { - "type": 6, + "type": 3, "mana": "47800", "amount": "47800", "nftId": "0x90e84936bd0cffd1595d2a58f63b1a8d0d3e333ed893950a5f3f0043c6e59ec1", diff --git a/sdk/src/client/api/block_builder/input_selection/mod.rs b/sdk/src/client/api/block_builder/input_selection/mod.rs index d20cb8b4be..3c7540b318 100644 --- a/sdk/src/client/api/block_builder/input_selection/mod.rs +++ b/sdk/src/client/api/block_builder/input_selection/mod.rs @@ -25,6 +25,7 @@ use crate::{ AccountOutput, AccountTransition, ChainId, FoundryOutput, NativeTokensBuilder, NftOutput, Output, OutputId, OUTPUT_COUNT_RANGE, }, + payload::transaction::TransactionCapabilities, protocol::ProtocolParameters, slot::SlotIndex, }, @@ -487,9 +488,10 @@ impl InputSelection { } } Output::Foundry(foundry_output) => { + let foundry_id = foundry_output.id(); let foundry_input = input_foundries.iter().find(|i| { if let Output::Foundry(foundry_input) = &i.output { - foundry_output.id() == foundry_input.id() + foundry_id == foundry_input.id() } else { false } @@ -500,6 +502,9 @@ impl InputSelection { foundry_output, input_native_tokens_builder.deref(), output_native_tokens_builder.deref(), + // We use `all` capabilities here because this transition may be burning + // native tokens, and validation will fail without the capability. + &TransactionCapabilities::all(), ) { log::debug!("validate_transitions error {err:?}"); return Err(Error::UnfulfillableRequirement(Requirement::Foundry( diff --git a/sdk/src/types/block/address/restricted.rs b/sdk/src/types/block/address/restricted.rs index 3cb93f4ebb..2b20223d7c 100644 --- a/sdk/src/types/block/address/restricted.rs +++ b/sdk/src/types/block/address/restricted.rs @@ -1,14 +1,14 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use alloc::boxed::Box; - -use derive_more::Deref; use getset::Getters; -use packable::{error::UnpackErrorExt, prefix::BoxedSlicePrefix, Packable, PackableExt}; +use packable::{Packable, PackableExt}; use super::Address; -use crate::types::block::Error; +use crate::types::block::{ + capabilities::{Capabilities, CapabilityFlag}, + Error, +}; #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Getters, Packable)] #[getset(get = "pub")] @@ -99,9 +99,12 @@ impl AddressCapabilityFlag { const ACCOUNT_OUTPUTS: u8 = 0b00100000; const NFT_OUTPUTS: u8 = 0b01000000; const DELEGATION_OUTPUTS: u8 = 0b10000000; +} - /// Converts the flag into the byte representation. - pub fn as_byte(&self) -> u8 { +impl CapabilityFlag for AddressCapabilityFlag { + type Iterator = core::array::IntoIter; + + fn as_byte(&self) -> u8 { match self { Self::OutputsWithNativeTokens => Self::OUTPUTS_WITH_NATIVE_TOKENS, Self::OutputsWithMana => Self::OUTPUTS_WITH_MANA, @@ -114,8 +117,7 @@ impl AddressCapabilityFlag { } } - /// Returns the index in [`AddressCapabilities`] to which this flag is applied. - pub fn index(&self) -> usize { + fn index(&self) -> usize { match self { Self::OutputsWithNativeTokens | Self::OutputsWithMana @@ -128,8 +130,7 @@ impl AddressCapabilityFlag { } } - /// Returns an iterator over all flags. - pub fn all() -> impl Iterator { + fn all() -> Self::Iterator { [ Self::OutputsWithNativeTokens, Self::OutputsWithMana, @@ -144,139 +145,7 @@ impl AddressCapabilityFlag { } } -/// A list of bitflags that represent the capabilities of an [`Address`]. -/// If an output is created by a transaction with an -/// [`UnlockCondition`](crate::types::block::output::UnlockCondition) containing a [`RestrictedAddress`], the -/// transaction is valid only if the specified conditions, corresponding to the [`AddressCapabilityFlag`]s, hold for -/// that output. -#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Deref)] -#[repr(transparent)] -pub struct AddressCapabilities(BoxedSlicePrefix); - -impl AddressCapabilities { - /// Returns an [`AddressCapabilities`] with every possible flag enabled. - pub fn all() -> Self { - let mut res = Self::default(); - res.set_all(); - res - } - - /// Returns an [`AddressCapabilities`] with every possible flag disabled. - pub fn none() -> Self { - Self::default() - } - - /// Returns whether every possible [`AddressCapabilityFlag`] is enabled. - pub fn is_all(&self) -> bool { - AddressCapabilityFlag::all().all(|flag| self.has_capability(flag)) - } - - /// Returns whether every possible [`AddressCapabilityFlag`] is disabled. - pub fn is_none(&self) -> bool { - self.0.iter().all(|b| 0.eq(b)) - } - - /// Enables every possible [`AddressCapabilityFlag`]. - pub fn set_all(&mut self) -> &mut Self { - for flag in AddressCapabilityFlag::all() { - self.add_capability(flag); - } - self - } - - /// Disabled every possible [`AddressCapabilityFlag`]. - pub fn set_none(&mut self) -> &mut Self { - *self = Default::default(); - self - } - - /// Enables a given [`AddressCapabilityFlag`]. - pub fn add_capability(&mut self, flag: AddressCapabilityFlag) -> &mut Self { - if self.0.len() <= flag.index() { - let mut v = Box::<[_]>::from(self.0.clone()).into_vec(); - v.resize(flag.index() + 1, 0); - // Unwrap: safe because the indexes are within u8 bounds - self.0 = v.into_boxed_slice().try_into().unwrap(); - } - self.0[flag.index()] |= flag.as_byte(); - self - } - - /// Enables a given set of [`AddressCapabilityFlag`]s. - pub fn add_capabilities(&mut self, flags: impl IntoIterator) -> &mut Self { - for flag in flags { - self.add_capability(flag); - } - self - } - - /// Enables a given set of [`AddressCapabilityFlag`]s. - pub fn with_capabilities(mut self, flags: impl IntoIterator) -> Self { - self.add_capabilities(flags); - self - } - - /// Enables a given set of [`AddressCapabilityFlag`]s. - pub fn set_capabilities(&mut self, flags: impl IntoIterator) -> &mut Self { - *self = Self::default().with_capabilities(flags); - self - } - - /// Returns whether a given [`AddressCapabilityFlag`] is enabled. - pub fn has_capability(&self, flag: AddressCapabilityFlag) -> bool { - self.0 - .get(flag.index()) - .map(|byte| byte & flag.as_byte() == flag.as_byte()) - .unwrap_or_default() - } - - /// Returns whether a given set of [`AddressCapabilityFlag`]s are enabled. - pub fn has_capabilities(&self, flags: impl IntoIterator) -> bool { - flags.into_iter().all(|flag| self.has_capability(flag)) - } - - /// Returns an iterator over all enabled [`AddressCapabilityFlag`]s. - pub fn capabilities_iter(&self) -> impl Iterator + '_ { - self.0.iter().enumerate().flat_map(|(idx, byte)| { - AddressCapabilityFlag::all().filter(move |f| (idx == f.index() && byte & f.as_byte() == f.as_byte())) - }) - } -} - -impl> From for AddressCapabilities { - fn from(value: I) -> Self { - Self::default().with_capabilities(value) - } -} - -impl Packable for AddressCapabilities { - type UnpackError = Error; - type UnpackVisitor = (); - - fn pack(&self, packer: &mut P) -> Result<(), P::Error> { - if !self.is_none() { - self.0.pack(packer)?; - } else { - 0_u8.pack(packer)?; - } - Ok(()) - } - - fn unpack( - unpacker: &mut U, - visitor: &Self::UnpackVisitor, - ) -> Result> { - use packable::prefix::UnpackPrefixError; - Ok(Self( - BoxedSlicePrefix::unpack::<_, VERIFY>(unpacker, visitor) - // TODO: not sure if this is the best way to do this - .map_packable_err(|e| match e { - UnpackPrefixError::Item(i) | UnpackPrefixError::Prefix(i) => i, - }) - .coerce()?, - )) - } -} +pub type AddressCapabilities = Capabilities; #[cfg(feature = "serde")] pub(crate) mod dto { @@ -319,12 +188,14 @@ pub(crate) mod dto { type Error = Error; fn try_from(value: RestrictedAddressDto) -> Result { - Ok(Self::new(value.address)?.with_allowed_capabilities(AddressCapabilities( - value - .allowed_capabilities - .try_into() - .map_err(Error::InvalidAddressCapabilitiesCount)?, - ))) + Ok( + Self::new(value.address)?.with_allowed_capabilities(AddressCapabilities::from_bytes( + value + .allowed_capabilities + .try_into() + .map_err(Error::InvalidCapabilitiesCount)?, + )), + ) } } diff --git a/sdk/src/types/block/capabilities.rs b/sdk/src/types/block/capabilities.rs new file mode 100644 index 0000000000..c4ded2a0a3 --- /dev/null +++ b/sdk/src/types/block/capabilities.rs @@ -0,0 +1,209 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use alloc::boxed::Box; +use core::marker::PhantomData; + +use derive_more::Deref; +use packable::{ + error::UnpackErrorExt, + prefix::{BoxedSlicePrefix, UnpackPrefixError}, + Packable, +}; + +/// A list of bitflags that represent capabilities. +#[derive(Debug, Deref)] +#[repr(transparent)] +pub struct Capabilities { + #[deref] + bytes: BoxedSlicePrefix, + _flag: PhantomData, +} + +impl Capabilities { + pub(crate) fn from_bytes(bytes: BoxedSlicePrefix) -> Self { + Self { + bytes, + _flag: PhantomData, + } + } + + /// Returns a [`Capabilities`] with every possible flag disabled. + pub fn none() -> Self { + Self::default() + } + + /// Returns whether every possible flag is disabled. + pub fn is_none(&self) -> bool { + self.iter().all(|b| 0.eq(b)) + } +} + +impl Capabilities { + /// Returns a [`Capabilities`] with every possible flag enabled. + pub fn all() -> Self { + let mut res = Self::default(); + res.set_all(); + res + } + + /// Returns whether every possible flag is enabled. + pub fn is_all(&self) -> bool { + Flag::all().all(|flag| self.has_capability(flag)) + } + + /// Enables every possible flag. + pub fn set_all(&mut self) -> &mut Self { + for flag in Flag::all() { + self.add_capability(flag); + } + self + } + + /// Disables every possible flag. + pub fn set_none(&mut self) -> &mut Self { + *self = Default::default(); + self + } + + /// Enables a given flag. + pub fn add_capability(&mut self, flag: Flag) -> &mut Self { + if self.bytes.len() <= flag.index() { + let mut v = Box::<[_]>::from(self.bytes.clone()).into_vec(); + v.resize(flag.index() + 1, 0); + // Unwrap: safe because the indexes are within u8 bounds + self.bytes = v.into_boxed_slice().try_into().unwrap(); + } + self.bytes[flag.index()] |= flag.as_byte(); + self + } + + /// Enables a given set of flags. + pub fn add_capabilities(&mut self, flags: impl IntoIterator) -> &mut Self { + for flag in flags { + self.add_capability(flag); + } + self + } + + /// Enables a given set of flags. + pub fn with_capabilities(mut self, flags: impl IntoIterator) -> Self { + self.add_capabilities(flags); + self + } + + /// Overwrites the flags with a given set of flags. + pub fn set_capabilities(&mut self, flags: impl IntoIterator) -> &mut Self { + *self = Self::default().with_capabilities(flags); + self + } + + /// Returns whether a given flag is enabled. + pub fn has_capability(&self, flag: Flag) -> bool { + self.get(flag.index()) + .map(|byte| byte & flag.as_byte() == flag.as_byte()) + .unwrap_or_default() + } + + /// Returns whether a given set of flags are enabled. + pub fn has_capabilities(&self, flags: impl IntoIterator) -> bool { + flags.into_iter().all(|flag| self.has_capability(flag)) + } + + /// Returns an iterator over all enabled flags. + pub fn capabilities_iter(&self) -> impl Iterator + '_ { + self.iter().enumerate().flat_map(|(idx, byte)| { + Flag::all().filter(move |f| (idx == f.index() && byte & f.as_byte() == f.as_byte())) + }) + } +} + +impl Default for Capabilities { + fn default() -> Self { + Self { + bytes: Default::default(), + _flag: PhantomData, + } + } +} + +impl Clone for Capabilities { + fn clone(&self) -> Self { + Self { + bytes: self.bytes.clone(), + _flag: PhantomData, + } + } +} + +impl PartialEq for Capabilities { + fn eq(&self, other: &Self) -> bool { + self.bytes == other.bytes + } +} + +impl Eq for Capabilities {} + +impl PartialOrd for Capabilities { + fn partial_cmp(&self, other: &Self) -> Option { + self.bytes.partial_cmp(&other.bytes) + } +} + +impl Ord for Capabilities { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.bytes.cmp(&other.bytes) + } +} + +impl core::hash::Hash for Capabilities { + fn hash(&self, state: &mut H) { + self.bytes.hash(state); + } +} + +impl, Flag: CapabilityFlag> From for Capabilities { + fn from(value: I) -> Self { + Self::default().with_capabilities(value) + } +} + +impl Packable for Capabilities { + type UnpackError = crate::types::block::Error; + type UnpackVisitor = (); + + fn pack(&self, packer: &mut P) -> Result<(), P::Error> { + if !self.is_none() { + self.bytes.pack(packer)?; + } else { + 0_u8.pack(packer)?; + } + Ok(()) + } + + fn unpack( + unpacker: &mut U, + visitor: &Self::UnpackVisitor, + ) -> Result> { + Ok(Self::from_bytes( + BoxedSlicePrefix::unpack::<_, VERIFY>(unpacker, visitor) + .map_packable_err(|e| match e { + UnpackPrefixError::Item(i) | UnpackPrefixError::Prefix(i) => i, + }) + .coerce()?, + )) + } +} + +pub trait CapabilityFlag { + type Iterator: Iterator; + + /// Converts the flag into the byte representation. + fn as_byte(&self) -> u8; + + /// Returns the index in [`Capabilities`] to which this flag is applied. + fn index(&self) -> usize; + + /// Returns an iterator over all flags. + fn all() -> Self::Iterator; +} diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index c79904ebef..5e9ae86b4f 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -31,8 +31,10 @@ use crate::types::block::{ pub enum Error { ManaAllotmentsNotUniqueSorted, ConsumedAmountOverflow, + ConsumedManaOverflow, ConsumedNativeTokensAmountOverflow, CreatedAmountOverflow, + CreatedManaOverflow, CreatedNativeTokensAmountOverflow, Crypto(CryptoError), DuplicateBicAccountId(AccountId), @@ -81,7 +83,7 @@ pub enum Error { InvalidInputCount(>::Error), InvalidInputOutputIndex(>::Error), InvalidBech32Hrp(Bech32HrpError), - InvalidAddressCapabilitiesCount(>::Error), + InvalidCapabilitiesCount(>::Error), InvalidBlockWrapperLength(usize), InvalidStateMetadataLength(>::Error), InvalidManaValue(u64), @@ -188,8 +190,10 @@ impl fmt::Display for Error { match self { Self::ManaAllotmentsNotUniqueSorted => write!(f, "mana allotments are not unique and/or sorted"), Self::ConsumedAmountOverflow => write!(f, "consumed amount overflow"), + Self::ConsumedManaOverflow => write!(f, "consumed mana overflow"), Self::ConsumedNativeTokensAmountOverflow => write!(f, "consumed native tokens amount overflow"), Self::CreatedAmountOverflow => write!(f, "created amount overflow"), + Self::CreatedManaOverflow => write!(f, "created mana overflow"), Self::CreatedNativeTokensAmountOverflow => write!(f, "created native tokens amount overflow"), Self::Crypto(e) => write!(f, "cryptographic error: {e}"), Self::DuplicateBicAccountId(account_id) => write!(f, "duplicate BIC account id: {account_id}"), @@ -218,7 +222,7 @@ impl fmt::Display for Error { Self::InvalidAddressKind(k) => write!(f, "invalid address kind: {k}"), Self::InvalidAccountIndex(index) => write!(f, "invalid account index: {index}"), Self::InvalidBech32Hrp(e) => write!(f, "invalid bech32 hrp: {e}"), - Self::InvalidAddressCapabilitiesCount(e) => write!(f, "invalid capabilities count: {e}"), + Self::InvalidCapabilitiesCount(e) => write!(f, "invalid capabilities count: {e}"), Self::InvalidBlockKind(k) => write!(f, "invalid block kind: {k}"), Self::InvalidRewardInputIndex(idx) => write!(f, "invalid reward input index: {idx}"), Self::InvalidStorageDepositAmount(amount) => { diff --git a/sdk/src/types/block/mod.rs b/sdk/src/types/block/mod.rs index de471203d6..2954d4c8cd 100644 --- a/sdk/src/types/block/mod.rs +++ b/sdk/src/types/block/mod.rs @@ -12,6 +12,8 @@ mod issuer_id; /// A module that provides types and syntactic validations of addresses. pub mod address; +/// A module that provides functionality for capabilities. +pub mod capabilities; /// A module that provides types and syntactic validations of context inputs. pub mod context_input; /// A module that provides types and syntactic validations of blocks. diff --git a/sdk/src/types/block/output/account.rs b/sdk/src/types/block/output/account.rs index 337f301c69..90dd7b3cde 100644 --- a/sdk/src/types/block/output/account.rs +++ b/sdk/src/types/block/output/account.rs @@ -25,6 +25,7 @@ use crate::types::{ NativeTokens, Output, OutputBuilderAmount, OutputId, Rent, RentStructure, StateTransitionError, StateTransitionVerifier, }, + payload::transaction::TransactionCapabilityFlag, protocol::ProtocolParameters, semantic::{TransactionFailureReason, ValidationContext}, unlock::Unlock, @@ -646,7 +647,14 @@ impl StateTransitionVerifier for AccountOutput { ) } - fn destruction(_current_state: &Self, _context: &ValidationContext<'_>) -> Result<(), StateTransitionError> { + fn destruction(_current_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> { + if !context + .essence + .has_capability(TransactionCapabilityFlag::DestroyAccountOutputs) + { + // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 + return Err(StateTransitionError::UnsupportedStateTransition); + } Ok(()) } } diff --git a/sdk/src/types/block/output/delegation.rs b/sdk/src/types/block/output/delegation.rs index 0c2dda387e..439f50ce0f 100644 --- a/sdk/src/types/block/output/delegation.rs +++ b/sdk/src/types/block/output/delegation.rs @@ -359,6 +359,7 @@ impl DelegationOutput { // Transition, just without full ValidationContext. 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()) { return Err(StateTransitionError::NonDelayedClaimingTransition); } diff --git a/sdk/src/types/block/output/foundry.rs b/sdk/src/types/block/output/foundry.rs index 90a042eade..691fe5809e 100644 --- a/sdk/src/types/block/output/foundry.rs +++ b/sdk/src/types/block/output/foundry.rs @@ -25,6 +25,7 @@ use crate::types::{ NativeTokens, Output, OutputBuilderAmount, OutputId, Rent, RentStructure, StateTransitionError, StateTransitionVerifier, TokenId, TokenScheme, }, + payload::transaction::{TransactionCapabilities, TransactionCapabilityFlag}, protocol::ProtocolParameters, semantic::{TransactionFailureReason, ValidationContext}, unlock::Unlock, @@ -457,6 +458,7 @@ impl FoundryOutput { next_state: &Self, input_native_tokens: &BTreeMap, output_native_tokens: &BTreeMap, + capabilities: &TransactionCapabilities, ) -> Result<(), StateTransitionError> { if current_state.account_address() != next_state.account_address() || current_state.serial_number != next_state.serial_number @@ -524,6 +526,13 @@ impl FoundryOutput { if melted_diff > token_diff { return Err(StateTransitionError::InconsistentNativeTokensMeltBurn); } + + let burned_diff = token_diff - melted_diff; + + if !burned_diff.is_zero() && !capabilities.has_capability(TransactionCapabilityFlag::BurnNativeTokens) { + // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 + return Err(StateTransitionError::UnsupportedStateTransition); + } } } @@ -574,10 +583,19 @@ impl StateTransitionVerifier for FoundryOutput { next_state, &context.input_native_tokens, &context.output_native_tokens, + context.essence.capabilities(), ) } fn destruction(current_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> { + if !context + .essence + .has_capability(TransactionCapabilityFlag::DestroyFoundryOutputs) + { + // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 + return Err(StateTransitionError::UnsupportedStateTransition); + } + let token_id = current_state.token_id(); let input_tokens = context.input_native_tokens.get(&token_id).copied().unwrap_or_default(); let TokenScheme::Simple(ref current_token_scheme) = current_state.token_scheme; diff --git a/sdk/src/types/block/output/nft.rs b/sdk/src/types/block/output/nft.rs index 6e8430e44e..3c0f649f39 100644 --- a/sdk/src/types/block/output/nft.rs +++ b/sdk/src/types/block/output/nft.rs @@ -22,6 +22,7 @@ use crate::types::{ NativeTokens, Output, OutputBuilderAmount, OutputId, Rent, RentStructure, StateTransitionError, StateTransitionVerifier, }, + payload::transaction::TransactionCapabilityFlag, protocol::ProtocolParameters, semantic::{TransactionFailureReason, ValidationContext}, unlock::Unlock, @@ -456,7 +457,14 @@ impl StateTransitionVerifier for NftOutput { Self::transition_inner(current_state, next_state) } - fn destruction(_current_state: &Self, _context: &ValidationContext<'_>) -> Result<(), StateTransitionError> { + fn destruction(_current_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> { + if !context + .essence + .has_capability(TransactionCapabilityFlag::DestroyNftOutputs) + { + // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 + return Err(StateTransitionError::UnsupportedStateTransition); + } Ok(()) } } diff --git a/sdk/src/types/block/payload/transaction/essence/mod.rs b/sdk/src/types/block/payload/transaction/essence/mod.rs index 025020e27e..c877f51cf1 100644 --- a/sdk/src/types/block/payload/transaction/essence/mod.rs +++ b/sdk/src/types/block/payload/transaction/essence/mod.rs @@ -8,7 +8,9 @@ use derive_more::From; use packable::PackableExt; pub(crate) use self::regular::{ContextInputCount, InputCount, OutputCount}; -pub use self::regular::{RegularTransactionEssence, RegularTransactionEssenceBuilder}; +pub use self::regular::{ + RegularTransactionEssence, RegularTransactionEssenceBuilder, TransactionCapabilities, TransactionCapabilityFlag, +}; use crate::types::block::Error; /// A generic essence that can represent different types defining transaction essences. diff --git a/sdk/src/types/block/payload/transaction/essence/regular.rs b/sdk/src/types/block/payload/transaction/essence/regular.rs index c21dfb0ddd..c6698790a4 100644 --- a/sdk/src/types/block/payload/transaction/essence/regular.rs +++ b/sdk/src/types/block/payload/transaction/essence/regular.rs @@ -8,6 +8,7 @@ use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix, Packable}; use crate::types::{ block::{ + capabilities::{Capabilities, CapabilityFlag}, context_input::{ContextInput, CONTEXT_INPUT_COUNT_RANGE}, input::{Input, INPUT_COUNT_RANGE}, mana::{verify_mana_allotments_sum, ManaAllotment, ManaAllotments}, @@ -30,6 +31,7 @@ pub struct RegularTransactionEssenceBuilder { inputs_commitment: InputsCommitment, outputs: Vec, allotments: BTreeSet, + capabilities: TransactionCapabilities, payload: OptionalPayload, creation_slot: Option, } @@ -44,6 +46,7 @@ impl RegularTransactionEssenceBuilder { inputs_commitment, outputs: Vec::new(), allotments: BTreeSet::new(), + capabilities: Default::default(), payload: OptionalPayload::default(), creation_slot: None, } @@ -79,18 +82,18 @@ impl RegularTransactionEssenceBuilder { self } - /// Adds [`ManaAllotment`]s to a [`RegularTransactionEssenceBuilder`]. - pub fn with_mana_allotments(mut self, allotments: impl IntoIterator) -> Self { - self.allotments = allotments.into_iter().collect(); - self - } - /// Adds an output to a [`RegularTransactionEssenceBuilder`]. pub fn add_output(mut self, output: Output) -> Self { self.outputs.push(output); self } + /// Adds [`ManaAllotment`]s to a [`RegularTransactionEssenceBuilder`]. + pub fn with_mana_allotments(mut self, allotments: impl IntoIterator) -> Self { + self.allotments = allotments.into_iter().collect(); + self + } + /// Adds a [`ManaAllotment`] to a [`RegularTransactionEssenceBuilder`]. pub fn add_mana_allotment(mut self, allotment: ManaAllotment) -> Self { self.allotments.insert(allotment); @@ -103,6 +106,11 @@ impl RegularTransactionEssenceBuilder { self } + pub fn with_capabilities(mut self, capabilities: TransactionCapabilities) -> Self { + self.capabilities = capabilities; + self + } + /// Adds a payload to a [`RegularTransactionEssenceBuilder`]. pub fn with_payload(mut self, payload: impl Into) -> Self { self.payload = payload.into(); @@ -182,6 +190,7 @@ impl RegularTransactionEssenceBuilder { inputs_commitment: self.inputs_commitment, outputs, allotments, + capabilities: self.capabilities, payload: self.payload, }) } @@ -220,6 +229,7 @@ pub struct RegularTransactionEssence { #[packable(unpack_error_with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidOutputCount(p.into())))] outputs: BoxedSlicePrefix, allotments: ManaAllotments, + capabilities: TransactionCapabilities, #[packable(verify_with = verify_payload_packable)] payload: OptionalPayload, } @@ -268,6 +278,15 @@ impl RegularTransactionEssence { &self.allotments } + pub fn capabilities(&self) -> &TransactionCapabilities { + &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() @@ -419,17 +438,78 @@ fn verify_payload_packable( Ok(()) } +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[non_exhaustive] +pub enum TransactionCapabilityFlag { + BurnNativeTokens, + BurnMana, + DestroyAccountOutputs, + DestroyFoundryOutputs, + DestroyNftOutputs, +} + +impl TransactionCapabilityFlag { + const BURN_NATIVE_TOKENS: u8 = 0b00000001; + const BURN_MANA: u8 = 0b00000010; + const DESTROY_ACCOUNT_OUTPUTS: u8 = 0b00000100; + const DESTROY_FOUNDRY_OUTPUTS: u8 = 0b00001000; + const DESTROY_NFT_OUTPUTS: u8 = 0b00010000; +} + +impl CapabilityFlag for TransactionCapabilityFlag { + type Iterator = core::array::IntoIter; + + fn as_byte(&self) -> u8 { + match self { + Self::BurnNativeTokens => Self::BURN_NATIVE_TOKENS, + Self::BurnMana => Self::BURN_MANA, + Self::DestroyAccountOutputs => Self::DESTROY_ACCOUNT_OUTPUTS, + Self::DestroyFoundryOutputs => Self::DESTROY_FOUNDRY_OUTPUTS, + Self::DestroyNftOutputs => Self::DESTROY_NFT_OUTPUTS, + } + } + + fn index(&self) -> usize { + match self { + Self::BurnNativeTokens + | Self::BurnMana + | Self::DestroyAccountOutputs + | Self::DestroyFoundryOutputs + | Self::DestroyNftOutputs => 0, + } + } + + fn all() -> Self::Iterator { + [ + Self::BurnNativeTokens, + Self::BurnMana, + Self::DestroyAccountOutputs, + Self::DestroyFoundryOutputs, + Self::DestroyNftOutputs, + ] + .into_iter() + } +} + +pub type TransactionCapabilities = Capabilities; + #[cfg(feature = "serde")] pub(crate) mod dto { - use alloc::string::{String, ToString}; + use alloc::{ + boxed::Box, + string::{String, ToString}, + }; use core::str::FromStr; use serde::{Deserialize, Serialize}; use super::*; - use crate::types::{ - block::{mana::ManaAllotmentDto, output::dto::OutputDto, payload::dto::PayloadDto, Error}, - TryFromDto, + use crate::{ + types::{ + block::{mana::ManaAllotmentDto, output::dto::OutputDto, payload::dto::PayloadDto, Error}, + TryFromDto, + }, + utils::serde::prefix_hex_bytes, }; /// Describes the essence data making up a transaction by defining its inputs and outputs and an optional payload. @@ -445,6 +525,8 @@ pub(crate) mod dto { pub inputs_commitment: String, pub outputs: Vec, pub allotments: Vec, + #[serde(with = "prefix_hex_bytes")] + pub capabilities: Box<[u8]>, #[serde(default, skip_serializing_if = "Option::is_none")] pub payload: Option, } @@ -460,6 +542,7 @@ pub(crate) mod dto { inputs_commitment: value.inputs_commitment().to_string(), outputs: value.outputs().iter().map(Into::into).collect(), allotments: value.mana_allotments().iter().map(Into::into).collect(), + capabilities: value.capabilities().iter().copied().collect(), payload: match value.payload() { Some(p @ Payload::TaggedData(_)) => Some(p.into()), Some(_) => unimplemented!(), @@ -494,7 +577,10 @@ pub(crate) mod dto { .with_context_inputs(dto.context_inputs) .with_inputs(dto.inputs) .with_outputs(outputs) - .with_mana_allotments(mana_allotments); + .with_mana_allotments(mana_allotments) + .with_capabilities(Capabilities::from_bytes( + dto.capabilities.try_into().map_err(Error::InvalidCapabilitiesCount)?, + )); builder = if let Some(p) = dto.payload { if let PayloadDto::TaggedData(i) = p { diff --git a/sdk/src/types/block/payload/transaction/mod.rs b/sdk/src/types/block/payload/transaction/mod.rs index f3356abc00..1821efe2f9 100644 --- a/sdk/src/types/block/payload/transaction/mod.rs +++ b/sdk/src/types/block/payload/transaction/mod.rs @@ -11,7 +11,10 @@ use packable::{error::UnpackError, packer::Packer, unpacker::Unpacker, Packable, pub(crate) use self::essence::{ContextInputCount, InputCount, OutputCount}; pub use self::{ - essence::{RegularTransactionEssence, RegularTransactionEssenceBuilder, TransactionEssence}, + essence::{ + RegularTransactionEssence, RegularTransactionEssenceBuilder, TransactionCapabilities, + TransactionCapabilityFlag, TransactionEssence, + }, transaction_id::TransactionId, }; use crate::types::block::{protocol::ProtocolParameters, unlock::Unlocks, Error}; diff --git a/sdk/src/types/block/semantic.rs b/sdk/src/types/block/semantic.rs index 70a82ecc25..d64919bb3c 100644 --- a/sdk/src/types/block/semantic.rs +++ b/sdk/src/types/block/semantic.rs @@ -10,7 +10,7 @@ use primitive_types::U256; use crate::types::block::{ address::{Address, AddressCapabilityFlag}, output::{ChainId, FoundryId, InputsCommitment, NativeTokens, Output, OutputId, TokenId, UnlockCondition}, - payload::transaction::{RegularTransactionEssence, TransactionEssence, TransactionId}, + payload::transaction::{RegularTransactionEssence, TransactionCapabilityFlag, TransactionEssence, TransactionId}, unlock::Unlocks, Error, }; @@ -152,32 +152,23 @@ impl TryFrom for TransactionFailureReason { /// pub struct ValidationContext<'a> { - /// - pub essence: &'a RegularTransactionEssence, - /// - pub essence_hash: [u8; 32], - /// - pub inputs_commitment: InputsCommitment, - /// - pub unlocks: &'a Unlocks, - /// - pub input_amount: u64, - /// - pub input_native_tokens: BTreeMap, - /// - pub input_chains: HashMap, - /// - pub output_amount: u64, - /// - pub output_native_tokens: BTreeMap, - /// - pub output_chains: HashMap, - /// - pub unlocked_addresses: HashSet
, - /// - pub storage_deposit_returns: HashMap, - /// - pub simple_deposits: HashMap, + pub(crate) essence: &'a RegularTransactionEssence, + pub(crate) essence_hash: [u8; 32], + pub(crate) inputs_commitment: InputsCommitment, + // TODO + #[allow(dead_code)] + pub(crate) unlocks: &'a Unlocks, + pub(crate) input_amount: u64, + pub(crate) input_mana: u64, + pub(crate) input_native_tokens: BTreeMap, + pub(crate) input_chains: HashMap, + pub(crate) output_amount: u64, + pub(crate) output_mana: u64, + pub(crate) output_native_tokens: BTreeMap, + pub(crate) output_chains: HashMap, + pub(crate) unlocked_addresses: HashSet
, + pub(crate) storage_deposit_returns: HashMap, + pub(crate) simple_deposits: HashMap, } impl<'a> ValidationContext<'a> { @@ -194,6 +185,7 @@ impl<'a> ValidationContext<'a> { essence_hash: TransactionEssence::from(essence.clone()).hash(), inputs_commitment: InputsCommitment::new(inputs.clone().map(|(_, output)| output)), input_amount: 0, + input_mana: 0, input_native_tokens: BTreeMap::::new(), input_chains: inputs .filter_map(|(output_id, input)| { @@ -203,6 +195,7 @@ impl<'a> ValidationContext<'a> { }) .collect(), output_amount: 0, + output_mana: 0, output_native_tokens: BTreeMap::::new(), output_chains: essence .outputs() @@ -237,34 +230,39 @@ pub fn semantic_validation( // Validation of inputs. for ((output_id, consumed_output), unlock) in inputs.iter().zip(unlocks.iter()) { - let (conflict, amount, consumed_native_tokens, unlock_conditions) = match consumed_output { + 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(), ), @@ -296,6 +294,11 @@ pub fn semantic_validation( .checked_add(amount) .ok_or(Error::ConsumedAmountOverflow)?; + context.input_mana = context + .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(); @@ -309,7 +312,7 @@ pub fn semantic_validation( // Validation of outputs. for created_output in context.essence.outputs() { - let (amount, created_native_tokens, features) = match created_output { + 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(); @@ -319,12 +322,32 @@ pub fn semantic_validation( .ok_or(Error::CreatedAmountOverflow)?; } - (output.amount(), Some(output.native_tokens()), Some(output.features())) + ( + output.amount(), + output.mana(), + Some(output.native_tokens()), + Some(output.features()), + ) } - Output::Account(output) => (output.amount(), Some(output.native_tokens()), Some(output.features())), - Output::Foundry(output) => (output.amount(), Some(output.native_tokens()), Some(output.features())), - Output::Nft(output) => (output.amount(), Some(output.native_tokens()), Some(output.features())), - Output::Delegation(output) => (output.amount(), None, None), + 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() { @@ -396,6 +419,11 @@ pub fn semantic_validation( .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 @@ -426,6 +454,12 @@ pub fn semantic_validation( return Ok(Some(TransactionFailureReason::SumInputsOutputsAmountMismatch)); } + 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)); + } + let mut native_token_ids = HashSet::new(); // Validation of input native tokens. diff --git a/sdk/tests/client/input_selection/foundry_outputs.rs b/sdk/tests/client/input_selection/foundry_outputs.rs index d52f9b1961..38d9019542 100644 --- a/sdk/tests/client/input_selection/foundry_outputs.rs +++ b/sdk/tests/client/input_selection/foundry_outputs.rs @@ -1033,3 +1033,71 @@ fn foundry_in_outputs_and_required() { } }); } + +#[test] +fn melt_and_burn_native_tokens() { + let protocol_parameters = protocol_parameters(); + let account_id = AccountId::from_str(ACCOUNT_ID_1).unwrap(); + let foundry_id = FoundryId::build(&AccountAddress::from(account_id), 1, SimpleTokenScheme::KIND); + let token_id = TokenId::from(foundry_id); + + let mut inputs = build_inputs([ + Basic(1_000_000, BECH32_ADDRESS_ED25519_0, None, None, None, None, None, None), + Foundry( + 1_000_000, + account_id, + 1, + SimpleTokenScheme::new(1000, 0, 1000).unwrap(), + Some(vec![(&token_id.to_string(), 1000)]), + ), + ]); + let account_output = AccountOutputBuilder::new_with_amount(1_000_000, account_id) + .add_unlock_condition(StateControllerAddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .add_unlock_condition(GovernorAddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .with_foundry_counter(1) + .finish_output(protocol_parameters.token_supply()) + .unwrap(); + inputs.push(InputSigningData { + output: account_output, + output_metadata: rand_output_metadata(), + chain: None, + }); + let outputs = build_outputs([Foundry( + 1_000_000, + account_id, + 1, + // Melt 123 native tokens + SimpleTokenScheme::new(1000, 123, 1000).unwrap(), + None, + )]); + + let selected = InputSelection::new( + inputs.clone(), + outputs, + addresses([BECH32_ADDRESS_ED25519_0]), + protocol_parameters, + ) + // Burn 456 native tokens + .with_burn(Burn::new().add_native_token(token_id, 456)) + .select() + .unwrap(); + + assert!(unsorted_eq(&selected.inputs, &inputs)); + // Account next state + foundry + basic output with native tokens + assert_eq!(selected.outputs.len(), 3); + // Account state index is increased + selected.outputs.iter().for_each(|output| { + if let Output::Account(account_output) = &output { + // Input account has index 0, output should have index 1 + assert_eq!(account_output.state_index(), 1); + } + if let Output::Basic(basic_output) = &output { + // Basic output remainder has the remaining native tokens + assert_eq!(basic_output.native_tokens().first().unwrap().amount().as_u32(), 421); + } + }); +} diff --git a/sdk/tests/types/address/restricted.rs b/sdk/tests/types/address/restricted.rs index f9dc5defd9..0db5b3ec22 100644 --- a/sdk/tests/types/address/restricted.rs +++ b/sdk/tests/types/address/restricted.rs @@ -3,6 +3,7 @@ use iota_sdk::types::block::{ address::{Address, AddressCapabilities, AddressCapabilityFlag, Ed25519Address, RestrictedAddress, ToBech32Ext}, + capabilities::CapabilityFlag, rand::address::rand_ed25519_address, }; use packable::PackableExt; diff --git a/sdk/tests/types/block_id.rs b/sdk/tests/types/block_id.rs index 2dac54118a..437ed7ddab 100644 --- a/sdk/tests/types/block_id.rs +++ b/sdk/tests/types/block_id.rs @@ -3,13 +3,7 @@ use core::str::FromStr; -use iota_sdk::types::{ - block::{ - output::RentStructure, protocol::ProtocolParameters, rand::bytes::rand_bytes_array, BlockHash, BlockId, - BlockWrapper, BlockWrapperDto, - }, - TryFromDto, -}; +use iota_sdk::types::block::{rand::bytes::rand_bytes_array, BlockHash, BlockId}; use packable::PackableExt; const BLOCK_ID: &str = "0x52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64900000000"; @@ -67,11 +61,11 @@ fn memory_layout() { assert_eq!(block_id.as_ref(), memory_layout); } -fn protocol_parameters() -> ProtocolParameters { - ProtocolParameters::new(3, "test", "rms", RentStructure::default(), 0, 1695275822, 10, 0).unwrap() -} +// TODO: re-enable below tests when source is updated +// fn protocol_parameters() -> ProtocolParameters { +// ProtocolParameters::new(3, "test", "rms", RentStructure::default(), 0, 1695275822, 10, 0).unwrap() +// } -// TODO // #[test] // fn basic_block_id_tagged_data_payload() { // // Test from https://github.com/iotaledger/tips-draft/blob/tip46/tips/TIP-0046/tip-0046.md#basic-block-id-tagged-data-payload @@ -144,7 +138,6 @@ fn protocol_parameters() -> ProtocolParameters { // ); // } -// TODO // #[test] // fn basic_block_id_transaction_payload() { // // Test from https://github.com/iotaledger/tips-draft/blob/tip46/tips/TIP-0046/tip-0046.md#basic-block-id-transaction-payload @@ -200,6 +193,7 @@ fn protocol_parameters() -> ProtocolParameters { // } // ], // "allotments": [], +// "capabilities": 0, // "payload": { // "type": 5, // "tag": "0x1d7b3e11697264111e130b0e", @@ -245,13 +239,13 @@ fn protocol_parameters() -> ProtocolParameters { // 105, 17, 60, 11, 92, 1, 11, 90, 72, 56, 79, 56, 47, 74, 73, 71, 28, 72, 96, 104, 60, 111, 10, 13, 68, // 111, 1, 46, 27, 17, 124, 78, 64, 95, 94, 36, 73, 124, 114, 105, 31, 67, 83, 92, 11, 66, 1, 22, 48, 7, 33, // 120, 3, 0, 96, 120, 4, 11, 15, 81, 80, 125, 53, 114, 53, 90, 69, 120, 57, 9, 94, 87, 47, 18, 85, 0, 64, -// 27, 125, 34, 12, 119, 43, 86, 22, 90, 18, 31, 1, 0, 0, 6, 0, 0, 0, 2, 248, 88, 2, 55, 185, 61, 170, 50, +// 27, 125, 34, 12, 119, 43, 86, 22, 90, 18, 32, 1, 0, 0, 6, 0, 0, 0, 2, 248, 88, 2, 55, 185, 61, 170, 50, // 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 36, 255, 155, 48, 56, 80, 111, 177, 180, 6, 48, 106, 73, 96, 1, // 195, 226, 78, 43, 224, 124, 131, 131, 23, 146, 43, 242, 29, 104, 106, 7, 143, 10, 0, 183, 12, 111, 134, // 161, 234, 3, 165, 154, 113, 215, 61, 205, 7, 226, 8, 43, 189, 240, 206, 151, 31, 170, 33, 116, 131, 72, // 188, 162, 47, 176, 35, 1, 0, 3, 16, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 217, 248, // 68, 88, 40, 109, 196, 28, 211, 71, 137, 222, 197, 102, 205, 9, 108, 244, 125, 233, 145, 170, 54, 169, -// 122, 235, 250, 234, 20, 18, 143, 109, 0, 0, 0, 33, 0, 0, 0, 5, 0, 0, 0, 12, 29, 123, 62, 17, 105, 114, +// 122, 235, 250, 234, 20, 18, 143, 109, 0, 0, 0, 0, 33, 0, 0, 0, 5, 0, 0, 0, 12, 29, 123, 62, 17, 105, 114, // 100, 17, 30, 19, 11, 14, 12, 0, 0, 0, 29, 123, 62, 17, 105, 114, 100, 17, 30, 19, 11, 14, 1, 0, 0, 0, // 128, 51, 97, 254, 30, 255, 200, 153, 220, 167, 249, 49, 216, 173, 7, 192, 27, 162, 58, 170, 147, 249, // 134, 173, 176, 77, 76, 23, 207, 99, 104, 216, 204, 221, 186, 195, 170, 172, 65, 62, 1, 147, 225, 109, @@ -268,7 +262,7 @@ fn protocol_parameters() -> ProtocolParameters { // assert_eq!( // block_id, -// "0x22215ad9e912989a4886d48a7147b23b753c251861cd0ed14649a11cd85028f60200000000000000" +// "0x95f37c6a1e838133726aaefa8c33a65204bfe811000542f593a6b0b997bc78d90200000000000000" // ); // } diff --git a/sdk/tests/types/transaction_id.rs b/sdk/tests/types/transaction_id.rs index 74cf770991..7a42e2bfd0 100644 --- a/sdk/tests/types/transaction_id.rs +++ b/sdk/tests/types/transaction_id.rs @@ -54,7 +54,7 @@ fn pack_unpack_valid() { ); } -// TODO +// TODO: re-enable when source is updated // #[test] // fn transaction_id() { // // Test from https://github.com/iotaledger/tips-draft/blob/tip46/tips/TIP-0046/tip-0046.md#transaction-id diff --git a/sdk/tests/types/transaction_regular_essence.rs b/sdk/tests/types/transaction_regular_essence.rs index 2e318a5457..e5c63915f9 100644 --- a/sdk/tests/types/transaction_regular_essence.rs +++ b/sdk/tests/types/transaction_regular_essence.rs @@ -13,7 +13,10 @@ use iota_sdk::types::block::{ Output, SimpleTokenScheme, TokenId, TokenScheme, }, payload::{ - transaction::{RegularTransactionEssence, TransactionId, TransactionPayload}, + transaction::{ + RegularTransactionEssence, TransactionCapabilities, TransactionCapabilityFlag, TransactionId, + TransactionPayload, + }, Payload, }, protocol::protocol_parameters, @@ -464,3 +467,46 @@ fn duplicate_output_foundry() { Err(Error::DuplicateOutputChain(ChainId::Foundry(foundry_id_0))) if foundry_id_0 == foundry_id )); } + +#[test] +fn transactions_capabilities() { + let protocol_parameters = protocol_parameters(); + let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); + let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap()); + let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1).unwrap()); + let address = Address::from(Ed25519Address::new(prefix_hex::decode(ED25519_ADDRESS_1).unwrap())); + let amount = 1_000_000; + let output = Output::Basic( + BasicOutput::build_with_amount(amount) + .add_unlock_condition(AddressUnlockCondition::new(address)) + .finish_with_params(&protocol_parameters) + .unwrap(), + ); + let essence = RegularTransactionEssence::builder(protocol_parameters.network_id(), rand_inputs_commitment()) + .with_inputs(vec![input1, input2]) + .add_output(output) + .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) + .finish_with_params(&protocol_parameters) + .unwrap(); + let mut capabilities = essence.capabilities().clone(); + + use TransactionCapabilityFlag as Flag; + + assert!(capabilities.is_none()); + + assert!(!capabilities.has_capability(Flag::BurnNativeTokens)); + capabilities.add_capability(Flag::BurnNativeTokens); + assert!(capabilities.has_capabilities([Flag::BurnNativeTokens])); + + assert!(!capabilities.has_capability(Flag::BurnMana)); + capabilities.set_capabilities([Flag::BurnMana, Flag::DestroyAccountOutputs]); + assert!(capabilities.has_capabilities([Flag::BurnMana, Flag::DestroyAccountOutputs])); + assert!(!capabilities.has_capability(Flag::BurnNativeTokens)); + + assert!(!capabilities.is_none()); + + assert!(!capabilities.has_capabilities(TransactionCapabilities::all().capabilities_iter())); + capabilities.set_all(); + assert!(capabilities.has_capabilities(TransactionCapabilities::all().capabilities_iter())); + assert!(capabilities.has_capabilities([Flag::DestroyFoundryOutputs, Flag::DestroyNftOutputs])); +}