diff --git a/sdk/src/types/block/address/multi.rs b/sdk/src/types/block/address/multi.rs index 593f300598..3db33a7d79 100644 --- a/sdk/src/types/block/address/multi.rs +++ b/sdk/src/types/block/address/multi.rs @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use alloc::{boxed::Box, vec::Vec}; +use alloc::{boxed::Box, collections::BTreeMap, vec::Vec}; use core::{fmt, ops::RangeInclusive}; use crypto::hashes::{blake2b::Blake2b256, Digest}; @@ -11,10 +11,7 @@ use packable::{bounded::BoundedU8, prefix::BoxedSlicePrefix, Packable, PackableE use crate::types::block::{address::Address, output::StorageScore, Error}; -pub(crate) type WeightedAddressCount = - BoundedU8<{ *MultiAddress::ADDRESSES_COUNT.start() }, { *MultiAddress::ADDRESSES_COUNT.end() }>; - -/// An address with an assigned weight. +/// An [`Address`] with an assigned weight. #[derive(Clone, Debug, Display, Eq, PartialEq, Ord, PartialOrd, Hash, From, AsRef, Deref, Packable)] #[display(fmt = "{address}")] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -30,7 +27,9 @@ pub struct WeightedAddress { impl WeightedAddress { /// Creates a new [`WeightedAddress`]. - pub fn new(address: Address, weight: u8) -> Result { + pub fn new(address: impl Into
, weight: u8) -> Result { + let address = address.into(); + verify_address::(&address)?; verify_weight::(&weight)?; @@ -69,9 +68,11 @@ fn verify_weight(weight: &u8) -> Result<(), Error> { } } -/// An address that consists of addresses with weights and a threshold value. -/// The Multi Address can be unlocked if the cumulative weight of all unlocked addresses is equal to or exceeds the -/// threshold. +pub(crate) type WeightedAddressCount = + BoundedU8<{ *MultiAddress::ADDRESSES_COUNT.start() }, { *MultiAddress::ADDRESSES_COUNT.end() }>; + +/// An [`Address`] that consists of addresses with weights and a threshold value. +/// It can be unlocked if the cumulative weight of all unlocked addresses is equal to or exceeds the threshold. #[derive(Clone, Debug, Deref, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)] #[packable(unpack_error = Error)] #[packable(verify_with = verify_multi_address)] @@ -95,10 +96,15 @@ impl MultiAddress { /// Creates a new [`MultiAddress`]. #[inline(always)] pub fn new(addresses: impl IntoIterator, threshold: u16) -> Result { - let mut addresses = addresses.into_iter().collect::>(); - - addresses.sort_by(|a, b| a.address().cmp(b.address())); - + // Using an intermediate BTreeMap to sort the addresses without having to repeatedly packing them. + let addresses = addresses + .into_iter() + .map(|address| (address.address().pack_to_vec(), address)) + .collect::>() + .into_values() + .collect::>(); + + verify_addresses::(&addresses)?; verify_threshold::(&threshold)?; let addresses = BoxedSlicePrefix::::try_from(addresses) @@ -136,7 +142,7 @@ impl MultiAddress { } fn verify_addresses(addresses: &[WeightedAddress]) -> Result<(), Error> { - if VERIFY && !is_unique_sorted(addresses.iter().map(WeightedAddress::address)) { + if VERIFY && !is_unique_sorted(addresses.iter().map(|a| a.address.pack_to_vec())) { Err(Error::WeightedAddressesNotUniqueSorted) } else { Ok(()) @@ -162,6 +168,7 @@ fn verify_multi_address(address: &MultiAddress) -> Result<() }); } } + Ok(()) } diff --git a/sdk/src/types/block/address/restricted.rs b/sdk/src/types/block/address/restricted.rs index 9ac601da5c..d52c316371 100644 --- a/sdk/src/types/block/address/restricted.rs +++ b/sdk/src/types/block/address/restricted.rs @@ -1,16 +1,22 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +//! An extension to the address format to make them configurable. +//! This enables an address to opt-in or -out of certain functionality, like disabling the receipt of Native Tokens, NFT +//! Outputs or Timelock Unlock Conditions. +//! [TIP-50: Configurable Addresses](https://github.com/iotaledger/tips/blob/tip50/tips/TIP-0050/tip-0050.md). + use getset::Getters; use packable::{Packable, PackableExt}; -use super::Address; use crate::types::block::{ + address::Address, capabilities::{Capabilities, CapabilityFlag}, output::{StorageScore, StorageScoreParameters}, Error, }; +/// An [`Address`] that contains another address and allows for configuring its capabilities. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Getters, Packable)] #[getset(get = "pub")] pub struct RestrictedAddress { diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index e2ec4241a5..196986c4bc 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -310,17 +310,18 @@ impl Output { }) } - /// Verifies if a valid storage deposit was made. Each [`Output`] has to have an amount that covers its associated - /// byte cost, given by [`StorageScoreParameters`]. + /// Verifies if a valid storage deposit was made. + /// Each [`Output`] has to have an amount that covers its associated byte cost, given by [`StorageScoreParameters`]. /// If there is a [`StorageDepositReturnUnlockCondition`](unlock_condition::StorageDepositReturnUnlockCondition), /// its amount is also checked. pub fn verify_storage_deposit(&self, params: StorageScoreParameters) -> Result<(), Error> { - let required_output_amount = self.minimum_amount(params); + let minimum_storage_deposit = self.minimum_amount(params); - if self.amount() < required_output_amount { + // For any created `Output` in a transaction, it must hold that `Output::Amount >= Minimum Storage Deposit`. + if self.amount() < minimum_storage_deposit { return Err(Error::InsufficientStorageDepositAmount { amount: self.amount(), - required: required_output_amount, + required: minimum_storage_deposit, }); } @@ -337,13 +338,13 @@ impl Output { }); } - let minimum_deposit = BasicOutput::minimum_amount(return_condition.return_address(), params); + let minimum_storage_deposit = BasicOutput::minimum_amount(return_condition.return_address(), params); // `Minimum Storage Deposit` ≤ `Return Amount` - if return_condition.amount() < minimum_deposit { + if return_condition.amount() < minimum_storage_deposit { return Err(Error::InsufficientStorageDepositReturnAmount { deposit: return_condition.amount(), - required: minimum_deposit, + required: minimum_storage_deposit, }); } } diff --git a/sdk/src/types/block/output/storage_score.rs b/sdk/src/types/block/output/storage_score.rs index 02d6b09be4..ad4bf77f36 100644 --- a/sdk/src/types/block/output/storage_score.rs +++ b/sdk/src/types/block/output/storage_score.rs @@ -1,6 +1,11 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +//! Storage deposit is a concept that creates a monetary incentive to keep the ledger state small. +//! This is achieved by enforcing a minimum IOTA coin deposit in every output based on the disk space that will actually +//! be used to store it. +//! [TIP-47: Storage Deposit Dust Protection](https://github.com/iotaledger/tips/blob/tip47/tips/TIP-0047/tip-0047.md). + use packable::Packable; use crate::types::block::{ @@ -13,16 +18,14 @@ use crate::types::block::{ BlockId, }; -const DEFAULT_STORAGE_COST: u64 = 500; +const DEFAULT_STORAGE_COST: u64 = 100; const DEFAULT_FACTOR_DATA: u8 = 1; const DEFAULT_OFFSET_OUTPUT_OVERHEAD: u64 = 10; -const DEFAULT_OFFSET_ED25519_BLOCK_ISSUER_KEY: u64 = 50; +const DEFAULT_OFFSET_ED25519_BLOCK_ISSUER_KEY: u64 = 100; const DEFAULT_OFFSET_STAKING_FEATURE: u64 = 100; const DEFAULT_OFFSET_DELEGATION: u64 = 100; -// Defines the parameters of storage score calculations on objects which take node resources. -// This structure defines the minimum base token deposit required on an object. This deposit does not -// generate Mana, which serves as a payment in Mana for storing the object. +// Parameters of storage score calculations on objects which take node resources. #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable)] #[cfg_attr( feature = "serde", @@ -30,21 +33,21 @@ const DEFAULT_OFFSET_DELEGATION: u64 = 100; serde(rename_all = "camelCase") )] pub struct StorageScoreParameters { - /// Defines the number of IOTA tokens required per unit of storage score. + /// Number of IOTA tokens required per unit of storage score. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] storage_cost: u64, - /// Defines the factor to be used for data only fields. + /// Factor to be used for data only fields. factor_data: u8, - /// Defines the offset to be applied to all outputs for the overhead of handling them in storage. + /// Offset to be applied to all outputs for the overhead of handling them in storage. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] offset_output_overhead: u64, - /// Defines the offset to be used for block issuer feature public keys. + /// Offset to be used for Ed25519-based block issuer keys. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] offset_ed25519_block_issuer_key: u64, - /// Defines the offset to be used for staking feature. + /// Offset to be used for staking feature. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] offset_staking_feature: u64, - /// Defines the offset to be used for delegation output. + /// Offset to be used for delegation. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] offset_delegation: u64, } diff --git a/sdk/src/types/block/payload/tagged_data.rs b/sdk/src/types/block/payload/tagged_data.rs index f99a2e280e..0bdec46c8d 100644 --- a/sdk/src/types/block/payload/tagged_data.rs +++ b/sdk/src/types/block/payload/tagged_data.rs @@ -1,7 +1,8 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! Module describing the tagged data payload. +//! A basic payload type that allows the addition of arbitrary data. +//! [TIP-53: Tagged Data](https://github.com/iotaledger/tips/blob/tip53/tips/TIP-0053/tip-0053.md). use alloc::boxed::Box; use core::ops::RangeInclusive; @@ -22,7 +23,7 @@ pub(crate) type TagLength = pub(crate) type TaggedDataLength = BoundedU32<{ *TaggedDataPayload::DATA_LENGTH_RANGE.start() }, { *TaggedDataPayload::DATA_LENGTH_RANGE.end() }>; -/// A payload which holds a tag and associated data. +/// A payload which holds optional data with an optional tag. #[derive(Clone, Eq, PartialEq, Packable)] #[packable(unpack_error = Error)] pub struct TaggedDataPayload { @@ -35,9 +36,9 @@ pub struct TaggedDataPayload { impl TaggedDataPayload { /// The [`Payload`](crate::types::block::payload::Payload) kind of a [`TaggedDataPayload`]. pub const KIND: u8 = 0; - /// Valid length range for the tag. + /// Valid tag length range. pub const TAG_LENGTH_RANGE: RangeInclusive = 0..=64; - /// Valid length range for the data. + /// Valid data length range. pub const DATA_LENGTH_RANGE: RangeInclusive = 0..=8192; /// Creates a new [`TaggedDataPayload`]. diff --git a/sdk/tests/types/address/multi.rs b/sdk/tests/types/address/multi.rs index 8705b358ec..a0ba5e6fb6 100644 --- a/sdk/tests/types/address/multi.rs +++ b/sdk/tests/types/address/multi.rs @@ -1,10 +1,32 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk::types::block::address::{Address, ToBech32Ext}; +use iota_sdk::types::block::{ + address::{AccountAddress, Address, Ed25519Address, MultiAddress, ToBech32Ext, WeightedAddress}, + output::AccountId, + rand::bytes::rand_bytes_array, +}; use packable::PackableExt; use pretty_assertions::assert_eq; +#[test] +fn ordered_by_packed_bytes() { + let mut bytes_1 = rand_bytes_array::<32>(); + bytes_1[0] = 0; + let mut bytes_2 = bytes_1.clone(); + bytes_2[0] = 1; + + let weighted_1 = WeightedAddress::new(AccountAddress::from(AccountId::from(bytes_1)), 1).unwrap(); + let weighted_2 = WeightedAddress::new(Ed25519Address::from(bytes_2), 1).unwrap(); + + let multi_1 = MultiAddress::new([weighted_1, weighted_2], 2).unwrap(); + let bytes = multi_1.pack_to_vec(); + let multi_2 = MultiAddress::unpack_verified(bytes, &()).unwrap(); + + assert!(multi_2.addresses()[0].address().is_ed25519()); + assert!(multi_2.addresses()[1].address().is_account()); +} + #[test] fn json_packable_bech32() { // Test from https://github.com/iotaledger/tips/blob/tip52/tips/TIP-0052/tip-0052.md#bech32