Skip to content

Commit

Permalink
refactor: change is_below_premium_threshold function and change tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sander2 committed Dec 15, 2023
1 parent bb9960e commit 99cdd84
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 70 deletions.
6 changes: 0 additions & 6 deletions crates/redeem/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,6 @@ pub(crate) mod vault_registry {
<vault_registry::Pallet<T>>::ensure_not_banned(vault_id)
}

pub fn is_vault_below_premium_threshold<T: crate::Config>(
vault_id: &DefaultVaultId<T>,
) -> Result<bool, DispatchError> {
<vault_registry::Pallet<T>>::is_vault_below_premium_threshold(vault_id)
}

pub fn is_vault_below_secure_threshold<T: crate::Config>(
vault_id: &DefaultVaultId<T>,
) -> Result<bool, DispatchError> {
Expand Down
5 changes: 1 addition & 4 deletions crates/redeem/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,22 +499,19 @@ impl<T: Config> Pallet<T> {
Error::<T>::AmountBelowDustAmount
);

let below_premium_redeem = ext::vault_registry::is_vault_below_premium_threshold::<T>(&vault_id)?;
let currency_id = vault_id.collateral_currency();

// Calculate the premium collateral amount based on whether the redemption is below the premium redeem
// threshold. This should come before increasing the `to_be_redeemed` tokens and locking the amount to
// ensure accurate premium redeem calculations.
let premium_collateral = if below_premium_redeem {
let premium_collateral = {
let redeem_amount_wrapped_in_collateral = user_to_be_received_btc.convert_to(currency_id)?;
let premium_redeem_rate = ext::fee::premium_redeem_reward_rate::<T>();
let premium_for_redeem_amount =
redeem_amount_wrapped_in_collateral.checked_rounded_mul(&premium_redeem_rate, Rounding::Down)?;

let max_premium = ext::vault_registry::get_vault_max_premium_redeem(&vault_id)?;
max_premium.min(&premium_for_redeem_amount)?
} else {
Amount::zero(currency_id)
};

// vault will get rid of the btc + btc_inclusion_fee
Expand Down
5 changes: 0 additions & 5 deletions crates/redeem/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,6 @@ fn test_request_redeem_succeeds_with_normal_redeem() {
});

ext::security::get_secure_id::<Test>.mock_safe(move |_| MockResult::Return(H256([0; 32])));
ext::vault_registry::is_vault_below_premium_threshold::<Test>.mock_safe(move |_| MockResult::Return(Ok(false)));
ext::fee::get_redeem_fee::<Test>.mock_safe(move |_| MockResult::Return(Ok(wrapped(redeem_fee))));
let btc_fee = Redeem::get_current_inclusion_fee(DEFAULT_WRAPPED_CURRENCY).unwrap();

Expand Down Expand Up @@ -286,7 +285,6 @@ fn test_request_redeem_succeeds_with_self_redeem() {
});

ext::security::get_secure_id::<Test>.mock_safe(move |_| MockResult::Return(H256::zero()));
ext::vault_registry::is_vault_below_premium_threshold::<Test>.mock_safe(move |_| MockResult::Return(Ok(false)));
let btc_fee = Redeem::get_current_inclusion_fee(DEFAULT_WRAPPED_CURRENCY).unwrap();

