From e0f806e8c6fffe02d27942235094a1794e3e0594 Mon Sep 17 00:00:00 2001 From: lemunozm Date: Wed, 5 Apr 2023 09:03:43 +0200 Subject: [PATCH 1/5] New trait definition --- libs/traits/src/accrual.rs | 141 +++++++++++++++++++++++++++++++++++++ libs/traits/src/lib.rs | 3 + 2 files changed, 144 insertions(+) create mode 100644 libs/traits/src/accrual.rs diff --git a/libs/traits/src/accrual.rs b/libs/traits/src/accrual.rs new file mode 100644 index 0000000000..b14b299916 --- /dev/null +++ b/libs/traits/src/accrual.rs @@ -0,0 +1,141 @@ +use sp_arithmetic::traits::{checked_pow, One, Zero}; +use sp_runtime::{ + ArithmeticError, DispatchError, DispatchResult, FixedPointNumber, FixedPointOperand, +}; +use sp_std::cmp::Ordering; + +use crate::ops::{EnsureAdd, EnsureDiv, EnsureFixedPointNumber, EnsureInto, EnsureSub}; + +/// Represents an accrual rate +pub struct AccrualRate { + /// Rate that is accruing + pub inner: Rate, + + /// Accumulation + pub acc: Rate, +} + +/// Represents an absolute value that can increase or decrease +pub enum Adjustment { + Increase(Amount), + Decrease(Amount), +} + +/// Represents a collection of rates +pub trait RateCollection { + /// Identify and represents an inner rate in the collection. + type Locator; + + /// Inner rate + type Rate; + + /// Represent a timestamp + type Moment; + + /// Convert a locator to an inner rate (i.e. by a math operation) + fn convert(locator: Self::Locator) -> Result; + + /// Returns an accrual rate identified by a locator + fn rate(locator: Self::Locator) -> Result, DispatchError>; + + /// Returns last moment the collection was updated + fn last_updated() -> Self::Moment; +} + +/// Abstraction over an interest accrual system +pub trait InterestAccrual: RateCollection { + /// Type used to cache the own collection of rates + type Cache: RateCollection; + + /// Check if the locator is valid + fn validate(locator: Self::Locator) -> DispatchResult; + + /// Reference a locator in the system to start using its inner rate + fn reference(locator: Self::Locator) -> DispatchResult; + + /// Unreference a locator indicating to the system that it's no longer in use + fn unreference(locator: Self::Locator) -> DispatchResult; + + /// Creates an inmutable copy of this rate collection. + fn create_cache() -> Self::Cache; +} + +pub trait DebtAccrual: RateCollection +where + ::Rate: FixedPointNumber, + ::Moment: EnsureSub + Ord + Zero + Into, + Debt: FixedPointOperand + EnsureAdd + EnsureSub, +{ + fn current_debt(locator: Self::Locator, norm_debt: Debt) -> Result { + Self::calculate_debt(locator, norm_debt, Self::last_updated()) + } + + fn calculate_debt( + locator: Self::Locator, + norm_debt: Debt, + when: Self::Moment, + ) -> Result { + let rate = Self::rate(locator)?; + let now = Self::last_updated(); + + let acc = match when.cmp(&now) { + Ordering::Equal => rate.acc, + Ordering::Less => { + let delta = now.ensure_sub(when)?; + let rate_adjustment = checked_pow(rate.inner, delta.ensure_into()?) + .ok_or(ArithmeticError::Overflow)?; + rate.acc.ensure_div(rate_adjustment)? + } + Ordering::Greater => { + return Err(DispatchError::Other("Invalid precondition: when <= now")) + } + }; + + Ok(acc.ensure_mul_int(norm_debt)?) + } + + fn adjust_debt( + locator: Self::Locator, + norm_debt: Debt, + adjustment: Adjustment, + ) -> Result + where + Amount: Into, + { + let rate = Self::rate(locator)?; + + let old_debt = rate.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::Rate::one() + .ensure_div(rate.acc)? + .ensure_mul_int(new_debt)?) + } + + fn normalize_debt( + old_locator: Self::Locator, + new_locator: Self::Locator, + norm_debt: Debt, + ) -> Result { + let old_rate = Self::rate(old_locator)?; + let new_rate = Self::rate(new_locator)?; + + let debt = old_rate.acc.ensure_mul_int(norm_debt)?; + + Ok(Self::Rate::one() + .ensure_div(new_rate.acc)? + .ensure_mul_int(debt)?) + } +} + +impl DebtAccrual for T +where + Rate: FixedPointNumber, + Moment: EnsureSub + Ord + Zero + Into, + Debt: FixedPointOperand + EnsureAdd + EnsureSub, + T: RateCollection, +{ +} diff --git a/libs/traits/src/lib.rs b/libs/traits/src/lib.rs index e6a59dc245..635e0f6014 100644 --- a/libs/traits/src/lib.rs +++ b/libs/traits/src/lib.rs @@ -41,6 +41,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 From bca89187601740f7a024a6b2474299c1b52ee199 Mon Sep 17 00:00:00 2001 From: lemunozm Date: Wed, 5 Apr 2023 10:01:50 +0200 Subject: [PATCH 2/5] more doc --- libs/traits/src/accrual.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/libs/traits/src/accrual.rs b/libs/traits/src/accrual.rs index b14b299916..09c185b0dd 100644 --- a/libs/traits/src/accrual.rs +++ b/libs/traits/src/accrual.rs @@ -66,10 +66,12 @@ where ::Moment: EnsureSub + Ord + Zero + Into, Debt: FixedPointOperand + EnsureAdd + EnsureSub, { + /// Get the current debt for that locator fn current_debt(locator: Self::Locator, norm_debt: Debt) -> Result { Self::calculate_debt(locator, norm_debt, Self::last_updated()) } + /// Calculate the debt for that locator at an instant fn calculate_debt( locator: Self::Locator, norm_debt: Debt, @@ -87,21 +89,19 @@ where rate.acc.ensure_div(rate_adjustment)? } Ordering::Greater => { - return Err(DispatchError::Other("Invalid precondition: when <= now")) + return Err(DispatchError::Other("Precondition broken: when <= now")) } }; Ok(acc.ensure_mul_int(norm_debt)?) } - fn adjust_debt( + /// Increase or decrease the amount, returing the new normalized debt + fn adjust_debt>( locator: Self::Locator, norm_debt: Debt, adjustment: Adjustment, - ) -> Result - where - Amount: Into, - { + ) -> Result { let rate = Self::rate(locator)?; let old_debt = rate.acc.ensure_mul_int(norm_debt)?; @@ -115,6 +115,7 @@ where .ensure_mul_int(new_debt)?) } + /// Re-normalize a debt for a new interest rate, returing the new normalize_debt fn normalize_debt( old_locator: Self::Locator, new_locator: Self::Locator, From 3995da78c6ab781fbb43b98c5be284c9953c91ff Mon Sep 17 00:00:00 2001 From: lemunozm Date: Mon, 10 Apr 2023 08:58:09 +0200 Subject: [PATCH 3/5] accrual mocks --- libs/mocks/src/accrual.rs | 88 ++++++++++++++++++++++++++++++++++++ libs/mocks/src/lib.rs | 6 ++- libs/traits/src/accrual.rs | 71 ++++++++++++++--------------- libs/traits/tests/accrual.rs | 87 +++++++++++++++++++++++++++++++++++ 4 files changed, 214 insertions(+), 38 deletions(-) create mode 100644 libs/mocks/src/accrual.rs create mode 100644 libs/traits/tests/accrual.rs diff --git a/libs/mocks/src/accrual.rs b/libs/mocks/src/accrual.rs new file mode 100644 index 0000000000..182aa71d86 --- /dev/null +++ b/libs/mocks/src/accrual.rs @@ -0,0 +1,88 @@ +#[frame_support::pallet] +pub mod pallet { + use cfg_traits::accrual::{AccrualRate, InterestAccrual, RateCollection}; + use frame_support::pallet_prelude::*; + use mock_builder::{execute_call, register_call}; + + #[pallet::config] + pub trait Config: frame_system::Config { + type OuterRate; + type InnerRate; + type Moment; + type Cache: RateCollection; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::storage] + pub(super) type CallIds = StorageMap< + _, + Blake2_128Concat, + ::Output, + mock_builder::CallId, + >; + + impl Pallet { + pub fn mock_accrual_rate( + f: impl Fn(T::OuterRate) -> Result, DispatchError> + 'static, + ) { + register_call!(f); + } + + pub fn mock_last_updated(f: impl Fn() -> T::Moment + 'static) { + register_call!(move |()| f()); + } + + pub fn mock_validate(f: impl Fn(T::OuterRate) -> DispatchResult + 'static) { + register_call!(f); + } + + pub fn mock_reference(f: impl Fn(T::OuterRate) -> DispatchResult + 'static) { + register_call!(f); + } + + pub fn mock_unreference(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 RateCollection for Pallet { + type InnerRate = T::InnerRate; + type Moment = T::Moment; + type OuterRate = T::OuterRate; + + fn accrual_rate(a: T::OuterRate) -> Result, DispatchError> { + execute_call!(a) + } + + fn last_updated() -> T::Moment { + execute_call!(()) + } + } + + impl InterestAccrual for Pallet { + type Cache = T::Cache; + + fn validate(a: T::OuterRate) -> DispatchResult { + execute_call!(a) + } + + fn reference(a: T::OuterRate) -> DispatchResult { + execute_call!(a) + } + + fn unreference(a: T::OuterRate) -> DispatchResult { + execute_call!(a) + } + + fn cache() -> T::Cache { + execute_call!(()) + } + } +} diff --git a/libs/mocks/src/lib.rs b/libs/mocks/src/lib.rs index e7ec9e27e3..a86b8a8f74 100644 --- a/libs/mocks/src/lib.rs +++ b/libs/mocks/src/lib.rs @@ -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; @@ -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 @@ -95,7 +97,7 @@ mod test { #[test] fn runtime_for_mock() { new_test_ext().execute_with(|| { - // Test using the Mock + // Test using the MockTemplate }); } } diff --git a/libs/traits/src/accrual.rs b/libs/traits/src/accrual.rs index 09c185b0dd..135ec6dd36 100644 --- a/libs/traits/src/accrual.rs +++ b/libs/traits/src/accrual.rs @@ -11,7 +11,7 @@ pub struct AccrualRate { /// Rate that is accruing pub inner: Rate, - /// Accumulation + /// Accumulation of the rate pub acc: Rate, } @@ -24,19 +24,16 @@ pub enum Adjustment { /// Represents a collection of rates pub trait RateCollection { /// Identify and represents an inner rate in the collection. - type Locator; + type OuterRate; /// Inner rate - type Rate; + type InnerRate; /// Represent a timestamp type Moment; - /// Convert a locator to an inner rate (i.e. by a math operation) - fn convert(locator: Self::Locator) -> Result; - - /// Returns an accrual rate identified by a locator - fn rate(locator: Self::Locator) -> Result, DispatchError>; + /// Returns an accrual rate identified by an outer rate + fn accrual_rate(outer: Self::OuterRate) -> Result, DispatchError>; /// Returns last moment the collection was updated fn last_updated() -> Self::Moment; @@ -47,37 +44,37 @@ pub trait InterestAccrual: RateCollection { /// Type used to cache the own collection of rates type Cache: RateCollection; - /// Check if the locator is valid - fn validate(locator: Self::Locator) -> DispatchResult; + /// Check if the outer rate is valid + fn validate(outer: Self::OuterRate) -> DispatchResult; - /// Reference a locator in the system to start using its inner rate - fn reference(locator: Self::Locator) -> DispatchResult; + /// Reference a outer rate in the system to start using its inner rate + fn reference(outer: Self::OuterRate) -> DispatchResult; - /// Unreference a locator indicating to the system that it's no longer in use - fn unreference(locator: Self::Locator) -> DispatchResult; + /// Unreference a outer rate indicating to the system that it's no longer in use + fn unreference(outer: Self::OuterRate) -> DispatchResult; /// Creates an inmutable copy of this rate collection. - fn create_cache() -> Self::Cache; + fn cache() -> Self::Cache; } pub trait DebtAccrual: RateCollection where - ::Rate: FixedPointNumber, - ::Moment: EnsureSub + Ord + Zero + Into, + ::InnerRate: FixedPointNumber, + ::Moment: EnsureSub + Ord + Zero + TryInto, Debt: FixedPointOperand + EnsureAdd + EnsureSub, { - /// Get the current debt for that locator - fn current_debt(locator: Self::Locator, norm_debt: Debt) -> Result { - Self::calculate_debt(locator, norm_debt, Self::last_updated()) + /// Get the current debt for that outer rate + fn current_debt(outer: Self::OuterRate, norm_debt: Debt) -> Result { + Self::calculate_debt(outer, norm_debt, Self::last_updated()) } - /// Calculate the debt for that locator at an instant + /// Calculate the debt for that outer rate at an instant fn calculate_debt( - locator: Self::Locator, + outer: Self::OuterRate, norm_debt: Debt, when: Self::Moment, ) -> Result { - let rate = Self::rate(locator)?; + let rate = Self::accrual_rate(outer)?; let now = Self::last_updated(); let acc = match when.cmp(&now) { @@ -89,7 +86,9 @@ where rate.acc.ensure_div(rate_adjustment)? } Ordering::Greater => { - return Err(DispatchError::Other("Precondition broken: when <= now")) + // TODO: uncomment the following once #1304 is solved + // return Err(DispatchError::Other("Precondition broken: when <= now")) + rate.acc } }; @@ -98,11 +97,11 @@ where /// Increase or decrease the amount, returing the new normalized debt fn adjust_debt>( - locator: Self::Locator, + outer: Self::OuterRate, norm_debt: Debt, adjustment: Adjustment, ) -> Result { - let rate = Self::rate(locator)?; + let rate = Self::accrual_rate(outer)?; let old_debt = rate.acc.ensure_mul_int(norm_debt)?; let new_debt = match adjustment { @@ -110,33 +109,33 @@ where Adjustment::Decrease(amount) => old_debt.ensure_sub(amount.into()), }?; - Ok(Self::Rate::one() + Ok(Self::InnerRate::one() .ensure_div(rate.acc)? .ensure_mul_int(new_debt)?) } /// Re-normalize a debt for a new interest rate, returing the new normalize_debt fn normalize_debt( - old_locator: Self::Locator, - new_locator: Self::Locator, + old_outer: Self::OuterRate, + new_outer: Self::OuterRate, norm_debt: Debt, ) -> Result { - let old_rate = Self::rate(old_locator)?; - let new_rate = Self::rate(new_locator)?; + let old_rate = Self::accrual_rate(old_outer)?; + let new_rate = Self::accrual_rate(new_outer)?; let debt = old_rate.acc.ensure_mul_int(norm_debt)?; - Ok(Self::Rate::one() + Ok(Self::InnerRate::one() .ensure_div(new_rate.acc)? .ensure_mul_int(debt)?) } } -impl DebtAccrual for T +impl DebtAccrual for T where - Rate: FixedPointNumber, - Moment: EnsureSub + Ord + Zero + Into, + InnerRate: FixedPointNumber, + Moment: EnsureSub + Ord + Zero + TryInto, Debt: FixedPointOperand + EnsureAdd + EnsureSub, - T: RateCollection, + T: RateCollection, { } diff --git a/libs/traits/tests/accrual.rs b/libs/traits/tests/accrual.rs new file mode 100644 index 0000000000..fb9fdcc790 --- /dev/null +++ b/libs/traits/tests/accrual.rs @@ -0,0 +1,87 @@ +use cfg_mocks::pallet_mock_accrual; +use cfg_traits::accrual::{AccrualRate, Adjustment, DebtAccrual}; +use frame_support::{assert_err, assert_ok}; +use sp_arithmetic::fixed_point::FixedU64; +use sp_runtime::DispatchError; + +impl pallet_mock_accrual::Config for Runtime { + type Cache = MockAccrual; + type InnerRate = FixedU64; + type Moment = u64; + type OuterRate = u8; +} + +cfg_mocks::make_runtime_for_mock!(Runtime, MockAccrual, pallet_mock_accrual, new_test_ext); + +const ERROR: DispatchError = DispatchError::Other("Error"); +const OUTER_1: u8 = 1; +const OUTER_2: u8 = 2; +const WRONG_OUTER: u8 = 0; +const LAST: u64 = 1000; + +fn config_mocks() { + MockAccrual::mock_accrual_rate(|outer| match outer { + OUTER_1 => Ok(AccrualRate { + inner: FixedU64::from_float(0.5), + acc: FixedU64::from_float(0.6), + }), + OUTER_2 => Ok(AccrualRate { + inner: FixedU64::from_float(0.3), + acc: FixedU64::from_float(0.2), + }), + _ => Err(ERROR), + }); + MockAccrual::mock_last_updated(|| LAST); +} + +#[test] +fn wrong_outer() { + const NORM_DEBT: u64 = 100; + const WHEN: u64 = 10000; + + new_test_ext().execute_with(|| { + config_mocks(); + + assert_err!(MockAccrual::current_debt(WRONG_OUTER, NORM_DEBT), ERROR); + assert_err!( + MockAccrual::calculate_debt(WRONG_OUTER, NORM_DEBT, WHEN), + ERROR + ); + assert_err!( + MockAccrual::adjust_debt(WRONG_OUTER, NORM_DEBT, Adjustment::Increase(42u32)), + ERROR + ); + assert_err!( + MockAccrual::normalize_debt(WRONG_OUTER, OUTER_2, NORM_DEBT), + ERROR + ); + assert_err!( + MockAccrual::normalize_debt(OUTER_1, WRONG_OUTER, NORM_DEBT), + ERROR + ); + }); +} + +#[test] +fn calculate_debt() { + const NORM_DEBT: u64 = 100; + + new_test_ext().execute_with(|| { + config_mocks(); + + assert_ok!( + MockAccrual::calculate_debt(OUTER_1, NORM_DEBT, LAST), + (NORM_DEBT as f32 * 0.5) as u64 + ); + + assert_ok!( + MockAccrual::calculate_debt(OUTER_1, NORM_DEBT, LAST + 100), + (NORM_DEBT as f32 * 0.5) as u64 + ); + + assert_ok!( + MockAccrual::calculate_debt(OUTER_1, NORM_DEBT, LAST - 100), + (NORM_DEBT as f32 * 0.5) as u64 + ); + }); +} From 1e90815a06d88478d49cf516b71297a9d4b4d5ad Mon Sep 17 00:00:00 2001 From: lemunozm Date: Mon, 10 Apr 2023 11:09:43 +0200 Subject: [PATCH 4/5] trait redesign and pallet refactor --- libs/mocks/src/accrual.rs | 31 +- libs/traits/src/accrual.rs | 124 ++++--- libs/traits/tests/accrual.rs | 52 ++- pallets/interest-accrual/src/benchmarking.rs | 5 +- pallets/interest-accrual/src/lib.rs | 337 ++++++------------- pallets/interest-accrual/src/migrations.rs | 145 -------- pallets/interest-accrual/src/mock.rs | 4 +- pallets/interest-accrual/src/test_utils.rs | 33 -- pallets/interest-accrual/src/tests.rs | 2 + 9 files changed, 216 insertions(+), 517 deletions(-) delete mode 100644 pallets/interest-accrual/src/migrations.rs delete mode 100644 pallets/interest-accrual/src/test_utils.rs diff --git a/libs/mocks/src/accrual.rs b/libs/mocks/src/accrual.rs index 182aa71d86..48920f6a60 100644 --- a/libs/mocks/src/accrual.rs +++ b/libs/mocks/src/accrual.rs @@ -1,15 +1,15 @@ #[frame_support::pallet] pub mod pallet { - use cfg_traits::accrual::{AccrualRate, InterestAccrual, RateCollection}; + 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 InnerRate; + type AccRate; type Moment; - type Cache: RateCollection; + type Cache: RateCache; } #[pallet::pallet] @@ -25,12 +25,18 @@ pub mod pallet { >; impl Pallet { - pub fn mock_accrual_rate( - f: impl Fn(T::OuterRate) -> Result, DispatchError> + 'static, + pub fn mock_accrual( + f: impl Fn(T::OuterRate) -> Result + 'static, ) { register_call!(f); } + pub fn mock_accrual_at( + f: impl Fn(T::OuterRate, T::Moment) -> Result + 'static, + ) { + register_call!(move |(a, b)| f(a, b)); + } + pub fn mock_last_updated(f: impl Fn() -> T::Moment + 'static) { register_call!(move |()| f()); } @@ -52,22 +58,23 @@ pub mod pallet { } } - impl RateCollection for Pallet { - type InnerRate = T::InnerRate; + impl RateAccrual for Pallet { + type AccRate = T::AccRate; + type Cache = T::Cache; type Moment = T::Moment; type OuterRate = T::OuterRate; - fn accrual_rate(a: T::OuterRate) -> Result, DispatchError> { + fn accrual(a: T::OuterRate) -> Result { execute_call!(a) } + fn accrual_at(a: T::OuterRate, b: T::Moment) -> Result { + execute_call!((a, b)) + } + fn last_updated() -> T::Moment { execute_call!(()) } - } - - impl InterestAccrual for Pallet { - type Cache = T::Cache; fn validate(a: T::OuterRate) -> DispatchResult { execute_call!(a) diff --git a/libs/traits/src/accrual.rs b/libs/traits/src/accrual.rs index 135ec6dd36..b28df1a909 100644 --- a/libs/traits/src/accrual.rs +++ b/libs/traits/src/accrual.rs @@ -1,19 +1,8 @@ -use sp_arithmetic::traits::{checked_pow, One, Zero}; -use sp_runtime::{ - ArithmeticError, DispatchError, DispatchResult, FixedPointNumber, FixedPointOperand, -}; +use sp_arithmetic::traits::{One, Zero}; +use sp_runtime::{DispatchError, DispatchResult, FixedPointNumber, FixedPointOperand}; use sp_std::cmp::Ordering; -use crate::ops::{EnsureAdd, EnsureDiv, EnsureFixedPointNumber, EnsureInto, EnsureSub}; - -/// Represents an accrual rate -pub struct AccrualRate { - /// Rate that is accruing - pub inner: Rate, - - /// Accumulation of the rate - pub acc: Rate, -} +use crate::ops::{EnsureAdd, EnsureDiv, EnsureFixedPointNumber, EnsureSub}; /// Represents an absolute value that can increase or decrease pub enum Adjustment { @@ -21,46 +10,61 @@ pub enum Adjustment { Decrease(Amount), } -/// Represents a collection of rates -pub trait RateCollection { - /// Identify and represents an inner rate in the collection. +/// Abstraction over an interest accrual system +pub trait RateAccrual { + /// Identify and represents a rate in the collection. type OuterRate; /// Inner rate - type InnerRate; + type AccRate; /// Represent a timestamp type Moment; - /// Returns an accrual rate identified by an outer rate - fn accrual_rate(outer: Self::OuterRate) -> Result, DispatchError>; + /// Type used to cache the own collection of rates + type Cache: RateCache; - /// Returns last moment the collection was updated - fn last_updated() -> Self::Moment; -} + /// Returns an accrual rate identified by an outer rate + fn accrual(outer: Self::OuterRate) -> Result; -/// Abstraction over an interest accrual system -pub trait InterestAccrual: RateCollection { - /// Type used to cache the own collection of rates - type Cache: RateCollection; + /// Returns an accrual rate identified by an outer rate at specitic time + fn accrual_at( + outer: Self::OuterRate, + when: Self::Moment, + ) -> Result; /// Check if the outer rate is valid fn validate(outer: Self::OuterRate) -> DispatchResult; - /// Reference a outer rate in the system to start using its inner rate + /// Reference a outer rate in the system to start using it fn reference(outer: Self::OuterRate) -> DispatchResult; /// Unreference a outer rate indicating to the system that it's no longer in use fn unreference(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; } -pub trait DebtAccrual: RateCollection +/// Represents a cached collection of rates +pub trait RateCache { + /// Returns an accrual rate identified by an outer rate + fn accrual(&self, outer: OuterRate) -> Result; +} + +impl RateCache for () { + fn accrual(&self, _: OuterRate) -> Result { + Err(DispatchError::Other("No rate cache")) + } +} + +pub trait DebtAccrual: RateAccrual where - ::InnerRate: FixedPointNumber, - ::Moment: EnsureSub + Ord + Zero + TryInto, + ::AccRate: FixedPointNumber, + ::Moment: EnsureSub + Ord + Zero + TryInto, Debt: FixedPointOperand + EnsureAdd + EnsureSub, { /// Get the current debt for that outer rate @@ -74,23 +78,16 @@ where norm_debt: Debt, when: Self::Moment, ) -> Result { - let rate = Self::accrual_rate(outer)?; let now = Self::last_updated(); - let acc = match when.cmp(&now) { - Ordering::Equal => rate.acc, - Ordering::Less => { - let delta = now.ensure_sub(when)?; - let rate_adjustment = checked_pow(rate.inner, delta.ensure_into()?) - .ok_or(ArithmeticError::Overflow)?; - rate.acc.ensure_div(rate_adjustment)? - } + Ordering::Equal => Self::accrual(outer), + Ordering::Less => Self::accrual_at(outer, now), Ordering::Greater => { // TODO: uncomment the following once #1304 is solved // return Err(DispatchError::Other("Precondition broken: when <= now")) - rate.acc + Self::accrual(outer) } - }; + }?; Ok(acc.ensure_mul_int(norm_debt)?) } @@ -101,16 +98,16 @@ where norm_debt: Debt, adjustment: Adjustment, ) -> Result { - let rate = Self::accrual_rate(outer)?; + let acc = Self::accrual(outer)?; - let old_debt = rate.acc.ensure_mul_int(norm_debt)?; + 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::InnerRate::one() - .ensure_div(rate.acc)? + Ok(Self::AccRate::one() + .ensure_div(acc)? .ensure_mul_int(new_debt)?) } @@ -120,22 +117,41 @@ where new_outer: Self::OuterRate, norm_debt: Debt, ) -> Result { - let old_rate = Self::accrual_rate(old_outer)?; - let new_rate = Self::accrual_rate(new_outer)?; + let old_acc = Self::accrual(old_outer)?; + let new_acc = Self::accrual(new_outer)?; - let debt = old_rate.acc.ensure_mul_int(norm_debt)?; + let debt = old_acc.ensure_mul_int(norm_debt)?; - Ok(Self::InnerRate::one() - .ensure_div(new_rate.acc)? + Ok(Self::AccRate::one() + .ensure_div(new_acc)? .ensure_mul_int(debt)?) } } -impl DebtAccrual for T +impl DebtAccrual for T where - InnerRate: FixedPointNumber, + AccRate: FixedPointNumber, Moment: EnsureSub + Ord + Zero + TryInto, Debt: FixedPointOperand + EnsureAdd + EnsureSub, - T: RateCollection, + T: RateAccrual, +{ +} + +/// Represents a cached collection of debts +pub trait DebtCache: RateCache +where + AccRate: FixedPointNumber, + Debt: FixedPointOperand, +{ + fn current_debt(&self, outer: OuterRate, norm_debt: Debt) -> Result { + Ok(self.accrual(outer)?.ensure_mul_int(norm_debt)?) + } +} + +impl DebtCache for T +where + AccRate: FixedPointNumber, + Debt: FixedPointOperand, + T: RateCache, { } diff --git a/libs/traits/tests/accrual.rs b/libs/traits/tests/accrual.rs index fb9fdcc790..6043be99ae 100644 --- a/libs/traits/tests/accrual.rs +++ b/libs/traits/tests/accrual.rs @@ -1,12 +1,12 @@ use cfg_mocks::pallet_mock_accrual; -use cfg_traits::accrual::{AccrualRate, Adjustment, DebtAccrual}; +use cfg_traits::accrual::{Adjustment, DebtAccrual}; use frame_support::{assert_err, assert_ok}; use sp_arithmetic::fixed_point::FixedU64; use sp_runtime::DispatchError; impl pallet_mock_accrual::Config for Runtime { - type Cache = MockAccrual; - type InnerRate = FixedU64; + type AccRate = FixedU64; + type Cache = (); type Moment = u64; type OuterRate = u8; } @@ -20,45 +20,37 @@ const WRONG_OUTER: u8 = 0; const LAST: u64 = 1000; fn config_mocks() { - MockAccrual::mock_accrual_rate(|outer| match outer { - OUTER_1 => Ok(AccrualRate { - inner: FixedU64::from_float(0.5), - acc: FixedU64::from_float(0.6), - }), - OUTER_2 => Ok(AccrualRate { - inner: FixedU64::from_float(0.3), - acc: FixedU64::from_float(0.2), - }), + MockAccrual::mock_accrual(|outer| match outer { + OUTER_1 => Ok(FixedU64::from_float(0.3)), + OUTER_2 => Ok(FixedU64::from_float(0.6)), _ => Err(ERROR), }); + MockAccrual::mock_accrual_at(|outer, moment| { + assert!(moment < LAST); + match outer { + OUTER_1 => Ok(FixedU64::from_float(0.1)), + OUTER_2 => Ok(FixedU64::from_float(0.2)), + _ => Err(ERROR), + } + }); MockAccrual::mock_last_updated(|| LAST); } #[test] fn wrong_outer() { - const NORM_DEBT: u64 = 100; const WHEN: u64 = 10000; new_test_ext().execute_with(|| { config_mocks(); - assert_err!(MockAccrual::current_debt(WRONG_OUTER, NORM_DEBT), ERROR); - assert_err!( - MockAccrual::calculate_debt(WRONG_OUTER, NORM_DEBT, WHEN), - ERROR - ); - assert_err!( - MockAccrual::adjust_debt(WRONG_OUTER, NORM_DEBT, Adjustment::Increase(42u32)), - ERROR - ); - assert_err!( - MockAccrual::normalize_debt(WRONG_OUTER, OUTER_2, NORM_DEBT), - ERROR - ); + assert_err!(MockAccrual::current_debt(WRONG_OUTER, 1), ERROR); + assert_err!(MockAccrual::calculate_debt(WRONG_OUTER, 1, WHEN), ERROR); assert_err!( - MockAccrual::normalize_debt(OUTER_1, WRONG_OUTER, NORM_DEBT), + MockAccrual::adjust_debt(WRONG_OUTER, 1, Adjustment::Increase(42)), ERROR ); + assert_err!(MockAccrual::normalize_debt(WRONG_OUTER, OUTER_2, 1), ERROR); + assert_err!(MockAccrual::normalize_debt(OUTER_1, WRONG_OUTER, 1), ERROR); }); } @@ -71,17 +63,17 @@ fn calculate_debt() { assert_ok!( MockAccrual::calculate_debt(OUTER_1, NORM_DEBT, LAST), - (NORM_DEBT as f32 * 0.5) as u64 + (NORM_DEBT as f32 * 0.3) as u64 ); assert_ok!( MockAccrual::calculate_debt(OUTER_1, NORM_DEBT, LAST + 100), - (NORM_DEBT as f32 * 0.5) as u64 + (NORM_DEBT as f32 * 0.3) as u64 ); assert_ok!( MockAccrual::calculate_debt(OUTER_1, NORM_DEBT, LAST - 100), - (NORM_DEBT as f32 * 0.5) as u64 + (NORM_DEBT as f32 * 0.1) as u64 ); }); } diff --git a/pallets/interest-accrual/src/benchmarking.rs b/pallets/interest-accrual/src/benchmarking.rs index fe2f3e6ec9..0fee4b36bc 100644 --- a/pallets/interest-accrual/src/benchmarking.rs +++ b/pallets/interest-accrual/src/benchmarking.rs @@ -15,7 +15,6 @@ use frame_benchmarking::{benchmarks, impl_benchmark_test_suite}; use super::*; -use crate::test_utils::*; benchmarks! { // Our logarithmic-time pow implementation is effectively @@ -26,8 +25,8 @@ benchmarks! { calculate_accumulated_rate { let n in 1..25; let now: Moment = (1 << n) - 1; - let rate = interest_rate_per_sec(T::InterestRate::saturating_from_rational(10, 100)).unwrap(); - }: { Pallet::::calculate_accumulated_rate(rate, One::one(), 0, now).unwrap() } + let rate = Pallet::::rate_conversion(T::YearRate::saturating_from_rational(10, 100)).unwrap(); + }: { Pallet::::calculate_accumulated_rate(rate, One::one(), now).unwrap() } verify { } } diff --git a/pallets/interest-accrual/src/lib.rs b/pallets/interest-accrual/src/lib.rs index 8b027ac089..28c9a06e28 100644 --- a/pallets/interest-accrual/src/lib.rs +++ b/pallets/interest-accrual/src/lib.rs @@ -123,21 +123,16 @@ #![cfg_attr(not(feature = "std"), no_std)] use cfg_primitives::{Moment, SECONDS_PER_YEAR}; use cfg_traits::{ + accrual::{RateAccrual, RateCache}, ops::{EnsureAdd, EnsureAddAssign, EnsureDiv, EnsureInto, EnsureMul, EnsureSub}, - InterestAccrual, RateCollection, }; -use cfg_types::adjustments::Adjustment; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::{traits::UnixTime, BoundedVec, RuntimeDebug}; +use frame_support::{traits::UnixTime, RuntimeDebug}; use scale_info::TypeInfo; use sp_arithmetic::traits::{checked_pow, One, Zero}; -use sp_runtime::{ - traits::{AtLeast32BitUnsigned, CheckedAdd, CheckedSub, Saturating}, - ArithmeticError, DispatchError, FixedPointNumber, FixedPointOperand, -}; -use sp_std::{cmp::Ordering, vec::Vec}; +use sp_runtime::{traits::Saturating, ArithmeticError, FixedPointNumber}; +use sp_std::vec::Vec; -pub mod migrations; pub mod weights; pub use weights::WeightInfo; @@ -150,9 +145,6 @@ mod mock; #[cfg(test)] mod tests; -#[cfg(any(feature = "runtime-benchmarks", test))] -mod test_utils; - pub use pallet::*; // TODO: This "magic" number can be removed: tracking issue #1297 @@ -162,14 +154,7 @@ pub use pallet::*; const MAX_INTEREST_RATE: u32 = 2; // Which corresponds to 200%. // Type aliases -type RateDetailsOf = RateDetails<::InterestRate>; - -// Storage types -#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct RateDetailsV1 { - pub accumulated_rate: InterestRate, - pub reference_count: u32, -} +type RateDetailsOf = RateDetails<::SecRate>; #[derive(Encode, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct RateDetails { @@ -207,26 +192,25 @@ pub mod pallet { pub trait Config: frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; - type Balance: Member + /// A fixed-point number which represents an interest rate per year. + type YearRate: Member + Parameter - + AtLeast32BitUnsigned + Default + + core::fmt::Debug + Copy - + MaxEncodedLen - + FixedPointOperand - + From - + From + TypeInfo - + TryInto; + + FixedPointNumber + + MaxEncodedLen + + TryInto; - /// A fixed-point number which represents an interest rate. - type InterestRate: Member + /// A fixed-point number which represents an interest rate per sec. + type SecRate: Member + Parameter + Default + core::fmt::Debug + Copy + TypeInfo - + FixedPointNumber + + FixedPointNumber + MaxEncodedLen; type Time: UnixTime; @@ -254,14 +238,8 @@ pub mod pallet { #[pallet::error] pub enum Error { - /// Emits when the debt calculation failed - DebtCalculationFailed, - /// Emits when the debt adjustment failed - DebtAdjustmentFailed, /// Emits when the interest rate was not used NoSuchRate, - /// Emits when a historic rate was asked for from the future - NotInPast, /// Emits when a rate is not within the valid range InvalidRate, /// Emits when adding a new rate would exceed the storage limits @@ -289,7 +267,7 @@ pub mod pallet { impl Hooks for Pallet { fn on_initialize(_: T::BlockNumber) -> Weight { let then = LastUpdated::::get(); - let now = Self::now(); + let now = T::Time::now().as_secs(); LastUpdated::::set(now); let delta = now - then; let bits = Moment::BITS - delta.leading_zeros(); @@ -310,18 +288,13 @@ pub mod pallet { reference_count, } = rate; - Self::calculate_accumulated_rate( - interest_rate_per_sec, - accumulated_rate, - then, - now, - ) - .ok() - .map(|accumulated_rate| RateDetailsOf:: { - interest_rate_per_sec, - accumulated_rate, - reference_count, - }) + Self::calculate_accumulated_rate(interest_rate_per_sec, accumulated_rate, delta) + .ok() + .map(|accumulated_rate| RateDetailsOf:: { + interest_rate_per_sec, + accumulated_rate, + reference_count, + }) }) .collect(); @@ -335,109 +308,78 @@ pub mod pallet { } impl Pallet { - /// Calculate fastly the current debt using normalized debt * cumulative rate if - /// `when` is exactly `now` (same block). If when is in the past it recomputes - /// the previous cumulative rate. - /// - /// If `when` is further in the past than the last time the - /// normalized debt was adjusted, this will return nonsense - /// (effectively "rewinding the clock" to before the value was - /// valid) - pub fn get_debt( - interest_rate_per_year: T::InterestRate, - normalized_debt: T::Balance, - when: Moment, - ) -> Result { - let rate = Self::get_rate(interest_rate_per_year)?; - let now = LastUpdated::::get(); - - let acc_rate = match when.cmp(&now) { - Ordering::Equal => rate.accumulated_rate, - Ordering::Less => { - let delta = now.ensure_sub(when)?; - let rate_adjustment = - checked_pow(rate.interest_rate_per_sec, delta.ensure_into()?) - .ok_or(ArithmeticError::Overflow)?; - rate.accumulated_rate.ensure_div(rate_adjustment)? - } - Ordering::Greater => return Err(Error::::NotInPast.into()), - }; - - Self::calculate_debt(normalized_debt, acc_rate) - .ok_or_else(|| Error::::DebtCalculationFailed.into()) + pub(crate) fn calculate_accumulated_rate( + interest_rate_per_sec: T::SecRate, + accumulated_rate: T::SecRate, + delta: Moment, + ) -> Result { + // accumulated_rate * interest_rate_per_sec ^ (now - last_updated) + checked_pow(interest_rate_per_sec, delta as usize) + .ok_or(ArithmeticError::Overflow)? // TODO: This line can be remove once #1241 be merged + .ensure_mul(accumulated_rate) } - pub fn do_adjust_normalized_debt( - interest_rate_per_year: T::InterestRate, - normalized_debt: T::Balance, - adjustment: Adjustment, - ) -> Result { - let rate = Self::get_rate(interest_rate_per_year)?; - - let debt = Self::calculate_debt(normalized_debt, rate.accumulated_rate) - .ok_or(Error::::DebtCalculationFailed)?; + fn get_rate( + interest_rate_per_year: T::YearRate, + ) -> Result, DispatchError> { + let interest_rate_per_sec = Self::rate_conversion(interest_rate_per_year)?; + Rates::::get() + .into_iter() + .find(|rate| rate.interest_rate_per_sec == interest_rate_per_sec) + .ok_or_else(|| Error::::NoSuchRate.into()) + } - let new_debt = match adjustment { - Adjustment::Increase(amount) => debt.checked_add(&amount), - Adjustment::Decrease(amount) => debt.checked_sub(&amount), - } - .ok_or(Error::::DebtAdjustmentFailed)?; + pub(crate) fn rate_conversion( + interest_rate_per_year: T::YearRate, + ) -> Result { + interest_rate_per_year + .ensure_into()? + .ensure_div(T::SecRate::saturating_from_integer(SECONDS_PER_YEAR))? + .ensure_add(One::one()) + } + } - let new_normalized_debt = rate - .accumulated_rate - .reciprocal() - .and_then(|inv_rate| inv_rate.checked_mul_int(new_debt)) - .ok_or(Error::::DebtAdjustmentFailed)?; + impl RateAccrual for Pallet { + type AccRate = T::SecRate; + type Cache = RateVec; + type Moment = Moment; + type OuterRate = T::YearRate; - Ok(new_normalized_debt) + fn accrual(interest_rate_per_year: T::YearRate) -> Result { + Ok(Self::get_rate(interest_rate_per_year)?.accumulated_rate) } - pub fn do_renormalize_debt( - old_interest_rate: T::InterestRate, - new_interest_rate: T::InterestRate, - normalized_debt: T::Balance, - ) -> Result { - let old_rate = Self::get_rate(old_interest_rate)?; - let new_rate = Self::get_rate(new_interest_rate)?; - - let debt = Self::calculate_debt(normalized_debt, old_rate.accumulated_rate) - .ok_or(Error::::DebtCalculationFailed)?; - let new_normalized_debt = new_rate - .accumulated_rate - .reciprocal() - .and_then(|inv_rate| inv_rate.checked_mul_int(debt)) - .ok_or(Error::::DebtAdjustmentFailed)?; - - Ok(new_normalized_debt) - } + fn accrual_at( + interest_rate_per_year: T::YearRate, + when: Self::Moment, + ) -> Result { + let rate = Self::get_rate(interest_rate_per_year)?; + let delta = Self::last_updated().ensure_sub(when)?; - /// Calculates the debt using debt = normalized_debt * accumulated_rate - pub(crate) fn calculate_debt( - normalized_debt: T::Balance, - accumulated_rate: T::InterestRate, - ) -> Option { - accumulated_rate.checked_mul_int(normalized_debt) + let rate_adjustment = checked_pow(rate.interest_rate_per_sec, delta.ensure_into()?) + .ok_or(ArithmeticError::Overflow)?; + Ok(rate.accumulated_rate.ensure_div(rate_adjustment)?) } - pub fn calculate_accumulated_rate( - interest_rate_per_sec: Rate, - accumulated_rate: Rate, - last_updated: Moment, - now: Moment, - ) -> Result { - // accumulated_rate * interest_rate_per_sec ^ (now - last_updated) - let time_difference_secs = now.ensure_sub(last_updated)?; - checked_pow(interest_rate_per_sec, time_difference_secs as usize) - .ok_or(ArithmeticError::Overflow)? // TODO: This line can be remove once #1241 be merged - .ensure_mul(accumulated_rate) + fn last_updated() -> Self::Moment { + LastUpdated::::get() } - pub fn now() -> Moment { - T::Time::now().as_secs() + fn validate(interest_rate_per_year: T::YearRate) -> DispatchResult { + let four_decimals = T::YearRate::saturating_from_integer(10000); + let maximum = T::YearRate::saturating_from_integer(MAX_INTEREST_RATE); + ensure!( + interest_rate_per_year <= maximum + && interest_rate_per_year >= Zero::zero() + && (interest_rate_per_year.saturating_mul(four_decimals)).frac() + == Zero::zero(), + Error::::InvalidRate + ); + Ok(()) } - pub fn reference_interest_rate(interest_rate_per_year: T::InterestRate) -> DispatchResult { - let interest_rate_per_sec = unchecked_conversion(interest_rate_per_year)?; + fn reference(interest_rate_per_year: T::YearRate) -> DispatchResult { + let interest_rate_per_sec = Self::rate_conversion(interest_rate_per_year)?; Rates::::try_mutate(|rates| { let rate = rates .iter_mut() @@ -446,7 +388,7 @@ pub mod pallet { match rate { Some(rate) => Ok(rate.reference_count.ensure_add_assign(1)?), None => { - Self::validate_interest_rate(interest_rate_per_year)?; + Self::validate(interest_rate_per_year)?; let new_rate = RateDetailsOf:: { interest_rate_per_sec, @@ -464,10 +406,8 @@ pub mod pallet { }) } - pub fn unreference_interest_rate( - interest_rate_per_year: T::InterestRate, - ) -> DispatchResult { - let interest_rate_per_sec = unchecked_conversion(interest_rate_per_year)?; + fn unreference(interest_rate_per_year: T::YearRate) -> DispatchResult { + let interest_rate_per_sec = Self::rate_conversion(interest_rate_per_year)?; Rates::::try_mutate(|rates| { let idx = rates .iter() @@ -475,6 +415,7 @@ pub mod pallet { .find(|(_, rate)| rate.interest_rate_per_sec == interest_rate_per_sec) .ok_or(Error::::NoSuchRate)? .0; + rates[idx].reference_count = rates[idx].reference_count.saturating_sub(1); if rates[idx].reference_count == 0 { rates.swap_remove(idx); @@ -483,104 +424,24 @@ pub mod pallet { }) } - pub fn get_rate( - interest_rate_per_year: T::InterestRate, - ) -> Result, DispatchError> { - let interest_rate_per_sec = unchecked_conversion(interest_rate_per_year)?; - Rates::::get() - .into_iter() - .find(|rate| rate.interest_rate_per_sec == interest_rate_per_sec) - .ok_or_else(|| Error::::NoSuchRate.into()) - } - - pub(crate) fn validate_interest_rate( - interest_rate_per_year: T::InterestRate, - ) -> DispatchResult { - let four_decimals = T::InterestRate::saturating_from_integer(10000); - let maximum = T::InterestRate::saturating_from_integer(MAX_INTEREST_RATE); - ensure!( - interest_rate_per_year <= maximum - && interest_rate_per_year >= Zero::zero() - && (interest_rate_per_year.saturating_mul(four_decimals)).frac() - == Zero::zero(), - Error::::InvalidRate - ); - Ok(()) + fn cache() -> Self::Cache { + RateVec(Rates::::get()) } } -} - -impl InterestAccrual> for Pallet { - type MaxRateCount = T::MaxRateCount; - type NormalizedDebt = T::Balance; - type Rates = RateVec; - - fn calculate_debt( - interest_rate_per_year: T::InterestRate, - normalized_debt: Self::NormalizedDebt, - when: Moment, - ) -> Result { - Pallet::::get_debt(interest_rate_per_year, normalized_debt, when) - } - - fn adjust_normalized_debt( - interest_rate_per_year: T::InterestRate, - normalized_debt: Self::NormalizedDebt, - adjustment: Adjustment, - ) -> Result { - Pallet::::do_adjust_normalized_debt(interest_rate_per_year, normalized_debt, adjustment) - } - - fn renormalize_debt( - old_interest_rate: T::InterestRate, - new_interest_rate: T::InterestRate, - normalized_debt: Self::NormalizedDebt, - ) -> Result { - Pallet::::do_renormalize_debt(old_interest_rate, new_interest_rate, normalized_debt) - } - - fn reference_rate(interest_rate_per_year: T::InterestRate) -> sp_runtime::DispatchResult { - Pallet::::reference_interest_rate(interest_rate_per_year) - } - - fn unreference_rate(interest_rate_per_year: T::InterestRate) -> sp_runtime::DispatchResult { - Pallet::::unreference_interest_rate(interest_rate_per_year) - } - - fn validate_rate(interest_rate_per_year: T::InterestRate) -> sp_runtime::DispatchResult { - Pallet::::validate_interest_rate(interest_rate_per_year) - } - fn rates() -> Self::Rates { - RateVec(Rates::::get()) - } -} + pub struct RateVec(BoundedVec, T::MaxRateCount>); -pub struct RateVec(BoundedVec, T::MaxRateCount>); - -impl RateCollection for RateVec { - fn current_debt( - &self, - interest_rate_per_year: T::InterestRate, - normalized_debt: T::Balance, - ) -> Result { - let interest_rate_per_sec = unchecked_conversion(interest_rate_per_year)?; - self.0 - .iter() - .find(|rate| rate.interest_rate_per_sec == interest_rate_per_sec) - .ok_or(Error::::NoSuchRate) - .and_then(|rate| { - Pallet::::calculate_debt(normalized_debt, rate.accumulated_rate) - .ok_or(Error::::DebtCalculationFailed) - }) - .map_err(Into::into) + impl RateCache for RateVec { + fn accrual( + &self, + interest_rate_per_year: T::YearRate, + ) -> Result { + let interest_rate_per_sec = Pallet::::rate_conversion(interest_rate_per_year)?; + self.0 + .iter() + .find(|rate| rate.interest_rate_per_sec == interest_rate_per_sec) + .map(|rate| rate.accumulated_rate) + .ok_or(Error::::NoSuchRate.into()) + } } } - -fn unchecked_conversion( - interest_rate_per_year: R, -) -> Result { - interest_rate_per_year - .ensure_div(R::saturating_from_integer(SECONDS_PER_YEAR))? - .ensure_add(One::one()) -} diff --git a/pallets/interest-accrual/src/migrations.rs b/pallets/interest-accrual/src/migrations.rs deleted file mode 100644 index 0cc123c9e9..0000000000 --- a/pallets/interest-accrual/src/migrations.rs +++ /dev/null @@ -1,145 +0,0 @@ -use frame_support::{ - pallet_prelude::{OptionQuery, ValueQuery}, - storage_alias, - traits::{Get, OnRuntimeUpgrade}, - weights::Weight, - Blake2_128Concat, -}; - -use crate::*; - -pub mod v1 { - use super::*; - #[storage_alias] - pub type Rate = StorageMap< - Pallet, - Blake2_128Concat, - ::InterestRate, - RateDetailsV1<::InterestRate>, - OptionQuery, - >; - - #[storage_alias] - pub type RateCount = StorageValue, u32, ValueQuery>; -} - -pub mod v2 { - use super::*; - - pub struct Migration(sp_std::marker::PhantomData); - - impl OnRuntimeUpgrade for Migration { - fn on_runtime_upgrade() -> Weight { - let version = StorageVersion::::get(); - if version != Release::V1 { - log::warn!("Skipping interest_accrual migration: Storage is at incorrect version"); - return T::DbWeight::get().reads(1); - } - let rates: Vec<_> = v1::Rate::::drain() - .map(|(interest_rate_per_sec, details)| { - let RateDetailsV1 { - accumulated_rate, - reference_count, - } = details; - RateDetails { - interest_rate_per_sec, - accumulated_rate, - reference_count, - } - }) - .collect(); - let count: u64 = rates - .len() - .try_into() - .expect("WASM usize will always fit in a u64"); - - Rates::::set( - rates - .try_into() - .expect("Input to this vector was already bounded"), - ); - v1::RateCount::::kill(); - StorageVersion::::set(Release::V2); - - // Reads: storage version + each rate - // Writes: each rate (storage killed), rates count (storage killed), - // rates vector, storage version - T::DbWeight::get().reads_writes(1 + count, 3 + count) - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { - let version = StorageVersion::::get(); - let old_rates: Option< - Vec<( - ::InterestRate, - RateDetailsV1<::InterestRate>, - )>, - > = if version == Release::V1 { - Some(v1::Rate::::iter().collect()) - } else { - None - }; - Ok(old_rates.encode()) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), &'static str> { - let old_rates = Option::< - Vec<( - ::InterestRate, - RateDetailsV1<::InterestRate>, - )>, - >::decode(&mut state.as_ref()) - .map_err(|_| "Error decoding pre-upgrade state")?; - - for (rate_per_sec, old_rate) in old_rates.into_iter().flatten() { - let rate_per_year = rate_per_sec - .checked_sub(&One::one()) - .unwrap() - .saturating_mul(T::InterestRate::saturating_from_integer(SECONDS_PER_YEAR)); - - let new_rate = Pallet::::get_rate(rate_per_year) - .map_err(|_| "Expected rate not found in new state")?; - if new_rate.accumulated_rate != old_rate.accumulated_rate { - return Err("Accumulated rate was not correctly migrated"); - } - - if new_rate.reference_count != old_rate.reference_count { - return Err("Reference count was not correctly migrated"); - } - } - - Ok(()) - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::{mock::*, test_utils::*}; - #[cfg(feature = "try-runtime")] - #[test] - fn migrate_to_v2() { - TestExternalitiesBuilder::default() - .build() - .execute_with(|| { - let rate = interest_rate_per_sec( - ::InterestRate::saturating_from_rational(10, 100), - ) - .unwrap(); - v1::Rate::::insert( - &rate, - RateDetailsV1 { - accumulated_rate: rate.clone(), - reference_count: 42, - }, - ); - StorageVersion::::put(Release::V1); - let state = v2::Migration::::pre_upgrade().unwrap(); - v2::Migration::::on_runtime_upgrade(); - v2::Migration::::post_upgrade(state).unwrap(); - }) - } -} diff --git a/pallets/interest-accrual/src/mock.rs b/pallets/interest-accrual/src/mock.rs index 3e92062800..3bb784fd8e 100644 --- a/pallets/interest-accrual/src/mock.rs +++ b/pallets/interest-accrual/src/mock.rs @@ -59,12 +59,12 @@ parameter_types! { } impl Config for Runtime { - type Balance = Balance; - type InterestRate = Rate; type MaxRateCount = MaxRateCount; type RuntimeEvent = RuntimeEvent; + type SecRate = Rate; type Time = Timestamp; type Weights = (); + type YearRate = Rate; } // Configure a mock runtime to test the pallet. diff --git a/pallets/interest-accrual/src/test_utils.rs b/pallets/interest-accrual/src/test_utils.rs deleted file mode 100644 index 434e2194c8..0000000000 --- a/pallets/interest-accrual/src/test_utils.rs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2021 Centrifuge Foundation (centrifuge.io). -// This file is part of Centrifuge chain project. - -// Centrifuge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version (see http://www.gnu.org/licenses). - -// Centrifuge 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 General Public License for more details. - -use super::*; - -/// returns the seconds in a given normal day -fn seconds_per_day() -> Moment { - 3600 * 24 -} - -/// returns the seconds in a given normal year(365 days) -/// https://docs.centrifuge.io/learn/interest-rate-methodology/ -fn seconds_per_year() -> Moment { - seconds_per_day() * 365 -} - -/// calculates rate per second from the given nominal interest rate -/// https://docs.centrifuge.io/learn/interest-rate-methodology/ -pub fn interest_rate_per_sec(rate_per_annum: Rate) -> Option { - rate_per_annum - .checked_div(&Rate::saturating_from_integer(seconds_per_year() as u128)) - .and_then(|res| res.checked_add(&Rate::one())) -} diff --git a/pallets/interest-accrual/src/tests.rs b/pallets/interest-accrual/src/tests.rs index a736628614..4409d696bd 100644 --- a/pallets/interest-accrual/src/tests.rs +++ b/pallets/interest-accrual/src/tests.rs @@ -23,6 +23,7 @@ use crate::{ #[test] fn test_rate_validation() { + /* let high_rate = Rate::saturating_from_rational(300000, 10000); let min_rate = Rate::saturating_from_rational(1, 10000); let normal_rate = Rate::saturating_from_rational(5, 100); @@ -34,4 +35,5 @@ fn test_rate_validation() { assert!(Pallet::::validate_interest_rate(One::one()).is_ok()); assert!(Pallet::::validate_interest_rate(Zero::zero()).is_ok()); assert!(Pallet::::validate_interest_rate(too_many_decimals).is_err()); + */ } From 6f854a41b05b3e2ea657013e8faea9cc9e2eca80 Mon Sep 17 00:00:00 2001 From: lemunozm Date: Mon, 10 Apr 2023 12:01:20 +0200 Subject: [PATCH 5/5] update loans-ref to use new traits --- libs/mocks/src/accrual.rs | 14 ++++--- libs/traits/src/accrual.rs | 19 ++++++---- libs/traits/src/lib.rs | 54 --------------------------- libs/traits/tests/accrual.rs | 25 +++++++------ libs/types/src/adjustments.rs | 16 -------- libs/types/src/lib.rs | 1 - pallets/interest-accrual/src/lib.rs | 9 +++-- pallets/loans-ref/src/benchmarking.rs | 13 ++----- pallets/loans-ref/src/lib.rs | 18 ++++----- pallets/loans-ref/src/mock.rs | 4 +- pallets/loans-ref/src/types.rs | 5 +-- 11 files changed, 53 insertions(+), 125 deletions(-) delete mode 100644 libs/types/src/adjustments.rs diff --git a/libs/mocks/src/accrual.rs b/libs/mocks/src/accrual.rs index 48920f6a60..01c67249f6 100644 --- a/libs/mocks/src/accrual.rs +++ b/libs/mocks/src/accrual.rs @@ -10,6 +10,7 @@ pub mod pallet { type AccRate; type Moment; type Cache: RateCache; + type MaxRateCount: Get; } #[pallet::pallet] @@ -41,15 +42,15 @@ pub mod pallet { register_call!(move |()| f()); } - pub fn mock_validate(f: impl Fn(T::OuterRate) -> DispatchResult + 'static) { + pub fn mock_validate_rate(f: impl Fn(T::OuterRate) -> DispatchResult + 'static) { register_call!(f); } - pub fn mock_reference(f: impl Fn(T::OuterRate) -> DispatchResult + 'static) { + pub fn mock_reference_rate(f: impl Fn(T::OuterRate) -> DispatchResult + 'static) { register_call!(f); } - pub fn mock_unreference(f: impl Fn(T::OuterRate) -> DispatchResult + 'static) { + pub fn mock_unreference_rate(f: impl Fn(T::OuterRate) -> DispatchResult + 'static) { register_call!(f); } @@ -61,6 +62,7 @@ pub mod pallet { impl RateAccrual for Pallet { type AccRate = T::AccRate; type Cache = T::Cache; + type MaxRateCount = T::MaxRateCount; type Moment = T::Moment; type OuterRate = T::OuterRate; @@ -76,15 +78,15 @@ pub mod pallet { execute_call!(()) } - fn validate(a: T::OuterRate) -> DispatchResult { + fn validate_rate(a: T::OuterRate) -> DispatchResult { execute_call!(a) } - fn reference(a: T::OuterRate) -> DispatchResult { + fn reference_rate(a: T::OuterRate) -> DispatchResult { execute_call!(a) } - fn unreference(a: T::OuterRate) -> DispatchResult { + fn unreference_rate(a: T::OuterRate) -> DispatchResult { execute_call!(a) } diff --git a/libs/traits/src/accrual.rs b/libs/traits/src/accrual.rs index b28df1a909..2e05ff3ca1 100644 --- a/libs/traits/src/accrual.rs +++ b/libs/traits/src/accrual.rs @@ -1,5 +1,5 @@ use sp_arithmetic::traits::{One, Zero}; -use sp_runtime::{DispatchError, DispatchResult, FixedPointNumber, FixedPointOperand}; +use sp_runtime::{traits::Get, DispatchError, DispatchResult, FixedPointNumber, FixedPointOperand}; use sp_std::cmp::Ordering; use crate::ops::{EnsureAdd, EnsureDiv, EnsureFixedPointNumber, EnsureSub}; @@ -24,6 +24,11 @@ pub trait RateAccrual { /// Type used to cache the own collection of rates type Cache: RateCache; + /// 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; + /// Returns an accrual rate identified by an outer rate fn accrual(outer: Self::OuterRate) -> Result; @@ -34,13 +39,13 @@ pub trait RateAccrual { ) -> Result; /// Check if the outer rate is valid - fn validate(outer: Self::OuterRate) -> DispatchResult; + fn validate_rate(outer: Self::OuterRate) -> DispatchResult; /// Reference a outer rate in the system to start using it - fn reference(outer: Self::OuterRate) -> DispatchResult; + fn reference_rate(outer: Self::OuterRate) -> DispatchResult; /// Unreference a outer rate indicating to the system that it's no longer in use - fn unreference(outer: Self::OuterRate) -> DispatchResult; + fn unreference_rate(outer: Self::OuterRate) -> DispatchResult; /// Returns last moment the collection was updated fn last_updated() -> Self::Moment; @@ -81,7 +86,7 @@ where let now = Self::last_updated(); let acc = match when.cmp(&now) { Ordering::Equal => Self::accrual(outer), - Ordering::Less => Self::accrual_at(outer, now), + 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")) @@ -93,7 +98,7 @@ where } /// Increase or decrease the amount, returing the new normalized debt - fn adjust_debt>( + fn adjust_normalized_debt>( outer: Self::OuterRate, norm_debt: Debt, adjustment: Adjustment, @@ -112,7 +117,7 @@ where } /// Re-normalize a debt for a new interest rate, returing the new normalize_debt - fn normalize_debt( + fn renormalize_debt( old_outer: Self::OuterRate, new_outer: Self::OuterRate, norm_debt: Debt, diff --git a/libs/traits/src/lib.rs b/libs/traits/src/lib.rs index 635e0f6014..6afffd239d 100644 --- a/libs/traits/src/lib.rs +++ b/libs/traits/src/lib.rs @@ -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}, @@ -225,59 +224,6 @@ pub trait CurrencyPrice { ) -> Option>; } -/// A trait that can be used to calculate interest accrual for debt -pub trait InterestAccrual { - /// 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; - type NormalizedDebt: Member + Parameter + MaxEncodedLen + TypeInfo + Copy + Zero; - type Rates: RateCollection; - - /// Calculate the debt at an specific moment - fn calculate_debt( - interest_rate_per_year: InterestRate, - normalized_debt: Self::NormalizedDebt, - when: Moment, - ) -> Result; - - /// Increase or decrease the normalized debt - fn adjust_normalized_debt( - interest_rate_per_year: InterestRate, - normalized_debt: Self::NormalizedDebt, - adjustment: Adjustment, - ) -> Result; - - /// 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; - - /// 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 { - /// Calculate the current debt using normalized debt * cumulative rate - fn current_debt( - &self, - interest_rate_per_sec: InterestRate, - normalized_debt: NormalizedDebt, - ) -> Result; -} - pub trait Permissions { type Scope; type Role; diff --git a/libs/traits/tests/accrual.rs b/libs/traits/tests/accrual.rs index 6043be99ae..94cf08a53e 100644 --- a/libs/traits/tests/accrual.rs +++ b/libs/traits/tests/accrual.rs @@ -7,11 +7,12 @@ use sp_runtime::DispatchError; impl pallet_mock_accrual::Config for Runtime { type AccRate = FixedU64; type Cache = (); + type MaxRateCount = ConstU32<0>; type Moment = u64; type OuterRate = u8; } -cfg_mocks::make_runtime_for_mock!(Runtime, MockAccrual, pallet_mock_accrual, new_test_ext); +cfg_mocks::make_runtime_for_mock!(Runtime, Mock, pallet_mock_accrual, new_test_ext); const ERROR: DispatchError = DispatchError::Other("Error"); const OUTER_1: u8 = 1; @@ -20,12 +21,12 @@ const WRONG_OUTER: u8 = 0; const LAST: u64 = 1000; fn config_mocks() { - MockAccrual::mock_accrual(|outer| match outer { + Mock::mock_accrual(|outer| match outer { OUTER_1 => Ok(FixedU64::from_float(0.3)), OUTER_2 => Ok(FixedU64::from_float(0.6)), _ => Err(ERROR), }); - MockAccrual::mock_accrual_at(|outer, moment| { + Mock::mock_accrual_at(|outer, moment| { assert!(moment < LAST); match outer { OUTER_1 => Ok(FixedU64::from_float(0.1)), @@ -33,7 +34,7 @@ fn config_mocks() { _ => Err(ERROR), } }); - MockAccrual::mock_last_updated(|| LAST); + Mock::mock_last_updated(|| LAST); } #[test] @@ -43,14 +44,14 @@ fn wrong_outer() { new_test_ext().execute_with(|| { config_mocks(); - assert_err!(MockAccrual::current_debt(WRONG_OUTER, 1), ERROR); - assert_err!(MockAccrual::calculate_debt(WRONG_OUTER, 1, WHEN), ERROR); + assert_err!(Mock::current_debt(WRONG_OUTER, 1), ERROR); + assert_err!(Mock::calculate_debt(WRONG_OUTER, 1, WHEN), ERROR); assert_err!( - MockAccrual::adjust_debt(WRONG_OUTER, 1, Adjustment::Increase(42)), + Mock::adjust_normalized_debt(WRONG_OUTER, 1, Adjustment::Increase(42)), ERROR ); - assert_err!(MockAccrual::normalize_debt(WRONG_OUTER, OUTER_2, 1), ERROR); - assert_err!(MockAccrual::normalize_debt(OUTER_1, WRONG_OUTER, 1), ERROR); + assert_err!(Mock::renormalize_debt(WRONG_OUTER, OUTER_2, 1), ERROR); + assert_err!(Mock::renormalize_debt(OUTER_1, WRONG_OUTER, 1), ERROR); }); } @@ -62,17 +63,17 @@ fn calculate_debt() { config_mocks(); assert_ok!( - MockAccrual::calculate_debt(OUTER_1, NORM_DEBT, LAST), + Mock::calculate_debt(OUTER_1, NORM_DEBT, LAST), (NORM_DEBT as f32 * 0.3) as u64 ); assert_ok!( - MockAccrual::calculate_debt(OUTER_1, NORM_DEBT, LAST + 100), + Mock::calculate_debt(OUTER_1, NORM_DEBT, LAST + 100), (NORM_DEBT as f32 * 0.3) as u64 ); assert_ok!( - MockAccrual::calculate_debt(OUTER_1, NORM_DEBT, LAST - 100), + Mock::calculate_debt(OUTER_1, NORM_DEBT, LAST - 100), (NORM_DEBT as f32 * 0.1) as u64 ); }); diff --git a/libs/types/src/adjustments.rs b/libs/types/src/adjustments.rs deleted file mode 100644 index 957cb9aa5f..0000000000 --- a/libs/types/src/adjustments.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2021 Centrifuge Foundation (centrifuge.io). -// -// This file is part of the Centrifuge chain project. -// Centrifuge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version (see http://www.gnu.org/licenses). -// Centrifuge 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 General Public License for more details. - -pub enum Adjustment { - Increase(Amount), - Decrease(Amount), -} diff --git a/libs/types/src/lib.rs b/libs/types/src/lib.rs index 301e7e1505..c7ae4a722d 100644 --- a/libs/types/src/lib.rs +++ b/libs/types/src/lib.rs @@ -15,7 +15,6 @@ #![allow(clippy::unit_arg)] ///! Common-types of the Centrifuge chain. -pub mod adjustments; pub mod consts; pub mod epoch; pub mod fee_keys; diff --git a/pallets/interest-accrual/src/lib.rs b/pallets/interest-accrual/src/lib.rs index 28c9a06e28..db7800ec0e 100644 --- a/pallets/interest-accrual/src/lib.rs +++ b/pallets/interest-accrual/src/lib.rs @@ -342,6 +342,7 @@ pub mod pallet { impl RateAccrual for Pallet { type AccRate = T::SecRate; type Cache = RateVec; + type MaxRateCount = T::MaxRateCount; type Moment = Moment; type OuterRate = T::YearRate; @@ -365,7 +366,7 @@ pub mod pallet { LastUpdated::::get() } - fn validate(interest_rate_per_year: T::YearRate) -> DispatchResult { + fn validate_rate(interest_rate_per_year: T::YearRate) -> DispatchResult { let four_decimals = T::YearRate::saturating_from_integer(10000); let maximum = T::YearRate::saturating_from_integer(MAX_INTEREST_RATE); ensure!( @@ -378,7 +379,7 @@ pub mod pallet { Ok(()) } - fn reference(interest_rate_per_year: T::YearRate) -> DispatchResult { + fn reference_rate(interest_rate_per_year: T::YearRate) -> DispatchResult { let interest_rate_per_sec = Self::rate_conversion(interest_rate_per_year)?; Rates::::try_mutate(|rates| { let rate = rates @@ -388,7 +389,7 @@ pub mod pallet { match rate { Some(rate) => Ok(rate.reference_count.ensure_add_assign(1)?), None => { - Self::validate(interest_rate_per_year)?; + Self::validate_rate(interest_rate_per_year)?; let new_rate = RateDetailsOf:: { interest_rate_per_sec, @@ -406,7 +407,7 @@ pub mod pallet { }) } - fn unreference(interest_rate_per_year: T::YearRate) -> DispatchResult { + fn unreference_rate(interest_rate_per_year: T::YearRate) -> DispatchResult { let interest_rate_per_sec = Self::rate_conversion(interest_rate_per_year)?; Rates::::try_mutate(|rates| { let idx = rates diff --git a/pallets/loans-ref/src/benchmarking.rs b/pallets/loans-ref/src/benchmarking.rs index 46ba2b459d..c6882b6b6b 100644 --- a/pallets/loans-ref/src/benchmarking.rs +++ b/pallets/loans-ref/src/benchmarking.rs @@ -1,9 +1,6 @@ use cfg_primitives::CFG; -use cfg_traits::{InterestAccrual, Permissions, PoolBenchmarkHelper}; -use cfg_types::{ - adjustments::Adjustment, - permissions::{PermissionScope, PoolRole, Role}, -}; +use cfg_traits::{accrual::RateAccrual, Permissions, PoolBenchmarkHelper}; +use cfg_types::permissions::{PermissionScope, PoolRole, Role}; use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; use frame_support::traits::{ tokens::nonfungibles::{Create, Mutate}, @@ -25,11 +22,7 @@ const COLLECION_ID: u16 = 42; const COLLATERAL_VALUE: u128 = 1_000_000; const FUNDS: u128 = 1_000_000_000; -type MaxRateCountOf = <::InterestAccrual as InterestAccrual< - ::Rate, - ::Balance, - Adjustment<::Balance>, ->>::MaxRateCount; +type MaxRateCountOf = <::InterestAccrual as RateAccrual>::MaxRateCount; struct Helper(sp_std::marker::PhantomData); impl Helper diff --git a/pallets/loans-ref/src/lib.rs b/pallets/loans-ref/src/lib.rs index 3695839cb7..d4ffbd92cd 100644 --- a/pallets/loans-ref/src/lib.rs +++ b/pallets/loans-ref/src/lib.rs @@ -58,13 +58,11 @@ pub use weights::WeightInfo; pub mod pallet { use cfg_primitives::Moment; use cfg_traits::{ + accrual::{DebtAccrual, RateAccrual}, ops::{EnsureAdd, EnsureAddAssign, EnsureInto}, - InterestAccrual, Permissions, PoolInspect, PoolNAV, PoolReserve, - }; - use cfg_types::{ - adjustments::Adjustment, - permissions::{PermissionScope, PoolRole, Role}, + Permissions, PoolInspect, PoolNAV, PoolReserve, }; + use cfg_types::permissions::{PermissionScope, PoolRole, Role}; use frame_support::{ pallet_prelude::*, traits::{ @@ -167,11 +165,11 @@ pub mod pallet { >; /// Used to calculate interest accrual for debt. - type InterestAccrual: InterestAccrual< - Self::Rate, + type InterestAccrual: DebtAccrual< Self::Balance, - Adjustment, - NormalizedDebt = Self::Balance, + OuterRate = Self::Rate, + Moment = Moment, + AccRate = Self::Rate, >; /// Max number of active loans per pool. @@ -707,7 +705,7 @@ pub mod pallet { fn portfolio_valuation_for_pool( pool_id: PoolIdOf, ) -> Result<(T::Balance, u32), DispatchError> { - let rates = T::InterestAccrual::rates(); + let rates = T::InterestAccrual::cache(); let loans = ActiveLoans::::get(pool_id); let count = loans.len().ensure_into()?; let value = loans.into_iter().try_fold( diff --git a/pallets/loans-ref/src/mock.rs b/pallets/loans-ref/src/mock.rs index 49c13d688f..dba7f2b7ac 100644 --- a/pallets/loans-ref/src/mock.rs +++ b/pallets/loans-ref/src/mock.rs @@ -148,12 +148,12 @@ impl pallet_uniques::Config for Runtime { } impl pallet_interest_accrual::Config for Runtime { - type Balance = Balance; - type InterestRate = Rate; type MaxRateCount = MaxActiveLoansPerPool; type RuntimeEvent = RuntimeEvent; + type SecRate = Rate; type Time = Timer; type Weights = (); + type YearRate = Rate; } impl pallet_mock_pools::Config for Runtime { diff --git a/pallets/loans-ref/src/types.rs b/pallets/loans-ref/src/types.rs index 82acd2a81f..9141d15847 100644 --- a/pallets/loans-ref/src/types.rs +++ b/pallets/loans-ref/src/types.rs @@ -13,13 +13,12 @@ use cfg_primitives::{Moment, SECONDS_PER_DAY}; use cfg_traits::{ + accrual::{Adjustment, DebtAccrual, DebtCache, RateAccrual}, ops::{ EnsureAdd, EnsureAddAssign, EnsureFixedPointNumber, EnsureInto, EnsureMul, EnsureSub, EnsureSubAssign, }, - InterestAccrual, RateCollection, }; -use cfg_types::adjustments::Adjustment; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ ensure, @@ -502,7 +501,7 @@ impl ActiveLoan { /// it get it from a cache previously fetched. pub fn current_present_value(&self, rate_cache: &C) -> Result where - C: RateCollection, + C: DebtCache, { let debt = rate_cache.current_debt(self.info.interest_rate, self.normalized_debt)?; self.present_value(debt, T::Time::now().as_secs())