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

Add MultiAddress #1573

Merged
merged 29 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bacea90
Add MultiAddress
thibault-martinez Nov 5, 2023
7082a82
Fix common feature set CI
thibault-martinez Nov 5, 2023
9f3b338
Add verify_weight
thibault-martinez Nov 6, 2023
42d24c9
Add verify_address
thibault-martinez Nov 6, 2023
8de4baa
Docs
thibault-martinez Nov 6, 2023
414b4cf
Add verify_threshold
thibault-martinez Nov 6, 2023
fcd0347
Merge branch '2.0' into multi-address
thibault-martinez Nov 6, 2023
899c820
Add impl Packable for MultiAddress
thibault-martinez Nov 6, 2023
095f948
Bring back Address Debug
thibault-martinez Nov 6, 2023
42cd390
Nit
thibault-martinez Nov 6, 2023
581f222
Add verify_cumulative_weight
thibault-martinez Nov 6, 2023
642a922
Add getters
thibault-martinez Nov 6, 2023
971830e
Add verify_addresses
thibault-martinez Nov 6, 2023
2f628df
Error nits
thibault-martinez Nov 6, 2023
8a802f9
Allow anchor
thibault-martinez Nov 6, 2023
05bb493
Nits
thibault-martinez Nov 6, 2023
b50808c
MultiAddress Display
thibault-martinez Nov 6, 2023
ebd4dab
Fix common feature sets CI
thibault-martinez Nov 6, 2023
e427afe
Update sdk/src/types/block/address/multi.rs
thibault-martinez Nov 6, 2023
8795db0
Merge branch '2.0' into multi-address
thibault-martinez Nov 6, 2023
0fd38b9
Use chain
thibault-martinez Nov 6, 2023
5f9c87c
Merge branch '2.0' into multi-address
thibault-martinez Nov 6, 2023
9ec9d41
Use impl IntoIterator<Item = WeightedAddress>
thibault-martinez Nov 6, 2023
ec0d0fd
Merge branch '2.0' into multi-address
thibault-martinez Nov 7, 2023
e2d3ddf
Impl better display
thibault-martinez Nov 7, 2023
d353eb6
Remove print
thibault-martinez Nov 7, 2023
d317490
Fix common features set
thibault-martinez Nov 7, 2023
00a5309
Update sdk/src/types/block/address/bech32.rs
thibault-martinez Nov 7, 2023
5d2bcd9
document unwrap
thibault-martinez Nov 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sdk/src/client/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub fn bech32_to_hex(bech32: impl ConvertTo<Bech32Address>) -> Result<String> {
Address::Nft(nft) => nft.to_string(),
Address::Anchor(anchor) => anchor.to_string(),
Address::ImplicitAccountCreation(implicit) => implicit.to_string(),
Address::Multi(multi) => multi.to_string(),
Address::Restricted(restricted) => restricted.to_string(),
})
}
Expand Down
20 changes: 14 additions & 6 deletions sdk/src/types/block/address/bech32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use alloc::{
};
use core::str::FromStr;

