use cfg_traits::{Seconds, TimeAsSecs};
use frame_support::{pallet_prelude::RuntimeDebug, traits::Get, BoundedVec};
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{EnsureAdd, EnsureSub, Zero},
DispatchError, DispatchResult,
};
use sp_std::{cmp::Ordering, marker::PhantomData, vec::Vec};
#[derive(Encode, Decode, Clone, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(MaxElems))]
pub struct PortfolioValuation<Balance, ElemId, MaxElems: Get<u32>> {
value: Balance,
last_updated: Seconds,
values: BoundedVec<(ElemId, Balance), MaxElems>,
}
impl<Balance, ElemId, MaxElems> PortfolioValuation<Balance, ElemId, MaxElems>
where
Balance: EnsureAdd + EnsureSub + Ord + Copy,
ElemId: Eq,
MaxElems: Get<u32>,
{
pub fn new(when: Seconds) -> Self {
Self {
value: Balance::zero(),
last_updated: when,
values: BoundedVec::default(),
}
}
pub fn from_values(
when: Seconds,
values: Vec<(ElemId, Balance)>,
) -> Result<Self, DispatchError> {
Ok(Self {
value: values.iter().try_fold(
Balance::zero(),
|sum, (_, value)| -> Result<Balance, DispatchError> { Ok(sum.ensure_add(*value)?) },
)?,
values: values
.try_into()
.map_err(|_| DispatchError::Other("Max portfolio size reached"))?,
last_updated: when,
})
}
pub fn value(&self) -> Balance {
self.value
}
pub fn last_updated(&self) -> Seconds {
self.last_updated
}
pub fn value_of(&self, id: ElemId) -> Option<Balance> {
self.values
.iter()
.find(|(elem_id, _)| *elem_id == id)
.map(|(_, balance)| *balance)
}
pub fn insert_elem(&mut self, id: ElemId, pv: Balance) -> DispatchResult {
self.values
.try_push((id, pv))
.map_err(|_| DispatchError::Other("Max portfolio size reached"))?;
self.value.ensure_add_assign(pv)?;
Ok(())
}
pub fn update_elem(&mut self, id: ElemId, new_pv: Balance) -> DispatchResult {
let old_pv = self
.values
.iter_mut()
.find(|(elem_id, _)| *elem_id == id)
.map(|(_, value)| value)
.ok_or(DispatchError::CannotLookup)?;
match new_pv.cmp(old_pv) {
Ordering::Greater => {
let diff = new_pv.ensure_sub(*old_pv)?;
self.value.ensure_add_assign(diff)?;
}
Ordering::Less => {
let diff = old_pv.ensure_sub(new_pv)?;
self.value.ensure_sub_assign(diff)?;
}
Ordering::Equal => (),
};
*old_pv = new_pv;
Ok(())
}
pub fn remove_elem(&mut self, elem_id: ElemId) -> DispatchResult {
let index = self
.values
.iter()
.position(|(id, _)| *id == elem_id)
.ok_or(DispatchError::CannotLookup)?;
let (_, pv) = self.values.swap_remove(index);
self.value.ensure_sub_assign(pv)?;
Ok(())
}
}
pub struct InitialPortfolioValuation<Timer>(PhantomData<Timer>);
impl<Balance, ElemId, MaxElems, Timer> Get<PortfolioValuation<Balance, ElemId, MaxElems>>
for InitialPortfolioValuation<Timer>
where
Balance: Zero + EnsureAdd + EnsureSub + Ord + Copy,
MaxElems: Get<u32>,
Timer: TimeAsSecs,
ElemId: Eq,
{
fn get() -> PortfolioValuation<Balance, ElemId, MaxElems> {
PortfolioValuation::new(<Timer as TimeAsSecs>::now())
}
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
pub enum PortfolioValuationUpdateType {
Exact,
Inexact,
}
#[cfg(test)]
mod tests {
use frame_support::assert_ok;
use sp_core::ConstU32;
use super::*;
#[test]
fn general_usage() {
let mut portfolio = PortfolioValuation::<u128, u64, ConstU32<3>>::new(10);
assert_ok!(portfolio.insert_elem(1, 100));
assert_ok!(portfolio.insert_elem(2, 200));
assert_ok!(portfolio.insert_elem(3, 300));
assert_eq!(portfolio.value(), 600);
assert_ok!(portfolio.update_elem(1, 300));
assert_eq!(portfolio.value(), 800);
assert_ok!(portfolio.update_elem(2, 200));
assert_eq!(portfolio.value(), 800);
assert_ok!(portfolio.update_elem(3, 100));
assert_eq!(portfolio.value(), 600);
assert_eq!(portfolio.value_of(1), Some(300));
assert_eq!(portfolio.value_of(2), Some(200));
assert_eq!(portfolio.value_of(3), Some(100));
assert_ok!(portfolio.remove_elem(1));
assert_ok!(portfolio.remove_elem(2));
assert_ok!(portfolio.remove_elem(3));
assert_eq!(portfolio.value(), 0);
}
}