Skip to content

Commit

Permalink
fix: update premium calculation flow
Browse files Browse the repository at this point in the history
  • Loading branch information
nakul1010 committed Dec 6, 2023
1 parent c983b2a commit c6e1948
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 74 deletions.
17 changes: 6 additions & 11 deletions crates/fee/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -427,17 +427,12 @@ impl<T: Config> Pallet<T> {
amount.checked_rounded_mul(&<PremiumRedeemFee<T>>::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<T>,
) -> Result<UnsignedFixedPoint<T>, DispatchError> {
Ok(amount
.checked_sub(&<PremiumRedeemFee<T>>::get())
.ok_or(ArithmeticError::Underflow)?)
/// # Returns
/// Returns the premium redeem reward rate.
pub fn premium_redeem_reward_rate() -> UnsignedFixedPoint<T> {
<PremiumRedeemFee<T>>::get()
}

/// Calculate punishment fee for a Vault that fails to execute a redeem
Expand Down
31 changes: 14 additions & 17 deletions crates/redeem/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: crate::Config>(vault_id: &DefaultVaultId<T>) -> Result<Amount<T>, DispatchError> {
Expand Down Expand Up @@ -86,12 +87,6 @@ pub(crate) mod vault_registry {
<vault_registry::Pallet<T>>::vault_to_be_backed_tokens(vault_id)
}

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

pub fn try_increase_to_be_redeemed_tokens<T: crate::Config>(
vault_id: &DefaultVaultId<T>,
amount: &Amount<T>,
Expand Down Expand Up @@ -184,10 +179,18 @@ pub(crate) mod vault_registry {
<vault_registry::Pallet<T>>::decrease_to_be_replaced_tokens(vault_id, tokens)
}

pub fn get_secure_threshold<T: crate::Config>(
vault_id: &DefaultVaultId<T>,
pub fn get_global_secure_threshold<T: crate::Config>(
currency_pair: &VaultCurrencyPair<CurrencyId<T>>,
) -> Result<UnsignedFixedPoint<T>, DispatchError> {
<vault_registry::Pallet<T>>::get_secure_threshold(vault_id)
<vault_registry::Pallet<T>>::get_global_secure_threshold(currency_pair)
}

pub fn required_collateral<T: crate::Config>(
vault_id: &DefaultVaultId<T>,
to_be_backed_tokens: &Amount<T>,
secure_threshold: UnsignedFixedPoint<T>,
) -> Result<Amount<T>, DispatchError> {
<vault_registry::Pallet<T>>::required_collateral(vault_id, to_be_backed_tokens, secure_threshold)
}
}

Expand Down Expand Up @@ -248,13 +251,7 @@ pub(crate) mod fee {
<fee::Pallet<T>>::get_punishment_fee(amount)
}

pub fn get_premium_redeem_fee<T: crate::Config>(amount: &Amount<T>) -> Result<Amount<T>, DispatchError> {
<fee::Pallet<T>>::get_premium_redeem_fee(amount)
}

pub fn apply_premium_redeem_discount<T: crate::Config>(
amount: &UnsignedFixedPoint<T>,
) -> Result<UnsignedFixedPoint<T>, DispatchError> {
<fee::Pallet<T>>::apply_premium_redeem_discount(amount)
pub fn premium_redeem_reward_rate<T: crate::Config>() -> UnsignedFixedPoint<T> {
<fee::Pallet<T>>::premium_redeem_reward_rate()
}
}
81 changes: 42 additions & 39 deletions crates/redeem/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -505,47 +508,47 @@ impl<T: Config> Pallet<T> {
let below_premium_redeem = ext::vault_registry::is_vault_below_premium_threshold::<T>(&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::<T>(&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::<T>(&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::<T>(
&token_exchange_value.saturating_sub(&backing_collateral)?, // (oldCol - oldTokens * EXCH * SECURE)
)? // FEE * (oldTokens * EXCH * SECURE - oldCol))
.checked_div(&ext::fee::apply_premium_redeem_discount::<T>(&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::<T>(&vault_id.currencies)?;
let premium_redeem_rate = ext::fee::premium_redeem_reward_rate::<T>();
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::<T>(&vault_id)?;
let missing_collateral = required_collateral.checked_sub(&current_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)
};
Expand Down
36 changes: 29 additions & 7 deletions crates/vault-registry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1089,18 +1089,40 @@ impl<T: Config> Pallet<T> {
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<T>) -> Result<UnsignedFixedPoint<T>, 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<CurrencyId<T>>,
) -> Result<UnsignedFixedPoint<T>, DispatchError> {
let global_secure_threshold =
Self::secure_collateral_threshold(&currency_pair).ok_or(Error::<T>::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<T>,
to_be_backed_tokens: &Amount<T>,
secure_threshold: UnsignedFixedPoint<T>,
) -> Result<Amount<T>, 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.
Expand Down

0 comments on commit c6e1948

Please sign in to comment.