Skip to content

Commit cda7e8f

Browse files
committed
token swap type (replay protection)
1 parent 88be1dc commit cda7e8f

File tree

4 files changed

+82
-9
lines changed

4 files changed

+82
-9
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

modules/token-swap/src/lib.rs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ use bp_messages::{
5454
DeliveredMessages, LaneId, MessageNonce,
5555
};
5656
use bp_runtime::{messages::DispatchFeePayment, InstanceId};
57-
use bp_token_swap::TokenSwap;
57+
use bp_token_swap::{TokenSwap, TokenSwapType};
5858
use codec::{Decode, Encode};
5959
use frame_support::{
6060
fail,
@@ -84,6 +84,8 @@ pub enum TokenSwapState {
8484

8585
pub use pallet::*;
8686

87+
// comes from #[pallet::event]
88+
#[allow(clippy::unused_unit)]
8789
#[frame_support::pallet]
8890
pub mod pallet {
8991
use super::*;
@@ -144,6 +146,7 @@ pub mod pallet {
144146
>;
145147
/// Type of `TokenSwap` used by the pallet.
146148
pub type TokenSwapOf<T, I> = TokenSwap<
149+
BlockNumberFor<T>,
147150
<<T as Config<I>>::ThisCurrency as Currency<<T as frame_system::Config>::AccountId>>::Balance,
148151
<T as frame_system::Config>::AccountId,
149152
<T as Config<I>>::BridgedBalance,
@@ -328,7 +331,16 @@ pub mod pallet {
328331
let swap_state = PendingSwaps::<T, I>::get(swap_hash);
329332
match swap_state {
330333
Some(TokenSwapState::Started) => fail!(Error::<T, I>::SwapIsPending),
331-
Some(TokenSwapState::Confirmed) => (),
334+
Some(TokenSwapState::Confirmed) => {
335+
let is_claim_allowed = match swap.swap_type {
336+
TokenSwapType::TemporaryTargetAccountAtBridgedChain => true,
337+
TokenSwapType::LockClaimUntilBlock(block_number, _) => {
338+
block_number < frame_system::Pallet::<T>::block_number()
339+
}
340+
};
341+
342+
ensure!(is_claim_allowed, Error::<T, I>::SwapIsTemporaryLocked);
343+
}
332344
Some(TokenSwapState::Failed) => fail!(Error::<T, I>::SwapIsFailed),
333345
None => fail!(Error::<T, I>::SwapIsInactive),
334346
}
@@ -338,7 +350,7 @@ pub mod pallet {
338350

339351
/// Return previously reserved `source_balance_at_this_chain` back to the `source_account_at_this_chain`.
340352
///
341-
/// This should be called only when transferhas failed at Bridged chain and we have received
353+
/// This should be called only when transfer has failed at Bridged chain and we have received
342354
/// notification about thate.
343355
#[pallet::weight(0)]
344356
pub fn cancel_swap(origin: OriginFor<T>, swap: TokenSwapOf<T, I>) -> DispatchResultWithPostInfo {
@@ -349,13 +361,16 @@ pub mod pallet {
349361
Error::<T, I>::MismatchedSwapSourceOrigin,
350362
);
351363

352-
// ensure that the swap is failed
364+
// ensure that the swap has failed
353365
let swap_hash = swap.using_encoded(blake2_256).into();
354366
let swap_state = PendingSwaps::<T, I>::get(swap_hash);
355367
match swap_state {
356368
Some(TokenSwapState::Started) => fail!(Error::<T, I>::SwapIsPending),
357369
Some(TokenSwapState::Confirmed) => fail!(Error::<T, I>::SwapIsConfirmed),
358-
Some(TokenSwapState::Failed) => (),
370+
Some(TokenSwapState::Failed) => {
371+
// we allow cancelling swap even before lock period is over - the `source_account_at_this_chain`
372+
// has already paid for nothing and it is up to him to decide whether he want to try again
373+
}
359374
None => fail!(Error::<T, I>::SwapIsInactive),
360375
}
361376

@@ -394,6 +409,11 @@ pub mod pallet {
394409
SwapIsPending,
395410
/// Someone is trying to claim swap that has failed.
396411
SwapIsFailed,
412+
/// Claiming swap is not allowed.
413+
///
414+
/// Now the only possible case when you may get this error, is when you're trying to claim swap with
415+
/// `TokenSwapType::LockClaimUntilBlock` before lock period is over.
416+
SwapIsTemporaryLocked,
397417
/// Someone is trying to cancel swap that has been confirmed.
398418
SwapIsConfirmed,
399419
/// Someone is trying to claim/cancel swap that is either not started or already claimed/cancelled.
@@ -496,12 +516,15 @@ mod tests {
496516
use crate::mock::*;
497517
use frame_support::{assert_noop, assert_ok};
498518

519+
const CAN_CLAIM_BLOCK_NUMBER: u64 = 11;
520+
499521
const BRIDGED_CHAIN_ACCOUNT_PUBLIC: BridgedAccountPublic = 1;
500522
const BRIDGED_CHAIN_ACCOUNT_SIGNATURE: BridgedAccountSignature = 2;
501523
const BRIDGED_CHAIN_ACCOUNT: BridgedAccountId = 3;
502524

503525
fn test_swap() -> TokenSwapOf<TestRuntime, ()> {
504526
bp_token_swap::TokenSwap {
527+
swap_type: TokenSwapType::LockClaimUntilBlock(CAN_CLAIM_BLOCK_NUMBER - 1, 0.into()),
505528
source_balance_at_this_chain: 100,
506529
source_account_at_this_chain: THIS_CHAIN_ACCOUNT,
507530
target_balance_at_bridged_chain: 200,
@@ -723,6 +746,7 @@ mod tests {
723746
#[test]
724747
fn claim_swap_fails_if_currency_transfer_from_swap_account_fails() {
725748
run_test(|| {
749+
frame_system::Pallet::<TestRuntime>::set_block_number(CAN_CLAIM_BLOCK_NUMBER);
726750
PendingSwaps::<TestRuntime, ()>::insert(test_swap_hash(), TokenSwapState::Confirmed);
727751

728752
assert_noop!(
@@ -735,13 +759,31 @@ mod tests {
735759
});
736760
}
737761

762+
#[test]
763+
fn claim_swap_fails_before_lock_period_is_completed() {
764+
run_test(|| {
765+
start_test_swap();
766+
receive_test_swap_confirmation(true);
767+
768+
frame_system::Pallet::<TestRuntime>::set_block_number(CAN_CLAIM_BLOCK_NUMBER - 1);
769+
770+
assert_noop!(
771+
Pallet::<TestRuntime>::claim_swap(
772+
Origin::signed(target_account_at_this_chain::<TestRuntime, ()>(&test_swap())),
773+
test_swap(),
774+
),
775+
Error::<TestRuntime, ()>::SwapIsTemporaryLocked
776+
);
777+
});
778+
}
779+
738780
#[test]
739781
fn claim_swap_succeeds() {
740782
run_test(|| {
741783
start_test_swap();
742784
receive_test_swap_confirmation(true);
743785

744-
frame_system::Pallet::<TestRuntime>::set_block_number(1);
786+
frame_system::Pallet::<TestRuntime>::set_block_number(CAN_CLAIM_BLOCK_NUMBER);
745787
frame_system::Pallet::<TestRuntime>::reset_events();
746788

747789
assert_ok!(Pallet::<TestRuntime>::claim_swap(

primitives/token-swap/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ codec = { package = "parity-scale-codec", version = "2.0.0", default-features =
1212
# Substrate Dependencies
1313

1414
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
15+
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
1516

1617
[features]
1718
default = ["std"]
1819
std = [
1920
"codec/std",
2021
"frame-support/std",
22+
"sp-core/std",
2123
]

primitives/token-swap/src/lib.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,43 @@
1616

1717
use codec::{Decode, Encode};
1818
use frame_support::RuntimeDebug;
19+
use sp_core::U256;
20+
21+
/// Token swap type.
22+
///
23+
/// Different swap types give a different guarantees regarding possible swap
24+
/// replay protection.
25+
#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq)]
26+
pub enum TokenSwapType<ThisBlockNumber> {
27+
/// The `target_account_at_bridged_chain` is temporary and only have funds for single swap.
28+
///
29+
/// ***WARNING**: if `target_account_at_bridged_chain` still exists after the swap has been
30+
/// completed (either by claiming or cancelling), the `source_account_at_this_chain` will be able
31+
/// to restart the swap again and repeat the swap until `target_account_at_bridged_chain` depletes.
32+
TemporaryTargetAccountAtBridgedChain,
33+
/// This swap type prevents `source_account_at_this_chain` from restarting the swap after it has
34+
/// been completed. There are two consequences:
35+
///
36+
/// 1) the `source_account_at_this_chain` won't be able to call `start_swap` after given <ThisBlockNumber>;
37+
/// 2) the `target_account_at_bridged_chain` won't be able to call `claim_swap` (over the bridge) before
38+
/// block `<ThisBlockNumber + 1>`.
39+
///
40+
/// The second element is the nonce of the swap. You must care about its uniqueness if you're
41+
/// planning to perform another swap with exactly the same parameters (i.e. same amount, same accounts,
42+
/// same `ThisBlockNumber`) to avoid collisions.
43+
LockClaimUntilBlock(ThisBlockNumber, U256),
44+
}
1945

2046
/// An intention to swap `source_balance_at_this_chain` owned by `source_account_at_this_chain`
2147
/// to `target_balance_at_bridged_chain` owned by `target_account_at_bridged_chain`.
2248
///
2349
/// **IMPORTANT NOTE**: this structure is always the same during single token swap. So even
24-
/// when chain changes, the meaning of This and Bridged chains is still the same. This chain
25-
/// is always the chain where swap has been started. And the Bridged chain is the other chain.
50+
/// when chain changes, the meaning of This and Bridged are still used to point to the same chains.
51+
/// This chain is always the chain where swap has been started. And the Bridged chain is the other chain.
2652
#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq)]
27-
pub struct TokenSwap<ThisBalance, ThisAccountId, BridgedBalance, BridgedAccountId> {
53+
pub struct TokenSwap<ThisBlockNumber, ThisBalance, ThisAccountId, BridgedBalance, BridgedAccountId> {
54+
/// The type of the swap.
55+
pub swap_type: TokenSwapType<ThisBlockNumber>,
2856
/// This chain balance to be swapped with `target_balance_at_bridged_chain`.
2957
pub source_balance_at_this_chain: ThisBalance,
3058
/// Account id of the party acting at This chain and owning the `source_account_at_this_chain`.

0 commit comments

Comments
 (0)