use std::collections::{BTreeMap, HashMap};
use crate::cell::*;
use crate::dict::{AugDictExtra, Dict};
use crate::error::Error;
use crate::num::{Tokens, VarUint248};
#[derive(Debug, Clone, Eq, PartialEq, Store, Load)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[must_use]
pub struct CurrencyCollection {
pub tokens: Tokens,
pub other: ExtraCurrencyCollection,
}
impl Default for CurrencyCollection {
#[inline]
fn default() -> Self {
Self::ZERO
}
}
impl CurrencyCollection {
pub const ZERO: Self = Self {
tokens: Tokens::ZERO,
other: ExtraCurrencyCollection::new(),
};
pub const fn new(tokens: u128) -> Self {
Self {
tokens: Tokens::new(tokens),
other: ExtraCurrencyCollection::new(),
}
}
pub fn is_zero(&self) -> bool {
self.tokens.is_zero() && self.other.is_empty()
}
pub const fn bit_len(&self) -> u16 {
self.tokens.unwrap_bit_len() + 1
}
pub fn checked_add(&self, other: &Self) -> Result<Self, Error> {
Ok(Self {
tokens: match self.tokens.checked_add(other.tokens) {
Some(value) => value,
None => return Err(Error::IntOverflow),
},
other: ok!(self.other.checked_add(&other.other)),
})
}
pub fn checked_sub(&self, other: &Self) -> Result<Self, Error> {
Ok(Self {
tokens: match self.tokens.checked_sub(other.tokens) {
Some(value) => value,
None => return Err(Error::IntOverflow),
},
other: ok!(self.other.checked_sub(&other.other)),
})
}
pub fn try_add_assign_tokens(&mut self, other: Tokens) -> Result<(), Error> {
match self.tokens.checked_add(other) {
Some(value) => {
self.tokens = value;
Ok(())
}
None => Err(Error::IntOverflow),
}
}
pub fn try_sub_assign_tokens(&mut self, other: Tokens) -> Result<(), Error> {
match self.tokens.checked_sub(other) {
Some(value) => {
self.tokens = value;
Ok(())
}
None => Err(Error::IntOverflow),
}
}
pub fn try_add_assign(&mut self, other: &Self) -> Result<(), Error> {
*self = ok!(self.checked_add(other));
Ok(())
}
pub fn try_sub_assign(&mut self, other: &Self) -> Result<(), Error> {
*self = ok!(self.checked_sub(other));
Ok(())
}
pub fn checked_clamp(&self, other: &Self) -> Result<Self, Error> {
Ok(Self {
other: ok!(self.other.checked_clamp(&other.other)),
tokens: std::cmp::min(self.tokens, other.tokens),
})
}
}
impl From<Tokens> for CurrencyCollection {
#[inline]
fn from(tokens: Tokens) -> Self {
Self {
tokens,
other: ExtraCurrencyCollection::new(),
}
}
}
impl ExactSize for CurrencyCollection {
#[inline]
fn exact_size(&self) -> Size {
self.tokens.exact_size() + self.other.exact_size()
}
}
impl AugDictExtra for CurrencyCollection {
fn comp_add(
left: &mut CellSlice,
right: &mut CellSlice,
b: &mut CellBuilder,
cx: &dyn CellContext,
) -> Result<(), Error> {
let left = ok!(Self::load_from(left));
let right = ok!(Self::load_from(right));
ok!(left.checked_add(&right)).store_into(b, cx)
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for CurrencyCollection {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Self {
tokens: u.arbitrary()?,
other: u.arbitrary()?,
})
}
#[inline]
fn size_hint(depth: usize) -> (usize, Option<usize>) {
Self::try_size_hint(depth).unwrap_or_default()
}
#[inline]
fn try_size_hint(
depth: usize,
) -> Result<(usize, Option<usize>), arbitrary::MaxRecursionReached> {
Ok(arbitrary::size_hint::and(
<Tokens as arbitrary::Arbitrary>::try_size_hint(depth)?,
<ExtraCurrencyCollection as arbitrary::Arbitrary>::try_size_hint(depth)?,
))
}
}
#[derive(Debug, Clone, Eq, PartialEq, Store, Load)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[must_use]
#[repr(transparent)]
pub struct ExtraCurrencyCollection(Dict<u32, VarUint248>);
impl Default for ExtraCurrencyCollection {
#[inline]
fn default() -> Self {
Self(Dict::new())
}
}
impl ExtraCurrencyCollection {
pub const fn new() -> Self {
Self(Dict::new())
}
pub const fn from_raw(dict: Option<Cell>) -> Self {
Self(Dict::from_raw(dict))
}
pub fn try_from_iter<I>(iter: I) -> Result<Self, Error>
where
I: IntoIterator<Item = (u32, VarUint248)>,
{
let mut values = iter.into_iter().collect::<Box<[_]>>();
values.sort_unstable_by_key(|(id, _)| *id);
Dict::try_from_sorted_slice(&values).map(Self)
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
pub const fn as_dict(&self) -> &Dict<u32, VarUint248> {
&self.0
}
#[inline]
pub fn into_dict(self) -> Dict<u32, VarUint248> {
self.0
}
#[inline]
pub fn as_dict_mut(&mut self) -> &mut Dict<u32, VarUint248> {
&mut self.0
}
pub fn normalized(&self) -> Result<Self, Error> {
let mut result = self.clone();
for entry in self.0.iter() {
let (currency_id, other) = ok!(entry);
if other.is_zero() {
ok!(result.0.remove(currency_id));
}
}
Ok(result)
}
pub fn normalize(&mut self) -> Result<(), Error> {
let mut result = self.clone();
for entry in self.0.iter() {
let (currency_id, other) = ok!(entry);
if other.is_zero() {
ok!(result.0.remove(currency_id));
}
}
*self = result;
Ok(())
}
pub fn checked_add(&self, other: &Self) -> Result<Self, Error> {
let mut result = self.clone();
for entry in other.0.iter() {
let (currency_id, other) = ok!(entry);
let existing = ok!(result.as_dict().get(currency_id)).unwrap_or_default();
match existing.checked_add(&other) {
Some(value) if value.is_zero() => {
ok!(result.0.remove(currency_id));
}
Some(ref value) => {
ok!(result.0.set(currency_id, value));
}
None => return Err(Error::IntOverflow),
};
}
Ok(result)
}
pub fn checked_sub(&self, other: &Self) -> Result<Self, Error> {
let mut result = self.clone();
for entry in other.0.iter() {
let (currency_id, other) = ok!(entry);
let existing = ok!(result.as_dict().get(currency_id)).unwrap_or_default();
match existing.checked_sub(&other) {
Some(value) if value.is_zero() => {
ok!(result.0.remove(currency_id));
}
Some(ref value) => {
ok!(result.0.set(currency_id, value));
}
None => return Err(Error::IntOverflow),
};
}
Ok(result)
}
pub fn checked_clamp(&self, other: &Self) -> Result<Self, Error> {
let mut result = self.clone();
for entry in self.0.iter() {
let (currency_id, balance) = ok!(entry);
match ok!(other.0.get(currency_id)) {
Some(other_balance) => {
if balance > other_balance {
ok!(result.0.set(currency_id, other_balance));
}
}
None if !balance.is_zero() => {
ok!(result.0.remove_raw(currency_id));
}
None => {}
}
}
Ok(result)
}
}
impl<S> TryFrom<&'_ HashMap<u32, VarUint248, S>> for ExtraCurrencyCollection
where
S: std::hash::BuildHasher,
{
type Error = Error;
fn try_from(value: &'_ HashMap<u32, VarUint248, S>) -> Result<Self, Self::Error> {
let mut values = value.iter().collect::<Box<[_]>>();
values.sort_unstable_by_key(|(id, _)| *id);
Dict::try_from_sorted_slice(&values).map(Self)
}
}
impl<S> TryFrom<HashMap<u32, VarUint248, S>> for ExtraCurrencyCollection
where
S: std::hash::BuildHasher,
{
type Error = Error;
fn try_from(value: HashMap<u32, VarUint248, S>) -> Result<Self, Self::Error> {
let mut values = value.into_iter().collect::<Box<[_]>>();
values.sort_unstable_by_key(|(id, _)| *id);
Dict::try_from_sorted_slice(&values).map(Self)
}
}
impl TryFrom<&'_ BTreeMap<u32, VarUint248>> for ExtraCurrencyCollection {
type Error = Error;
#[inline]
fn try_from(value: &'_ BTreeMap<u32, VarUint248>) -> Result<Self, Self::Error> {
Dict::try_from_btree(value).map(Self)
}
}
impl TryFrom<BTreeMap<u32, VarUint248>> for ExtraCurrencyCollection {
type Error = Error;
#[inline]
fn try_from(value: BTreeMap<u32, VarUint248>) -> Result<Self, Self::Error> {
Self::try_from(&value)
}
}
impl From<Dict<u32, VarUint248>> for ExtraCurrencyCollection {
#[inline]
fn from(value: Dict<u32, VarUint248>) -> Self {
Self(value)
}
}
impl From<ExtraCurrencyCollection> for Dict<u32, VarUint248> {
#[inline]
fn from(value: ExtraCurrencyCollection) -> Self {
value.0
}
}
impl ExactSize for ExtraCurrencyCollection {
#[inline]
fn exact_size(&self) -> Size {
self.0.exact_size()
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for ExtraCurrencyCollection {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let size = u.arbitrary::<u8>()?;
if size <= 128 {
Ok(Self(Dict::new()))
} else {
let mut dict = Dict::<u32, VarUint248>::new();
for _ in 128..size {
dict.set(u.arbitrary::<u32>()?, u.arbitrary::<VarUint248>()?)
.unwrap();
}
Ok(Self(dict))
}
}
fn size_hint(_: usize) -> (usize, Option<usize>) {
(1, None)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cell::Lazy;
use crate::models::{DepthBalanceInfo, ShardAccount, ShardAccounts};
fn _cc_must_use() -> anyhow::Result<()> {
#[expect(unused_must_use)]
{
CurrencyCollection::new(10).checked_add(&CurrencyCollection::ZERO)?;
}
#[expect(unused_must_use)]
{
ExtraCurrencyCollection::new().checked_add(&ExtraCurrencyCollection::new())?;
}
Ok(())
}
#[test]
fn cc_math() -> anyhow::Result<()> {
let value = CurrencyCollection {
tokens: Tokens::new(1),
other: ExtraCurrencyCollection::try_from_iter([(1, VarUint248::new(1000))])?,
};
let mut new_value = CurrencyCollection::ZERO;
new_value.try_add_assign(&value)?;
assert_eq!(new_value, value);
new_value.try_add_assign(&value)?;
assert_ne!(new_value, value);
let extra = new_value.other.as_dict().get(1)?;
assert_eq!(extra, Some(VarUint248::new(2000)));
Ok(())
}
#[test]
fn aug_dict() -> anyhow::Result<()> {
let mut accounts = ShardAccounts::new();
accounts.set(
HashBytes([0; 32]),
DepthBalanceInfo {
split_depth: 0,
balance: CurrencyCollection {
tokens: Tokens::new(500_000_000_000),
other: ExtraCurrencyCollection::new(),
},
},
ShardAccount {
account: Lazy::from_raw(Cell::empty_cell())?,
last_trans_lt: 0,
last_trans_hash: Default::default(),
},
)?;
accounts.set(
HashBytes([1; 32]),
DepthBalanceInfo {
split_depth: 0,
balance: CurrencyCollection {
tokens: Tokens::new(500_000_000_000),
other: ExtraCurrencyCollection::try_from_iter([(2, VarUint248::new(1000))])?,
},
},
ShardAccount {
account: Lazy::from_raw(Cell::empty_cell())?,
last_trans_lt: 0,
last_trans_hash: Default::default(),
},
)?;
Ok(())
}
}