Skip to content

Commit

Permalink
Merge pull request #702 from interlay/feat/self-redeem
Browse files Browse the repository at this point in the history
[Breaking] feat: self-redeem
  • Loading branch information
gregdhill committed Sep 13, 2022
2 parents 26a252b + 65db349 commit 0597988
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 15 deletions.
29 changes: 29 additions & 0 deletions crates/redeem/src/benchmarking.rs
Expand Up @@ -380,6 +380,35 @@ BtcRelay::<T>::parachain_confirmations() + 1u32.into());
set_redeem_period {
}: _(RawOrigin::Root, 1u32.into())

self_redeem {
assert_ok!(Oracle::<T>::_set_exchange_rate(get_collateral_currency_id::<T>(),
UnsignedFixedPoint::<T>::one()
));

let vault_id = get_vault_id::<T>();
let origin = vault_id.account_id.clone();
let amount = 1000;

register_public_key::<T>(vault_id.clone());

VaultRegistry::<T>::insert_vault(
&vault_id,
Vault::new(vault_id.clone())
);

mint_wrapped::<T>(&origin, amount.into());

mint_collateral::<T>(&vault_id.account_id, 100_000u32.into());
assert_ok!(VaultRegistry::<T>::try_deposit_collateral(&vault_id, &collateral(100_000)));

assert_ok!(VaultRegistry::<T>::try_increase_to_be_issued_tokens(&vault_id, &wrapped(amount)));
assert_ok!(VaultRegistry::<T>::issue_tokens(&vault_id, &wrapped(amount)));

let currency_pair = VaultCurrencyPair {
collateral: get_collateral_currency_id::<T>(),
wrapped: get_wrapped_currency_id::<T>()
};
}: _(RawOrigin::Signed(origin), currency_pair, amount.into())
}

impl_benchmark_test_suite!(
Expand Down
7 changes: 7 additions & 0 deletions crates/redeem/src/default_weights.rs
Expand Up @@ -40,6 +40,7 @@ pub trait WeightInfo {
fn cancel_redeem_reimburse() -> Weight;
fn cancel_redeem_retry() -> Weight;
fn set_redeem_period() -> Weight;
fn self_redeem() -> Weight;
}

/// Weights for redeem using the Substrate node and recommended hardware.
Expand Down Expand Up @@ -141,6 +142,9 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
(3_288_000 as Weight)
.saturating_add(T::DbWeight::get().writes(1 as Weight))
}
fn self_redeem() -> Weight {
Self::execute_redeem() // Dummy until we rerun benchmarks
}
}

// For backwards compatibility and tests
Expand Down Expand Up @@ -241,5 +245,8 @@ impl WeightInfo for () {
(3_288_000 as Weight)
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
}
fn self_redeem() -> Weight {
Self::execute_redeem() // dummy until we rerun benchmarks
}
}

6 changes: 6 additions & 0 deletions crates/redeem/src/ext.rs
Expand Up @@ -59,6 +59,12 @@ pub(crate) mod vault_registry {
<vault_registry::Pallet<T>>::get_liquidated_collateral(vault_id)
}

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

pub fn transfer_funds<T: crate::Config>(
from: CurrencySource<T>,
to: CurrencySource<T>,
Expand Down
132 changes: 118 additions & 14 deletions crates/redeem/src/lib.rs
Expand Up @@ -110,6 +110,11 @@ pub mod pallet {
RedeemPeriodChange {
period: T::BlockNumber,
},
SelfRedeem {
vault_id: DefaultVaultId<T>,
amount: BalanceOf<T>,
fee: BalanceOf<T>,
},
}

#[pallet::error]
Expand Down Expand Up @@ -336,9 +341,103 @@ pub mod pallet {
Self::_mint_tokens_for_reimbursed_redeem(vault_id, redeem_id)?;
Ok(().into())
}

#[pallet::weight(<T as Config>::WeightInfo::self_redeem())]
#[transactional]
pub fn self_redeem(
origin: OriginFor<T>,
currency_pair: DefaultVaultCurrencyPair<T>,
amount_wrapped: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let account_id = ensure_signed(origin)?;
let vault_id = VaultId::new(account_id, currency_pair.collateral, currency_pair.wrapped);
let amount_wrapped = Amount::new(amount_wrapped, vault_id.wrapped_currency());

self_redeem::execute::<T>(vault_id, amount_wrapped)?;

Ok(().into())
}
}
}

