Skip to content

Commit

Permalink
Some TIP cleanup (#1791)
Browse files Browse the repository at this point in the history
* Some TIP cleanup

* TaggedDataPayload nits

* MultiAddress ordered_by_packed_bytes

* nit

* Nits

* Nit

* Add TIP link

* Update sdk/src/types/block/output/storage_score.rs

Co-authored-by: DaughterOfMars <[email protected]>

* format

* Sort with a BTreeMap

* format

* Clippy

---------

Co-authored-by: DaughterOfMars <[email protected]>
  • Loading branch information
thibault-martinez and DaughterOfMars authored Jan 9, 2024
1 parent 8647df1 commit a70e508
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 39 deletions.
35 changes: 21 additions & 14 deletions sdk/src/types/block/address/multi.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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))]
Expand All @@ -30,7 +27,9 @@ pub struct WeightedAddress {

impl WeightedAddress {
/// Creates a new [`WeightedAddress`].
pub fn new(address: Address, weight: u8) -> Result<Self, Error> {
pub fn new(address: impl Into<Address>, weight: u8) -> Result<Self, Error> {
let address = address.into();

verify_address::<true>(&address)?;
verify_weight::<true>(&weight)?;

Expand Down Expand Up @@ -69,9 +68,11 @@ fn verify_weight<const VERIFY: bool>(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)]
Expand All @@ -95,10 +96,15 @@ impl MultiAddress {
/// Creates a new [`MultiAddress`].
#[inline(always)]
pub fn new(addresses: impl IntoIterator<Item = WeightedAddress>, threshold: u16) -> Result<Self, Error> {
let mut addresses = addresses.into_iter().collect::<Box<[_]>>();

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::<BTreeMap<_, _>>()
.into_values()
.collect::<Box<[_]>>();

verify_addresses::<true>(&addresses)?;
verify_threshold::<true>(&threshold)?;

let addresses = BoxedSlicePrefix::<WeightedAddress, WeightedAddressCount>::try_from(addresses)
Expand Down Expand Up @@ -136,7 +142,7 @@ impl MultiAddress {
}

fn verify_addresses<const VERIFY: bool>(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(())
Expand All @@ -162,6 +168,7 @@ fn verify_multi_address<const VERIFY: bool>(address: &MultiAddress) -> Result<()
});
}
}

Ok(())
}

Expand Down
8 changes: 7 additions & 1 deletion sdk/src/types/block/address/restricted.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
17 changes: 9 additions & 8 deletions sdk/src/types/block/output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
}

Expand All @@ -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,
});
}
}
Expand Down
25 changes: 14 additions & 11 deletions sdk/src/types/block/output/storage_score.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -13,38 +18,36 @@ 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",
derive(serde::Serialize, serde::Deserialize),
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,
}
Expand Down
9 changes: 5 additions & 4 deletions sdk/src/types/block/payload/tagged_data.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {
Expand All @@ -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<u8> = 0..=64;
/// Valid length range for the data.
/// Valid data length range.
pub const DATA_LENGTH_RANGE: RangeInclusive<u32> = 0..=8192;

/// Creates a new [`TaggedDataPayload`].
Expand Down
24 changes: 23 additions & 1 deletion sdk/tests/types/address/multi.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down

0 comments on commit a70e508

Please sign in to comment.