Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: InterestAccrual traits refactor #1310

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions libs/mocks/src/accrual.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#[frame_support::pallet]
pub mod pallet {
use cfg_traits::accrual::{RateAccrual, RateCache};
use frame_support::pallet_prelude::*;
use mock_builder::{execute_call, register_call};

#[pallet::config]
pub trait Config: frame_system::Config {
type OuterRate;
type AccRate;
type Moment;
type Cache: RateCache<Self::OuterRate, Self::AccRate>;
type MaxRateCount: Get<u32>;
}

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

#[pallet::storage]
pub(super) type CallIds<T: Config> = StorageMap<
_,
Blake2_128Concat,
<Blake2_128 as frame_support::StorageHasher>::Output,
mock_builder::CallId,
>;

impl<T: Config> Pallet<T> {
pub fn mock_accrual(
f: impl Fn(T::OuterRate) -> Result<T::AccRate, DispatchError> + 'static,
) {
register_call!(f);
}

pub fn mock_accrual_at(
f: impl Fn(T::OuterRate, T::Moment) -> Result<T::AccRate, DispatchError> + 'static,
) {
register_call!(move |(a, b)| f(a, b));
}

pub fn mock_last_updated(f: impl Fn() -> T::Moment + 'static) {
register_call!(move |()| f());
}

pub fn mock_validate_rate(f: impl Fn(T::OuterRate) -> DispatchResult + 'static) {
register_call!(f);
}

pub fn mock_reference_rate(f: impl Fn(T::OuterRate) -> DispatchResult + 'static) {
register_call!(f);
}

pub fn mock_unreference_rate(f: impl Fn(T::OuterRate) -> DispatchResult + 'static) {
register_call!(f);
}

pub fn mock_cache(f: impl Fn() -> T::Cache + 'static) {
register_call!(move |()| f());
}
}

impl<T: Config> RateAccrual for Pallet<T> {
type AccRate = T::AccRate;
type Cache = T::Cache;
type MaxRateCount = T::MaxRateCount;
type Moment = T::Moment;
type OuterRate = T::OuterRate;

fn accrual(a: T::OuterRate) -> Result<T::AccRate, DispatchError> {
execute_call!(a)
}

fn accrual_at(a: T::OuterRate, b: T::Moment) -> Result<T::AccRate, DispatchError> {
execute_call!((a, b))
}

fn last_updated() -> T::Moment {
execute_call!(())
}

fn validate_rate(a: T::OuterRate) -> DispatchResult {
execute_call!(a)
}

fn reference_rate(a: T::OuterRate) -> DispatchResult {
execute_call!(a)
}

fn unreference_rate(a: T::OuterRate) -> DispatchResult {
execute_call!(a)
}

fn cache() -> T::Cache {
execute_call!(())
}
}
}
6 changes: 4 additions & 2 deletions libs/mocks/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
mod accrual;
mod fees;
mod permissions;
mod pools;
mod rewards;

