-
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.
- Loading branch information
1 parent
95b9470
commit ac3e5a4
Showing
3 changed files
with
274 additions
and
0 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,40 @@ | ||
[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 } | ||
|
||
|
||
[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,214 @@ | ||
#![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 sp_runtime::{ | ||
traits::{Convert, SaturatedConversion, Saturating, SignedExtension, Zero}, | ||
transaction_validity::{ | ||
InvalidTransaction, TransactionPriority, TransactionValidity, TransactionValidityError, ValidTransaction, | ||
}, | ||
}; | ||
//use support::{Ratio}; | ||
use rstd::prelude::*; | ||
|
||
//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) | ||
} | ||
|
||
/// Compute the final fee value for a particular transaction. | ||
/// | ||
/// The final fee is composed of: | ||
/// - _base_fee_: This is the minimum amount a user pays for a transaction. | ||
/// - _len_fee_: This is the amount paid merely to pay for size of the transaction. | ||
/// - _weight_fee_: This amount is computed based on the weight of the transaction. Unlike | ||
/// size-fee, this is not input dependent and reflects the _complexity_ of the execution | ||
/// and the time it consumes. | ||
/// - _targeted_fee_adjustment_: This is a multiplier that can tune the final fee based on | ||
/// the congestion of the network. | ||
/// - (optional) _tip_: if included in the transaction, it will be added on top. Only signed | ||
/// transactions can have a tip. | ||
/// | ||
/// final_fee = base_fee + targeted_fee_adjustment(len_fee + weight_fee) + tip; | ||
fn compute_fee( | ||
len: u32, | ||
info: <Self as SignedExtension>::DispatchInfo, | ||
tip: PalletBalanceOf<T>, | ||
) -> PalletBalanceOf<T> | ||
where | ||
PalletBalanceOf<T>: Sync + Send, | ||
{ | ||
if info.pays_fee { | ||
let len = <PalletBalanceOf<T>>::from(len); | ||
let per_byte = <T as pallet_transaction_payment::Trait>::TransactionByteFee::get(); | ||
let len_fee = per_byte.saturating_mul(len); | ||
|
||
let weight_fee = { | ||
// cap the weight to the maximum defined in runtime, otherwise it will be the `Bounded` | ||
// maximum of its data type, which is not desired. | ||
let capped_weight = info.weight.min(<T as system::Trait>::MaximumBlockWeight::get()); | ||
<T as pallet_transaction_payment::Trait>::WeightToFee::convert(capped_weight) | ||
}; | ||
|
||
// the adjustable part of the fee | ||
let adjustable_fee = len_fee.saturating_add(weight_fee); | ||
let targeted_fee_adjustment = <pallet_transaction_payment::Module<T>>::next_fee_multiplier(); | ||
// adjusted_fee = adjustable_fee + (adjustable_fee * targeted_fee_adjustment) | ||
let adjusted_fee = targeted_fee_adjustment.saturated_multiply_accumulate(adjustable_fee); | ||
|
||
let base_fee = <T as pallet_transaction_payment::Trait>::TransactionBaseFee::get(); | ||
let final_fee = base_fee.saturating_add(adjusted_fee).saturating_add(tip); | ||
|
||
final_fee | ||
} else { | ||
tip | ||
} | ||
} | ||
} | ||
|
||
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 { | ||
let call = match call.is_sub_type() { | ||
Some(call) => call, | ||
None => return Ok(ValidTransaction::default()), | ||
}; | ||
|
||
// check call type | ||
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, | ||
}; | ||
|
||
// pay any fees. | ||
let tip = self.0; | ||
let fee: PalletBalanceOf<T> = Self::compute_fee(len as u32, info, tip); | ||
|
||
// 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) | ||
} | ||
} |