Skip to content

Commit

Permalink
Add mana structure and fix max mana checks (#1145)
Browse files Browse the repository at this point in the history
* add mana structure and fix max mana checks

* fix block ID test

* suggestions

* feature gate

* renames

---------

Co-authored-by: Thibault Martinez <[email protected]>
  • Loading branch information
Alexandcoats and thibault-martinez authored Sep 8, 2023
1 parent bba0f9d commit 01e0286
Show file tree
Hide file tree
Showing 18 changed files with 232 additions and 149 deletions.
9 changes: 7 additions & 2 deletions sdk/src/types/block/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,10 @@ pub enum Error {
InvalidTokenSchemeKind(u8),
InvalidTransactionAmountSum(u128),
InvalidTransactionNativeTokensCount(u16),
InvalidManaAllotmentSum(u128),
InvalidManaAllotmentSum {
max: u64,
sum: u128,
},
InvalidUnlockCount(<UnlockCount as TryFrom<usize>>::Error),
InvalidUnlockKind(u8),
InvalidUnlockReference(u16),
Expand Down Expand Up @@ -293,7 +296,9 @@ impl fmt::Display for Error {
Self::InvalidTransactionNativeTokensCount(count) => {
write!(f, "invalid transaction native tokens count: {count}")
}
Self::InvalidManaAllotmentSum(value) => write!(f, "invalid mana allotment sum: {value}"),
Self::InvalidManaAllotmentSum { max, sum } => {
write!(f, "invalid mana allotment sum: {sum} greater than max of {max}")
}
Self::InvalidUnlockCount(count) => write!(f, "invalid unlock count: {count}"),
Self::InvalidUnlockKind(k) => write!(f, "invalid unlock kind: {k}"),
Self::InvalidUnlockReference(index) => {
Expand Down
76 changes: 41 additions & 35 deletions sdk/src/types/block/mana/allotment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@ use packable::{
Packable,
};

use super::THEORETICAL_MANA_MAX;
use crate::types::block::{output::AccountId, Error};
use crate::types::block::{output::AccountId, protocol::ProtocolParameters, 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 ManaAllotment {
pub(crate) account_id: AccountId,
#[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))]
pub(crate) mana: u64,
}

Expand All @@ -34,8 +31,8 @@ impl Ord for ManaAllotment {
}

impl ManaAllotment {
pub fn new(account_id: AccountId, mana: u64) -> Result<Self, Error> {
if mana > THEORETICAL_MANA_MAX {
pub fn new(account_id: AccountId, mana: u64, protocol_params: &ProtocolParameters) -> Result<Self, Error> {
if mana > protocol_params.mana_structure().max_mana() {
return Err(Error::InvalidManaValue(mana));
}
Ok(Self { account_id, mana })
Expand All @@ -52,7 +49,7 @@ impl ManaAllotment {

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

fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error> {
self.account_id.pack(packer)?;
Expand All @@ -65,44 +62,53 @@ impl Packable for ManaAllotment {
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()?;
let account_id = AccountId::unpack::<_, VERIFY>(unpacker, &()).coerce()?;
let mana = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?;

Self::new(account_id, mana).map_err(UnpackError::Packable)
Self::new(account_id, mana, visitor).map_err(UnpackError::Packable)
}
}

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

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

impl<'de> Deserialize<'de> for ManaAllotment {
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 ManaAllotment {
type Error = Error;
use crate::{types::TryFromDto, utils::serde::string};

#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ManaAllotmentDto {
pub account_id: AccountId,
#[serde(with = "string")]
pub mana: u64,
}

fn try_from(value: AllotmentDto) -> Result<Self, Self::Error> {
Self::new(value.account_id, value.mana)
}
impl From<&ManaAllotment> for ManaAllotmentDto {
fn from(value: &ManaAllotment) -> Self {
Self {
account_id: value.account_id,
mana: value.mana,
}
}
}

AllotmentDto::deserialize(d)?
.try_into()
.map_err(serde::de::Error::custom)
impl TryFromDto for ManaAllotment {
type Dto = ManaAllotmentDto;
type Error = Error;

fn try_from_dto_with_params_inner(
dto: Self::Dto,
params: crate::types::ValidationParams<'_>,
) -> Result<Self, Self::Error> {
Ok(if let Some(params) = params.protocol_parameters() {
Self::new(dto.account_id, dto.mana, params)?
} else {
Self {
account_id: dto.account_id,
mana: dto.mana,
}
})
}
}
}
85 changes: 51 additions & 34 deletions sdk/src/types/block/mana/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

mod allotment;
mod protocol;

use alloc::{boxed::Box, collections::BTreeSet, vec::Vec};
use core::ops::RangeInclusive;
Expand All @@ -10,13 +11,10 @@ use derive_more::Deref;
use iterator_sorted::is_unique_sorted;
use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix, Packable};

pub use self::allotment::ManaAllotment;
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 THEORETICAL_MANA_MAX: u64 = u64::MAX >> 1;
#[cfg(feature = "serde")]
pub use self::allotment::dto::ManaAllotmentDto;
pub use self::{allotment::ManaAllotment, protocol::ManaStructure};
use super::{output::AccountId, protocol::ProtocolParameters, Error};

pub(crate) type ManaAllotmentCount =
BoundedU16<{ *ManaAllotments::COUNT_RANGE.start() }, { *ManaAllotments::COUNT_RANGE.end() }>;
Expand All @@ -25,7 +23,7 @@ pub(crate) type ManaAllotmentCount =
#[derive(Clone, Debug, Eq, PartialEq, Deref, Packable)]
#[packable(unpack_error = Error, with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidManaAllotmentCount(p.into())))]
pub struct ManaAllotments(
#[packable(verify_with = verify_allotments)] BoxedSlicePrefix<ManaAllotment, ManaAllotmentCount>,
#[packable(verify_with = verify_mana_allotments)] BoxedSlicePrefix<ManaAllotment, ManaAllotmentCount>,
);

impl ManaAllotments {
Expand All @@ -38,24 +36,25 @@ impl ManaAllotments {

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

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

Ok(Self(allotments))
verify_mana_allotments_unique_sorted(&allotments)?;

Ok(Self(
allotments
.into_boxed_slice()
.try_into()
.map_err(Error::InvalidManaAllotmentCount)?,
))
}

/// Creates a new [`ManaAllotments`] from an ordered set.
pub fn from_set(allotments: BTreeSet<ManaAllotment>) -> Result<Self, Error> {
let allotments = BoxedSlicePrefix::<ManaAllotment, ManaAllotmentCount>::try_from(
allotments.into_iter().collect::<Box<[_]>>(),
)
.map_err(Error::InvalidManaAllotmentCount)?;

verify_allotments_sum(allotments.as_ref())?;

Ok(Self(allotments))
Ok(Self(
allotments
.into_iter()
.collect::<Box<[_]>>()
.try_into()
.map_err(Error::InvalidManaAllotmentCount)?,
))
}

/// Gets a reference to an [`ManaAllotment`], if one exists, using an [`AccountId`].
Expand All @@ -65,27 +64,45 @@ impl ManaAllotments {
}
}

fn verify_allotments<const VERIFY: bool>(allotments: &[ManaAllotment], _visitor: &()) -> Result<(), Error> {
fn verify_mana_allotments<const VERIFY: bool>(
allotments: &[ManaAllotment],
protocol_params: &ProtocolParameters,
) -> Result<(), Error> {
if VERIFY {
if !is_unique_sorted(allotments.iter()) {
return Err(Error::ManaAllotmentsNotUniqueSorted);
}
verify_allotments_sum(allotments)?;
verify_mana_allotments_unique_sorted(allotments)?;
verify_mana_allotments_sum(allotments, protocol_params)?;
}

Ok(())
}

fn verify_allotments_sum<'a>(allotments: impl IntoIterator<Item = &'a ManaAllotment>) -> Result<(), Error> {
fn verify_mana_allotments_unique_sorted<'a>(
allotments: impl IntoIterator<Item = &'a ManaAllotment>,
) -> Result<(), Error> {
if !is_unique_sorted(allotments.into_iter()) {
return Err(Error::ManaAllotmentsNotUniqueSorted);
}
Ok(())
}

pub(crate) fn verify_mana_allotments_sum<'a>(
allotments: impl IntoIterator<Item = &'a ManaAllotment>,
protocol_params: &ProtocolParameters,
) -> Result<(), Error> {
let mut mana_sum: u64 = 0;
let max_mana = protocol_params.mana_structure().max_mana();

for ManaAllotment { mana, .. } in allotments {
mana_sum = mana_sum
.checked_add(*mana)
.ok_or(Error::InvalidManaAllotmentSum(mana_sum as u128 + *mana as u128))?;

if mana_sum > THEORETICAL_MANA_MAX {
return Err(Error::InvalidManaAllotmentSum(mana_sum as u128));
mana_sum = mana_sum.checked_add(*mana).ok_or(Error::InvalidManaAllotmentSum {
sum: mana_sum as u128 + *mana as u128,
max: max_mana,
})?;

if mana_sum > max_mana {
return Err(Error::InvalidManaAllotmentSum {
sum: mana_sum as u128,
max: max_mana,
});
}
}

Expand Down
67 changes: 67 additions & 0 deletions sdk/src/types/block/mana/protocol.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use getset::CopyGetters;
use packable::{prefix::BoxedSlicePrefix, Packable};

use crate::types::block::{slot::EpochIndex, Error};

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, CopyGetters)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
#[packable(unpack_error = Error)]
#[getset(get_copy = "pub")]
pub struct ManaStructure {
/// The number of bits used to represent Mana.
pub(crate) mana_bits_count: u8,
/// The amount of potential Mana generated by 1 IOTA in 1 slot.
pub(crate) mana_generation_rate: u8,
/// The scaling of `mana_generation_rate` expressed as an exponent of 2.
pub(crate) mana_generation_rate_exponent: u8,
/// A lookup table of epoch index diff to mana decay factor.
#[packable(unpack_error_with = |_| Error::InvalidManaDecayFactors)]
#[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::boxed_slice_prefix"))]
#[getset(skip)]
pub(crate) mana_decay_factors: BoxedSlicePrefix<u32, u16>,
/// The scaling of `mana_decay_factors` expressed as an exponent of 2.
pub(crate) mana_decay_factors_exponent: u8,
/// An integer approximation of the sum of decay over epochs.
pub(crate) mana_decay_factor_epochs_sum: u32,
/// The scaling of `mana_decay_factor_epochs_sum` expressed as an exponent of 2.
pub(crate) mana_decay_factor_epochs_sum_exponent: u8,
}

impl ManaStructure {
/// Returns the mana decay factors slice.
pub fn mana_decay_factors(&self) -> &[u32] {
&self.mana_decay_factors
}

/// Returns the mana decay factor for the given epoch index.
pub fn mana_decay_factor_at(&self, epoch_index: EpochIndex) -> Option<u32> {
self.mana_decay_factors.get(*epoch_index as usize).copied()
}

/// Returns the max mana that can exist with the mana bits defined.
pub fn max_mana(&self) -> u64 {
(1 << self.mana_bits_count) - 1
}
}

impl Default for ManaStructure {
fn default() -> Self {
// TODO: use actual values
Self {
mana_bits_count: 10,
mana_generation_rate: Default::default(),
mana_generation_rate_exponent: Default::default(),
mana_decay_factors: Default::default(),
mana_decay_factors_exponent: Default::default(),
mana_decay_factor_epochs_sum: Default::default(),
mana_decay_factor_epochs_sum_exponent: Default::default(),
}
}
}
Loading

0 comments on commit 01e0286

Please sign in to comment.