use crypto::hashes::{blake2b::Blake2b256, Digest};
use derive_more::{AsRef, Deref, Display};
use packable::{
error::{UnpackError, UnpackErrorExt},
Expand All @@ -15,7 +16,10 @@ use packable::{
Packable, PackableExt,
};

use crate::types::block::{address::Address, ConvertTo, Error};
use crate::types::block::{
address::{Address, MultiAddress},
ConvertTo, Error,
};

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deref, Display)]
#[repr(transparent)]
Expand Down Expand Up @@ -172,11 +176,15 @@ impl Bech32Address {

impl core::fmt::Display for Bech32Address {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"{}",
bech32::encode::<bech32::Bech32>(self.hrp.0, &self.inner.pack_to_vec(),).unwrap()
)
let bytes = if self.inner.is_multi() {
std::iter::once(MultiAddress::KIND)
.chain(Blake2b256::digest(self.inner.pack_to_vec()))
.collect()
} else {
self.inner.pack_to_vec()
};

write!(f, "{}", bech32::encode::<bech32::Bech32>(self.hrp.0, &bytes,).unwrap())
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
10 changes: 9 additions & 1 deletion sdk/src/types/block/address/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod anchor;
mod bech32;
mod ed25519;
mod implicit_account_creation;
mod multi;
mod nft;
mod restricted;

Expand All @@ -14,12 +15,14 @@ use alloc::boxed::Box;
use derive_more::{Display, From};
use packable::Packable;

pub(crate) use self::multi::WeightedAddressCount;
pub use self::{
account::AccountAddress,
anchor::AnchorAddress,
bech32::{Bech32Address, Hrp},
ed25519::Ed25519Address,
implicit_account_creation::ImplicitAccountCreationAddress,
multi::MultiAddress,
nft::NftAddress,
restricted::{AddressCapabilities, AddressCapabilityFlag, RestrictedAddress},
};
Expand Down Expand Up @@ -52,6 +55,9 @@ pub enum Address {
/// An implicit account creation address.
#[packable(tag = ImplicitAccountCreationAddress::KIND)]
ImplicitAccountCreation(ImplicitAccountCreationAddress),
/// A multi address.
#[packable(tag = MultiAddress::KIND)]
Multi(MultiAddress),
/// An address with restricted capabilities.
#[packable(tag = RestrictedAddress::KIND)]
#[from(ignore)]
Expand All @@ -72,6 +78,7 @@ impl core::fmt::Debug for Address {
Self::Nft(address) => address.fmt(f),
Self::Anchor(address) => address.fmt(f),
Self::ImplicitAccountCreation(address) => address.fmt(f),
Self::Multi(address) => address.fmt(f),
Self::Restricted(address) => address.fmt(f),
}
}
Expand All @@ -86,11 +93,12 @@ impl Address {
Self::Nft(_) => NftAddress::KIND,
Self::Anchor(_) => AnchorAddress::KIND,
Self::ImplicitAccountCreation(_) => ImplicitAccountCreationAddress::KIND,
Self::Multi(_) => MultiAddress::KIND,
Self::Restricted(_) => RestrictedAddress::KIND,
}
}

crate::def_is_as_opt!(Address: Ed25519, Account, Nft, Anchor, ImplicitAccountCreation, Restricted);
crate::def_is_as_opt!(Address: Ed25519, Account, Nft, Anchor, ImplicitAccountCreation, Multi, Restricted);

/// Tries to create an [`Address`] from a bech32 encoded string.
pub fn try_from_bech32(address: impl AsRef<str>) -> Result<Self, Error> {
Expand Down
236 changes: 236 additions & 0 deletions sdk/src/types/block/address/multi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use alloc::vec::Vec;
use core::{fmt, ops::RangeInclusive};

use derive_more::{AsRef, Display, From};
use iterator_sorted::is_unique_sorted;
use packable::{
bounded::BoundedU8,
error::{UnpackError, UnpackErrorExt},
packer::Packer,
prefix::BoxedSlicePrefix,
unpacker::Unpacker,
Packable,
};

use crate::types::block::{address::Address, Error};

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)]
#[display(fmt = "{address}")]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct WeightedAddress {
/// The unlocked address.
#[packable(verify_with = verify_address)]
address: Address,
/// The weight of the unlocked address.
#[packable(verify_with = verify_weight)]
weight: u8,
}

impl WeightedAddress {
/// Creates a new [`WeightedAddress`].
pub fn new(address: Address, weight: u8) -> Result<WeightedAddress, Error> {
verify_address::<true>(&address, &())?;
verify_weight::<true>(&weight, &())?;

Ok(Self { address, weight })
}

/// Returns the address of the [`WeightedAddress`].
pub fn address(&self) -> &Address {
&self.address
}

/// Returns the weight of the [`WeightedAddress`].
pub fn weight(&self) -> u8 {
self.weight
}
}

