Skip to content
Snippets Groups Projects

feat(runtime): create UDs manually with new call universalDividend.claim_uds

Merged Éloïs requested to merge elois-manual-ud into master
All threads resolved!
Files
2
@@ -17,13 +17,16 @@
#![cfg_attr(not(feature = "std"), no_std)]
mod benchmarking;
mod compute_claim_uds;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
mod types;
mod weights;
pub use pallet::*;
pub use types::*;
pub use weights::WeightInfo;
use frame_support::traits::{tokens::ExistenceRequirement, Currency};
@@ -32,17 +35,15 @@ use sp_arithmetic::{
traits::{One, Saturating, Zero},
};
use sp_runtime::traits::StaticLookup;
use sp_std::prelude::*;
const OFFCHAIN_PREFIX_UD_HISTORY: &[u8] = b"ud::history::";
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_support::traits::StorageVersion;
use frame_support::traits::{StorageVersion, StoredMap};
use frame_system::pallet_prelude::*;
use sp_runtime::traits::Convert;
use sp_std::vec::Vec;
pub type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
@@ -53,7 +54,7 @@ pub mod pallet {
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info]
//#[pallet::without_storage_info]
pub struct Pallet<T>(_);
#[pallet::config]
@@ -64,10 +65,16 @@ pub mod pallet {
type Currency: Currency<Self::AccountId>;
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
#[pallet::constant]
/// Maximum number of past UD revaluations to keep in storage.
type MaxPastReeval: Get<u32>;
/// Somethings that must provide the number of accounts allowed to create the universal dividend
type MembersCount: Get<BalanceOf<Self>>;
/// Somethings that must provide the list of accounts ids allowed to create the universal dividend
type MembersIds: Get<Vec<<Self as frame_system::Config>::AccountId>>;
type MembersStorage: frame_support::traits::StoredMap<Self::AccountId, FirstEligibleUd>;
/// An iterator over all members
type MembersStorageIter: From<Option<Vec<u8>>>
+ Iterator<Item = (Self::AccountId, FirstEligibleUd)>;
#[pallet::constant]
/// Square of the money growth rate per ud reevaluation period
type SquareMoneyGrowthRate: Get<Perbill>;
@@ -93,6 +100,29 @@ pub mod pallet {
#[pallet::getter(fn current_ud)]
pub type CurrentUd<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
#[pallet::type_value]
pub fn DefaultForCurrentUdIndex() -> UdIndex {
1
}
/// Current UD index
#[pallet::storage]
#[pallet::getter(fn ud_index)]
pub type CurrentUdIndex<T: Config> =
StorageValue<_, UdIndex, ValueQuery, DefaultForCurrentUdIndex>;
#[cfg(test)]
#[pallet::storage]
pub type TestMembers<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
FirstEligibleUd,
ValueQuery,
GetDefault,
ConstU32<300_000>,
>;
/// Total quantity of money created by universal dividend (does not take into account the possible destruction of money)
#[pallet::storage]
#[pallet::getter(fn total_money_created)]
@@ -103,6 +133,12 @@ pub mod pallet {
#[pallet::getter(fn next_reeval)]
pub type NextReeval<T: Config> = StorageValue<_, T::BlockNumber, ValueQuery>;
/// Past UD reevaluations
#[pallet::storage]
#[pallet::getter(fn past_reevals)]
pub type PastReevals<T: Config> =
StorageValue<_, BoundedVec<(UdIndex, BalanceOf<T>), T::MaxPastReeval>, ValueQuery>;
// GENESIS
#[pallet::genesis_config]
@@ -110,6 +146,8 @@ pub mod pallet {
pub first_reeval: T::BlockNumber,
pub first_ud: BalanceOf<T>,
pub initial_monetary_mass: BalanceOf<T>,
#[cfg(test)]
pub initial_members: Vec<T::AccountId>,
}
#[cfg(feature = "std")]
@@ -119,6 +157,8 @@ pub mod pallet {
first_reeval: Default::default(),
first_ud: Default::default(),
initial_monetary_mass: Default::default(),
#[cfg(test)]
initial_members: Default::default(),
}
}
}
@@ -132,6 +172,18 @@ pub mod pallet {
<CurrentUd<T>>::put(self.first_ud);
<MonetaryMass<T>>::put(self.initial_monetary_mass);
NextReeval::<T>::put(self.first_reeval);
let mut past_reevals = BoundedVec::default();
past_reevals
.try_push((1, self.first_ud))
.expect("MaxPastReeval should be greather than zero");
PastReevals::<T>::put(past_reevals);
#[cfg(test)]
{
for member in &self.initial_members {
TestMembers::<T>::insert(member, FirstEligibleUd::min());
}
}
}
}
@@ -140,21 +192,21 @@ pub mod pallet {
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(n: T::BlockNumber) -> Weight {
let mut total_weight = T::WeightInfo::on_initialize();
if (n % T::UdCreationPeriod::get()).is_zero() {
let current_members_count = T::MembersCount::get();
let next_reeval = NextReeval::<T>::get();
if n >= next_reeval {
NextReeval::<T>::put(next_reeval.saturating_add(T::UdReevalPeriod::get()));
total_weight += Self::reeval_ud(current_members_count)
+ Self::create_ud(current_members_count, n)
+ T::DbWeight::get().reads_writes(2, 1);
Self::reeval_ud(current_members_count);
Self::create_ud(current_members_count);
T::WeightInfo::on_initialize_ud_reevalued()
} else {
total_weight +=
Self::create_ud(current_members_count, n) + T::DbWeight::get().reads(2);
Self::create_ud(current_members_count);
T::WeightInfo::on_initialize_ud_created()
}
} else {
T::WeightInfo::on_initialize()
}
total_weight
}
}
@@ -165,45 +217,92 @@ pub mod pallet {
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// A new universal dividend is created
/// [amout, members_count]
/// A new universal dividend is created.
NewUdCreated {
amount: BalanceOf<T>,
index: UdIndex,
monetary_mass: BalanceOf<T>,
members_count: BalanceOf<T>,
},
/// The universal dividend has been re-evaluated
/// [new_ud_amount, monetary_mass, members_count]
/// The universal dividend has been re-evaluated.
UdReevalued {
new_ud_amount: BalanceOf<T>,
monetary_mass: BalanceOf<T>,
members_count: BalanceOf<T>,
},
/// DUs were automatically transferred as part of a member removal.
UdsAutoPaidAtRemoval {
count: UdIndex,
total: BalanceOf<T>,
who: T::AccountId,
},
/// A member claimed his UDs.
UdsClaimed {
count: UdIndex,
total: BalanceOf<T>,
who: T::AccountId,
},
}
// ERRORS //
#[pallet::error]
pub enum Error<T> {
/// This account is not allowed to claim UDs.
AccountNotAllowedToClaimUds,
}
// INTERNAL FUNCTIONS //
impl<T: Config> Pallet<T> {
fn create_ud(members_count: BalanceOf<T>, n: T::BlockNumber) -> Weight {
let total_weight: Weight = 0;
fn create_ud(members_count: BalanceOf<T>) {
let ud_amount = <CurrentUd<T>>::get();
let monetary_mass = <MonetaryMass<T>>::get();
let ud_amount = <CurrentUd<T>>::try_get().expect("corrupted storage");
let monetary_mass = <MonetaryMass<T>>::try_get().expect("corrupted storage");
for account_id in T::MembersIds::get() {
T::Currency::deposit_creating(&account_id, ud_amount);
Self::write_ud_history(n, account_id, ud_amount);
}
// Increment ud index
let ud_index = CurrentUdIndex::<T>::mutate(|next_ud_index| {
core::mem::replace(next_ud_index, next_ud_index.saturating_add(1))
});
let new_monetary_mass =
monetary_mass.saturating_add(ud_amount.saturating_mul(members_count));
MonetaryMass::<T>::put(new_monetary_mass);
Self::deposit_event(Event::NewUdCreated {
amount: ud_amount,
index: ud_index,
members_count,
monetary_mass: new_monetary_mass,
});
total_weight
}
fn do_claim_uds(who: &T::AccountId) -> DispatchResultWithPostInfo {
T::MembersStorage::try_mutate_exists(who, |maybe_first_eligible_ud| {
if let Some(FirstEligibleUd(Some(ref mut first_ud_index))) = maybe_first_eligible_ud
{
let current_ud_index = CurrentUdIndex::<T>::get();
if first_ud_index.get() >= current_ud_index {
DispatchResultWithPostInfo::Ok(().into())
} else {
let (uds_count, uds_total) = compute_claim_uds::compute_claim_uds(
current_ud_index,
first_ud_index.get(),
PastReevals::<T>::get().into_iter(),
);
let _ = core::mem::replace(
first_ud_index,
core::num::NonZeroU16::new(current_ud_index)
.expect("unrechable because current_ud_index is never zero."),
);
T::Currency::deposit_creating(who, uds_total);
Self::deposit_event(Event::UdsClaimed {
count: uds_count,
total: uds_total,
who: who.clone(),
});
Ok(().into())
}
} else {
Err(Error::<T>::AccountNotAllowedToClaimUds.into())
}
})
}
fn do_transfer_ud(
origin: OriginFor<T>,
@@ -213,8 +312,7 @@ pub mod pallet {
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let dest = T::Lookup::lookup(dest)?;
let ud_amount =
<CurrentUd<T>>::try_get().map_err(|_| DispatchError::Other("corrupted storage"))?;
let ud_amount = <CurrentUd<T>>::get();
T::Currency::transfer(
&who,
&dest,
@@ -223,12 +321,10 @@ pub mod pallet {
)?;
Ok(().into())
}
fn reeval_ud(members_count: BalanceOf<T>) -> Weight {
let total_weight: Weight = 0;
fn reeval_ud(members_count: BalanceOf<T>) {
let ud_amount = <CurrentUd<T>>::get();
let ud_amount = <CurrentUd<T>>::try_get().expect("corrupted storage");
let monetary_mass = <MonetaryMass<T>>::try_get().expect("corrupted storage");
let monetary_mass = <MonetaryMass<T>>::get();
let new_ud_amount = Self::reeval_ud_formula(
ud_amount,
@@ -240,15 +336,21 @@ pub mod pallet {
),
);
<CurrentUd<T>>::put(new_ud_amount);
CurrentUd::<T>::put(new_ud_amount);
PastReevals::<T>::mutate(|past_reevals| {
if past_reevals.len() == T::MaxPastReeval::get() as usize {
past_reevals.remove(0);
}
past_reevals
.try_push((CurrentUdIndex::<T>::get(), new_ud_amount))
.expect("Unreachable, because we removed an element just before.")
});
Self::deposit_event(Event::UdReevalued {
new_ud_amount,
monetary_mass,
members_count,
});
total_weight
}
fn reeval_ud_formula(
ud_t: BalanceOf<T>,
@@ -265,19 +367,18 @@ pub mod pallet {
// UD(t+1) = UD(t) + c² (M(t+1) / N(t+1)) / (dt/udFrequency)
ud_t + (c_square * monetary_mass) / (members_count * count_uds_beetween_two_reevals)
}
fn write_ud_history(n: T::BlockNumber, account_id: T::AccountId, ud_amount: BalanceOf<T>) {
let mut key = Vec::with_capacity(57);
key.extend_from_slice(OFFCHAIN_PREFIX_UD_HISTORY);
account_id.encode_to(&mut key);
n.encode_to(&mut key);
sp_io::offchain_index::set(key.as_ref(), ud_amount.encode().as_ref());
}
}
// CALLS //
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Claim Universal Dividends
#[pallet::weight(T::WeightInfo::claim_uds(T::MaxPastReeval::get()))]
pub fn claim_uds(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
Self::do_claim_uds(&who)
}
/// Transfer some liquid free balance to another account, in milliUD.
#[pallet::weight(T::WeightInfo::transfer_ud())]
pub fn transfer_ud(
@@ -298,4 +399,31 @@ pub mod pallet {
Self::do_transfer_ud(origin, dest, value, ExistenceRequirement::KeepAlive)
}
}
// PUBLIC FUNCTIONS
impl<T: Config> Pallet<T> {
pub fn init_first_eligible_ud() -> FirstEligibleUd {
CurrentUdIndex::<T>::get().into()
}
pub fn on_removed_member(first_ud_index: UdIndex, who: &T::AccountId) -> Weight {
let current_ud_index = CurrentUdIndex::<T>::get();
if first_ud_index < current_ud_index {
let (uds_count, uds_total) = compute_claim_uds::compute_claim_uds(
current_ud_index,
first_ud_index,
PastReevals::<T>::get().into_iter(),
);
T::Currency::deposit_creating(who, uds_total);
Self::deposit_event(Event::UdsAutoPaidAtRemoval {
count: uds_count,
total: uds_total,
who: who.clone(),
});
T::DbWeight::get().reads_writes(2, 1)
} else {
T::DbWeight::get().reads(1)
}
}
}
}
Loading