Skip to content

Commit

Permalink
Merge pull request #580 from gregdhill/feat/recovery-replace
Browse files Browse the repository at this point in the history
feat: allow cancelled replace requests to be executed
  • Loading branch information
gregdhill authored Apr 20, 2022
2 parents 96b64bd + 2159e4d commit f1ed520
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 26 deletions.
70 changes: 54 additions & 16 deletions crates/replace/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,20 @@ impl<T: Config> Pallet<T> {
Ok(())
}

fn accept_replace_tokens(
old_vault_id: &DefaultVaultId<T>,
new_vault_id: &DefaultVaultId<T>,
redeemable_tokens: &Amount<T>,
) -> DispatchResult {
// increase old-vault's to-be-redeemed tokens - this should never fail
ext::vault_registry::try_increase_to_be_redeemed_tokens::<T>(old_vault_id, redeemable_tokens)?;

// increase new-vault's to-be-issued tokens - this will fail if there is insufficient collateral
ext::vault_registry::try_increase_to_be_issued_tokens::<T>(new_vault_id, redeemable_tokens)?;

Ok(())
}

fn _accept_replace(
old_vault_id: DefaultVaultId<T>,
new_vault_id: DefaultVaultId<T>,
Expand Down Expand Up @@ -424,11 +438,7 @@ impl<T: Config> Pallet<T> {

ext::vault_registry::try_deposit_collateral::<T>(&new_vault_id, &actual_new_vault_collateral)?;

// increase old-vault's to-be-redeemed tokens - this should never fail
ext::vault_registry::try_increase_to_be_redeemed_tokens::<T>(&old_vault_id, &redeemable_tokens)?;

// increase new-vault's to-be-issued tokens - this will fail if there is insufficient collateral
ext::vault_registry::try_increase_to_be_issued_tokens::<T>(&new_vault_id, &redeemable_tokens)?;
Self::accept_replace_tokens(&old_vault_id, &new_vault_id, &redeemable_tokens)?;

ext::vault_registry::transfer_funds(
CurrencySource::AvailableReplaceCollateral(old_vault_id.clone()),
Expand Down Expand Up @@ -466,9 +476,10 @@ impl<T: Config> Pallet<T> {
Ok(())
}

fn _execute_replace(replace_id: H256, raw_merkle_proof: Vec<u8>, raw_tx: Vec<u8>) -> Result<(), DispatchError> {
// Retrieve the ReplaceRequest as per the replaceId parameter from Vaults in the VaultRegistry
let replace = Self::get_open_replace_request(&replace_id)?;
fn _execute_replace(replace_id: H256, raw_merkle_proof: Vec<u8>, raw_tx: Vec<u8>) -> DispatchResult {
// retrieve the replace request using the id parameter
// we can still execute cancelled requests
let replace = Self::get_open_or_cancelled_replace_request(&replace_id)?;

let griefing_collateral: Amount<T> = replace.griefing_collateral();
let amount = replace.amount();
Expand All @@ -489,17 +500,35 @@ impl<T: Config> Pallet<T> {
replace_id,
)?;

// only return griefing collateral if not already slashed
let collateral = match replace.status {
ReplaceRequestStatus::Pending => {
// give old-vault the griefing collateral
ext::vault_registry::transfer_funds(
CurrencySource::ActiveReplaceCollateral(old_vault_id.clone()),
CurrencySource::FreeBalance(old_vault_id.account_id.clone()),
&griefing_collateral,
)?;
// NOTE: this is just the additional collateral already locked on accept
// it is only used in the ReplaceTokens event
collateral
}
ReplaceRequestStatus::Cancelled => {
// we need to re-accept first, this will check that the vault is over the secure threshold
Self::accept_replace_tokens(&old_vault_id, &new_vault_id, &amount)?;
// no additional collateral locked for this
Amount::zero(collateral.currency())
}
ReplaceRequestStatus::Completed => {
// we never enter this branch as completed requests are filtered
return Err(Error::<T>::ReplaceCompleted.into());
}
};

// decrease old-vault's issued & to-be-redeemed tokens, and
// change new-vault's to-be-issued tokens to issued tokens
ext::vault_registry::replace_tokens::<T>(&old_vault_id, &new_vault_id, &amount, &collateral)?;

// Give oldvault back its griefing collateral
ext::vault_registry::transfer_funds(
CurrencySource::ActiveReplaceCollateral(old_vault_id.clone()),
CurrencySource::FreeBalance(old_vault_id.account_id.clone()),
&griefing_collateral,
)?;

// Emit ExecuteReplace event.
Self::deposit_event(Event::<T>::ExecuteReplace {
replace_id: replace_id,
Expand Down Expand Up @@ -547,7 +576,7 @@ impl<T: Config> Pallet<T> {
)?;

// if the new_vault locked additional collateral especially for this replace,
// release it if it does not cause him to be undercollateralized
// release it if it does not cause them to be undercollateralized
if !ext::vault_registry::is_vault_liquidated::<T>(&new_vault_id)?
&& ext::vault_registry::is_allowed_to_withdraw_collateral::<T>(&new_vault_id, &collateral)?
{
Expand Down Expand Up @@ -612,6 +641,15 @@ impl<T: Config> Pallet<T> {
}
}

/// Get a open or cancelled replace request by id. Completed requests are not returned.
pub fn get_open_or_cancelled_replace_request(id: &H256) -> Result<DefaultReplaceRequest<T>, DispatchError> {
let request = <ReplaceRequests<T>>::get(id).ok_or(Error::<T>::ReplaceIdNotFound)?;
match request.status {
ReplaceRequestStatus::Pending | ReplaceRequestStatus::Cancelled => Ok(request),
ReplaceRequestStatus::Completed => Err(Error::<T>::ReplaceCompleted.into()),
}
}

fn insert_replace_request(key: &H256, value: &DefaultReplaceRequest<T>) {
<ReplaceRequests<T>>::insert(key, value)
}
Expand Down
42 changes: 35 additions & 7 deletions crates/replace/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::{ext, mock::*, ReplaceRequest, ReplaceRequestStatus};
use crate::{
mock::{CurrencyId, *},
*,
};

use bitcoin::types::{MerkleProof, Transaction};
use btc_relay::BtcAddress;
Expand Down Expand Up @@ -142,11 +145,11 @@ mod accept_replace_tests {
BtcAddress::default()
));
assert_event_matches!(Event::AcceptReplace{
replace_id: _,
replace_id: _,
old_vault_id: OLD_VAULT,
new_vault_id: NEW_VAULT,
amount: 5,
collateral: 10,
amount: 5,
collateral: 10,
btc_address: addr} if addr == BtcAddress::default());
})
}
Expand All @@ -171,7 +174,7 @@ mod accept_replace_tests {
old_vault_id: OLD_VAULT,
new_vault_id: NEW_VAULT,
amount: 4,
collateral: 8,
collateral: 8,
btc_address: addr} if addr == BtcAddress::default());
})
}
Expand All @@ -196,11 +199,11 @@ mod execute_replace_test {
use super::*;

fn setup_mocks() {
Replace::get_open_replace_request.mock_safe(move |_| {
ReplaceRequests::<Test>::insert(H256::zero(), {
let mut replace = test_request();
replace.old_vault = OLD_VAULT;
replace.new_vault = NEW_VAULT;
MockResult::Return(Ok(replace))
replace
});

Replace::replace_period.mock_safe(|| MockResult::Return(20));
Expand All @@ -212,6 +215,9 @@ mod execute_replace_test {
ext::vault_registry::replace_tokens::<Test>.mock_safe(|_, _, _, _| MockResult::Return(Ok(())));
Amount::<Test>::unlock_on.mock_safe(|_, _| MockResult::Return(Ok(())));
ext::vault_registry::transfer_funds::<Test>.mock_safe(|_, _, _| MockResult::Return(Ok(())));

ext::vault_registry::try_increase_to_be_redeemed_tokens::<Test>.mock_safe(|_, _| MockResult::Return(Ok(())));
ext::vault_registry::try_increase_to_be_issued_tokens::<Test>.mock_safe(|_, _| MockResult::Return(Ok(())));
}

#[test]
Expand All @@ -226,6 +232,28 @@ mod execute_replace_test {
});
})
}