fn verify_address<const VERIFY: bool>(address: &Address, _visitor: &()) -> Result<(), Error> {
if VERIFY {
if !matches!(
address,
Address::Ed25519(_) | Address::Account(_) | Address::Nft(_) | Address::Anchor(_)
) {
return Err(Error::InvalidAddressKind(address.kind()));
}
}
Ok(())
}

fn verify_weight<const VERIFY: bool>(weight: &u8, _visitor: &()) -> Result<(), Error> {
if VERIFY && *weight == 0 {
return Err(Error::InvalidAddressWeight(*weight));
} else {
Ok(())
}
}

/// 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)]
pub struct MultiAddress {
/// The weighted unlocked addresses.
addresses: BoxedSlicePrefix<WeightedAddress, WeightedAddressCount>,
/// The threshold that needs to be reached by the unlocked addresses in order to unlock the multi address.
threshold: u16,
}

impl MultiAddress {
/// The [`Address`](crate::types::block::address::Address) kind of a [`MultiAddress`].
pub const KIND: u8 = 40;
/// The allowed range of inner [`Address`]es.
pub const ADDRESSES_COUNT: RangeInclusive<u8> = 1..=10;

/// Creates a new [`MultiAddress`].
#[inline(always)]
pub fn new(addresses: impl IntoIterator<Item = WeightedAddress>, threshold: u16) -> Result<Self, Error> {
let addresses = addresses.into_iter().collect::<Box<[_]>>();

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

let addresses = BoxedSlicePrefix::<WeightedAddress, WeightedAddressCount>::try_from(addresses)
.map_err(Error::InvalidWeightedAddressCount)?;

verify_cumulative_weight::<true>(&addresses, &threshold, &())?;

Ok(Self { addresses, threshold })
}

/// Returns the addresses of a [`MultiAddress`].
#[inline(always)]
pub fn addresses(&self) -> &[WeightedAddress] {
&self.addresses
}

/// Returns the threshold of a [`MultiAddress`].
#[inline(always)]
pub fn threshold(&self) -> u16 {
self.threshold
}
}

impl Packable for MultiAddress {
type UnpackError = Error;
type UnpackVisitor = ();

#[inline]
fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error> {
self.addresses.pack(packer)?;
self.threshold.pack(packer)?;

Ok(())
}

#[inline]
fn unpack<U: Unpacker, const VERIFY: bool>(
unpacker: &mut U,
visitor: &Self::UnpackVisitor,
) -> Result<Self, UnpackError<Self::UnpackError, U::Error>> {
let addresses =
BoxedSlicePrefix::<WeightedAddress, WeightedAddressCount>::unpack::<_, VERIFY>(unpacker, visitor)
.map_packable_err(|e| e.unwrap_item_err_or_else(|e| Error::InvalidWeightedAddressCount(e.into())))?;

verify_addresses::<VERIFY>(&addresses, &()).map_err(UnpackError::Packable)?;

let threshold = u16::unpack::<_, VERIFY>(unpacker, visitor).coerce()?;

verify_threshold::<VERIFY>(&threshold, &()).map_err(UnpackError::Packable)?;
verify_cumulative_weight::<VERIFY>(&addresses, &threshold, &()).map_err(UnpackError::Packable)?;

Ok(Self { addresses, threshold })
}
}

fn verify_addresses<const VERIFY: bool>(addresses: &[WeightedAddress], _visitor: &()) -> Result<(), Error> {
if VERIFY && !is_unique_sorted(addresses.iter().map(WeightedAddress::address)) {
return Err(Error::WeightedAddressesNotUniqueSorted);
} else {
Ok(())
}
}

fn verify_threshold<const VERIFY: bool>(threshold: &u16, _visitor: &()) -> Result<(), Error> {
if VERIFY && *threshold == 0 {
return Err(Error::InvalidMultiAddressThreshold(*threshold));
} else {
Ok(())
}
}

