From c275fbe714706a03615dad3f314417f03d08b685 Mon Sep 17 00:00:00 2001 From: Sander Bosma Date: Fri, 15 Dec 2023 11:44:41 +0100 Subject: [PATCH] refactor: change is_below_premium_threshold function and change tests --- crates/redeem/src/ext.rs | 6 - crates/redeem/src/lib.rs | 5 +- crates/redeem/src/tests.rs | 5 - crates/vault-registry/src/lib.rs | 12 +- .../runtime-tests/src/parachain/redeem.rs | 125 +++++++++++------- 5 files changed, 83 insertions(+), 70 deletions(-) diff --git a/crates/redeem/src/ext.rs b/crates/redeem/src/ext.rs index ecc3eaf666..bb4e42f09a 100644 --- a/crates/redeem/src/ext.rs +++ b/crates/redeem/src/ext.rs @@ -143,12 +143,6 @@ pub(crate) mod vault_registry { >::ensure_not_banned(vault_id) } - pub fn is_vault_below_premium_threshold( - vault_id: &DefaultVaultId, - ) -> Result { - >::is_vault_below_premium_threshold(vault_id) - } - pub fn is_vault_below_secure_threshold( vault_id: &DefaultVaultId, ) -> Result { diff --git a/crates/redeem/src/lib.rs b/crates/redeem/src/lib.rs index fddc491996..d374cd9ad0 100644 --- a/crates/redeem/src/lib.rs +++ b/crates/redeem/src/lib.rs @@ -499,13 +499,12 @@ impl Pallet { Error::::AmountBelowDustAmount ); - let below_premium_redeem = ext::vault_registry::is_vault_below_premium_threshold::(&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::(); let premium_for_redeem_amount = @@ -513,8 +512,6 @@ impl Pallet { 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 diff --git a/crates/redeem/src/tests.rs b/crates/redeem/src/tests.rs index c31a0afd15..17f522fd3b 100644 --- a/crates/redeem/src/tests.rs +++ b/crates/redeem/src/tests.rs @@ -182,7 +182,6 @@ fn test_request_redeem_succeeds_with_normal_redeem() { }); ext::security::get_secure_id::.mock_safe(move |_| MockResult::Return(H256([0; 32]))); - ext::vault_registry::is_vault_below_premium_threshold::.mock_safe(move |_| MockResult::Return(Ok(false))); ext::fee::get_redeem_fee::.mock_safe(move |_| MockResult::Return(Ok(wrapped(redeem_fee)))); let btc_fee = Redeem::get_current_inclusion_fee(DEFAULT_WRAPPED_CURRENCY).unwrap(); @@ -286,7 +285,6 @@ fn test_request_redeem_succeeds_with_self_redeem() { }); ext::security::get_secure_id::.mock_safe(move |_| MockResult::Return(H256::zero())); - ext::vault_registry::is_vault_below_premium_threshold::.mock_safe(move |_| MockResult::Return(Ok(false))); let btc_fee = Redeem::get_current_inclusion_fee(DEFAULT_WRAPPED_CURRENCY).unwrap(); assert_ok!(Redeem::request_redeem( @@ -760,8 +758,6 @@ mod spec_based_tests { ext::vault_registry::ensure_not_banned::.mock_safe(move |_vault_id| MockResult::Return(Ok(()))); ext::vault_registry::try_increase_to_be_redeemed_tokens:: .mock_safe(move |_vault_id, _amount| MockResult::Return(Ok(()))); - ext::vault_registry::is_vault_below_premium_threshold:: - .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; @@ -919,7 +915,6 @@ mod spec_based_tests { inject_redeem_request(H256([0u8; 32]), redeem_request.clone()); ext::btc_relay::has_request_expired::.mock_safe(|_, _, _| MockResult::Return(Ok(true))); - ext::vault_registry::is_vault_below_secure_threshold::.mock_safe(|_| MockResult::Return(Ok(false))); ext::vault_registry::ban_vault::.mock_safe(move |vault| { assert_eq!(vault, &VAULT); MockResult::Return(Ok(())) diff --git a/crates/vault-registry/src/lib.rs b/crates/vault-registry/src/lib.rs index 14fe02bd7f..280abe73df 100644 --- a/crates/vault-registry/src/lib.rs +++ b/crates/vault-registry/src/lib.rs @@ -804,10 +804,10 @@ impl Pallet { 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(¤t_collateral)?; + // factor = fee / (secure - fee) let factor = premium_redeem_rate .checked_div( &global_secure_threshold @@ -1563,7 +1563,10 @@ impl Pallet { Ok(Self::get_vault_from_id(&vault_id)?.is_liquidated()) } - pub fn is_vault_below_premium_threshold(vault_id: &DefaultVaultId) -> Result { + #[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) -> Result { let vault = Self::get_rich_vault_from_id(&vault_id)?; let threshold = Self::premium_redeem_threshold(&vault_id.currencies).ok_or(Error::::ThresholdNotSet)?; let collateral = Self::get_backing_collateral(vault_id)?; @@ -1704,10 +1707,7 @@ impl Pallet { 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 diff --git a/parachain/runtime/runtime-tests/src/parachain/redeem.rs b/parachain/runtime/runtime-tests/src/parachain/redeem.rs index 4e9a361f19..112dea33ea 100644 --- a/parachain/runtime/runtime-tests/src/parachain/redeem.rs +++ b/parachain/runtime/runtime-tests/src/parachain/redeem.rs @@ -127,15 +127,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] @@ -144,46 +150,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); + }); + + execute_redeem(redeem_id); - // Setup another redeem request - let redeem_id = setup_redeem(vault_id.wrapped(2_000), USER, &vault_id); + 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(); + }) + ); + // 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(); - - // No premium should be given for this request 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); }); } @@ -195,13 +208,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 { @@ -213,23 +227,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 @@ -239,9 +254,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 @@ -254,12 +281,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();