From c6e1948ad8ec478b9d3df9c7c57c4ab8eaf81634 Mon Sep 17 00:00:00 2001 From: nakul1010 Date: Wed, 6 Dec 2023 13:56:28 +0530 Subject: [PATCH] fix: update premium calculation flow --- crates/fee/src/lib.rs | 17 +++---- crates/redeem/src/ext.rs | 31 ++++++------ crates/redeem/src/lib.rs | 81 +++++++++++++++++--------------- crates/vault-registry/src/lib.rs | 36 +++++++++++--- 4 files changed, 91 insertions(+), 74 deletions(-) diff --git a/crates/fee/src/lib.rs b/crates/fee/src/lib.rs index 0185a1d54b..c172324475 100644 --- a/crates/fee/src/lib.rs +++ b/crates/fee/src/lib.rs @@ -38,7 +38,7 @@ pub use pallet::*; use primitives::VaultId; use reward::RewardsApi; use sp_arithmetic::{traits::*, FixedPointNumber}; -use sp_runtime::{traits::AccountIdConversion, ArithmeticError, TransactionOutcome}; +use sp_runtime::{traits::AccountIdConversion, TransactionOutcome}; use sp_std::{ convert::{TryFrom, TryInto}, fmt::Debug, @@ -427,17 +427,12 @@ impl Pallet { amount.checked_rounded_mul(&>::get(), Rounding::NearestPrefUp) } - /// Apply a premium redeem discount to the given unsigned fixed-point value + /// Get the premium redeem reward rate. /// - /// # Arguments - /// - /// * `amount` - amount in collateral (at current exchange rate) - pub fn apply_premium_redeem_discount( - amount: &UnsignedFixedPoint, - ) -> Result, DispatchError> { - Ok(amount - .checked_sub(&>::get()) - .ok_or(ArithmeticError::Underflow)?) + /// # Returns + /// Returns the premium redeem reward rate. + pub fn premium_redeem_reward_rate() -> UnsignedFixedPoint { + >::get() } /// Calculate punishment fee for a Vault that fails to execute a redeem diff --git a/crates/redeem/src/ext.rs b/crates/redeem/src/ext.rs index 9d9cc1f7ba..f53e27b393 100644 --- a/crates/redeem/src/ext.rs +++ b/crates/redeem/src/ext.rs @@ -42,6 +42,7 @@ pub(crate) mod vault_registry { use crate::DefaultVaultId; use currency::Amount; use frame_support::dispatch::{DispatchError, DispatchResult}; + use primitives::VaultCurrencyPair; use vault_registry::types::{CurrencyId, CurrencySource, DefaultVault, UnsignedFixedPoint}; pub fn get_backing_collateral(vault_id: &DefaultVaultId) -> Result, DispatchError> { @@ -86,12 +87,6 @@ pub(crate) mod vault_registry { >::vault_to_be_backed_tokens(vault_id) } - pub fn vault_capacity_at_secure_threshold( - vault_id: &DefaultVaultId, - ) -> Result, DispatchError> { - >::vault_capacity_at_secure_threshold(vault_id) - } - pub fn try_increase_to_be_redeemed_tokens( vault_id: &DefaultVaultId, amount: &Amount, @@ -184,10 +179,18 @@ pub(crate) mod vault_registry { >::decrease_to_be_replaced_tokens(vault_id, tokens) } - pub fn get_secure_threshold( - vault_id: &DefaultVaultId, + pub fn get_global_secure_threshold( + currency_pair: &VaultCurrencyPair>, ) -> Result, DispatchError> { - >::get_secure_threshold(vault_id) + >::get_global_secure_threshold(currency_pair) + } + + pub fn required_collateral( + vault_id: &DefaultVaultId, + to_be_backed_tokens: &Amount, + secure_threshold: UnsignedFixedPoint, + ) -> Result, DispatchError> { + >::required_collateral(vault_id, to_be_backed_tokens, secure_threshold) } } @@ -248,13 +251,7 @@ pub(crate) mod fee { >::get_punishment_fee(amount) } - pub fn get_premium_redeem_fee(amount: &Amount) -> Result, DispatchError> { - >::get_premium_redeem_fee(amount) - } - - pub fn apply_premium_redeem_discount( - amount: &UnsignedFixedPoint, - ) -> Result, DispatchError> { - >::apply_premium_redeem_discount(amount) + pub fn premium_redeem_reward_rate() -> UnsignedFixedPoint { + >::premium_redeem_reward_rate() } } diff --git a/crates/redeem/src/lib.rs b/crates/redeem/src/lib.rs index 0ab97d3316..07b64f01c5 100644 --- a/crates/redeem/src/lib.rs +++ b/crates/redeem/src/lib.rs @@ -42,7 +42,10 @@ use frame_support::{ use frame_system::{ensure_root, ensure_signed}; use oracle::OracleKey; use sp_core::H256; -use sp_runtime::{ArithmeticError, FixedPointNumber}; +use sp_runtime::{ + traits::{CheckedDiv, CheckedSub}, + ArithmeticError, FixedPointNumber, +}; use sp_std::{convert::TryInto, vec::Vec}; use types::DefaultVaultId; use vault_registry::{ @@ -505,47 +508,47 @@ impl Pallet { let below_premium_redeem = ext::vault_registry::is_vault_below_premium_threshold::(&vault_id)?; let currency_id = vault_id.collateral_currency(); + // Still ToDos: change rpc, comments, let premium_collateral = if below_premium_redeem { - // Calculate the secure vault capacity - let secure_vault_capacity = ext::vault_registry::vault_capacity_at_secure_threshold(&vault_id)?; - let to_be_backed_tokens = ext::vault_registry::vault_to_be_backed_tokens(&vault_id)?; - let difference_in_tokens_to_reach_secure_threshold = - to_be_backed_tokens.saturating_sub(&secure_vault_capacity)?; + // The goal of premium redeems is to get the vault back the a healthy collateralization ratio. As such, + // we only award a premium for the amount of tokens required to get the vault back to secure threshold. - if difference_in_tokens_to_reach_secure_threshold.gt(&user_to_be_received_btc)? { - let premium_tokens_in_collateral = user_to_be_received_btc.convert_to(currency_id)?; + // The CollateralizationRate is defined as `totalCollateral / convertToCollateral(totalTokens)` + // When paying a premium, the collateralization rate gets updated according to the following formula: + // `NewCollateralization = (oldCol - awardedPremium) / ( oldTokens*EXCH - awardedPremium/FEE)` - ext::fee::get_premium_redeem_fee::(&premium_tokens_in_collateral)? - } else { - // The goal of premium redeems is to get the vault back the a healthy collateralization ratio. As such, - // we only award a premium for the amount of tokens required to get the vault back to secure threshold. - // The CollateralizationRate is defined as `totalCollateral / convertToCollateral(totalTokens)` - // When paying a premium, the collateralization rate gets updated according to the following formula: - // `NewCollateralization = (oldCol - awardedPremium) / ( oldTokens*EXCH - awardedPremium/FEE)` - // To calculate the maximum premium we are willing to pay, we set the newCollateralization to - // the secure threshold, which gives: - // `SECURE = (oldCol - awardedPremium) / (oldTokens*EXCH - awardedPremium/FEE)`` - // We can rewrite this formula to calculate the `premium` amount that would get us to the secure threshold: - // `maxPremium = (oldTokens * EXCH * SECURE - oldCol) * (FEE / (SECURE - FEE))` - // Which can be interpreted as: - // `maxPremium = missingCollateral * (FEE / (SECURE - FEE)) - // Note that to prevent repeated premium redeems while waiting for execution, we use to_be_backed_tokens - // for `oldCol`, which takes into account pending issues and redeems - - let backing_collateral = ext::vault_registry::get_backing_collateral(&vault_id)?; - - let secure_threshold = ext::vault_registry::get_secure_threshold::(&vault_id)?; - - let issued_tokens_in_collateral = to_be_backed_tokens.convert_to(currency_id)?; // oldTokens * EXCH - - let token_exchange_value = - issued_tokens_in_collateral.checked_rounded_mul(&secure_threshold, Rounding::NearestPrefUp)?; // oldTokens * EXCH * SECURE - - ext::fee::get_premium_redeem_fee::( - &token_exchange_value.saturating_sub(&backing_collateral)?, // (oldCol - oldTokens * EXCH * SECURE) - )? // FEE * (oldTokens * EXCH * SECURE - oldCol)) - .checked_div(&ext::fee::apply_premium_redeem_discount::(&secure_threshold)?)? // (SECURE - FEE) - } + // To calculate the maximum premium we are willing to pay, we set the newCollateralization to + // the secure threshold, which gives: + // `SECURE = (oldCol - awardedPremium) / (oldTokens*EXCH - awardedPremium/FEE)`` + // We can rewrite this formula to calculate the `premium` amount that would get us to the secure + // threshold: `maxPremium = (oldTokens * EXCH * SECURE - oldCol) * (FEE / (SECURE - + // FEE))` Which can be interpreted as: + // `maxPremium = missingCollateral * (FEE / (SECURE - FEE)) + + // Note that to prevent repeated premium redeems while waiting for execution, we use to_be_backed_tokens + // for `oldCol`, which takes into account pending issues and redeems + + let to_be_backed_tokens = ext::vault_registry::vault_to_be_backed_tokens(&vault_id)?; + let global_secure_threshold = ext::vault_registry::get_global_secure_threshold::(&vault_id.currencies)?; + let premium_redeem_rate = ext::fee::premium_redeem_reward_rate::(); + let required_collateral = + ext::vault_registry::required_collateral(&vault_id, &to_be_backed_tokens, global_secure_threshold)?; + let current_collateral = ext::vault_registry::get_backing_collateral::(&vault_id)?; + let missing_collateral = required_collateral.checked_sub(¤t_collateral)?; + let factor = premium_redeem_rate + .checked_div( + &global_secure_threshold + .checked_sub(&premium_redeem_rate) + .ok_or(ArithmeticError::Underflow)?, + ) + .ok_or(ArithmeticError::DivisionByZero)?; + let max_premium = missing_collateral.checked_mul(&factor)?; + + let redeem_amount_wrapped_in_collateral = user_to_be_received_btc.convert_to(currency_id)?; + let premium_for_redeem_amount = redeem_amount_wrapped_in_collateral + .checked_rounded_mul(&premium_redeem_rate, Rounding::NearestPrefUp)?; + + max_premium.min(&premium_for_redeem_amount)? } else { Amount::zero(currency_id) }; diff --git a/crates/vault-registry/src/lib.rs b/crates/vault-registry/src/lib.rs index e04e53f91c..fc50d3cd9d 100644 --- a/crates/vault-registry/src/lib.rs +++ b/crates/vault-registry/src/lib.rs @@ -1089,18 +1089,40 @@ impl Pallet { Ok(()) } - /// Get the secure threshold for a specified vault. + /// Get the global secure threshold for a specified currency pair. + /// /// # Arguments - /// * `vault_id` - the id of the vault from which to issue tokens + /// * `currency_pair` - The currency pair for which to retrieve the global secure threshold. /// /// # Returns - /// Returns the secure threshold of the specified vault or an error if the vault retrieval fails. + /// Returns the global secure threshold for the specified currency pair or an error if the threshold is not set. /// /// # Errors - /// * `VaultNotFound` - if no vault exists for the given `vault_id` - pub fn get_secure_threshold(vault_id: &DefaultVaultId) -> Result, DispatchError> { - let vault = Self::get_rich_vault_from_id(&vault_id)?; - vault.get_secure_threshold() + /// * `ThresholdNotSet` - If the secure collateral threshold for the given `currency_pair` is not set. + pub fn get_global_secure_threshold( + currency_pair: &VaultCurrencyPair>, + ) -> Result, DispatchError> { + let global_secure_threshold = + Self::secure_collateral_threshold(¤cy_pair).ok_or(Error::::ThresholdNotSet)?; + Ok(global_secure_threshold) + } + + /// Calculate the required collateral for a vault given the specified parameters. + /// + /// # Arguments + /// * `vault_id` - The identifier of the vault for which to calculate the required collateral. + /// * `to_be_backed_tokens` - The amount of tokens to be backed by collateral. + /// * `secure_threshold` - The secure collateral threshold to be applied in the calculation. + /// + /// # Returns + /// Returns the required collateral amount or an error if the calculation fails. + pub fn required_collateral( + vault_id: &DefaultVaultId, + to_be_backed_tokens: &Amount, + secure_threshold: UnsignedFixedPoint, + ) -> Result, DispatchError> { + let issued_tokens_in_collateral = to_be_backed_tokens.convert_to(vault_id.collateral_currency())?; // oldTokens * EXCH + issued_tokens_in_collateral.checked_rounded_mul(&secure_threshold, Rounding::NearestPrefUp) } /// Adds an amount tokens to the to-be-redeemed tokens balance of a vault.