fn verify_cumulative_weight<const VERIFY: bool>(
addresses: &[WeightedAddress],
threshold: &u16,
_visitor: &(),
) -> Result<(), Error> {
if VERIFY {
let cumulative_weight = addresses.iter().map(|address| address.weight as u16).sum::<u16>();

if cumulative_weight < *threshold {
return Err(Error::InvalidMultiAddressCumulativeWeight {
cumulative_weight,
threshold: *threshold,
});
}
}
Ok(())
}

impl fmt::Display for MultiAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"[{}]",
self.addresses()
.iter()
.map(|address| address.to_string())
.collect::<Vec<_>>()
.join(", ")
)
}
}

#[cfg(feature = "serde")]
mod dto {
use serde::{Deserialize, Serialize};

use super::*;

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct MultiAddressDto {
#[serde(rename = "type")]
kind: u8,
addresses: Vec<WeightedAddress>,
threshold: u16,
}

impl From<&MultiAddress> for MultiAddressDto {
fn from(value: &MultiAddress) -> Self {
Self {
kind: MultiAddress::KIND,
addresses: value.addresses.to_vec(),
threshold: value.threshold,
}
}
}

impl TryFrom<MultiAddressDto> for MultiAddress {
type Error = Error;

fn try_from(value: MultiAddressDto) -> Result<Self, Self::Error> {
Self::new(value.addresses, value.threshold)
}
}

crate::impl_serde_typed_dto!(MultiAddress, MultiAddressDto, "multi address");
}
24 changes: 24 additions & 0 deletions sdk/src/types/block/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use primitive_types::U256;

use super::slot::EpochIndex;
use crate::types::block::{
address::WeightedAddressCount,
context_input::RewardContextInputIndex,
input::UtxoInput,
mana::ManaAllotmentCount,
Expand Down Expand Up @@ -70,6 +71,14 @@ pub enum Error {
deposit: u64,
required: u64,
},
InvalidAddressWeight(u8),
InvalidMultiAddressThreshold(u16),
InvalidMultiAddressCumulativeWeight {
cumulative_weight: u16,
threshold: u16,
},
InvalidWeightedAddressCount(<WeightedAddressCount as TryFrom<usize>>::Error),
WeightedAddressesNotUniqueSorted,
InvalidContextInputKind(u8),
InvalidContextInputCount(<ContextInputCount as TryFrom<usize>>::Error),
InvalidFeatureCount(<FeatureCount as TryFrom<usize>>::Error),
Expand Down Expand Up @@ -261,6 +270,21 @@ impl fmt::Display for Error {
"storage deposit return of {deposit} exceeds the original output amount of {amount}"
),
Self::InvalidContextInputCount(count) => write!(f, "invalid context input count: {count}"),
Self::InvalidAddressWeight(w) => write!(f, "invalid address weight: {w}"),
Self::InvalidMultiAddressThreshold(t) => write!(f, "invalid multi address threshold: {t}"),
Self::InvalidMultiAddressCumulativeWeight {
cumulative_weight,
threshold,
} => {
write!(
f,
"invalid multi address cumulative weight {cumulative_weight} < threshold {threshold}"
)
}
Self::InvalidWeightedAddressCount(count) => write!(f, "invalid weighted address count: {count}"),
Self::WeightedAddressesNotUniqueSorted => {
kwek20 marked this conversation as resolved.
Show resolved Hide resolved
write!(f, "weighted addresses are not unique and/or sorted")
}
Self::InvalidContextInputKind(k) => write!(f, "invalid context input kind: {k}"),
Self::InvalidFeatureCount(count) => write!(f, "invalid feature count: {count}"),
Self::InvalidFeatureKind(k) => write!(f, "invalid feature kind: {k}"),
Expand Down
1 change: 1 addition & 0 deletions sdk/tests/types/address/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
mod account;
mod bech32;
mod ed25519;
mod multi;
mod nft;
mod restricted;

Expand Down
Loading
Loading