Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

pallet-atomic-swap: generialized swap action #6421

Merged
merged 4 commits into from
Jun 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// and set impl_version to 0. If only runtime
// implementation changes and behavior does not, then leave spec_version as
// is and increment impl_version.
spec_version: 253,
spec_version: 254,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
Expand Down
145 changes: 102 additions & 43 deletions frame/atomic-swap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@

mod tests;

use sp_std::prelude::*;
use sp_std::{prelude::*, marker::PhantomData, ops::{Deref, DerefMut}};
use sp_io::hashing::blake2_256;
use frame_support::{
decl_module, decl_storage, decl_event, decl_error, ensure,
Parameter, decl_module, decl_storage, decl_event, decl_error, ensure,
traits::{Get, Currency, ReservableCurrency, BalanceStatus},
weights::Weight,
dispatch::DispatchResult,
Expand All @@ -35,37 +35,98 @@ use codec::{Encode, Decode};
use sp_runtime::RuntimeDebug;

/// Pending atomic swap operation.
#[derive(Clone, RuntimeDebug, Eq, PartialEq, Encode, Decode)]
pub struct PendingSwap<AccountId, Balance, BlockNumber> {
#[derive(Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode)]
pub struct PendingSwap<T: Trait> {
/// Source of the swap.
pub source: AccountId,
/// Balance value of the swap.
pub balance: Balance,
pub source: T::AccountId,
/// Action of this swap.
pub action: T::SwapAction,
/// End block of the lock.
pub end_block: BlockNumber,
pub end_block: T::BlockNumber,
}

/// Balance type from the pallet's point of view.
pub type BalanceFor<T> = <<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
/// Hashed proof type.
pub type HashedProof = [u8; 32];

/// AccountId type from the pallet's point of view.
pub type AccountIdFor<T> = <T as frame_system::Trait>::AccountId;
/// Definition of a pending atomic swap action. It contains the following three phrases:
///
/// - **Reserve**: reserve the resources needed for a swap. This is to make sure that **Claim**
/// succeeds with best efforts.
/// - **Claim**: claim any resources reserved in the first phrase.
/// - **Cancel**: cancel any resources reserved in the first phrase.
pub trait SwapAction<T: Trait> {
/// Reserve the resources needed for the swap, from the given `source`. The reservation is
/// allowed to fail. If that is the case, the the full swap creation operation is cancelled.
fn reserve(&self, source: &T::AccountId) -> DispatchResult;
/// Claim the reserved resources, with `source` and `target`. Returns whether the claim
/// succeeds.
fn claim(&self, source: &T::AccountId, target: &T::AccountId) -> bool;
/// Weight for executing the operation.
fn weight(&self) -> Weight;
/// Cancel the resources reserved in `source`.
fn cancel(&self, source: &T::AccountId);
}

/// BlockNumber type from the pallet's point of view.
pub type BlockNumberFor<T> = <T as frame_system::Trait>::BlockNumber;
/// A swap action that only allows transferring balances.
#[derive(Clone, RuntimeDebug, Eq, PartialEq, Encode, Decode)]
pub struct BalanceSwapAction<T: Trait, C: ReservableCurrency<T::AccountId>> {
value: <C as Currency<<T as frame_system::Trait>::AccountId>>::Balance,
_marker: PhantomData<C>,
}

/// PendingSwap type from the pallet's point of view.
pub type PendingSwapFor<T> = PendingSwap<AccountIdFor<T>, BalanceFor<T>, BlockNumberFor<T>>;
impl<T: Trait, C> BalanceSwapAction<T, C> where
C: ReservableCurrency<T::AccountId>,
{
/// Create a new swap action value of balance.
pub fn new(value: <C as Currency<<T as frame_system::Trait>::AccountId>>::Balance) -> Self {
Self { value, _marker: PhantomData }
}
}

/// Hashed proof type.
pub type HashedProof = [u8; 32];
impl<T: Trait, C> Deref for BalanceSwapAction<T, C> where
C: ReservableCurrency<T::AccountId>,
{
type Target = <C as Currency<<T as frame_system::Trait>::AccountId>>::Balance;

fn deref(&self) -> &Self::Target {
&self.value
}
}

impl<T: Trait, C> DerefMut for BalanceSwapAction<T, C> where
C: ReservableCurrency<T::AccountId>,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.value
}
}

