Skip to content

Commit

Permalink
fix(loans): inconsistent lend token balance migration
Browse files Browse the repository at this point in the history
fix(kintsugi): set orml-tokens hooks in runtime config
  • Loading branch information
daniel-savu committed Apr 21, 2023
1 parent 33fabb2 commit e111ced
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 38 deletions.
34 changes: 6 additions & 28 deletions crates/loans/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use frame_support::{
log,
pallet_prelude::*,
require_transactional,
traits::{tokens::fungibles::Inspect, UnixTime},
traits::{tokens::fungibles::Inspect, OnRuntimeUpgrade, UnixTime},
transactional, PalletId,
};
use frame_system::pallet_prelude::*;
Expand All @@ -61,6 +61,7 @@ pub use types::{BorrowSnapshot, EarnedSnapshot, Market, MarketState, RewardMarke

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod migration;

#[cfg(test)]
mod mock;
Expand Down Expand Up @@ -436,34 +437,11 @@ pub mod pallet {

#[pallet::hooks]
impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {
fn on_runtime_upgrade() -> frame_support::weights::Weight {
let max_exchange_rate = crate::MaxExchangeRate::<T>::get();
if max_exchange_rate.is_zero() {
crate::MaxExchangeRate::<T>::put(Rate::from_inner(DEFAULT_MAX_EXCHANGE_RATE));
}
let min_exchange_rate = crate::MinExchangeRate::<T>::get();
if min_exchange_rate.is_zero() {
crate::MinExchangeRate::<T>::put(Rate::from_inner(DEFAULT_MIN_EXCHANGE_RATE));
}
T::DbWeight::get().reads_writes(2, 2)
}

#[cfg(feature = "try-runtime")]
fn post_upgrade(_pre_upgrade_state: sp_std::vec::Vec<u8>) -> Result<(), &'static str> {
let max_exchange_rate = crate::MaxExchangeRate::<T>::get();
let min_exchange_rate = crate::MinExchangeRate::<T>::get();
ensure!(
!min_exchange_rate.is_zero(),
"Minimum lending exchange rate must be greater than zero"
);
ensure!(
!max_exchange_rate.is_zero(),
"Minimum lending exchange rate must be greater than zero"
);
ensure!(
min_exchange_rate.lt(&max_exchange_rate),
"Minimum lending exchange rate must be greater than the maximum exchange rate"
);
fn post_upgrade(pre_upgrade_state: sp_std::vec::Vec<u8>) -> Result<(), &'static str> {
frame_support::assert_ok!(migration::collateral_toggle::Migration::<T>::post_upgrade(
pre_upgrade_state
));
Ok(())
}
}
Expand Down
148 changes: 148 additions & 0 deletions crates/loans/src/migration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
use super::*;
use frame_support::traits::OnRuntimeUpgrade;

// #[cfg(feature = "try-runtime")]
use sp_std::{vec, vec::Vec};

use currency::Amount;

/// The log target.
const TARGET: &'static str = "runtime::loans::migration";

pub mod collateral_toggle {
use super::*;

struct InconsistentBalance<T>
where
T: Config,
{
currency_id: CurrencyId<T>,
account_id: T::AccountId,
free_balance: BalanceOf<T>,
}

fn get_inconsistent_lend_token_balances<T: Config>() -> (Vec<InconsistentBalance<T>>, u64) {
// Iterating `AccountDeposits` should guarantee there are no other
let mut reads = 0;
let inconsistent_balances = crate::AccountDeposits::<T>::iter()
.filter_map(|(currency_id, account_id, _collateral)| {
reads += 1;
let free_balance = Amount::<T>::new(
orml_tokens::Pallet::<T>::free_balance(currency_id.clone(), &account_id),
currency_id.clone(),
);

let reserved_balance = Amount::<T>::new(
orml_tokens::Pallet::<T>::reserved_balance(currency_id.clone(), &account_id),
currency_id.clone(),
);

if !free_balance.is_zero() && !reserved_balance.is_zero() {
return Some(InconsistentBalance {
currency_id: currency_id.clone(),
account_id: account_id.clone(),
free_balance: free_balance.amount(),
});
}
None
})
.collect();
(inconsistent_balances, reads)
}

pub struct Migration<T>(sp_std::marker::PhantomData<T>);

impl<T: Config> OnRuntimeUpgrade for Migration<T> {
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, &'static str> {
let (inconsistent_balances, _) = get_inconsistent_lend_token_balances::<T>();
ensure!(
inconsistent_balances.len() > 0,
"There are no inconsistent balances to migrate"
);
log::info!(
target: TARGET,
"{} inconsistent lending collateral balances will be migrated",
inconsistent_balances.len()
);
Ok(vec![])
}

fn on_runtime_upgrade() -> Weight {
let mut writes = 0;
let (inconsistent_balances, mut reads) = get_inconsistent_lend_token_balances::<T>();
for b in inconsistent_balances {
reads += 1;
match Pallet::<T>::lock_if_account_deposited(&b.account_id, &Amount::new(b.free_balance, b.currency_id))
{
Err(e) => log::warn!(
target: TARGET,
"Failed to lock collateral for account {:?}, collateral: {:?}. Error: {:?}",
b.account_id,
b.currency_id,
e
),
Ok(_) => {
writes += 1;
log::info!(
target: TARGET,
"Locked all free collateral for {:?}, in {:?}",
b.account_id,
b.currency_id,
);
}
}
}
T::DbWeight::get().reads_writes(reads, writes)
}

#[cfg(feature = "try-runtime")]
fn post_upgrade(_state: Vec<u8>) -> Result<(), &'static str> {
let (inconsistent_balances, _) = get_inconsistent_lend_token_balances::<T>();
ensure!(
inconsistent_balances.len() == 0,
"There should be no inconsistent lending collateral balances left"
);
Ok(())
}
}
}

