Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some TIP cleanup #1791

Merged
merged 16 commits into from
Jan 9, 2024
37 changes: 22 additions & 15 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 @@ -128,15 +134,15 @@ impl MultiAddress {
pub fn hash(&self) -> [u8; 32] {
let mut digest = Blake2b256::new();

digest.update([MultiAddress::KIND]);
digest.update([Self::KIND]);
digest.update(self.pack_to_vec());

digest.finalize().into()
}
}

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).
Alex6323 marked this conversation as resolved.
Show resolved Hide resolved

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
Loading