mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 21:21:11 +00:00
Transaction Fee Module (#1648)
* wip * Split bytes fee charging and charging by amount into different traits. * Move to edition 2018. * Implemented charge fee traits for fees module. * Implemented 'on_finalise' for fee module. * Updated fees finalize impl. * Renaming and documentation update. * Added overflow & underflow check for fee calculation. * Added mock and unit tests for fee module. * More unit tests for fees module. * Fixed srml-executive unit tests. * Remove transaction base/bytes fee from balances module, fix unit tests. * fix compile error * Fixed unit test. * Minor fixes. * Bump spec version. * Bump spec version. * Updated fees module and runtime wasm. * Fees module code style improvement; updated runtime wasm. * Bump spec and impl version.
This commit is contained in:
committed by
Bastian Köcher
parent
6a6c3155a6
commit
fafffdb771
Generated
+21
@@ -1927,6 +1927,7 @@ dependencies = [
|
||||
"srml-balances 0.1.0",
|
||||
"srml-consensus 0.1.0",
|
||||
"srml-contract 0.1.0",
|
||||
"srml-fees 0.1.0",
|
||||
"srml-grandpa 0.1.0",
|
||||
"srml-indices 0.1.0",
|
||||
"srml-session 0.1.0",
|
||||
@@ -1981,6 +1982,7 @@ dependencies = [
|
||||
"srml-council 0.1.0",
|
||||
"srml-democracy 0.1.0",
|
||||
"srml-executive 0.1.0",
|
||||
"srml-fees 0.1.0",
|
||||
"srml-grandpa 0.1.0",
|
||||
"srml-indices 0.1.0",
|
||||
"srml-session 0.1.0",
|
||||
@@ -2045,6 +2047,7 @@ dependencies = [
|
||||
"srml-balances 0.1.0",
|
||||
"srml-consensus 0.1.0",
|
||||
"srml-executive 0.1.0",
|
||||
"srml-fees 0.1.0",
|
||||
"srml-indices 0.1.0",
|
||||
"srml-sudo 0.1.0",
|
||||
"srml-support 0.1.0",
|
||||
@@ -3177,6 +3180,7 @@ dependencies = [
|
||||
"sr-std 0.1.0",
|
||||
"srml-balances 0.1.0",
|
||||
"srml-consensus 0.1.0",
|
||||
"srml-fees 0.1.0",
|
||||
"srml-support 0.1.0",
|
||||
"srml-system 0.1.0",
|
||||
"srml-timestamp 0.1.0",
|
||||
@@ -3249,12 +3253,29 @@ dependencies = [
|
||||
"sr-primitives 0.1.0",
|
||||
"sr-std 0.1.0",
|
||||
"srml-balances 0.1.0",
|
||||
"srml-fees 0.1.0",
|
||||
"srml-indices 0.1.0",
|
||||
"srml-support 0.1.0",
|
||||
"srml-system 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "srml-fees"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-literal 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-codec 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-codec-derive 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sr-io 0.1.0",
|
||||
"sr-primitives 0.1.0",
|
||||
"sr-std 0.1.0",
|
||||
"srml-support 0.1.0",
|
||||
"srml-system 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "srml-grandpa"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -65,6 +65,7 @@ members = [
|
||||
"srml/democracy",
|
||||
"srml/example",
|
||||
"srml/executive",
|
||||
"srml/fees",
|
||||
"srml/grandpa",
|
||||
"srml/indices",
|
||||
"srml/metadata",
|
||||
|
||||
@@ -126,15 +126,57 @@ pub trait BlockNumberToHash {
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple payment making trait, operating on a single generic `AccountId` type.
|
||||
pub trait MakePayment<AccountId> {
|
||||
/// Make some sort of payment concerning `who` for an extrinsic (transaction) of encoded length
|
||||
/// `encoded_len` bytes. Return true iff the payment was successful.
|
||||
fn make_payment(who: &AccountId, encoded_len: usize) -> Result<(), &'static str>;
|
||||
/// Charge bytes fee trait
|
||||
pub trait ChargeBytesFee<AccountId> {
|
||||
/// Charge fees from `transactor` for an extrinsic (transaction) of encoded length
|
||||
/// `encoded_len` bytes. Return Ok iff the payment was successful.
|
||||
fn charge_base_bytes_fee(transactor: &AccountId, encoded_len: usize) -> Result<(), &'static str>;
|
||||
}
|
||||
|
||||
impl<T> MakePayment<T> for () {
|
||||
fn make_payment(_: &T, _: usize) -> Result<(), &'static str> { Ok(()) }
|
||||
/// Charge fee trait
|
||||
pub trait ChargeFee<AccountId>: ChargeBytesFee<AccountId> {
|
||||
/// The type of fee amount.
|
||||
type Amount;
|
||||
|
||||
/// Charge `amount` of fees from `transactor`. Return Ok iff the payment was successful.
|
||||
fn charge_fee(transactor: &AccountId, amount: Self::Amount) -> Result<(), &'static str>;
|
||||
|
||||
/// Refund `amount` of previous charged fees from `transactor`. Return Ok iff the refund was successful.
|
||||
fn refund_fee(transactor: &AccountId, amount: Self::Amount) -> Result<(), &'static str>;
|
||||
}
|
||||
|
||||
/// Transfer fungible asset trait
|
||||
pub trait TransferAsset<AccountId> {
|
||||
/// The type of asset amount.
|
||||
type Amount;
|
||||
|
||||
/// Transfer asset from `from` account to `to` account with `amount` of asset.
|
||||
fn transfer(from: &AccountId, to: &AccountId, amount: Self::Amount) -> Result<(), &'static str>;
|
||||
|
||||
/// Remove asset from `who` account by deducing `amount` in the account balances.
|
||||
fn remove_from(who: &AccountId, amount: Self::Amount) -> Result<(), &'static str>;
|
||||
|
||||
/// Add asset to `who` account by increasing `amount` in the account balances.
|
||||
fn add_to(who: &AccountId, amount: Self::Amount) -> Result<(), &'static str>;
|
||||
}
|
||||
|
||||
impl<T> ChargeBytesFee<T> for () {
|
||||
fn charge_base_bytes_fee(_: &T, _: usize) -> Result<(), &'static str> { Ok(()) }
|
||||
}
|
||||
|
||||
impl<T> ChargeFee<T> for () {
|
||||
type Amount = ();
|
||||
|
||||
fn charge_fee(_: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
|
||||
fn refund_fee(_: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
|
||||
}
|
||||
|
||||
impl<T> TransferAsset<T> for () {
|
||||
type Amount = ();
|
||||
|
||||
fn transfer(_: &T, _: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
|
||||
fn remove_from(_: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
|
||||
fn add_to(_: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
|
||||
}
|
||||
|
||||
/// Extensible conversion trait. Generic over both source and destination types.
|
||||
|
||||
BIN
Binary file not shown.
@@ -16,6 +16,7 @@ version = { package = "sr-version", path = "../../core/sr-version", default_feat
|
||||
support = { package = "srml-support", path = "../../srml/support", default_features = false }
|
||||
primitives = { package = "substrate-primitives", path = "../../core/primitives", default_features = false }
|
||||
balances = { package = "srml-balances", path = "../../srml/balances", default_features = false }
|
||||
fees = { package = "srml-fees", path = "../../srml/fees", default_features = false }
|
||||
consensus = { package = "srml-consensus", path = "../../srml/consensus", default_features = false }
|
||||
aura = { package = "srml-aura", path = "../../srml/aura", default_features = false }
|
||||
executive = { package = "srml-executive", path = "../../srml/executive", default_features = false }
|
||||
@@ -38,6 +39,7 @@ std = [
|
||||
"runtime-io/std",
|
||||
"support/std",
|
||||
"balances/std",
|
||||
"fees/std",
|
||||
"executive/std",
|
||||
"aura/std",
|
||||
"indices/std",
|
||||
|
||||
@@ -164,6 +164,12 @@ impl balances::Trait for Runtime {
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
impl fees::Trait for Runtime {
|
||||
type Amount = u128;
|
||||
type TransferAsset = Balances;
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
impl sudo::Trait for Runtime {
|
||||
/// The uniquitous event type.
|
||||
type Event = Event;
|
||||
@@ -188,6 +194,7 @@ construct_runtime!(
|
||||
Indices: indices,
|
||||
Balances: balances,
|
||||
Sudo: sudo,
|
||||
Fees: fees::{Module, Storage, Config<T>, Event<T>},
|
||||
// Used for the module template in `./template.rs`
|
||||
TemplateModule: template::{Module, Call, Storage, Event<T>},
|
||||
}
|
||||
@@ -208,7 +215,7 @@ pub type UncheckedExtrinsic = generic::UncheckedMortalCompactExtrinsic<Address,
|
||||
/// Extrinsic type that has already been checked.
|
||||
pub type CheckedExtrinsic = generic::CheckedExtrinsic<AccountId, Nonce, Call>;
|
||||
/// Executive: handles dispatch to the various modules.
|
||||
pub type Executive = executive::Executive<Runtime, Block, Context, Balances, AllModules>;
|
||||
pub type Executive = executive::Executive<Runtime, Block, Context, Fees, AllModules>;
|
||||
|
||||
// Implement our runtime API endpoints. This is just a bunch of proxying.
|
||||
impl_runtime_apis! {
|
||||
|
||||
+17
@@ -655,6 +655,7 @@ dependencies = [
|
||||
"srml-balances 0.1.0",
|
||||
"srml-consensus 0.1.0",
|
||||
"srml-executive 0.1.0",
|
||||
"srml-fees 0.1.0",
|
||||
"srml-indices 0.1.0",
|
||||
"srml-sudo 0.1.0",
|
||||
"srml-support 0.1.0",
|
||||
@@ -1333,6 +1334,22 @@ dependencies = [
|
||||
"srml-system 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "srml-fees"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-codec 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-codec-derive 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sr-io 0.1.0",
|
||||
"sr-primitives 0.1.0",
|
||||
"sr-std 0.1.0",
|
||||
"srml-support 0.1.0",
|
||||
"srml-system 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "srml-indices"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use primitives::{Ed25519AuthorityId, ed25519};
|
||||
use node_template_runtime::{
|
||||
AccountId, GenesisConfig, ConsensusConfig, TimestampConfig, BalancesConfig,
|
||||
SudoConfig, IndicesConfig
|
||||
SudoConfig, IndicesConfig, FeesConfig,
|
||||
};
|
||||
use substrate_service;
|
||||
|
||||
@@ -90,8 +90,6 @@ fn testnet_genesis(initial_authorities: Vec<Ed25519AuthorityId>, endowed_account
|
||||
ids: endowed_accounts.clone(),
|
||||
}),
|
||||
balances: Some(BalancesConfig {
|
||||
transaction_base_fee: 1,
|
||||
transaction_byte_fee: 0,
|
||||
existential_deposit: 500,
|
||||
transfer_fee: 0,
|
||||
creation_fee: 0,
|
||||
@@ -101,5 +99,9 @@ fn testnet_genesis(initial_authorities: Vec<Ed25519AuthorityId>, endowed_account
|
||||
sudo: Some(SudoConfig {
|
||||
key: root_key,
|
||||
}),
|
||||
fees: Some(FeesConfig {
|
||||
transaction_base_fee: 1,
|
||||
transaction_byte_fee: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ use primitives::{Ed25519AuthorityId, ed25519};
|
||||
use node_primitives::AccountId;
|
||||
use node_runtime::{ConsensusConfig, CouncilSeatsConfig, CouncilVotingConfig, DemocracyConfig,
|
||||
SessionConfig, StakingConfig, TimestampConfig, BalancesConfig, TreasuryConfig,
|
||||
SudoConfig, ContractConfig, GrandpaConfig, IndicesConfig, Permill, Perbill};
|
||||
SudoConfig, ContractConfig, GrandpaConfig, IndicesConfig, FeesConfig, Permill, Perbill};
|
||||
pub use node_runtime::GenesisConfig;
|
||||
use substrate_service;
|
||||
use hex_literal::{hex, hex_impl};
|
||||
@@ -64,8 +64,6 @@ fn staging_testnet_config_genesis() -> GenesisConfig {
|
||||
system: None,
|
||||
balances: Some(BalancesConfig {
|
||||
balances: endowed_accounts.iter().map(|&k| (k, 10_000_000 * DOLLARS)).collect(),
|
||||
transaction_base_fee: 1 * CENTS,
|
||||
transaction_byte_fee: 10 * MILLICENTS,
|
||||
existential_deposit: 1 * DOLLARS,
|
||||
transfer_fee: 1 * CENTS,
|
||||
creation_fee: 1 * CENTS,
|
||||
@@ -139,7 +137,11 @@ fn staging_testnet_config_genesis() -> GenesisConfig {
|
||||
}),
|
||||
grandpa: Some(GrandpaConfig {
|
||||
authorities: initial_authorities.clone().into_iter().map(|k| (k, 1)).collect(),
|
||||
})
|
||||
}),
|
||||
fees: Some(FeesConfig {
|
||||
transaction_base_fee: 1 * CENTS,
|
||||
transaction_byte_fee: 10 * MILLICENTS,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,8 +194,6 @@ pub fn testnet_genesis(
|
||||
ids: endowed_accounts.iter().map(|x| x.0.into()).collect(),
|
||||
}),
|
||||
balances: Some(BalancesConfig {
|
||||
transaction_base_fee: 1,
|
||||
transaction_byte_fee: 0,
|
||||
existential_deposit: 500,
|
||||
transfer_fee: 0,
|
||||
creation_fee: 0,
|
||||
@@ -267,7 +267,11 @@ pub fn testnet_genesis(
|
||||
}),
|
||||
grandpa: Some(GrandpaConfig {
|
||||
authorities: initial_authorities.clone().into_iter().map(|k| (k, 1)).collect(),
|
||||
})
|
||||
}),
|
||||
fees: Some(FeesConfig {
|
||||
transaction_base_fee: 1,
|
||||
transaction_byte_fee: 0,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ treasury = { package = "srml-treasury", path = "../../srml/treasury" }
|
||||
contract = { package = "srml-contract", path = "../../srml/contract" }
|
||||
grandpa = { package = "srml-grandpa", path = "../../srml/grandpa" }
|
||||
indices = { package = "srml-indices", path = "../../srml/indices" }
|
||||
fees = { package = "srml-fees", path = "../../srml/fees" }
|
||||
wabt = "~0.7.4"
|
||||
|
||||
[features]
|
||||
|
||||
@@ -46,7 +46,7 @@ mod tests {
|
||||
use system::{EventRecord, Phase};
|
||||
use node_runtime::{Header, Block, UncheckedExtrinsic, CheckedExtrinsic, Call, Runtime, Balances,
|
||||
BuildStorage, GenesisConfig, BalancesConfig, SessionConfig, StakingConfig, System,
|
||||
SystemConfig, GrandpaConfig, IndicesConfig, Event, Log};
|
||||
SystemConfig, GrandpaConfig, IndicesConfig, FeesConfig, Event, Log};
|
||||
use wabt;
|
||||
use hex_literal::{hex, hex_impl};
|
||||
use primitives::map;
|
||||
@@ -112,13 +112,13 @@ mod tests {
|
||||
let mut t = TestExternalities::<Blake2Hasher>::new_with_code(BLOATY_CODE, map![
|
||||
twox_128(&<balances::FreeBalance<Runtime>>::key_for(alice())).to_vec() => vec![69u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
twox_128(<balances::TotalIssuance<Runtime>>::key()).to_vec() => vec![69u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
twox_128(<balances::TransactionBaseFee<Runtime>>::key()).to_vec() => vec![70u8; 16],
|
||||
twox_128(<balances::TransactionByteFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::ExistentialDeposit<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::CreationFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::TransferFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<indices::NextEnumSet<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(&<system::BlockHash<Runtime>>::key_for(0)).to_vec() => vec![0u8; 32]
|
||||
twox_128(&<system::BlockHash<Runtime>>::key_for(0)).to_vec() => vec![0u8; 32],
|
||||
twox_128(<fees::TransactionBaseFee<Runtime>>::key()).to_vec() => vec![70u8; 16],
|
||||
twox_128(<fees::TransactionByteFee<Runtime>>::key()).to_vec() => vec![0u8; 16]
|
||||
]);
|
||||
|
||||
let r = executor().call::<_, NeverNativeValue, fn() -> _>(
|
||||
@@ -145,13 +145,13 @@ mod tests {
|
||||
let mut t = TestExternalities::<Blake2Hasher>::new_with_code(COMPACT_CODE, map![
|
||||
twox_128(&<balances::FreeBalance<Runtime>>::key_for(alice())).to_vec() => vec![69u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
twox_128(<balances::TotalIssuance<Runtime>>::key()).to_vec() => vec![69u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
twox_128(<balances::TransactionBaseFee<Runtime>>::key()).to_vec() => vec![70u8; 16],
|
||||
twox_128(<balances::TransactionByteFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::ExistentialDeposit<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::CreationFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::TransferFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<indices::NextEnumSet<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(&<system::BlockHash<Runtime>>::key_for(0)).to_vec() => vec![0u8; 32]
|
||||
twox_128(&<system::BlockHash<Runtime>>::key_for(0)).to_vec() => vec![0u8; 32],
|
||||
twox_128(<fees::TransactionBaseFee<Runtime>>::key()).to_vec() => vec![70u8; 16],
|
||||
twox_128(<fees::TransactionByteFee<Runtime>>::key()).to_vec() => vec![0u8; 16]
|
||||
]);
|
||||
|
||||
let r = executor().call::<_, NeverNativeValue, fn() -> _>(
|
||||
@@ -178,13 +178,13 @@ mod tests {
|
||||
let mut t = TestExternalities::<Blake2Hasher>::new_with_code(COMPACT_CODE, map![
|
||||
twox_128(&<balances::FreeBalance<Runtime>>::key_for(alice())).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
twox_128(<balances::TotalIssuance<Runtime>>::key()).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
twox_128(<balances::TransactionBaseFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::TransactionByteFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::ExistentialDeposit<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::CreationFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::TransferFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<indices::NextEnumSet<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(&<system::BlockHash<Runtime>>::key_for(0)).to_vec() => vec![0u8; 32]
|
||||
twox_128(&<system::BlockHash<Runtime>>::key_for(0)).to_vec() => vec![0u8; 32],
|
||||
twox_128(<fees::TransactionBaseFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<fees::TransactionByteFee<Runtime>>::key()).to_vec() => vec![0u8; 16]
|
||||
]);
|
||||
|
||||
let r = executor().call::<_, NeverNativeValue, fn() -> _>(
|
||||
@@ -215,13 +215,13 @@ mod tests {
|
||||
let mut t = TestExternalities::<Blake2Hasher>::new_with_code(BLOATY_CODE, map![
|
||||
twox_128(&<balances::FreeBalance<Runtime>>::key_for(alice())).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
twox_128(<balances::TotalIssuance<Runtime>>::key()).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
twox_128(<balances::TransactionBaseFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::TransactionByteFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::ExistentialDeposit<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::CreationFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::TransferFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<indices::NextEnumSet<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(&<system::BlockHash<Runtime>>::key_for(0)).to_vec() => vec![0u8; 32]
|
||||
twox_128(&<system::BlockHash<Runtime>>::key_for(0)).to_vec() => vec![0u8; 32],
|
||||
twox_128(<fees::TransactionBaseFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<fees::TransactionByteFee<Runtime>>::key()).to_vec() => vec![0u8; 16]
|
||||
]);
|
||||
|
||||
let r = executor().call::<_, NeverNativeValue, fn() -> _>(
|
||||
@@ -266,8 +266,6 @@ mod tests {
|
||||
(alice(), 111),
|
||||
(charlie(), 100_000_000),
|
||||
],
|
||||
transaction_base_fee: 1,
|
||||
transaction_byte_fee: 0,
|
||||
existential_deposit: 0,
|
||||
transfer_fee: 0,
|
||||
creation_fee: 0,
|
||||
@@ -305,6 +303,10 @@ mod tests {
|
||||
(Keyring::Charlie.to_raw_public().into(), 1),
|
||||
],
|
||||
}),
|
||||
fees: Some(FeesConfig {
|
||||
transaction_base_fee: 1,
|
||||
transaction_byte_fee: 0,
|
||||
}),
|
||||
}.build_storage().unwrap().0)
|
||||
}
|
||||
|
||||
@@ -512,6 +514,10 @@ mod tests {
|
||||
EventRecord {
|
||||
phase: Phase::Finalization,
|
||||
event: Event::treasury(treasury::RawEvent::Rollover(0))
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::Finalization,
|
||||
event: Event::fees(fees::RawEvent::Charged(1, 1))
|
||||
}
|
||||
]);
|
||||
});
|
||||
@@ -589,6 +595,14 @@ mod tests {
|
||||
EventRecord {
|
||||
phase: Phase::Finalization,
|
||||
event: Event::treasury(treasury::RawEvent::Rollover(0))
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::Finalization,
|
||||
event: Event::fees(fees::RawEvent::Charged(1, 1))
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::Finalization,
|
||||
event: Event::fees(fees::RawEvent::Charged(2, 1))
|
||||
}
|
||||
]);
|
||||
});
|
||||
@@ -800,13 +814,13 @@ mod tests {
|
||||
let mut t = TestExternalities::<Blake2Hasher>::new_with_code(foreign_code, map![
|
||||
twox_128(&<balances::FreeBalance<Runtime>>::key_for(alice())).to_vec() => vec![69u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
twox_128(<balances::TotalIssuance<Runtime>>::key()).to_vec() => vec![69u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
twox_128(<balances::TransactionBaseFee<Runtime>>::key()).to_vec() => vec![70u8; 16],
|
||||
twox_128(<balances::TransactionByteFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::ExistentialDeposit<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::CreationFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::TransferFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<indices::NextEnumSet<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(&<system::BlockHash<Runtime>>::key_for(0)).to_vec() => vec![0u8; 32]
|
||||
twox_128(&<system::BlockHash<Runtime>>::key_for(0)).to_vec() => vec![0u8; 32],
|
||||
twox_128(<fees::TransactionBaseFee<Runtime>>::key()).to_vec() => vec![70u8; 16],
|
||||
twox_128(<fees::TransactionByteFee<Runtime>>::key()).to_vec() => vec![0u8; 16]
|
||||
]);
|
||||
|
||||
let r = WasmExecutor::new().call(&mut t, 8, COMPACT_CODE, "Core_initialise_block", &vec![].and(&from_block_number(1u64)));
|
||||
@@ -822,13 +836,13 @@ mod tests {
|
||||
let mut t = TestExternalities::<Blake2Hasher>::new_with_code(foreign_code, map![
|
||||
twox_128(&<balances::FreeBalance<Runtime>>::key_for(alice())).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
twox_128(<balances::TotalIssuance<Runtime>>::key()).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
twox_128(<balances::TransactionBaseFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::TransactionByteFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::ExistentialDeposit<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::CreationFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<balances::TransferFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<indices::NextEnumSet<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(&<system::BlockHash<Runtime>>::key_for(0)).to_vec() => vec![0u8; 32]
|
||||
twox_128(&<system::BlockHash<Runtime>>::key_for(0)).to_vec() => vec![0u8; 32],
|
||||
twox_128(<fees::TransactionBaseFee<Runtime>>::key()).to_vec() => vec![0u8; 16],
|
||||
twox_128(<fees::TransactionByteFee<Runtime>>::key()).to_vec() => vec![0u8; 16]
|
||||
]);
|
||||
|
||||
let r = WasmExecutor::new().call(&mut t, 8, COMPACT_CODE, "Core_initialise_block", &vec![].and(&from_block_number(1u64)));
|
||||
|
||||
@@ -31,6 +31,7 @@ timestamp = { package = "srml-timestamp", path = "../../srml/timestamp", default
|
||||
treasury = { package = "srml-treasury", path = "../../srml/treasury", default-features = false }
|
||||
sudo = { package = "srml-sudo", path = "../../srml/sudo", default-features = false }
|
||||
srml-upgrade-key = { path = "../../srml/upgrade-key", default-features = false }
|
||||
fees = { package = "srml-fees", path = "../../srml/fees", default-features = false }
|
||||
node-primitives = { path = "../primitives", default-features = false }
|
||||
consensus_aura = { package = "substrate-consensus-aura-primitives", path = "../../core/consensus/aura/primitives", default-features = false }
|
||||
rustc-hex = { version = "2.0", optional = true }
|
||||
@@ -61,6 +62,7 @@ std = [
|
||||
"treasury/std",
|
||||
"sudo/std",
|
||||
"srml-upgrade-key/std",
|
||||
"fees/std",
|
||||
"version/std",
|
||||
"node-primitives/std",
|
||||
"serde/std",
|
||||
|
||||
@@ -60,7 +60,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
spec_name: create_runtime_str!("node"),
|
||||
impl_name: create_runtime_str!("substrate-node"),
|
||||
authoring_version: 10,
|
||||
spec_version: 28,
|
||||
spec_version: 29,
|
||||
impl_version: 29,
|
||||
apis: RUNTIME_API_VERSIONS,
|
||||
};
|
||||
@@ -107,6 +107,12 @@ impl balances::Trait for Runtime {
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
impl fees::Trait for Runtime {
|
||||
type Event = Event;
|
||||
type Amount = Balance;
|
||||
type TransferAsset = Balances;
|
||||
}
|
||||
|
||||
impl consensus::Trait for Runtime {
|
||||
type Log = Log;
|
||||
type SessionKey = SessionKey;
|
||||
@@ -210,6 +216,7 @@ construct_runtime!(
|
||||
Treasury: treasury,
|
||||
Contract: contract::{Module, Call, Storage, Config<T>, Event<T>},
|
||||
Sudo: sudo,
|
||||
Fees: fees::{Module, Storage, Config<T>, Event<T>},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -228,7 +235,7 @@ pub type UncheckedExtrinsic = generic::UncheckedMortalCompactExtrinsic<Address,
|
||||
/// Extrinsic type that has already been checked.
|
||||
pub type CheckedExtrinsic = generic::CheckedExtrinsic<AccountId, Index, Call>;
|
||||
/// Executive: handles dispatch to the various modules.
|
||||
pub type Executive = executive::Executive<Runtime, Block, system::ChainContext<Runtime>, Balances, AllModules>;
|
||||
pub type Executive = executive::Executive<Runtime, Block, system::ChainContext<Runtime>, Fees, AllModules>;
|
||||
|
||||
impl_runtime_apis! {
|
||||
impl client_api::Core<Block> for Runtime {
|
||||
|
||||
Generated
+18
@@ -651,6 +651,7 @@ dependencies = [
|
||||
"srml-council 0.1.0",
|
||||
"srml-democracy 0.1.0",
|
||||
"srml-executive 0.1.0",
|
||||
"srml-fees 0.1.0",
|
||||
"srml-grandpa 0.1.0",
|
||||
"srml-indices 0.1.0",
|
||||
"srml-session 0.1.0",
|
||||
@@ -1349,6 +1350,7 @@ dependencies = [
|
||||
"sr-sandbox 0.1.0",
|
||||
"sr-std 0.1.0",
|
||||
"srml-balances 0.1.0",
|
||||
"srml-fees 0.1.0",
|
||||
"srml-support 0.1.0",
|
||||
"srml-system 0.1.0",
|
||||
"srml-timestamp 0.1.0",
|
||||
@@ -1402,6 +1404,22 @@ dependencies = [
|
||||
"srml-system 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "srml-fees"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-codec 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-codec-derive 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.81 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sr-io 0.1.0",
|
||||
"sr-primitives 0.1.0",
|
||||
"sr-std 0.1.0",
|
||||
"srml-support 0.1.0",
|
||||
"srml-system 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "srml-grandpa"
|
||||
version = "0.1.0"
|
||||
|
||||
BIN
Binary file not shown.
@@ -28,11 +28,11 @@ use rstd::prelude::*;
|
||||
use rstd::{cmp, result};
|
||||
use parity_codec::Codec;
|
||||
use parity_codec_derive::{Encode, Decode};
|
||||
use srml_support::{StorageValue, StorageMap, Parameter, decl_event, decl_storage, decl_module};
|
||||
use srml_support::{StorageValue, StorageMap, Parameter, decl_event, decl_storage, decl_module, ensure};
|
||||
use srml_support::traits::{UpdateBalanceOutcome, Currency, EnsureAccountLiquid, OnFreeBalanceZero};
|
||||
use srml_support::dispatch::Result;
|
||||
use primitives::traits::{Zero, SimpleArithmetic, MakePayment,
|
||||
As, StaticLookup, Member, CheckedAdd, CheckedSub, MaybeSerializeDebug};
|
||||
use primitives::traits::{Zero, SimpleArithmetic,
|
||||
As, StaticLookup, Member, CheckedAdd, CheckedSub, MaybeSerializeDebug, TransferAsset};
|
||||
use system::{IsDeadAccount, OnNewAccount, ensure_signed};
|
||||
|
||||
mod mock;
|
||||
@@ -122,7 +122,7 @@ decl_storage! {
|
||||
|
||||
let per_block = balance / length;
|
||||
let offset = begin * per_block + balance;
|
||||
|
||||
|
||||
(who.clone(), VestingSchedule { offset, per_block })
|
||||
})
|
||||
}).collect::<Vec<_>>()
|
||||
@@ -154,14 +154,6 @@ decl_storage! {
|
||||
/// `system::AccountNonce` is also deleted if `FreeBalance` is also zero (it also gets
|
||||
/// collapsed to zero if it ever becomes less than `ExistentialDeposit`.
|
||||
pub ReservedBalance get(reserved_balance): map T::AccountId => T::Balance;
|
||||
|
||||
|
||||
// Payment stuff.
|
||||
|
||||
/// The fee to be paid for making a transaction; the base.
|
||||
pub TransactionBaseFee get(transaction_base_fee) config(): T::Balance;
|
||||
/// The fee to be paid for making a transaction; the per-byte portion.
|
||||
pub TransactionByteFee get(transaction_byte_fee) config(): T::Balance;
|
||||
}
|
||||
add_extra_genesis {
|
||||
config(balances): Vec<(T::AccountId, T::Balance)>;
|
||||
@@ -505,15 +497,25 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> MakePayment<T::AccountId> for Module<T> {
|
||||
fn make_payment(transactor: &T::AccountId, encoded_len: usize) -> Result {
|
||||
let b = Self::free_balance(transactor);
|
||||
let transaction_fee = Self::transaction_base_fee() + Self::transaction_byte_fee() * <T::Balance as As<u64>>::sa(encoded_len as u64);
|
||||
if b < transaction_fee + Self::existential_deposit() {
|
||||
return Err("not enough funds for transaction fee");
|
||||
}
|
||||
Self::set_free_balance(transactor, b - transaction_fee);
|
||||
Self::decrease_total_stake_by(transaction_fee);
|
||||
impl<T: Trait> TransferAsset<T::AccountId> for Module<T> {
|
||||
type Amount = T::Balance;
|
||||
|
||||
fn transfer(from: &T::AccountId, to: &T::AccountId, amount: T::Balance) -> Result {
|
||||
Self::make_transfer(from, to, amount)
|
||||
}
|
||||
|
||||
fn remove_from(who: &T::AccountId, value: T::Balance) -> Result {
|
||||
T::EnsureAccountLiquid::ensure_account_liquid(who)?;
|
||||
let b = Self::free_balance(who);
|
||||
ensure!(b >= value, "account has too few funds");
|
||||
Self::set_free_balance(who, b - value);
|
||||
Self::decrease_total_stake_by(value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_to(who: &T::AccountId, value: T::Balance) -> Result {
|
||||
Self::set_free_balance_creating(who, Self::free_balance(who) + value);
|
||||
Self::increase_total_stake_by(value);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,8 +106,6 @@ impl ExtBuilder {
|
||||
} else {
|
||||
vec![(10, balance_factor), (20, balance_factor)]
|
||||
},
|
||||
transaction_base_fee: 0,
|
||||
transaction_byte_fee: 0,
|
||||
existential_deposit: self.existential_deposit,
|
||||
transfer_fee: self.transfer_fee,
|
||||
creation_fee: self.creation_fee,
|
||||
|
||||
@@ -20,6 +20,7 @@ srml-support = { path = "../support", default-features = false }
|
||||
system = { package = "srml-system", path = "../system", default-features = false }
|
||||
balances = { package = "srml-balances", path = "../balances", default-features = false }
|
||||
timestamp = { package = "srml-timestamp", path = "../timestamp", default-features = false }
|
||||
fees = { package = "srml-fees", path = "../fees", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
wabt = "~0.7.4"
|
||||
@@ -45,4 +46,5 @@ std = [
|
||||
"timestamp/std",
|
||||
"parity-wasm/std",
|
||||
"pwasm-utils/std",
|
||||
"fees/std",
|
||||
]
|
||||
|
||||
@@ -78,6 +78,7 @@ use srml_support::traits::OnFreeBalanceZero;
|
||||
use system::{ensure_signed, RawOrigin};
|
||||
use runtime_io::{blake2_256, twox_128};
|
||||
use timestamp;
|
||||
use fees;
|
||||
|
||||
pub type CodeHash<T> = <T as system::Trait>::Hash;
|
||||
|
||||
@@ -91,7 +92,7 @@ pub trait ComputeDispatchFee<Call, Balance> {
|
||||
fn compute_dispatch_fee(call: &Call) -> Balance;
|
||||
}
|
||||
|
||||
pub trait Trait: balances::Trait + timestamp::Trait {
|
||||
pub trait Trait: fees::Trait + balances::Trait + timestamp::Trait {
|
||||
/// The outer call dispatch type.
|
||||
type Call: Parameter + Dispatchable<Origin=<Self as system::Trait>::Origin>;
|
||||
|
||||
@@ -135,14 +136,14 @@ where
|
||||
}
|
||||
|
||||
/// The default dispatch fee computor computes the fee in the same way that
|
||||
/// implementation of `MakePayment` for balances module does.
|
||||
/// implementation of `ChargeBytesFee` for fees module does.
|
||||
pub struct DefaultDispatchFeeComputor<T: Trait>(PhantomData<T>);
|
||||
impl<T: Trait> ComputeDispatchFee<T::Call, T::Balance> for DefaultDispatchFeeComputor<T> {
|
||||
fn compute_dispatch_fee(call: &T::Call) -> T::Balance {
|
||||
let encoded_len = parity_codec::Encode::encode(&call).len();
|
||||
let base_fee = <balances::Module<T>>::transaction_base_fee();
|
||||
let byte_fee = <balances::Module<T>>::transaction_byte_fee();
|
||||
base_fee + byte_fee * <T::Balance as As<u64>>::sa(encoded_len as u64)
|
||||
let base_fee = <fees::Module<T>>::transaction_base_fee();
|
||||
let byte_fee = <fees::Module<T>>::transaction_byte_fee();
|
||||
<T::Balance as As<u64>>::sa(base_fee.as_() + byte_fee.as_() * encoded_len as u64)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ use runtime_io;
|
||||
use srml_support::{StorageMap, StorageDoubleMap, assert_ok, impl_outer_event, impl_outer_dispatch, impl_outer_origin};
|
||||
use substrate_primitives::{Blake2Hasher};
|
||||
use system::{self, Phase, EventRecord};
|
||||
use fees;
|
||||
use {wabt, balances, consensus};
|
||||
use hex_literal::*;
|
||||
use assert_matches::assert_matches;
|
||||
@@ -44,7 +45,7 @@ mod contract {
|
||||
}
|
||||
impl_outer_event! {
|
||||
pub enum MetaEvent for Test {
|
||||
balances<T>, contract<T>,
|
||||
balances<T>, contract<T>, fees<T>,
|
||||
}
|
||||
}
|
||||
impl_outer_origin! {
|
||||
@@ -88,6 +89,11 @@ impl consensus::Trait for Test {
|
||||
type SessionKey = UintAuthorityId;
|
||||
type InherentOfflineReport = ();
|
||||
}
|
||||
impl fees::Trait for Test {
|
||||
type Event = MetaEvent;
|
||||
type Amount = u64;
|
||||
type TransferAsset = Balances;
|
||||
}
|
||||
impl Trait for Test {
|
||||
type Call = Call;
|
||||
type Gas = u64;
|
||||
@@ -165,8 +171,6 @@ impl ExtBuilder {
|
||||
t.extend(
|
||||
balances::GenesisConfig::<Test> {
|
||||
balances: vec![],
|
||||
transaction_base_fee: 0,
|
||||
transaction_byte_fee: 0,
|
||||
existential_deposit: self.existential_deposit,
|
||||
transfer_fee: self.transfer_fee,
|
||||
creation_fee: self.creation_fee,
|
||||
|
||||
@@ -100,8 +100,6 @@ mod tests {
|
||||
let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap().0;
|
||||
t.extend(balances::GenesisConfig::<Test>{
|
||||
balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)],
|
||||
transaction_base_fee: 0,
|
||||
transaction_byte_fee: 0,
|
||||
existential_deposit: 0,
|
||||
transfer_fee: 0,
|
||||
creation_fee: 0,
|
||||
|
||||
@@ -486,8 +486,6 @@ mod tests {
|
||||
let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap().0;
|
||||
t.extend(balances::GenesisConfig::<Test>{
|
||||
balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)],
|
||||
transaction_base_fee: 0,
|
||||
transaction_byte_fee: 0,
|
||||
existential_deposit: 0,
|
||||
transfer_fee: 0,
|
||||
creation_fee: 0,
|
||||
|
||||
@@ -18,6 +18,7 @@ hex-literal = "0.1.0"
|
||||
substrate-primitives = { path = "../../core/primitives" }
|
||||
srml-indices = { path = "../indices" }
|
||||
balances = { package = "srml-balances", path = "../balances" }
|
||||
fees = { package = "srml-fees", path = "../fees" }
|
||||
parity-codec-derive = { version = "3.0", default-features = false }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -18,11 +18,12 @@
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
|
||||
use rstd::prelude::*;
|
||||
use rstd::marker::PhantomData;
|
||||
use rstd::result;
|
||||
use primitives::traits::{self, Header, Zero, One, Checkable, Applyable, CheckEqual, OnFinalise,
|
||||
OnInitialise, MakePayment, Hash, As, Digest};
|
||||
OnInitialise, ChargeBytesFee, Hash, As, Digest};
|
||||
use srml_support::Dispatchable;
|
||||
use parity_codec::{Codec, Encode};
|
||||
use system::extrinsics_root;
|
||||
@@ -58,7 +59,7 @@ impl<
|
||||
Context: Default,
|
||||
System: system::Trait,
|
||||
Block: traits::Block<Header=System::Header, Hash=System::Hash>,
|
||||
Payment: MakePayment<System::AccountId>,
|
||||
Payment: ChargeBytesFee<System::AccountId>,
|
||||
AllModules: OnInitialise<System::BlockNumber> + OnFinalise<System::BlockNumber>,
|
||||
> Executive<System, Block, Context, Payment, AllModules> where
|
||||
Block::Extrinsic: Checkable<Context> + Codec,
|
||||
@@ -166,7 +167,7 @@ impl<
|
||||
) }
|
||||
|
||||
// pay any fees.
|
||||
Payment::make_payment(sender, encoded_len).map_err(|_| internal::ApplyError::CantPay)?;
|
||||
Payment::charge_base_bytes_fee(sender, encoded_len).map_err(|_| internal::ApplyError::CantPay)?;
|
||||
|
||||
// AUDIT: Under no circumstances may this function panic from here onwards.
|
||||
|
||||
@@ -238,7 +239,7 @@ impl<
|
||||
|
||||
if let (Some(sender), Some(index)) = (xt.sender(), xt.index()) {
|
||||
// pay any fees.
|
||||
if Payment::make_payment(sender, encoded_len).is_err() {
|
||||
if Payment::charge_base_bytes_fee(sender, encoded_len).is_err() {
|
||||
return TransactionValidity::Invalid(ApplyError::CantPay as i8)
|
||||
}
|
||||
|
||||
@@ -284,6 +285,7 @@ mod tests {
|
||||
use primitives::testing::{Digest, DigestItem, Header, Block};
|
||||
use srml_support::{traits::Currency, impl_outer_origin, impl_outer_event};
|
||||
use system;
|
||||
use fees;
|
||||
use hex_literal::{hex, hex_impl};
|
||||
|
||||
impl_outer_origin! {
|
||||
@@ -293,7 +295,7 @@ mod tests {
|
||||
|
||||
impl_outer_event!{
|
||||
pub enum MetaEvent for Runtime {
|
||||
balances<T>,
|
||||
balances<T>, fees<T>,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,22 +322,29 @@ mod tests {
|
||||
type EnsureAccountLiquid = ();
|
||||
type Event = MetaEvent;
|
||||
}
|
||||
impl fees::Trait for Runtime {
|
||||
type Event = MetaEvent;
|
||||
type Amount = u64;
|
||||
type TransferAsset = balances::Module<Runtime>;
|
||||
}
|
||||
|
||||
type TestXt = primitives::testing::TestXt<Call<Runtime>>;
|
||||
type Executive = super::Executive<Runtime, Block<TestXt>, system::ChainContext<Runtime>, balances::Module<Runtime>, ()>;
|
||||
type Executive = super::Executive<Runtime, Block<TestXt>, system::ChainContext<Runtime>, fees::Module<Runtime>, ()>;
|
||||
|
||||
#[test]
|
||||
fn balance_transfer_dispatch_works() {
|
||||
let mut t = system::GenesisConfig::<Runtime>::default().build_storage().unwrap().0;
|
||||
t.extend(balances::GenesisConfig::<Runtime> {
|
||||
balances: vec![(1, 111)],
|
||||
transaction_base_fee: 10,
|
||||
transaction_byte_fee: 0,
|
||||
existential_deposit: 0,
|
||||
transfer_fee: 0,
|
||||
creation_fee: 0,
|
||||
vesting: vec![],
|
||||
}.build_storage().unwrap().0);
|
||||
t.extend(fees::GenesisConfig::<Runtime> {
|
||||
transaction_base_fee: 10,
|
||||
transaction_byte_fee: 0,
|
||||
}.build_storage().unwrap().0);
|
||||
let xt = primitives::testing::TestXt(Some(1), 0, Call::transfer(2, 69));
|
||||
let mut t = runtime_io::TestExternalities::<Blake2Hasher>::new(t);
|
||||
with_externalities(&mut t, || {
|
||||
@@ -360,7 +369,7 @@ mod tests {
|
||||
header: Header {
|
||||
parent_hash: [69u8; 32].into(),
|
||||
number: 1,
|
||||
state_root: hex!("49cd58a254ccf6abc4a023d9a22dcfc421e385527a250faec69f8ad0d8ed3e48").into(),
|
||||
state_root: hex!("6651861f40a8f42c033b3e937cb3513e6dbaf4be6bafb1561a19f884be3f58dd").into(),
|
||||
extrinsics_root: hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into(),
|
||||
digest: Digest { logs: vec![], },
|
||||
},
|
||||
@@ -394,7 +403,7 @@ mod tests {
|
||||
header: Header {
|
||||
parent_hash: [69u8; 32].into(),
|
||||
number: 1,
|
||||
state_root: hex!("49cd58a254ccf6abc4a023d9a22dcfc421e385527a250faec69f8ad0d8ed3e48").into(),
|
||||
state_root: hex!("6651861f40a8f42c033b3e937cb3513e6dbaf4be6bafb1561a19f884be3f58dd").into(),
|
||||
extrinsics_root: [0u8; 32].into(),
|
||||
digest: Digest { logs: vec![], },
|
||||
},
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "srml-fees"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
hex-literal = "0.1.0"
|
||||
serde = { version = "1.0", default-features = false }
|
||||
parity-codec = { version = "3.0", default-features = false }
|
||||
parity-codec-derive = { version = "3.0", default-features = false }
|
||||
primitives = { package = "substrate-primitives", path = "../../core/primitives", default-features = false }
|
||||
rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false }
|
||||
runtime_io = { package = "sr-io", path = "../../core/sr-io", default-features = false }
|
||||
runtime_primitives = { package = "sr-primitives", path = "../../core/sr-primitives", default-features = false }
|
||||
srml-support = { package = "srml-support", path = "../support", default-features = false }
|
||||
system = { package = "srml-system", path = "../system", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"serde/std",
|
||||
"parity-codec/std",
|
||||
"parity-codec-derive/std",
|
||||
"primitives/std",
|
||||
"rstd/std",
|
||||
"runtime_io/std",
|
||||
"runtime_primitives/std",
|
||||
"srml-support/std",
|
||||
"system/std",
|
||||
]
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Handles all transaction fee related operations
|
||||
|
||||
// Ensure we're `no_std` when compiling for Wasm.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use srml_support::{dispatch::Result, Parameter, StorageMap, decl_event, decl_storage, decl_module};
|
||||
use runtime_primitives::traits::{
|
||||
As, Member, SimpleArithmetic, ChargeBytesFee, ChargeFee,
|
||||
TransferAsset, CheckedAdd, CheckedSub, CheckedMul, Zero
|
||||
};
|
||||
use system;
|
||||
|
||||
mod mock;
|
||||
mod tests;
|
||||
|
||||
pub trait Trait: system::Trait {
|
||||
/// The overarching event type.
|
||||
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
|
||||
|
||||
/// The unit for fee amount
|
||||
type Amount: Member + Parameter + SimpleArithmetic + Default + Copy + As<u64>;
|
||||
|
||||
/// A function does the asset transfer between accounts
|
||||
type TransferAsset: TransferAsset<Self::AccountId, Amount = Self::Amount>;
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
fn deposit_event<T>() = default;
|
||||
|
||||
fn on_finalise() {
|
||||
let extrinsic_count = <system::Module<T>>::extrinsic_count();
|
||||
(0..extrinsic_count).for_each(|index| {
|
||||
// Deposit `Charged` event if some amount of fee charged.
|
||||
let fee = <CurrentTransactionFee<T>>::take(index);
|
||||
if !fee.is_zero() {
|
||||
Self::deposit_event(RawEvent::Charged(index, fee));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decl_event!(
|
||||
pub enum Event<T> where <T as Trait>::Amount {
|
||||
/// Fee charged (extrinsic_index, fee_amount)
|
||||
Charged(u32, Amount),
|
||||
}
|
||||
);
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as Fees {
|
||||
/// The fee to be paid for making a transaction; the base.
|
||||
pub TransactionBaseFee get(transaction_base_fee) config(): T::Amount;
|
||||
/// The fee to be paid for making a transaction; the per-byte portion.
|
||||
pub TransactionByteFee get(transaction_byte_fee) config(): T::Amount;
|
||||
|
||||
/// The `extrinsic_index => accumulated_fees` map, containing records to
|
||||
/// track the overall charged fees for each transaction.
|
||||
///
|
||||
/// All records should be removed at finalise stage.
|
||||
CurrentTransactionFee get(current_transaction_fee): map u32 => T::Amount;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> ChargeBytesFee<T::AccountId> for Module<T> {
|
||||
fn charge_base_bytes_fee(transactor: &T::AccountId, encoded_len: usize) -> Result {
|
||||
let bytes_fee = Self::transaction_byte_fee().checked_mul(
|
||||
&<T::Amount as As<u64>>::sa(encoded_len as u64)
|
||||
).ok_or_else(|| "bytes fee overflow")?;
|
||||
let overall = Self::transaction_base_fee().checked_add(&bytes_fee).ok_or_else(|| "bytes fee overflow")?;
|
||||
Self::charge_fee(transactor, overall)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> ChargeFee<T::AccountId> for Module<T> {
|
||||
type Amount = T::Amount;
|
||||
|
||||
fn charge_fee(transactor: &T::AccountId, amount: T::Amount) -> Result {
|
||||
let extrinsic_index = <system::Module<T>>::extrinsic_index().ok_or_else(|| "no extrinsic index found")?;
|
||||
let current_fee = Self::current_transaction_fee(extrinsic_index);
|
||||
let new_fee = current_fee.checked_add(&amount).ok_or_else(|| "fee got overflow after charge")?;
|
||||
|
||||
T::TransferAsset::remove_from(transactor, amount)?;
|
||||
|
||||
<CurrentTransactionFee<T>>::insert(extrinsic_index, new_fee);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn refund_fee(transactor: &T::AccountId, amount: T::Amount) -> Result {
|
||||
let extrinsic_index = <system::Module<T>>::extrinsic_index().ok_or_else(|| "no extrinsic index found")?;
|
||||
let current_fee = Self::current_transaction_fee(extrinsic_index);
|
||||
let new_fee = current_fee.checked_sub(&amount).ok_or_else(|| "fee got underflow after refund")?;
|
||||
|
||||
T::TransferAsset::add_to(transactor, amount)?;
|
||||
|
||||
<CurrentTransactionFee<T>>::insert(extrinsic_index, new_fee);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Test utilities
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use runtime_primitives::BuildStorage;
|
||||
use runtime_primitives::{
|
||||
traits::{IdentityLookup, BlakeTwo256, TransferAsset},
|
||||
testing::{Digest, DigestItem, Header},
|
||||
};
|
||||
use primitives::{H256, Blake2Hasher};
|
||||
use runtime_io;
|
||||
use srml_support::{impl_outer_origin, impl_outer_event};
|
||||
use crate::{GenesisConfig, Module, Trait, system};
|
||||
|
||||
impl_outer_origin!{
|
||||
pub enum Origin for Test {}
|
||||
}
|
||||
|
||||
mod fees {
|
||||
pub use crate::Event;
|
||||
}
|
||||
|
||||
impl_outer_event!{
|
||||
pub enum TestEvent for Test {
|
||||
fees<T>,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TransferAssetMock;
|
||||
|
||||
impl<AccountId> TransferAsset<AccountId> for TransferAssetMock {
|
||||
type Amount = u64;
|
||||
|
||||
fn transfer(_: &AccountId, _: &AccountId, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
|
||||
fn remove_from(_: &AccountId, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
|
||||
fn add_to(_: &AccountId, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
|
||||
}
|
||||
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Test;
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<u64>;
|
||||
type Header = Header;
|
||||
type Event = TestEvent;
|
||||
type Log = DigestItem;
|
||||
}
|
||||
impl Trait for Test {
|
||||
type Amount = u64;
|
||||
type Event =TestEvent;
|
||||
type TransferAsset = TransferAssetMock;
|
||||
}
|
||||
|
||||
pub type System = system::Module<Test>;
|
||||
pub type Fees = Module<Test>;
|
||||
|
||||
pub struct ExtBuilder {
|
||||
transaction_base_fee: u64,
|
||||
transaction_byte_fee: u64,
|
||||
}
|
||||
impl Default for ExtBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
transaction_base_fee: 0,
|
||||
transaction_byte_fee: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ExtBuilder {
|
||||
pub fn transaction_base_fee(mut self, transaction_base_fee: u64) -> Self {
|
||||
self.transaction_base_fee = transaction_base_fee;
|
||||
self
|
||||
}
|
||||
pub fn transaction_byte_fee(mut self, transaction_byte_fee: u64) -> Self {
|
||||
self.transaction_byte_fee = transaction_byte_fee;
|
||||
self
|
||||
}
|
||||
pub fn build(self) -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap().0;
|
||||
t.extend(GenesisConfig::<Test> {
|
||||
transaction_base_fee: self.transaction_base_fee,
|
||||
transaction_byte_fee: self.transaction_byte_fee,
|
||||
}.build_storage().unwrap().0);
|
||||
t.into()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tests for the module.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
use runtime_io::with_externalities;
|
||||
use runtime_primitives::traits::{OnFinalise};
|
||||
use system::{EventRecord, Phase};
|
||||
|
||||
use mock::{Fees, System, ExtBuilder};
|
||||
use srml_support::{assert_ok, assert_err};
|
||||
|
||||
#[test]
|
||||
fn charge_base_bytes_fee_should_work() {
|
||||
with_externalities(
|
||||
&mut ExtBuilder::default()
|
||||
.transaction_base_fee(3)
|
||||
.transaction_byte_fee(5)
|
||||
.build(),
|
||||
|| {
|
||||
System::set_extrinsic_index(0);
|
||||
assert_ok!(Fees::charge_base_bytes_fee(&0, 7));
|
||||
assert_eq!(Fees::current_transaction_fee(0), 3 + 5 * 7);
|
||||
|
||||
System::set_extrinsic_index(1);
|
||||
assert_ok!(Fees::charge_base_bytes_fee(&0, 11));
|
||||
assert_eq!(Fees::current_transaction_fee(1), 3 + 5 * 11);
|
||||
|
||||
System::set_extrinsic_index(3);
|
||||
assert_ok!(Fees::charge_base_bytes_fee(&0, 13));
|
||||
assert_eq!(Fees::current_transaction_fee(3), 3 + 5 * 13);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn charge_base_bytes_fee_should_not_work_if_bytes_fee_overflow() {
|
||||
// bytes fee overflows.
|
||||
with_externalities(
|
||||
&mut ExtBuilder::default()
|
||||
.transaction_base_fee(0)
|
||||
.transaction_byte_fee(u64::max_value())
|
||||
.build(),
|
||||
|| {
|
||||
System::set_extrinsic_index(0);
|
||||
assert_err!(
|
||||
Fees::charge_base_bytes_fee(&0, 2),
|
||||
"bytes fee overflow"
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn charge_base_bytes_fee_should_not_work_if_overall_fee_overflow() {
|
||||
// bytes fee doesn't overflow, but overall fee (bytes_fee + base_fee) does
|
||||
with_externalities(
|
||||
&mut ExtBuilder::default()
|
||||
.transaction_base_fee(u64::max_value())
|
||||
.transaction_byte_fee(1)
|
||||
.build(),
|
||||
|| {
|
||||
System::set_extrinsic_index(0);
|
||||
assert_err!(
|
||||
Fees::charge_base_bytes_fee(&0, 1),
|
||||
"bytes fee overflow"
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn charge_fee_should_work() {
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
System::set_extrinsic_index(0);
|
||||
assert_ok!(Fees::charge_fee(&0, 2));
|
||||
assert_ok!(Fees::charge_fee(&0, 3));
|
||||
assert_eq!(Fees::current_transaction_fee(0), 2 + 3);
|
||||
|
||||
System::set_extrinsic_index(2);
|
||||
assert_ok!(Fees::charge_fee(&0, 5));
|
||||
assert_ok!(Fees::charge_fee(&0, 7));
|
||||
assert_eq!(Fees::current_transaction_fee(2), 5 + 7);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn charge_fee_when_overflow_should_not_work() {
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
System::set_extrinsic_index(0);
|
||||
assert_ok!(Fees::charge_fee(&0, u64::max_value()));
|
||||
assert_err!(Fees::charge_fee(&0, 1), "fee got overflow after charge");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refund_fee_should_work() {
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
System::set_extrinsic_index(0);
|
||||
assert_ok!(Fees::charge_fee(&0, 5));
|
||||
assert_ok!(Fees::refund_fee(&0, 3));
|
||||
assert_eq!(Fees::current_transaction_fee(0), 5 - 3);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refund_fee_when_underflow_should_not_work() {
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
System::set_extrinsic_index(0);
|
||||
assert_err!(Fees::refund_fee(&0, 1), "fee got underflow after refund");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn on_finalise_should_work() {
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
// charge fees in extrinsic index 3
|
||||
System::set_extrinsic_index(3);
|
||||
assert_ok!(Fees::charge_fee(&0, 1));
|
||||
System::note_applied_extrinsic(&Ok(()), 1);
|
||||
// charge fees in extrinsic index 5
|
||||
System::set_extrinsic_index(5);
|
||||
assert_ok!(Fees::charge_fee(&0, 1));
|
||||
System::note_applied_extrinsic(&Ok(()), 1);
|
||||
System::note_finished_extrinsics();
|
||||
|
||||
// `current_transaction_fee`, `extrinsic_count` should be as expected.
|
||||
assert_eq!(Fees::current_transaction_fee(3), 1);
|
||||
assert_eq!(Fees::current_transaction_fee(5), 1);
|
||||
assert_eq!(System::extrinsic_count(), 5 + 1);
|
||||
|
||||
<Fees as OnFinalise<u64>>::on_finalise(1);
|
||||
|
||||
// When finalised, `CurrentTransactionFee` records should be cleared.
|
||||
assert_eq!(Fees::current_transaction_fee(3), 0);
|
||||
assert_eq!(Fees::current_transaction_fee(5), 0);
|
||||
|
||||
// When finalised, if any fee charged in a extrinsic, a `Charged` event should be deposited
|
||||
// for it.
|
||||
let fee_charged_events: Vec<EventRecord<mock::TestEvent>> = System::events()
|
||||
.into_iter()
|
||||
.filter(|e| match e.event {
|
||||
mock::TestEvent::fees(RawEvent::Charged(_, _)) => return true,
|
||||
_ => return false,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(fee_charged_events, vec![
|
||||
EventRecord {
|
||||
phase: Phase::Finalization,
|
||||
event: RawEvent::Charged(3, 1).into(),
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::Finalization,
|
||||
event: RawEvent::Charged(5, 1).into(),
|
||||
},
|
||||
]);
|
||||
});
|
||||
}
|
||||
@@ -104,8 +104,6 @@ pub fn new_test_ext(
|
||||
} else {
|
||||
vec![(10, balance_factor), (20, balance_factor)]
|
||||
},
|
||||
transaction_base_fee: 0,
|
||||
transaction_byte_fee: 0,
|
||||
existential_deposit: ext_deposit,
|
||||
transfer_fee: 0,
|
||||
creation_fee: 0,
|
||||
|
||||
@@ -273,6 +273,11 @@ impl<T: Trait> Module<T> {
|
||||
storage::unhashed::get(well_known_keys::EXTRINSIC_INDEX)
|
||||
}
|
||||
|
||||
/// Gets extrinsics count.
|
||||
pub fn extrinsic_count() -> u32 {
|
||||
<ExtrinsicCount<T>>::get().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Gets a total length of all executed extrinsics.
|
||||
pub fn all_extrinsics_len() -> u32 {
|
||||
<AllExtrinsicsLen<T>>::get().unwrap_or_default()
|
||||
|
||||
@@ -303,8 +303,6 @@ mod tests {
|
||||
let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap().0;
|
||||
t.extend(balances::GenesisConfig::<Test>{
|
||||
balances: vec![(0, 100), (1, 99), (2, 1)],
|
||||
transaction_base_fee: 0,
|
||||
transaction_byte_fee: 0,
|
||||
transfer_fee: 0,
|
||||
creation_fee: 0,
|
||||
existential_deposit: 0,
|
||||
|
||||
Reference in New Issue
Block a user