-
Notifications
You must be signed in to change notification settings - Fork 569
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
6 changed files
with
437 additions
and
5 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
[package] | ||
name = "module-accounts" | ||
version = "0.0.1" | ||
authors = ["Acala Developers"] | ||
edition = "2018" | ||
|
||
[dependencies] | ||
serde = { version = "1.0", optional = true } | ||
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false } | ||
sp-runtime = { git = "https://github.com/paritytech/substrate.git", default-features = false } | ||
frame-support = { package = "frame-support", git = "https://github.com/paritytech/substrate.git", default-features = false } | ||
system = { package = "frame-system", git = "https://github.com/paritytech/substrate.git", default-features = false } | ||
rstd = { package = "sp-std", git = "https://github.com/paritytech/substrate.git", default-features = false } | ||
orml-traits = { package = "orml-traits", path = "../../orml/traits", default-features = false } | ||
orml-tokens = { package = "orml-tokens", path = "../../orml/tokens", default-features = false } | ||
orml-currencies = { package = "orml-currencies", path = "../../orml/currencies", default-features = false } | ||
support = { package = "module-support", path = "../support", default-features = false } | ||
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate.git", default-features = false } | ||
|
||
[dev-dependencies] | ||
primitives = { package = "sp-core", git = "https://github.com/paritytech/substrate.git", default-features = false } | ||
runtime-io = { package = "sp-io", git = "https://github.com/paritytech/substrate.git", default-features = false } | ||
pallet-balances= { package = "pallet-balances", git = "https://github.com/paritytech/substrate.git", default-features = false } | ||
pallet-timestamp= { package = "pallet-timestamp", git = "https://github.com/paritytech/substrate.git", default-features = false } | ||
|
||
|
||
[features] | ||
default = ["std"] | ||
std = [ | ||
"serde", | ||
"codec/std", | ||
"sp-runtime/std", | ||
"frame-support/std", | ||
"system/std", | ||
"rstd/std", | ||
"orml-traits/std", | ||
"orml-tokens/std", | ||
"orml-currencies/std", | ||
"support/std", | ||
"pallet-transaction-payment/std", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
#![cfg_attr(not(feature = "std"), no_std)] | ||
|
||
use codec::{Decode, Encode}; | ||
use frame_support::{ | ||
decl_module, decl_storage, | ||
dispatch::Dispatchable, | ||
traits::{Currency, ExistenceRequirement, Get, OnReapAccount, OnUnbalanced, Time, WithdrawReason}, | ||
weights::DispatchInfo, | ||
IsSubType, Parameter, | ||
}; | ||
use orml_traits::MultiCurrency; | ||
use rstd::prelude::*; | ||
use sp_runtime::{ | ||
traits::{SaturatedConversion, Saturating, SignedExtension, Zero}, | ||
transaction_validity::{ | ||
InvalidTransaction, TransactionPriority, TransactionValidity, TransactionValidityError, ValidTransaction, | ||
}, | ||
}; | ||
|
||
mod mock; | ||
mod tests; | ||
|
||
//type BalanceOf<T> = <<T as Trait>::Currency as MultiCurrency<<T as system::Trait>::AccountId>>::Balance; | ||
type MomentOf<T> = <<T as Trait>::Time as Time>::Moment; | ||
type PalletBalanceOf<T> = | ||
<<T as pallet_transaction_payment::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance; | ||
|
||
pub trait Trait: system::Trait + pallet_transaction_payment::Trait + orml_currencies::Trait { | ||
type FreeTransferCount: Get<u8>; | ||
type FreeTransferPeriod: Get<MomentOf<Self>>; | ||
type Time: Time; | ||
type Currency: MultiCurrency<Self::AccountId> + Send + Sync; | ||
type Call: Parameter | ||
+ Dispatchable<Origin = <Self as system::Trait>::Origin> | ||
+ IsSubType<orml_currencies::Module<Self>, Self>; | ||
} | ||
|
||
decl_storage! { | ||
trait Store for Module<T: Trait> as Accounts { | ||
LastFreeTransfers get(fn last_free_transfers): map T::AccountId => Vec<MomentOf<T>>; | ||
} | ||
} | ||
|
||
decl_module! { | ||
pub struct Module<T: Trait> for enum Call where origin: T::Origin { | ||
const FreeTransferCount: u8 = T::FreeTransferCount::get(); | ||
const FreeTransferPeriod: MomentOf<T> = T::FreeTransferPeriod::get(); | ||
} | ||
} | ||
|
||
impl<T: Trait> Module<T> { | ||
pub fn try_free_transfer(who: &T::AccountId) -> bool { | ||
let mut last_free_transfer = Self::last_free_transfers(who); | ||
let now = T::Time::now(); | ||
let free_transfer_period = T::FreeTransferPeriod::get(); | ||
|
||
// remove all the expired entries | ||
last_free_transfer.retain(|&x| x.saturating_add(free_transfer_period) > now); | ||
|
||
// check if can transfer for free | ||
if last_free_transfer.len() < T::FreeTransferCount::get() as usize { | ||
// add entry to last_free_transfer | ||
last_free_transfer.push(now); | ||
<LastFreeTransfers<T>>::insert(who, last_free_transfer); | ||
true | ||
} else { | ||
false | ||
} | ||
} | ||
} | ||
|
||
impl<T: Trait> OnReapAccount<T::AccountId> for Module<T> { | ||
fn on_reap_account(who: &T::AccountId) { | ||
<LastFreeTransfers<T>>::remove(who); | ||
} | ||
} | ||
|
||
/// Require the transactor pay for themselves and maybe include a tip to gain additional priority | ||
/// in the queue. | ||
#[derive(Encode, Decode, Clone, Eq, PartialEq)] | ||
pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(#[codec(compact)] PalletBalanceOf<T>); | ||
|
||
impl<T: Trait + Send + Sync> ChargeTransactionPayment<T> { | ||
/// utility constructor. Used only in client/factory code. | ||
pub fn from(fee: PalletBalanceOf<T>) -> Self { | ||
Self(fee) | ||
} | ||
} | ||
|
||
impl<T: Trait + Send + Sync> rstd::fmt::Debug for ChargeTransactionPayment<T> { | ||
#[cfg(feature = "std")] | ||
fn fmt(&self, f: &mut rstd::fmt::Formatter) -> rstd::fmt::Result { | ||
write!(f, "ChargeTransactionPayment<{:?}>", self.0) | ||
} | ||
#[cfg(not(feature = "std"))] | ||
fn fmt(&self, _: &mut rstd::fmt::Formatter) -> rstd::fmt::Result { | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl<T: Trait + Send + Sync> SignedExtension for ChargeTransactionPayment<T> | ||
where | ||
PalletBalanceOf<T>: Send + Sync, | ||
{ | ||
type AccountId = T::AccountId; | ||
type Call = <T as Trait>::Call; | ||
type AdditionalSigned = (); | ||
type DispatchInfo = DispatchInfo; | ||
type Pre = (); | ||
fn additional_signed(&self) -> rstd::result::Result<(), TransactionValidityError> { | ||
Ok(()) | ||
} | ||
|
||
fn validate( | ||
&self, | ||
who: &Self::AccountId, | ||
call: &Self::Call, | ||
info: Self::DispatchInfo, | ||
len: usize, | ||
) -> TransactionValidity { | ||
// pay any fees. | ||
let tip = self.0; | ||
let fee: PalletBalanceOf<T> = | ||
pallet_transaction_payment::ChargeTransactionPayment::<T>::compute_fee(len as u32, info, tip); | ||
|
||
// check call type | ||
let call = match call.is_sub_type() { | ||
Some(call) => call, | ||
None => return Ok(ValidTransaction::default()), | ||
}; | ||
let skip_pay_fee = match call { | ||
orml_currencies::Call::transfer(..) => { | ||
// call try_free_transfer | ||
if <Module<T>>::try_free_transfer(who) { | ||
true | ||
} else { | ||
false | ||
} | ||
} | ||
_ => false, | ||
}; | ||
|
||
// skip payment withdraw if match conditions | ||
if !skip_pay_fee { | ||
let imbalance = match <T as pallet_transaction_payment::Trait>::Currency::withdraw( | ||
who, | ||
fee, | ||
if tip.is_zero() { | ||
WithdrawReason::TransactionPayment.into() | ||
} else { | ||
WithdrawReason::TransactionPayment | WithdrawReason::Tip | ||
}, | ||
ExistenceRequirement::KeepAlive, | ||
) { | ||
Ok(imbalance) => imbalance, | ||
Err(_) => return InvalidTransaction::Payment.into(), | ||
}; | ||
<T as pallet_transaction_payment::Trait>::OnTransactionPayment::on_unbalanced(imbalance); | ||
} | ||
|
||
let mut r = ValidTransaction::default(); | ||
// NOTE: we probably want to maximize the _fee (of any type) per weight unit_ here, which | ||
// will be a bit more than setting the priority to tip. For now, this is enough. | ||
r.priority = fee.saturated_into::<TransactionPriority>(); | ||
Ok(r) | ||
} | ||
} |
Oops, something went wrong.