Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: only award redeem premium upto the secure threshold #1201

Merged
merged 20 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions crates/fee/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ pub mod pallet {
TryIntoIntError,
/// Value exceeds the expected upper bound for storage fields in this pallet.
AboveMaxExpectedValue,
/// Subtraction of the premium redeem fee from a value failed.
PremiumRedeemSubtractionFailed,
nakul1010 marked this conversation as resolved.
Show resolved Hide resolved
}

#[pallet::hooks]
Expand Down Expand Up @@ -427,6 +429,19 @@ 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
///
/// # Arguments
///
/// * `amount` - amount in collateral (at current exchange rate)
nakul1010 marked this conversation as resolved.
Show resolved Hide resolved
pub fn apply_premium_redeem_discount(
amount: &UnsignedFixedPoint<T>,
) -> Result<UnsignedFixedPoint<T>, DispatchError> {
Ok(amount
.checked_sub(&<PremiumRedeemFee<T>>::get())
.ok_or(Error::<T>::PremiumRedeemSubtractionFailed)?)
}

/// Calculate punishment fee for a Vault that fails to execute a redeem
/// request before the expiry.
///
Expand Down
2 changes: 1 addition & 1 deletion crates/fee/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use scale_info::TypeInfo;

pub(crate) type BalanceOf<T> = <T as currency::Config>::Balance;

pub(crate) type UnsignedFixedPoint<T> = <T as currency::Config>::UnsignedFixedPoint;
pub type UnsignedFixedPoint<T> = <T as currency::Config>::UnsignedFixedPoint;

pub(crate) type DefaultVaultId<T> = VaultId<<T as frame_system::Config>::AccountId, CurrencyId<T>>;

Expand Down
7 changes: 7 additions & 0 deletions crates/redeem/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,13 @@ pub mod benchmarks {

#[extrinsic_call]
_(RawOrigin::Signed(caller), amount, btc_address, vault_id.clone());
let redeem_vault_request = Redeem::<T>::get_redeem_requests_for_vault(vault_id.account_id.clone());
let redeem_request_hash = redeem_vault_request
.first()
.cloned()
.unwrap_or_else(|| panic!("No redeem request found"));
let redeem_struct = RedeemRequests::<T>::get(redeem_request_hash).unwrap();
assert!(redeem_struct.premium > 0);
}

#[benchmark]
Expand Down
1,321 changes: 657 additions & 664 deletions crates/redeem/src/default_weights.rs

Large diffs are not rendered by default.

