-
Notifications
You must be signed in to change notification settings - Fork 28
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
Showing
14 changed files
with
3,133 additions
and
8 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,44 @@ | ||
[package] | ||
name = "pallet-balances" | ||
version = "2.0.0" | ||
authors = ["Parity Technologies <[email protected]>"] | ||
edition = "2018" | ||
license = "Apache-2.0" | ||
homepage = "https://substrate.dev" | ||
repository = "https://github.com/paritytech/substrate/" | ||
description = "FRAME pallet to manage balances" | ||
readme = "README.md" | ||
|
||
[package.metadata.docs.rs] | ||
targets = ["x86_64-unknown-linux-gnu"] | ||
|
||
[dependencies] | ||
serde = { version = "1.0.101", optional = true } | ||
codec = { package = "parity-scale-codec", version = "1.3.4", default-features = false, features = ["derive"] } | ||
sp-api = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "master" } | ||
sp-std = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "master" } | ||
sp-runtime = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "master" } | ||
frame-benchmarking = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "master", optional = true } | ||
frame-support = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "master" } | ||
frame-system = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "master" } | ||
chrono = { version = "0.4", default-features = false, features = ["alloc"] } | ||
pallet-timestamp = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "master" } | ||
[dev-dependencies] | ||
sp-io = { version = "2.0.0", git = "https://github.com/paritytech/substrate.git", branch = "master" } | ||
sp-core = { version = "2.0.0", git = "https://github.com/paritytech/substrate.git", branch = "master" } | ||
pallet-transaction-payment = { version = "2.0.0", git = "https://github.com/paritytech/substrate.git", branch = "master" } | ||
|
||
[features] | ||
default = ["std"] | ||
std = [ | ||
"serde", | ||
"codec/std", | ||
"chrono/std", | ||
"sp-api/std", | ||
"sp-std/std", | ||
"sp-runtime/std", | ||
"frame-benchmarking/std", | ||
"frame-support/std", | ||
"frame-system/std", | ||
] | ||
runtime-benchmarks = ["frame-benchmarking"] |
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,122 @@ | ||
# Balances Module | ||
|
||
The Balances module provides functionality for handling accounts and balances. | ||
|
||
- [`balances::Trait`](https://docs.rs/pallet-balances/latest/pallet_balances/trait.Trait.html) | ||
- [`Call`](https://docs.rs/pallet-balances/latest/pallet_balances/enum.Call.html) | ||
- [`Module`](https://docs.rs/pallet-balances/latest/pallet_balances/struct.Module.html) | ||
|
||
## Overview | ||
|
||
The Balances module provides functions for: | ||
|
||
- Getting and setting free balances. | ||
- Retrieving total, reserved and unreserved balances. | ||
- Repatriating a reserved balance to a beneficiary account that exists. | ||
- Transferring a balance between accounts (when not reserved). | ||
- Slashing an account balance. | ||
- Account creation and removal. | ||
- Managing total issuance. | ||
- Setting and managing locks. | ||
|
||
### Terminology | ||
|
||
- **Existential Deposit:** The minimum balance required to create or keep an account open. This prevents | ||
"dust accounts" from filling storage. When the free plus the reserved balance (i.e. the total balance) | ||
fall below this, then the account is said to be dead; and it loses its functionality as well as any | ||
prior history and all information on it is removed from the chain's state. | ||
No account should ever have a total balance that is strictly between 0 and the existential | ||
deposit (exclusive). If this ever happens, it indicates either a bug in this module or an | ||
erroneous raw mutation of storage. | ||
|
||
- **Total Issuance:** The total number of units in existence in a system. | ||
|
||
- **Reaping an account:** The act of removing an account by resetting its nonce. Happens after its | ||
total balance has become zero (or, strictly speaking, less than the Existential Deposit). | ||
|
||
- **Free Balance:** The portion of a balance that is not reserved. The free balance is the only | ||
balance that matters for most operations. | ||
|
||
- **Reserved Balance:** Reserved balance still belongs to the account holder, but is suspended. | ||
Reserved balance can still be slashed, but only after all the free balance has been slashed. | ||
|
||
- **Imbalance:** A condition when some funds were credited or debited without equal and opposite accounting | ||
(i.e. a difference between total issuance and account balances). Functions that result in an imbalance will | ||
return an object of the `Imbalance` trait that can be managed within your runtime logic. (If an imbalance is | ||
simply dropped, it should automatically maintain any book-keeping such as total issuance.) | ||
|
||
- **Lock:** A freeze on a specified amount of an account's free balance until a specified block number. Multiple | ||
locks always operate over the same funds, so they "overlay" rather than "stack". | ||
|
||
### Implementations | ||
|
||
The Balances module provides implementations for the following traits. If these traits provide the functionality | ||
that you need, then you can avoid coupling with the Balances module. | ||
|
||
- [`Currency`](https://docs.rs/frame-support/latest/frame_support/traits/trait.Currency.html): Functions for dealing with a | ||
fungible assets system. | ||
- [`ReservableCurrency`](https://docs.rs/frame-support/latest/frame_support/traits/trait.ReservableCurrency.html): | ||
Functions for dealing with assets that can be reserved from an account. | ||
- [`LockableCurrency`](https://docs.rs/frame-support/latest/frame_support/traits/trait.LockableCurrency.html): Functions for | ||
dealing with accounts that allow liquidity restrictions. | ||
- [`Imbalance`](https://docs.rs/frame-support/latest/frame_support/traits/trait.Imbalance.html): Functions for handling | ||
imbalances between total issuance in the system and account balances. Must be used when a function | ||
creates new funds (e.g. a reward) or destroys some funds (e.g. a system fee). | ||
- [`IsDeadAccount`](https://docs.rs/frame-support/latest/frame_support/traits/trait.IsDeadAccount.html): Determiner to say whether a | ||
given account is unused. | ||
|
||
## Interface | ||
|
||
### Dispatchable Functions | ||
|
||
- `transfer` - Transfer some liquid free balance to another account. | ||
- `set_balance` - Set the balances of a given account. The origin of this call must be root. | ||
|
||
## Usage | ||
|
||
The following examples show how to use the Balances module in your custom module. | ||
|
||
### Examples from the FRAME | ||
|
||
The Contract module uses the `Currency` trait to handle gas payment, and its types inherit from `Currency`: | ||
|
||
```rust | ||
use frame_support::traits::Currency; | ||
|
||
pub type BalanceOf<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance; | ||
pub type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance; | ||
|
||
``` | ||
|
||
The Staking module uses the `LockableCurrency` trait to lock a stash account's funds: | ||
|
||
```rust | ||
use frame_support::traits::{WithdrawReasons, LockableCurrency}; | ||
use sp_runtime::traits::Bounded; | ||
pub trait Config: frame_system::Config { | ||
type Currency: LockableCurrency<Self::AccountId, Moment=Self::BlockNumber>; | ||
} | ||
|
||
fn update_ledger<T: Config>( | ||
controller: &T::AccountId, | ||
ledger: &StakingLedger<T> | ||
) { | ||
T::Currency::set_lock( | ||
STAKING_ID, | ||
&ledger.stash, | ||
ledger.total, | ||
WithdrawReasons::all() | ||
); | ||
// <Ledger<T>>::insert(controller, ledger); // Commented out as we don't have access to Staking's storage here. | ||
} | ||
``` | ||
|
||
## Genesis config | ||
|
||
The Balances module depends on the [`GenesisConfig`](https://docs.rs/pallet-balances/latest/pallet_balances/struct.GenesisConfig.html). | ||
|
||
## Assumptions | ||
|
||
* Total issued balanced of all accounts should be less than `Config::Balance::max_value()`. | ||
|
||
License: Apache-2.0 |
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,196 @@ | ||
// This file is part of Substrate. | ||
|
||
// Copyright (C) 2020 Parity Technologies (UK) Ltd. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
//! Balances pallet benchmarking. | ||
#![cfg(feature = "runtime-benchmarks")] | ||
|
||
use super::*; | ||
|
||
use frame_system::RawOrigin; | ||
use frame_benchmarking::{benchmarks, account, whitelisted_caller}; | ||
use sp_runtime::traits::Bounded; | ||
|
||
use crate::Module as Balances; | ||
|
||
const SEED: u32 = 0; | ||
// existential deposit multiplier | ||
const ED_MULTIPLIER: u32 = 10; | ||
|
||
|
||
benchmarks! { | ||
_ { } | ||
|
||
// Benchmark `transfer` extrinsic with the worst possible conditions: | ||
// * Transfer will kill the sender account. | ||
// * Transfer will create the recipient account. | ||
transfer { | ||
let existential_deposit = T::ExistentialDeposit::get(); | ||
let caller = whitelisted_caller(); | ||
|
||
// Give some multiple of the existential deposit + creation fee + transfer fee | ||
let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); | ||
let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&caller, balance); | ||
|
||
// Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account, and reap this user. | ||
let recipient: T::AccountId = account("recipient", 0, SEED); | ||
let recipient_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(recipient.clone()); | ||
let transfer_amount = existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into(); | ||
}: transfer(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount) | ||
verify { | ||
assert_eq!(Balances::<T>::free_balance(&caller), Zero::zero()); | ||
assert_eq!(Balances::<T>::free_balance(&recipient), transfer_amount); | ||
} | ||
|
||
// Benchmark `transfer` with the best possible condition: | ||
// * Both accounts exist and will continue to exist. | ||
#[extra] | ||
transfer_best_case { | ||
let caller = whitelisted_caller(); | ||
let recipient: T::AccountId = account("recipient", 0, SEED); | ||
let recipient_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(recipient.clone()); | ||
|
||
// Give the sender account max funds for transfer (their account will never reasonably be killed). | ||
let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value()); | ||
|
||
// Give the recipient account existential deposit (thus their account already exists). | ||
let existential_deposit = T::ExistentialDeposit::get(); | ||
let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&recipient, existential_deposit); | ||
let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); | ||
}: transfer(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount) | ||
verify { | ||
assert!(!Balances::<T>::free_balance(&caller).is_zero()); | ||
assert!(!Balances::<T>::free_balance(&recipient).is_zero()); | ||
} | ||
|
||
// Benchmark `transfer_keep_alive` with the worst possible condition: | ||
// * The recipient account is created. | ||
transfer_keep_alive { | ||
let caller = whitelisted_caller(); | ||
let recipient: T::AccountId = account("recipient", 0, SEED); | ||
let recipient_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(recipient.clone()); | ||
|
||
// Give the sender account max funds, thus a transfer will not kill account. | ||
let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value()); | ||
let existential_deposit = T::ExistentialDeposit::get(); | ||
let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); | ||
}: _(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount) | ||
verify { | ||
assert!(!Balances::<T>::free_balance(&caller).is_zero()); | ||
assert_eq!(Balances::<T>::free_balance(&recipient), transfer_amount); | ||
} | ||
|
||
// Benchmark `set_balance` coming from ROOT account. This always creates an account. | ||
set_balance_creating { | ||
let user: T::AccountId = account("user", 0, SEED); | ||
let user_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(user.clone()); | ||
|
||
// Give the user some initial balance. | ||
let existential_deposit = T::ExistentialDeposit::get(); | ||
let balance_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); | ||
let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&user, balance_amount); | ||
}: set_balance(RawOrigin::Root, user_lookup, balance_amount, balance_amount) | ||
verify { | ||
assert_eq!(Balances::<T>::free_balance(&user), balance_amount); | ||
assert_eq!(Balances::<T>::reserved_balance(&user), balance_amount); | ||
} | ||
|
||
// Benchmark `set_balance` coming from ROOT account. This always kills an account. | ||
set_balance_killing { | ||
let user: T::AccountId = account("user", 0, SEED); | ||
let user_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(user.clone()); | ||
|
||
// Give the user some initial balance. | ||
let existential_deposit = T::ExistentialDeposit::get(); | ||
let balance_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); | ||
let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&user, balance_amount); | ||
}: set_balance(RawOrigin::Root, user_lookup, Zero::zero(), Zero::zero()) | ||
verify { | ||
assert!(Balances::<T>::free_balance(&user).is_zero()); | ||
} | ||
|
||
// Benchmark `force_transfer` extrinsic with the worst possible conditions: | ||
// * Transfer will kill the sender account. | ||
// * Transfer will create the recipient account. | ||
force_transfer { | ||
let existential_deposit = T::ExistentialDeposit::get(); | ||
let source: T::AccountId = account("source", 0, SEED); | ||
let source_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(source.clone()); | ||
|
||
// Give some multiple of the existential deposit + creation fee + transfer fee | ||
let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); | ||
let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&source, balance); | ||
|
||
// Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account, and reap this user. | ||
let recipient: T::AccountId = account("recipient", 0, SEED); | ||
let recipient_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(recipient.clone()); | ||
let transfer_amount = existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into(); | ||
}: force_transfer(RawOrigin::Root, source_lookup, recipient_lookup, transfer_amount) | ||
verify { | ||
assert_eq!(Balances::<T>::free_balance(&source), Zero::zero()); | ||
assert_eq!(Balances::<T>::free_balance(&recipient), transfer_amount); | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use crate::tests_composite::{ExtBuilder, Test}; | ||
use frame_support::assert_ok; | ||
|
||
#[test] | ||
fn transfer() { | ||
ExtBuilder::default().build().execute_with(|| { | ||
assert_ok!(test_benchmark_transfer::<Test>()); | ||
}); | ||
} | ||
|
||
#[test] | ||
fn transfer_best_case() { | ||
ExtBuilder::default().build().execute_with(|| { | ||
assert_ok!(test_benchmark_transfer_best_case::<Test>()); | ||
}); | ||
} | ||
|
||
#[test] | ||
fn transfer_keep_alive() { | ||
ExtBuilder::default().build().execute_with(|| { | ||
assert_ok!(test_benchmark_transfer_keep_alive::<Test>()); | ||
}); | ||
} | ||
|
||
#[test] | ||
fn transfer_set_balance_creating() { | ||
ExtBuilder::default().build().execute_with(|| { | ||
assert_ok!(test_benchmark_set_balance_creating::<Test>()); | ||
}); | ||
} | ||
|
||
#[test] | ||
fn transfer_set_balance_killing() { | ||
ExtBuilder::default().build().execute_with(|| { | ||
assert_ok!(test_benchmark_set_balance_killing::<Test>()); | ||
}); | ||
} | ||
|
||
#[test] | ||
fn force_transfer() { | ||
ExtBuilder::default().build().execute_with(|| { | ||
assert_ok!(test_benchmark_force_transfer::<Test>()); | ||
}); | ||
} | ||
} |
Oops, something went wrong.