Skip to content

Commit

Permalink
Merge pull request #853 from daniel-savu/dan/loans-capacity-model
Browse files Browse the repository at this point in the history
feat(loans): update capacity model on exchange rate updates
  • Loading branch information
gregdhill authored Jan 18, 2023
2 parents 6d8b68a + 63a0b9f commit c044ef8
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 16 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions crates/loans/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ codec = { package = "parity-scale-codec", version = "3.1.5", default-features =
scale-info = { version = "2.2.0", default-features = false, features = ["derive"] }
num-traits = { default-features = false, version = "0.2" }

visibility = { version = "0.0.1", optional = true }

# Substrate dependencies
sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false }
Expand Down Expand Up @@ -73,6 +75,9 @@ runtime-benchmarks = [
"frame-system/runtime-benchmarks",
]
try-runtime = ["frame-support/try-runtime"]
integration-tests = [
"visibility"
]

[lib]
doctest = false
2 changes: 2 additions & 0 deletions crates/loans/src/interest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::*;

impl<T: Config> Pallet<T> {
/// Accrue interest and update corresponding storage
#[cfg_attr(any(test, feature = "integration-tests"), visibility::make(pub))]
pub(crate) fn accrue_interest(asset_id: AssetIdOf<T>) -> DispatchResult {
let now = T::UnixTime::now().as_secs();
let last_accrued_interest_time = Self::last_accrued_interest_time(asset_id);
Expand All @@ -47,6 +48,7 @@ impl<T: Config> Pallet<T> {
BorrowRate::<T>::insert(asset_id, borrow_rate);
SupplyRate::<T>::insert(asset_id, supply_rate);
ExchangeRate::<T>::insert(asset_id, exchange_rate);
Self::on_exchange_rate_change(&asset_id);

Self::deposit_event(Event::<T>::InterestAccrued {
underlying_currency_id: asset_id,
Expand Down
2 changes: 2 additions & 0 deletions standalone/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "
sp-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false }
sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false }
sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false }
sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false }

frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false }
frame-executive = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false }
Expand Down Expand Up @@ -206,6 +207,7 @@ std = [
"currency/testing-utils",
"fee/integration-tests",
"staking/integration-tests",
"loans/integration-tests",
]
runtime-benchmarks = [
"hex-literal",
Expand Down
25 changes: 23 additions & 2 deletions standalone/runtime/tests/mock/loans_testing_utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use currency::Amount;
use loans::JumpModel;
use traits::LoansApi;

use crate::*;

Expand Down Expand Up @@ -36,7 +37,8 @@ pub fn activate_market(underlying_id: CurrencyId, lend_token_id: CurrencyId) {
.dispatch(root()));
}