assert_ok!(Redeem::request_redeem(
Expand Down Expand Up @@ -760,8 +758,6 @@ mod spec_based_tests {
ext::vault_registry::ensure_not_banned::<Test>.mock_safe(move |_vault_id| MockResult::Return(Ok(())));
ext::vault_registry::try_increase_to_be_redeemed_tokens::<Test>
.mock_safe(move |_vault_id, _amount| MockResult::Return(Ok(())));
ext::vault_registry::is_vault_below_premium_threshold::<Test>
.mock_safe(move |_vault_id| MockResult::Return(Ok(false)));
let redeem_fee = Fee::get_redeem_fee(&wrapped(amount_to_redeem)).unwrap();
let burned_tokens = wrapped(amount_to_redeem) - redeem_fee;

Expand Down Expand Up @@ -919,7 +915,6 @@ mod spec_based_tests {
inject_redeem_request(H256([0u8; 32]), redeem_request.clone());

ext::btc_relay::has_request_expired::<Test>.mock_safe(|_, _, _| MockResult::Return(Ok(true)));
ext::vault_registry::is_vault_below_secure_threshold::<Test>.mock_safe(|_| MockResult::Return(Ok(false)));
ext::vault_registry::ban_vault::<Test>.mock_safe(move |vault| {
assert_eq!(vault, &VAULT);
MockResult::Return(Ok(()))
Expand Down
12 changes: 6 additions & 6 deletions crates/vault-registry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -804,10 +804,10 @@ impl<T: Config> Pallet<T> {

let required_collateral =
Self::get_required_collateral_for_wrapped(&to_be_backed_tokens, vault_id.collateral_currency())?;

let current_collateral = Self::get_backing_collateral(&vault_id)?;
let missing_collateral = required_collateral.saturating_sub(&current_collateral)?;

// factor = fee / (secure - fee)
let factor = premium_redeem_rate
.checked_div(
&global_secure_threshold
Expand Down Expand Up @@ -1563,7 +1563,10 @@ impl<T: Config> Pallet<T> {
Ok(Self::get_vault_from_id(&vault_id)?.is_liquidated())
}

pub fn is_vault_below_premium_threshold(vault_id: &DefaultVaultId<T>) -> Result<bool, DispatchError> {
#[cfg(feature = "integration-tests")]
// note: unlike `is_vault_below_secure_threshold` and `is_vault_below_liquidation_threshold`,
// this function uses to_be_backed tokens
pub fn will_be_below_premium_threshold(vault_id: &DefaultVaultId<T>) -> Result<bool, DispatchError> {
let vault = Self::get_rich_vault_from_id(&vault_id)?;
let threshold = Self::premium_redeem_threshold(&vault_id.currencies).ok_or(Error::<T>::ThresholdNotSet)?;
let collateral = Self::get_backing_collateral(vault_id)?;
Expand Down Expand Up @@ -1704,10 +1707,7 @@ impl<T: Config> Pallet<T> {

let request_redeem_tokens_for_max_premium = vault_to_burn_tokens.checked_div(&amount_wrapped).ok()?;

if Self::ensure_not_banned(&vault_id).is_ok()
&& !request_redeem_tokens_for_max_premium.is_zero()
&& Self::is_vault_below_premium_threshold(&vault_id).unwrap_or(false)
{
if Self::ensure_not_banned(&vault_id).is_ok() && !request_redeem_tokens_for_max_premium.is_zero() {
Some((vault_id, request_redeem_tokens_for_max_premium))
} else {
None
Expand Down
127 changes: 78 additions & 49 deletions parachain/runtime/runtime-tests/src/parachain/redeem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ fn consume_to_be_replaced(vault: &mut CoreVaultData, amount_btc: Amount<Runtime>
}

mod premium_redeem_tests {
use std::future;

use super::{assert_eq, *};

fn setup_vault_below_premium_threshold(vault_id: VaultId) {
Expand All @@ -127,15 +129,21 @@ mod premium_redeem_tests {
CoreVaultData::force_to(
&vault_id,
CoreVaultData {
issued: vault_id.wrapped(450_000),
to_be_issued: vault_id.wrapped(250_000),
to_be_redeemed: vault_id.wrapped(50_000),
backing_collateral: vault_id.collateral(2_000_000),
issued: vault_id.wrapped(450_000_000),
to_be_issued: vault_id.wrapped(250_000_000),
to_be_redeemed: vault_id.wrapped(50_000_000),
backing_collateral: vault_id.collateral(2_000_000_000),
to_be_replaced: vault_id.wrapped(0),
replace_collateral: griefing(0),
..default_vault_state(&vault_id)
},
);

// make sure user has enough tokens to redeem
let mut user_state = UserData::get(USER);
(*user_state.balances.get_mut(&vault_id.wrapped_currency()).unwrap()).free =
(*user_state.balances.get_mut(&vault_id.wrapped_currency()).unwrap()).free * 1000;
UserData::force_to(USER, user_state);
}

#[test]
Expand All @@ -144,46 +152,53 @@ mod premium_redeem_tests {
setup_vault_below_premium_threshold(vault_id.clone());

assert!(!VaultRegistryPallet::is_vault_below_secure_threshold(&vault_id).unwrap());
assert!(VaultRegistryPallet::is_vault_below_premium_threshold(&vault_id).unwrap());
assert!(VaultRegistryPallet::will_be_below_premium_threshold(&vault_id).unwrap());

let redeem_id = setup_redeem(vault_id.wrapped(400_000), USER, &vault_id);
let compute_collateral = VaultRegistryPallet::compute_collateral(&vault_id).unwrap().amount();
assert_eq!(compute_collateral, 2_000_000_000);

assert!(!VaultRegistryPallet::is_vault_below_secure_threshold(&vault_id).unwrap());
assert!(!VaultRegistryPallet::is_vault_below_premium_threshold(&vault_id).unwrap());
let initial_state = ParachainState::get(&vault_id);

let redeem_id = setup_redeem(vault_id.wrapped(400_000_000), USER, &vault_id);
let redeem = RedeemPallet::get_open_redeem_request_from_id(&redeem_id).unwrap();
// we should get rewarded only for 150_000 + 3840 tokens (that's when we reach nearer to secure threshold)
let expected_premium = FeePallet::get_premium_redeem_fee(
&vault_id
.wrapped(150_000 + 3840) // need to add 0.384 = 153.84
.convert_to(vault_id.collateral_currency())
.unwrap(),
)
.unwrap();
assert_eq!(vault_id.collateral(redeem.premium), expected_premium);

// Execute redeem
execute_redeem(redeem_id);
assert!(!VaultRegistryPallet::is_vault_below_secure_threshold(&vault_id).unwrap());
assert!(!VaultRegistryPallet::will_be_below_premium_threshold(&vault_id).unwrap());

let compute_collateral = VaultRegistryPallet::compute_collateral(&vault_id).unwrap().amount();
assert_eq!(compute_collateral, 2000000 - 15384); //15.384 COL tokens lost as premium fees
dry_run(|| {
// further redeems will have no rewards, even though the premium redeem
// has not executed yet
let redeem_id = setup_redeem(vault_id.wrapped(2_000_000), USER, &vault_id);
let redeem = RedeemPallet::get_open_redeem_request_from_id(&redeem_id).unwrap();
assert_eq!(redeem.premium, 0);
});

// Setup another redeem request
let redeem_id = setup_redeem(vault_id.wrapped(2_000), USER, &vault_id);
execute_redeem(redeem_id);

let redeem = RedeemPallet::get_open_redeem_request_from_id(&redeem_id).unwrap();
assert_eq!(
ParachainState::get(&vault_id),
initial_state.with_changes(|user, vault, _, fee_pool| {
// premium transferred to user
// we should get rewarded only for 15.3846153846 *10^6 tokens (that's when we reach nearer to secure
// threshold)
let expected_premium = vault_id.collateral(15_384_615);
vault.backing_collateral -= expected_premium;
(*user.balances.get_mut(&vault_id.collateral_currency()).unwrap()).free += expected_premium;

// bitcoin balance update as usual
(*user.balances.get_mut(&vault_id.wrapped_currency()).unwrap()).free -=
redeem.amount_btc() + redeem.fee() + redeem.transfer_fee_btc();
vault.issued -= redeem.amount_btc() + redeem.transfer_fee_btc();
*fee_pool.rewards_for(&vault_id) += redeem.fee();
})
);

// No premium should be given for this request
// We already checked that redeems have no more rewards after requesting the
// premium redeem. Here we do a sanity check that it's still the case after
// execution
let redeem_id = setup_redeem(vault_id.wrapped(2_000_000), USER, &vault_id);
let redeem = RedeemPallet::get_open_redeem_request_from_id(&redeem_id).unwrap();
assert_eq!(redeem.premium, 0);

// Execute redeem
execute_redeem(redeem_id);

// initially 400 tokens, 1st redeem consumed 398 tokens , 2nd redeem consumed 1.99 tokens, remaining 0.01
let get_free_redeemable_tokens = VaultRegistryPallet::get_free_redeemable_tokens(&vault_id)
.unwrap()
.amount();
assert_eq!(get_free_redeemable_tokens, 10);
});
}

Expand All @@ -195,13 +210,14 @@ mod premium_redeem_tests {
let global_secure = VaultRegistryPallet::get_global_secure_threshold(&vault_id.currencies).unwrap(); // 200%

// secure > premium > liquidation threshold
// at start vault should be below premium threshold, while above global secure & secure threshold
// at start the vault is above the custom&global secure threshold, but due to the to_be_issued
// tokens it is already eligible for premium redeem
assert!(!VaultRegistryPallet::is_vault_below_secure_threshold(&vault_id).unwrap());
assert!(VaultRegistryPallet::is_vault_below_premium_threshold(&vault_id).unwrap());
assert!(!VaultRegistryPallet::is_vault_below_certain_threshold(&vault_id, global_secure).unwrap());
assert!(VaultRegistryPallet::will_be_below_premium_threshold(&vault_id).unwrap());

// Change vault secure threshold,
// now secure > global secure > premium > liquidation threshold
// now custom secure > global secure > premium > liquidation threshold
let vault_custom_secure_threshold = UnsignedFixedPoint::checked_from_rational(300, 100);
assert_ok!(
RuntimeCall::VaultRegistry(VaultRegistryCall::set_custom_secure_threshold {
Expand All @@ -213,23 +229,24 @@ mod premium_redeem_tests {

// vault should be below premium & secure threshold, while above global secure threshold
assert!(VaultRegistryPallet::is_vault_below_secure_threshold(&vault_id).unwrap());
assert!(VaultRegistryPallet::is_vault_below_premium_threshold(&vault_id).unwrap());
assert!(!VaultRegistryPallet::is_vault_below_certain_threshold(&vault_id, global_secure).unwrap());
assert!(VaultRegistryPallet::will_be_below_premium_threshold(&vault_id).unwrap());

let max_premium_for_vault = VaultRegistryPallet::get_vault_max_premium_redeem(&vault_id).unwrap();
// get premium redeem vaults
let premium_redeem_vaults = RedeemPallet::get_premium_redeem_vaults()
.unwrap()
.get(0)
.unwrap()
.clone();
let premium_redeem_vaults = RedeemPallet::get_premium_redeem_vaults().unwrap()[0].clone();
// non-zero amount of tokens that are elible for premium redeem
assert!(!premium_redeem_vaults.1.is_zero());

// request redeem tokens given by RPC
let redeem_id_1 = setup_redeem(premium_redeem_vaults.1, USER, &vault_id);

let redeem_1 = RedeemPallet::get_open_redeem_request_from_id(&redeem_id_1).unwrap();
// recv premium should be equal to max premium
assert_eq!(redeem_1.premium, max_premium_for_vault.amount());
// premium should be equal to max premium, but allow rounding error in this check.
assert!(
redeem_1.premium >= max_premium_for_vault.amount() - 1
&& redeem_1.premium <= max_premium_for_vault.amount() + 1
);
assert!(!redeem_1.premium.is_zero());

// max premium for vault should be zero
Expand All @@ -239,9 +256,21 @@ mod premium_redeem_tests {
// redeeming the max premium amount put backs vault above premium threshold
// vault should be below secure threshold, while above global secure & premium threshold
assert!(VaultRegistryPallet::is_vault_below_secure_threshold(&vault_id).unwrap());
assert!(!VaultRegistryPallet::is_vault_below_premium_threshold(&vault_id).unwrap());
assert!(!VaultRegistryPallet::will_be_below_premium_threshold(&vault_id).unwrap());
assert!(!VaultRegistryPallet::is_vault_below_certain_threshold(&vault_id, global_secure).unwrap());

execute_redeem(redeem_id_1);
// We should be almost exactly at the secure threshold (there should only be minor
// rounding errors)
let vault = CoreVaultData::vault(vault_id.clone());
let future_tokens = vault.to_be_issued + vault.issued - vault.to_be_redeemed;
let collateral = vault.backing_collateral;
let future_ratio = collateral
.ratio(&future_tokens.convert_to(vault_id.collateral_currency()).unwrap())
.unwrap();
// actual collateralization rate: 2.000004822104648639. Allow small rounding changes
assert!(future_ratio - global_secure < FixedU128::from_float(0.00001));

let redeem_id_2 = setup_redeem(vault_id.wrapped(800_00), USER, &vault_id);
let redeem_2 = RedeemPallet::get_open_redeem_request_from_id(&redeem_id_2).unwrap();
// no premium is given out for new redeems
Expand All @@ -254,12 +283,12 @@ mod premium_redeem_tests {
setup_vault_below_premium_threshold(vault_id.clone());

assert!(!VaultRegistryPallet::is_vault_below_secure_threshold(&vault_id).unwrap());
assert!(VaultRegistryPallet::is_vault_below_premium_threshold(&vault_id).unwrap());
assert!(VaultRegistryPallet::will_be_below_premium_threshold(&vault_id).unwrap());

let redeem_id = setup_redeem(vault_id.wrapped(100_000), USER, &vault_id);
let redeem_id = setup_redeem(vault_id.wrapped(100_000_000), USER, &vault_id);

assert!(!VaultRegistryPallet::is_vault_below_secure_threshold(&vault_id).unwrap());
assert!(!VaultRegistryPallet::is_vault_below_premium_threshold(&vault_id).unwrap());
assert!(!VaultRegistryPallet::will_be_below_premium_threshold(&vault_id).unwrap());

let redeem = RedeemPallet::get_open_redeem_request_from_id(&redeem_id).unwrap();

Expand Down

0 comments on commit 99cdd84

Please sign in to comment.