From e1f123bbb8488beef6e3d23804631bc9a675969f Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Tue, 7 Nov 2023 15:39:33 +0100 Subject: [PATCH 01/22] Add MultiUnlock --- sdk/src/types/block/error.rs | 6 ++- sdk/src/types/block/unlock/mod.rs | 14 +++-- sdk/src/types/block/unlock/multi.rs | 81 +++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 sdk/src/types/block/unlock/multi.rs diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index 17a6023d38..7fb39b3728 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -23,7 +23,7 @@ use crate::types::block::{ }, payload::{ContextInputCount, InputCount, OutputCount, TagLength, TaggedDataLength}, protocol::ProtocolParametersHash, - unlock::{UnlockCount, UnlockIndex}, + unlock::{UnlockCount, UnlockIndex, UnlocksCount}, }; /// Error occurring when creating/parsing/validating blocks. @@ -78,6 +78,8 @@ pub enum Error { threshold: u16, }, InvalidWeightedAddressCount(>::Error), + InvalidMultiUnlockCount(>::Error), + MultiUnlockRecursion, WeightedAddressesNotUniqueSorted, InvalidContextInputKind(u8), InvalidContextInputCount(>::Error), @@ -282,6 +284,8 @@ impl fmt::Display for Error { ) } Self::InvalidWeightedAddressCount(count) => write!(f, "invalid weighted address count: {count}"), + Self::InvalidMultiUnlockCount(count) => write!(f, "invalid multi unlock count: {count}"), + Self::MultiUnlockRecursion => write!(f, "multi unlock recursion"), Self::WeightedAddressesNotUniqueSorted => { write!(f, "weighted addresses are not unique and/or sorted") } diff --git a/sdk/src/types/block/unlock/mod.rs b/sdk/src/types/block/unlock/mod.rs index 07e8c36273..94b256b044 100644 --- a/sdk/src/types/block/unlock/mod.rs +++ b/sdk/src/types/block/unlock/mod.rs @@ -3,6 +3,7 @@ mod account; mod anchor; +mod multi; mod nft; mod reference; mod signature; @@ -15,13 +16,14 @@ use hashbrown::HashSet; use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix, Packable}; pub use self::{ - account::AccountUnlock, anchor::AnchorUnlock, nft::NftUnlock, reference::ReferenceUnlock, + account::AccountUnlock, anchor::AnchorUnlock, multi::MultiUnlock, nft::NftUnlock, reference::ReferenceUnlock, signature::SignatureUnlock, }; use crate::types::block::{ input::{INPUT_COUNT_MAX, INPUT_COUNT_RANGE, INPUT_INDEX_MAX}, Error, }; +pub(crate) use multi::UnlocksCount; /// The maximum number of unlocks of a transaction. pub const UNLOCK_COUNT_MAX: u16 = INPUT_COUNT_MAX; // 128 @@ -50,12 +52,15 @@ pub enum Unlock { /// An account unlock. #[packable(tag = AccountUnlock::KIND)] Account(AccountUnlock), - /// An Anchor unlock. + /// An anchor unlock. #[packable(tag = AnchorUnlock::KIND)] Anchor(AnchorUnlock), /// An NFT unlock. #[packable(tag = NftUnlock::KIND)] Nft(NftUnlock), + /// A multi unlock. + #[packable(tag = MultiUnlock::KIND)] + Multi(MultiUnlock), } impl From for Unlock { @@ -72,6 +77,7 @@ impl core::fmt::Debug for Unlock { Self::Account(unlock) => unlock.fmt(f), Self::Anchor(unlock) => unlock.fmt(f), Self::Nft(unlock) => unlock.fmt(f), + Self::Multi(unlock) => unlock.fmt(f), } } } @@ -85,10 +91,11 @@ impl Unlock { Self::Account(_) => AccountUnlock::KIND, Self::Anchor(_) => AnchorUnlock::KIND, Self::Nft(_) => NftUnlock::KIND, + Self::Multi(_) => MultiUnlock::KIND, } } - crate::def_is_as_opt!(Unlock: Signature, Reference, Account, Nft); + crate::def_is_as_opt!(Unlock: Signature, Reference, Account, Anchor, Nft, Multi); } pub(crate) type UnlockCount = BoundedU16<{ *UNLOCK_COUNT_RANGE.start() }, { *UNLOCK_COUNT_RANGE.end() }>; @@ -154,6 +161,7 @@ fn verify_unlocks(unlocks: &[Unlock], _: &()) -> Result<(), return Err(Error::InvalidUnlockNft(index)); } } + Unlock::Multi(_) => todo!(), } } } diff --git a/sdk/src/types/block/unlock/multi.rs b/sdk/src/types/block/unlock/multi.rs new file mode 100644 index 0000000000..05b9167780 --- /dev/null +++ b/sdk/src/types/block/unlock/multi.rs @@ -0,0 +1,81 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::ops::RangeInclusive; + +use packable::{bounded::BoundedU8, prefix::BoxedSlicePrefix, Packable}; + +use crate::types::block::{unlock::Unlock, Error}; + +pub(crate) type UnlocksCount = + BoundedU8<{ *MultiUnlock::UNLOCKS_COUNT.start() }, { *MultiUnlock::UNLOCKS_COUNT.end() }>; + +/// Unlocks a Multi Address with a list of other unlocks. +#[derive(Clone, Debug, Eq, PartialEq, Hash, Packable)] +// #[packable(unpack_error = Error, with = |_| Error::InvalidMultiUnlockCount)] +pub struct MultiUnlock(#[packable(verify_with = verify_unlocks)] BoxedSlicePrefix); + +impl MultiUnlock { + /// The [`Unlock`](crate::types::block::unlock::Unlock) kind of an [`MultiUnlock`]. + pub const KIND: u8 = 5; + /// The allowed range of inner [`Unlock`]s. + pub const UNLOCKS_COUNT: RangeInclusive = 1..=10; + + /// Creates a new [`MultiUnlock`]. + #[inline(always)] + pub fn new(unlocks: impl IntoIterator) -> Result { + let unlocks = unlocks.into_iter().collect::>(); + + verify_unlocks::(&unlocks, &())?; + + Ok(Self( + BoxedSlicePrefix::::try_from(unlocks).map_err(Error::InvalidMultiUnlockCount)?, + )) + } + + /// Return the inner unlocks of an [`MultiUnlock`]. + #[inline(always)] + pub fn unlocks(&self) -> &[Unlock] { + &self.0 + } +} + +fn verify_unlocks(unlocks: &[Unlock], _visitor: &()) -> Result<(), Error> { + if VERIFY && unlocks.iter().any(|unlock| unlock.is_multi()) { + return Err(Error::MultiUnlockRecursion); + } else { + Ok(()) + } +} + +mod dto { + use serde::{Deserialize, Serialize}; + + use super::*; + + #[derive(Serialize, Deserialize)] + struct MultiUnlockDto { + #[serde(rename = "type")] + kind: u8, + unlocks: Vec, + } + + impl From<&MultiUnlock> for MultiUnlockDto { + fn from(value: &MultiUnlock) -> Self { + Self { + kind: MultiUnlock::KIND, + unlocks: value.0.to_vec(), + } + } + } + + impl TryFrom for MultiUnlock { + type Error = Error; + + fn try_from(value: MultiUnlockDto) -> Result { + Self::new(value.unlocks) + } + } + + crate::impl_serde_typed_dto!(MultiUnlock, MultiUnlockDto, "multi unlock"); +} From eb2036cf72f6fcd35719edfcb0c6238aa99447e1 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Tue, 7 Nov 2023 17:55:27 +0100 Subject: [PATCH 02/22] Add EmptyUnlock --- sdk/src/types/block/address/mod.rs | 20 ++++++++++++++ sdk/src/types/block/unlock/empty.rs | 43 +++++++++++++++++++++++++++++ sdk/src/types/block/unlock/mod.rs | 13 +++++++-- 3 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 sdk/src/types/block/unlock/empty.rs diff --git a/sdk/src/types/block/address/mod.rs b/sdk/src/types/block/address/mod.rs index 626eeb43ca..a52b97843f 100644 --- a/sdk/src/types/block/address/mod.rs +++ b/sdk/src/types/block/address/mod.rs @@ -166,6 +166,26 @@ impl Address { return Err(TransactionFailureReason::InvalidInputUnlock); } } + (Self::Multi(address), Unlock::Multi(unlock)) => { + let threshold = address.threshold(); + + if address.addresses().len() != unlock.unlocks().len() { + todo!(); + } + + let mut cumulative_unlocked_eight = 0u16; + + for (address, unlock) in address.addresses().iter().zip(unlock.unlocks()) { + if !unlock.is_empty() { + address.address().unlock(unlock, context)?; + cumulative_unlocked_eight += address.weight() as u16; + } + } + + if cumulative_unlocked_eight < threshold { + todo!(); + } + } // TODO maybe shouldn't be a semantic error but this function currently returns a TransactionFailureReason. (Self::Anchor(_), _) => return Err(TransactionFailureReason::SemanticValidationFailed), _ => return Err(TransactionFailureReason::InvalidInputUnlock), diff --git a/sdk/src/types/block/unlock/empty.rs b/sdk/src/types/block/unlock/empty.rs new file mode 100644 index 0000000000..721c1b8a78 --- /dev/null +++ b/sdk/src/types/block/unlock/empty.rs @@ -0,0 +1,43 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::types::block::Error; + +/// Used to maintain correct index relationship between addresses and signatures when unlocking a Multi Address where not all addresses are unlocked. +#[derive(Clone, Debug, Eq, PartialEq, Hash, packable::Packable)] +pub struct EmptyUnlock; + +impl EmptyUnlock { + /// The [`Unlock`](crate::types::block::unlock::Unlock) kind of an [`EmptyUnlock`]. + pub const KIND: u8 = 6; +} + +mod dto { + use serde::{Deserialize, Serialize}; + + use super::*; + + #[derive(Serialize, Deserialize)] + struct EmptyUnlockDto { + #[serde(rename = "type")] + kind: u8, + } + + impl From<&EmptyUnlock> for EmptyUnlockDto { + fn from(value: &EmptyUnlock) -> Self { + Self { + kind: EmptyUnlock::KIND, + } + } + } + + impl TryFrom for EmptyUnlock { + type Error = Error; + + fn try_from(value: EmptyUnlockDto) -> Result { + Ok(Self) + } + } + + crate::impl_serde_typed_dto!(EmptyUnlock, EmptyUnlockDto, "anchor unlock"); +} diff --git a/sdk/src/types/block/unlock/mod.rs b/sdk/src/types/block/unlock/mod.rs index 94b256b044..efb516195b 100644 --- a/sdk/src/types/block/unlock/mod.rs +++ b/sdk/src/types/block/unlock/mod.rs @@ -3,6 +3,7 @@ mod account; mod anchor; +mod empty; mod multi; mod nft; mod reference; @@ -16,8 +17,8 @@ use hashbrown::HashSet; use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix, Packable}; pub use self::{ - account::AccountUnlock, anchor::AnchorUnlock, multi::MultiUnlock, nft::NftUnlock, reference::ReferenceUnlock, - signature::SignatureUnlock, + account::AccountUnlock, anchor::AnchorUnlock, empty::EmptyUnlock, multi::MultiUnlock, nft::NftUnlock, + reference::ReferenceUnlock, signature::SignatureUnlock, }; use crate::types::block::{ input::{INPUT_COUNT_MAX, INPUT_COUNT_RANGE, INPUT_INDEX_MAX}, @@ -61,6 +62,9 @@ pub enum Unlock { /// A multi unlock. #[packable(tag = MultiUnlock::KIND)] Multi(MultiUnlock), + /// An empty unlock. + #[packable(tag = EmptyUnlock::KIND)] + Empty(EmptyUnlock), } impl From for Unlock { @@ -78,6 +82,7 @@ impl core::fmt::Debug for Unlock { Self::Anchor(unlock) => unlock.fmt(f), Self::Nft(unlock) => unlock.fmt(f), Self::Multi(unlock) => unlock.fmt(f), + Self::Empty(unlock) => unlock.fmt(f), } } } @@ -92,10 +97,11 @@ impl Unlock { Self::Anchor(_) => AnchorUnlock::KIND, Self::Nft(_) => NftUnlock::KIND, Self::Multi(_) => MultiUnlock::KIND, + Self::Empty(_) => EmptyUnlock::KIND, } } - crate::def_is_as_opt!(Unlock: Signature, Reference, Account, Anchor, Nft, Multi); + crate::def_is_as_opt!(Unlock: Signature, Reference, Account, Anchor, Nft, Multi, Empty); } pub(crate) type UnlockCount = BoundedU16<{ *UNLOCK_COUNT_RANGE.start() }, { *UNLOCK_COUNT_RANGE.end() }>; @@ -162,6 +168,7 @@ fn verify_unlocks(unlocks: &[Unlock], _: &()) -> Result<(), } } Unlock::Multi(_) => todo!(), + Unlock::Empty(_) => todo!(), } } } From 0fcefffc6086e6a71a9b698066da2d00c4ed91d6 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Tue, 7 Nov 2023 18:43:37 +0100 Subject: [PATCH 03/22] Make WeightedAddress public --- sdk/src/types/block/address/mod.rs | 2 +- sdk/src/types/block/unlock/empty.rs | 3 ++- sdk/src/types/block/unlock/mod.rs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sdk/src/types/block/address/mod.rs b/sdk/src/types/block/address/mod.rs index a52b97843f..c64dc449d3 100644 --- a/sdk/src/types/block/address/mod.rs +++ b/sdk/src/types/block/address/mod.rs @@ -22,7 +22,7 @@ pub use self::{ bech32::{Bech32Address, Hrp}, ed25519::Ed25519Address, implicit_account_creation::ImplicitAccountCreationAddress, - multi::MultiAddress, + multi::{MultiAddress, WeightedAddress}, nft::NftAddress, restricted::{AddressCapabilities, AddressCapabilityFlag, RestrictedAddress}, }; diff --git a/sdk/src/types/block/unlock/empty.rs b/sdk/src/types/block/unlock/empty.rs index 721c1b8a78..de36b721e7 100644 --- a/sdk/src/types/block/unlock/empty.rs +++ b/sdk/src/types/block/unlock/empty.rs @@ -3,7 +3,8 @@ use crate::types::block::Error; -/// Used to maintain correct index relationship between addresses and signatures when unlocking a Multi Address where not all addresses are unlocked. +/// Used to maintain correct index relationship between addresses and signatures when unlocking a Multi Address where +/// not all addresses are unlocked. #[derive(Clone, Debug, Eq, PartialEq, Hash, packable::Packable)] pub struct EmptyUnlock; diff --git a/sdk/src/types/block/unlock/mod.rs b/sdk/src/types/block/unlock/mod.rs index efb516195b..2b5b0c2fb6 100644 --- a/sdk/src/types/block/unlock/mod.rs +++ b/sdk/src/types/block/unlock/mod.rs @@ -14,6 +14,7 @@ use core::ops::RangeInclusive; use derive_more::{Deref, From}; use hashbrown::HashSet; +pub(crate) use multi::UnlocksCount; use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix, Packable}; pub use self::{ @@ -24,7 +25,6 @@ use crate::types::block::{ input::{INPUT_COUNT_MAX, INPUT_COUNT_RANGE, INPUT_INDEX_MAX}, Error, }; -pub(crate) use multi::UnlocksCount; /// The maximum number of unlocks of a transaction. pub const UNLOCK_COUNT_MAX: u16 = INPUT_COUNT_MAX; // 128 From b6152637553d02e0d74a3a94822095acb884e6bf Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 10:15:44 +0100 Subject: [PATCH 04/22] Fix packable compilation issue --- sdk/src/types/block/unlock/multi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/types/block/unlock/multi.rs b/sdk/src/types/block/unlock/multi.rs index 05b9167780..b52dbc2a98 100644 --- a/sdk/src/types/block/unlock/multi.rs +++ b/sdk/src/types/block/unlock/multi.rs @@ -12,7 +12,7 @@ pub(crate) type UnlocksCount = /// Unlocks a Multi Address with a list of other unlocks. #[derive(Clone, Debug, Eq, PartialEq, Hash, Packable)] -// #[packable(unpack_error = Error, with = |_| Error::InvalidMultiUnlockCount)] +#[packable(unpack_error = Error, with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidMultiUnlockCount(p.into())))] pub struct MultiUnlock(#[packable(verify_with = verify_unlocks)] BoxedSlicePrefix); impl MultiUnlock { From c5c4cf55af894c1ac427b5d81fe2cace353ddbbf Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 10:17:26 +0100 Subject: [PATCH 05/22] Nits --- sdk/src/types/block/unlock/empty.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/src/types/block/unlock/empty.rs b/sdk/src/types/block/unlock/empty.rs index de36b721e7..64915620d2 100644 --- a/sdk/src/types/block/unlock/empty.rs +++ b/sdk/src/types/block/unlock/empty.rs @@ -25,7 +25,7 @@ mod dto { } impl From<&EmptyUnlock> for EmptyUnlockDto { - fn from(value: &EmptyUnlock) -> Self { + fn from(_: &EmptyUnlock) -> Self { Self { kind: EmptyUnlock::KIND, } @@ -35,10 +35,10 @@ mod dto { impl TryFrom for EmptyUnlock { type Error = Error; - fn try_from(value: EmptyUnlockDto) -> Result { + fn try_from(_: EmptyUnlockDto) -> Result { Ok(Self) } } - crate::impl_serde_typed_dto!(EmptyUnlock, EmptyUnlockDto, "anchor unlock"); + crate::impl_serde_typed_dto!(EmptyUnlock, EmptyUnlockDto, "empty unlock"); } From 17df149f0afb2bdce40ca8881c9e671d518b01ff Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 10:20:18 +0100 Subject: [PATCH 06/22] Nit --- sdk/src/types/block/unlock/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/types/block/unlock/mod.rs b/sdk/src/types/block/unlock/mod.rs index 2b5b0c2fb6..3ff781aa3a 100644 --- a/sdk/src/types/block/unlock/mod.rs +++ b/sdk/src/types/block/unlock/mod.rs @@ -14,9 +14,9 @@ use core::ops::RangeInclusive; use derive_more::{Deref, From}; use hashbrown::HashSet; -pub(crate) use multi::UnlocksCount; use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix, Packable}; +pub(crate) use self::multi::UnlocksCount; pub use self::{ account::AccountUnlock, anchor::AnchorUnlock, empty::EmptyUnlock, multi::MultiUnlock, nft::NftUnlock, reference::ReferenceUnlock, signature::SignatureUnlock, From f048f697491c87f93f4ba91582b6fe792a06f2bc Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 10:25:53 +0100 Subject: [PATCH 07/22] return errors --- sdk/src/types/block/address/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/types/block/address/mod.rs b/sdk/src/types/block/address/mod.rs index c64dc449d3..45ab19e6b9 100644 --- a/sdk/src/types/block/address/mod.rs +++ b/sdk/src/types/block/address/mod.rs @@ -170,7 +170,7 @@ impl Address { let threshold = address.threshold(); if address.addresses().len() != unlock.unlocks().len() { - todo!(); + return Err(TransactionFailureReason::InvalidInputUnlock); } let mut cumulative_unlocked_eight = 0u16; @@ -183,7 +183,7 @@ impl Address { } if cumulative_unlocked_eight < threshold { - todo!(); + return Err(TransactionFailureReason::InvalidInputUnlock); } } // TODO maybe shouldn't be a semantic error but this function currently returns a TransactionFailureReason. From 7a3b28ca74906eebfd0c0172021a9e4326f39fcd Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 10:34:13 +0100 Subject: [PATCH 08/22] Remove variable --- sdk/src/types/block/address/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sdk/src/types/block/address/mod.rs b/sdk/src/types/block/address/mod.rs index 45ab19e6b9..4d5e91c779 100644 --- a/sdk/src/types/block/address/mod.rs +++ b/sdk/src/types/block/address/mod.rs @@ -167,8 +167,6 @@ impl Address { } } (Self::Multi(address), Unlock::Multi(unlock)) => { - let threshold = address.threshold(); - if address.addresses().len() != unlock.unlocks().len() { return Err(TransactionFailureReason::InvalidInputUnlock); } @@ -182,7 +180,7 @@ impl Address { } } - if cumulative_unlocked_eight < threshold { + if cumulative_unlocked_eight < address.threshold() { return Err(TransactionFailureReason::InvalidInputUnlock); } } From f1f01efee34aac0c685b0930597c9435badce944 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 10:37:20 +0100 Subject: [PATCH 09/22] Derive deref --- sdk/src/types/block/address/mod.rs | 2 +- sdk/src/types/block/address/multi.rs | 5 +++-- sdk/src/types/block/unlock/multi.rs | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/sdk/src/types/block/address/mod.rs b/sdk/src/types/block/address/mod.rs index 4d5e91c779..a71919b90d 100644 --- a/sdk/src/types/block/address/mod.rs +++ b/sdk/src/types/block/address/mod.rs @@ -167,7 +167,7 @@ impl Address { } } (Self::Multi(address), Unlock::Multi(unlock)) => { - if address.addresses().len() != unlock.unlocks().len() { + if address.len() != unlock.len() { return Err(TransactionFailureReason::InvalidInputUnlock); } diff --git a/sdk/src/types/block/address/multi.rs b/sdk/src/types/block/address/multi.rs index 4402dce12c..2c092ddb1d 100644 --- a/sdk/src/types/block/address/multi.rs +++ b/sdk/src/types/block/address/multi.rs @@ -4,7 +4,7 @@ use alloc::{boxed::Box, string::ToString, vec::Vec}; use core::{fmt, ops::RangeInclusive}; -use derive_more::{AsRef, Display, From}; +use derive_more::{AsRef, Deref, Display, From}; use iterator_sorted::is_unique_sorted; use packable::{ bounded::BoundedU8, @@ -76,9 +76,10 @@ fn verify_weight(weight: &u8, _visitor: &()) -> Result<(), E /// 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. -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Clone, Debug, Deref, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct MultiAddress { /// The weighted unlocked addresses. + #[deref] addresses: BoxedSlicePrefix, /// The threshold that needs to be reached by the unlocked addresses in order to unlock the multi address. threshold: u16, diff --git a/sdk/src/types/block/unlock/multi.rs b/sdk/src/types/block/unlock/multi.rs index b52dbc2a98..ea6d4edc68 100644 --- a/sdk/src/types/block/unlock/multi.rs +++ b/sdk/src/types/block/unlock/multi.rs @@ -3,6 +3,7 @@ use core::ops::RangeInclusive; +use derive_more::Deref; use packable::{bounded::BoundedU8, prefix::BoxedSlicePrefix, Packable}; use crate::types::block::{unlock::Unlock, Error}; @@ -11,7 +12,7 @@ pub(crate) type UnlocksCount = BoundedU8<{ *MultiUnlock::UNLOCKS_COUNT.start() }, { *MultiUnlock::UNLOCKS_COUNT.end() }>; /// Unlocks a Multi Address with a list of other unlocks. -#[derive(Clone, Debug, Eq, PartialEq, Hash, Packable)] +#[derive(Clone, Debug, Deref, Eq, PartialEq, Hash, Packable)] #[packable(unpack_error = Error, with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidMultiUnlockCount(p.into())))] pub struct MultiUnlock(#[packable(verify_with = verify_unlocks)] BoxedSlicePrefix); From 6519e53e1f8d261f725675cffd76b09ae0c8f4a1 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 10:38:23 +0100 Subject: [PATCH 10/22] Typo --- sdk/src/types/block/address/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/src/types/block/address/mod.rs b/sdk/src/types/block/address/mod.rs index a71919b90d..0e809ad68a 100644 --- a/sdk/src/types/block/address/mod.rs +++ b/sdk/src/types/block/address/mod.rs @@ -171,16 +171,16 @@ impl Address { return Err(TransactionFailureReason::InvalidInputUnlock); } - let mut cumulative_unlocked_eight = 0u16; + let mut cumulative_unlocked_weight = 0u16; for (address, unlock) in address.addresses().iter().zip(unlock.unlocks()) { if !unlock.is_empty() { address.address().unlock(unlock, context)?; - cumulative_unlocked_eight += address.weight() as u16; + cumulative_unlocked_weight += address.weight() as u16; } } - if cumulative_unlocked_eight < address.threshold() { + if cumulative_unlocked_weight < address.threshold() { return Err(TransactionFailureReason::InvalidInputUnlock); } } From 8f9d0381ad90a2874d14f9a02b253e9047af8646 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 10:42:34 +0100 Subject: [PATCH 11/22] More deref --- sdk/src/types/block/address/mod.rs | 2 +- sdk/src/types/block/address/multi.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk/src/types/block/address/mod.rs b/sdk/src/types/block/address/mod.rs index 0e809ad68a..0a2de03d27 100644 --- a/sdk/src/types/block/address/mod.rs +++ b/sdk/src/types/block/address/mod.rs @@ -175,7 +175,7 @@ impl Address { for (address, unlock) in address.addresses().iter().zip(unlock.unlocks()) { if !unlock.is_empty() { - address.address().unlock(unlock, context)?; + address.unlock(unlock, context)?; cumulative_unlocked_weight += address.weight() as u16; } } diff --git a/sdk/src/types/block/address/multi.rs b/sdk/src/types/block/address/multi.rs index 2c092ddb1d..36fdc297a0 100644 --- a/sdk/src/types/block/address/multi.rs +++ b/sdk/src/types/block/address/multi.rs @@ -21,11 +21,12 @@ pub(crate) type WeightedAddressCount = BoundedU8<{ *MultiAddress::ADDRESSES_COUNT.start() }, { *MultiAddress::ADDRESSES_COUNT.end() }>; /// An address with an assigned weight. -#[derive(Clone, Debug, Display, Eq, PartialEq, Ord, PartialOrd, Hash, From, AsRef, Packable)] +#[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))] pub struct WeightedAddress { /// The unlocked address. + #[deref] #[packable(verify_with = verify_address)] address: Address, /// The weight of the unlocked address. From 0c7a13af81a5245899d4d114bd78a242b604ebd4 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 10:46:03 +0100 Subject: [PATCH 12/22] UnlocksCount = WeightedAddressCount --- sdk/src/types/block/unlock/multi.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/sdk/src/types/block/unlock/multi.rs b/sdk/src/types/block/unlock/multi.rs index ea6d4edc68..5e6f2152ad 100644 --- a/sdk/src/types/block/unlock/multi.rs +++ b/sdk/src/types/block/unlock/multi.rs @@ -1,15 +1,12 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use core::ops::RangeInclusive; - use derive_more::Deref; -use packable::{bounded::BoundedU8, prefix::BoxedSlicePrefix, Packable}; +use packable::{prefix::BoxedSlicePrefix, Packable}; -use crate::types::block::{unlock::Unlock, Error}; +use crate::types::block::{address::WeightedAddressCount, unlock::Unlock, Error}; -pub(crate) type UnlocksCount = - BoundedU8<{ *MultiUnlock::UNLOCKS_COUNT.start() }, { *MultiUnlock::UNLOCKS_COUNT.end() }>; +pub(crate) type UnlocksCount = WeightedAddressCount; /// Unlocks a Multi Address with a list of other unlocks. #[derive(Clone, Debug, Deref, Eq, PartialEq, Hash, Packable)] @@ -19,8 +16,6 @@ pub struct MultiUnlock(#[packable(verify_with = verify_unlocks)] BoxedSlicePrefi impl MultiUnlock { /// The [`Unlock`](crate::types::block::unlock::Unlock) kind of an [`MultiUnlock`]. pub const KIND: u8 = 5; - /// The allowed range of inner [`Unlock`]s. - pub const UNLOCKS_COUNT: RangeInclusive = 1..=10; /// Creates a new [`MultiUnlock`]. #[inline(always)] From 1523fa9c21aff5de787645c8928030700352b4af Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 10:52:14 +0100 Subject: [PATCH 13/22] nit --- sdk/src/types/block/unlock/multi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/types/block/unlock/multi.rs b/sdk/src/types/block/unlock/multi.rs index 5e6f2152ad..deacf0152b 100644 --- a/sdk/src/types/block/unlock/multi.rs +++ b/sdk/src/types/block/unlock/multi.rs @@ -37,7 +37,7 @@ impl MultiUnlock { } fn verify_unlocks(unlocks: &[Unlock], _visitor: &()) -> Result<(), Error> { - if VERIFY && unlocks.iter().any(|unlock| unlock.is_multi()) { + if VERIFY && unlocks.iter().any(Unlock::is_multi) { return Err(Error::MultiUnlockRecursion); } else { Ok(()) From 86e03fcdb6ee308d2bb14ca8a1879e11f13fc284 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 11:54:39 +0100 Subject: [PATCH 14/22] Fix verify_unlocks --- sdk/src/types/block/unlock/mod.rs | 74 +++++++++++++++++++------------ 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/sdk/src/types/block/unlock/mod.rs b/sdk/src/types/block/unlock/mod.rs index 3ff781aa3a..5ad91ca480 100644 --- a/sdk/src/types/block/unlock/mod.rs +++ b/sdk/src/types/block/unlock/mod.rs @@ -133,42 +133,60 @@ impl Unlocks { } } +fn verify_unlock<'a>( + unlocks: &'a [Unlock], + unlock: &'a Unlock, + index: u16, + seen_signatures: &mut HashSet<&'a SignatureUnlock>, +) -> Result<(), Error> { + match unlock { + Unlock::Signature(signature) => { + if !seen_signatures.insert(signature.as_ref()) { + return Err(Error::DuplicateSignatureUnlock(index)); + } + } + Unlock::Reference(reference) => { + if index == 0 + || reference.index() >= index + || !matches!(unlocks[reference.index() as usize], Unlock::Signature(_)) + { + return Err(Error::InvalidUnlockReference(index)); + } + } + Unlock::Account(account) => { + if index == 0 || account.index() >= index { + return Err(Error::InvalidUnlockAccount(index)); + } + } + Unlock::Anchor(anchor) => { + if index == 0 || anchor.index() >= index { + return Err(Error::InvalidUnlockAnchor(index)); + } + } + Unlock::Nft(nft) => { + if index == 0 || nft.index() >= index { + return Err(Error::InvalidUnlockNft(index)); + } + } + Unlock::Multi(_) => return Err(Error::MultiUnlockRecursion), + Unlock::Empty(_) => {} + } + + Ok(()) +} + fn verify_unlocks(unlocks: &[Unlock], _: &()) -> Result<(), Error> { if VERIFY { let mut seen_signatures = HashSet::new(); for (index, unlock) in (0u16..).zip(unlocks.iter()) { match unlock { - Unlock::Signature(signature) => { - if !seen_signatures.insert(signature) { - return Err(Error::DuplicateSignatureUnlock(index)); - } - } - Unlock::Reference(reference) => { - if index == 0 - || reference.index() >= index - || !matches!(unlocks[reference.index() as usize], Unlock::Signature(_)) - { - return Err(Error::InvalidUnlockReference(index)); - } - } - Unlock::Account(account) => { - if index == 0 || account.index() >= index { - return Err(Error::InvalidUnlockAccount(index)); - } - } - Unlock::Anchor(anchor) => { - if index == 0 || anchor.index() >= index { - return Err(Error::InvalidUnlockAnchor(index)); - } - } - Unlock::Nft(nft) => { - if index == 0 || nft.index() >= index { - return Err(Error::InvalidUnlockNft(index)); + Unlock::Multi(multi) => { + for unlock in multi.unlocks() { + verify_unlock(unlocks, unlock, index, &mut seen_signatures)? } } - Unlock::Multi(_) => todo!(), - Unlock::Empty(_) => todo!(), + _ => verify_unlock(unlocks, unlock, index, &mut seen_signatures)?, } } } From 9fb200e84332ecca839056b239ebb8d20c585d02 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 11:56:42 +0100 Subject: [PATCH 15/22] no_std --- sdk/src/types/block/unlock/multi.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdk/src/types/block/unlock/multi.rs b/sdk/src/types/block/unlock/multi.rs index deacf0152b..d09396be58 100644 --- a/sdk/src/types/block/unlock/multi.rs +++ b/sdk/src/types/block/unlock/multi.rs @@ -1,6 +1,8 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use alloc::{boxed::Box, vec::Vec}; + use derive_more::Deref; use packable::{prefix::BoxedSlicePrefix, Packable}; From 2a839bbbda51a2ebc8ce2d0791053bfe42906c42 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 12:28:33 +0100 Subject: [PATCH 16/22] Update sdk/src/types/block/unlock/multi.rs Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> --- sdk/src/types/block/unlock/multi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/types/block/unlock/multi.rs b/sdk/src/types/block/unlock/multi.rs index d09396be58..024a9d8c88 100644 --- a/sdk/src/types/block/unlock/multi.rs +++ b/sdk/src/types/block/unlock/multi.rs @@ -10,7 +10,7 @@ use crate::types::block::{address::WeightedAddressCount, unlock::Unlock, Error}; pub(crate) type UnlocksCount = WeightedAddressCount; -/// Unlocks a Multi Address with a list of other unlocks. +/// Unlocks a [`MultiAddress`](crate::types::block::address::MultiAddress) with a list of other unlocks. #[derive(Clone, Debug, Deref, Eq, PartialEq, Hash, Packable)] #[packable(unpack_error = Error, with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidMultiUnlockCount(p.into())))] pub struct MultiUnlock(#[packable(verify_with = verify_unlocks)] BoxedSlicePrefix); From 871d30e049c9a7815f041ea3c15d7319abbadf0e Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 12:28:40 +0100 Subject: [PATCH 17/22] Update sdk/src/types/block/unlock/empty.rs Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> --- sdk/src/types/block/unlock/empty.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/types/block/unlock/empty.rs b/sdk/src/types/block/unlock/empty.rs index 64915620d2..63667830c2 100644 --- a/sdk/src/types/block/unlock/empty.rs +++ b/sdk/src/types/block/unlock/empty.rs @@ -3,7 +3,7 @@ use crate::types::block::Error; -/// Used to maintain correct index relationship between addresses and signatures when unlocking a Multi Address where +/// Used to maintain correct index relationship between addresses and signatures when unlocking a [`MultiAddress`](crate::types::block::address::MultiAddress) where /// not all addresses are unlocked. #[derive(Clone, Debug, Eq, PartialEq, Hash, packable::Packable)] pub struct EmptyUnlock; From 34673f77490d05281a4e98465df334e396e3be4b Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 12:29:19 +0100 Subject: [PATCH 18/22] Fmt --- sdk/src/types/block/unlock/empty.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/types/block/unlock/empty.rs b/sdk/src/types/block/unlock/empty.rs index 63667830c2..777414c8ce 100644 --- a/sdk/src/types/block/unlock/empty.rs +++ b/sdk/src/types/block/unlock/empty.rs @@ -3,8 +3,8 @@ use crate::types::block::Error; -/// Used to maintain correct index relationship between addresses and signatures when unlocking a [`MultiAddress`](crate::types::block::address::MultiAddress) where -/// not all addresses are unlocked. +/// Used to maintain correct index relationship between addresses and signatures when unlocking a +/// [`MultiAddress`](crate::types::block::address::MultiAddress) where not all addresses are unlocked. #[derive(Clone, Debug, Eq, PartialEq, Hash, packable::Packable)] pub struct EmptyUnlock; From 7bf436c9a9182e91c3c7dbbc329ad562c3b1697c Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 12:32:21 +0100 Subject: [PATCH 19/22] Comment and rename --- sdk/src/types/block/unlock/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sdk/src/types/block/unlock/mod.rs b/sdk/src/types/block/unlock/mod.rs index 5ad91ca480..fce07f8150 100644 --- a/sdk/src/types/block/unlock/mod.rs +++ b/sdk/src/types/block/unlock/mod.rs @@ -133,7 +133,9 @@ impl Unlocks { } } -fn verify_unlock<'a>( +/// Verifies the consistency of non-multi unlocks. +/// Will error on multi unlocks as they can't be nested. +fn verify_non_multi_unlock<'a>( unlocks: &'a [Unlock], unlock: &'a Unlock, index: u16, @@ -183,10 +185,10 @@ fn verify_unlocks(unlocks: &[Unlock], _: &()) -> Result<(), match unlock { Unlock::Multi(multi) => { for unlock in multi.unlocks() { - verify_unlock(unlocks, unlock, index, &mut seen_signatures)? + verify_non_multi_unlock(unlocks, unlock, index, &mut seen_signatures)? } } - _ => verify_unlock(unlocks, unlock, index, &mut seen_signatures)?, + _ => verify_non_multi_unlock(unlocks, unlock, index, &mut seen_signatures)?, } } } From 55f117222a122e16344225ba49c27813b01eb5a7 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 14:28:39 +0100 Subject: [PATCH 20/22] Move import to dto --- sdk/src/types/block/unlock/empty.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sdk/src/types/block/unlock/empty.rs b/sdk/src/types/block/unlock/empty.rs index 777414c8ce..a4b89ff15a 100644 --- a/sdk/src/types/block/unlock/empty.rs +++ b/sdk/src/types/block/unlock/empty.rs @@ -1,8 +1,6 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::types::block::Error; - /// Used to maintain correct index relationship between addresses and signatures when unlocking a /// [`MultiAddress`](crate::types::block::address::MultiAddress) where not all addresses are unlocked. #[derive(Clone, Debug, Eq, PartialEq, Hash, packable::Packable)] @@ -17,6 +15,7 @@ mod dto { use serde::{Deserialize, Serialize}; use super::*; + use crate::types::block::Error; #[derive(Serialize, Deserialize)] struct EmptyUnlockDto { From 5502da1b3ad0df39ae14d4164fe4ef78e35de06e Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 14:30:19 +0100 Subject: [PATCH 21/22] Order --- sdk/src/types/block/address/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/src/types/block/address/mod.rs b/sdk/src/types/block/address/mod.rs index f9f5db184d..6258a1fd1b 100644 --- a/sdk/src/types/block/address/mod.rs +++ b/sdk/src/types/block/address/mod.rs @@ -166,6 +166,11 @@ impl Address { return Err(TransactionFailureReason::InvalidInputUnlock); } } + // TODO maybe shouldn't be a semantic error but this function currently returns a TransactionFailureReason. + (Self::Anchor(_), _) => return Err(TransactionFailureReason::SemanticValidationFailed), + (Self::ImplicitAccountCreation(address), _) => { + return Self::from(*address.ed25519_address()).unlock(unlock, context); + } (Self::Multi(address), Unlock::Multi(unlock)) => { if address.len() != unlock.len() { return Err(TransactionFailureReason::InvalidInputUnlock); @@ -184,11 +189,6 @@ impl Address { return Err(TransactionFailureReason::InvalidInputUnlock); } } - // TODO maybe shouldn't be a semantic error but this function currently returns a TransactionFailureReason. - (Self::Anchor(_), _) => return Err(TransactionFailureReason::SemanticValidationFailed), - (Self::ImplicitAccountCreation(address), _) => { - return Self::from(*address.ed25519_address()).unlock(unlock, context); - } _ => return Err(TransactionFailureReason::InvalidInputUnlock), } From a029910b80200798ad9567db070763ad55ea1901 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 9 Nov 2023 14:42:15 +0100 Subject: [PATCH 22/22] review --- sdk/src/types/block/address/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk/src/types/block/address/mod.rs b/sdk/src/types/block/address/mod.rs index 6258a1fd1b..4857bf4093 100644 --- a/sdk/src/types/block/address/mod.rs +++ b/sdk/src/types/block/address/mod.rs @@ -168,24 +168,24 @@ impl Address { } // TODO maybe shouldn't be a semantic error but this function currently returns a TransactionFailureReason. (Self::Anchor(_), _) => return Err(TransactionFailureReason::SemanticValidationFailed), - (Self::ImplicitAccountCreation(address), _) => { - return Self::from(*address.ed25519_address()).unlock(unlock, context); + (Self::ImplicitAccountCreation(implicit_account_creation_address), _) => { + return Self::from(*implicit_account_creation_address.ed25519_address()).unlock(unlock, context); } - (Self::Multi(address), Unlock::Multi(unlock)) => { - if address.len() != unlock.len() { + (Self::Multi(multi_address), Unlock::Multi(unlock)) => { + if multi_address.len() != unlock.len() { return Err(TransactionFailureReason::InvalidInputUnlock); } let mut cumulative_unlocked_weight = 0u16; - for (address, unlock) in address.addresses().iter().zip(unlock.unlocks()) { + for (address, unlock) in multi_address.addresses().iter().zip(unlock.unlocks()) { if !unlock.is_empty() { address.unlock(unlock, context)?; cumulative_unlocked_weight += address.weight() as u16; } } - if cumulative_unlocked_weight < address.threshold() { + if cumulative_unlocked_weight < multi_address.threshold() { return Err(TransactionFailureReason::InvalidInputUnlock); } }