pub fn mint_lend_tokens(account_id: AccountId, underlying_id: CurrencyId) {
pub fn mint_lend_tokens(account_id: AccountId, lend_token_id: CurrencyId) {
let underlying_id = LoansPallet::underlying_id(lend_token_id).unwrap();
let balance_to_mint = FUND_LIMIT_CEILING;
let amount: Amount<Runtime> = Amount::new(balance_to_mint, underlying_id);
assert_ok!(amount.mint_to(&account_id));
Expand All @@ -48,9 +50,28 @@ pub fn mint_lend_tokens(account_id: AccountId, underlying_id: CurrencyId) {
.dispatch(origin_of(account_id)));
}

/// Deposits and borrows the same currency, for the sake of having accruing interest in the market
pub fn deposit_and_borrow(account_id: AccountId, amount: Amount<Runtime>) {
assert_ok!(amount.mint_to(&account_id));
assert_ok!(RuntimeCall::Loans(LoansCall::mint {
asset_id: amount.currency(),
mint_amount: amount.amount()
})
.dispatch(origin_of(account_id.clone())));
assert_ok!(RuntimeCall::Loans(LoansCall::deposit_all_collateral {
asset_id: amount.currency()
})
.dispatch(origin_of(account_id.clone())));
assert_ok!(RuntimeCall::Loans(LoansCall::borrow {
asset_id: amount.currency(),
borrow_amount: amount.amount() / 2,
})
.dispatch(origin_of(account_id.clone())));
}

pub fn activate_lending_and_mint(underlying_id: CurrencyId, lend_token_id: CurrencyId) {
activate_market(underlying_id, lend_token_id);
for account in iter_endowed_with_lend_token() {
mint_lend_tokens(account, underlying_id);
mint_lend_tokens(account, lend_token_id);
}
}
2 changes: 2 additions & 0 deletions standalone/runtime/tests/mock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ pub type LoansCall = loans::Call<Runtime>;
pub type LoansError = loans::Error<Runtime>;
pub type LoansPallet = loans::Pallet<Runtime>;

pub type AuraPallet = pallet_aura::Pallet<Runtime>;

pub const DEFAULT_COLLATERAL_CURRENCY: <Runtime as orml_tokens::Config>::CurrencyId = Token(DOT);
pub const DEFAULT_WRAPPED_CURRENCY: <Runtime as orml_tokens::Config>::CurrencyId = Token(IBTC);
pub const DEFAULT_NATIVE_CURRENCY: <Runtime as orml_tokens::Config>::CurrencyId = Token(INTR);
Expand Down
107 changes: 93 additions & 14 deletions standalone/runtime/tests/test_fee_pool.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
mod mock;
use currency::Amount;
use interbtc_runtime_standalone::UnsignedFixedPoint;
use mock::{assert_eq, nomination_testing_utils::*, reward_testing_utils::IdealRewardPool, *};
use frame_support::storage::migration::put_storage_value;
use interbtc_runtime_standalone::{Timestamp, UnsignedFixedPoint};
use mock::{
assert_eq,
loans_testing_utils::{deposit_and_borrow, mint_lend_tokens},
nomination_testing_utils::*,
reward_testing_utils::IdealRewardPool,
*,
};
use rand::Rng;
use sp_consensus_aura::{Slot, SlotDuration};
use sp_timestamp::Timestamp as SlotTimestamp;
use std::collections::BTreeMap;
use traits::LoansApi;
use vault_registry::DefaultVaultId;

const VAULT_2: [u8; 32] = DAVE;
Expand Down Expand Up @@ -41,7 +51,7 @@ fn test_with<R>(execute: impl Fn(VaultId) -> R) {
let test_with = |currency_id, wrapped_id| {
ExtBuilder::build().execute_with(|| {
SecurityPallet::set_active_block_number(1);
for currency_id in iter_collateral_currencies() {
for currency_id in iter_collateral_currencies().filter(|c| !c.is_lend_token()) {
assert_ok!(OraclePallet::_set_exchange_rate(
currency_id,
FixedU128::from_float(DEFAULT_EXCHANGE_RATE)
Expand Down Expand Up @@ -69,6 +79,7 @@ fn test_with<R>(execute: impl Fn(VaultId) -> R) {
test_with(Token(KSM), Token(IBTC));
test_with(Token(DOT), Token(IBTC));
test_with(ForeignAsset(1), Token(IBTC));
test_with(LendToken(1), Token(IBTC));
}

fn distribute_rewards(amount: Amount<Runtime>) {
Expand Down Expand Up @@ -414,13 +425,19 @@ fn do_random_nomination_sequence() {
let nominators: Vec<_> = (100..107).map(|id| account_of([id; 32])).collect();
for nominator in nominators.iter() {
for currency_id in [vault_id.collateral_currency(), token1, token2] {
assert_ok!(RuntimeCall::Tokens(TokensCall::set_balance {
who: nominator.clone(),
currency_id,
new_free: max_collateral,
new_reserved: 0,
})
.dispatch(root()));
if currency_id.is_lend_token() {
// Hardcoding the lend_token balance would break the internal exchange rate calculation
// in the Loans pallet, which is using the total amount of issued lend_tokens
mint_lend_tokens(nominator.clone(), currency_id);
} else {
assert_ok!(RuntimeCall::Tokens(TokensCall::set_balance {
who: nominator.clone(),
currency_id,
new_free: max_collateral,
new_reserved: 0,
})
.dispatch(root()));
}
}
}

Expand Down Expand Up @@ -464,8 +481,16 @@ fn do_random_nomination_sequence() {
let threshold = VaultRegistryPallet::get_vault_secure_threshold(&vault_id).unwrap();
reference_pool.set_secure_threshold(&vault_id, threshold);

OraclePallet::_set_exchange_rate(vault_id.collateral_currency(), FixedU128::one()).unwrap();
reference_pool.set_exchange_rate(vault_id.collateral_currency(), FixedU128::one());
if vault_id.collateral_currency().is_lend_token() {
let underlying_id = LoansPallet::underlying_id(vault_id.collateral_currency()).unwrap();
let lend_token_rate = FixedU128::one().mul(LoansPallet::exchange_rate(underlying_id));
OraclePallet::_set_exchange_rate(underlying_id, FixedU128::one()).unwrap();
reference_pool.set_exchange_rate(vault_id.collateral_currency(), lend_token_rate);
reference_pool.set_exchange_rate(underlying_id, FixedU128::one());
} else {
OraclePallet::_set_exchange_rate(vault_id.collateral_currency(), FixedU128::one()).unwrap();
reference_pool.set_exchange_rate(vault_id.collateral_currency(), FixedU128::one());
}
}

let mut actual_rewards = BTreeMap::new();
Expand Down Expand Up @@ -571,8 +596,22 @@ fn do_random_nomination_sequence() {
let vault_id = vaults[rng.gen_range(0..vaults.len())].clone();
let currency_id = vault_id.collateral_currency();
let exchange_rate = FixedU128::from_float(rng.gen_range(0.5..5.0));
OraclePallet::_set_exchange_rate(currency_id, exchange_rate.clone()).unwrap();
reference_pool.set_exchange_rate(currency_id, exchange_rate.clone());
if currency_id.is_lend_token() {
let underlying_id = LoansPallet::underlying_id(vault_id.collateral_currency()).unwrap();
let lend_token_rate = exchange_rate.mul(LoansPallet::exchange_rate(underlying_id));
// Only need to set the exchange rate of the underlying currency in the oracle pallet
OraclePallet::_set_exchange_rate(underlying_id, exchange_rate.clone()).unwrap();
// The reference pool must store both exchange rates explicitly
reference_pool.set_exchange_rate(currency_id, lend_token_rate);
reference_pool.set_exchange_rate(underlying_id, exchange_rate.clone());
} else {
OraclePallet::_set_exchange_rate(currency_id, exchange_rate.clone()).unwrap();
reference_pool.set_exchange_rate(currency_id, exchange_rate.clone());
if let Ok(lend_token_id) = LoansPallet::lend_token_id(currency_id.clone()) {
let lend_token_rate = exchange_rate.div(LoansPallet::exchange_rate(currency_id.clone()));
reference_pool.set_exchange_rate(lend_token_id, lend_token_rate);
}
}
}
Action::DistributeRewards => {
let amount = rng.gen_range(0..10_000_000_000);
Expand Down Expand Up @@ -618,3 +657,43 @@ fn do_random_nomination_sequence() {
}
})
}

#[test]
fn accrued_lend_token_interest_increases_reward_share() {
ExtBuilder::build().execute_with(|| {
SecurityPallet::set_active_block_number(1);
// The timestamp has to be non-zero for interest to start accruing
Timestamp::set_timestamp(1_000);
for currency_id in iter_collateral_currencies().filter(|c| !c.is_lend_token()) {
assert_ok!(OraclePallet::_set_exchange_rate(
currency_id,
FixedU128::from_float(DEFAULT_EXCHANGE_RATE)
));
}
activate_lending_and_mint(Token(DOT), LendToken(1));
let vault_id = PrimitiveVaultId::new(account_of(VAULT), LendToken(1), Token(IBTC));
CoreVaultData::force_to(&vault_id, default_vault_state(&vault_id));

// Borrow some lend_tokens so interest starts accruing in the market
let initial_lend_token_stake: u128 = CapacityRewardsPallet::get_stake(&(), &vault_id.collateral_currency()).unwrap();
let amount_to_borrow = Amount::<Runtime>::new(1_000_000_000_000_000, Token(DOT));
deposit_and_borrow(account_of(USER), amount_to_borrow);

// A timestamp needs to be set to a future time, when a meaningful amount of interest has accrued.
// The set timestamp has to be within the bounds of the current Aura slot, so set the slot to a high
// value first, by overwriting storage.
let slot_duration = SlotDuration::from_millis(AuraPallet::slot_duration());
let slot_to_set = Slot::from_timestamp(SlotTimestamp::try_from(1000000000000000).unwrap(), slot_duration);
put_storage_value(b"Aura", b"CurrentSlot", &[], slot_to_set);
Timestamp::set_timestamp(*slot_to_set * AuraPallet::slot_duration());

// Manually trigger interest accrual
assert_ok!(LoansPallet::accrue_interest(Token(DOT),));
let final_lend_token_stake: u128 = CapacityRewardsPallet::get_stake(&(), &vault_id.collateral_currency()).unwrap();

assert!(
final_lend_token_stake.gt(&initial_lend_token_stake),
"Expected stake of lend_tokens to increase in the Capacity model pool, because their value increased due to accrued interest."
);
});
}

0 comments on commit c044ef8

Please sign in to comment.