Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Fee Handler #161

Merged
merged 11 commits into from
Dec 22, 2021
Merged
Show file tree
Hide file tree
Changes from 8 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
23 changes: 18 additions & 5 deletions pallets/payment/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ mod types;

#[frame_support::pallet]
pub mod pallet {
pub use crate::types::{DisputeResolver, PaymentDetail, PaymentHandler, PaymentState};
pub use crate::types::{
DisputeResolver, FeeHandler, PaymentDetail, PaymentHandler, PaymentState,
};
use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*};
use frame_system::pallet_prelude::*;
use orml_traits::{MultiCurrency, MultiReservableCurrency};
Expand All @@ -34,7 +36,9 @@ pub mod pallet {
type Asset: MultiReservableCurrency<Self::AccountId>;
/// Dispute resolution account
type DisputeResolver: DisputeResolver<Self::AccountId>;

/// Fee handler trait
type FeeHandler: FeeHandler<Self::AccountId>;
/// Incentive percentage - amount witheld from sender
#[pallet::constant]
type IncentivePercentage: Get<Percent>;
}
Expand Down Expand Up @@ -177,12 +181,15 @@ pub mod pallet {
recipient.clone(),
|maybe_payment| -> DispatchResult {
let incentive_amount = T::IncentivePercentage::get() * amount;
let (fee_recipient, fee_percent) = T::FeeHandler::apply_fees(&from, &recipient);
let fee_amount = fee_percent * amount;
let new_payment = Some(PaymentDetail {
asset,
amount,
incentive_amount,
state: PaymentState::Created,
resolver_account: T::DisputeResolver::get_origin(),
fee_detail: (fee_recipient, fee_amount),
});
match maybe_payment {
Some(x) => {
Expand All @@ -193,8 +200,8 @@ pub mod pallet {
x.state != PaymentState::Created,
Error::<T>::PaymentAlreadyInProcess
);
// reserve the incentive amount from the payment creator
T::Asset::reserve(asset, &from, incentive_amount)?;
// reserve the incentive + fees amount from the payment creator
T::Asset::reserve(asset, &from, incentive_amount + fee_amount)?;
// transfer amount to recipient
T::Asset::transfer(asset, &from, &recipient, amount)?;
// reserved the amount in the recipient account
Expand Down Expand Up @@ -233,7 +240,13 @@ pub mod pallet {
T::Asset::unreserve(payment.asset, &from, payment.incentive_amount);
// unreserve the amount to the recipent
T::Asset::unreserve(payment.asset, &to, payment.amount);

// transfer fee amount to marketplace
T::Asset::transfer(
payment.asset,
&from, // fee is paid by payment creator
stanly-johnson marked this conversation as resolved.
Show resolved Hide resolved
&payment.fee_detail.0, // account of fee recipient
payment.fee_detail.1, // amount of fee
)?;
payment.state = PaymentState::Released;

Ok(())
Expand Down
13 changes: 13 additions & 0 deletions pallets/payment/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub const PAYMENT_CREATOR: AccountId = 10;
pub const PAYMENT_RECIPENT: AccountId = 11;
pub const CURRENCY_ID: u32 = 1u32;
pub const RESOLVER_ACCOUNT: AccountId = 12;
pub const FEE_RECIPIENT_ACCOUNT: AccountId = 20;
pub const PAYMENT_RECIPENT_FEE_CHARGED: AccountId = 21;

frame_support::construct_runtime!(
pub enum Test where
Expand Down Expand Up @@ -98,6 +100,16 @@ impl crate::types::DisputeResolver<AccountId> for MockDisputeResolver {
}
}

pub struct MockFeeHandler;
impl crate::types::FeeHandler<AccountId> for MockFeeHandler {
fn apply_fees(_from: &AccountId, to: &AccountId) -> (AccountId, Percent) {
match to {
&PAYMENT_RECIPENT_FEE_CHARGED => (FEE_RECIPIENT_ACCOUNT, Percent::from_percent(10)),
_ => (FEE_RECIPIENT_ACCOUNT, Percent::from_percent(0)),
}
}
}

parameter_types! {
pub const IncentivePercentage: Percent = Percent::from_percent(10);
}
Expand All @@ -107,6 +119,7 @@ impl payment::Config for Test {
type Asset = Tokens;
type DisputeResolver = MockDisputeResolver;
type IncentivePercentage = IncentivePercentage;
type FeeHandler = MockFeeHandler;
}

// Build genesis storage according to the mock runtime.
Expand Down
63 changes: 53 additions & 10 deletions pallets/payment/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ fn test_create_payment_works() {
amount: 20,
incentive_amount: 2,
state: PaymentState::Created,
resolver_account: RESOLVER_ACCOUNT
resolver_account: RESOLVER_ACCOUNT,
fee_detail: (FEE_RECIPIENT_ACCOUNT, 0)
stanly-johnson marked this conversation as resolved.
Show resolved Hide resolved
})
);
// the payment amount should be reserved correctly
Expand All @@ -52,14 +53,15 @@ fn test_create_payment_works() {
amount: 20,
incentive_amount: 2,
state: PaymentState::Created,
resolver_account: RESOLVER_ACCOUNT
resolver_account: RESOLVER_ACCOUNT,
fee_detail: (FEE_RECIPIENT_ACCOUNT, 0)
})
);
});
}