mod self_redeem {
use super::*;

pub(crate) fn execute<T: Config>(vault_id: DefaultVaultId<T>, amount_wrapped: Amount<T>) -> DispatchResult {
// ensure that vault is not liquidated and not banned
ext::vault_registry::ensure_not_banned::<T>(&vault_id)?;

// for self-redeem, dustAmount is effectively 1 satoshi
ensure!(!amount_wrapped.is_zero(), Error::<T>::AmountBelowDustAmount);

let (fees, consumed_issued_tokens) = calculate_token_amounts::<T>(&vault_id, &amount_wrapped)?;

take_user_tokens::<T>(&vault_id.account_id, &consumed_issued_tokens, &fees)?;

update_vault_tokens::<T>(&vault_id, &consumed_issued_tokens)?;

Pallet::<T>::deposit_event(Event::<T>::SelfRedeem {
vault_id,
amount: consumed_issued_tokens.amount(),
fee: fees.amount(),
});

Ok(())
}

/// returns (fees, consumed_issued_tokens)
fn calculate_token_amounts<T: Config>(
vault_id: &DefaultVaultId<T>,
requested_redeem_amount: &Amount<T>,
) -> Result<(Amount<T>, Amount<T>), DispatchError> {
let redeemable_tokens = ext::vault_registry::get_free_redeemable_tokens(&vault_id)?;

let fees = if redeemable_tokens.eq(&requested_redeem_amount)? {
Amount::zero(vault_id.wrapped_currency())
} else {
ext::fee::get_redeem_fee::<T>(&requested_redeem_amount)?
};

let consumed_issued_tokens = requested_redeem_amount.checked_sub(&fees)?;

Ok((fees, consumed_issued_tokens))
}

fn take_user_tokens<T: Config>(
account_id: &T::AccountId,
consumed_issued_tokens: &Amount<T>,
fees: &Amount<T>,
) -> DispatchResult {
// burn the tokens that the vault no longer is backing
consumed_issued_tokens
.lock_on(account_id)
.map_err(|_| Error::<T>::AmountExceedsUserBalance)?;
consumed_issued_tokens.burn_from(account_id)?;

// transfer fees to pool
fees.transfer(account_id, &ext::fee::fee_pool_account_id::<T>())
.map_err(|_| Error::<T>::AmountExceedsUserBalance)?;
ext::fee::distribute_rewards::<T>(fees)?;

Ok(())
}

fn update_vault_tokens<T: Config>(
vault_id: &DefaultVaultId<T>,
consumed_issued_tokens: &Amount<T>,
) -> DispatchResult {
ext::vault_registry::try_increase_to_be_redeemed_tokens::<T>(vault_id, consumed_issued_tokens)?;
ext::vault_registry::redeem_tokens::<T>(
vault_id,
consumed_issued_tokens,
&Amount::zero(vault_id.collateral_currency()),
&vault_id.account_id,
)?;

Pallet::<T>::release_replace_collateral(vault_id, consumed_issued_tokens)?;
Ok(())
}
}
// "Internal" functions, callable by code.
#[cfg_attr(test, mockable)]
impl<T: Config> Pallet<T> {
Expand Down Expand Up @@ -403,20 +502,7 @@ impl<T: Config> Pallet<T> {
Amount::zero(currency_id)
};

// decrease to-be-replaced tokens - when the vault requests tokens to be replaced, it
// want to get rid of tokens, and it does not matter whether this is through a redeem,
// or a replace. As such, we decrease the to-be-replaced tokens here. This call will
// never fail due to insufficient to-be-replaced tokens
let (_, griefing_collateral) =
ext::vault_registry::decrease_to_be_replaced_tokens::<T>(&vault_id, &vault_to_be_burned_tokens)?;
// release the griefing collateral that is locked for the replace request
if !griefing_collateral.is_zero() {
ext::vault_registry::transfer_funds(
CurrencySource::AvailableReplaceCollateral(vault_id.clone()),
CurrencySource::FreeBalance(vault_id.account_id.clone()),
&griefing_collateral,
)?;
}
Self::release_replace_collateral(&vault_id, &vault_to_be_burned_tokens)?;

Self::insert_redeem_request(
&redeem_id,
Expand Down Expand Up @@ -653,6 +739,24 @@ impl<T: Config> Pallet<T> {
Ok(())
}

fn release_replace_collateral(vault_id: &DefaultVaultId<T>, burned_tokens: &Amount<T>) -> DispatchResult {
// decrease to-be-replaced tokens - when the vault requests tokens to be replaced, it
// want to get rid of tokens, and it does not matter whether this is through a redeem,
// or a replace. As such, we decrease the to-be-replaced tokens here. This call will
// never fail due to insufficient to-be-replaced tokens
let (_, griefing_collateral) =
ext::vault_registry::decrease_to_be_replaced_tokens::<T>(&vault_id, &burned_tokens)?;
// release the griefing collateral that is locked for the replace request
if !griefing_collateral.is_zero() {
ext::vault_registry::transfer_funds(
CurrencySource::AvailableReplaceCollateral(vault_id.clone()),
CurrencySource::FreeBalance(vault_id.account_id.clone()),
&griefing_collateral,
)?;
}
Ok(())
}

/// Insert a new redeem request into state.
///
/// # Arguments
Expand Down
4 changes: 4 additions & 0 deletions crates/vault-registry/src/lib.rs
Expand Up @@ -818,6 +818,10 @@ impl<T: Config> Pallet<T> {
Ok(Amount::new(vault.liquidated_collateral, vault_id.currencies.collateral))
}

pub fn get_free_redeemable_tokens(vault_id: &DefaultVaultId<T>) -> Result<Amount<T>, DispatchError> {
Ok(Self::get_rich_vault_from_id(vault_id)?.freely_redeemable_tokens()?)
}

/// Like get_vault_from_id, but additionally checks that the vault is active
pub fn get_active_vault_from_id(vault_id: &DefaultVaultId<T>) -> Result<DefaultVault<T>, DispatchError> {
let vault = Self::get_vault_from_id(vault_id)?;
Expand Down
6 changes: 5 additions & 1 deletion crates/vault-registry/src/types.rs
Expand Up @@ -574,6 +574,10 @@ impl<T: Config> RichVault<T> {
Amount::new(self.data.to_be_issued_tokens, self.id().wrapped_currency())
}

pub(crate) fn freely_redeemable_tokens(&self) -> Result<Amount<T>, DispatchError> {
Ok(self.issued_tokens().checked_sub(&self.to_be_redeemed_tokens())?)
}

pub(crate) fn request_issue_tokens(&mut self, tokens: &Amount<T>) -> DispatchResult {
self.increase_to_be_issued(tokens)
}
Expand Down Expand Up @@ -608,7 +612,7 @@ impl<T: Config> RichVault<T> {
if self.data.is_liquidated() {
Ok(())
} else {
let stake = self.issued_tokens().checked_sub(&self.to_be_redeemed_tokens())?;
let stake = self.freely_redeemable_tokens()?;
ext::reward::set_stake(&self.id(), &stake)
}
}
Expand Down
22 changes: 22 additions & 0 deletions standalone/runtime/tests/mock/redeem_testing_utils.rs
Expand Up @@ -135,6 +135,28 @@ pub fn assert_redeem_request_event() -> H256 {
ids.last().unwrap().clone()
}

/// returns (fee, amount)
pub fn assert_self_redeem_event() -> (Amount<Runtime>, Amount<Runtime>) {
let events = SystemPallet::events();
let ids = events
.iter()
.filter_map(|r| match r.event {
Event::Redeem(RedeemEvent::SelfRedeem {
ref vault_id,
amount,
fee,
}) => {
let fee = Amount::new(fee, vault_id.wrapped_currency());
let amount = Amount::new(amount, vault_id.wrapped_currency());
Some((fee, amount))
}
_ => None,
})
.collect::<Vec<_>>();
assert!(ids.len() >= 1);
ids.last().unwrap().clone()
}

pub fn execute_redeem(redeem_id: H256) {
ExecuteRedeemBuilder::new(redeem_id).assert_execute();
}
Expand Down

0 comments on commit 0597988

Please sign in to comment.