Skip to content

Commit

Permalink
feat: allow requester to call cancel_issue
Browse files Browse the repository at this point in the history
Signed-off-by: Gregory Hill <gregorydhill@outlook.com>
  • Loading branch information
gregdhill committed Aug 18, 2022
1 parent 99f30ae commit 053839e
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 26 deletions.
82 changes: 60 additions & 22 deletions crates/issue/src/lib.rs
Expand Up @@ -36,6 +36,7 @@ use frame_support::{dispatch::DispatchError, ensure, traits::Get, transactional}
use frame_system::{ensure_root, ensure_signed};
pub use pallet::*;
use sp_core::H256;
use sp_runtime::traits::{Convert, Saturating};
use sp_std::vec::Vec;
use types::IssueRequestExt;
use vault_registry::{types::CurrencyId, CurrencySource, VaultStatus};
Expand All @@ -60,6 +61,9 @@ pub mod pallet {
/// The overarching event type.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;

/// Convert the block number into a balance.
type BlockNumberToBalance: Convert<Self::BlockNumber, BalanceOf<Self>>;

/// Weight information for the extrinsics in this module.
type WeightInfo: WeightInfo;
}
Expand Down Expand Up @@ -465,37 +469,71 @@ impl<T: Config> Pallet<T> {
fn _cancel_issue(requester: T::AccountId, issue_id: H256) -> Result<(), DispatchError> {
let issue = Self::get_issue_request_from_id(&issue_id)?;

// only cancellable after the request has expired
ensure!(
ext::btc_relay::has_request_expired::<T>(
issue.opentime,
issue.btc_height,
Self::issue_period().max(issue.period)
)?,
Error::<T>::TimeNotExpired
);

// Decrease to-be-redeemed tokens:
let full_amount = issue.amount().checked_add(&issue.fee())?;

ext::vault_registry::decrease_to_be_issued_tokens::<T>(&issue.vault, &full_amount)?;

let griefing_collateral = issue.griefing_collateral();
if ext::vault_registry::is_vault_liquidated::<T>(&issue.vault)? {
griefing_collateral.unlock_on(&issue.requester)?;
} else {
let griefing_collateral = if ext::btc_relay::has_request_expired::<T>(
issue.opentime,
issue.btc_height,
Self::issue_period().max(issue.period),
)? {
// anyone can cancel the issue request once expired
let griefing_collateral = issue.griefing_collateral();
if ext::vault_registry::is_vault_liquidated::<T>(&issue.vault)? {
griefing_collateral.unlock_on(&issue.requester)?;
} else {
ext::vault_registry::transfer_funds::<T>(
CurrencySource::UserGriefing(issue.requester.clone()),
CurrencySource::FreeBalance(issue.vault.account_id.clone()),
&griefing_collateral,
)?;
}
griefing_collateral
} else if issue.requester == requester {
// slash/release griefing collateral proportionally to the time elapsed
let blocks_elapsed = ext::security::active_block_number::<T>().saturating_sub(issue.opentime);
// NOTE: if global issue period increases requester will get more griefing collateral
let expected_end = Self::issue_period().max(issue.period);

let griefing_collateral = issue.griefing_collateral();
let slashed_collateral = ext::vault_registry::calculate_collateral::<T>(
&griefing_collateral,
// NOTE: workaround since BlockNumber doesn't inherit Into<U256>
&Amount::new(
T::BlockNumberToBalance::convert(blocks_elapsed),
griefing_collateral.currency(),
),
&Amount::new(
T::BlockNumberToBalance::convert(expected_end),
griefing_collateral.currency(),
),
)?
// we can never slash more than the griefing collateral
.min(&griefing_collateral)?;
// give this to the vault to cover the inconvenience
ext::vault_registry::transfer_funds::<T>(
CurrencySource::UserGriefing(issue.requester.clone()),
CurrencySource::FreeBalance(issue.vault.account_id.clone()),
&griefing_collateral,
&slashed_collateral,
)?;
}

// refund anything not slashed
let released_collateral = griefing_collateral.saturating_sub(&slashed_collateral)?;
released_collateral.unlock_on(&requester)?;

// TODO: update `issue.griefing_collateral`?
slashed_collateral
} else {
return Err(Error::<T>::TimeNotExpired.into());
};

// decrease to-be-redeemed tokens
let full_amount = issue.amount().checked_add(&issue.fee())?;
ext::vault_registry::decrease_to_be_issued_tokens::<T>(&issue.vault, &full_amount)?;

Self::set_issue_status(issue_id, IssueRequestStatus::Cancelled);

Self::deposit_event(Event::CancelIssue {
issue_id,
requester,
griefing_collateral: issue.griefing_collateral,
griefing_collateral: griefing_collateral.amount(),
});
Ok(())
}
Expand Down
11 changes: 10 additions & 1 deletion crates/issue/src/mock.rs
Expand Up @@ -14,7 +14,7 @@ use sp_arithmetic::{FixedI128, FixedPointNumber, FixedU128};
use sp_core::H256;
use sp_runtime::{
testing::{Header, TestXt},
traits::{BlakeTwo256, IdentityLookup, One, Zero},
traits::{BlakeTwo256, Convert, IdentityLookup, One, Zero},
};

type TestExtrinsic = TestXt<Call, ()>;
Expand Down Expand Up @@ -242,8 +242,17 @@ impl fee::Config for Test {
type MaxExpectedValue = MaxExpectedValue;
}

pub struct BlockNumberToBalance;

impl Convert<BlockNumber, Balance> for BlockNumberToBalance {
fn convert(a: BlockNumber) -> Balance {
a.into()
}
}

impl Config for Test {
type Event = TestEvent;
type BlockNumberToBalance = BlockNumberToBalance;
type WeightInfo = ();
}

Expand Down
65 changes: 63 additions & 2 deletions crates/issue/src/tests.rs
Expand Up @@ -5,6 +5,7 @@ use btc_relay::{BtcAddress, BtcPublicKey};
use currency::Amount;
use frame_support::{assert_noop, assert_ok, dispatch::DispatchError};
use mocktopus::mocking::*;
use primitives::issue::IssueRequestStatus;
use sp_arithmetic::FixedU128;
use sp_core::H256;
use sp_runtime::traits::One;
Expand Down Expand Up @@ -269,15 +270,75 @@ fn test_cancel_issue_not_found_fails() {
}

#[test]
fn test_cancel_issue_not_expired_fails() {
fn test_cancel_issue_not_expired_and_not_requester_fails() {
run_test(|| {
ext::vault_registry::get_active_vault_from_id::<Test>
.mock_safe(|_| MockResult::Return(Ok(init_zero_vault(VAULT))));

let issue_id = request_issue_ok(USER, 3, VAULT);
// issue period is 10, we issued at block 1, so at block 5 the cancel should fail
<security::Pallet<Test>>::set_active_block_number(5);
assert_noop!(cancel_issue(USER, &issue_id), TestError::TimeNotExpired,);
assert_noop!(cancel_issue(3, &issue_id), TestError::TimeNotExpired);
})
}

#[test]
fn test_cancel_issue_not_expired_and_requester_succeeds() {
run_test(|| {
ext::vault_registry::get_active_vault_from_id::<Test>
.mock_safe(|_| MockResult::Return(Ok(init_zero_vault(VAULT))));
ext::vault_registry::decrease_to_be_issued_tokens::<Test>.mock_safe(move |_, _| MockResult::Return(Ok(())));
ext::fee::get_issue_griefing_collateral::<Test>.mock_safe(move |_| MockResult::Return(Ok(griefing(100))));

let issue_id = request_issue_ok(USER, 300, VAULT);

unsafe {
// issue period is 10, we issued at block 1, so at block 4 the requester gets 70% griefing back
<security::Pallet<Test>>::set_active_block_number(4);

ext::vault_registry::transfer_funds::<Test>.mock_raw(|_, _, amount| {
assert_eq!(amount, &griefing(30));
MockResult::Return(Ok(()))
});

assert_ok!(cancel_issue(USER, &issue_id));

assert_eq!(
Issue::issue_requests(&issue_id).unwrap().status,
IssueRequestStatus::Cancelled
);
}
})
}

#[test]
fn test_cancel_issue_expired_succeeds() {
run_test(|| {
ext::vault_registry::get_active_vault_from_id::<Test>
.mock_safe(|_| MockResult::Return(Ok(init_zero_vault(VAULT))));
ext::vault_registry::decrease_to_be_issued_tokens::<Test>.mock_safe(move |_, _| MockResult::Return(Ok(())));
ext::vault_registry::is_vault_liquidated::<Test>.mock_safe(move |_| MockResult::Return(Ok(false)));
ext::fee::get_issue_griefing_collateral::<Test>.mock_safe(move |_| MockResult::Return(Ok(griefing(100))));
ext::btc_relay::has_request_expired::<Test>.mock_safe(move |_, _, _| MockResult::Return(Ok(true)));

let issue_id = request_issue_ok(USER, 300, VAULT);

unsafe {
// issue period is 10, we issued at block 1, so at block 12 the request has expired
<security::Pallet<Test>>::set_active_block_number(12);

ext::vault_registry::transfer_funds::<Test>.mock_raw(|_, _, amount| {
assert_eq!(amount, &griefing(100));
MockResult::Return(Ok(()))
});

assert_ok!(cancel_issue(USER, &issue_id));

assert_eq!(
Issue::issue_requests(&issue_id).unwrap().status,
IssueRequestStatus::Cancelled
);
}
})
}

Expand Down
1 change: 1 addition & 0 deletions parachain/runtime/interlay/src/lib.rs
Expand Up @@ -1013,6 +1013,7 @@ pub use issue::{Event as IssueEvent, IssueRequest};

impl issue::Config for Runtime {
type Event = Event;
type BlockNumberToBalance = BlockNumberToBalance;
type WeightInfo = ();
}

Expand Down
1 change: 1 addition & 0 deletions parachain/runtime/kintsugi/src/lib.rs
Expand Up @@ -1112,6 +1112,7 @@ pub use issue::{Event as IssueEvent, IssueRequest};

impl issue::Config for Runtime {
type Event = Event;
type BlockNumberToBalance = BlockNumberToBalance;
type WeightInfo = ();
}

Expand Down
1 change: 1 addition & 0 deletions parachain/runtime/testnet-interlay/src/lib.rs
Expand Up @@ -985,6 +985,7 @@ pub use issue::{Event as IssueEvent, IssueRequest};

impl issue::Config for Runtime {
type Event = Event;
type BlockNumberToBalance = BlockNumberToBalance;
type WeightInfo = ();
}

Expand Down
1 change: 1 addition & 0 deletions parachain/runtime/testnet-kintsugi/src/lib.rs
Expand Up @@ -985,6 +985,7 @@ pub use issue::{Event as IssueEvent, IssueRequest};

impl issue::Config for Runtime {
type Event = Event;
type BlockNumberToBalance = BlockNumberToBalance;
type WeightInfo = ();
}

Expand Down
1 change: 1 addition & 0 deletions standalone/runtime/src/lib.rs
Expand Up @@ -919,6 +919,7 @@ pub use issue::{Event as IssueEvent, IssueRequest};

impl issue::Config for Runtime {
type Event = Event;
type BlockNumberToBalance = BlockNumberToBalance;
type WeightInfo = ();
}

Expand Down
2 changes: 1 addition & 1 deletion standalone/runtime/tests/test_issue.rs
Expand Up @@ -56,7 +56,7 @@ mod expiry_test {
}

fn cancel_issue(issue_id: H256) -> DispatchResultWithPostInfo {
Call::Issue(IssueCall::cancel_issue { issue_id: issue_id }).dispatch(origin_of(account_of(USER)))
Call::Issue(IssueCall::cancel_issue { issue_id: issue_id }).dispatch(origin_of(account_of(VAULT)))
}

#[test]
Expand Down

0 comments on commit 053839e

Please sign in to comment.