#[test]
fn test_release_payment_works() {
fn test_cancel_payment_works() {
new_test_ext().execute_with(|| {
// should be able to create a payment with available balance
assert_ok!(Payment::create(
Expand All @@ -75,7 +77,8 @@ fn test_release_payment_works() {
amount: 40,
incentive_amount: 4,
state: PaymentState::Created,
resolver_account: RESOLVER_ACCOUNT
resolver_account: RESOLVER_ACCOUNT,
fee_detail: (FEE_RECIPIENT_ACCOUNT, 0)
})
);
// the payment amount should be reserved
Expand Down Expand Up @@ -103,7 +106,8 @@ fn test_release_payment_works() {
amount: 40,
incentive_amount: 4,
state: PaymentState::Cancelled,
resolver_account: RESOLVER_ACCOUNT
resolver_account: RESOLVER_ACCOUNT,
fee_detail: (FEE_RECIPIENT_ACCOUNT, 0)
})
);
// cannot call cancel again
Expand All @@ -115,7 +119,7 @@ fn test_release_payment_works() {
}

#[test]
fn test_cancel_payment_works() {
fn test_release_payment_works() {
new_test_ext().execute_with(|| {
// should be able to create a payment with available balance
assert_ok!(Payment::create(
Expand All @@ -131,7 +135,8 @@ fn test_cancel_payment_works() {
amount: 40,
incentive_amount: 4,
state: PaymentState::Created,
resolver_account: RESOLVER_ACCOUNT
resolver_account: RESOLVER_ACCOUNT,
fee_detail: (FEE_RECIPIENT_ACCOUNT, 0)
})
);
// the payment amount should be reserved
Expand All @@ -153,7 +158,8 @@ fn test_cancel_payment_works() {
amount: 40,
incentive_amount: 4,
state: PaymentState::Released,
resolver_account: RESOLVER_ACCOUNT
resolver_account: RESOLVER_ACCOUNT,
fee_detail: (FEE_RECIPIENT_ACCOUNT, 0)
})
);
// cannot call release again
Expand Down Expand Up @@ -218,7 +224,8 @@ fn test_set_state_payment_works() {
amount: 40,
incentive_amount: 4,
state: PaymentState::Released,
resolver_account: RESOLVER_ACCOUNT
resolver_account: RESOLVER_ACCOUNT,
fee_detail: (FEE_RECIPIENT_ACCOUNT, 0)
})
);

Expand Down Expand Up @@ -250,8 +257,44 @@ fn test_set_state_payment_works() {
amount: 40,
incentive_amount: 4,
state: PaymentState::Cancelled,
resolver_account: RESOLVER_ACCOUNT
resolver_account: RESOLVER_ACCOUNT,
fee_detail: (FEE_RECIPIENT_ACCOUNT, 0)
})
);
});
}

