From bbfb3e4bcce73eec41ab3c10eb207cc47bc7df95 Mon Sep 17 00:00:00 2001 From: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> Date: Fri, 17 Mar 2023 16:54:01 +0100 Subject: [PATCH 1/2] feat(loans): mutation testing decorations --- Cargo.lock | 40 ++++++++++++++++++ crates/loans/Cargo.toml | 1 + crates/loans/src/farming.rs | 9 ++++ crates/loans/src/interest.rs | 8 ++++ crates/loans/src/lib.rs | 76 ++++++++++++++++++++++++++++++++++ crates/loans/src/rate_model.rs | 12 ++++++ crates/loans/src/types.rs | 8 ++++ 7 files changed, 154 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 0640365c30..c9f2390bd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4510,6 +4510,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + [[package]] name = "jsonrpsee" version = "0.16.2" @@ -5535,6 +5541,7 @@ dependencies = [ "frame-system", "interbtc-primitives", "mocktopus", + "mutagen", "num-traits", "orml-oracle", "orml-tokens", @@ -5954,6 +5961,39 @@ dependencies = [ "unsigned-varint", ] +[[package]] +name = "mutagen" +version = "0.2.0" +source = "git+https://github.com/llogiq/mutagen#a6377c4c3f360afeb7a287c1c17e4b69456d5f53" +dependencies = [ + "mutagen-core", + "mutagen-transform", +] + +[[package]] +name = "mutagen-core" +version = "0.2.0" +source = "git+https://github.com/llogiq/mutagen#a6377c4c3f360afeb7a287c1c17e4b69456d5f53" +dependencies = [ + "anyhow", + "json", + "lazy_static", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", +] + +[[package]] +name = "mutagen-transform" +version = "0.2.0" +source = "git+https://github.com/llogiq/mutagen#a6377c4c3f360afeb7a287c1c17e4b69456d5f53" +dependencies = [ + "mutagen-core", + "proc-macro2", +] + [[package]] name = "nalgebra" version = "0.27.1" diff --git a/crates/loans/Cargo.toml b/crates/loans/Cargo.toml index 7280102afb..1b35186d6e 100644 --- a/crates/loans/Cargo.toml +++ b/crates/loans/Cargo.toml @@ -43,6 +43,7 @@ orml-oracle = { git = "https://github.com/open-web3-stack/open-runtime-module-li mocktopus = "0.8.0" visibility = { version = "0.0.1" } currency = { path = "../currency", features = ["testing-utils"] } +mutagen = { git = "https://github.com/llogiq/mutagen" } [features] default = ["std"] diff --git a/crates/loans/src/farming.rs b/crates/loans/src/farming.rs index 59225496b1..1712f61c4f 100644 --- a/crates/loans/src/farming.rs +++ b/crates/loans/src/farming.rs @@ -24,10 +24,12 @@ impl Pallet { T::PalletId::get().into_sub_account_truncating(REWARD_SUB_ACCOUNT) } + #[cfg_attr(test, mutate)] fn reward_scale() -> u128 { 10_u128.pow(12) } + #[cfg_attr(test, mutate)] fn calculate_reward_delta_index( delta_block: T::BlockNumber, reward_speed: BalanceOf, @@ -47,6 +49,7 @@ impl Pallet { Ok(delta_index) } + #[cfg_attr(test, mutate)] fn calculate_reward_delta( share: BalanceOf, reward_delta_index: u128, @@ -60,6 +63,7 @@ impl Pallet { Ok(reward_delta) } + #[cfg_attr(test, mutate)] pub(crate) fn update_reward_supply_index(asset_id: CurrencyId) -> DispatchResult { let current_block_number = >::block_number(); RewardSupplyState::::try_mutate(asset_id, |supply_state| -> DispatchResult { @@ -82,6 +86,7 @@ impl Pallet { }) } + #[cfg_attr(test, mutate)] pub(crate) fn update_reward_borrow_index(asset_id: CurrencyId) -> DispatchResult { let current_block_number = >::block_number(); RewardBorrowState::::try_mutate(asset_id, |borrow_state| -> DispatchResult { @@ -106,6 +111,7 @@ impl Pallet { }) } + #[cfg_attr(test, mutate)] pub(crate) fn distribute_supplier_reward(asset_id: CurrencyId, supplier: &T::AccountId) -> DispatchResult { RewardSupplierIndex::::try_mutate(asset_id, supplier, |supplier_index| -> DispatchResult { let supply_state = RewardSupplyState::::get(asset_id); @@ -137,6 +143,7 @@ impl Pallet { }) } + #[cfg_attr(test, mutate)] pub(crate) fn distribute_borrower_reward(asset_id: CurrencyId, borrower: &T::AccountId) -> DispatchResult { RewardBorrowerIndex::::try_mutate(asset_id, borrower, |borrower_index| -> DispatchResult { let borrow_state = RewardBorrowState::::get(asset_id); @@ -166,6 +173,7 @@ impl Pallet { }) } + #[cfg_attr(test, mutate)] pub(crate) fn collect_market_reward(asset_id: CurrencyId, user: &T::AccountId) -> DispatchResult { Self::update_reward_supply_index(asset_id)?; Self::distribute_supplier_reward(asset_id, user)?; @@ -176,6 +184,7 @@ impl Pallet { Ok(()) } + #[cfg_attr(test, mutate)] pub(crate) fn pay_reward(user: &T::AccountId) -> DispatchResult { let pool_account = Self::reward_account_id(); let reward_asset = T::RewardAssetId::get(); diff --git a/crates/loans/src/interest.rs b/crates/loans/src/interest.rs index 095113c14d..48c5f7c0b8 100644 --- a/crates/loans/src/interest.rs +++ b/crates/loans/src/interest.rs @@ -23,6 +23,7 @@ use crate::*; impl Pallet { /// Accrue interest and update corresponding storage #[cfg_attr(any(test, feature = "integration-tests"), visibility::make(pub))] + #[cfg_attr(test, mutate)] pub(crate) fn accrue_interest(asset_id: CurrencyId) -> DispatchResult { let now = T::UnixTime::now().as_secs(); let last_accrued_interest_time = Self::last_accrued_interest_time(asset_id); @@ -64,6 +65,7 @@ impl Pallet { Ok(()) } + #[cfg_attr(test, mutate)] pub fn get_market_status( asset_id: CurrencyId, ) -> Result<(Rate, Rate, Rate, Ratio, BalanceOf, BalanceOf, FixedU128), DispatchError> { @@ -124,6 +126,7 @@ impl Pallet { /// Update the exchange rate according to the totalCash, totalBorrows and totalSupply. /// This function does not accrue interest before calculating the exchange rate. /// exchangeRate = (totalCash + totalBorrows - totalReserves) / totalSupply + #[cfg_attr(test, mutate)] pub fn exchange_rate_stored(asset_id: CurrencyId) -> Result { let total_supply = Self::total_supply(asset_id)?; let total_cash = Self::get_total_cash(asset_id); @@ -136,6 +139,7 @@ impl Pallet { /// Calculate the borrowing utilization ratio of the specified market /// /// utilizationRatio = totalBorrows / (totalCash + totalBorrows − totalReserves) + #[cfg_attr(test, mutate)] pub(crate) fn calc_utilization_ratio( cash: &Amount, borrows: &Amount, @@ -155,6 +159,7 @@ impl Pallet { /// This ensures the exchange rate cannot be attacked by a deposit so big that /// subsequent deposits to receive zero lendTokens (because of rounding down). See this /// PR for more details: https://github.com/parallel-finance/parallel/pull/1552/files + #[cfg_attr(test, mutate)] pub(crate) fn ensure_valid_exchange_rate(exchange_rate: Rate) -> DispatchResult { ensure!( exchange_rate >= Self::min_exchange_rate() && exchange_rate < Self::max_exchange_rate(), @@ -164,6 +169,7 @@ impl Pallet { Ok(()) } + #[cfg_attr(test, mutate)] pub(crate) fn update_last_accrued_interest_time(asset_id: CurrencyId, time: Timestamp) -> DispatchResult { LastAccruedInterestTime::::try_mutate(asset_id, |last_time| -> DispatchResult { *last_time = time; @@ -171,6 +177,7 @@ impl Pallet { }) } + #[cfg_attr(test, mutate)] pub(crate) fn accrue_index(borrow_rate: Rate, index: Rate, delta_time: Timestamp) -> Result { // Compound interest: // new_index = old_index * (1 + annual_borrow_rate / SECONDS_PER_YEAR) ^ delta_time @@ -184,6 +191,7 @@ impl Pallet { Ok(index.checked_mul(&compounded_rate).ok_or(ArithmeticError::Overflow)?) } + #[cfg_attr(test, mutate)] fn calculate_exchange_rate( total_supply: &Amount, total_cash: &Amount, diff --git a/crates/loans/src/lib.rs b/crates/loans/src/lib.rs index 795314d2fe..3527626b02 100644 --- a/crates/loans/src/lib.rs +++ b/crates/loans/src/lib.rs @@ -59,6 +59,9 @@ pub use default_weights::WeightInfo; pub use orml_traits::currency::{OnDeposit, OnSlash, OnTransfer}; pub use types::{BorrowSnapshot, EarnedSnapshot, Market, MarketState, RewardMarketState}; +#[cfg(test)] +use mutagen::mutate; + #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -121,6 +124,7 @@ pub struct OnSlashHook(marker::PhantomData); impl OnSlash, BalanceOf> for OnSlashHook { /// Whenever a lend_token balance is mutated, the supplier incentive rewards accumulated up to that point /// have to be distributed. + #[cfg_attr(test, mutate)] fn on_slash(currency_id: CurrencyId, account_id: &T::AccountId, amount: BalanceOf) { if currency_id.is_lend_token() { // Note that wherever `on_slash` is called in the lending pallet, and the `account_id` has non-zero @@ -150,6 +154,7 @@ pub struct PreDeposit(marker::PhantomData); impl OnDeposit, BalanceOf> for PreDeposit { /// Whenever a lend_token balance is mutated, the supplier incentive rewards accumulated up to that point /// have to be distributed. + #[cfg_attr(test, mutate)] fn on_deposit(currency_id: CurrencyId, account_id: &T::AccountId, _amount: BalanceOf) -> DispatchResult { if currency_id.is_lend_token() { let underlying_id = Pallet::::underlying_id(currency_id)?; @@ -162,6 +167,7 @@ impl OnDeposit, BalanceOf> for PreDepo pub struct PostDeposit(marker::PhantomData); impl OnDeposit, BalanceOf> for PostDeposit { + #[cfg_attr(test, mutate)] fn on_deposit(currency_id: CurrencyId, account_id: &T::AccountId, amount: BalanceOf) -> DispatchResult { if currency_id.is_lend_token() { Pallet::::lock_if_account_deposited(account_id, &Amount::new(amount, currency_id))?; @@ -174,6 +180,7 @@ pub struct PreTransfer(marker::PhantomData); impl OnTransfer, BalanceOf> for PreTransfer { /// Whenever a lend_token balance is mutated, the supplier incentive rewards accumulated up to that point /// have to be distributed. + #[cfg_attr(test, mutate)] fn on_transfer( currency_id: CurrencyId, from: &T::AccountId, @@ -195,6 +202,7 @@ impl OnTransfer, BalanceOf> for PostTr /// If an account has locked their lend_token balance as collateral, any incoming lend_tokens /// have to be automatically locked as well, in order to enforce a "collateral toggle" that /// offers the same UX as Compound V2's lending protocol implementation. + #[cfg_attr(test, mutate)] fn on_transfer( currency_id: CurrencyId, _from: &T::AccountId, @@ -648,6 +656,7 @@ pub mod pallet { #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::add_market())] #[transactional] + #[cfg_attr(test, mutate)] pub fn add_market( origin: OriginFor, asset_id: CurrencyId, @@ -717,6 +726,7 @@ pub mod pallet { #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::activate_market())] #[transactional] + #[cfg_attr(test, mutate)] pub fn activate_market(origin: OriginFor, asset_id: CurrencyId) -> DispatchResultWithPostInfo { T::UpdateOrigin::ensure_origin(origin)?; // TODO: if the market is already active throw an error, @@ -742,6 +752,7 @@ pub mod pallet { #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::update_rate_model())] #[transactional] + #[cfg_attr(test, mutate)] pub fn update_rate_model( origin: OriginFor, asset_id: CurrencyId, @@ -775,6 +786,7 @@ pub mod pallet { #[pallet::call_index(3)] #[pallet::weight(::WeightInfo::update_market())] #[transactional] + #[cfg_attr(test, mutate)] pub fn update_market( origin: OriginFor, asset_id: CurrencyId, @@ -851,6 +863,7 @@ pub mod pallet { #[pallet::call_index(4)] #[pallet::weight(::WeightInfo::force_update_market())] #[transactional] + #[cfg_attr(test, mutate)] pub fn force_update_market( origin: OriginFor, asset_id: CurrencyId, @@ -883,6 +896,7 @@ pub mod pallet { #[pallet::call_index(5)] #[pallet::weight(::WeightInfo::add_reward())] #[transactional] + #[cfg_attr(test, mutate)] pub fn add_reward(origin: OriginFor, amount: BalanceOf) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; ensure!(!amount.is_zero(), Error::::InvalidAmount); @@ -908,6 +922,7 @@ pub mod pallet { #[pallet::call_index(6)] #[pallet::weight(::WeightInfo::update_market_reward_speed())] #[transactional] + #[cfg_attr(test, mutate)] pub fn update_market_reward_speed( origin: OriginFor, asset_id: CurrencyId, @@ -951,6 +966,7 @@ pub mod pallet { #[pallet::call_index(7)] #[pallet::weight(::WeightInfo::claim_reward())] #[transactional] + #[cfg_attr(test, mutate)] pub fn claim_reward(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; @@ -969,6 +985,7 @@ pub mod pallet { #[pallet::call_index(8)] #[pallet::weight(::WeightInfo::claim_reward_for_market())] #[transactional] + #[cfg_attr(test, mutate)] pub fn claim_reward_for_market(origin: OriginFor, asset_id: CurrencyId) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; @@ -987,6 +1004,7 @@ pub mod pallet { #[pallet::call_index(9)] #[pallet::weight(::WeightInfo::mint())] #[transactional] + #[cfg_attr(test, mutate)] pub fn mint( origin: OriginFor, asset_id: CurrencyId, @@ -1007,6 +1025,7 @@ pub mod pallet { #[pallet::call_index(10)] #[pallet::weight(::WeightInfo::redeem())] #[transactional] + #[cfg_attr(test, mutate)] pub fn redeem( origin: OriginFor, asset_id: CurrencyId, @@ -1031,6 +1050,7 @@ pub mod pallet { #[pallet::call_index(11)] #[pallet::weight(::WeightInfo::redeem_all())] #[transactional] + #[cfg_attr(test, mutate)] pub fn redeem_all(origin: OriginFor, asset_id: CurrencyId) -> DispatchResultWithPostInfo { let who = ensure_signed(origin.clone())?; @@ -1052,6 +1072,7 @@ pub mod pallet { #[pallet::call_index(12)] #[pallet::weight(::WeightInfo::borrow())] #[transactional] + #[cfg_attr(test, mutate)] pub fn borrow( origin: OriginFor, asset_id: CurrencyId, @@ -1072,6 +1093,7 @@ pub mod pallet { #[pallet::call_index(13)] #[pallet::weight(::WeightInfo::repay_borrow())] #[transactional] + #[cfg_attr(test, mutate)] pub fn repay_borrow( origin: OriginFor, asset_id: CurrencyId, @@ -1091,6 +1113,7 @@ pub mod pallet { #[pallet::call_index(14)] #[pallet::weight(::WeightInfo::repay_borrow_all())] #[transactional] + #[cfg_attr(test, mutate)] pub fn repay_borrow_all(origin: OriginFor, asset_id: CurrencyId) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; Self::ensure_active_market(asset_id)?; @@ -1116,6 +1139,7 @@ pub mod pallet { #[pallet::call_index(15)] #[pallet::weight(::WeightInfo::deposit_all_collateral())] #[transactional] + #[cfg_attr(test, mutate)] pub fn deposit_all_collateral(origin: OriginFor, asset_id: CurrencyId) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let free_lend_tokens = Self::free_lend_tokens(asset_id, &who)?; @@ -1138,6 +1162,7 @@ pub mod pallet { #[pallet::call_index(16)] #[pallet::weight(::WeightInfo::withdraw_all_collateral())] #[transactional] + #[cfg_attr(test, mutate)] pub fn withdraw_all_collateral(origin: OriginFor, asset_id: CurrencyId) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; @@ -1164,6 +1189,7 @@ pub mod pallet { #[pallet::call_index(17)] #[pallet::weight(::WeightInfo::liquidate_borrow())] #[transactional] + #[cfg_attr(test, mutate)] pub fn liquidate_borrow( origin: OriginFor, borrower: T::AccountId, @@ -1193,6 +1219,7 @@ pub mod pallet { #[pallet::call_index(18)] #[pallet::weight(::WeightInfo::add_reserves())] #[transactional] + #[cfg_attr(test, mutate)] pub fn add_reserves( origin: OriginFor, payer: ::Source, @@ -1231,6 +1258,7 @@ pub mod pallet { #[pallet::call_index(19)] #[pallet::weight(::WeightInfo::reduce_reserves())] #[transactional] + #[cfg_attr(test, mutate)] pub fn reduce_reserves( origin: OriginFor, receiver: ::Source, @@ -1271,6 +1299,7 @@ pub mod pallet { #[pallet::call_index(20)] #[pallet::weight(::WeightInfo::reduce_incentive_reserves())] #[transactional] + #[cfg_attr(test, mutate)] pub fn reduce_incentive_reserves( origin: OriginFor, receiver: ::Source, @@ -1320,6 +1349,7 @@ impl Pallet { T::PalletId::get().into_account_truncating() } + #[cfg_attr(test, mutate)] pub fn get_account_liquidity(account: &T::AccountId) -> Result, DispatchError> { let total_collateral_value = Self::total_collateral_value(account)?; let total_borrow_value = Self::total_borrowed_value(account)?; @@ -1333,6 +1363,7 @@ impl Pallet { AccountLiquidity::from_collateral_and_debt(total_collateral_value, total_borrow_value) } + #[cfg_attr(test, mutate)] pub fn get_account_liquidation_threshold_liquidity( account: &T::AccountId, ) -> Result, DispatchError> { @@ -1348,6 +1379,7 @@ impl Pallet { AccountLiquidity::from_collateral_and_debt(total_collateral_value, total_borrow_value) } + #[cfg_attr(test, mutate)] fn total_borrowed_value(borrower: &T::AccountId) -> Result, DispatchError> { let mut total_borrow_value = Amount::::zero(T::ReferenceAssetId::get()); for (asset_id, _) in Self::active_markets() { @@ -1362,6 +1394,7 @@ impl Pallet { Ok(total_borrow_value) } + #[cfg_attr(test, mutate)] fn collateral_amount_value(voucher: &Amount) -> Result, DispatchError> { let underlying = voucher.to_underlying()?; let market = Self::market(underlying.currency())?; @@ -1370,6 +1403,7 @@ impl Pallet { Self::get_asset_value(&effects) } + #[cfg_attr(test, mutate)] fn collateral_asset_value(supplier: &T::AccountId, asset_id: CurrencyId) -> Result, DispatchError> { let lend_token_id = Self::lend_token_id(asset_id)?; let deposits = Self::account_deposits(lend_token_id, supplier); @@ -1379,6 +1413,7 @@ impl Pallet { Self::collateral_amount_value(&deposits) } + #[cfg_attr(test, mutate)] fn liquidation_threshold_asset_value( borrower: &T::AccountId, asset_id: CurrencyId, @@ -1398,6 +1433,7 @@ impl Pallet { Self::get_asset_value(&effects_amount) } + #[cfg_attr(test, mutate)] fn total_collateral_value(supplier: &T::AccountId) -> Result, DispatchError> { let mut total_asset_value = Amount::::zero(T::ReferenceAssetId::get()); for (asset_id, _market) in Self::active_markets() { @@ -1407,6 +1443,7 @@ impl Pallet { Ok(total_asset_value) } + #[cfg_attr(test, mutate)] fn total_liquidation_threshold_value(borrower: &T::AccountId) -> Result, DispatchError> { let mut total_asset_value = Amount::::zero(T::ReferenceAssetId::get()); for (asset_id, _market) in Self::active_markets() { @@ -1419,6 +1456,7 @@ impl Pallet { /// Checks if the redeemer should be allowed to redeem tokens in given market. /// Takes into account both `free` and `locked` (i.e. deposited as collateral) lend_tokens of the redeemer. + #[cfg_attr(test, mutate)] fn redeem_allowed(redeemer: &T::AccountId, voucher: &Amount) -> DispatchResult { let asset_id = Self::underlying_id(voucher.currency())?; log::trace!( @@ -1449,6 +1487,7 @@ impl Pallet { } /// Borrower shouldn't borrow more than their total collateral value allows + #[cfg_attr(test, mutate)] fn borrow_allowed(borrower: &T::AccountId, borrow: &Amount) -> DispatchResult { Self::ensure_under_borrow_cap(borrow)?; Self::ensure_enough_cash(borrow)?; @@ -1459,6 +1498,7 @@ impl Pallet { } #[require_transactional] + #[cfg_attr(test, mutate)] fn do_repay_borrow_with_amount( borrower: &T::AccountId, asset_id: CurrencyId, @@ -1493,6 +1533,7 @@ impl Pallet { // Calculates and returns the most recent amount of borrowed balance of `currency_id` // for `who`. + #[cfg_attr(test, mutate)] pub fn current_borrow_balance(who: &T::AccountId, asset_id: CurrencyId) -> Result, DispatchError> { let snapshot: BorrowSnapshot> = Self::account_borrows(asset_id, who); if snapshot.principal.is_zero() || snapshot.borrow_index.is_zero() { @@ -1508,6 +1549,7 @@ impl Pallet { ) } + #[cfg_attr(test, mutate)] pub fn borrow_balance_from_old_and_new_index( old_index: &FixedU128, new_index: &FixedU128, @@ -1521,6 +1563,7 @@ impl Pallet { } /// Checks if the liquidation should be allowed to occur + #[cfg_attr(test, mutate)] fn liquidate_borrow_allowed( borrower: &T::AccountId, underlying: &Amount, @@ -1567,6 +1610,7 @@ impl Pallet { /// and liquidator will receive collateral_asset_id (as voucher amount) from /// borrower. #[require_transactional] + #[cfg_attr(test, mutate)] pub fn do_liquidate_borrow( liquidator: T::AccountId, borrower: T::AccountId, @@ -1610,6 +1654,7 @@ impl Pallet { } #[require_transactional] + #[cfg_attr(test, mutate)] fn liquidated_transfer( liquidator: &T::AccountId, borrower: &T::AccountId, @@ -1703,6 +1748,7 @@ impl Pallet { Ok(()) } + #[cfg_attr(test, mutate)] pub fn lock_if_account_deposited(account_id: &T::AccountId, lend_tokens: &Amount) -> DispatchResult { // if the receiver already has their collateral deposited let deposit = Pallet::::account_deposits(lend_tokens.currency(), account_id); @@ -1715,6 +1761,7 @@ impl Pallet { } // Ensures a given `asset_id` is an active market. + #[cfg_attr(test, mutate)] fn ensure_active_market(asset_id: CurrencyId) -> Result>, DispatchError> { Self::active_markets() .find(|(id, _)| id == &asset_id) @@ -1723,6 +1770,7 @@ impl Pallet { } /// Ensure supplying `amount` asset does not exceed the market's supply cap. + #[cfg_attr(test, mutate)] fn ensure_under_supply_cap(asset: &Amount) -> DispatchResult { let asset_id = asset.currency(); @@ -1739,6 +1787,7 @@ impl Pallet { } /// Ensure borrowing `amount` asset does not exceed the market's borrow cap. + #[cfg_attr(test, mutate)] fn ensure_under_borrow_cap(asset: &Amount) -> DispatchResult { let asset_id = asset.currency(); let market = Self::market(asset_id)?; @@ -1765,6 +1814,7 @@ impl Pallet { /// https://github.com/compound-finance/compound-protocol/blob/a3214f67b73310d547e00fc578e8355911c9d376/contracts/CToken.sol#L518 /// - but getCashPrior is the entire balance of the contract: /// https://github.com/compound-finance/compound-protocol/blob/a3214f67b73310d547e00fc578e8355911c9d376/contracts/CToken.sol#L1125 + #[cfg_attr(test, mutate)] fn ensure_enough_cash(amount: &Amount) -> DispatchResult { let reducible_cash = Self::get_total_cash(amount.currency()).checked_sub(&Self::total_reserves(amount.currency()))?; @@ -1776,6 +1826,7 @@ impl Pallet { } /// Ensures a given `lend_token_id` is unique in `Markets` and `UnderlyingAssetId`. + #[cfg_attr(test, mutate)] fn ensure_lend_token(lend_token_id: CurrencyId) -> DispatchResult { // The lend_token id is unique, cannot be repeated ensure!( @@ -1796,6 +1847,7 @@ impl Pallet { /// Returns `Err` If InsufficientLiquidity /// `account`: account that needs a liquidity check /// `reduce_amount`: amount to reduce the liquidity (collateral) of the `account` by + #[cfg_attr(test, mutate)] fn ensure_liquidity(account: &T::AccountId, reduce_amount: Amount) -> DispatchResult { if Self::get_account_liquidity(account)?.liquidity().ge(&reduce_amount)? { return Ok(()); @@ -1804,6 +1856,7 @@ impl Pallet { } /// Transferrable balance in the pallet account (`free - frozen`) + #[cfg_attr(test, mutate)] fn get_total_cash(asset_id: CurrencyId) -> Amount { Amount::new( orml_tokens::Pallet::::reducible_balance(asset_id, &Self::account_id(), true), @@ -1813,12 +1866,14 @@ impl Pallet { /// Get the total balance of `who`. /// Ignores any frozen balance of this account (`free + reserved`) + #[cfg_attr(test, mutate)] fn balance(asset_id: CurrencyId, who: &T::AccountId) -> Amount { let balance = as MultiCurrency>::total_balance(asset_id, who); Amount::new(balance, asset_id) } /// Total issuance of lending tokens (lend_tokens), given the underlying + #[cfg_attr(test, mutate)] pub fn total_supply(asset_id: CurrencyId) -> Result, DispatchError> { let lend_token_id = Self::lend_token_id(asset_id)?; let issuance = orml_tokens::Pallet::::total_issuance(lend_token_id); @@ -1826,6 +1881,7 @@ impl Pallet { } /// Free lending tokens (lend_tokens) of an account, given the underlying + #[cfg_attr(test, mutate)] pub fn free_lend_tokens(asset_id: CurrencyId, account_id: &T::AccountId) -> Result, DispatchError> { let lend_token_id = Self::lend_token_id(asset_id)?; let amount = Amount::new( @@ -1836,6 +1892,7 @@ impl Pallet { } /// Reserved lending tokens (lend_tokens) of an account, given the underlying + #[cfg_attr(test, mutate)] pub fn reserved_lend_tokens( asset_id: CurrencyId, account_id: &T::AccountId, @@ -1850,6 +1907,7 @@ impl Pallet { // Returns the value of the asset, in the reference currency. // Returns `Err` if oracle price not ready or arithmetic error. + #[cfg_attr(test, mutate)] pub fn get_asset_value(asset: &Amount) -> Result, DispatchError> { asset.convert_to(T::ReferenceAssetId::get()) } @@ -1857,6 +1915,7 @@ impl Pallet { // Returns a stored Market. // // Returns `Err` if market does not exist. + #[cfg_attr(test, mutate)] pub fn market(asset_id: CurrencyId) -> Result>, DispatchError> { Markets::::try_get(asset_id).map_err(|_err| Error::::MarketDoesNotExist.into()) } @@ -1864,6 +1923,7 @@ impl Pallet { // Mutates a stored Market. // // Returns `Err` if market does not exist. + #[cfg_attr(test, mutate)] pub(crate) fn mutate_market(asset_id: CurrencyId, cb: F) -> Result>, DispatchError> where F: FnOnce(&mut Market>) -> Market>, @@ -1877,6 +1937,7 @@ impl Pallet { } // All markets that are `MarketStatus::Active`. + #[cfg_attr(test, mutate)] fn active_markets() -> impl Iterator, Market>)> { Markets::::iter().filter(|(_, market)| market.state == MarketState::Active) } @@ -1884,6 +1945,7 @@ impl Pallet { // Returns the lend_token_id of the related asset // // Returns `Err` if market does not exist. + #[cfg_attr(test, mutate)] pub fn lend_token_id(asset_id: CurrencyId) -> Result, DispatchError> { if let Ok(market) = Self::market(asset_id) { Ok(market.lend_token_id) @@ -1893,12 +1955,14 @@ impl Pallet { } // Returns the incentive reward account + #[cfg_attr(test, mutate)] pub fn incentive_reward_account_id() -> T::AccountId { T::PalletId::get().into_sub_account_truncating(INCENTIVE_SUB_ACCOUNT) } } impl LoansTrait, AccountIdOf, Amount> for Pallet { + #[cfg_attr(test, mutate)] fn do_mint(supplier: &AccountIdOf, amount: &Amount) -> Result<(), DispatchError> { let asset_id = amount.currency(); Self::ensure_active_market(asset_id)?; @@ -1925,6 +1989,7 @@ impl LoansTrait, AccountIdOf, Amount> for Pallet< Ok(()) } + #[cfg_attr(test, mutate)] fn do_borrow(borrower: &AccountIdOf, borrow: &Amount) -> Result<(), DispatchError> { let asset_id = borrow.currency(); Self::ensure_active_market(asset_id)?; @@ -1959,6 +2024,7 @@ impl LoansTrait, AccountIdOf, Amount> for Pallet< Ok(()) } + #[cfg_attr(test, mutate)] fn do_deposit_collateral(supplier: &AccountIdOf, lend_token_amount: &Amount) -> Result<(), DispatchError> { // If the given asset_id is not a valid lend_token, fetching the underlying will fail let underlying_id = Self::underlying_id(lend_token_amount.currency())?; @@ -1980,6 +2046,7 @@ impl LoansTrait, AccountIdOf, Amount> for Pallet< Ok(()) } + #[cfg_attr(test, mutate)] fn do_withdraw_collateral(supplier: &AccountIdOf, voucher: &Amount) -> Result<(), DispatchError> { // If the given asset_id is not a valid lend_token, fetching the underlying will fail let underlying_id = Self::underlying_id(voucher.currency())?; @@ -2025,6 +2092,7 @@ impl LoansTrait, AccountIdOf, Amount> for Pallet< Ok(()) } + #[cfg_attr(test, mutate)] fn do_repay_borrow(borrower: &AccountIdOf, borrow: &Amount) -> Result<(), DispatchError> { let asset_id = borrow.currency(); Self::ensure_active_market(asset_id)?; @@ -2039,6 +2107,7 @@ impl LoansTrait, AccountIdOf, Amount> for Pallet< Ok(()) } + #[cfg_attr(test, mutate)] fn do_redeem(supplier: &AccountIdOf, underlying: &Amount, voucher: &Amount) -> Result<(), DispatchError> { let asset_id = underlying.currency(); @@ -2077,6 +2146,7 @@ impl LoansTrait, AccountIdOf, Amount> for Pallet< } // NOTE: used in OracleApi, so don't use oracle calls here or it'll recurse forever + #[cfg_attr(test, mutate)] fn recompute_underlying_amount(lend_tokens: &Amount) -> Result, DispatchError> { // This function could be called externally to this pallet, with interest // possibly not having accrued for a few blocks. This would result in using an @@ -2091,11 +2161,13 @@ impl LoansTrait, AccountIdOf, Amount> for Pallet< // Returns a stored asset_id // // Returns `Err` if asset_id does not exist, it also means that lend_token_id is invalid. + #[cfg_attr(test, mutate)] fn underlying_id(lend_token_id: CurrencyId) -> Result, DispatchError> { UnderlyingAssetId::::try_get(lend_token_id).map_err(|_err| Error::::InvalidLendTokenId.into()) } // NOTE: used in OracleApi, so don't use oracle calls here or it'll recurse forever + #[cfg_attr(test, mutate)] fn recompute_collateral_amount(underlying: &Amount) -> Result, DispatchError> { // This function could be called externally to this pallet, with interest // possibly not having accrued for a few blocks. This would result in using an @@ -2111,6 +2183,7 @@ impl LoansTrait, AccountIdOf, Amount> for Pallet< } impl LoansMarketDataProvider, BalanceOf> for Pallet { + #[cfg_attr(test, mutate)] fn get_market_info(asset_id: CurrencyId) -> Result { let market = Self::market(asset_id)?; let full_rate = Self::get_full_interest_rate(asset_id).ok_or(Error::::InvalidRateModelParam)?; @@ -2123,6 +2196,7 @@ impl LoansMarketDataProvider, BalanceOf> for Pallet< }) } + #[cfg_attr(test, mutate)] fn get_market_status(asset_id: CurrencyId) -> Result>, DispatchError> { let (borrow_rate, supply_rate, exchange_rate, utilization, total_borrows, total_reserves, borrow_index) = Self::get_market_status(asset_id)?; @@ -2137,6 +2211,7 @@ impl LoansMarketDataProvider, BalanceOf> for Pallet< }) } + #[cfg_attr(test, mutate)] fn get_full_interest_rate(asset_id: CurrencyId) -> Option { if let Ok(market) = Self::market(asset_id) { let rate = match market.rate_model { @@ -2150,6 +2225,7 @@ impl LoansMarketDataProvider, BalanceOf> for Pallet< } impl OnExchangeRateChange> for Pallet { + #[cfg_attr(test, mutate)] fn on_exchange_rate_change(currency_id: &CurrencyId) { // todo: propagate error if let Ok(lend_token_id) = Pallet::::lend_token_id(*currency_id) { diff --git a/crates/loans/src/rate_model.rs b/crates/loans/src/rate_model.rs index 9fb808c586..64ea7cf6e6 100644 --- a/crates/loans/src/rate_model.rs +++ b/crates/loans/src/rate_model.rs @@ -30,6 +30,7 @@ pub enum InterestRateModel { } impl Default for InterestRateModel { + #[cfg_attr(test, mutate)] fn default() -> Self { Self::new_jump_model( Rate::saturating_from_rational(2, 100), @@ -41,14 +42,17 @@ impl Default for InterestRateModel { } impl InterestRateModel { + #[cfg_attr(test, mutate)] pub fn new_jump_model(base_rate: Rate, jump_rate: Rate, full_rate: Rate, jump_utilization: Ratio) -> Self { Self::Jump(JumpModel::new_model(base_rate, jump_rate, full_rate, jump_utilization)) } + #[cfg_attr(test, mutate)] pub fn new_curve_model(base_rate: Rate) -> Self { Self::Curve(CurveModel::new_model(base_rate)) } + #[cfg_attr(test, mutate)] pub fn check_model(&self) -> bool { match self { Self::Jump(jump) => jump.check_model(), @@ -57,6 +61,7 @@ impl InterestRateModel { } /// Calculates the current borrow interest rate + #[cfg_attr(test, mutate)] pub fn get_borrow_rate(&self, utilization: Ratio) -> Option { match self { Self::Jump(jump) => jump.get_borrow_rate(utilization), @@ -65,6 +70,7 @@ impl InterestRateModel { } /// Calculates the current supply interest rate + #[cfg_attr(test, mutate)] pub fn get_supply_rate(borrow_rate: Rate, util: Ratio, reserve_factor: Ratio) -> Rate { // ((1 - reserve_factor) * borrow_rate) * utilization let one_minus_reserve_factor = Ratio::one().saturating_sub(reserve_factor); @@ -94,6 +100,7 @@ impl JumpModel { pub const MAX_FULL_RATE: Rate = Rate::from_inner(500_000_000_000_000_000); // 50% /// Create a new rate model + #[cfg_attr(test, mutate)] pub fn new_model(base_rate: Rate, jump_rate: Rate, full_rate: Rate, jump_utilization: Ratio) -> JumpModel { Self { base_rate, @@ -104,6 +111,7 @@ impl JumpModel { } /// Check the jump model for sanity + #[cfg_attr(test, mutate)] pub fn check_model(&self) -> bool { if self.base_rate > Self::MAX_BASE_RATE || self.jump_rate > Self::MAX_JUMP_RATE @@ -119,6 +127,7 @@ impl JumpModel { } /// Calculates the borrow interest rate of jump model + #[cfg_attr(test, mutate)] pub fn get_borrow_rate(&self, utilization: Ratio) -> Option { if utilization <= self.jump_utilization { // utilization * (jump_rate - zero_rate) / jump_utilization + zero_rate @@ -156,16 +165,19 @@ impl CurveModel { pub const MAX_BASE_RATE: Rate = Rate::from_inner(100_000_000_000_000_000); // 10% /// Create a new curve model + #[cfg_attr(test, mutate)] pub fn new_model(base_rate: Rate) -> CurveModel { Self { base_rate } } /// Check the curve model for sanity + #[cfg_attr(test, mutate)] pub fn check_model(&self) -> bool { self.base_rate <= Self::MAX_BASE_RATE } /// Calculates the borrow interest rate of curve model + #[cfg_attr(test, mutate)] pub fn get_borrow_rate(&self, utilization: Ratio) -> Option { const NINE: usize = 9; let utilization_rate: Rate = utilization.into(); diff --git a/crates/loans/src/types.rs b/crates/loans/src/types.rs index f42cb6e251..0d6b93df33 100644 --- a/crates/loans/src/types.rs +++ b/crates/loans/src/types.rs @@ -4,6 +4,9 @@ use frame_support::pallet_prelude::*; use primitives::{CurrencyId, Liquidity, Rate, Ratio, Shortfall}; use scale_info::TypeInfo; +#[cfg(test)] +use mutagen::mutate; + // TODO: `cargo doc` crashes on this type, remove the `hidden` macro // when upgrading rustc in case that fixes it /// Container for account liquidity information @@ -15,6 +18,7 @@ pub enum AccountLiquidity { } impl AccountLiquidity { + #[cfg_attr(test, mutate)] pub fn from_collateral_and_debt( collateral_value: Amount, borrow_value: Amount, @@ -27,12 +31,14 @@ impl AccountLiquidity { Ok(account_liquidity) } + #[cfg_attr(test, mutate)] pub fn currency(&self) -> CurrencyId { match &self { AccountLiquidity::Liquidity(x) | AccountLiquidity::Shortfall(x) => x.currency(), } } + #[cfg_attr(test, mutate)] pub fn liquidity(&self) -> Amount { if let AccountLiquidity::Liquidity(x) = &self { return x.clone(); @@ -40,6 +46,7 @@ impl AccountLiquidity { Amount::::zero(self.currency()) } + #[cfg_attr(test, mutate)] pub fn shortfall(&self) -> Amount { if let AccountLiquidity::Shortfall(x) = &self { return x.clone(); @@ -47,6 +54,7 @@ impl AccountLiquidity { Amount::::zero(self.currency()) } + #[cfg_attr(test, mutate)] pub fn to_rpc_tuple(&self) -> Result<(Liquidity, Shortfall), DispatchError> { Ok(( self.liquidity().to_unsigned_fixed_point()?, From bd9afb8f2f6b9858945c4aa607dbda9cad9fca27 Mon Sep 17 00:00:00 2001 From: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> Date: Tue, 23 May 2023 14:57:59 +0200 Subject: [PATCH 2/2] chore: update Cargo.lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 8b549650c6..958edbc381 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6213,7 +6213,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn", + "syn 1.0.109", ] [[package]]