Skip to content
Snippets Groups Projects
lib.rs 15.64 KiB
// Copyright 2021 Axiom-Team
//
// This file is part of Substrate-Libre-Currency.
//
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Substrate-Libre-Currency is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.

#![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};
use sp_arithmetic::{
    per_things::Perbill,
    traits::{One, Saturating, Zero},
};
use sp_runtime::traits::StaticLookup;

#[frame_support::pallet]
pub mod pallet {
    use super::*;
    use frame_support::pallet_prelude::*;
    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;

    /// The current storage version.
    const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);

    #[pallet::pallet]
    #[pallet::generate_store(pub(super) trait Store)]
    #[pallet::storage_version(STORAGE_VERSION)]
    //#[pallet::without_storage_info]
    pub struct Pallet<T>(_);

    #[pallet::config]
    pub trait Config: frame_system::Config {
        // BlockNumber into Balance converter
        type BlockNumberIntoBalance: Convert<Self::BlockNumber, BalanceOf<Self>>;
        // The currency
        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 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>;
        #[pallet::constant]
        /// Universal dividend creation period
        type UdCreationPeriod: Get<Self::BlockNumber>;
        #[pallet::constant]
        /// Universal dividend reevaluation period (in number of blocks)
        type UdReevalPeriod: Get<Self::BlockNumber>;
        #[pallet::constant]
        /// The number of units to divide the amounts expressed in number of UDs
        /// Example: If you wish to express the UD amounts with a maximum precision of the order
        /// of the milliUD, choose 1000
        type UnitsPerUd: Get<BalanceOf<Self>>;
        /// Pallet weights info
        type WeightInfo: WeightInfo;
    }

    // STORAGE //

    /// Current UD amount
    #[pallet::storage]
    #[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)]
    pub type MonetaryMass<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;

    /// Next UD reevaluation
    #[pallet::storage]
    #[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]
    pub struct GenesisConfig<T: Config> {
        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")]
    impl<T: Config> Default for GenesisConfig<T> {
        fn default() -> Self {
            Self {
                first_reeval: Default::default(),
                first_ud: Default::default(),
                initial_monetary_mass: Default::default(),
                #[cfg(test)]
                initial_members: Default::default(),
            }
        }
    }

    #[pallet::genesis_build]
    impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
        fn build(&self) {
            assert!(!self.first_ud.is_zero());
            assert!(self.initial_monetary_mass >= T::Currency::total_issuance());

            <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());
                }
            }
        }
    }

    // HOOKS //

    #[pallet::hooks]
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
        fn on_initialize(n: T::BlockNumber) -> Weight {
            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()));
                    Self::reeval_ud(current_members_count);
                    Self::create_ud(current_members_count);
                    T::WeightInfo::on_initialize_ud_reevalued()
                } else {
                    Self::create_ud(current_members_count);
                    T::WeightInfo::on_initialize_ud_created()
                }
            } else {
                T::WeightInfo::on_initialize()
            }
        }
    }

    // EVENTS //

    // Pallets use events to inform users when important changes are made.
    // https://substrate.dev/docs/en/knowledgebase/runtime/events
    #[pallet::event]
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
    pub enum Event<T: Config> {
        /// 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.
        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>) {
            let ud_amount = <CurrentUd<T>>::get();
            let monetary_mass = <MonetaryMass<T>>::get();

            // 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,
            });
        }
        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>,
            dest: <T::Lookup as StaticLookup>::Source,
            value: BalanceOf<T>,
            existence_requirement: ExistenceRequirement,
        ) -> DispatchResultWithPostInfo {
            let who = ensure_signed(origin)?;
            let dest = T::Lookup::lookup(dest)?;
            let ud_amount = <CurrentUd<T>>::get();
            T::Currency::transfer(
                &who,
                &dest,
                value.saturating_mul(ud_amount) / T::UnitsPerUd::get(),
                existence_requirement,
            )?;
            Ok(().into())
        }
        fn reeval_ud(members_count: BalanceOf<T>) {
            let ud_amount = <CurrentUd<T>>::get();

            let monetary_mass = <MonetaryMass<T>>::get();

            let new_ud_amount = Self::reeval_ud_formula(
                ud_amount,
                T::SquareMoneyGrowthRate::get(),
                monetary_mass,
                members_count,
                T::BlockNumberIntoBalance::convert(
                    T::UdReevalPeriod::get() / T::UdCreationPeriod::get(),
                ),
            );

            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,
            });
        }
        fn reeval_ud_formula(
            ud_t: BalanceOf<T>,
            c_square: Perbill,
            monetary_mass: BalanceOf<T>,
            mut members_count: BalanceOf<T>,
            count_uds_beetween_two_reevals: BalanceOf<T>, // =(dt/udFrequency)
        ) -> BalanceOf<T> {
            // Ensure that we do not divide by zero
            if members_count.is_zero() {
                members_count = One::one();
            }

            // 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)
        }
    }

    // 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(
            origin: OriginFor<T>,
            dest: <T::Lookup as StaticLookup>::Source,
            #[pallet::compact] value: BalanceOf<T>,
        ) -> DispatchResultWithPostInfo {
            Self::do_transfer_ud(origin, dest, value, ExistenceRequirement::AllowDeath)
        }

        /// Transfer some liquid free balance to another account, in milliUD.
        #[pallet::weight(T::WeightInfo::transfer_ud_keep_alive())]
        pub fn transfer_ud_keep_alive(
            origin: OriginFor<T>,
            dest: <T::Lookup as StaticLookup>::Source,
            #[pallet::compact] value: BalanceOf<T>,
        ) -> DispatchResultWithPostInfo {
            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)
            }
        }
    }
}