pub use accrual::pallet as pallet_mock_accrual;
pub use fees::pallet as pallet_mock_fees;
pub use permissions::pallet as pallet_mock_permissions;
pub use pools::pallet as pallet_mock_pools;
Expand Down Expand Up @@ -86,7 +88,7 @@ mod test {

use super::*;

make_runtime_for_mock!(Runtime, Mock, pallet_mock_template, new_test_ext);
make_runtime_for_mock!(Runtime, MockTemplate, pallet_mock_template, new_test_ext);

impl pallet_mock_template::Config for Runtime {
// Configure your associated types here
Expand All @@ -95,7 +97,7 @@ mod test {
#[test]
fn runtime_for_mock() {
new_test_ext().execute_with(|| {
// Test using the Mock
// Test using the MockTemplate
});
}
}
162 changes: 162 additions & 0 deletions libs/traits/src/accrual.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
use sp_arithmetic::traits::{One, Zero};
use sp_runtime::{traits::Get, DispatchError, DispatchResult, FixedPointNumber, FixedPointOperand};
use sp_std::cmp::Ordering;

use crate::ops::{EnsureAdd, EnsureDiv, EnsureFixedPointNumber, EnsureSub};

/// Represents an absolute value that can increase or decrease
pub enum Adjustment<Amount> {
Increase(Amount),
Decrease(Amount),
}

/// Abstraction over an interest accrual system
pub trait RateAccrual {
/// Identify and represents a rate in the collection.
type OuterRate;

/// Inner rate
type AccRate;

/// Represent a timestamp
type Moment;

/// Type used to cache the own collection of rates
type Cache: RateCache<Self::OuterRate, Self::AccRate>;

/// Maximum rates this implementation can contain.
/// Used for weight calculations in consumers of this trait,
/// but is otherwise unused in this interface.
type MaxRateCount: Get<u32>;

/// Returns an accrual rate identified by an outer rate
fn accrual(outer: Self::OuterRate) -> Result<Self::AccRate, DispatchError>;

/// Returns an accrual rate identified by an outer rate at specitic time
fn accrual_at(
outer: Self::OuterRate,
when: Self::Moment,
) -> Result<Self::AccRate, DispatchError>;

/// Check if the outer rate is valid
fn validate_rate(outer: Self::OuterRate) -> DispatchResult;

/// Reference a outer rate in the system to start using it
fn reference_rate(outer: Self::OuterRate) -> DispatchResult;

/// Unreference a outer rate indicating to the system that it's no longer in use
fn unreference_rate(outer: Self::OuterRate) -> DispatchResult;

/// Returns last moment the collection was updated
fn last_updated() -> Self::Moment;

/// Creates an inmutable copy of this rate collection.
fn cache() -> Self::Cache;
}

/// Represents a cached collection of rates
pub trait RateCache<OuterRate, AccRate> {
/// Returns an accrual rate identified by an outer rate
fn accrual(&self, outer: OuterRate) -> Result<AccRate, DispatchError>;
}

impl<OuterRate, AccRate> RateCache<OuterRate, AccRate> for () {
fn accrual(&self, _: OuterRate) -> Result<AccRate, DispatchError> {
Err(DispatchError::Other("No rate cache"))
}
}

pub trait DebtAccrual<Debt>: RateAccrual
where
<Self as RateAccrual>::AccRate: FixedPointNumber,
<Self as RateAccrual>::Moment: EnsureSub + Ord + Zero + TryInto<usize>,
Debt: FixedPointOperand + EnsureAdd + EnsureSub,
{
/// Get the current debt for that outer rate
fn current_debt(outer: Self::OuterRate, norm_debt: Debt) -> Result<Debt, DispatchError> {
Self::calculate_debt(outer, norm_debt, Self::last_updated())
}

/// Calculate the debt for that outer rate at an instant
fn calculate_debt(
outer: Self::OuterRate,
norm_debt: Debt,
when: Self::Moment,
) -> Result<Debt, DispatchError> {
let now = Self::last_updated();
let acc = match when.cmp(&now) {
Ordering::Equal => Self::accrual(outer),
Ordering::Less => Self::accrual_at(outer, when),
Ordering::Greater => {
// TODO: uncomment the following once #1304 is solved
// return Err(DispatchError::Other("Precondition broken: when <= now"))
Self::accrual(outer)
}
}?;

Ok(acc.ensure_mul_int(norm_debt)?)
}

/// Increase or decrease the amount, returing the new normalized debt
fn adjust_normalized_debt<Amount: Into<Debt>>(
outer: Self::OuterRate,
norm_debt: Debt,
adjustment: Adjustment<Amount>,
) -> Result<Debt, DispatchError> {
let acc = Self::accrual(outer)?;

let old_debt = acc.ensure_mul_int(norm_debt)?;
let new_debt = match adjustment {
Adjustment::Increase(amount) => old_debt.ensure_add(amount.into()),
Adjustment::Decrease(amount) => old_debt.ensure_sub(amount.into()),
}?;

Ok(Self::AccRate::one()
.ensure_div(acc)?
.ensure_mul_int(new_debt)?)
}

/// Re-normalize a debt for a new interest rate, returing the new normalize_debt
fn renormalize_debt(
old_outer: Self::OuterRate,
new_outer: Self::OuterRate,
norm_debt: Debt,
) -> Result<Debt, DispatchError> {
let old_acc = Self::accrual(old_outer)?;
let new_acc = Self::accrual(new_outer)?;

let debt = old_acc.ensure_mul_int(norm_debt)?;

Ok(Self::AccRate::one()
.ensure_div(new_acc)?
.ensure_mul_int(debt)?)
}
}

impl<OuterRate, AccRate, Moment, Debt, T> DebtAccrual<Debt> for T
where
AccRate: FixedPointNumber,
Moment: EnsureSub + Ord + Zero + TryInto<usize>,
Debt: FixedPointOperand + EnsureAdd + EnsureSub,
T: RateAccrual<OuterRate = OuterRate, AccRate = AccRate, Moment = Moment>,
{
}

/// Represents a cached collection of debts
pub trait DebtCache<OuterRate, AccRate, Debt>: RateCache<OuterRate, AccRate>
where
AccRate: FixedPointNumber,
Debt: FixedPointOperand,
{
fn current_debt(&self, outer: OuterRate, norm_debt: Debt) -> Result<Debt, DispatchError> {
Ok(self.accrual(outer)?.ensure_mul_int(norm_debt)?)
}
}

impl<OuterRate, AccRate, Debt, T> DebtCache<OuterRate, AccRate, Debt> for T
where
AccRate: FixedPointNumber,
Debt: FixedPointOperand,
T: RateCache<OuterRate, AccRate>,
{
}
57 changes: 3 additions & 54 deletions libs/traits/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
// Ensure we're `no_std` when compiling for WebAssembly.
#![cfg_attr(not(feature = "std"), no_std)]

use cfg_primitives::Moment;
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{
dispatch::{Codec, DispatchResult, DispatchResultWithPostInfo},
Expand All @@ -41,6 +40,9 @@ pub mod ops;
/// Traits related to rewards.
pub mod rewards;

/// Traits related to accrual rates.
pub mod accrual;

/// A trait used for loosely coupling the claim pallet with a reward mechanism.
///
/// ## Overview
Expand Down Expand Up @@ -222,59 +224,6 @@ pub trait CurrencyPrice<CurrencyId> {
) -> Option<PriceValue<CurrencyId, Self::Rate, Self::Moment>>;
}

/// A trait that can be used to calculate interest accrual for debt
pub trait InterestAccrual<InterestRate, Balance, Adjustment> {
/// The maximum number of rates this `InterestAccrual` can
/// contain. It is necessary for rate calculations in consumers of
/// this pallet, but is otherwise unused in this interface.
type MaxRateCount: Get<u32>;
type NormalizedDebt: Member + Parameter + MaxEncodedLen + TypeInfo + Copy + Zero;
type Rates: RateCollection<InterestRate, Balance, Self::NormalizedDebt>;

/// Calculate the debt at an specific moment
fn calculate_debt(
interest_rate_per_year: InterestRate,
normalized_debt: Self::NormalizedDebt,
when: Moment,
) -> Result<Balance, DispatchError>;

/// Increase or decrease the normalized debt
fn adjust_normalized_debt(
interest_rate_per_year: InterestRate,
normalized_debt: Self::NormalizedDebt,
adjustment: Adjustment,
) -> Result<Self::NormalizedDebt, DispatchError>;

/// Re-normalize a debt for a new interest rate
fn renormalize_debt(
old_interest_rate: InterestRate,
new_interest_rate: InterestRate,
normalized_debt: Self::NormalizedDebt,
) -> Result<Self::NormalizedDebt, DispatchError>;

/// Validate and indicate that a yearly rate is in use
fn reference_rate(interest_rate_per_year: InterestRate) -> DispatchResult;

/// Indicate that a rate is no longer in use
fn unreference_rate(interest_rate_per_year: InterestRate) -> DispatchResult;

/// Ask if the rate is valid to use by the implementation
fn validate_rate(interest_rate_per_year: InterestRate) -> DispatchResult;

/// Returns a collection of pre-computed rates to perform multiple operations with
fn rates() -> Self::Rates;
}

/// A collection of pre-computed interest rates for performing interest accrual
pub trait RateCollection<InterestRate, Balance, NormalizedDebt> {
/// Calculate the current debt using normalized debt * cumulative rate
fn current_debt(
&self,
interest_rate_per_sec: InterestRate,
normalized_debt: NormalizedDebt,
) -> Result<Balance, DispatchError>;
}

pub trait Permissions<AccountId> {
type Scope;
type Role;
Expand Down
Loading