#[test]
fn should_execute_cancelled_request() {
run_test(|| {
setup_mocks();

ReplaceRequests::<Test>::insert(H256::zero(), {
let mut replace = test_request();
replace.old_vault = OLD_VAULT;
replace.new_vault = NEW_VAULT;
replace.status = ReplaceRequestStatus::Cancelled;
replace
});

assert_ok!(Replace::_execute_replace(H256::zero(), Vec::new(), Vec::new()));
assert_event_matches!(Event::ExecuteReplace {
replace_id: _,
old_vault_id: OLD_VAULT,
new_vault_id: NEW_VAULT
});
})
}
}

mod cancel_replace_tests {
Expand Down
10 changes: 10 additions & 0 deletions standalone/runtime/tests/mock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,16 @@ pub struct UserData {
pub balances: BTreeMap<CurrencyId, AccountData>,
}

pub trait Collateral {
fn collateral(&self, amount: Balance) -> Amount<Runtime>;
}

impl Collateral for VaultId {
fn collateral(&self, amount: Balance) -> Amount<Runtime> {
Amount::new(amount, self.collateral_currency())
}
}

pub trait Wrapped {
fn wrapped(&self, amount: Balance) -> Amount<Runtime>;
}
Expand Down
11 changes: 10 additions & 1 deletion standalone/runtime/tests/mock/replace_testing_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ pub fn setup_replace(
old_vault_id: &VaultId,
new_vault_id: &VaultId,
issued_tokens: Amount<Runtime>,
) -> (ReplaceRequest<AccountId32, BlockNumber, Balance, CurrencyId>, H256) {
setup_replace_with_collateral(old_vault_id, new_vault_id, issued_tokens, old_vault_id.collateral(0))
}

pub fn setup_replace_with_collateral(
old_vault_id: &VaultId,
new_vault_id: &VaultId,
issued_tokens: Amount<Runtime>,
collateral: Amount<Runtime>,
) -> (ReplaceRequest<AccountId32, BlockNumber, Balance, CurrencyId>, H256) {
let new_vault_btc_address = BtcAddress::P2PKH(H160([2; 20]));

Expand All @@ -23,7 +32,7 @@ pub fn setup_replace(
&old_vault_id,
&new_vault_id,
issued_tokens,
griefing(0),
collateral,
new_vault_btc_address,
)
.unwrap();
Expand Down
64 changes: 62 additions & 2 deletions standalone/runtime/tests/test_replace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -744,12 +744,12 @@ fn integration_test_replace_cancel_replace() {
// liquidation tests

fn execute_replace(replace_id: H256) -> DispatchResultWithPostInfo {
let replace = ReplacePallet::get_open_replace_request(&replace_id).unwrap();
let replace = ReplacePallet::get_open_or_cancelled_replace_request(&replace_id).unwrap();
execute_replace_with_amount(replace_id, replace.amount())
}

fn execute_replace_with_amount(replace_id: H256, amount: Amount<Runtime>) -> DispatchResultWithPostInfo {
let replace = ReplacePallet::get_open_replace_request(&replace_id).unwrap();
let replace = ReplacePallet::get_open_or_cancelled_replace_request(&replace_id).unwrap();

// send the btc from the old_vault to the new_vault
let (_tx_id, _tx_block_height, merkle_proof, raw_tx, _) = generate_transaction_and_mine(
Expand Down Expand Up @@ -918,6 +918,66 @@ fn integration_test_replace_execute_replace_both_vaults_liquidated() {
});
}

#[test]
fn integration_test_replace_execute_replace_with_cancelled() {
test_with(|old_vault_id, new_vault_id| {
let issued_tokens = old_vault_id.wrapped(1000);
let (replace, replace_id) = setup_replace(&old_vault_id, &new_vault_id, issued_tokens);

let before = ParachainTwoVaultState::get(&old_vault_id, &new_vault_id);
cancel_replace(replace_id);
assert_ok!(execute_replace(replace_id));

assert_eq!(
ParachainTwoVaultState::get(&old_vault_id, &new_vault_id),
before.with_changes(|old_vault, new_vault, _| {
new_vault.to_be_issued -= issued_tokens;
new_vault.issued += issued_tokens;
old_vault.to_be_redeemed -= issued_tokens;
old_vault.issued -= issued_tokens;

old_vault.griefing_collateral -= replace.griefing_collateral();
*new_vault.free_balance.get_mut(&DEFAULT_GRIEFING_CURRENCY).unwrap() += replace.griefing_collateral();
})
);
});
}

#[test]
fn integration_test_replace_execute_replace_with_additional_and_cancelled() {
test_with(|old_vault_id, new_vault_id| {
let issued_tokens = old_vault_id.wrapped(1000);
let collateral = old_vault_id.collateral(100000);
let (replace, replace_id) =
setup_replace_with_collateral(&old_vault_id, &new_vault_id, issued_tokens, collateral);

let before = ParachainTwoVaultState::get(&old_vault_id, &new_vault_id);
cancel_replace(replace_id);
assert_ok!(execute_replace(replace_id));

assert_eq!(
ParachainTwoVaultState::get(&old_vault_id, &new_vault_id),
before.with_changes(|old_vault, new_vault, _| {
new_vault.to_be_issued -= issued_tokens;
new_vault.issued += issued_tokens;
old_vault.to_be_redeemed -= issued_tokens;
old_vault.issued -= issued_tokens;

// griefing collateral is slashed
old_vault.griefing_collateral -= replace.griefing_collateral();
*new_vault.free_balance.get_mut(&DEFAULT_GRIEFING_CURRENCY).unwrap() += replace.griefing_collateral();

// backing collateral is returned
new_vault.backing_collateral -= replace.collateral().unwrap();
*new_vault
.free_balance
.get_mut(&new_vault_id.collateral_currency())
.unwrap() += replace.collateral().unwrap();
})
);
});
}

#[test]
fn integration_test_replace_cancel_replace_success() {
test_with(|old_vault_id, new_vault_id| {
Expand Down

0 comments on commit f1ed520

Please sign in to comment.