Skip to content

Commit

Permalink
Merge branch '2.0' into pr/qrayven/889
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandcoats authored Aug 3, 2023
2 parents b3c23b9 + a1e2802 commit ec16552
Show file tree
Hide file tree
Showing 17 changed files with 294 additions and 13 deletions.
13 changes: 11 additions & 2 deletions sdk/src/types/block/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crypto::Error as CryptoError;
use prefix_hex::Error as HexError;
use primitive_types::U256;

use super::mana::AllotmentCount;
use crate::types::block::{
input::UtxoInput,
output::{
Expand All @@ -30,6 +31,7 @@ pub enum Error {
CreatedAmountOverflow,
CreatedNativeTokensAmountOverflow,
Crypto(CryptoError),
DuplicateAllotment(AccountId),
DuplicateSignatureUnlock(u16),
DuplicateUtxo(UtxoInput),
ExpirationUnlockConditionZero,
Expand All @@ -55,13 +57,15 @@ pub enum Error {
InvalidBech32Hrp(String),
InvalidBlockLength(usize),
InvalidStateMetadataLength(<StateMetadataLength as TryFrom<usize>>::Error),
InvalidManaValue(u64),
InvalidMetadataFeatureLength(<MetadataFeatureLength as TryFrom<usize>>::Error),
InvalidNativeTokenCount(<NativeTokenCount as TryFrom<usize>>::Error),
InvalidNetworkName(FromUtf8Error),
InvalidNftIndex(<UnlockIndex as TryFrom<u16>>::Error),
InvalidOutputAmount(u64),
InvalidOutputCount(<OutputCount as TryFrom<usize>>::Error),
InvalidOutputKind(u8),
InvalidAllotmentCount(<AllotmentCount as TryFrom<usize>>::Error),
// TODO this would now need to be generic, not sure if possible.
// https://github.com/iotaledger/iota-sdk/issues/647
// InvalidParentCount(<BoundedU8 as TryFrom<usize>>::Error),
Expand All @@ -81,6 +85,7 @@ pub enum Error {
InvalidTokenSchemeKind(u8),
InvalidTransactionAmountSum(u128),
InvalidTransactionNativeTokensCount(u16),
InvalidAllotmentManaSum(u128),
InvalidUnlockCount(<UnlockCount as TryFrom<usize>>::Error),
InvalidUnlockKind(u8),
InvalidUnlockReference(u16),
Expand Down Expand Up @@ -125,6 +130,7 @@ impl fmt::Display for Error {
Self::CreatedAmountOverflow => write!(f, "created amount overflow"),
Self::CreatedNativeTokensAmountOverflow => write!(f, "created native tokens amount overflow"),
Self::Crypto(e) => write!(f, "cryptographic error: {e}"),
Self::DuplicateAllotment(id) => write!(f, "duplicate allotment, account ID: {id}"),
Self::DuplicateSignatureUnlock(index) => {
write!(f, "duplicate signature unlock at index: {index}")
}
Expand Down Expand Up @@ -181,16 +187,18 @@ impl fmt::Display for Error {
Self::InvalidInputCount(count) => write!(f, "invalid input count: {count}"),
Self::InvalidInputOutputIndex(index) => write!(f, "invalid input or output index: {index}"),
Self::InvalidBlockLength(length) => write!(f, "invalid block length {length}"),
Self::InvalidStateMetadataLength(length) => write!(f, "invalid state metadata length {length}"),
Self::InvalidStateMetadataLength(length) => write!(f, "invalid state metadata length: {length}"),
Self::InvalidManaValue(mana) => write!(f, "invalid mana value: {mana}"),
Self::InvalidMetadataFeatureLength(length) => {
write!(f, "invalid metadata feature length {length}")
write!(f, "invalid metadata feature length: {length}")
}
Self::InvalidNativeTokenCount(count) => write!(f, "invalid native token count: {count}"),
Self::InvalidNetworkName(err) => write!(f, "invalid network name: {err}"),
Self::InvalidNftIndex(index) => write!(f, "invalid nft index: {index}"),
Self::InvalidOutputAmount(amount) => write!(f, "invalid output amount: {amount}"),
Self::InvalidOutputCount(count) => write!(f, "invalid output count: {count}"),
Self::InvalidOutputKind(k) => write!(f, "invalid output kind: {k}"),
Self::InvalidAllotmentCount(count) => write!(f, "invalid allotment count: {count}"),
Self::InvalidParentCount => {
write!(f, "invalid parents count")
}
Expand Down Expand Up @@ -219,6 +227,7 @@ impl fmt::Display for Error {
Self::InvalidTransactionNativeTokensCount(count) => {
write!(f, "invalid transaction native tokens count: {count}")
}
Self::InvalidAllotmentManaSum(value) => write!(f, "invalid allotment mana sum: {value}"),
Self::InvalidUnlockCount(count) => write!(f, "invalid unlock count: {count}"),
Self::InvalidUnlockKind(k) => write!(f, "invalid unlock kind: {k}"),
Self::InvalidUnlockReference(index) => {
Expand Down
94 changes: 94 additions & 0 deletions sdk/src/types/block/mana/allotment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use packable::{
error::{UnpackError, UnpackErrorExt},
packer::Packer,
unpacker::Unpacker,
Packable,
};

use super::MAX_THEORETICAL_MANA;
use crate::types::block::{output::AccountId, Error};

/// An allotment of Mana which will be added upon commitment of the slot in which the containing transaction was issued,
/// in the form of Block Issuance Credits to the account.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(rename_all = "camelCase"))]
pub struct Allotment {
pub(crate) account_id: AccountId,
#[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))]
pub(crate) mana: u64,
}

impl Allotment {
pub fn new(account_id: AccountId, mana: u64) -> Result<Self, Error> {
if mana > MAX_THEORETICAL_MANA {
return Err(Error::InvalidManaValue(mana));
}
Ok(Self { account_id, mana })
}

pub fn account_id(&self) -> &AccountId {
&self.account_id
}

pub fn mana(&self) -> u64 {
self.mana
}
}

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

fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error> {
self.account_id.pack(packer)?;
self.mana.pack(packer)?;
Ok(())
}

fn unpack<U: Unpacker, const VERIFY: bool>(
unpacker: &mut U,
visitor: &Self::UnpackVisitor,
) -> Result<Self, UnpackError<Self::UnpackError, U::Error>> {
let account_id = AccountId::unpack::<_, VERIFY>(unpacker, visitor).coerce()?;
let mana = u64::unpack::<_, VERIFY>(unpacker, visitor).coerce()?;
Ok(Self { account_id, mana })
}
}

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

use super::*;
use crate::utils::serde::string;

impl<'de> Deserialize<'de> for Allotment {
fn deserialize<D>(d: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct AllotmentDto {
account_id: AccountId,
#[serde(with = "string")]
mana: u64,
}

impl TryFrom<AllotmentDto> for Allotment {
type Error = Error;

fn try_from(value: AllotmentDto) -> Result<Self, Self::Error> {
Self::new(value.account_id, value.mana)
}
}

AllotmentDto::deserialize(d)?
.try_into()
.map_err(serde::de::Error::custom)
}
}
}
95 changes: 95 additions & 0 deletions sdk/src/types/block/mana/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

mod allotment;

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

use derive_more::Deref;
use hashbrown::HashSet;
use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix, Packable};

pub use self::allotment::Allotment;
use super::{output::AccountId, Error};

/// The number of bits that a given mana value can use, excluding the sign bit.
pub const MANA_BITS: u64 = 63;
/// Equivalent to `2^MANA_BITS - 1`
pub const MAX_THEORETICAL_MANA: u64 = u64::MAX >> 1;

pub(crate) type AllotmentCount = BoundedU16<{ *Allotments::COUNT_RANGE.start() }, { *Allotments::COUNT_RANGE.end() }>;

/// A list of [`Allotment`]s with unique [`AccountId`]s.
#[derive(Clone, Debug, Eq, PartialEq, Deref, Packable)]
#[packable(unpack_error = Error, with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidAllotmentCount(p.into())))]
pub struct Allotments(#[packable(verify_with = verify_allotments)] BoxedSlicePrefix<Allotment, AllotmentCount>);

fn verify_allotments<const VERIFY: bool>(allotments: &[Allotment], _visitor: &()) -> Result<(), Error> {
if VERIFY {
let mut mana_sum: u64 = 0;
let mut unique_ids = HashSet::with_capacity(allotments.len());
for Allotment { account_id, mana } in allotments.iter() {
mana_sum = mana_sum
.checked_add(*mana)
.ok_or(Error::InvalidAllotmentManaSum(mana_sum as u128 + *mana as u128))?;

if mana_sum > MAX_THEORETICAL_MANA {
return Err(Error::InvalidAllotmentManaSum(mana_sum as u128));
}

if !unique_ids.insert(account_id) {
return Err(Error::DuplicateAllotment(*account_id));
}
}
}

Ok(())
}

impl TryFrom<Vec<Allotment>> for Allotments {
type Error = Error;

#[inline(always)]
fn try_from(allotments: Vec<Allotment>) -> Result<Self, Self::Error> {
Self::from_vec(allotments)
}
}

impl IntoIterator for Allotments {
type Item = Allotment;
type IntoIter = alloc::vec::IntoIter<Self::Item>;

fn into_iter(self) -> Self::IntoIter {
Vec::from(Into::<Box<[Allotment]>>::into(self.0)).into_iter()
}
}

impl Allotments {
/// The minimum number of allotments of a transaction.
pub const COUNT_MIN: u16 = 1;
/// The maximum number of allotments of a transaction.
pub const COUNT_MAX: u16 = 128;
/// The range of valid numbers of allotments of a transaction.
pub const COUNT_RANGE: RangeInclusive<u16> = Self::COUNT_MIN..=Self::COUNT_MAX; // [1..128]

/// Creates a new [`Allotments`] from a vec.
pub fn from_vec(allotments: Vec<Allotment>) -> Result<Self, Error> {
let allotments = BoxedSlicePrefix::<Allotment, AllotmentCount>::try_from(allotments.into_boxed_slice())
.map_err(Error::InvalidAllotmentCount)?;

verify_allotments::<true>(&allotments, &())?;

Ok(Self(allotments))
}

/// Gets a reference to an [`Allotment`], if one exists, using an [`AccountId`].
#[inline(always)]
pub fn get(&self, account_id: &AccountId) -> Option<&Allotment> {
self.0
.iter()
.position(|a| a.account_id() == account_id)
// PANIC: indexation is fine since the index has been found.
.map(|index| &self.0[index])
}
}
2 changes: 2 additions & 0 deletions sdk/src/types/block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub mod core;
pub mod helper;
/// A module that provides types and syntactic validations of inputs.
pub mod input;
/// A module that provides types and syntactic validations of mana.
pub mod mana;
/// A module that provides types and syntactic validations of outputs.
pub mod output;
/// A module that provides types and syntactic validations of parents.
Expand Down
4 changes: 2 additions & 2 deletions sdk/src/types/block/output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,11 @@ use crate::types::{

/// The maximum number of outputs of a transaction.
pub const OUTPUT_COUNT_MAX: u16 = 128;
/// The range of valid numbers of outputs of a transaction .
/// The range of valid numbers of outputs of a transaction.
pub const OUTPUT_COUNT_RANGE: RangeInclusive<u16> = 1..=OUTPUT_COUNT_MAX; // [1..128]
/// The maximum index of outputs of a transaction.
pub const OUTPUT_INDEX_MAX: u16 = OUTPUT_COUNT_MAX - 1; // 127
/// The range of valid indices of outputs of a transaction .
/// The range of valid indices of outputs of a transaction.
pub const OUTPUT_INDEX_RANGE: RangeInclusive<u16> = 0..=OUTPUT_INDEX_MAX; // [0..127]

#[derive(Copy, Clone)]
Expand Down
Loading

0 comments on commit ec16552

Please sign in to comment.