From 38e72897e8908b5244fef8a262c95b2a7131ea00 Mon Sep 17 00:00:00 2001 From: Sander Bosma Date: Mon, 24 May 2021 15:08:20 +0200 Subject: [PATCH 1/3] feat: added MintTokensForReimbursedRedeem event --- crates/redeem/src/lib.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/crates/redeem/src/lib.rs b/crates/redeem/src/lib.rs index 13bbc8f1b4..2866c77bca 100644 --- a/crates/redeem/src/lib.rs +++ b/crates/redeem/src/lib.rs @@ -106,6 +106,8 @@ decl_event!( ExecuteRedeem(H256, AccountId, Wrapped, Wrapped, AccountId), // [redeem_id, redeemer, vault_id, slashing_amount_in_collateral, reimburse] CancelRedeem(H256, AccountId, AccountId, Collateral, bool), + // [vault_id, redeem_id, amount_minted] + MintTokensForReimbursedRedeem(AccountId, H256, Wrapped), } ); @@ -229,8 +231,8 @@ decl_module! { fn mint_tokens_for_reimbursed_redeem(origin, redeem_id: H256) -> DispatchResult { - let redeemer = ensure_signed(origin)?; - Self::_mint_tokens_for_reimbursed_redeem(redeemer, redeem_id)?; + let vault = ensure_signed(origin)?; + Self::_mint_tokens_for_reimbursed_redeem(vault, redeem_id)?; Ok(()) } } @@ -485,7 +487,7 @@ impl Module { // first update the issued tokens; this logic is the same regardless of whether or not the vault is liquidated if reimburse { // Transfer the transaction fee to the pool. Even though the redeem was not - // successful, the user receives a premium in collateral, so it's to take the fee. + // successful, the user receives a premium in collateral, so it's OK to take the fee. ext::treasury::unlock_and_transfer::( redeem.redeemer.clone(), ext::fee::fee_pool_account_id::(), @@ -556,7 +558,13 @@ impl Module { ext::vault_registry::issue_tokens::(&vault_id, reimbursed_amount)?; ext::treasury::mint::(vault_id, reimbursed_amount); - Self::set_redeem_status(redeem_id, RedeemRequestStatus::Completed); + Self::set_redeem_status(redeem_id, RedeemRequestStatus::Reimbursed(true)); + + Self::deposit_event(>::MintTokensForReimbursedRedeem( + vault_id, + redeem_id, + amount_wrapped, + )); Ok(()) } From 4c6d9575c555105239cfffd91eb49ea1843a96bf Mon Sep 17 00:00:00 2001 From: Sander Bosma Date: Mon, 24 May 2021 15:14:58 +0200 Subject: [PATCH 2/3] fix: CancelRedeem status to differentiate between Reimbursed(true)&Reimbursed(false) --- crates/redeem/src/lib.rs | 27 ++++++++++++++------------- crates/redeem/src/tests.rs | 8 +++++++- crates/redeem/src/types.rs | 2 +- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/crates/redeem/src/lib.rs b/crates/redeem/src/lib.rs index 2866c77bca..4c907a878a 100644 --- a/crates/redeem/src/lib.rs +++ b/crates/redeem/src/lib.rs @@ -104,8 +104,8 @@ decl_event!( LiquidationRedeem(AccountId, Wrapped), // [redeem_id, redeemer, amount_wrapped, fee_wrapped, vault] ExecuteRedeem(H256, AccountId, Wrapped, Wrapped, AccountId), - // [redeem_id, redeemer, vault_id, slashing_amount_in_collateral, reimburse] - CancelRedeem(H256, AccountId, AccountId, Collateral, bool), + // [redeem_id, redeemer, vault_id, slashing_amount_in_collateral, status] + CancelRedeem(H256, AccountId, AccountId, Collateral, RedeemRequestStatus), // [vault_id, redeem_id, amount_minted] MintTokensForReimbursedRedeem(AccountId, H256, Wrapped), } @@ -485,7 +485,7 @@ impl Module { }; // first update the issued tokens; this logic is the same regardless of whether or not the vault is liquidated - if reimburse { + let new_status = if reimburse { // Transfer the transaction fee to the pool. Even though the redeem was not // successful, the user receives a premium in collateral, so it's OK to take the fee. ext::treasury::unlock_and_transfer::( @@ -499,7 +499,7 @@ impl Module { // vault can not afford to back the tokens that he would receive, so we burn it ext::treasury::burn::(redeemer.clone(), vault_to_be_burned_tokens)?; ext::vault_registry::decrease_tokens::(&redeem.vault, &redeem.redeemer, vault_to_be_burned_tokens)?; - Self::set_redeem_status(redeem_id, RedeemRequestStatus::Reimbursed(false)); + Self::set_redeem_status(redeem_id, RedeemRequestStatus::Reimbursed(false)) } else { // Transfer the rest of the user's issued tokens (i.e. excluding fee) to the vault ext::treasury::unlock_and_transfer::( @@ -508,7 +508,7 @@ impl Module { vault_to_be_burned_tokens, )?; ext::vault_registry::decrease_to_be_redeemed_tokens::(&vault_id, vault_to_be_burned_tokens)?; - Self::set_redeem_status(redeem_id, RedeemRequestStatus::Reimbursed(true)); + Self::set_redeem_status(redeem_id, RedeemRequestStatus::Reimbursed(true)) } } else { // unlock user's issued tokens, including fee @@ -520,8 +520,8 @@ impl Module { .ok_or(Error::::ArithmeticOverflow)?; ext::treasury::unlock::(redeemer.clone(), total_wrapped)?; ext::vault_registry::decrease_to_be_redeemed_tokens::(&vault_id, vault_to_be_burned_tokens)?; - Self::set_redeem_status(redeem_id, RedeemRequestStatus::Retried); - } + Self::set_redeem_status(redeem_id, RedeemRequestStatus::Retried) + }; ext::sla::event_update_vault_sla::(&vault_id, ext::sla::VaultEvent::RedeemFailure)?; Self::deposit_event(>::CancelRedeem( @@ -529,7 +529,7 @@ impl Module { redeemer, redeem.vault, slashed_amount, - reimburse, + new_status, )); Ok(()) @@ -561,9 +561,9 @@ impl Module { Self::set_redeem_status(redeem_id, RedeemRequestStatus::Reimbursed(true)); Self::deposit_event(>::MintTokensForReimbursedRedeem( - vault_id, + redeem.vault, redeem_id, - amount_wrapped, + reimbursed_amount, )); Ok(()) @@ -593,11 +593,12 @@ impl Module { >::insert(key, value) } - fn set_redeem_status(id: H256, status: RedeemRequestStatus) { - // TODO: delete redeem request from storage + fn set_redeem_status(id: H256, status: RedeemRequestStatus) -> RedeemRequestStatus { >::mutate(id, |request| { - request.status = status; + request.status = status.clone(); }); + + status } /// Fetch all redeem requests for the specified account. diff --git a/crates/redeem/src/tests.rs b/crates/redeem/src/tests.rs index 34bebe8a5a..9799893fe4 100644 --- a/crates/redeem/src/tests.rs +++ b/crates/redeem/src/tests.rs @@ -520,7 +520,13 @@ fn test_cancel_redeem_succeeds() { Redeem::get_open_redeem_request_from_id(&H256([0u8; 32])), TestError::RedeemCancelled, ); - assert_emitted!(Event::CancelRedeem(H256([0; 32]), ALICE, BOB, 0, false)); + assert_emitted!(Event::CancelRedeem( + H256([0; 32]), + ALICE, + BOB, + 0, + RedeemRequestStatus::Retried + )); }) } diff --git a/crates/redeem/src/types.rs b/crates/redeem/src/types.rs index e8075f0e0f..4c3f246267 100644 --- a/crates/redeem/src/types.rs +++ b/crates/redeem/src/types.rs @@ -24,7 +24,7 @@ pub(crate) type Collateral = <>:: pub(crate) type Wrapped = <>::Currency as Currency<::AccountId>>::Balance; -#[derive(Encode, Decode, Clone, PartialEq)] +#[derive(Encode, Decode, Clone, Eq, PartialEq)] #[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] pub enum RedeemRequestStatus { Pending, From 109ed9cf82063a21af10d4816d23e0c01ea1d230 Mon Sep 17 00:00:00 2001 From: Sander Bosma Date: Mon, 24 May 2021 16:57:55 +0200 Subject: [PATCH 3/3] test: mint_tokens_for_reimbursed_redeem equivalence to immediate Reimbursed(true) result --- parachain/runtime/tests/test_redeem.rs | 61 ++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/parachain/runtime/tests/test_redeem.rs b/parachain/runtime/tests/test_redeem.rs index 632a7e718b..129a9efef6 100644 --- a/parachain/runtime/tests/test_redeem.rs +++ b/parachain/runtime/tests/test_redeem.rs @@ -802,3 +802,64 @@ fn integration_test_redeem_banning() { ); }) } + +mod mint_tokens_for_reimbursed_redeem_equivalence_test { + use super::*; + + fn setup_cancelable_redeem_with_insufficient_collateral_for_reimburse() -> H256 { + let amount_btc = 10_000; + + // set collateral to the minimum amount required, such that the vault can not afford to both + // reimburse and keep collateral his current tokens + let required_collateral = + VaultRegistryPallet::get_required_collateral_for_wrapped(DEFAULT_VAULT_ISSUED).unwrap(); + CoreVaultData::force_to( + VAULT, + CoreVaultData { + backing_collateral: required_collateral, + ..CoreVaultData::vault(VAULT) + }, + ); + let redeem_id = setup_cancelable_redeem(USER, VAULT, 100000000, amount_btc); + let redeem = RedeemPallet::get_open_redeem_request_from_id(&redeem_id).unwrap(); + let amount_without_fee_as_collateral = + ExchangeRateOraclePallet::wrapped_to_collateral(redeem.amount_btc + redeem.transfer_fee_btc).unwrap(); + + let punishment_fee = FeePallet::get_punishment_fee(amount_without_fee_as_collateral).unwrap(); + assert!(punishment_fee > 0); + + SlaPallet::set_vault_sla(&account_of(VAULT), FixedI128::from(80)); + redeem_id + } + + fn get_additional_collateral() { + assert_ok!(VaultRegistryPallet::transfer_funds( + CurrencySource::FreeBalance(account_of(FAUCET)), + CurrencySource::Collateral(account_of(VAULT)), + 100_000_000_000, + )); + } + + #[test] + fn integration_test_mint_tokens_for_reimbursed_redeem_equivalence_to_succesful_cancel() { + // scenario 1: sufficient collateral + let result1 = test_with(|| { + let redeem_id = setup_cancelable_redeem_with_insufficient_collateral_for_reimburse(); + get_additional_collateral(); + assert_ok!(Call::Redeem(RedeemCall::cancel_redeem(redeem_id, true)).dispatch(origin_of(account_of(USER)))); + ParachainState::get() + }); + // scenario 2: insufficient collateral + let result2 = test_with(|| { + let redeem_id = setup_cancelable_redeem_with_insufficient_collateral_for_reimburse(); + assert_ok!(Call::Redeem(RedeemCall::cancel_redeem(redeem_id, true)).dispatch(origin_of(account_of(USER)))); + get_additional_collateral(); + SecurityPallet::set_active_block_number(100000000); + assert_ok!(Call::Redeem(RedeemCall::mint_tokens_for_reimbursed_redeem(redeem_id)) + .dispatch(origin_of(account_of(VAULT)))); + ParachainState::get() + }); + // the states should be identical + assert_eq!(result1, result2); + } +}