31 changes: 30 additions & 1 deletion crates/redeem/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ pub(crate) mod vault_registry {
use crate::DefaultVaultId;
use currency::Amount;
use frame_support::dispatch::{DispatchError, DispatchResult};
use vault_registry::types::{CurrencyId, CurrencySource, DefaultVault};
use vault_registry::types::{CurrencyId, CurrencySource, DefaultVault, UnsignedFixedPoint};

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

pub fn get_liquidated_collateral<T: crate::Config>(
vault_id: &DefaultVaultId<T>,
Expand Down Expand Up @@ -76,6 +80,18 @@ pub(crate) mod vault_registry {
<vault_registry::Pallet<T>>::get_vault_from_id(vault_id)
}

pub fn vault_to_be_backed_tokens<T: crate::Config>(
vault_id: &DefaultVaultId<T>,
) -> Result<Amount<T>, DispatchError> {
<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 @@ -167,6 +183,12 @@ pub(crate) mod vault_registry {
) -> Result<(Amount<T>, Amount<T>), DispatchError> {
<vault_registry::Pallet<T>>::decrease_to_be_replaced_tokens(vault_id, tokens)
}

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

#[cfg_attr(test, mockable)]
Expand Down Expand Up @@ -207,6 +229,7 @@ pub(crate) mod oracle {
#[cfg_attr(test, mockable)]
pub(crate) mod fee {
use currency::Amount;
use fee::types::UnsignedFixedPoint;
use frame_support::dispatch::{DispatchError, DispatchResult};

pub fn fee_pool_account_id<T: crate::Config>() -> T::AccountId {
Expand All @@ -228,4 +251,10 @@ pub(crate) mod fee {
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)
}
}
46 changes: 36 additions & 10 deletions crates/redeem/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub use crate::types::{DefaultRedeemRequest, RedeemRequest, RedeemRequestStatus}
use crate::types::{BalanceOf, RedeemRequestExt, Version};
use bitcoin::types::FullTransactionProof;
use btc_relay::BtcAddress;
use currency::Amount;
use currency::{Amount, Rounding};
use frame_support::{
dispatch::{DispatchError, DispatchResult},
ensure,
Expand Down Expand Up @@ -456,6 +456,7 @@ mod self_redeem {
Ok(())
}
}

// "Internal" functions, callable by code.
#[cfg_attr(test, mockable)]
impl<T: Config> Pallet<T> {
Expand Down Expand Up @@ -501,23 +502,48 @@ impl<T: Config> Pallet<T> {
Error::<T>::AmountBelowDustAmount
);

// vault will get rid of the btc + btc_inclusion_fee
ext::vault_registry::try_increase_to_be_redeemed_tokens::<T>(&vault_id, &vault_to_be_burned_tokens)?;

// lock full amount (inc. fee)
amount_wrapped.lock_on(&redeemer)?;
let redeem_id = ext::security::get_secure_id::<T>(&redeemer);

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

let premium_collateral = if below_premium_redeem {
let redeem_amount_wrapped_in_collateral = user_to_be_received_btc.convert_to(currency_id)?;
ext::fee::get_premium_redeem_fee::<T>(&redeem_amount_wrapped_in_collateral)?
// 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)?;

if difference_in_tokens_to_reach_secure_threshold.gt(&user_to_be_received_btc)? {
nakul1010 marked this conversation as resolved.
Show resolved Hide resolved
let premium_tokens_in_collateral = user_to_be_received_btc.convert_to(currency_id)?;

ext::fee::get_premium_redeem_fee::<T>(&premium_tokens_in_collateral)?
} else {
// Formula = max_premium_collateral = (FEE * (oldTokens * EXCH * SECURE - oldCol)) / (SECURE - FEE)
nakul1010 marked this conversation as resolved.
Show resolved Hide resolved

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)
nakul1010 marked this conversation as resolved.
Show resolved Hide resolved
)? // FEE * (oldTokens * EXCH * SECURE - oldCol))
.checked_div(&ext::fee::apply_premium_redeem_discount::<T>(&secure_threshold)?)? // (SECURE - FEE)
nakul1010 marked this conversation as resolved.
Show resolved Hide resolved
}
} else {
Amount::zero(currency_id)
};

// vault will get rid of the btc + btc_inclusion_fee
ext::vault_registry::try_increase_to_be_redeemed_tokens::<T>(&vault_id, &vault_to_be_burned_tokens)?;
nakul1010 marked this conversation as resolved.
Show resolved Hide resolved

// lock full amount (inc. fee)
amount_wrapped.lock_on(&redeemer)?;
let redeem_id = ext::security::get_secure_id::<T>(&redeemer);

Self::release_replace_collateral(&vault_id, &vault_to_be_burned_tokens)?;

Self::insert_redeem_request(
Expand Down
32 changes: 31 additions & 1 deletion crates/vault-registry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,20 @@ impl<T: Config> Pallet<T> {
Ok(())
}

/// Get the secure threshold for a specified vault.
/// # Arguments
/// * `vault_id` - the id of the vault from which to issue tokens
///
/// # Returns
/// Returns the secure threshold of the specified vault or an error if the vault retrieval fails.
///
/// # 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()
}

/// Adds an amount tokens to the to-be-redeemed tokens balance of a vault.
/// This function serves as a prevention against race conditions in the
/// redeem and replace procedures. If, for example, a vault would receive
Expand Down Expand Up @@ -1499,8 +1513,11 @@ impl<T: Config> Pallet<T> {
}

pub fn is_vault_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)?;
Self::is_vault_below_threshold(vault_id, threshold)
let collateral = Self::get_backing_collateral(vault_id)?;

Self::is_collateral_below_threshold(&collateral, &vault.to_be_backed_tokens()?, threshold)
sander2 marked this conversation as resolved.
Show resolved Hide resolved
}

/// check if the vault is below the liquidation threshold.
Expand Down Expand Up @@ -1886,6 +1903,19 @@ impl<T: Config> Pallet<T> {
collateral.convert_to(wrapped_currency)?.checked_div(&threshold)
}

pub fn vault_capacity_at_secure_threshold(vault_id: &DefaultVaultId<T>) -> Result<Amount<T>, DispatchError> {
nakul1010 marked this conversation as resolved.
Show resolved Hide resolved
let threshold = Self::secure_collateral_threshold(&vault_id.currencies).ok_or(Error::<T>::ThresholdNotSet)?;
let collateral = Self::get_backing_collateral(vault_id)?;
let wrapped_currency = vault_id.wrapped_currency();

Self::calculate_max_wrapped_from_collateral_for_threshold(&collateral, wrapped_currency, threshold)
}

pub fn vault_to_be_backed_tokens(vault_id: &DefaultVaultId<T>) -> Result<Amount<T>, DispatchError> {
nakul1010 marked this conversation as resolved.
Show resolved Hide resolved
let vault = Self::get_active_rich_vault_from_id(vault_id)?;
vault.to_be_backed_tokens()
}

pub fn new_vault_deposit_address(
vault_id: &DefaultVaultId<T>,
secure_id: H256,
Expand Down
14 changes: 13 additions & 1 deletion crates/vault-registry/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl<T: Config> CurrencySource<T> {

pub(crate) type BalanceOf<T> = <T as currency::Config>::Balance;

pub(crate) type UnsignedFixedPoint<T> = <T as currency::Config>::UnsignedFixedPoint;
pub type UnsignedFixedPoint<T> = <T as currency::Config>::UnsignedFixedPoint;

pub type CurrencyId<T> = <T as orml_tokens::Config>::CurrencyId;

Expand Down Expand Up @@ -381,6 +381,18 @@ impl<T: Config> RichVault<T> {
Ok(Amount::new(amount, self.wrapped_currency()))
}

/// the number of issued tokens if all issues and redeems execute successfully
pub(crate) fn to_be_backed_tokens(&self) -> Result<Amount<T>, DispatchError> {
let amount = self
.data
.issued_tokens
.checked_add(&self.data.to_be_issued_tokens)
.ok_or(ArithmeticError::Overflow)?
.checked_sub(&self.data.to_be_redeemed_tokens)
.ok_or(ArithmeticError::Underflow)?;
Ok(Amount::new(amount, self.wrapped_currency()))
}

pub(crate) fn to_be_replaced_tokens(&self) -> Amount<T> {
Amount::new(self.data.to_be_replaced_tokens, self.wrapped_currency())
}
Expand Down
Loading