#[cfg(test)]
#[cfg(feature = "try-runtime")]
mod test {
use super::*;
use crate::{
mock::*,
tests::lend_tokens::{free_balance, reserved_balance},
};
use frame_support::{assert_err, assert_ok};

use primitives::{
CurrencyId::{self, Token},
KINT as KINT_CURRENCY,
};

const KINT: CurrencyId = Token(KINT_CURRENCY);

#[test]
fn inconsistent_balances_migration_works() {
new_test_ext().execute_with(|| {
assert_ok!(Loans::mint(RuntimeOrigin::signed(DAVE), KINT, unit(100)));
assert_ok!(Loans::deposit_all_collateral(RuntimeOrigin::signed(DAVE), KINT));
assert_ok!(Loans::mint(RuntimeOrigin::signed(DAVE), KINT, unit(100)));
assert_err!(
Loans::deposit_all_collateral(RuntimeOrigin::signed(DAVE), KINT),
Error::<Test>::TokensAlreadyLocked
);
// Both `free` and `reserved` balances are nonzero at the same time,
// ẉhich is an invalid state
assert_eq!(free_balance(LEND_KINT, &DAVE), unit(100) * 50);
assert_eq!(reserved_balance(LEND_KINT, &DAVE), unit(100) * 50);

let state = collateral_toggle::Migration::<Test>::pre_upgrade().unwrap();
let _w = collateral_toggle::Migration::<Test>::on_runtime_upgrade();
collateral_toggle::Migration::<Test>::post_upgrade(state).unwrap();
});
}
}
3 changes: 3 additions & 0 deletions crates/loans/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ impl orml_tokens::Config for Test {
type CurrencyId = CurrencyId;
type WeightInfo = ();
type ExistentialDeposits = ExistentialDeposits;
#[cfg(feature = "try-runtime")]
type CurrencyHooks = ();
#[cfg(not(feature = "try-runtime"))]
type CurrencyHooks = CurrencyHooks<Test>;
type MaxLocks = MaxLocks;
type DustRemovalWhitelist = Everything;
Expand Down
2 changes: 1 addition & 1 deletion crates/loans/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

mod edge_cases;
mod interest_rate;
mod lend_tokens;
pub mod lend_tokens;
mod liquidate_borrow;
mod market;

Expand Down
1 change: 1 addition & 0 deletions parachain/runtime/interlay/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ parameter_type_with_key! {
};
}

// TODO!: Set `OnSlash`, and Pre/Post hooks before the lending launch on interlay
pub struct CurrencyHooks<T>(PhantomData<T>);
impl<T: orml_tokens::Config> MutationHooks<T::AccountId, T::CurrencyId, T::Balance> for CurrencyHooks<T>
where
Expand Down
26 changes: 17 additions & 9 deletions parachain/runtime/kintsugi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use frame_system::{
limits::{BlockLength, BlockWeights},
EnsureRoot, EnsureRootWithSuccess, RawOrigin,
};
use loans::{OnSlashHook, PostDeposit, PostTransfer, PreDeposit, PreTransfer};
use orml_asset_registry::SequentialId;
use orml_traits::{currency::MutationHooks, parameter_type_with_key};
use pallet_transaction_payment::{Multiplier, TargetedFeeAdjustment};
Expand Down Expand Up @@ -96,7 +97,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("kintsugi-parachain"),
impl_name: create_runtime_str!("kintsugi-parachain"),
authoring_version: 1,
spec_version: 1023002,
spec_version: 1023003,
impl_version: 1,
transaction_version: 3, // added preimage
apis: RUNTIME_API_VERSIONS,
Expand Down Expand Up @@ -691,16 +692,17 @@ parameter_type_with_key! {
}

pub struct CurrencyHooks<T>(PhantomData<T>);
impl<T: orml_tokens::Config> MutationHooks<T::AccountId, T::CurrencyId, T::Balance> for CurrencyHooks<T>
impl<T: orml_tokens::Config + loans::Config>
MutationHooks<T::AccountId, T::CurrencyId, <T as currency::Config>::Balance> for CurrencyHooks<T>
where
T::AccountId: From<sp_runtime::AccountId32>,
{
type OnDust = orml_tokens::TransferDust<T, FeeAccount>;
type OnSlash = ();
type PreDeposit = ();
type PostDeposit = ();
type PreTransfer = ();
type PostTransfer = ();
type OnSlash = OnSlashHook<T>;
type PreDeposit = PreDeposit<T>;
type PostDeposit = PostDeposit<T>;
type PreTransfer = PreTransfer<T>;
type PostTransfer = PostTransfer<T>;
type OnNewTokenAccount = ();
type OnKilledTokenAccount = ();
}
Expand Down Expand Up @@ -1301,8 +1303,14 @@ pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<Address, RuntimeCall,
/// Extrinsic type that has already been checked.
pub type CheckedExtrinsic = generic::CheckedExtrinsic<AccountId, RuntimeCall, SignedExtra>;
/// Executive: handles dispatch to the various modules.
pub type Executive =
frame_executive::Executive<Runtime, Block, frame_system::ChainContext<Runtime>, Runtime, AllPalletsWithSystem>;
pub type Executive = frame_executive::Executive<
Runtime,
Block,
frame_system::ChainContext<Runtime>,
Runtime,
AllPalletsWithSystem,
loans::migration::collateral_toggle::Migration<Runtime>,
>;

#[cfg(not(feature = "disable-runtime-api"))]
impl_runtime_apis! {
Expand Down

0 comments on commit e111ced

Please sign in to comment.