-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch '2.0' into pr/qrayven/889
- Loading branch information
Showing
17 changed files
with
294 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.