#[test]
fn test_charging_fee_payment_works() {
new_test_ext().execute_with(|| {
// should be able to create a payment with available balance
assert_ok!(Payment::create(
Origin::signed(PAYMENT_CREATOR),
PAYMENT_RECIPENT_FEE_CHARGED,
CURRENCY_ID,
40,
));
assert_eq!(
PaymentStore::<Test>::get(PAYMENT_CREATOR, PAYMENT_RECIPENT_FEE_CHARGED),
Some(PaymentDetail {
asset: CURRENCY_ID,
amount: 40,
incentive_amount: 4,
state: PaymentState::Created,
resolver_account: RESOLVER_ACCOUNT,
fee_detail: (FEE_RECIPIENT_ACCOUNT, 4)
})
);
// the payment amount should be reserved
assert_eq!(Tokens::free_balance(CURRENCY_ID, &PAYMENT_CREATOR), 56);
assert_eq!(Tokens::free_balance(CURRENCY_ID, &PAYMENT_RECIPENT_FEE_CHARGED), 0);

// should succeed for valid payment
assert_ok!(Payment::release(Origin::signed(PAYMENT_CREATOR), PAYMENT_RECIPENT_FEE_CHARGED));
// the payment amount should be transferred
assert_eq!(Tokens::free_balance(CURRENCY_ID, &PAYMENT_CREATOR), 56);
assert_eq!(Tokens::free_balance(CURRENCY_ID, &PAYMENT_RECIPENT_FEE_CHARGED), 40);
assert_eq!(Tokens::free_balance(CURRENCY_ID, &FEE_RECIPIENT_ACCOUNT), 4);
assert_eq!(Tokens::total_issuance(CURRENCY_ID), 100);
});
}
9 changes: 8 additions & 1 deletion pallets/payment/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![allow(unused_qualifications)]
use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;
use sp_runtime::DispatchResult;
use sp_runtime::{DispatchResult, Percent};

#[derive(Encode, Decode, Debug, Clone, PartialEq, Eq, TypeInfo)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
Expand All @@ -11,6 +11,7 @@ pub struct PaymentDetail<Asset, Amount, Account> {
pub incentive_amount: Amount,
pub state: PaymentState,
pub resolver_account: Account,
pub fee_detail: (Account, Amount),
}

#[derive(Encode, Decode, Debug, Clone, PartialEq, Eq, TypeInfo)]
Expand Down Expand Up @@ -59,3 +60,9 @@ pub trait DisputeResolver<Account> {
/// Get a DisputeResolver (Judge) account
fn get_origin() -> Account;
}

/// Fee Handler trait that defines how to handle marketplace fees to every payment/swap
pub trait FeeHandler<Account> {
/// Get the distribution of fees to marketplace participants
fn apply_fees(from: &Account, to: &Account) -> (Account, Percent);
}
9 changes: 9 additions & 0 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,14 @@ impl virto_payment::DisputeResolver<AccountId> for VirtoDisputeResolver {
}
}

pub struct VirtoFeeHandler;
impl virto_payment::FeeHandler<AccountId> for VirtoFeeHandler {
fn apply_fees(_from: &AccountId, _to: &AccountId) -> (AccountId, Percent) {
const VIRTO_MARKETPLACE_FEE_PERCENT: Percent = Percent::from_percent(0);
(Sudo::key(), VIRTO_MARKETPLACE_FEE_PERCENT)
}
}

parameter_types! {
pub const IncentivePercentage: Percent = Percent::from_percent(10);
}
Expand All @@ -611,6 +619,7 @@ impl virto_payment::Config for Runtime {
type Asset = Assets;
type DisputeResolver = VirtoDisputeResolver;
type IncentivePercentage = IncentivePercentage;
type FeeHandler = VirtoFeeHandler;
}

parameter_types! {
Expand Down