impl<T: Trait, C> SwapAction<T> for BalanceSwapAction<T, C> where
C: ReservableCurrency<T::AccountId>,
{
fn reserve(&self, source: &T::AccountId) -> DispatchResult {
C::reserve(&source, self.value)
}

fn claim(&self, source: &T::AccountId, target: &T::AccountId) -> bool {
C::repatriate_reserved(source, target, self.value, BalanceStatus::Free).is_ok()
}

fn weight(&self) -> Weight {
T::DbWeight::get().reads_writes(1, 1)
}

fn cancel(&self, source: &T::AccountId) {
C::unreserve(source, self.value);
}
}

/// Atomic swap's pallet configuration trait.
pub trait Trait: frame_system::Trait {
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
/// The currency mechanism.
type Currency: ReservableCurrency<Self::AccountId>;
/// Swap action.
type SwapAction: SwapAction<Self> + Parameter;
/// Limit of proof size.
///
/// Atomic swap is only atomic if once the proof is revealed, both parties can submit the proofs
Expand All @@ -83,7 +144,7 @@ decl_storage! {
trait Store for Module<T: Trait> as AtomicSwap {
pub PendingSwaps: double_map
hasher(twox_64_concat) T::AccountId, hasher(blake2_128_concat) HashedProof
=> Option<PendingSwapFor<T>>;
=> Option<PendingSwap<T>>;
}
}

Expand All @@ -101,6 +162,8 @@ decl_error! {
AlreadyClaimed,
/// Swap does not exist.
NotExist,
/// Claim action mismatch.
ClaimActionMismatch,
/// Duration has not yet passed for the swap to be cancelled.
DurationNotPassed,
}
Expand All @@ -109,14 +172,13 @@ decl_error! {
decl_event!(
/// Event of atomic swap pallet.
pub enum Event<T> where
Balance = BalanceFor<T>,
AccountId = AccountIdFor<T>,
PendingSwap = PendingSwapFor<T>,
AccountId = <T as system::Trait>::AccountId,
PendingSwap = PendingSwap<T>,
{
/// Swap created.
NewSwap(AccountId, HashedProof, PendingSwap),
/// Swap claimed. The last parameter indicates whether the execution succeeds.
SwapClaimed(AccountId, HashedProof, Balance, bool),
SwapClaimed(AccountId, HashedProof, bool),
/// Swap cancelled.
SwapCancelled(AccountId, HashedProof),
}
Expand Down Expand Up @@ -144,22 +206,22 @@ decl_module! {
#[weight = T::DbWeight::get().reads_writes(1, 1).saturating_add(40_000_000)]
fn create_swap(
origin,
target: AccountIdFor<T>,
target: T::AccountId,
hashed_proof: HashedProof,
balance: BalanceFor<T>,
duration: BlockNumberFor<T>,
action: T::SwapAction,
duration: T::BlockNumber,
) {
let source = ensure_signed(origin)?;
ensure!(
!PendingSwaps::<T>::contains_key(&target, hashed_proof),
Error::<T>::AlreadyExist
);

T::Currency::reserve(&source, balance)?;
action.reserve(&source)?;

let swap = PendingSwap {
source,
balance,
action,
end_block: frame_system::Module::<T>::block_number() + duration,
};
PendingSwaps::<T>::insert(target.clone(), hashed_proof.clone(), swap.clone());
Expand All @@ -174,13 +236,17 @@ decl_module! {
/// The dispatch origin for this call must be _Signed_.
///
/// - `proof`: Revealed proof of the claim.
#[weight = T::DbWeight::get().reads_writes(2, 2)
/// - `action`: Action defined in the swap, it must match the entry in blockchain. Otherwise
/// the operation fails. This is used for weight calculation.
#[weight = T::DbWeight::get().reads_writes(1, 1)
.saturating_add(40_000_000)
.saturating_add((proof.len() as Weight).saturating_mul(100))
.saturating_add(action.weight())
]
fn claim_swap(
origin,
proof: Vec<u8>,
action: T::SwapAction,
) -> DispatchResult {
ensure!(
proof.len() <= T::ProofLimit::get() as usize,
Expand All @@ -192,18 +258,14 @@ decl_module! {

let swap = PendingSwaps::<T>::get(&target, hashed_proof)
.ok_or(Error::<T>::InvalidProof)?;
ensure!(swap.action == action, Error::<T>::ClaimActionMismatch);

let succeeded = T::Currency::repatriate_reserved(
&swap.source,
&target,
swap.balance,
BalanceStatus::Free,
).is_ok();
let succeeded = swap.action.claim(&swap.source, &target);

PendingSwaps::<T>::remove(target.clone(), hashed_proof.clone());

Self::deposit_event(
RawEvent::SwapClaimed(target, hashed_proof, swap.balance, succeeded)
RawEvent::SwapClaimed(target, hashed_proof, succeeded)
);

Ok(())
Expand All @@ -218,7 +280,7 @@ decl_module! {
#[weight = T::DbWeight::get().reads_writes(1, 1).saturating_add(40_000_000)]
fn cancel_swap(
origin,
target: AccountIdFor<T>,
target: T::AccountId,
hashed_proof: HashedProof,
) {
let source = ensure_signed(origin)?;
Expand All @@ -234,10 +296,7 @@ decl_module! {
Error::<T>::DurationNotPassed,
);

T::Currency::unreserve(
&swap.source,
swap.balance,
);
swap.action.cancel(&swap.source);
PendingSwaps::<T>::remove(&target, hashed_proof.clone());

Self::deposit_event(
Expand Down
10 changes: 6 additions & 4 deletions frame/atomic-swap/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl_outer_origin! {
// For testing the pallet, we construct most of a mock runtime. This means
// first constructing a configuration type (`Test`) which `impl`s each of the
// configuration traits of pallets we want to use.
#[derive(Clone, Eq, PartialEq)]
#[derive(Clone, Eq, Debug, PartialEq)]
pub struct Test;
parameter_types! {
pub const BlockHashCount: u64 = 250;
Expand Down Expand Up @@ -71,7 +71,7 @@ parameter_types! {
}
impl Trait for Test {
type Event = ();
type Currency = Balances;
type SwapAction = BalanceSwapAction<Test, Balances>;
type ProofLimit = ProofLimit;
}
type System = frame_system::Module<Test>;
Expand Down Expand Up @@ -109,7 +109,7 @@ fn two_party_successful_swap() {
Origin::signed(A),
B,
hashed_proof.clone(),
50,
BalanceSwapAction::new(50),
1000,
).unwrap();

Expand All @@ -123,7 +123,7 @@ fn two_party_successful_swap() {
Origin::signed(B),
A,
hashed_proof.clone(),
75,
BalanceSwapAction::new(75),
1000,
).unwrap();

Expand All @@ -136,6 +136,7 @@ fn two_party_successful_swap() {
AtomicSwap::claim_swap(
Origin::signed(A),
proof.to_vec(),
BalanceSwapAction::new(75),
).unwrap();

assert_eq!(Balances::free_balance(A), 100 + 75);
Expand All @@ -147,6 +148,7 @@ fn two_party_successful_swap() {
AtomicSwap::claim_swap(
Origin::signed(B),
proof.to_vec(),
BalanceSwapAction::new(50),
).unwrap();

assert_eq!(Balances::free_balance(A), 100 - 50);
Expand Down