Deprecate Currency; introduce holds and freezing into fungible traits (#12951)

* First reworking of fungibles API

* New API and docs

* More fungible::* API improvements

* New ref-counting logic for old API

* Missing files

* Fixes

* Use the new transfer logic

* Use fungibles for the dispatchables

* Use shelve/restore names

* Locking works with total balance.

* repotting and removal

* Separate Holds from Reserves

* Introduce freezes

* Missing files

* Tests for freezing

* Fix hold+freeze combo

* More tests

* Fee-free dispatchable for upgrading accounts

* Benchmarks and a few fixes

* Another test

* Docs and refactor to avoid blanket impls

* Repot

* Fit out ItemOf fully

* Add events to Balanced traits

* Introduced events into Hold traits

* Fix Assets pallet tests

* Assets benchmarks pass

* Missing files and fixes

* Fixes

* Fixes

* Benchmarks fixes

* Fix balance benchmarks

* Formatting

* Expose fungible sub modules

* Move NIS to fungible API

* Fix broken impl and add test

* Fix tests

* API for `transfer_and_hold`

* Use composite APIs

* Formatting

* Upgraded event

* Fixes

* Fixes

* Fixes

* Fixes

* Repot tests and some fixed

* Fix some bits

* Fix dust tests

* Rename `set_balance`

- `Balances::set_balance` becomes `Balances::force_set_balance`
- `Unbalanced::set_balance` becomes `Unbalances::write_balance`

* becomes

* Move dust handling to fungibles API

* Formatting

* Fixes and more refactoring

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Use reducible_balance for better correctness on fees

* Reducing hold to zero should remove entry.

* Add test

* Docs

* Update frame/support/src/traits/tokens/fungibles/hold.rs

Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>

* Update frame/support/src/traits/tokens/fungibles/regular.rs

Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>

* Update frame/support/src/traits/tokens/fungible/hold.rs

Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>

* Update frame/support/src/traits/tokens/fungible/regular.rs

Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>

* Docs

* Docs

* Docs

* Fix NIS benchmarks

* Doc comment

* Remove post_mutation

* Fix some tests

* Fix some grumbles

* Enumify bool args to fungible(s) functions

* Fix up assets and balances

* Formatting

* Fix contracts

* Fix tests & benchmarks build

* Typify minted boolean arg

* Typify on_hold boolean arg; renames

* Fix numerous tests

* Fix dependency issue

* Privatize dangerous API mutate_account

* Fix contracts (@alext - please check this commit)

* Remove println

* Fix tests for contracts

* Fix broken rename

* Fix broken rename

* Fix broken rename

* Docs

* Update frame/support/src/traits/tokens/fungible/hold.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* remove from_ref_time

* Update frame/executive/src/lib.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/executive/src/lib.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Reenable test

* Update frame/support/src/traits/tokens/fungibles/hold.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungible/hold.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungible/hold.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungible/hold.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/currency.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/lottery/src/tests.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungible/mod.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungible/regular.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungibles/freeze.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungible/regular.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungibles/hold.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungibles/hold.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Update frame/support/src/traits/tokens/fungibles/hold.rs

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>

* Rename UnwantedRemoval to UnwantedAccountRemoval

* Docs

* Formatting

* Update frame/balances/src/lib.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update primitives/runtime/src/lib.rs

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* handle_raw_dust oes nothing

* Formatting

* Fixes

* Grumble

* Fixes

* Add test

* Add test

* Tests for reducible_balance

* Fixes

* Fix Salary

* Fixes

* Disable broken test

* Disable nicely

* Fixes

* Fixes

* Fixes

* Rename some events

* Fix nomination pools breakage

* Add compatibility stub for transfer tx

* Reinstate a safely compatible version of Balances set_balance

* Fixes

* Grumble

* Update frame/nis/src/lib.rs

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_balances

* disable flakey tests

* Update frame/balances/src/lib.rs

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Grumbles

* Grumble

---------

Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>
Co-authored-by: Alexander Theißen <alex.theissen@me.com>
Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: command-bot <>
This commit is contained in:
Gavin Wood
2023-03-18 14:47:55 +00:00
committed by GitHub
parent c699876ab8
commit 5d81f23f8f
129 changed files with 8370 additions and 6164 deletions
@@ -243,6 +243,10 @@ impl pallet_balances::Config for Runtime {
type ExistentialDeposit = ConstU128<EXISTENTIAL_DEPOSIT>;
type AccountStore = System;
type WeightInfo = pallet_balances::weights::SubstrateWeight<Runtime>;
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
parameter_types! {
@@ -162,7 +162,7 @@ fn prepare_benchmark(client: &FullClient) -> (usize, Vec<OpaqueExtrinsic>) {
let extrinsic: OpaqueExtrinsic = create_extrinsic(
client,
src.clone(),
BalancesCall::transfer { dest: dst.clone(), value: 1 * DOLLARS },
BalancesCall::transfer_allow_death { dest: dst.clone(), value: 1 * DOLLARS },
Some(nonce),
)
.into();
@@ -140,10 +140,9 @@ fn create_account_extrinsics(
Sr25519Keyring::Alice.pair(),
SudoCall::sudo {
call: Box::new(
BalancesCall::set_balance {
BalancesCall::force_set_balance {
who: AccountId::from(a.public()).into(),
new_free: 0,
new_reserved: 0,
}
.into(),
),
@@ -156,10 +155,9 @@ fn create_account_extrinsics(
Sr25519Keyring::Alice.pair(),
SudoCall::sudo {
call: Box::new(
BalancesCall::set_balance {
BalancesCall::force_set_balance {
who: AccountId::from(a.public()).into(),
new_free: 1_000_000 * DOLLARS,
new_reserved: 0,
}
.into(),
),
@@ -184,7 +182,7 @@ fn create_benchmark_extrinsics(
create_extrinsic(
client,
account.clone(),
BalancesCall::transfer {
BalancesCall::transfer_allow_death {
dest: Sr25519Keyring::Bob.to_account_id().into(),
value: 1 * DOLLARS,
},
+1 -1
View File
@@ -772,7 +772,7 @@ mod tests {
};
let signer = charlie.clone();
let function = RuntimeCall::Balances(BalancesCall::transfer {
let function = RuntimeCall::Balances(BalancesCall::transfer_allow_death {
dest: to.into(),
value: amount,
});
@@ -26,7 +26,9 @@ use std::{
pub mod common;
#[tokio::test]
#[allow(dead_code)]
// Apparently `#[ignore]` doesn't actually work to disable this one.
//#[tokio::test]
async fn temp_base_path_works() {
common::run_with_timeout(Duration::from_secs(60 * 10), async move {
let mut cmd = Command::new(cargo_bin("substrate"));
+1 -1
View File
@@ -167,7 +167,7 @@ fn test_blocks(
}];
block1_extrinsics.extend((0..20).map(|i| CheckedExtrinsic {
signed: Some((alice(), signed_extra(i, 0))),
function: RuntimeCall::Balances(pallet_balances::Call::transfer {
function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
dest: bob().into(),
value: 1 * DOLLARS,
}),
+35 -22
View File
@@ -89,7 +89,7 @@ fn changes_trie_block() -> (Vec<u8>, Hash) {
},
CheckedExtrinsic {
signed: Some((alice(), signed_extra(0, 0))),
function: RuntimeCall::Balances(pallet_balances::Call::transfer {
function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
dest: bob().into(),
value: 69 * DOLLARS,
}),
@@ -116,7 +116,7 @@ fn blocks() -> ((Vec<u8>, Hash), (Vec<u8>, Hash)) {
},
CheckedExtrinsic {
signed: Some((alice(), signed_extra(0, 0))),
function: RuntimeCall::Balances(pallet_balances::Call::transfer {
function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
dest: bob().into(),
value: 69 * DOLLARS,
}),
@@ -136,14 +136,14 @@ fn blocks() -> ((Vec<u8>, Hash), (Vec<u8>, Hash)) {
},
CheckedExtrinsic {
signed: Some((bob(), signed_extra(0, 0))),
function: RuntimeCall::Balances(pallet_balances::Call::transfer {
function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
dest: alice().into(),
value: 5 * DOLLARS,
}),
},
CheckedExtrinsic {
signed: Some((alice(), signed_extra(1, 0))),
function: RuntimeCall::Balances(pallet_balances::Call::transfer {
function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
dest: bob().into(),
value: 15 * DOLLARS,
}),
@@ -183,7 +183,12 @@ fn panic_execution_with_foreign_code_gives_error() {
let mut t = new_test_ext(bloaty_code_unwrap());
t.insert(
<frame_system::Account<Runtime>>::hashed_key_for(alice()),
(69u128, 0u32, 0u128, 0u128, 0u128).encode(),
AccountInfo::<<Runtime as frame_system::Config>::Index, _> {
providers: 1,
data: (69u128, 0u128, 0u128, 1u128 << 127),
..Default::default()
}
.encode(),
);
t.insert(<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(), 69_u128.encode());
t.insert(<frame_system::BlockHash<Runtime>>::hashed_key_for(0), vec![0u8; 32]);
@@ -204,9 +209,14 @@ fn bad_extrinsic_with_native_equivalent_code_gives_error() {
let mut t = new_test_ext(compact_code_unwrap());
t.insert(
<frame_system::Account<Runtime>>::hashed_key_for(alice()),
(0u32, 0u32, 0u32, 69u128, 0u128, 0u128, 0u128).encode(),
AccountInfo::<<Runtime as frame_system::Config>::Index, _> {
providers: 1,
data: (69u128, 0u128, 0u128, 1u128 << 127),
..Default::default()
}
.encode(),
);
t.insert(<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(), 69_u128.encode());
t.insert(<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(), 69u128.encode());
t.insert(<frame_system::BlockHash<Runtime>>::hashed_key_for(0), vec![0u8; 32]);
let r =
@@ -226,17 +236,18 @@ fn successful_execution_with_native_equivalent_code_gives_ok() {
t.insert(
<frame_system::Account<Runtime>>::hashed_key_for(alice()),
AccountInfo::<<Runtime as frame_system::Config>::Index, _> {
data: (111 * DOLLARS, 0u128, 0u128, 0u128),
providers: 1,
data: (111 * DOLLARS, 0u128, 0u128, 1u128 << 127),
..Default::default()
}
.encode(),
);
t.insert(
<frame_system::Account<Runtime>>::hashed_key_for(bob()),
AccountInfo::<<Runtime as frame_system::Config>::Index, _> {
data: (0 * DOLLARS, 0u128, 0u128, 0u128),
..Default::default()
}
AccountInfo::<
<Runtime as frame_system::Config>::Index,
<Runtime as frame_system::Config>::AccountData,
>::default()
.encode(),
);
t.insert(
@@ -267,17 +278,18 @@ fn successful_execution_with_foreign_code_gives_ok() {
t.insert(
<frame_system::Account<Runtime>>::hashed_key_for(alice()),
AccountInfo::<<Runtime as frame_system::Config>::Index, _> {
data: (111 * DOLLARS, 0u128, 0u128, 0u128),
providers: 1,
data: (111 * DOLLARS, 0u128, 0u128, 1u128 << 127),
..Default::default()
}
.encode(),
);
t.insert(
<frame_system::Account<Runtime>>::hashed_key_for(bob()),
AccountInfo::<<Runtime as frame_system::Config>::Index, _> {
data: (0 * DOLLARS, 0u128, 0u128, 0u128),
..Default::default()
}
AccountInfo::<
<Runtime as frame_system::Config>::Index,
<Runtime as frame_system::Config>::AccountData,
>::default()
.encode(),
);
t.insert(
@@ -784,17 +796,18 @@ fn successful_execution_gives_ok() {
t.insert(
<frame_system::Account<Runtime>>::hashed_key_for(alice()),
AccountInfo::<<Runtime as frame_system::Config>::Index, _> {
data: (111 * DOLLARS, 0u128, 0u128, 0u128),
providers: 1,
data: (111 * DOLLARS, 0u128, 0u128, 1u128 << 127),
..Default::default()
}
.encode(),
);
t.insert(
<frame_system::Account<Runtime>>::hashed_key_for(bob()),
AccountInfo::<<Runtime as frame_system::Config>::Index, _> {
data: (0 * DOLLARS, 0u128, 0u128, 0u128),
..Default::default()
}
AccountInfo::<
<Runtime as frame_system::Config>::Index,
<Runtime as frame_system::Config>::AccountData,
>::default()
.encode(),
);
t.insert(
+4 -1
View File
@@ -87,7 +87,10 @@ pub fn sign(xt: CheckedExtrinsic) -> UncheckedExtrinsic {
}
pub fn default_transfer_call() -> pallet_balances::Call<Runtime> {
pallet_balances::Call::<Runtime>::transfer { dest: bob().into(), value: 69 * DOLLARS }
pallet_balances::Call::<Runtime>::transfer_allow_death {
dest: bob().into(),
value: 69 * DOLLARS,
}
}
pub fn from_block_number(n: u32) -> Header {
+3 -3
View File
@@ -120,9 +120,9 @@ fn new_account_info(free_dollars: u128) -> Vec<u8> {
frame_system::AccountInfo {
nonce: 0u32,
consumers: 0,
providers: 0,
providers: 1,
sufficients: 0,
data: (free_dollars * DOLLARS, 0 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS),
data: (free_dollars * DOLLARS, 0 * DOLLARS, 0 * DOLLARS, 1u128 << 127),
}
.encode()
}
@@ -214,7 +214,7 @@ fn block_weight_capacity_report() {
let mut xts = (0..num_transfers)
.map(|i| CheckedExtrinsic {
signed: Some((charlie(), signed_extra(nonce + i as Index, 0))),
function: RuntimeCall::Balances(pallet_balances::Call::transfer {
function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
dest: bob().into(),
value: 0,
}),
@@ -86,7 +86,7 @@ fn should_submit_signed_transaction() {
t.execute_with(|| {
let results =
Signer::<Runtime, TestAuthorityId>::all_accounts().send_signed_transaction(|_| {
pallet_balances::Call::transfer {
pallet_balances::Call::transfer_allow_death {
dest: Alice.to_account_id().into(),
value: Default::default(),
}
@@ -123,7 +123,7 @@ fn should_submit_signed_twice_from_the_same_account() {
t.execute_with(|| {
let result =
Signer::<Runtime, TestAuthorityId>::any_account().send_signed_transaction(|_| {
pallet_balances::Call::transfer {
pallet_balances::Call::transfer_allow_death {
dest: Alice.to_account_id().into(),
value: Default::default(),
}
@@ -135,7 +135,7 @@ fn should_submit_signed_twice_from_the_same_account() {
// submit another one from the same account. The nonce should be incremented.
let result =
Signer::<Runtime, TestAuthorityId>::any_account().send_signed_transaction(|_| {
pallet_balances::Call::transfer {
pallet_balances::Call::transfer_allow_death {
dest: Alice.to_account_id().into(),
value: Default::default(),
}
@@ -174,7 +174,7 @@ fn should_submit_signed_twice_from_all_accounts() {
t.execute_with(|| {
let results = Signer::<Runtime, TestAuthorityId>::all_accounts()
.send_signed_transaction(|_| {
pallet_balances::Call::transfer { dest: Alice.to_account_id().into(), value: Default::default() }
pallet_balances::Call::transfer_allow_death { dest: Alice.to_account_id().into(), value: Default::default() }
});
let len = results.len();
@@ -185,7 +185,7 @@ fn should_submit_signed_twice_from_all_accounts() {
// submit another one from the same account. The nonce should be incremented.
let results = Signer::<Runtime, TestAuthorityId>::all_accounts()
.send_signed_transaction(|_| {
pallet_balances::Call::transfer { dest: Alice.to_account_id().into(), value: Default::default() }
pallet_balances::Call::transfer_allow_death { dest: Alice.to_account_id().into(), value: Default::default() }
});
let len = results.len();
@@ -238,7 +238,7 @@ fn submitted_transaction_should_be_valid() {
t.execute_with(|| {
let results =
Signer::<Runtime, TestAuthorityId>::all_accounts().send_signed_transaction(|_| {
pallet_balances::Call::transfer {
pallet_balances::Call::transfer_allow_death {
dest: Alice.to_account_id().into(),
value: Default::default(),
}
+2 -2
View File
@@ -24,7 +24,7 @@ use crate::{
use frame_support::{
pallet_prelude::*,
traits::{
fungibles::{Balanced, CreditOf},
fungibles::{Balanced, Credit},
Currency, OnUnbalanced,
},
};
@@ -45,7 +45,7 @@ impl OnUnbalanced<NegativeImbalance> for Author {
/// Will drop and burn the assets in case the transfer fails.
pub struct CreditToBlockAuthor;
impl HandleCredit<AccountId, Assets> for CreditToBlockAuthor {
fn handle_credit(credit: CreditOf<AccountId, Assets>) {
fn handle_credit(credit: Credit<AccountId, Assets>) {
if let Some(author) = pallet_authorship::Pallet::<Runtime>::author() {
// Drop the result which will trigger the `OnDrop` of the imbalance in case of error.
let _ = Assets::resolve(&author, credit);
+17 -4
View File
@@ -59,6 +59,7 @@ use pallet_nis::WithMaximumOf;
use pallet_session::historical as pallet_session_historical;
pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment};
use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo};
use scale_info::TypeInfo;
use sp_api::impl_runtime_apis;
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
use sp_consensus_grandpa::AuthorityId as GrandpaId;
@@ -432,6 +433,15 @@ parameter_types! {
pub const MaxReserves: u32 = 50;
}
/// A reason for placing a hold on funds.
#[derive(
Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, Debug, TypeInfo,
)]
pub enum HoldReason {
/// The NIS Pallet has reserved it for a non-fungible receipt.
Nis,
}
impl pallet_balances::Config for Runtime {
type MaxLocks = MaxLocks;
type MaxReserves = MaxReserves;
@@ -442,6 +452,10 @@ impl pallet_balances::Config for Runtime {
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = frame_system::Pallet<Runtime>;
type WeightInfo = pallet_balances::weights::SubstrateWeight<Runtime>;
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = HoldReason;
type MaxHolds = ConstU32<1>;
}
parameter_types! {
@@ -1481,7 +1495,6 @@ impl pallet_assets::Config for Runtime {
}
parameter_types! {
pub IgnoredIssuance: Balance = Treasury::pot();
pub const QueueCount: u32 = 300;
pub const MaxQueueLen: u32 = 1000;
pub const FifoQueueLen: u32 = 500;
@@ -1493,7 +1506,7 @@ parameter_types! {
pub const ThawThrottle: (Perquintill, BlockNumber) = (Perquintill::from_percent(25), 5);
pub Target: Perquintill = Perquintill::zero();
pub const NisPalletId: PalletId = PalletId(*b"py/nis ");
pub const NisReserveId: [u8; 8] = *b"py/nis ";
pub const NisHoldReason: HoldReason = HoldReason::Nis;
}
impl pallet_nis::Config for Runtime {
@@ -1505,7 +1518,7 @@ impl pallet_nis::Config for Runtime {
type Counterpart = ItemOf<Assets, ConstU32<9u32>, AccountId>;
type CounterpartAmount = WithMaximumOf<ConstU128<21_000_000_000_000_000_000u128>>;
type Deficit = ();
type IgnoredIssuance = IgnoredIssuance;
type IgnoredIssuance = ();
type Target = Target;
type PalletId = NisPalletId;
type QueueCount = QueueCount;
@@ -1517,7 +1530,7 @@ impl pallet_nis::Config for Runtime {
type IntakePeriod = IntakePeriod;
type MaxIntakeWeight = MaxIntakeWeight;
type ThawThrottle = ThawThrottle;
type ReserveId = NisReserveId;
type HoldReason = NisHoldReason;
}
parameter_types! {
+1 -1
View File
@@ -308,7 +308,7 @@ impl<'a> Iterator for BlockContentIterator<'a> {
value: kitchensink_runtime::ExistentialDeposit::get() + 1,
}),
BlockType::RandomTransfersReaping => {
RuntimeCall::Balances(BalancesCall::transfer {
RuntimeCall::Balances(BalancesCall::transfer_allow_death {
dest: sp_runtime::MultiAddress::Id(receiver),
// Transfer so that ending balance would be 1 less than existential
// deposit so that we kill the sender account.
+4
View File
@@ -85,6 +85,10 @@ impl pallet_balances::Config for Test {
type MaxLocks = MaxLocks;
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
const MOTION_DURATION_IN_BLOCKS: BlockNumber = 3;
+6 -2
View File
@@ -105,14 +105,18 @@ fn add_sufficients<T: Config<I>, I: 'static>(minter: T::AccountId, n: u32) {
fn add_approvals<T: Config<I>, I: 'static>(minter: T::AccountId, n: u32) {
let asset_id = default_asset_id::<T, I>();
T::Currency::deposit_creating(&minter, T::ApprovalDeposit::get() * n.into());
T::Currency::deposit_creating(
&minter,
T::ApprovalDeposit::get() * n.into() + T::Currency::minimum_balance(),
);
let minter_lookup = T::Lookup::unlookup(minter.clone());
let origin = SystemOrigin::Signed(minter);
Assets::<T, I>::mint(origin.clone().into(), asset_id, minter_lookup, (100 * (n + 1)).into())
.unwrap();
let enough = T::Currency::minimum_balance();
for i in 0..n {
let target = account("approval", i, SEED);
T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance());
T::Currency::make_free_balance_be(&target, enough);
let target_lookup = T::Lookup::unlookup(target);
Assets::<T, I>::approve_transfer(
origin.clone().into(),
+11 -4
View File
@@ -76,7 +76,14 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
d.sufficients += 1;
ExistenceReason::Sufficient
} else {
frame_system::Pallet::<T>::inc_consumers(who).map_err(|_| Error::<T, I>::NoProvider)?;
frame_system::Pallet::<T>::inc_consumers(who)
.map_err(|_| Error::<T, I>::UnavailableConsumer)?;
// We ensure that we can still increment consumers once more because we could otherwise
// allow accidental usage of all consumer references which could cause grief.
if !frame_system::Pallet::<T>::can_inc_consumer(who) {
frame_system::Pallet::<T>::dec_consumers(who);
return Err(Error::<T, I>::UnavailableConsumer.into())
}
ExistenceReason::Consumer
};
d.accounts = accounts;
@@ -165,7 +172,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
}
let account = match Account::<T, I>::get(id, who) {
Some(a) => a,
None => return NoFunds,
None => return BalanceLow,
};
if account.is_frozen {
return Frozen
@@ -193,7 +200,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
Success
}
} else {
NoFunds
BalanceLow
}
}
@@ -254,7 +261,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
ensure!(f.best_effort || actual >= amount, Error::<T, I>::BalanceLow);
let conseq = Self::can_decrease(id, target, actual, f.keep_alive);
let actual = match conseq.into_result() {
let actual = match conseq.into_result(f.keep_alive) {
Ok(dust) => actual.saturating_add(dust), //< guaranteed by reducible_balance
Err(e) => {
debug_assert!(false, "passed from reducible_balance; qed");
+45 -81
View File
@@ -17,6 +17,16 @@
//! Implementations for fungibles trait.
use frame_support::{
defensive,
traits::tokens::{
Fortitude,
Precision::{self, BestEffort},
Preservation::{self, Expendable},
Provenance::{self, Minted},
},
};
use super::*;
impl<T: Config<I>, I: 'static> fungibles::Inspect<<T as SystemConfig>::AccountId> for Pallet<T, I> {
@@ -35,21 +45,27 @@ impl<T: Config<I>, I: 'static> fungibles::Inspect<<T as SystemConfig>::AccountId
Pallet::<T, I>::balance(asset, who)
}
fn total_balance(asset: Self::AssetId, who: &<T as SystemConfig>::AccountId) -> Self::Balance {
Pallet::<T, I>::balance(asset, who)
}
fn reducible_balance(
asset: Self::AssetId,
who: &<T as SystemConfig>::AccountId,
keep_alive: bool,
preservation: Preservation,
_: Fortitude,
) -> Self::Balance {
Pallet::<T, I>::reducible_balance(asset, who, keep_alive).unwrap_or(Zero::zero())
Pallet::<T, I>::reducible_balance(asset, who, !matches!(preservation, Expendable))
.unwrap_or(Zero::zero())
}
fn can_deposit(
asset: Self::AssetId,
who: &<T as SystemConfig>::AccountId,
amount: Self::Balance,
mint: bool,
provenance: Provenance,
) -> DepositConsequence {
Pallet::<T, I>::can_increase(asset, who, amount, mint)
Pallet::<T, I>::can_increase(asset, who, amount, provenance == Minted)
}
fn can_withdraw(
@@ -65,69 +81,26 @@ impl<T: Config<I>, I: 'static> fungibles::Inspect<<T as SystemConfig>::AccountId
}
}
impl<T: Config<I>, I: 'static> fungibles::InspectMetadata<<T as SystemConfig>::AccountId>
impl<T: Config<I>, I: 'static> fungibles::Mutate<<T as SystemConfig>::AccountId> for Pallet<T, I> {}
impl<T: Config<I>, I: 'static> fungibles::Balanced<<T as SystemConfig>::AccountId>
for Pallet<T, I>
{
/// Return the name of an asset.
fn name(asset: &Self::AssetId) -> Vec<u8> {
Metadata::<T, I>::get(asset).name.to_vec()
}
/// Return the symbol of an asset.
fn symbol(asset: &Self::AssetId) -> Vec<u8> {
Metadata::<T, I>::get(asset).symbol.to_vec()
}
/// Return the decimals of an asset.
fn decimals(asset: &Self::AssetId) -> u8 {
Metadata::<T, I>::get(asset).decimals
}
}
impl<T: Config<I>, I: 'static> fungibles::Mutate<<T as SystemConfig>::AccountId> for Pallet<T, I> {
fn mint_into(
asset: Self::AssetId,
who: &<T as SystemConfig>::AccountId,
amount: Self::Balance,
) -> DispatchResult {
Self::do_mint(asset, who, amount, None)
}
fn burn_from(
asset: Self::AssetId,
who: &<T as SystemConfig>::AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
let f = DebitFlags { keep_alive: false, best_effort: false };
Self::do_burn(asset, who, amount, None, f)
}
fn slash(
asset: Self::AssetId,
who: &<T as SystemConfig>::AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
let f = DebitFlags { keep_alive: false, best_effort: true };
Self::do_burn(asset, who, amount, None, f)
}
}
impl<T: Config<I>, I: 'static> fungibles::Transfer<T::AccountId> for Pallet<T, I> {
fn transfer(
asset: Self::AssetId,
source: &T::AccountId,
dest: &T::AccountId,
amount: T::Balance,
keep_alive: bool,
) -> Result<T::Balance, DispatchError> {
let f = TransferFlags { keep_alive, best_effort: false, burn_dust: false };
Self::do_transfer(asset, source, dest, amount, None, f)
}
type OnDropCredit = fungibles::DecreaseIssuance<T::AccountId, Self>;
type OnDropDebt = fungibles::IncreaseIssuance<T::AccountId, Self>;
}
impl<T: Config<I>, I: 'static> fungibles::Unbalanced<T::AccountId> for Pallet<T, I> {
fn set_balance(_: Self::AssetId, _: &T::AccountId, _: Self::Balance) -> DispatchResult {
unreachable!("set_balance is not used if other functions are impl'd");
fn handle_raw_dust(_: Self::AssetId, _: Self::Balance) {}
fn handle_dust(_: fungibles::Dust<T::AccountId, Self>) {
defensive!("`decrease_balance` and `increase_balance` have non-default impls; nothing else calls this; qed");
}
fn write_balance(
_: Self::AssetId,
_: &T::AccountId,
_: Self::Balance,
) -> Result<Option<Self::Balance>, DispatchError> {
defensive!("write_balance is not used if other functions are impl'd");
Err(DispatchError::Unavailable)
}
fn set_total_issuance(id: T::AssetId, amount: Self::Balance) {
Asset::<T, I>::mutate_exists(id, |maybe_asset| {
@@ -140,36 +113,27 @@ impl<T: Config<I>, I: 'static> fungibles::Unbalanced<T::AccountId> for Pallet<T,
asset: T::AssetId,
who: &T::AccountId,
amount: Self::Balance,
precision: Precision,
preservation: Preservation,
_: Fortitude,
) -> Result<Self::Balance, DispatchError> {
let f = DebitFlags { keep_alive: false, best_effort: false };
let f = DebitFlags {
keep_alive: preservation != Expendable,
best_effort: precision == BestEffort,
};
Self::decrease_balance(asset, who, amount, f, |_, _| Ok(()))
}
fn decrease_balance_at_most(
asset: T::AssetId,
who: &T::AccountId,
amount: Self::Balance,
) -> Self::Balance {
let f = DebitFlags { keep_alive: false, best_effort: true };
Self::decrease_balance(asset, who, amount, f, |_, _| Ok(())).unwrap_or(Zero::zero())
}
fn increase_balance(
asset: T::AssetId,
who: &T::AccountId,
amount: Self::Balance,
_: Precision,
) -> Result<Self::Balance, DispatchError> {
Self::increase_balance(asset, who, amount, |_| Ok(()))?;
Ok(amount)
}
fn increase_balance_at_most(
asset: T::AssetId,
who: &T::AccountId,
amount: Self::Balance,
) -> Self::Balance {
match Self::increase_balance(asset, who, amount, |_| Ok(())) {
Ok(()) => amount,
Err(_) => Zero::zero(),
}
}
// TODO: #13196 implement deactivate/reactivate once we have inactive balance tracking.
}
impl<T: Config<I>, I: 'static> fungibles::Create<T::AccountId> for Pallet<T, I> {
+3 -3
View File
@@ -540,9 +540,9 @@ pub mod pallet {
/// Minimum balance should be non-zero.
MinBalanceZero,
/// Unable to increment the consumer reference counters on the account. Either no provider
/// reference exists to allow a non-zero balance of a non-self-sufficient asset, or the
/// maximum number of consumers has been reached.
NoProvider,
/// reference exists to allow a non-zero balance of a non-self-sufficient asset, or one
/// fewer then the maximum number of consumers has been reached.
UnavailableConsumer,
/// Invalid metadata given.
BadMetadata,
/// No approval exists that would allow the transfer.
+5 -1
View File
@@ -74,7 +74,7 @@ impl frame_system::Config for Test {
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = ConstU32<2>;
type MaxConsumers = ConstU32<3>;
}
impl pallet_balances::Config for Test {
@@ -87,6 +87,10 @@ impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type HoldIdentifier = ();
type FreezeIdentifier = ();
type MaxHolds = ();
type MaxFreezes = ();
}
pub struct AssetsCallbackHandle;
+18 -12
View File
@@ -57,7 +57,10 @@ fn minting_too_many_insufficient_assets_fails() {
Balances::make_free_balance_be(&1, 100);
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 1, 1, 100));
assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100), TokenError::CannotCreate);
assert_noop!(
Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100),
Error::<Test>::UnavailableConsumer
);
Balances::make_free_balance_be(&2, 1);
assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 100));
@@ -75,7 +78,10 @@ fn minting_insufficient_asset_with_deposit_should_work_when_consumers_exhausted(
Balances::make_free_balance_be(&1, 100);
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 1, 1, 100));
assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100), TokenError::CannotCreate);
assert_noop!(
Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100),
Error::<Test>::UnavailableConsumer
);
assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 2));
assert_eq!(Balances::reserved_balance(&1), 10);
@@ -93,7 +99,7 @@ fn minting_insufficient_assets_with_deposit_without_consumer_should_work() {
assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0));
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
assert_eq!(Balances::reserved_balance(&1), 10);
assert_eq!(System::consumers(&1), 0);
assert_eq!(System::consumers(&1), 1);
});
}
@@ -167,7 +173,7 @@ fn approval_lifecycle_works() {
// so we create it :)
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
Balances::make_free_balance_be(&1, 1);
Balances::make_free_balance_be(&1, 2);
assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50));
assert_eq!(Asset::<Test>::get(0).unwrap().approvals, 1);
assert_eq!(Balances::reserved_balance(&1), 1);
@@ -193,7 +199,7 @@ fn transfer_approved_all_funds() {
// so we create it :)
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
Balances::make_free_balance_be(&1, 1);
Balances::make_free_balance_be(&1, 2);
assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50));
assert_eq!(Asset::<Test>::get(0).unwrap().approvals, 1);
assert_eq!(Balances::reserved_balance(&1), 1);
@@ -215,7 +221,7 @@ fn approval_deposits_work() {
let e = BalancesError::<Test>::InsufficientBalance;
assert_noop!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50), e);
Balances::make_free_balance_be(&1, 1);
Balances::make_free_balance_be(&1, 2);
assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50));
assert_eq!(Balances::reserved_balance(&1), 1);
@@ -233,7 +239,7 @@ fn cannot_transfer_more_than_approved() {
new_test_ext().execute_with(|| {
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
Balances::make_free_balance_be(&1, 1);
Balances::make_free_balance_be(&1, 2);
assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50));
let e = Error::<Test>::Unapproved;
assert_noop!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 51), e);
@@ -245,7 +251,7 @@ fn cannot_transfer_more_than_exists() {
new_test_ext().execute_with(|| {
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
Balances::make_free_balance_be(&1, 1);
Balances::make_free_balance_be(&1, 2);
assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 101));
let e = Error::<Test>::BalanceLow;
assert_noop!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 101), e);
@@ -257,7 +263,7 @@ fn cancel_approval_works() {
new_test_ext().execute_with(|| {
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
Balances::make_free_balance_be(&1, 1);
Balances::make_free_balance_be(&1, 2);
assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50));
assert_eq!(Asset::<Test>::get(0).unwrap().approvals, 1);
assert_noop!(
@@ -287,7 +293,7 @@ fn force_cancel_approval_works() {
new_test_ext().execute_with(|| {
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
Balances::make_free_balance_be(&1, 1);
Balances::make_free_balance_be(&1, 2);
assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50));
assert_eq!(Asset::<Test>::get(0).unwrap().approvals, 1);
let e = Error::<Test>::NoPermission;
@@ -516,7 +522,7 @@ fn min_balance_should_work() {
// Death by `transfer_approved`.
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
Balances::make_free_balance_be(&1, 1);
Balances::make_free_balance_be(&1, 2);
assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 100));
assert_ok!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 91));
assert_eq!(take_hooks(), vec![Hook::Died(0, 1)]);
@@ -1217,7 +1223,7 @@ fn querying_allowance_should_work() {
use frame_support::traits::tokens::fungibles::approvals::{Inspect, Mutate};
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
Balances::make_free_balance_be(&1, 1);
Balances::make_free_balance_be(&1, 2);
assert_ok!(Assets::approve(0, &1, &2, 50));
assert_eq!(Assets::allowance(0, &1, &2), 50);
// Transfer asset 0, from owner 1 and delegate 2 to destination 3
+4
View File
@@ -62,6 +62,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
impl Config for Test {
+4
View File
@@ -144,6 +144,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU128<1>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
pallet_staking_reward_curve::build! {
+1 -1
View File
@@ -70,7 +70,7 @@ given account is unused.
### 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.
- `force_set_balance` - Set the balances of a given account. The origin of this call must be root.
## Usage
+56 -18
View File
@@ -24,6 +24,8 @@ use crate::Pallet as Balances;
use frame_benchmarking::v2::*;
use frame_system::RawOrigin;
use sp_runtime::traits::Bounded;
use types::ExtraFlags;
const SEED: u32 = 0;
// existential deposit multiplier
@@ -37,7 +39,7 @@ mod benchmarks {
// * Transfer will kill the sender account.
// * Transfer will create the recipient account.
#[benchmark]
fn transfer() {
fn transfer_allow_death() {
let existential_deposit = T::ExistentialDeposit::get();
let caller = whitelisted_caller();
@@ -79,7 +81,7 @@ mod benchmarks {
let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
#[extrinsic_call]
transfer(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
transfer_allow_death(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
assert!(!Balances::<T, I>::free_balance(&caller).is_zero());
assert!(!Balances::<T, I>::free_balance(&recipient).is_zero());
@@ -106,9 +108,9 @@ mod benchmarks {
assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount);
}
// Benchmark `set_balance` coming from ROOT account. This always creates an account.
// Benchmark `force_set_balance` coming from ROOT account. This always creates an account.
#[benchmark]
fn set_balance_creating() {
fn force_set_balance_creating() {
let user: T::AccountId = account("user", 0, SEED);
let user_lookup = T::Lookup::unlookup(user.clone());
@@ -118,15 +120,14 @@ mod benchmarks {
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&user, balance_amount);
#[extrinsic_call]
set_balance(RawOrigin::Root, user_lookup, balance_amount, balance_amount);
force_set_balance(RawOrigin::Root, user_lookup, balance_amount);
assert_eq!(Balances::<T, I>::free_balance(&user), balance_amount);
assert_eq!(Balances::<T, I>::reserved_balance(&user), balance_amount);
}
// Benchmark `set_balance` coming from ROOT account. This always kills an account.
// Benchmark `force_set_balance` coming from ROOT account. This always kills an account.
#[benchmark]
fn set_balance_killing() {
fn force_set_balance_killing() {
let user: T::AccountId = account("user", 0, SEED);
let user_lookup = T::Lookup::unlookup(user.clone());
@@ -136,7 +137,7 @@ mod benchmarks {
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&user, balance_amount);
#[extrinsic_call]
set_balance(RawOrigin::Root, user_lookup, Zero::zero(), Zero::zero());
force_set_balance(RawOrigin::Root, user_lookup, Zero::zero());
assert!(Balances::<T, I>::free_balance(&user).is_zero());
}
@@ -197,7 +198,7 @@ mod benchmarks {
}
#[extrinsic_call]
transfer(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
transfer_allow_death(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
assert_eq!(Balances::<T, I>::free_balance(&caller), Zero::zero());
assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount);
@@ -230,27 +231,64 @@ mod benchmarks {
let user_lookup = T::Lookup::unlookup(user.clone());
// Give some multiple of the existential deposit
let existential_deposit = T::ExistentialDeposit::get();
let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
let ed = T::ExistentialDeposit::get();
let balance = ed + ed;
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&user, balance);
// Reserve the balance
<Balances<T, I> as ReservableCurrency<_>>::reserve(&user, balance)?;
assert_eq!(Balances::<T, I>::reserved_balance(&user), balance);
assert!(Balances::<T, I>::free_balance(&user).is_zero());
<Balances<T, I> as ReservableCurrency<_>>::reserve(&user, ed)?;
assert_eq!(Balances::<T, I>::reserved_balance(&user), ed);
assert_eq!(Balances::<T, I>::free_balance(&user), ed);
#[extrinsic_call]
_(RawOrigin::Root, user_lookup, balance);
assert!(Balances::<T, I>::reserved_balance(&user).is_zero());
assert_eq!(Balances::<T, I>::free_balance(&user), balance);
assert_eq!(Balances::<T, I>::free_balance(&user), ed + ed);
Ok(())
}
#[benchmark]
fn upgrade_accounts(u: Linear<1, 1_000>) {
let caller: T::AccountId = whitelisted_caller();
let who = (0..u)
.into_iter()
.map(|i| -> T::AccountId {
let user = account("old_user", i, SEED);
let account = AccountData {
free: T::ExistentialDeposit::get(),
reserved: T::ExistentialDeposit::get(),
frozen: Zero::zero(),
flags: ExtraFlags::old_logic(),
};
frame_system::Pallet::<T>::inc_providers(&user);
assert!(T::AccountStore::try_mutate_exists(&user, |a| -> DispatchResult {
*a = Some(account);
Ok(())
})
.is_ok());
assert!(!Balances::<T, I>::account(&user).flags.is_new_logic());
assert_eq!(frame_system::Pallet::<T>::providers(&user), 1);
assert_eq!(frame_system::Pallet::<T>::consumers(&user), 0);
user
})
.collect();
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), who);
for i in 0..u {
let user: T::AccountId = account("old_user", i, SEED);
assert!(Balances::<T, I>::account(&user).flags.is_new_logic());
assert_eq!(frame_system::Pallet::<T>::providers(&user), 1);
assert_eq!(frame_system::Pallet::<T>::consumers(&user), 1);
}
}
impl_benchmark_test_suite! {
Balances,
crate::tests_composite::ExtBuilder::default().build(),
crate::tests_composite::Test,
crate::tests::ExtBuilder::default().build(),
crate::tests::Test,
}
}
@@ -0,0 +1,898 @@
// This file is part of Substrate.
// Copyright (C) 2017-2022 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.
//! Implementations for the `Currency` family of traits.
use super::*;
use frame_support::{
ensure,
pallet_prelude::DispatchResult,
traits::{
tokens::{fungible, BalanceStatus as Status},
Currency, DefensiveSaturating, ExistenceRequirement,
ExistenceRequirement::AllowDeath,
Get, Imbalance, LockIdentifier, LockableCurrency, NamedReservableCurrency,
ReservableCurrency, SignedImbalance, TryDrop, WithdrawReasons,
},
};
pub use imbalances::{NegativeImbalance, PositiveImbalance};
// wrapping these imbalances in a private module is necessary to ensure absolute privacy
// of the inner member.
mod imbalances {
use super::{result, Config, Imbalance, RuntimeDebug, Saturating, TryDrop, Zero};
use frame_support::traits::SameOrOther;
use sp_std::mem;
/// Opaque, move-only struct with private fields that serves as a token denoting that
/// funds have been created without any equal and opposite accounting.
#[must_use]
#[derive(RuntimeDebug, PartialEq, Eq)]
pub struct PositiveImbalance<T: Config<I>, I: 'static = ()>(T::Balance);
impl<T: Config<I>, I: 'static> PositiveImbalance<T, I> {
/// Create a new positive imbalance from a balance.
pub fn new(amount: T::Balance) -> Self {
PositiveImbalance(amount)
}
}
/// Opaque, move-only struct with private fields that serves as a token denoting that
/// funds have been destroyed without any equal and opposite accounting.
#[must_use]
#[derive(RuntimeDebug, PartialEq, Eq)]
pub struct NegativeImbalance<T: Config<I>, I: 'static = ()>(T::Balance);
impl<T: Config<I>, I: 'static> NegativeImbalance<T, I> {
/// Create a new negative imbalance from a balance.
pub fn new(amount: T::Balance) -> Self {
NegativeImbalance(amount)
}
}
impl<T: Config<I>, I: 'static> TryDrop for PositiveImbalance<T, I> {
fn try_drop(self) -> result::Result<(), Self> {
self.drop_zero()
}
}
impl<T: Config<I>, I: 'static> Default for PositiveImbalance<T, I> {
fn default() -> Self {
Self::zero()
}
}
impl<T: Config<I>, I: 'static> Imbalance<T::Balance> for PositiveImbalance<T, I> {
type Opposite = NegativeImbalance<T, I>;
fn zero() -> Self {
Self(Zero::zero())
}
fn drop_zero(self) -> result::Result<(), Self> {
if self.0.is_zero() {
Ok(())
} else {
Err(self)
}
}
fn split(self, amount: T::Balance) -> (Self, Self) {
let first = self.0.min(amount);
let second = self.0 - first;
mem::forget(self);
(Self(first), Self(second))
}
fn merge(mut self, other: Self) -> Self {
self.0 = self.0.saturating_add(other.0);
mem::forget(other);
self
}
fn subsume(&mut self, other: Self) {
self.0 = self.0.saturating_add(other.0);
mem::forget(other);
}
fn offset(self, other: Self::Opposite) -> SameOrOther<Self, Self::Opposite> {
let (a, b) = (self.0, other.0);
mem::forget((self, other));
if a > b {
SameOrOther::Same(Self(a - b))
} else if b > a {
SameOrOther::Other(NegativeImbalance::new(b - a))
} else {
SameOrOther::None
}
}
fn peek(&self) -> T::Balance {
self.0
}
}
impl<T: Config<I>, I: 'static> TryDrop for NegativeImbalance<T, I> {
fn try_drop(self) -> result::Result<(), Self> {
self.drop_zero()
}
}
impl<T: Config<I>, I: 'static> Default for NegativeImbalance<T, I> {
fn default() -> Self {
Self::zero()
}
}
impl<T: Config<I>, I: 'static> Imbalance<T::Balance> for NegativeImbalance<T, I> {
type Opposite = PositiveImbalance<T, I>;
fn zero() -> Self {
Self(Zero::zero())
}
fn drop_zero(self) -> result::Result<(), Self> {
if self.0.is_zero() {
Ok(())
} else {
Err(self)
}
}
fn split(self, amount: T::Balance) -> (Self, Self) {
let first = self.0.min(amount);
let second = self.0 - first;
mem::forget(self);
(Self(first), Self(second))
}
fn merge(mut self, other: Self) -> Self {
self.0 = self.0.saturating_add(other.0);
mem::forget(other);
self
}
fn subsume(&mut self, other: Self) {
self.0 = self.0.saturating_add(other.0);
mem::forget(other);
}
fn offset(self, other: Self::Opposite) -> SameOrOther<Self, Self::Opposite> {
let (a, b) = (self.0, other.0);
mem::forget((self, other));
if a > b {
SameOrOther::Same(Self(a - b))
} else if b > a {
SameOrOther::Other(PositiveImbalance::new(b - a))
} else {
SameOrOther::None
}
}
fn peek(&self) -> T::Balance {
self.0
}
}
impl<T: Config<I>, I: 'static> Drop for PositiveImbalance<T, I> {
/// Basic drop handler will just square up the total issuance.
fn drop(&mut self) {
<super::TotalIssuance<T, I>>::mutate(|v| *v = v.saturating_add(self.0));
}
}
impl<T: Config<I>, I: 'static> Drop for NegativeImbalance<T, I> {
/// Basic drop handler will just square up the total issuance.
fn drop(&mut self) {
<super::TotalIssuance<T, I>>::mutate(|v| *v = v.saturating_sub(self.0));
}
}
}
impl<T: Config<I>, I: 'static> Currency<T::AccountId> for Pallet<T, I>
where
T::Balance: MaybeSerializeDeserialize + Debug,
{
type Balance = T::Balance;
type PositiveImbalance = PositiveImbalance<T, I>;
type NegativeImbalance = NegativeImbalance<T, I>;
fn total_balance(who: &T::AccountId) -> Self::Balance {
Self::account(who).total()
}
// Check if `value` amount of free balance can be slashed from `who`.
fn can_slash(who: &T::AccountId, value: Self::Balance) -> bool {
if value.is_zero() {
return true
}
Self::free_balance(who) >= value
}
fn total_issuance() -> Self::Balance {
TotalIssuance::<T, I>::get()
}
fn active_issuance() -> Self::Balance {
<Self as fungible::Inspect<_>>::active_issuance()
}
fn deactivate(amount: Self::Balance) {
<Self as fungible::Unbalanced<_>>::deactivate(amount);
}
fn reactivate(amount: Self::Balance) {
<Self as fungible::Unbalanced<_>>::reactivate(amount);
}
fn minimum_balance() -> Self::Balance {
T::ExistentialDeposit::get()
}
// Burn funds from the total issuance, returning a positive imbalance for the amount burned.
// Is a no-op if amount to be burned is zero.
fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance {
if amount.is_zero() {
return PositiveImbalance::zero()
}
<TotalIssuance<T, I>>::mutate(|issued| {
*issued = issued.checked_sub(&amount).unwrap_or_else(|| {
amount = *issued;
Zero::zero()
});
});
PositiveImbalance::new(amount)
}
// Create new funds into the total issuance, returning a negative imbalance
// for the amount issued.
// Is a no-op if amount to be issued it zero.
fn issue(mut amount: Self::Balance) -> Self::NegativeImbalance {
if amount.is_zero() {
return NegativeImbalance::zero()
}
<TotalIssuance<T, I>>::mutate(|issued| {
*issued = issued.checked_add(&amount).unwrap_or_else(|| {
amount = Self::Balance::max_value() - *issued;
Self::Balance::max_value()
})
});
NegativeImbalance::new(amount)
}
fn free_balance(who: &T::AccountId) -> Self::Balance {
Self::account(who).free
}
// Ensure that an account can withdraw from their free balance given any existing withdrawal
// restrictions like locks and vesting balance.
// Is a no-op if amount to be withdrawn is zero.
fn ensure_can_withdraw(
who: &T::AccountId,
amount: T::Balance,
_reasons: WithdrawReasons,
new_balance: T::Balance,
) -> DispatchResult {
if amount.is_zero() {
return Ok(())
}
ensure!(new_balance >= Self::account(who).frozen, Error::<T, I>::LiquidityRestrictions);
Ok(())
}
// Transfer some free balance from `transactor` to `dest`, respecting existence requirements.
// Is a no-op if value to be transferred is zero or the `transactor` is the same as `dest`.
fn transfer(
transactor: &T::AccountId,
dest: &T::AccountId,
value: Self::Balance,
existence_requirement: ExistenceRequirement,
) -> DispatchResult {
if value.is_zero() || transactor == dest {
return Ok(())
}
let keep_alive = match existence_requirement {
ExistenceRequirement::KeepAlive => Preserve,
ExistenceRequirement::AllowDeath => Expendable,
};
<Self as fungible::Mutate<_>>::transfer(transactor, dest, value, keep_alive)?;
Ok(())
}
/// Slash a target account `who`, returning the negative imbalance created and any left over
/// amount that could not be slashed.
///
/// Is a no-op if `value` to be slashed is zero or the account does not exist.
///
/// NOTE: `slash()` prefers free balance, but assumes that reserve balance can be drawn
/// from in extreme circumstances. `can_slash()` should be used prior to `slash()` to avoid
/// having to draw from reserved funds, however we err on the side of punishment if things are
/// inconsistent or `can_slash` wasn't used appropriately.
fn slash(who: &T::AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) {
if value.is_zero() {
return (NegativeImbalance::zero(), Zero::zero())
}
if Self::total_balance(who).is_zero() {
return (NegativeImbalance::zero(), value)
}
let result = match Self::try_mutate_account_handling_dust(
who,
|account, _is_new| -> Result<(Self::NegativeImbalance, Self::Balance), DispatchError> {
// Best value is the most amount we can slash following liveness rules.
let ed = T::ExistentialDeposit::get();
let actual = match system::Pallet::<T>::can_dec_provider(who) {
true => value.min(account.free),
false => value.min(account.free.saturating_sub(ed)),
};
account.free.saturating_reduce(actual);
let remaining = value.saturating_sub(actual);
Ok((NegativeImbalance::new(actual), remaining))
},
) {
Ok((imbalance, remaining)) => {
Self::deposit_event(Event::Slashed {
who: who.clone(),
amount: value.saturating_sub(remaining),
});
(imbalance, remaining)
},
Err(_) => (Self::NegativeImbalance::zero(), value),
};
result
}
/// Deposit some `value` into the free balance of an existing target account `who`.
///
/// Is a no-op if the `value` to be deposited is zero.
fn deposit_into_existing(
who: &T::AccountId,
value: Self::Balance,
) -> Result<Self::PositiveImbalance, DispatchError> {
if value.is_zero() {
return Ok(PositiveImbalance::zero())
}
Self::try_mutate_account_handling_dust(
who,
|account, is_new| -> Result<Self::PositiveImbalance, DispatchError> {
ensure!(!is_new, Error::<T, I>::DeadAccount);
account.free = account.free.checked_add(&value).ok_or(ArithmeticError::Overflow)?;
Self::deposit_event(Event::Deposit { who: who.clone(), amount: value });
Ok(PositiveImbalance::new(value))
},
)
}
/// Deposit some `value` into the free balance of `who`, possibly creating a new account.
///
/// This function is a no-op if:
/// - the `value` to be deposited is zero; or
/// - the `value` to be deposited is less than the required ED and the account does not yet
/// exist; or
/// - the deposit would necessitate the account to exist and there are no provider references;
/// or
/// - `value` is so large it would cause the balance of `who` to overflow.
fn deposit_creating(who: &T::AccountId, value: Self::Balance) -> Self::PositiveImbalance {
if value.is_zero() {
return Self::PositiveImbalance::zero()
}
Self::try_mutate_account_handling_dust(
who,
|account, is_new| -> Result<Self::PositiveImbalance, DispatchError> {
let ed = T::ExistentialDeposit::get();
ensure!(value >= ed || !is_new, Error::<T, I>::ExistentialDeposit);
// defensive only: overflow should never happen, however in case it does, then this
// operation is a no-op.
account.free = match account.free.checked_add(&value) {
Some(x) => x,
None => return Ok(Self::PositiveImbalance::zero()),
};
Self::deposit_event(Event::Deposit { who: who.clone(), amount: value });
Ok(PositiveImbalance::new(value))
},
)
.unwrap_or_else(|_| Self::PositiveImbalance::zero())
}
/// Withdraw some free balance from an account, respecting existence requirements.
///
/// Is a no-op if value to be withdrawn is zero.
fn withdraw(
who: &T::AccountId,
value: Self::Balance,
reasons: WithdrawReasons,
liveness: ExistenceRequirement,
) -> result::Result<Self::NegativeImbalance, DispatchError> {
if value.is_zero() {
return Ok(NegativeImbalance::zero())
}
Self::try_mutate_account_handling_dust(
who,
|account, _| -> Result<Self::NegativeImbalance, DispatchError> {
let new_free_account =
account.free.checked_sub(&value).ok_or(Error::<T, I>::InsufficientBalance)?;
// bail if we need to keep the account alive and this would kill it.
let ed = T::ExistentialDeposit::get();
let would_be_dead = new_free_account < ed;
let would_kill = would_be_dead && account.free >= ed;
ensure!(liveness == AllowDeath || !would_kill, Error::<T, I>::Expendability);
Self::ensure_can_withdraw(who, value, reasons, new_free_account)?;
account.free = new_free_account;
Self::deposit_event(Event::Withdraw { who: who.clone(), amount: value });
Ok(NegativeImbalance::new(value))
},
)
}
/// Force the new free balance of a target account `who` to some new value `balance`.
fn make_free_balance_be(
who: &T::AccountId,
value: Self::Balance,
) -> SignedImbalance<Self::Balance, Self::PositiveImbalance> {
Self::try_mutate_account_handling_dust(
who,
|account,
is_new|
-> Result<SignedImbalance<Self::Balance, Self::PositiveImbalance>, DispatchError> {
let ed = T::ExistentialDeposit::get();
// If we're attempting to set an existing account to less than ED, then
// bypass the entire operation. It's a no-op if you follow it through, but
// since this is an instance where we might account for a negative imbalance
// (in the dust cleaner of set_account) before we account for its actual
// equal and opposite cause (returned as an Imbalance), then in the
// instance that there's no other accounts on the system at all, we might
// underflow the issuance and our arithmetic will be off.
ensure!(value >= ed || !is_new, Error::<T, I>::ExistentialDeposit);
let imbalance = if account.free <= value {
SignedImbalance::Positive(PositiveImbalance::new(value - account.free))
} else {
SignedImbalance::Negative(NegativeImbalance::new(account.free - value))
};
account.free = value;
Self::deposit_event(Event::BalanceSet { who: who.clone(), free: account.free });
Ok(imbalance)
},
)
.unwrap_or_else(|_| SignedImbalance::Positive(Self::PositiveImbalance::zero()))
}
}
impl<T: Config<I>, I: 'static> ReservableCurrency<T::AccountId> for Pallet<T, I>
where
T::Balance: MaybeSerializeDeserialize + Debug,
{
/// Check if `who` can reserve `value` from their free balance.
///
/// Always `true` if value to be reserved is zero.
fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool {
if value.is_zero() {
return true
}
Self::account(who).free.checked_sub(&value).map_or(false, |new_balance| {
Self::ensure_can_withdraw(who, value, WithdrawReasons::RESERVE, new_balance).is_ok()
})
}
fn reserved_balance(who: &T::AccountId) -> Self::Balance {
Self::account(who).reserved
}
/// Move `value` from the free balance from `who` to their reserved balance.
///
/// Is a no-op if value to be reserved is zero.
fn reserve(who: &T::AccountId, value: Self::Balance) -> DispatchResult {
if value.is_zero() {
return Ok(())
}
Self::try_mutate_account_handling_dust(who, |account, _| -> DispatchResult {
account.free =
account.free.checked_sub(&value).ok_or(Error::<T, I>::InsufficientBalance)?;
account.reserved =
account.reserved.checked_add(&value).ok_or(ArithmeticError::Overflow)?;
Self::ensure_can_withdraw(&who, value, WithdrawReasons::RESERVE, account.free)
})?;
Self::deposit_event(Event::Reserved { who: who.clone(), amount: value });
Ok(())
}
/// Unreserve some funds, returning any amount that was unable to be unreserved.
///
/// Is a no-op if the value to be unreserved is zero or the account does not exist.
///
/// NOTE: returns amount value which wasn't successfully unreserved.
fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance {
if value.is_zero() {
return Zero::zero()
}
if Self::total_balance(who).is_zero() {
return value
}
let actual = match Self::mutate_account_handling_dust(who, |account| {
let actual = cmp::min(account.reserved, value);
account.reserved -= actual;
// defensive only: this can never fail since total issuance which is at least
// free+reserved fits into the same data type.
account.free = account.free.defensive_saturating_add(actual);
actual
}) {
Ok(x) => x,
Err(_) => {
// This should never happen since we don't alter the total amount in the account.
// If it ever does, then we should fail gracefully though, indicating that nothing
// could be done.
return value
},
};
Self::deposit_event(Event::Unreserved { who: who.clone(), amount: actual });
value - actual
}
/// Slash from reserved balance, returning the negative imbalance created,
/// and any amount that was unable to be slashed.
///
/// Is a no-op if the value to be slashed is zero or the account does not exist.
fn slash_reserved(
who: &T::AccountId,
value: Self::Balance,
) -> (Self::NegativeImbalance, Self::Balance) {
if value.is_zero() {
return (NegativeImbalance::zero(), Zero::zero())
}
if Self::total_balance(who).is_zero() {
return (NegativeImbalance::zero(), value)
}
// NOTE: `mutate_account` may fail if it attempts to reduce the balance to the point that an
// account is attempted to be illegally destroyed.
match Self::mutate_account_handling_dust(who, |account| {
let actual = value.min(account.reserved);
account.reserved.saturating_reduce(actual);
// underflow should never happen, but it if does, there's nothing to be done here.
(NegativeImbalance::new(actual), value.saturating_sub(actual))
}) {
Ok((imbalance, not_slashed)) => {
Self::deposit_event(Event::Slashed {
who: who.clone(),
amount: value.saturating_sub(not_slashed),
});
(imbalance, not_slashed)
},
Err(_) => (Self::NegativeImbalance::zero(), value),
}
}
/// Move the reserved balance of one account into the balance of another, according to `status`.
///
/// Is a no-op if:
/// - the value to be moved is zero; or
/// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`.
fn repatriate_reserved(
slashed: &T::AccountId,
beneficiary: &T::AccountId,
value: Self::Balance,
status: Status,
) -> Result<Self::Balance, DispatchError> {
let actual = Self::do_transfer_reserved(slashed, beneficiary, value, true, status)?;
Ok(value.saturating_sub(actual))
}
}
impl<T: Config<I>, I: 'static> NamedReservableCurrency<T::AccountId> for Pallet<T, I>
where
T::Balance: MaybeSerializeDeserialize + Debug,
{
type ReserveIdentifier = T::ReserveIdentifier;
fn reserved_balance_named(id: &Self::ReserveIdentifier, who: &T::AccountId) -> Self::Balance {
let reserves = Self::reserves(who);
reserves
.binary_search_by_key(id, |data| data.id)
.map(|index| reserves[index].amount)
.unwrap_or_default()
}
/// Move `value` from the free balance from `who` to a named reserve balance.
///
/// Is a no-op if value to be reserved is zero.
fn reserve_named(
id: &Self::ReserveIdentifier,
who: &T::AccountId,
value: Self::Balance,
) -> DispatchResult {
if value.is_zero() {
return Ok(())
}
Reserves::<T, I>::try_mutate(who, |reserves| -> DispatchResult {
match reserves.binary_search_by_key(id, |data| data.id) {
Ok(index) => {
// this add can't overflow but just to be defensive.
reserves[index].amount = reserves[index].amount.defensive_saturating_add(value);
},
Err(index) => {
reserves
.try_insert(index, ReserveData { id: *id, amount: value })
.map_err(|_| Error::<T, I>::TooManyReserves)?;
},
};
<Self as ReservableCurrency<_>>::reserve(who, value)?;
Ok(())
})
}
/// Unreserve some funds, returning any amount that was unable to be unreserved.
///
/// Is a no-op if the value to be unreserved is zero.
fn unreserve_named(
id: &Self::ReserveIdentifier,
who: &T::AccountId,
value: Self::Balance,
) -> Self::Balance {
if value.is_zero() {
return Zero::zero()
}
Reserves::<T, I>::mutate_exists(who, |maybe_reserves| -> Self::Balance {
if let Some(reserves) = maybe_reserves.as_mut() {
match reserves.binary_search_by_key(id, |data| data.id) {
Ok(index) => {
let to_change = cmp::min(reserves[index].amount, value);
let remain = <Self as ReservableCurrency<_>>::unreserve(who, to_change);
// remain should always be zero but just to be defensive here.
let actual = to_change.defensive_saturating_sub(remain);
// `actual <= to_change` and `to_change <= amount`; qed;
reserves[index].amount -= actual;
if reserves[index].amount.is_zero() {
if reserves.len() == 1 {
// no more named reserves
*maybe_reserves = None;
} else {
// remove this named reserve
reserves.remove(index);
}
}
value - actual
},
Err(_) => value,
}
} else {
value
}
})
}
/// Slash from reserved balance, returning the negative imbalance created,
/// and any amount that was unable to be slashed.
///
/// Is a no-op if the value to be slashed is zero.
fn slash_reserved_named(
id: &Self::ReserveIdentifier,
who: &T::AccountId,
value: Self::Balance,
) -> (Self::NegativeImbalance, Self::Balance) {
if value.is_zero() {
return (NegativeImbalance::zero(), Zero::zero())
}
Reserves::<T, I>::mutate(who, |reserves| -> (Self::NegativeImbalance, Self::Balance) {
match reserves.binary_search_by_key(id, |data| data.id) {
Ok(index) => {
let to_change = cmp::min(reserves[index].amount, value);
let (imb, remain) =
<Self as ReservableCurrency<_>>::slash_reserved(who, to_change);
// remain should always be zero but just to be defensive here.
let actual = to_change.defensive_saturating_sub(remain);
// `actual <= to_change` and `to_change <= amount`; qed;
reserves[index].amount -= actual;
Self::deposit_event(Event::Slashed { who: who.clone(), amount: actual });
(imb, value - actual)
},
Err(_) => (NegativeImbalance::zero(), value),
}
})
}
/// Move the reserved balance of one account into the balance of another, according to `status`.
/// If `status` is `Reserved`, the balance will be reserved with given `id`.
///
/// Is a no-op if:
/// - the value to be moved is zero; or
/// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`.
fn repatriate_reserved_named(
id: &Self::ReserveIdentifier,
slashed: &T::AccountId,
beneficiary: &T::AccountId,
value: Self::Balance,
status: Status,
) -> Result<Self::Balance, DispatchError> {
if value.is_zero() {
return Ok(Zero::zero())
}
if slashed == beneficiary {
return match status {
Status::Free => Ok(Self::unreserve_named(id, slashed, value)),
Status::Reserved =>
Ok(value.saturating_sub(Self::reserved_balance_named(id, slashed))),
}
}
Reserves::<T, I>::try_mutate(slashed, |reserves| -> Result<Self::Balance, DispatchError> {
match reserves.binary_search_by_key(id, |data| data.id) {
Ok(index) => {
let to_change = cmp::min(reserves[index].amount, value);
let actual = if status == Status::Reserved {
// make it the reserved under same identifier
Reserves::<T, I>::try_mutate(
beneficiary,
|reserves| -> Result<T::Balance, DispatchError> {
match reserves.binary_search_by_key(id, |data| data.id) {
Ok(index) => {
let remain =
<Self as ReservableCurrency<_>>::repatriate_reserved(
slashed,
beneficiary,
to_change,
status,
)?;
// remain should always be zero but just to be defensive
// here.
let actual = to_change.defensive_saturating_sub(remain);
// this add can't overflow but just to be defensive.
reserves[index].amount =
reserves[index].amount.defensive_saturating_add(actual);
Ok(actual)
},
Err(index) => {
let remain =
<Self as ReservableCurrency<_>>::repatriate_reserved(
slashed,
beneficiary,
to_change,
status,
)?;
// remain should always be zero but just to be defensive
// here
let actual = to_change.defensive_saturating_sub(remain);
reserves
.try_insert(
index,
ReserveData { id: *id, amount: actual },
)
.map_err(|_| Error::<T, I>::TooManyReserves)?;
Ok(actual)
},
}
},
)?
} else {
let remain = <Self as ReservableCurrency<_>>::repatriate_reserved(
slashed,
beneficiary,
to_change,
status,
)?;
// remain should always be zero but just to be defensive here
to_change.defensive_saturating_sub(remain)
};
// `actual <= to_change` and `to_change <= amount`; qed;
reserves[index].amount -= actual;
Ok(value - actual)
},
Err(_) => Ok(value),
}
})
}
}
impl<T: Config<I>, I: 'static> LockableCurrency<T::AccountId> for Pallet<T, I>
where
T::Balance: MaybeSerializeDeserialize + Debug,
{
type Moment = T::BlockNumber;
type MaxLocks = T::MaxLocks;
// Set a lock on the balance of `who`.
// Is a no-op if lock amount is zero or `reasons` `is_none()`.
fn set_lock(
id: LockIdentifier,
who: &T::AccountId,
amount: T::Balance,
reasons: WithdrawReasons,
) {
if amount.is_zero() || reasons.is_empty() {
return
}
let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() });
let mut locks = Self::locks(who)
.into_iter()
.filter_map(|l| if l.id == id { new_lock.take() } else { Some(l) })
.collect::<Vec<_>>();
if let Some(lock) = new_lock {
locks.push(lock)
}
Self::update_locks(who, &locks[..]);
}
// Extend a lock on the balance of `who`.
// Is a no-op if lock amount is zero or `reasons` `is_none()`.
fn extend_lock(
id: LockIdentifier,
who: &T::AccountId,
amount: T::Balance,
reasons: WithdrawReasons,
) {
if amount.is_zero() || reasons.is_empty() {
return
}
let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() });
let mut locks = Self::locks(who)
.into_iter()
.filter_map(|l| {
if l.id == id {
new_lock.take().map(|nl| BalanceLock {
id: l.id,
amount: l.amount.max(nl.amount),
reasons: l.reasons | nl.reasons,
})
} else {
Some(l)
}
})
.collect::<Vec<_>>();
if let Some(lock) = new_lock {
locks.push(lock)
}
Self::update_locks(who, &locks[..]);
}
fn remove_lock(id: LockIdentifier, who: &T::AccountId) {
let mut locks = Self::locks(who);
locks.retain(|l| l.id != id);
Self::update_locks(who, &locks[..]);
}
}
@@ -0,0 +1,358 @@
// This file is part of Substrate.
// Copyright (C) 2017-2022 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.
//! Implementation of `fungible` traits for Balances pallet.
use super::*;
use frame_support::traits::tokens::{
Fortitude,
Preservation::{self, Preserve, Protect},
Provenance::{self, Minted},
};
impl<T: Config<I>, I: 'static> fungible::Inspect<T::AccountId> for Pallet<T, I> {
type Balance = T::Balance;
fn total_issuance() -> Self::Balance {
TotalIssuance::<T, I>::get()
}
fn active_issuance() -> Self::Balance {
TotalIssuance::<T, I>::get().saturating_sub(InactiveIssuance::<T, I>::get())
}
fn minimum_balance() -> Self::Balance {
T::ExistentialDeposit::get()
}
fn total_balance(who: &T::AccountId) -> Self::Balance {
Self::account(who).total()
}
fn balance(who: &T::AccountId) -> Self::Balance {
Self::account(who).free
}
fn reducible_balance(
who: &T::AccountId,
preservation: Preservation,
force: Fortitude,
) -> Self::Balance {
let a = Self::account(who);
let mut untouchable = Zero::zero();
if force == Polite {
// Frozen balance applies to total. Anything on hold therefore gets discounted from the
// limit given by the freezes.
untouchable = a.frozen.saturating_sub(a.reserved);
}
// If we want to keep our provider ref..
if preservation == Preserve
// ..or we don't want the account to die and our provider ref is needed for it to live..
|| preservation == Protect && !a.free.is_zero() &&
frame_system::Pallet::<T>::providers(who) == 1
// ..or we don't care about the account dying but our provider ref is required..
|| preservation == Expendable && !a.free.is_zero() &&
!frame_system::Pallet::<T>::can_dec_provider(who)
{
// ..then the ED needed..
untouchable = untouchable.max(T::ExistentialDeposit::get());
}
// Liquid balance is what is neither on hold nor frozen/required for provider.
a.free.saturating_sub(untouchable)
}
fn can_deposit(
who: &T::AccountId,
amount: Self::Balance,
provenance: Provenance,
) -> DepositConsequence {
if amount.is_zero() {
return DepositConsequence::Success
}
if provenance == Minted && TotalIssuance::<T, I>::get().checked_add(&amount).is_none() {
return DepositConsequence::Overflow
}
let account = Self::account(who);
let new_free = match account.free.checked_add(&amount) {
None => return DepositConsequence::Overflow,
Some(x) if x < T::ExistentialDeposit::get() => return DepositConsequence::BelowMinimum,
Some(x) => x,
};
match account.reserved.checked_add(&new_free) {
Some(_) => {},
None => return DepositConsequence::Overflow,
};
// NOTE: We assume that we are a provider, so don't need to do any checks in the
// case of account creation.
DepositConsequence::Success
}
fn can_withdraw(
who: &T::AccountId,
amount: Self::Balance,
) -> WithdrawConsequence<Self::Balance> {
if amount.is_zero() {
return WithdrawConsequence::Success
}
if TotalIssuance::<T, I>::get().checked_sub(&amount).is_none() {
return WithdrawConsequence::Underflow
}
let account = Self::account(who);
let new_free_balance = match account.free.checked_sub(&amount) {
Some(x) => x,
None => return WithdrawConsequence::BalanceLow,
};
let liquid = Self::reducible_balance(who, Expendable, Polite);
if amount > liquid {
return WithdrawConsequence::Frozen
}
// Provider restriction - total account balance cannot be reduced to zero if it cannot
// sustain the loss of a provider reference.
// NOTE: This assumes that the pallet is a provider (which is true). Is this ever changes,
// then this will need to adapt accordingly.
let ed = T::ExistentialDeposit::get();
let success = if new_free_balance < ed {
if frame_system::Pallet::<T>::can_dec_provider(who) {
WithdrawConsequence::ReducedToZero(new_free_balance)
} else {
return WithdrawConsequence::WouldDie
}
} else {
WithdrawConsequence::Success
};
let new_total_balance = new_free_balance.saturating_add(account.reserved);
// Eventual free funds must be no less than the frozen balance.
if new_total_balance < account.frozen {
return WithdrawConsequence::Frozen
}
success
}
}
impl<T: Config<I>, I: 'static> fungible::Unbalanced<T::AccountId> for Pallet<T, I> {
fn handle_dust(dust: fungible::Dust<T::AccountId, Self>) {
T::DustRemoval::on_unbalanced(dust.into_credit());
}
fn write_balance(
who: &T::AccountId,
amount: Self::Balance,
) -> Result<Option<Self::Balance>, DispatchError> {
let max_reduction =
<Self as fungible::Inspect<_>>::reducible_balance(who, Expendable, Force);
let (result, maybe_dust) = Self::mutate_account(who, |account| -> DispatchResult {
// Make sure the reduction (if there is one) is no more than the maximum allowed.
let reduction = account.free.saturating_sub(amount);
ensure!(reduction <= max_reduction, Error::<T, I>::InsufficientBalance);
account.free = amount;
Ok(())
})?;
result?;
Ok(maybe_dust)
}
fn set_total_issuance(amount: Self::Balance) {
TotalIssuance::<T, I>::mutate(|t| *t = amount);
}
fn deactivate(amount: Self::Balance) {
InactiveIssuance::<T, I>::mutate(|b| b.saturating_accrue(amount));
}
fn reactivate(amount: Self::Balance) {
InactiveIssuance::<T, I>::mutate(|b| b.saturating_reduce(amount));
}
}
impl<T: Config<I>, I: 'static> fungible::Mutate<T::AccountId> for Pallet<T, I> {
fn done_mint_into(who: &T::AccountId, amount: Self::Balance) {
Self::deposit_event(Event::<T, I>::Minted { who: who.clone(), amount });
}
fn done_burn_from(who: &T::AccountId, amount: Self::Balance) {
Self::deposit_event(Event::<T, I>::Burned { who: who.clone(), amount });
}
fn done_shelve(who: &T::AccountId, amount: Self::Balance) {
Self::deposit_event(Event::<T, I>::Suspended { who: who.clone(), amount });
}
fn done_restore(who: &T::AccountId, amount: Self::Balance) {
Self::deposit_event(Event::<T, I>::Restored { who: who.clone(), amount });
}
fn done_transfer(source: &T::AccountId, dest: &T::AccountId, amount: Self::Balance) {
Self::deposit_event(Event::<T, I>::Transfer {
from: source.clone(),
to: dest.clone(),
amount,
});
}
}
impl<T: Config<I>, I: 'static> fungible::MutateHold<T::AccountId> for Pallet<T, I> {}
impl<T: Config<I>, I: 'static> fungible::InspectHold<T::AccountId> for Pallet<T, I> {
type Reason = T::HoldIdentifier;
fn total_balance_on_hold(who: &T::AccountId) -> T::Balance {
Self::account(who).reserved
}
fn reducible_total_balance_on_hold(who: &T::AccountId, force: Fortitude) -> Self::Balance {
// The total balance must never drop below the freeze requirements if we're not forcing:
let a = Self::account(who);
let unavailable = if force == Force {
Self::Balance::zero()
} else {
// The freeze lock applies to the total balance, so we can discount the free balance
// from the amount which the total reserved balance must provide to satisfy it.
a.frozen.saturating_sub(a.free)
};
a.reserved.saturating_sub(unavailable)
}
fn balance_on_hold(reason: &Self::Reason, who: &T::AccountId) -> T::Balance {
Holds::<T, I>::get(who)
.iter()
.find(|x| &x.id == reason)
.map_or_else(Zero::zero, |x| x.amount)
}
fn hold_available(reason: &Self::Reason, who: &T::AccountId) -> bool {
if frame_system::Pallet::<T>::providers(who) == 0 {
return false
}
let holds = Holds::<T, I>::get(who);
if holds.is_full() && !holds.iter().any(|x| &x.id == reason) {
return false
}
true
}
}
impl<T: Config<I>, I: 'static> fungible::UnbalancedHold<T::AccountId> for Pallet<T, I> {
fn set_balance_on_hold(
reason: &Self::Reason,
who: &T::AccountId,
amount: Self::Balance,
) -> DispatchResult {
let mut new_account = Self::account(who);
let mut holds = Holds::<T, I>::get(who);
let mut increase = true;
let mut delta = amount;
if let Some(item) = holds.iter_mut().find(|x| &x.id == reason) {
delta = item.amount.max(amount) - item.amount.min(amount);
increase = amount > item.amount;
item.amount = amount;
holds.retain(|x| !x.amount.is_zero());
} else {
if !amount.is_zero() {
holds
.try_push(IdAmount { id: *reason, amount })
.map_err(|_| Error::<T, I>::TooManyHolds)?;
}
}
new_account.reserved = if increase {
new_account.reserved.checked_add(&delta).ok_or(ArithmeticError::Overflow)?
} else {
new_account.reserved.checked_sub(&delta).ok_or(ArithmeticError::Underflow)?
};
let (result, maybe_dust) = Self::try_mutate_account(who, |a, _| -> DispatchResult {
*a = new_account;
Ok(())
})?;
debug_assert!(
maybe_dust.is_none(),
"Does not alter main balance; dust only happens when it is altered; qed"
);
Holds::<T, I>::insert(who, holds);
Ok(result)
}
}
impl<T: Config<I>, I: 'static> fungible::InspectFreeze<T::AccountId> for Pallet<T, I> {
type Id = T::FreezeIdentifier;
fn balance_frozen(id: &Self::Id, who: &T::AccountId) -> Self::Balance {
let locks = Freezes::<T, I>::get(who);
locks.into_iter().find(|l| &l.id == id).map_or(Zero::zero(), |l| l.amount)
}
fn can_freeze(id: &Self::Id, who: &T::AccountId) -> bool {
let l = Freezes::<T, I>::get(who);
!l.is_full() || l.iter().any(|x| &x.id == id)
}
}
impl<T: Config<I>, I: 'static> fungible::MutateFreeze<T::AccountId> for Pallet<T, I> {
fn set_freeze(id: &Self::Id, who: &T::AccountId, amount: Self::Balance) -> DispatchResult {
if amount.is_zero() {
return Self::thaw(id, who)
}
let mut locks = Freezes::<T, I>::get(who);
if let Some(i) = locks.iter_mut().find(|x| &x.id == id) {
i.amount = amount;
} else {
locks
.try_push(IdAmount { id: *id, amount })
.map_err(|_| Error::<T, I>::TooManyFreezes)?;
}
Self::update_freezes(who, locks.as_bounded_slice())
}
fn extend_freeze(id: &Self::Id, who: &T::AccountId, amount: Self::Balance) -> DispatchResult {
if amount.is_zero() {
return Ok(())
}
let mut locks = Freezes::<T, I>::get(who);
if let Some(i) = locks.iter_mut().find(|x| &x.id == id) {
i.amount = i.amount.max(amount);
} else {
locks
.try_push(IdAmount { id: *id, amount })
.map_err(|_| Error::<T, I>::TooManyFreezes)?;
}
Self::update_freezes(who, locks.as_bounded_slice())
}
fn thaw(id: &Self::Id, who: &T::AccountId) -> DispatchResult {
let mut locks = Freezes::<T, I>::get(who);
locks.retain(|l| &l.id != id);
Self::update_freezes(who, locks.as_bounded_slice())
}
}
impl<T: Config<I>, I: 'static> fungible::Balanced<T::AccountId> for Pallet<T, I> {
type OnDropCredit = fungible::DecreaseIssuance<T::AccountId, Self>;
type OnDropDebt = fungible::IncreaseIssuance<T::AccountId, Self>;
fn done_deposit(who: &T::AccountId, amount: Self::Balance) {
Self::deposit_event(Event::<T, I>::Deposit { who: who.clone(), amount });
}
fn done_withdraw(who: &T::AccountId, amount: Self::Balance) {
Self::deposit_event(Event::<T, I>::Withdraw { who: who.clone(), amount });
}
fn done_issue(amount: Self::Balance) {
Self::deposit_event(Event::<T, I>::Issued { amount });
}
fn done_rescind(amount: Self::Balance) {
Self::deposit_event(Event::<T, I>::Rescinded { amount });
}
}
impl<T: Config<I>, I: 'static> fungible::BalancedHold<T::AccountId> for Pallet<T, I> {}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,224 @@
// This file is part of Substrate.
// Copyright (C) 2017-2022 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.
//! Tests regarding the functionality of the dispatchables/extrinsics.
use super::*;
use frame_support::traits::tokens::Preservation::Expendable;
use fungible::{hold::Mutate as HoldMutate, Inspect, Mutate};
#[test]
fn default_indexing_on_new_accounts_should_not_work2() {
ExtBuilder::default()
.existential_deposit(10)
.monied(true)
.build_and_execute_with(|| {
// account 5 should not exist
// ext_deposit is 10, value is 9, not satisfies for ext_deposit
assert_noop!(
Balances::transfer_allow_death(Some(1).into(), 5, 9),
TokenError::BelowMinimum,
);
assert_eq!(Balances::free_balance(1), 100);
});
}
#[test]
fn dust_account_removal_should_work() {
ExtBuilder::default()
.existential_deposit(100)
.monied(true)
.build_and_execute_with(|| {
System::inc_account_nonce(&2);
assert_eq!(System::account_nonce(&2), 1);
assert_eq!(Balances::total_balance(&2), 2000);
// index 1 (account 2) becomes zombie
assert_ok!(Balances::transfer_allow_death(Some(2).into(), 5, 1901));
assert_eq!(Balances::total_balance(&2), 0);
assert_eq!(Balances::total_balance(&5), 1901);
assert_eq!(System::account_nonce(&2), 0);
});
}
#[test]
fn balance_transfer_works() {
ExtBuilder::default().build_and_execute_with(|| {
let _ = Balances::mint_into(&1, 111);
assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 69));
assert_eq!(Balances::total_balance(&1), 42);
assert_eq!(Balances::total_balance(&2), 69);
});
}
#[test]
fn force_transfer_works() {
ExtBuilder::default().build_and_execute_with(|| {
let _ = Balances::mint_into(&1, 111);
assert_noop!(Balances::force_transfer(Some(2).into(), 1, 2, 69), BadOrigin,);
assert_ok!(Balances::force_transfer(RawOrigin::Root.into(), 1, 2, 69));
assert_eq!(Balances::total_balance(&1), 42);
assert_eq!(Balances::total_balance(&2), 69);
});
}
#[test]
fn balance_transfer_when_on_hold_should_not_work() {
ExtBuilder::default().build_and_execute_with(|| {
let _ = Balances::mint_into(&1, 111);
assert_ok!(Balances::hold(&TestId::Foo, &1, 69));
assert_noop!(
Balances::transfer_allow_death(Some(1).into(), 2, 69),
TokenError::FundsUnavailable,
);
});
}
#[test]
fn transfer_keep_alive_works() {
ExtBuilder::default().existential_deposit(1).build_and_execute_with(|| {
let _ = Balances::mint_into(&1, 100);
assert_noop!(
Balances::transfer_keep_alive(Some(1).into(), 2, 100),
TokenError::NotExpendable
);
assert_eq!(Balances::total_balance(&1), 100);
assert_eq!(Balances::total_balance(&2), 0);
});
}
#[test]
fn transfer_keep_alive_all_free_succeed() {
ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| {
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 300));
assert_ok!(Balances::hold(&TestId::Foo, &1, 100));
assert_ok!(Balances::transfer_keep_alive(Some(1).into(), 2, 100));
assert_eq!(Balances::total_balance(&1), 200);
assert_eq!(Balances::total_balance(&2), 100);
});
}
#[test]
fn transfer_all_works_1() {
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
// setup
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 200));
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0));
// transfer all and allow death
assert_ok!(Balances::transfer_all(Some(1).into(), 2, false));
assert_eq!(Balances::total_balance(&1), 0);
assert_eq!(Balances::total_balance(&2), 200);
});
}
#[test]
fn transfer_all_works_2() {
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
// setup
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 200));
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0));
// transfer all and keep alive
assert_ok!(Balances::transfer_all(Some(1).into(), 2, true));
assert_eq!(Balances::total_balance(&1), 100);
assert_eq!(Balances::total_balance(&2), 100);
});
}
#[test]
fn transfer_all_works_3() {
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
// setup
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 210));
assert_ok!(Balances::hold(&TestId::Foo, &1, 10));
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0));
// transfer all and allow death w/ reserved
assert_ok!(Balances::transfer_all(Some(1).into(), 2, false));
assert_eq!(Balances::total_balance(&1), 110);
assert_eq!(Balances::total_balance(&2), 100);
});
}
#[test]
fn transfer_all_works_4() {
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
// setup
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 210));
assert_ok!(Balances::hold(&TestId::Foo, &1, 10));
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0));
// transfer all and keep alive w/ reserved
assert_ok!(Balances::transfer_all(Some(1).into(), 2, true));
assert_eq!(Balances::total_balance(&1), 110);
assert_eq!(Balances::total_balance(&2), 100);
});
}
#[test]
fn set_balance_handles_killing_account() {
ExtBuilder::default().build_and_execute_with(|| {
let _ = Balances::mint_into(&1, 111);
assert_ok!(frame_system::Pallet::<Test>::inc_consumers(&1));
assert_noop!(
Balances::force_set_balance(RuntimeOrigin::root(), 1, 0),
DispatchError::ConsumerRemaining,
);
});
}
#[test]
fn set_balance_handles_total_issuance() {
ExtBuilder::default().build_and_execute_with(|| {
let old_total_issuance = Balances::total_issuance();
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1337, 69));
assert_eq!(Balances::total_issuance(), old_total_issuance + 69);
assert_eq!(Balances::total_balance(&1337), 69);
assert_eq!(Balances::free_balance(&1337), 69);
});
}
#[test]
fn upgrade_accounts_should_work() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
System::inc_providers(&7);
assert_ok!(<Test as Config>::AccountStore::try_mutate_exists(
&7,
|a| -> DispatchResult {
*a = Some(AccountData {
free: 5,
reserved: 5,
frozen: Zero::zero(),
flags: crate::types::ExtraFlags::old_logic(),
});
Ok(())
}
));
assert!(!Balances::account(&7).flags.is_new_logic());
assert_eq!(System::providers(&7), 1);
assert_eq!(System::consumers(&7), 0);
assert_ok!(Balances::upgrade_accounts(Some(1).into(), vec![7]));
assert!(Balances::account(&7).flags.is_new_logic());
assert_eq!(System::providers(&7), 1);
assert_eq!(System::consumers(&7), 1);
<Balances as frame_support::traits::ReservableCurrency<_>>::unreserve(&7, 5);
assert_ok!(<Balances as fungible::Mutate<_>>::transfer(&7, &1, 10, Expendable));
assert_eq!(Balances::total_balance(&7), 0);
assert_eq!(System::providers(&7), 0);
assert_eq!(System::consumers(&7), 0);
});
}
@@ -0,0 +1,399 @@
// This file is part of Substrate.
// Copyright (C) 2017-2022 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.
//! Tests regarding the functionality of the `fungible` trait set implementations.
use super::*;
use frame_support::traits::tokens::{
Fortitude::{Force, Polite},
Precision::{BestEffort, Exact},
Preservation::{Expendable, Preserve, Protect},
Restriction::Free,
};
use fungible::{Inspect, InspectFreeze, InspectHold, Mutate, MutateFreeze, MutateHold, Unbalanced};
#[test]
fn inspect_trait_reducible_balance_basic_works() {
ExtBuilder::default().existential_deposit(10).build_and_execute_with(|| {
Balances::set_balance(&1, 100);
assert_eq!(Balances::reducible_balance(&1, Expendable, Polite), 100);
assert_eq!(Balances::reducible_balance(&1, Protect, Polite), 90);
assert_eq!(Balances::reducible_balance(&1, Preserve, Polite), 90);
assert_eq!(Balances::reducible_balance(&1, Expendable, Force), 100);
assert_eq!(Balances::reducible_balance(&1, Protect, Force), 90);
assert_eq!(Balances::reducible_balance(&1, Preserve, Force), 90);
});
}
#[test]
fn inspect_trait_reducible_balance_other_provide_works() {
ExtBuilder::default().existential_deposit(10).build_and_execute_with(|| {
Balances::set_balance(&1, 100);
System::inc_providers(&1);
assert_eq!(Balances::reducible_balance(&1, Expendable, Polite), 100);
assert_eq!(Balances::reducible_balance(&1, Protect, Polite), 100);
assert_eq!(Balances::reducible_balance(&1, Preserve, Polite), 90);
assert_eq!(Balances::reducible_balance(&1, Expendable, Force), 100);
assert_eq!(Balances::reducible_balance(&1, Protect, Force), 100);
assert_eq!(Balances::reducible_balance(&1, Preserve, Force), 90);
});
}
#[test]
fn inspect_trait_reducible_balance_frozen_works() {
ExtBuilder::default().existential_deposit(10).build_and_execute_with(|| {
Balances::set_balance(&1, 100);
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 50));
assert_eq!(Balances::reducible_balance(&1, Expendable, Polite), 50);
assert_eq!(Balances::reducible_balance(&1, Protect, Polite), 50);
assert_eq!(Balances::reducible_balance(&1, Preserve, Polite), 50);
assert_eq!(Balances::reducible_balance(&1, Expendable, Force), 90);
assert_eq!(Balances::reducible_balance(&1, Protect, Force), 90);
assert_eq!(Balances::reducible_balance(&1, Preserve, Force), 90);
});
}
#[test]
fn unbalanced_trait_set_balance_works() {
ExtBuilder::default().build_and_execute_with(|| {
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 0);
assert_ok!(Balances::write_balance(&1337, 100));
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 100);
assert_ok!(<Balances as fungible::MutateHold<_>>::hold(&TestId::Foo, &1337, 60));
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 40);
assert_eq!(<Balances as fungible::InspectHold<_>>::total_balance_on_hold(&1337), 60);
assert_eq!(
<Balances as fungible::InspectHold<_>>::balance_on_hold(&TestId::Foo, &1337),
60
);
assert_noop!(Balances::write_balance(&1337, 0), Error::<Test>::InsufficientBalance);
assert_ok!(Balances::write_balance(&1337, 1));
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 1);
assert_eq!(
<Balances as fungible::InspectHold<_>>::balance_on_hold(&TestId::Foo, &1337),
60
);
assert_ok!(<Balances as fungible::MutateHold<_>>::release(&TestId::Foo, &1337, 60, Exact));
assert_eq!(<Balances as fungible::InspectHold<_>>::balance_on_hold(&TestId::Foo, &1337), 0);
assert_eq!(<Balances as fungible::InspectHold<_>>::total_balance_on_hold(&1337), 0);
});
}
#[test]
fn unbalanced_trait_set_total_issuance_works() {
ExtBuilder::default().build_and_execute_with(|| {
assert_eq!(<Balances as fungible::Inspect<_>>::total_issuance(), 0);
Balances::set_total_issuance(100);
assert_eq!(<Balances as fungible::Inspect<_>>::total_issuance(), 100);
});
}
#[test]
fn unbalanced_trait_decrease_balance_simple_works() {
ExtBuilder::default().build_and_execute_with(|| {
// An Account that starts at 100
assert_ok!(Balances::write_balance(&1337, 100));
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 100);
// and reserves 50
assert_ok!(<Balances as fungible::MutateHold<_>>::hold(&TestId::Foo, &1337, 50));
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 50);
// and is decreased by 20
assert_ok!(Balances::decrease_balance(&1337, 20, Exact, Expendable, Polite));
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 30);
});
}
#[test]
fn unbalanced_trait_decrease_balance_works_1() {
ExtBuilder::default().build_and_execute_with(|| {
assert_ok!(Balances::write_balance(&1337, 100));
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 100);
assert_noop!(
Balances::decrease_balance(&1337, 101, Exact, Expendable, Polite),
TokenError::FundsUnavailable
);
assert_eq!(Balances::decrease_balance(&1337, 100, Exact, Expendable, Polite), Ok(100));
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 0);
});
}
#[test]
fn unbalanced_trait_decrease_balance_works_2() {
ExtBuilder::default().build_and_execute_with(|| {
// free: 40, reserved: 60
assert_ok!(Balances::write_balance(&1337, 100));
assert_ok!(Balances::hold(&TestId::Foo, &1337, 60));
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 40);
assert_eq!(Balances::total_balance_on_hold(&1337), 60);
assert_noop!(
Balances::decrease_balance(&1337, 40, Exact, Expendable, Polite),
Error::<Test>::InsufficientBalance
);
assert_eq!(Balances::decrease_balance(&1337, 39, Exact, Expendable, Polite), Ok(39));
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 1);
assert_eq!(Balances::total_balance_on_hold(&1337), 60);
});
}
#[test]
fn unbalanced_trait_decrease_balance_at_most_works_1() {
ExtBuilder::default().build_and_execute_with(|| {
assert_ok!(Balances::write_balance(&1337, 100));
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 100);
assert_eq!(Balances::decrease_balance(&1337, 101, BestEffort, Expendable, Polite), Ok(100));
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 0);
});
}
#[test]
fn unbalanced_trait_decrease_balance_at_most_works_2() {
ExtBuilder::default().build_and_execute_with(|| {
assert_ok!(Balances::write_balance(&1337, 99));
assert_eq!(Balances::decrease_balance(&1337, 99, BestEffort, Expendable, Polite), Ok(99));
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 0);
});
}
#[test]
fn unbalanced_trait_decrease_balance_at_most_works_3() {
ExtBuilder::default().build_and_execute_with(|| {
// free: 40, reserved: 60
assert_ok!(Balances::write_balance(&1337, 100));
assert_ok!(Balances::hold(&TestId::Foo, &1337, 60));
assert_eq!(Balances::free_balance(1337), 40);
assert_eq!(Balances::total_balance_on_hold(&1337), 60);
assert_eq!(Balances::decrease_balance(&1337, 0, BestEffort, Expendable, Polite), Ok(0));
assert_eq!(Balances::free_balance(1337), 40);
assert_eq!(Balances::total_balance_on_hold(&1337), 60);
assert_eq!(Balances::decrease_balance(&1337, 10, BestEffort, Expendable, Polite), Ok(10));
assert_eq!(Balances::free_balance(1337), 30);
assert_eq!(Balances::decrease_balance(&1337, 200, BestEffort, Expendable, Polite), Ok(29));
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 1);
assert_eq!(Balances::free_balance(1337), 1);
assert_eq!(Balances::total_balance_on_hold(&1337), 60);
});
}
#[test]
fn unbalanced_trait_increase_balance_works() {
ExtBuilder::default().build_and_execute_with(|| {
assert_noop!(Balances::increase_balance(&1337, 0, Exact), TokenError::BelowMinimum);
assert_eq!(Balances::increase_balance(&1337, 1, Exact), Ok(1));
assert_noop!(Balances::increase_balance(&1337, u64::MAX, Exact), ArithmeticError::Overflow);
});
}
#[test]
fn unbalanced_trait_increase_balance_at_most_works() {
ExtBuilder::default().build_and_execute_with(|| {
assert_eq!(Balances::increase_balance(&1337, 0, BestEffort), Ok(0));
assert_eq!(Balances::increase_balance(&1337, 1, BestEffort), Ok(1));
assert_eq!(Balances::increase_balance(&1337, u64::MAX, BestEffort), Ok(u64::MAX - 1));
});
}
#[test]
fn freezing_and_holds_should_overlap() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10));
assert_ok!(Balances::hold(&TestId::Foo, &1, 9));
assert_eq!(Balances::account(&1).free, 1);
assert_eq!(System::consumers(&1), 1);
assert_eq!(Balances::account(&1).free, 1);
assert_eq!(Balances::account(&1).frozen, 10);
assert_eq!(Balances::account(&1).reserved, 9);
assert_eq!(Balances::total_balance_on_hold(&1), 9);
});
}
#[test]
fn frozen_hold_balance_cannot_be_moved_without_force() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10));
assert_ok!(Balances::hold(&TestId::Foo, &1, 9));
assert_eq!(Balances::reducible_total_balance_on_hold(&1, Force), 9);
assert_eq!(Balances::reducible_total_balance_on_hold(&1, Polite), 0);
let e = TokenError::Frozen;
assert_noop!(
Balances::transfer_on_hold(&TestId::Foo, &1, &2, 1, Exact, Free, Polite),
e
);
assert_ok!(Balances::transfer_on_hold(&TestId::Foo, &1, &2, 1, Exact, Free, Force));
});
}
#[test]
fn frozen_hold_balance_best_effort_transfer_works() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5));
assert_ok!(Balances::hold(&TestId::Foo, &1, 9));
assert_eq!(Balances::reducible_total_balance_on_hold(&1, Force), 9);
assert_eq!(Balances::reducible_total_balance_on_hold(&1, Polite), 5);
assert_ok!(Balances::transfer_on_hold(
&TestId::Foo,
&1,
&2,
10,
BestEffort,
Free,
Polite
));
assert_eq!(Balances::total_balance(&1), 5);
assert_eq!(Balances::total_balance(&2), 25);
});
}
#[test]
fn partial_freezing_should_work() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5));
assert_eq!(System::consumers(&1), 1);
assert_ok!(<Balances as fungible::Mutate<_>>::transfer(&1, &2, 5, Expendable));
assert_noop!(
<Balances as fungible::Mutate<_>>::transfer(&1, &2, 1, Expendable),
TokenError::Frozen
);
});
}
#[test]
fn thaw_should_work() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, u64::MAX));
assert_ok!(Balances::thaw(&TestId::Foo, &1));
assert_eq!(System::consumers(&1), 0);
assert_eq!(Balances::balance_frozen(&TestId::Foo, &1), 0);
assert_eq!(Balances::account(&1).frozen, 0);
assert_ok!(<Balances as fungible::Mutate<_>>::transfer(&1, &2, 10, Expendable));
});
}
#[test]
fn set_freeze_zero_should_work() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, u64::MAX));
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 0));
assert_eq!(System::consumers(&1), 0);
assert_eq!(Balances::balance_frozen(&TestId::Foo, &1), 0);
assert_eq!(Balances::account(&1).frozen, 0);
assert_ok!(<Balances as fungible::Mutate<_>>::transfer(&1, &2, 10, Expendable));
});
}
#[test]
fn set_freeze_should_work() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, u64::MAX));
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5));
assert_ok!(<Balances as fungible::Mutate<_>>::transfer(&1, &2, 5, Expendable));
assert_noop!(
<Balances as fungible::Mutate<_>>::transfer(&1, &2, 1, Expendable),
TokenError::Frozen
);
});
}
#[test]
fn extend_freeze_should_work() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5));
assert_ok!(Balances::extend_freeze(&TestId::Foo, &1, 10));
assert_eq!(Balances::account(&1).frozen, 10);
assert_eq!(Balances::balance_frozen(&TestId::Foo, &1), 10);
assert_noop!(
<Balances as fungible::Mutate<_>>::transfer(&1, &2, 1, Expendable),
TokenError::Frozen
);
});
}
#[test]
fn double_freezing_should_work() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5));
assert_ok!(Balances::set_freeze(&TestId::Bar, &1, 5));
assert_eq!(System::consumers(&1), 1);
assert_ok!(<Balances as fungible::Mutate<_>>::transfer(&1, &2, 5, Expendable));
assert_noop!(
<Balances as fungible::Mutate<_>>::transfer(&1, &2, 1, Expendable),
TokenError::Frozen
);
});
}
#[test]
fn can_hold_entire_balance_when_second_provider() {
ExtBuilder::default()
.existential_deposit(1)
.monied(false)
.build_and_execute_with(|| {
<Balances as fungible::Mutate<_>>::set_balance(&1, 100);
assert_noop!(Balances::hold(&TestId::Foo, &1, 100), TokenError::FundsUnavailable);
System::inc_providers(&1);
assert_eq!(System::providers(&1), 2);
assert_ok!(Balances::hold(&TestId::Foo, &1, 100));
assert_eq!(System::providers(&1), 1);
assert_noop!(System::dec_providers(&1), DispatchError::ConsumerRemaining);
});
}
#[test]
fn unholding_frees_hold_slot() {
ExtBuilder::default()
.existential_deposit(1)
.monied(false)
.build_and_execute_with(|| {
<Balances as fungible::Mutate<_>>::set_balance(&1, 100);
assert_ok!(Balances::hold(&TestId::Foo, &1, 10));
assert_ok!(Balances::hold(&TestId::Bar, &1, 10));
assert_ok!(Balances::release(&TestId::Foo, &1, 10, Exact));
assert_ok!(Balances::hold(&TestId::Baz, &1, 10));
});
}
+296
View File
@@ -0,0 +1,296 @@
// This file is part of Substrate.
// Copyright (C) 2018-2022 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.
//! Tests.
#![cfg(test)]
use crate::{self as pallet_balances, AccountData, Config, CreditOf, Error, Pallet};
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{
assert_err, assert_noop, assert_ok, assert_storage_noop,
dispatch::DispatchInfo,
parameter_types,
traits::{
tokens::fungible, ConstU32, ConstU64, ConstU8, Imbalance as ImbalanceT, OnUnbalanced,
StorageMapShim, StoredMap,
},
weights::{IdentityFee, Weight},
RuntimeDebug,
};
use frame_system::{self as system, RawOrigin};
use pallet_transaction_payment::{ChargeTransactionPayment, CurrencyAdapter, Multiplier};
use scale_info::TypeInfo;
use sp_core::H256;
use sp_io;
use sp_runtime::{
testing::Header,
traits::{BadOrigin, IdentityLookup, SignedExtension, Zero},
ArithmeticError, DispatchError, DispatchResult, FixedPointNumber, TokenError,
};
mod currency_tests;
mod dispatchable_tests;
mod fungible_tests;
mod reentrancy_tests;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
#[derive(
Encode,
Decode,
Copy,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
MaxEncodedLen,
TypeInfo,
RuntimeDebug,
)]
pub enum TestId {
Foo,
Bar,
Baz,
}
frame_support::construct_runtime!(
pub struct Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event<T>},
}
);
parameter_types! {
pub BlockWeights: frame_system::limits::BlockWeights =
frame_system::limits::BlockWeights::simple_max(
frame_support::weights::Weight::from_parts(1024, u64::MAX),
);
pub static ExistentialDeposit: u64 = 0;
}
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = BlockWeights;
type BlockLength = ();
type DbWeight = ();
type RuntimeOrigin = RuntimeOrigin;
type Index = u64;
type BlockNumber = u64;
type RuntimeCall = RuntimeCall;
type Hash = H256;
type Hashing = ::sp_runtime::traits::BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = ConstU64<250>;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = super::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
impl pallet_transaction_payment::Config for Test {
type RuntimeEvent = RuntimeEvent;
type OnChargeTransaction = CurrencyAdapter<Pallet<Test>, ()>;
type OperationalFeeMultiplier = ConstU8<5>;
type WeightToFee = IdentityFee<u64>;
type LengthToFee = IdentityFee<u64>;
type FeeMultiplierUpdate = ();
}
impl Config for Test {
type Balance = u64;
type DustRemoval = DustTrap;
type RuntimeEvent = RuntimeEvent;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = TestAccountStore;
type MaxLocks = ConstU32<50>;
type MaxReserves = ConstU32<2>;
type ReserveIdentifier = TestId;
type WeightInfo = ();
type HoldIdentifier = TestId;
type FreezeIdentifier = TestId;
type MaxFreezes = ConstU32<2>;
type MaxHolds = ConstU32<2>;
}
#[derive(Clone)]
pub struct ExtBuilder {
existential_deposit: u64,
monied: bool,
dust_trap: Option<u64>,
}
impl Default for ExtBuilder {
fn default() -> Self {
Self { existential_deposit: 1, monied: false, dust_trap: None }
}
}
impl ExtBuilder {
pub fn existential_deposit(mut self, existential_deposit: u64) -> Self {
self.existential_deposit = existential_deposit;
self
}
pub fn monied(mut self, monied: bool) -> Self {
self.monied = monied;
if self.existential_deposit == 0 {
self.existential_deposit = 1;
}
self
}
pub fn dust_trap(mut self, account: u64) -> Self {
self.dust_trap = Some(account);
self
}
pub fn set_associated_consts(&self) {
DUST_TRAP_TARGET.with(|v| v.replace(self.dust_trap));
EXISTENTIAL_DEPOSIT.with(|v| v.replace(self.existential_deposit));
}
pub fn build(self) -> sp_io::TestExternalities {
self.set_associated_consts();
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
pallet_balances::GenesisConfig::<Test> {
balances: if self.monied {
vec![
(1, 10 * self.existential_deposit),
(2, 20 * self.existential_deposit),
(3, 30 * self.existential_deposit),
(4, 40 * self.existential_deposit),
(12, 10 * self.existential_deposit),
]
} else {
vec![]
},
}
.assimilate_storage(&mut t)
.unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
pub fn build_and_execute_with(self, f: impl Fn()) {
let other = self.clone();
UseSystem::set(false);
other.build().execute_with(|| f());
UseSystem::set(true);
self.build().execute_with(|| f());
}
}
parameter_types! {
static DustTrapTarget: Option<u64> = None;
}
pub struct DustTrap;
impl OnUnbalanced<CreditOf<Test, ()>> for DustTrap {
fn on_nonzero_unbalanced(amount: CreditOf<Test, ()>) {
match DustTrapTarget::get() {
None => drop(amount),
Some(a) => {
let result = <Balances as fungible::Balanced<_>>::resolve(&a, amount);
debug_assert!(result.is_ok());
},
}
}
}
parameter_types! {
pub static UseSystem: bool = false;
}
type BalancesAccountStore = StorageMapShim<super::Account<Test>, u64, super::AccountData<u64>>;
type SystemAccountStore = frame_system::Pallet<Test>;
pub struct TestAccountStore;
impl StoredMap<u64, super::AccountData<u64>> for TestAccountStore {
fn get(k: &u64) -> super::AccountData<u64> {
if UseSystem::get() {
<SystemAccountStore as StoredMap<_, _>>::get(k)
} else {
<BalancesAccountStore as StoredMap<_, _>>::get(k)
}
}
fn try_mutate_exists<R, E: From<DispatchError>>(
k: &u64,
f: impl FnOnce(&mut Option<super::AccountData<u64>>) -> Result<R, E>,
) -> Result<R, E> {
if UseSystem::get() {
<SystemAccountStore as StoredMap<_, _>>::try_mutate_exists(k, f)
} else {
<BalancesAccountStore as StoredMap<_, _>>::try_mutate_exists(k, f)
}
}
fn mutate<R>(
k: &u64,
f: impl FnOnce(&mut super::AccountData<u64>) -> R,
) -> Result<R, DispatchError> {
if UseSystem::get() {
<SystemAccountStore as StoredMap<_, _>>::mutate(k, f)
} else {
<BalancesAccountStore as StoredMap<_, _>>::mutate(k, f)
}
}
fn mutate_exists<R>(
k: &u64,
f: impl FnOnce(&mut Option<super::AccountData<u64>>) -> R,
) -> Result<R, DispatchError> {
if UseSystem::get() {
<SystemAccountStore as StoredMap<_, _>>::mutate_exists(k, f)
} else {
<BalancesAccountStore as StoredMap<_, _>>::mutate_exists(k, f)
}
}
fn insert(k: &u64, t: super::AccountData<u64>) -> Result<(), DispatchError> {
if UseSystem::get() {
<SystemAccountStore as StoredMap<_, _>>::insert(k, t)
} else {
<BalancesAccountStore as StoredMap<_, _>>::insert(k, t)
}
}
fn remove(k: &u64) -> Result<(), DispatchError> {
if UseSystem::get() {
<SystemAccountStore as StoredMap<_, _>>::remove(k)
} else {
<BalancesAccountStore as StoredMap<_, _>>::remove(k)
}
}
}
pub fn events() -> Vec<RuntimeEvent> {
let evt = System::events().into_iter().map(|evt| evt.event).collect::<Vec<_>>();
System::reset_events();
evt
}
/// create a transaction info struct from weight. Handy to avoid building the whole struct.
pub fn info_from_weight(w: Weight) -> DispatchInfo {
DispatchInfo { weight: w, ..Default::default() }
}
@@ -0,0 +1,195 @@
// This file is part of Substrate.
// Copyright (C) 2017-2022 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.
//! Tests regarding the reentrancy functionality.
use super::*;
use frame_support::traits::tokens::{
Fortitude::Force,
Precision::BestEffort,
Preservation::{Expendable, Protect},
};
use fungible::Balanced;
#[test]
fn transfer_dust_removal_tst1_should_work() {
ExtBuilder::default()
.existential_deposit(100)
.dust_trap(1)
.build_and_execute_with(|| {
// Verification of reentrancy in dust removal
assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 1000));
assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 2, 500));
// In this transaction, account 2 free balance
// drops below existential balance
// and dust balance is removed from account 2
assert_ok!(Balances::transfer_allow_death(RawOrigin::Signed(2).into(), 3, 450));
// As expected dust balance is removed.
assert_eq!(Balances::free_balance(&2), 0);
// As expected beneficiary account 3
// received the transfered fund.
assert_eq!(Balances::free_balance(&3), 450);
// Dust balance is deposited to account 1
// during the process of dust removal.
assert_eq!(Balances::free_balance(&1), 1050);
// Verify the events
assert_eq!(System::events().len(), 12);
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer {
from: 2,
to: 3,
amount: 450,
}));
System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost {
account: 2,
amount: 50,
}));
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit {
who: 1,
amount: 50,
}));
});
}
#[test]
fn transfer_dust_removal_tst2_should_work() {
ExtBuilder::default()
.existential_deposit(100)
.dust_trap(1)
.build_and_execute_with(|| {
// Verification of reentrancy in dust removal
assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 1000));
assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 2, 500));
// In this transaction, account 2 free balance
// drops below existential balance
// and dust balance is removed from account 2
assert_ok!(Balances::transfer_allow_death(RawOrigin::Signed(2).into(), 1, 450));
// As expected dust balance is removed.
assert_eq!(Balances::free_balance(&2), 0);
// Dust balance is deposited to account 1
// during the process of dust removal.
assert_eq!(Balances::free_balance(&1), 1500);
// Verify the events
assert_eq!(System::events().len(), 10);
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer {
from: 2,
to: 1,
amount: 450,
}));
System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost {
account: 2,
amount: 50,
}));
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit {
who: 1,
amount: 50,
}));
});
}
#[test]
fn repatriating_reserved_balance_dust_removal_should_work() {
ExtBuilder::default()
.existential_deposit(100)
.dust_trap(1)
.build_and_execute_with(|| {
// Verification of reentrancy in dust removal
assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 1000));
assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 2, 500));
// Reserve a value on account 2,
// Such that free balance is lower than
// Exestintial deposit.
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), 1, 450));
// Since free balance of account 2 is lower than
// existential deposit, dust amount is
// removed from the account 2
assert_eq!(Balances::reserved_balance(2), 0);
assert_eq!(Balances::free_balance(2), 0);
// account 1 is credited with reserved amount
// together with dust balance during dust
// removal.
assert_eq!(Balances::reserved_balance(1), 0);
assert_eq!(Balances::free_balance(1), 1500);
// Verify the events
assert_eq!(System::events().len(), 10);
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer {
from: 2,
to: 1,
amount: 450,
}));
System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost {
account: 2,
amount: 50,
}));
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit {
who: 1,
amount: 50,
}));
});
}
#[test]
fn emit_events_with_no_existential_deposit_suicide_with_dust() {
ExtBuilder::default().existential_deposit(2).build_and_execute_with(|| {
assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 100));
assert_eq!(
events(),
[
RuntimeEvent::System(system::Event::NewAccount { account: 1 }),
RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }),
RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100 }),
]
);
let res = Balances::withdraw(&1, 98, BestEffort, Protect, Force);
assert_eq!(res.unwrap().peek(), 98);
// no events
assert_eq!(
events(),
[RuntimeEvent::Balances(crate::Event::Withdraw { who: 1, amount: 98 })]
);
let res = Balances::withdraw(&1, 1, BestEffort, Expendable, Force);
assert_eq!(res.unwrap().peek(), 1);
assert_eq!(
events(),
[
RuntimeEvent::System(system::Event::KilledAccount { account: 1 }),
RuntimeEvent::Balances(crate::Event::DustLost { account: 1, amount: 1 }),
RuntimeEvent::Balances(crate::Event::Withdraw { who: 1, amount: 1 })
]
);
});
}
@@ -1,149 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 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.
//! Test utilities
#![cfg(test)]
use crate::{self as pallet_balances, decl_tests, Config, Pallet};
use frame_support::{
dispatch::DispatchInfo,
parameter_types,
traits::{ConstU32, ConstU64, ConstU8},
weights::{IdentityFee, Weight},
};
use pallet_transaction_payment::CurrencyAdapter;
use sp_core::H256;
use sp_io;
use sp_runtime::{testing::Header, traits::IdentityLookup};
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
frame_support::construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event<T>},
}
);
parameter_types! {
pub BlockWeights: frame_system::limits::BlockWeights =
frame_system::limits::BlockWeights::simple_max(
frame_support::weights::Weight::from_parts(1024, u64::MAX),
);
pub static ExistentialDeposit: u64 = 0;
}
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = BlockWeights;
type BlockLength = ();
type DbWeight = ();
type RuntimeOrigin = RuntimeOrigin;
type Index = u64;
type BlockNumber = u64;
type RuntimeCall = RuntimeCall;
type Hash = H256;
type Hashing = ::sp_runtime::traits::BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = ConstU64<250>;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = super::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
impl pallet_transaction_payment::Config for Test {
type RuntimeEvent = RuntimeEvent;
type OnChargeTransaction = CurrencyAdapter<Pallet<Test>, ()>;
type OperationalFeeMultiplier = ConstU8<5>;
type WeightToFee = IdentityFee<u64>;
type LengthToFee = IdentityFee<u64>;
type FeeMultiplierUpdate = ();
}
impl Config for Test {
type Balance = u64;
type DustRemoval = ();
type RuntimeEvent = RuntimeEvent;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = frame_system::Pallet<Test>;
type MaxLocks = ();
type MaxReserves = ConstU32<2>;
type ReserveIdentifier = [u8; 8];
type WeightInfo = ();
}
pub struct ExtBuilder {
existential_deposit: u64,
monied: bool,
}
impl Default for ExtBuilder {
fn default() -> Self {
Self { existential_deposit: 1, monied: false }
}
}
impl ExtBuilder {
pub fn existential_deposit(mut self, existential_deposit: u64) -> Self {
self.existential_deposit = existential_deposit;
self
}
pub fn monied(mut self, monied: bool) -> Self {
self.monied = monied;
self
}
pub fn set_associated_consts(&self) {
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
}
pub fn build(self) -> sp_io::TestExternalities {
self.set_associated_consts();
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
pallet_balances::GenesisConfig::<Test> {
balances: if self.monied {
vec![
(1, 10 * self.existential_deposit),
(2, 20 * self.existential_deposit),
(3, 30 * self.existential_deposit),
(4, 40 * self.existential_deposit),
(12, 10 * self.existential_deposit),
]
} else {
vec![]
},
}
.assimilate_storage(&mut t)
.unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
}
decl_tests! { Test, ExtBuilder, EXISTENTIAL_DEPOSIT }
-191
View File
@@ -1,191 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 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.
//! Test utilities
#![cfg(test)]
use crate::{self as pallet_balances, decl_tests, Config, Pallet};
use frame_support::{
dispatch::DispatchInfo,
parameter_types,
traits::{ConstU32, ConstU64, ConstU8, StorageMapShim},
weights::{IdentityFee, Weight},
};
use pallet_transaction_payment::CurrencyAdapter;
use sp_core::H256;
use sp_io;
use sp_runtime::{testing::Header, traits::IdentityLookup};
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
frame_support::construct_runtime!(
pub struct Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event<T>},
}
);
parameter_types! {
pub BlockWeights: frame_system::limits::BlockWeights =
frame_system::limits::BlockWeights::simple_max(
frame_support::weights::Weight::from_parts(1024, u64::MAX),
);
pub static ExistentialDeposit: u64 = 0;
}
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = BlockWeights;
type BlockLength = ();
type DbWeight = ();
type RuntimeOrigin = RuntimeOrigin;
type Index = u64;
type BlockNumber = u64;
type RuntimeCall = RuntimeCall;
type Hash = H256;
type Hashing = ::sp_runtime::traits::BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = ConstU64<250>;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
impl pallet_transaction_payment::Config for Test {
type RuntimeEvent = RuntimeEvent;
type OnChargeTransaction = CurrencyAdapter<Pallet<Test>, ()>;
type OperationalFeeMultiplier = ConstU8<5>;
type WeightToFee = IdentityFee<u64>;
type LengthToFee = IdentityFee<u64>;
type FeeMultiplierUpdate = ();
}
impl Config for Test {
type Balance = u64;
type DustRemoval = ();
type RuntimeEvent = RuntimeEvent;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore =
StorageMapShim<super::Account<Test>, system::Provider<Test>, u64, super::AccountData<u64>>;
type MaxLocks = ConstU32<50>;
type MaxReserves = ConstU32<2>;
type ReserveIdentifier = [u8; 8];
type WeightInfo = ();
}
pub struct ExtBuilder {
existential_deposit: u64,
monied: bool,
}
impl Default for ExtBuilder {
fn default() -> Self {
Self { existential_deposit: 1, monied: false }
}
}
impl ExtBuilder {
pub fn existential_deposit(mut self, existential_deposit: u64) -> Self {
self.existential_deposit = existential_deposit;
self
}
pub fn monied(mut self, monied: bool) -> Self {
self.monied = monied;
if self.existential_deposit == 0 {
self.existential_deposit = 1;
}
self
}
pub fn set_associated_consts(&self) {
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
}
pub fn build(self) -> sp_io::TestExternalities {
self.set_associated_consts();
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
pallet_balances::GenesisConfig::<Test> {
balances: if self.monied {
vec![
(1, 10 * self.existential_deposit),
(2, 20 * self.existential_deposit),
(3, 30 * self.existential_deposit),
(4, 40 * self.existential_deposit),
(12, 10 * self.existential_deposit),
]
} else {
vec![]
},
}
.assimilate_storage(&mut t)
.unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
}
decl_tests! { Test, ExtBuilder, EXISTENTIAL_DEPOSIT }
#[test]
fn emit_events_with_no_existential_deposit_suicide_with_dust() {
<ExtBuilder>::default().existential_deposit(2).build().execute_with(|| {
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 100, 0));
assert_eq!(
events(),
[
RuntimeEvent::System(system::Event::NewAccount { account: 1 }),
RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }),
RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100, reserved: 0 }),
]
);
let res = Balances::slash(&1, 98);
assert_eq!(res, (NegativeImbalance::new(98), 0));
// no events
assert_eq!(
events(),
[RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 98 })]
);
let res = Balances::slash(&1, 1);
assert_eq!(res, (NegativeImbalance::new(1), 0));
assert_eq!(
events(),
[
RuntimeEvent::System(system::Event::KilledAccount { account: 1 }),
RuntimeEvent::Balances(crate::Event::DustLost { account: 1, amount: 1 }),
RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 1 })
]
);
});
}
@@ -1,264 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 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.
//! Test setup for potential reentracy and lost updates of nested mutations.
#![cfg(test)]
use crate::{self as pallet_balances, Config};
use frame_support::{
parameter_types,
traits::{ConstU32, ConstU64, StorageMapShim},
};
use sp_core::H256;
use sp_io;
use sp_runtime::{testing::Header, traits::IdentityLookup};
use crate::*;
use frame_support::{
assert_ok,
traits::{Currency, ReservableCurrency},
};
use frame_system::RawOrigin;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
frame_support::construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
}
);
parameter_types! {
pub BlockWeights: frame_system::limits::BlockWeights =
frame_system::limits::BlockWeights::simple_max(
frame_support::weights::Weight::from_parts(1024, u64::MAX),
);
pub static ExistentialDeposit: u64 = 0;
}
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = BlockWeights;
type BlockLength = ();
type DbWeight = ();
type RuntimeOrigin = RuntimeOrigin;
type Index = u64;
type BlockNumber = u64;
type RuntimeCall = RuntimeCall;
type Hash = H256;
type Hashing = ::sp_runtime::traits::BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = ConstU64<250>;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
pub struct OnDustRemoval;
impl OnUnbalanced<NegativeImbalance<Test>> for OnDustRemoval {
fn on_nonzero_unbalanced(amount: NegativeImbalance<Test>) {
assert_ok!(Balances::resolve_into_existing(&1, amount));
}
}
impl Config for Test {
type Balance = u64;
type DustRemoval = OnDustRemoval;
type RuntimeEvent = RuntimeEvent;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore =
StorageMapShim<super::Account<Test>, system::Provider<Test>, u64, super::AccountData<u64>>;
type MaxLocks = ConstU32<50>;
type MaxReserves = ConstU32<2>;
type ReserveIdentifier = [u8; 8];
type WeightInfo = ();
}
pub struct ExtBuilder {
existential_deposit: u64,
}
impl Default for ExtBuilder {
fn default() -> Self {
Self { existential_deposit: 1 }
}
}
impl ExtBuilder {
pub fn existential_deposit(mut self, existential_deposit: u64) -> Self {
self.existential_deposit = existential_deposit;
self
}
pub fn set_associated_consts(&self) {
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
}
pub fn build(self) -> sp_io::TestExternalities {
self.set_associated_consts();
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
pallet_balances::GenesisConfig::<Test> { balances: vec![] }
.assimilate_storage(&mut t)
.unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
}
#[test]
fn transfer_dust_removal_tst1_should_work() {
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
// Verification of reentrancy in dust removal
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 1000, 0));
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 2, 500, 0));
// In this transaction, account 2 free balance
// drops below existential balance
// and dust balance is removed from account 2
assert_ok!(Balances::transfer(RawOrigin::Signed(2).into(), 3, 450));
// As expected dust balance is removed.
assert_eq!(Balances::free_balance(&2), 0);
// As expected beneficiary account 3
// received the transfered fund.
assert_eq!(Balances::free_balance(&3), 450);
// Dust balance is deposited to account 1
// during the process of dust removal.
assert_eq!(Balances::free_balance(&1), 1050);
// Verify the events
assert_eq!(System::events().len(), 12);
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer {
from: 2,
to: 3,
amount: 450,
}));
System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost {
account: 2,
amount: 50,
}));
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit {
who: 1,
amount: 50,
}));
});
}
#[test]
fn transfer_dust_removal_tst2_should_work() {
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
// Verification of reentrancy in dust removal
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 1000, 0));
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 2, 500, 0));
// In this transaction, account 2 free balance
// drops below existential balance
// and dust balance is removed from account 2
assert_ok!(Balances::transfer(RawOrigin::Signed(2).into(), 1, 450));
// As expected dust balance is removed.
assert_eq!(Balances::free_balance(&2), 0);
// Dust balance is deposited to account 1
// during the process of dust removal.
assert_eq!(Balances::free_balance(&1), 1500);
// Verify the events
assert_eq!(System::events().len(), 10);
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer {
from: 2,
to: 1,
amount: 450,
}));
System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost {
account: 2,
amount: 50,
}));
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit {
who: 1,
amount: 50,
}));
});
}
#[test]
fn repatriating_reserved_balance_dust_removal_should_work() {
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
// Verification of reentrancy in dust removal
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 1000, 0));
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 2, 500, 0));
// Reserve a value on account 2,
// Such that free balance is lower than
// Exestintial deposit.
assert_ok!(Balances::reserve(&2, 450));
// Transfer of reserved fund from slashed account 2 to
// beneficiary account 1
assert_ok!(Balances::repatriate_reserved(&2, &1, 450, Status::Free), 0);
// Since free balance of account 2 is lower than
// existential deposit, dust amount is
// removed from the account 2
assert_eq!(Balances::reserved_balance(2), 0);
assert_eq!(Balances::free_balance(2), 0);
// account 1 is credited with reserved amount
// together with dust balance during dust
// removal.
assert_eq!(Balances::reserved_balance(1), 0);
assert_eq!(Balances::free_balance(1), 1500);
// Verify the events
assert_eq!(System::events().len(), 11);
System::assert_has_event(RuntimeEvent::Balances(crate::Event::ReserveRepatriated {
from: 2,
to: 1,
amount: 450,
destination_status: Status::Free,
}));
System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost {
account: 2,
amount: 50,
}));
System::assert_last_event(RuntimeEvent::Balances(crate::Event::Deposit {
who: 1,
amount: 50,
}));
});
}
+157
View File
@@ -0,0 +1,157 @@
// This file is part of Substrate.
// Copyright (C) 2017-2022 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.
//! Types used in the pallet.
use crate::{Config, CreditOf, Event, Pallet};
use codec::{Decode, Encode, MaxEncodedLen};
use core::ops::BitOr;
use frame_support::{
traits::{Imbalance, LockIdentifier, OnUnbalanced, WithdrawReasons},
RuntimeDebug,
};
use scale_info::TypeInfo;
use sp_runtime::Saturating;
/// Simplified reasons for withdrawing balance.
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub enum Reasons {
/// Paying system transaction fees.
Fee = 0,
/// Any reason other than paying system transaction fees.
Misc = 1,
/// Any reason at all.
All = 2,
}
impl From<WithdrawReasons> for Reasons {
fn from(r: WithdrawReasons) -> Reasons {
if r == WithdrawReasons::TRANSACTION_PAYMENT {
Reasons::Fee
} else if r.contains(WithdrawReasons::TRANSACTION_PAYMENT) {
Reasons::All
} else {
Reasons::Misc
}
}
}
impl BitOr for Reasons {
type Output = Reasons;
fn bitor(self, other: Reasons) -> Reasons {
if self == other {
return self
}
Reasons::All
}
}
/// A single lock on a balance. There can be many of these on an account and they "overlap", so the
/// same balance is frozen by multiple locks.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub struct BalanceLock<Balance> {
/// An identifier for this lock. Only one lock may be in existence for each identifier.
pub id: LockIdentifier,
/// The amount which the free balance may not drop below when this lock is in effect.
pub amount: Balance,
/// If true, then the lock remains in effect even for payment of transaction fees.
pub reasons: Reasons,
}
/// Store named reserved balance.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub struct ReserveData<ReserveIdentifier, Balance> {
/// The identifier for the named reserve.
pub id: ReserveIdentifier,
/// The amount of the named reserve.
pub amount: Balance,
}
/// An identifier and balance.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub struct IdAmount<Id, Balance> {
/// An identifier for this item.
pub id: Id,
/// Some amount for this item.
pub amount: Balance,
}
/// All balance information for an account.
#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub struct AccountData<Balance> {
/// Non-reserved part of the balance which the account holder may be able to control.
///
/// This is the only balance that matters in terms of most operations on tokens.
pub free: Balance,
/// Balance which is has active holds on it and may not be used at all.
///
/// This is the sum of all individual holds together with any sums still under the (deprecated)
/// reserves API.
pub reserved: Balance,
/// The amount that `free` may not drop below when reducing the balance, except for actions
/// where the account owner cannot reasonably benefit from thr balance reduction, such as
/// slashing.
pub frozen: Balance,
/// Extra information about this account. The MSB is a flag indicating whether the new ref-
/// counting logic is in place for this account.
pub flags: ExtraFlags,
}
const IS_NEW_LOGIC: u128 = 0x80000000_00000000_00000000_00000000u128;
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub struct ExtraFlags(u128);
impl Default for ExtraFlags {
fn default() -> Self {
Self(IS_NEW_LOGIC)
}
}
impl ExtraFlags {
pub fn old_logic() -> Self {
Self(0)
}
pub fn set_new_logic(&mut self) {
self.0 = self.0 | IS_NEW_LOGIC
}
pub fn is_new_logic(&self) -> bool {
(self.0 & IS_NEW_LOGIC) == IS_NEW_LOGIC
}
}
impl<Balance: Saturating + Copy + Ord> AccountData<Balance> {
pub fn usable(&self) -> Balance {
self.free.saturating_sub(self.frozen)
}
/// The total balance in this account including any that is reserved and ignoring any frozen.
pub fn total(&self) -> Balance {
self.free.saturating_add(self.reserved)
}
}
pub struct DustCleaner<T: Config<I>, I: 'static = ()>(
pub(crate) Option<(T::AccountId, CreditOf<T, I>)>,
);
impl<T: Config<I>, I: 'static> Drop for DustCleaner<T, I> {
fn drop(&mut self) {
if let Some((who, dust)) = self.0.take() {
Pallet::<T, I>::deposit_event(Event::DustLost { account: who, amount: dust.peek() });
T::DustRemoval::on_unbalanced(dust);
}
}
}
+40 -9
View File
@@ -49,13 +49,14 @@ use sp_std::marker::PhantomData;
/// Weight functions needed for pallet_balances.
pub trait WeightInfo {
fn transfer() -> Weight;
fn transfer_allow_death() -> Weight;
fn transfer_keep_alive() -> Weight;
fn set_balance_creating() -> Weight;
fn set_balance_killing() -> Weight;
fn force_set_balance_creating() -> Weight;
fn force_set_balance_killing() -> Weight;
fn force_transfer() -> Weight;
fn transfer_all() -> Weight;
fn force_unreserve() -> Weight;
fn upgrade_accounts(u: u32, ) -> Weight;
}
/// Weights for pallet_balances using the Substrate node and recommended hardware.
@@ -63,7 +64,7 @@ pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Storage: System Account (r:1 w:1)
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
fn transfer() -> Weight {
fn transfer_allow_death() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `3593`
@@ -85,7 +86,7 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
}
/// Storage: System Account (r:1 w:1)
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
fn set_balance_creating() -> Weight {
fn force_set_balance_creating() -> Weight {
// Proof Size summary in bytes:
// Measured: `174`
// Estimated: `3593`
@@ -96,7 +97,7 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
}
/// Storage: System Account (r:1 w:1)
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
fn set_balance_killing() -> Weight {
fn force_set_balance_killing() -> Weight {
// Proof Size summary in bytes:
// Measured: `174`
// Estimated: `3593`
@@ -138,13 +139,28 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: System Account (r:999 w:999)
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
/// The range of component `u` is `[1, 1000]`.
fn upgrade_accounts(u: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `0 + u * (135 ±0)`
// Estimated: `990 + u * (2603 ±0)`
// Minimum execution time: 19_851_000 picoseconds.
Weight::from_parts(20_099_000, 990)
// Standard Error: 15_586
.saturating_add(Weight::from_parts(14_892_860, 0).saturating_mul(u.into()))
.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(u.into())))
.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(u.into())))
.saturating_add(Weight::from_parts(0, 2603).saturating_mul(u.into()))
}
}
// For backwards compatibility and tests
impl WeightInfo for () {
/// Storage: System Account (r:1 w:1)
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
fn transfer() -> Weight {
fn transfer_allow_death() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `3593`
@@ -166,7 +182,7 @@ impl WeightInfo for () {
}
/// Storage: System Account (r:1 w:1)
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
fn set_balance_creating() -> Weight {
fn force_set_balance_creating() -> Weight {
// Proof Size summary in bytes:
// Measured: `174`
// Estimated: `3593`
@@ -177,7 +193,7 @@ impl WeightInfo for () {
}
/// Storage: System Account (r:1 w:1)
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
fn set_balance_killing() -> Weight {
fn force_set_balance_killing() -> Weight {
// Proof Size summary in bytes:
// Measured: `174`
// Estimated: `3593`
@@ -219,4 +235,19 @@ impl WeightInfo for () {
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: System Account (r:999 w:999)
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
/// The range of component `u` is `[1, 1000]`.
fn upgrade_accounts(u: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `0 + u * (135 ±0)`
// Estimated: `990 + u * (2603 ±0)`
// Minimum execution time: 19_851_000 picoseconds.
Weight::from_parts(20_099_000, 990)
// Standard Error: 15_586
.saturating_add(Weight::from_parts(14_892_860, 0).saturating_mul(u.into()))
.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(u.into())))
.saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(u.into())))
.saturating_add(Weight::from_parts(0, 2603).saturating_mul(u.into()))
}
}
+4
View File
@@ -161,6 +161,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU128<1>;
type AccountStore = System;
type WeightInfo = ();
type HoldIdentifier = ();
type MaxHolds = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
}
impl pallet_timestamp::Config for Test {
+5 -2
View File
@@ -57,9 +57,12 @@ fn setup_bounty<T: Config<I>, I: 'static>(
let fee = value / 2u32.into();
let deposit = T::BountyDepositBase::get() +
T::DataDepositPerByte::get() * T::MaximumReasonLength::get().into();
let _ = T::Currency::make_free_balance_be(&caller, deposit);
let _ = T::Currency::make_free_balance_be(&caller, deposit + T::Currency::minimum_balance());
let curator = account("curator", u, SEED);
let _ = T::Currency::make_free_balance_be(&curator, fee / 2u32.into());
let _ = T::Currency::make_free_balance_be(
&curator,
fee / 2u32.into() + T::Currency::minimum_balance(),
);
let reason = vec![0; d as usize];
(caller, curator, fee, value, reason)
}
+6 -2
View File
@@ -100,6 +100,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
parameter_types! {
pub const ProposalBond: Permill = Permill::from_percent(5);
@@ -754,7 +758,7 @@ fn award_and_claim_bounty_works() {
System::set_block_number(5);
<Treasury as OnInitialize<u64>>::on_initialize(5);
assert_ok!(Balances::transfer(
assert_ok!(Balances::transfer_allow_death(
RuntimeOrigin::signed(0),
Bounties::bounty_account_id(0),
10
@@ -832,7 +836,7 @@ fn cancel_and_refund() {
System::set_block_number(2);
<Treasury as OnInitialize<u64>>::on_initialize(2);
assert_ok!(Balances::transfer(
assert_ok!(Balances::transfer_allow_death(
RuntimeOrigin::signed(0),
Bounties::bounty_account_id(0),
10
@@ -63,9 +63,12 @@ fn setup_bounty<T: Config>(
let fee = value / 2u32.into();
let deposit = T::BountyDepositBase::get() +
T::DataDepositPerByte::get() * T::MaximumReasonLength::get().into();
let _ = T::Currency::make_free_balance_be(&caller, deposit);
let _ = T::Currency::make_free_balance_be(&caller, deposit + T::Currency::minimum_balance());
let curator = account("curator", user, SEED);
let _ = T::Currency::make_free_balance_be(&curator, fee / 2u32.into());
let _ = T::Currency::make_free_balance_be(
&curator,
fee / 2u32.into() + T::Currency::minimum_balance(),
);
let reason = vec![0; description as usize];
(caller, curator, fee, value, reason)
}
@@ -73,7 +76,10 @@ fn setup_bounty<T: Config>(
fn setup_child_bounty<T: Config>(user: u32, description: u32) -> BenchmarkChildBounty<T> {
let (caller, curator, fee, value, reason) = setup_bounty::<T>(user, description);
let child_curator = account("child-curator", user, SEED);
let _ = T::Currency::make_free_balance_be(&child_curator, fee / 2u32.into());
let _ = T::Currency::make_free_balance_be(
&child_curator,
fee / 2u32.into() + T::Currency::minimum_balance(),
);
let child_bounty_value = (value - fee) / 4u32.into();
let child_bounty_fee = child_bounty_value / 2u32.into();
+6 -2
View File
@@ -35,7 +35,7 @@ use sp_core::H256;
use sp_runtime::{
testing::Header,
traits::{BadOrigin, BlakeTwo256, IdentityLookup},
Perbill, Permill,
Perbill, Permill, TokenError,
};
use super::Event as ChildBountiesEvent;
@@ -103,6 +103,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
parameter_types! {
pub const ProposalBond: Permill = Permill::from_percent(5);
@@ -248,7 +252,7 @@ fn add_child_bounty() {
assert_noop!(
ChildBounties::add_child_bounty(RuntimeOrigin::signed(4), 0, 50, b"12345-p1".to_vec()),
pallet_balances::Error::<Test>::KeepAlive,
TokenError::NotExpendable,
);
assert_noop!(
@@ -798,15 +798,15 @@ benchmarks! {
let instance = Contract::<T>::new(code, vec![])?;
let origin = RawOrigin::Signed(instance.caller.clone());
let deposit_account = instance.info()?.deposit_account().clone();
assert_eq!(T::Currency::total_balance(&beneficiary), 0u32.into());
assert_eq!(<T::Currency as Currency<_>>::total_balance(&beneficiary), 0u32.into());
assert_eq!(T::Currency::free_balance(&instance.account_id), Pallet::<T>::min_balance() * 2u32.into());
assert_ne!(T::Currency::free_balance(&deposit_account), 0u32.into());
}: call(origin, instance.addr.clone(), 0u32.into(), Weight::MAX, None, vec![])
verify {
if r > 0 {
assert_eq!(T::Currency::total_balance(&instance.account_id), 0u32.into());
assert_eq!(T::Currency::total_balance(&deposit_account), 0u32.into());
assert_eq!(T::Currency::total_balance(&beneficiary), Pallet::<T>::min_balance() * 2u32.into());
assert_eq!(<T::Currency as Currency<_>>::total_balance(&instance.account_id), 0u32.into());
assert_eq!(<T::Currency as Currency<_>>::total_balance(&deposit_account), 0u32.into());
assert_eq!(<T::Currency as Currency<_>>::total_balance(&beneficiary), Pallet::<T>::min_balance() * 2u32.into());
}
}
@@ -1573,12 +1573,12 @@ benchmarks! {
instance.set_balance(value * (r + 1).into());
let origin = RawOrigin::Signed(instance.caller.clone());
for account in &accounts {
assert_eq!(T::Currency::total_balance(account), 0u32.into());
assert_eq!(<T::Currency as Currency<_>>::total_balance(account), 0u32.into());
}
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
verify {
for account in &accounts {
assert_eq!(T::Currency::total_balance(account), value);
assert_eq!(<T::Currency as Currency<_>>::total_balance(account), value);
}
}
+10 -5
View File
@@ -25,7 +25,10 @@ use frame_support::{
crypto::ecdsa::ECDSAExt,
dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable},
storage::{with_transaction, TransactionOutcome},
traits::{Contains, Currency, ExistenceRequirement, OriginTrait, Randomness, Time},
traits::{
tokens::{Fortitude::Polite, Preservation::Expendable},
Contains, Currency, ExistenceRequirement, OriginTrait, Randomness, Time,
},
weights::Weight,
Blake2_128Concat, BoundedVec, StorageHasher,
};
@@ -1198,7 +1201,7 @@ where
T::Currency::transfer(
&frame.account_id,
beneficiary,
T::Currency::reducible_balance(&frame.account_id, false),
T::Currency::reducible_balance(&frame.account_id, Expendable, Polite),
ExistenceRequirement::AllowDeath,
)?;
info.queue_trie_for_deletion()?;
@@ -2751,8 +2754,10 @@ mod tests {
RuntimeCall::System(SysCall::remark_with_event { remark: b"Hello".to_vec() });
// transfers are disallowed by the `TestFiler` (see below)
let forbidden_call =
RuntimeCall::Balances(BalanceCall::transfer { dest: CHARLIE, value: 22 });
let forbidden_call = RuntimeCall::Balances(BalanceCall::transfer_allow_death {
dest: CHARLIE,
value: 22,
});
// simple cases: direct call
assert_err!(
@@ -2772,7 +2777,7 @@ mod tests {
});
TestFilter::set_filter(|call| match call {
RuntimeCall::Balances(pallet_balances::Call::transfer { .. }) => false,
RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { .. }) => false,
_ => true,
});
+1 -1
View File
@@ -187,7 +187,7 @@ pub mod pallet {
type Randomness: Randomness<Self::Hash, Self::BlockNumber>;
/// The currency in which fees are paid and contract balances are held.
type Currency: ReservableCurrency<Self::AccountId>
type Currency: ReservableCurrency<Self::AccountId> // TODO: Move to fungible traits
+ Inspect<Self::AccountId, Balance = BalanceOf<Self>>;
/// The overarching event type.
@@ -25,7 +25,10 @@ use codec::Encode;
use frame_support::{
dispatch::DispatchError,
ensure,
traits::{tokens::WithdrawConsequence, Currency, ExistenceRequirement, Get},
traits::{
tokens::{Fortitude::Polite, Preservation::Protect, WithdrawConsequence},
Currency, ExistenceRequirement, Get,
},
DefaultNoBound, RuntimeDebugNoBound,
};
use pallet_contracts_primitives::StorageDeposit as Deposit;
@@ -456,7 +459,7 @@ impl<T: Config> Ext<T> for ReservingExt {
// We are sending the `min_leftover` and the `min_balance` from the origin
// account as part of a contract call. Hence origin needs to have those left over
// as free balance after accounting for all deposits.
let max = T::Currency::reducible_balance(origin, true)
let max = T::Currency::reducible_balance(origin, Protect, Polite)
.saturating_sub(min_leftover)
.saturating_sub(Pallet::<T>::min_balance());
let limit = limit.unwrap_or(max);
+8 -65
View File
@@ -37,7 +37,7 @@ use frame_support::{
storage::child,
traits::{
ConstU32, ConstU64, Contains, Currency, ExistenceRequirement, Get, LockableCurrency,
OnIdle, OnInitialize, ReservableCurrency, WithdrawReasons,
OnIdle, OnInitialize, WithdrawReasons,
},
weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight},
};
@@ -48,7 +48,7 @@ use sp_keystore::{testing::MemoryKeystore, KeystoreExt};
use sp_runtime::{
testing::{Header, H256},
traits::{BlakeTwo256, Convert, Hash, IdentityLookup},
AccountId32,
AccountId32, TokenError,
};
use std::{ops::Deref, sync::Arc};
@@ -318,6 +318,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
impl pallet_timestamp::Config for Test {
@@ -1054,7 +1058,7 @@ fn transfer_allow_death_cannot_kill_account() {
total_balance,
ExistenceRequirement::AllowDeath,
),
pallet_balances::Error::<Test>::KeepAlive,
TokenError::Frozen,
);
assert_eq!(<Test as Config>::Currency::total_balance(&addr), total_balance);
@@ -1474,25 +1478,6 @@ fn transfer_return_code() {
.result
.unwrap();
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
// Contract has enough total balance in order to not go below the min balance
// threshold when transfering 100 balance but this balance is reserved so
// the transfer still fails.
Balances::make_free_balance_be(&addr, min_balance + 100);
Balances::reserve(&addr, min_balance + 100).unwrap();
let result = Contracts::bare_call(
ALICE,
addr,
0,
GAS_LIMIT,
None,
vec![],
false,
Determinism::Deterministic,
)
.result
.unwrap();
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
});
}
@@ -1569,29 +1554,6 @@ fn call_return_code() {
.unwrap();
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
// Contract has enough total balance in order to not go below the min balance
// threshold when transfering 100 balance but this balance is reserved so
// the transfer still fails.
Balances::make_free_balance_be(&addr_bob, min_balance + 100);
Balances::reserve(&addr_bob, min_balance + 100).unwrap();
let result = Contracts::bare_call(
ALICE,
addr_bob.clone(),
0,
GAS_LIMIT,
None,
AsRef::<[u8]>::as_ref(&addr_django)
.iter()
.chain(&0u32.to_le_bytes())
.cloned()
.collect(),
false,
Determinism::Deterministic,
)
.result
.unwrap();
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
// Contract has enough balance but callee reverts because "1" is passed.
Balances::make_free_balance_be(&addr_bob, min_balance + 1000);
let result = Contracts::bare_call(
@@ -1683,25 +1645,6 @@ fn instantiate_return_code() {
.unwrap();
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
// Contract has enough total balance in order to not go below the min_balance
// threshold when transfering the balance but this balance is reserved so
// the transfer still fails.
Balances::make_free_balance_be(&addr, min_balance + 10_000);
Balances::reserve(&addr, min_balance + 10_000).unwrap();
let result = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
None,
callee_hash.clone(),
false,
Determinism::Deterministic,
)
.result
.unwrap();
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
// Contract has enough balance but the passed code hash is invalid
Balances::make_free_balance_be(&addr, min_balance + 10_000);
let result = Contracts::bare_call(
@@ -2805,7 +2748,7 @@ fn gas_estimation_call_runtime() {
// Call something trivial with a huge gas limit so that we can observe the effects
// of pre-charging. This should create a difference between consumed and required.
let call = RuntimeCall::Balances(pallet_balances::Call::transfer {
let call = RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
dest: addr_callee,
value: min_balance * 10,
});
@@ -23,7 +23,11 @@ use assert_matches::assert_matches;
use frame_benchmarking::v1::{account, benchmarks_instance_pallet, whitelist_account};
use frame_support::{
dispatch::RawOrigin,
traits::{fungible, Currency, Get},
traits::{
fungible,
tokens::{Fortitude::Polite, Preservation::Expendable},
Currency, Get,
},
};
use sp_runtime::traits::Bounded;
use sp_std::collections::btree_map::BTreeMap;
@@ -257,13 +261,13 @@ benchmarks_instance_pallet! {
}
}
let orig_usable = <T::Currency as fungible::Inspect<T::AccountId>>::reducible_balance(&caller, false);
let orig_usable = <T::Currency as fungible::Inspect<T::AccountId>>::reducible_balance(&caller, Expendable, Polite);
let polls = &all_polls[&class];
// Vote big on the class with the most ongoing votes of them to bump the lock and make it
// hard to recompute when removed.
ConvictionVoting::<T, I>::vote(RawOrigin::Signed(caller.clone()).into(), polls[0], big_account_vote)?;
let now_usable = <T::Currency as fungible::Inspect<T::AccountId>>::reducible_balance(&caller, false);
let now_usable = <T::Currency as fungible::Inspect<T::AccountId>>::reducible_balance(&caller, Expendable, Polite);
assert_eq!(orig_usable - now_usable, 100u32.into());
// Remove the vote
@@ -272,7 +276,7 @@ benchmarks_instance_pallet! {
// We can now unlock on `class` from 200 to 100...
}: _(RawOrigin::Signed(caller.clone()), class, caller_lookup)
verify {
assert_eq!(orig_usable, <T::Currency as fungible::Inspect<T::AccountId>>::reducible_balance(&caller, false));
assert_eq!(orig_usable, <T::Currency as fungible::Inspect<T::AccountId>>::reducible_balance(&caller, Expendable, Polite));
}
impl_benchmark_test_suite!(
+5 -2
View File
@@ -388,7 +388,10 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
poll_index: PollIndexOf<T, I>,
vote: AccountVote<BalanceOf<T, I>>,
) -> DispatchResult {
ensure!(vote.balance() <= T::Currency::free_balance(who), Error::<T, I>::InsufficientFunds);
ensure!(
vote.balance() <= T::Currency::total_balance(who),
Error::<T, I>::InsufficientFunds
);
T::Polls::try_access_poll(poll_index, |poll_status| {
let (tally, class) = poll_status.ensure_ongoing().ok_or(Error::<T, I>::NotOngoing)?;
VotingFor::<T, I>::try_mutate(who, &class, |voting| {
@@ -548,7 +551,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
) -> Result<u32, DispatchError> {
ensure!(who != target, Error::<T, I>::Nonsense);
T::Polls::classes().binary_search(&class).map_err(|_| Error::<T, I>::BadClass)?;
ensure!(balance <= T::Currency::free_balance(&who), Error::<T, I>::InsufficientFunds);
ensure!(balance <= T::Currency::total_balance(&who), Error::<T, I>::InsufficientFunds);
let votes =
VotingFor::<T, I>::try_mutate(&who, &class, |voting| -> Result<u32, DispatchError> {
let old = sp_std::mem::replace(
@@ -51,7 +51,7 @@ frame_support::construct_runtime!(
pub struct BaseFilter;
impl Contains<RuntimeCall> for BaseFilter {
fn contains(call: &RuntimeCall) -> bool {
!matches!(call, &RuntimeCall::Balances(pallet_balances::Call::set_balance { .. }))
!matches!(call, &RuntimeCall::Balances(pallet_balances::Call::force_set_balance { .. }))
}
}
@@ -92,6 +92,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
#[derive(Clone, PartialEq, Eq, Debug)]
+6 -2
View File
@@ -72,7 +72,7 @@ frame_support::construct_runtime!(
pub struct BaseFilter;
impl Contains<RuntimeCall> for BaseFilter {
fn contains(call: &RuntimeCall) -> bool {
!matches!(call, &RuntimeCall::Balances(pallet_balances::Call::set_balance { .. }))
!matches!(call, &RuntimeCall::Balances(pallet_balances::Call::force_set_balance { .. }))
}
}
@@ -144,6 +144,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
parameter_types! {
pub static PreimageByteDeposit: u64 = 0;
@@ -223,7 +227,7 @@ fn params_should_work() {
}
fn set_balance_proposal(value: u64) -> BoundedCallOf<Test> {
let inner = pallet_balances::Call::set_balance { who: 42, new_free: value, new_reserved: 0 };
let inner = pallet_balances::Call::force_set_balance { who: 42, new_free: value };
let outer = RuntimeCall::Balances(inner);
Preimage::bound(outer).unwrap()
}
@@ -219,7 +219,7 @@ frame_benchmarking::benchmarks! {
finalize_signed_phase_accept_solution {
let receiver = account("receiver", 0, SEED);
let initial_balance = T::Currency::minimum_balance() * 10u32.into();
let initial_balance = T::Currency::minimum_balance() + 10u32.into();
T::Currency::make_free_balance_be(&receiver, initial_balance);
let ready = Default::default();
let deposit: BalanceOf<T> = 10u32.into();
@@ -228,7 +228,7 @@ frame_benchmarking::benchmarks! {
let call_fee: BalanceOf<T> = 30u32.into();
assert_ok!(T::Currency::reserve(&receiver, deposit));
assert_eq!(T::Currency::free_balance(&receiver), initial_balance - 10u32.into());
assert_eq!(T::Currency::free_balance(&receiver), T::Currency::minimum_balance());
}: {
<MultiPhase<T>>::finalize_signed_phase_accept_solution(
ready,
@@ -246,17 +246,17 @@ frame_benchmarking::benchmarks! {
finalize_signed_phase_reject_solution {
let receiver = account("receiver", 0, SEED);
let initial_balance = T::Currency::minimum_balance().max(One::one()) * 10u32.into();
let initial_balance = T::Currency::minimum_balance() + 10u32.into();
let deposit: BalanceOf<T> = 10u32.into();
T::Currency::make_free_balance_be(&receiver, initial_balance);
assert_ok!(T::Currency::reserve(&receiver, deposit));
assert_eq!(T::Currency::free_balance(&receiver), initial_balance - 10u32.into());
assert_eq!(T::Currency::free_balance(&receiver), T::Currency::minimum_balance());
assert_eq!(T::Currency::reserved_balance(&receiver), 10u32.into());
}: {
<MultiPhase<T>>::finalize_signed_phase_reject_solution(&receiver, deposit)
} verify {
assert_eq!(T::Currency::free_balance(&receiver), initial_balance - 10u32.into());
assert_eq!(T::Currency::free_balance(&receiver), T::Currency::minimum_balance());
assert_eq!(T::Currency::reserved_balance(&receiver), 0u32.into());
}
@@ -254,6 +254,10 @@ impl pallet_balances::Config for Runtime {
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
#[derive(Default, Eq, PartialEq, Debug, Clone, Copy)]
@@ -1248,6 +1248,10 @@ mod tests {
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
frame_support::parameter_types! {
@@ -2121,7 +2125,8 @@ mod tests {
assert_ok!(submit_candidacy(RuntimeOrigin::signed(4)));
// User has 100 free and 50 reserved.
assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 2, 100, 50));
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 150));
assert_ok!(Balances::reserve(&2, 50));
// User tries to vote with 150 tokens.
assert_ok!(vote(RuntimeOrigin::signed(2), vec![4, 5], 150));
// We truncate to only their free balance, after reserving additional for voting.
@@ -87,6 +87,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
impl Config for Test {
+42 -58
View File
@@ -688,13 +688,10 @@ mod tests {
use frame_support::{
assert_err, parameter_types,
traits::{
ConstU32, ConstU64, ConstU8, Currency, LockIdentifier, LockableCurrency,
WithdrawReasons,
},
traits::{fungible, ConstU32, ConstU64, ConstU8, Currency},
weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight, WeightToFee},
};
use frame_system::{Call as SystemCall, ChainContext, LastRuntimeUpgradeInfo};
use frame_system::{ChainContext, LastRuntimeUpgradeInfo};
use pallet_balances::Call as BalancesCall;
use pallet_transaction_payment::CurrencyAdapter;
@@ -899,6 +896,10 @@ mod tests {
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ConstU32<1>;
type HoldIdentifier = ();
type MaxHolds = ConstU32<1>;
}
parameter_types! {
@@ -972,7 +973,7 @@ mod tests {
}
fn call_transfer(dest: u64, value: u64) -> RuntimeCall {
RuntimeCall::Balances(BalancesCall::transfer { dest, value })
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest, value })
}
#[test]
@@ -1025,13 +1026,13 @@ mod tests {
block_import_works_inner(
new_test_ext_v0(1),
array_bytes::hex_n_into_unchecked(
"216e61b2689d1243eb56d89c9084db48e50ebebc4871d758db131432c675d7c0",
"65e953676859e7a33245908af7ad3637d6861eb90416d433d485e95e2dd174a1",
),
);
block_import_works_inner(
new_test_ext(1),
array_bytes::hex_n_into_unchecked(
"4738b4c0aab02d6ddfa62a2a6831ccc975a9f978f7db8d7ea8e68eba8639530a",
"5a19b3d6fdb7241836349fdcbe2d9df4d4f945b949d979e31ad50bff1cbcd1c2",
),
);
}
@@ -1116,7 +1117,7 @@ mod tests {
let mut t = new_test_ext(10000);
// given: TestXt uses the encoded len as fixed Len:
let xt = TestXt::new(
RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
sign_extra(1, 0, 0),
);
let encoded = xt.encode();
@@ -1139,7 +1140,10 @@ mod tests {
for nonce in 0..=num_to_exhaust_block {
let xt = TestXt::new(
RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer_allow_death {
dest: 33,
value: 0,
}),
sign_extra(1, nonce.into(), 0),
);
let res = Executive::apply_extrinsic(xt);
@@ -1164,15 +1168,15 @@ mod tests {
#[test]
fn block_weight_and_size_is_stored_per_tx() {
let xt = TestXt::new(
RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
sign_extra(1, 0, 0),
);
let x1 = TestXt::new(
RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
sign_extra(1, 1, 0),
);
let x2 = TestXt::new(
RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
sign_extra(1, 2, 0),
);
let len = xt.clone().encode().len() as u32;
@@ -1258,50 +1262,30 @@ mod tests {
}
#[test]
fn can_pay_for_tx_fee_on_full_lock() {
let id: LockIdentifier = *b"0 ";
let execute_with_lock = |lock: WithdrawReasons| {
let mut t = new_test_ext(1);
t.execute_with(|| {
<pallet_balances::Pallet<Runtime> as LockableCurrency<Balance>>::set_lock(
id, &1, 110, lock,
);
let xt = TestXt::new(
RuntimeCall::System(SystemCall::remark { remark: vec![1u8] }),
sign_extra(1, 0, 0),
);
let weight = xt.get_dispatch_info().weight +
<Runtime as frame_system::Config>::BlockWeights::get()
.get(DispatchClass::Normal)
.base_extrinsic;
let fee: Balance =
<Runtime as pallet_transaction_payment::Config>::WeightToFee::weight_to_fee(
&weight,
);
Executive::initialize_block(&Header::new(
1,
H256::default(),
H256::default(),
[69u8; 32].into(),
Digest::default(),
));
fn can_not_pay_for_tx_fee_on_full_lock() {
let mut t = new_test_ext(1);
t.execute_with(|| {
<pallet_balances::Pallet<Runtime> as fungible::MutateFreeze<u64>>::set_freeze(
&(),
&1,
110,
)
.unwrap();
let xt = TestXt::new(
RuntimeCall::System(frame_system::Call::remark { remark: vec![1u8] }),
sign_extra(1, 0, 0),
);
Executive::initialize_block(&Header::new(
1,
H256::default(),
H256::default(),
[69u8; 32].into(),
Digest::default(),
));
if lock == WithdrawReasons::except(WithdrawReasons::TRANSACTION_PAYMENT) {
assert!(Executive::apply_extrinsic(xt).unwrap().is_ok());
// tx fee has been deducted.
assert_eq!(<pallet_balances::Pallet<Runtime>>::total_balance(&1), 111 - fee);
} else {
assert_eq!(
Executive::apply_extrinsic(xt),
Err(InvalidTransaction::Payment.into()),
);
assert_eq!(<pallet_balances::Pallet<Runtime>>::total_balance(&1), 111);
}
});
};
execute_with_lock(WithdrawReasons::all());
execute_with_lock(WithdrawReasons::except(WithdrawReasons::TRANSACTION_PAYMENT));
assert_eq!(Executive::apply_extrinsic(xt), Err(InvalidTransaction::Payment.into()),);
assert_eq!(<pallet_balances::Pallet<Runtime>>::total_balance(&1), 111);
});
}
#[test]
@@ -1443,7 +1427,7 @@ mod tests {
#[test]
fn custom_runtime_upgrade_is_called_when_using_execute_block_trait() {
let xt = TestXt::new(
RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
sign_extra(1, 0, 0),
);
@@ -1569,7 +1553,7 @@ mod tests {
#[should_panic(expected = "Invalid inherent position for extrinsic at index 1")]
fn invalid_inherent_position_fail() {
let xt1 = TestXt::new(
RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
sign_extra(1, 0, 0),
);
let xt2 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent_call {}), None);
+4
View File
@@ -89,6 +89,10 @@ impl pallet_balances::Config for Runtime {
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
pallet_staking_reward_curve::build! {
+4
View File
@@ -142,6 +142,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU128<1>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
impl pallet_timestamp::Config for Test {
+4
View File
@@ -85,6 +85,10 @@ impl pallet_balances::Config for Test {
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
parameter_types! {
+4
View File
@@ -76,6 +76,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
impl Config for Test {
+4
View File
@@ -89,6 +89,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
parameter_types! {
+47 -30
View File
@@ -23,8 +23,7 @@ use mock::{
new_test_ext, run_to_block, Balances, BalancesCall, Lottery, RuntimeCall, RuntimeOrigin,
SystemCall, Test,
};
use pallet_balances::Error as BalancesError;
use sp_runtime::traits::BadOrigin;
use sp_runtime::{traits::BadOrigin, TokenError};
#[test]
fn initial_state() {
@@ -45,7 +44,7 @@ fn basic_end_to_end_works() {
let delay = 5;
let calls = vec![
RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }),
];
// Set calls for the lottery
@@ -56,7 +55,10 @@ fn basic_end_to_end_works() {
assert!(crate::Lottery::<Test>::get().is_some());
assert_eq!(Balances::free_balance(&1), 100);
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 20 }));
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death {
dest: 2,
value: 20,
}));
assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call.clone()));
// 20 from the transfer, 10 from buying a ticket
assert_eq!(Balances::free_balance(&1), 100 - 20 - 10);
@@ -129,7 +131,7 @@ fn set_calls_works() {
let calls = vec![
RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }),
];
assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls));
@@ -137,7 +139,7 @@ fn set_calls_works() {
let too_many_calls = vec![
RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }),
RuntimeCall::System(SystemCall::remark { remark: vec![] }),
];
@@ -157,7 +159,7 @@ fn call_to_indices_works() {
new_test_ext().execute_with(|| {
let calls = vec![
RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }),
];
let indices = Lottery::calls_to_indices(&calls).unwrap().into_inner();
// Only comparing the length since it is otherwise dependant on the API
@@ -166,7 +168,7 @@ fn call_to_indices_works() {
let too_many_calls = vec![
RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }),
RuntimeCall::System(SystemCall::remark { remark: vec![] }),
];
assert_noop!(Lottery::calls_to_indices(&too_many_calls), Error::<Test>::TooManyCalls);
@@ -203,7 +205,10 @@ fn buy_ticket_works_as_simple_passthrough() {
// as a simple passthrough to the real call.
new_test_ext().execute_with(|| {
// No lottery set up
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 20 }));
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death {
dest: 2,
value: 20,
}));
// This is just a basic transfer then
assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call.clone()));
assert_eq!(Balances::free_balance(&1), 100 - 20);
@@ -212,7 +217,7 @@ fn buy_ticket_works_as_simple_passthrough() {
// Lottery is set up, but too expensive to enter, so `do_buy_ticket` fails.
let calls = vec![
RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }),
];
assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls));
@@ -223,11 +228,13 @@ fn buy_ticket_works_as_simple_passthrough() {
assert_eq!(TicketsCount::<Test>::get(), 0);
// If call would fail, the whole thing still fails the same
let fail_call =
Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 1000 }));
let fail_call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death {
dest: 2,
value: 1000,
}));
assert_noop!(
Lottery::buy_ticket(RuntimeOrigin::signed(1), fail_call),
BalancesError::<Test, _>::InsufficientBalance,
ArithmeticError::Underflow,
);
let bad_origin_call = Box::new(RuntimeCall::Balances(BalancesCall::force_transfer {
@@ -243,8 +250,10 @@ fn buy_ticket_works_as_simple_passthrough() {
assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(2), remark_call));
assert_eq!(TicketsCount::<Test>::get(), 0);
let successful_call =
Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 1 }));
let successful_call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death {
dest: 2,
value: 1,
}));
assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(2), successful_call));
assert_eq!(TicketsCount::<Test>::get(), 1);
});
@@ -256,12 +265,15 @@ fn buy_ticket_works() {
// Set calls for the lottery.
let calls = vec![
RuntimeCall::System(SystemCall::remark { remark: vec![] }),
RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }),
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }),
];
assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls));
// Can't buy ticket before start
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 1 }));
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death {
dest: 2,
value: 1,
}));
assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call.clone()));
assert_eq!(TicketsCount::<Test>::get(), 0);
@@ -274,7 +286,10 @@ fn buy_ticket_works() {
assert_eq!(TicketsCount::<Test>::get(), 1);
// Can't buy another of the same ticket (even if call is slightly changed)
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 3, value: 30 }));
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death {
dest: 3,
value: 30,
}));
assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call));
assert_eq!(TicketsCount::<Test>::get(), 1);
@@ -302,7 +317,8 @@ fn buy_ticket_works() {
#[test]
fn do_buy_ticket_already_participating() {
new_test_ext().execute_with(|| {
let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })];
let calls =
vec![RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 })];
assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone()));
assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 1, 10, 10, false));
@@ -317,7 +333,8 @@ fn do_buy_ticket_already_participating() {
#[test]
fn buy_ticket_already_participating() {
new_test_ext().execute_with(|| {
let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })];
let calls =
vec![RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 })];
assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone()));
assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 1, 10, 10, false));
@@ -337,7 +354,8 @@ fn buy_ticket_already_participating() {
#[test]
fn buy_ticket_insufficient_balance() {
new_test_ext().execute_with(|| {
let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })];
let calls =
vec![RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 })];
assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone()));
// Price set to 100.
assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 100, 10, 10, false));
@@ -352,16 +370,14 @@ fn buy_ticket_insufficient_balance() {
#[test]
fn do_buy_ticket_insufficient_balance() {
new_test_ext().execute_with(|| {
let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })];
let calls =
vec![RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 })];
assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone()));
// Price set to 101.
assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 101, 10, 10, false));
// Buying fails with InsufficientBalance.
assert_noop!(
Lottery::do_buy_ticket(&1, &calls[0]),
BalancesError::<Test, _>::InsufficientBalance
);
assert_noop!(Lottery::do_buy_ticket(&1, &calls[0]), TokenError::FundsUnavailable,);
assert!(TicketsCount::<Test>::get().is_zero());
});
}
@@ -369,13 +385,13 @@ fn do_buy_ticket_insufficient_balance() {
#[test]
fn do_buy_ticket_keep_alive() {
new_test_ext().execute_with(|| {
let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })];
let calls =
vec![RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 })];
assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone()));
// Price set to 100.
assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 100, 10, 10, false));
// Buying fails with KeepAlive.
assert_noop!(Lottery::do_buy_ticket(&1, &calls[0]), BalancesError::<Test, _>::KeepAlive);
assert_noop!(Lottery::do_buy_ticket(&1, &calls[0]), TokenError::NotExpendable);
assert!(TicketsCount::<Test>::get().is_zero());
});
}
@@ -421,7 +437,8 @@ fn choose_ticket_trivial_cases() {
#[test]
fn choose_account_one_participant() {
new_test_ext().execute_with(|| {
let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })];
let calls =
vec![RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 })];
assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone()));
assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 10, 10, 10, false));
let call = Box::new(calls[0].clone());
+38 -34
View File
@@ -30,6 +30,7 @@ use sp_core::H256;
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup},
TokenError,
};
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
@@ -84,6 +85,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
pub struct TestBaseCallFilter;
@@ -107,7 +112,7 @@ impl Config for Test {
type WeightInfo = ();
}
use pallet_balances::{Call as BalancesCall, Error as BalancesError};
use pallet_balances::Call as BalancesCall;
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
@@ -126,16 +131,16 @@ fn now() -> Timepoint<u64> {
}
fn call_transfer(dest: u64, value: u64) -> Box<RuntimeCall> {
Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest, value }))
Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest, value }))
}
#[test]
fn multisig_deposit_is_taken_and_returned() {
new_test_ext().execute_with(|| {
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2);
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
let call = call_transfer(6, 15);
let call_weight = call.get_dispatch_info().weight;
@@ -196,9 +201,9 @@ fn cancel_multisig_returns_deposit() {
fn timepoint_checking_works() {
new_test_ext().execute_with(|| {
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2);
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
let call = call_transfer(6, 15);
let hash = blake2_256(&call.encode());
@@ -254,9 +259,9 @@ fn timepoint_checking_works() {
fn multisig_2_of_3_works() {
new_test_ext().execute_with(|| {
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2);
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
let call = call_transfer(6, 15);
let call_weight = call.get_dispatch_info().weight;
@@ -287,9 +292,9 @@ fn multisig_2_of_3_works() {
fn multisig_3_of_3_works() {
new_test_ext().execute_with(|| {
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 3);
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
let call = call_transfer(6, 15);
let call_weight = call.get_dispatch_info().weight;
@@ -357,9 +362,9 @@ fn cancel_multisig_works() {
fn multisig_2_of_3_as_multi_works() {
new_test_ext().execute_with(|| {
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2);
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
let call = call_transfer(6, 15);
let call_weight = call.get_dispatch_info().weight;
@@ -389,9 +394,9 @@ fn multisig_2_of_3_as_multi_works() {
fn multisig_2_of_3_as_multi_with_many_calls_works() {
new_test_ext().execute_with(|| {
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2);
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
let call1 = call_transfer(6, 10);
let call1_weight = call1.get_dispatch_info().weight;
@@ -440,9 +445,9 @@ fn multisig_2_of_3_as_multi_with_many_calls_works() {
fn multisig_2_of_3_cannot_reissue_same_call() {
new_test_ext().execute_with(|| {
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2);
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
let call = call_transfer(6, 10);
let call_weight = call.get_dispatch_info().weight;
@@ -482,14 +487,13 @@ fn multisig_2_of_3_cannot_reissue_same_call() {
call_weight
));
let err = DispatchError::from(BalancesError::<Test, _>::InsufficientBalance).stripped();
System::assert_last_event(
pallet_multisig::Event::MultisigExecuted {
approving: 3,
timepoint: now(),
multisig: multi,
call_hash: hash,
result: Err(err),
result: Err(TokenError::FundsUnavailable.into()),
}
.into(),
);
@@ -593,9 +597,9 @@ fn duplicate_approvals_are_ignored() {
fn multisig_1_of_3_works() {
new_test_ext().execute_with(|| {
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 1);
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
let call = call_transfer(6, 15);
let hash = blake2_256(&call.encode());
@@ -646,9 +650,9 @@ fn multisig_filters() {
fn weight_check_works() {
new_test_ext().execute_with(|| {
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2);
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
let call = call_transfer(6, 15);
assert_ok!(Multisig::as_multi(
@@ -682,9 +686,9 @@ fn multisig_handles_no_preimage_after_all_approve() {
// the call will go through.
new_test_ext().execute_with(|| {
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 3);
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
let call = call_transfer(6, 15);
let call_weight = call.get_dispatch_info().weight;
+4
View File
@@ -89,6 +89,10 @@ impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ConstU32<50>;
type ReserveIdentifier = [u8; 8];
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
parameter_types! {
+4
View File
@@ -295,6 +295,10 @@ mod tests {
type ExistentialDeposit = ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
ord_parameter_types! {
+4 -1
View File
@@ -1,2 +1,5 @@
# NIS Module
License: Apache-2.0
Provides a non-interactiove variant of staking.
License: Apache-2.0
+65 -41
View File
@@ -21,7 +21,9 @@
use super::*;
use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller, BenchmarkError};
use frame_support::traits::{nonfungible::Inspect, Currency, EnsureOrigin, Get};
use frame_support::traits::{
fungible::Inspect as FunInspect, nonfungible::Inspect, EnsureOrigin, Get,
};
use frame_system::RawOrigin;
use sp_arithmetic::Perquintill;
use sp_runtime::{
@@ -35,7 +37,7 @@ use crate::Pallet as Nis;
const SEED: u32 = 0;
type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
<<T as Config>::Currency as FunInspect<<T as frame_system::Config>::AccountId>>::Balance;
fn fill_queues<T: Config>() -> Result<(), DispatchError> {
// filling queues involves filling the first queue entirely and placing a single item in all
@@ -45,10 +47,7 @@ fn fill_queues<T: Config>() -> Result<(), DispatchError> {
let bids = T::MaxQueueLen::get();
let caller: T::AccountId = whitelisted_caller();
T::Currency::make_free_balance_be(
&caller,
T::MinBid::get() * BalanceOf::<T>::from(queues + bids),
);
T::Currency::set_balance(&caller, T::MinBid::get() * BalanceOf::<T>::from(queues + bids));
for _ in 0..bids {
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
@@ -63,7 +62,9 @@ benchmarks! {
place_bid {
let l in 0..(T::MaxQueueLen::get() - 1);
let caller: T::AccountId = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
let ed = T::Currency::minimum_balance();
let bid = T::MinBid::get();
T::Currency::set_balance(&caller, (ed + bid) * BalanceOf::<T>::from(l + 1) + bid);
for i in 0..l {
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
}
@@ -75,7 +76,10 @@ benchmarks! {
place_bid_max {
let caller: T::AccountId = whitelisted_caller();
let origin = RawOrigin::Signed(caller.clone());
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
let ed = T::Currency::minimum_balance();
let bid = T::MinBid::get();
let ql = T::MaxQueueLen::get();
T::Currency::set_balance(&caller, (ed + bid) * BalanceOf::<T>::from(ql + 1) + bid);
for i in 0..T::MaxQueueLen::get() {
Nis::<T>::place_bid(origin.clone().into(), T::MinBid::get(), 1)?;
}
@@ -90,7 +94,9 @@ benchmarks! {
retract_bid {
let l in 1..T::MaxQueueLen::get();
let caller: T::AccountId = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
let ed = T::Currency::minimum_balance();
let bid = T::MinBid::get();
T::Currency::set_balance(&caller, (ed + bid) * BalanceOf::<T>::from(l + 1) + bid);
for i in 0..l {
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
}
@@ -104,50 +110,41 @@ benchmarks! {
T::FundOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
let caller: T::AccountId = whitelisted_caller();
let bid = T::MinBid::get().max(One::one());
T::Currency::make_free_balance_be(&caller, bid);
let ed = T::Currency::minimum_balance();
T::Currency::set_balance(&caller, ed + bid);
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
Nis::<T>::process_queues(Perquintill::one(), 1, 1, &mut WeightCounter::unlimited());
Nis::<T>::communify(RawOrigin::Signed(caller.clone()).into(), 0)?;
let original = T::Currency::free_balance(&Nis::<T>::account_id());
T::Currency::make_free_balance_be(&Nis::<T>::account_id(), BalanceOf::<T>::min_value());
let original = T::Currency::balance(&Nis::<T>::account_id());
T::Currency::set_balance(&Nis::<T>::account_id(), BalanceOf::<T>::min_value());
}: _<T::RuntimeOrigin>(origin)
verify {
// Must fund at least 99.999% of the required amount.
let missing = Perquintill::from_rational(
T::Currency::free_balance(&Nis::<T>::account_id()), original).left_from_one();
T::Currency::balance(&Nis::<T>::account_id()), original).left_from_one();
assert!(missing <= Perquintill::one() / 100_000);
}
thaw_private {
communify {
let caller: T::AccountId = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, T::MinBid::get() * BalanceOf::<T>::from(3u32));
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
let bid = T::MinBid::get().max(One::one()) * 100u32.into();
let ed = T::Currency::minimum_balance();
T::Currency::set_balance(&caller, ed + bid + bid);
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
Nis::<T>::process_queues(Perquintill::one(), 1, 2, &mut WeightCounter::unlimited());
Receipts::<T>::mutate(0, |m_g| if let Some(ref mut g) = m_g { g.expiry = Zero::zero() });
}: _(RawOrigin::Signed(caller.clone()), 0, None)
verify {
assert!(Receipts::<T>::get(0).is_none());
}
thaw_communal {
let caller: T::AccountId = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, T::MinBid::get() * BalanceOf::<T>::from(3u32));
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
Nis::<T>::process_queues(Perquintill::one(), 1, 2, &mut WeightCounter::unlimited());
Receipts::<T>::mutate(0, |m_g| if let Some(ref mut g) = m_g { g.expiry = Zero::zero() });
Nis::<T>::communify(RawOrigin::Signed(caller.clone()).into(), 0)?;
}: _(RawOrigin::Signed(caller.clone()), 0)
verify {
assert!(Receipts::<T>::get(0).is_none());
assert_eq!(Nis::<T>::owner(&0), None);
}
privatize {
let caller: T::AccountId = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, T::MinBid::get() * BalanceOf::<T>::from(3u32));
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
let bid = T::MinBid::get().max(One::one());
let ed = T::Currency::minimum_balance();
T::Currency::set_balance(&caller, ed + bid + bid);
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
Nis::<T>::process_queues(Perquintill::one(), 1, 2, &mut WeightCounter::unlimited());
Nis::<T>::communify(RawOrigin::Signed(caller.clone()).into(), 0)?;
}: _(RawOrigin::Signed(caller.clone()), 0)
@@ -155,15 +152,39 @@ benchmarks! {
assert_eq!(Nis::<T>::owner(&0), Some(caller));
}
communify {
thaw_private {
let whale: T::AccountId = account("whale", 0, SEED);
let caller: T::AccountId = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, T::MinBid::get() * BalanceOf::<T>::from(3u32));
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
let bid = T::MinBid::get().max(One::one());
let ed = T::Currency::minimum_balance();
T::Currency::set_balance(&caller, ed + bid + bid);
// Ensure we don't get throttled.
T::Currency::set_balance(&whale, T::ThawThrottle::get().0.saturating_reciprocal_mul_ceil(T::Currency::balance(&caller)));
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
Nis::<T>::process_queues(Perquintill::one(), 1, 2, &mut WeightCounter::unlimited());
frame_system::Pallet::<T>::set_block_number(Receipts::<T>::get(0).unwrap().expiry);
}: _(RawOrigin::Signed(caller.clone()), 0, None)
verify {
assert!(Receipts::<T>::get(0).is_none());
}
thaw_communal {
let whale: T::AccountId = account("whale", 0, SEED);
let caller: T::AccountId = whitelisted_caller();
let bid = T::MinBid::get().max(One::one());
let ed = T::Currency::minimum_balance();
T::Currency::set_balance(&caller, ed + bid + bid);
// Ensure we don't get throttled.
T::Currency::set_balance(&whale, T::ThawThrottle::get().0.saturating_reciprocal_mul_ceil(T::Currency::balance(&caller)));
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
Nis::<T>::process_queues(Perquintill::one(), 1, 2, &mut WeightCounter::unlimited());
frame_system::Pallet::<T>::set_block_number(Receipts::<T>::get(0).unwrap().expiry);
Nis::<T>::communify(RawOrigin::Signed(caller.clone()).into(), 0)?;
}: _(RawOrigin::Signed(caller.clone()), 0)
verify {
assert_eq!(Nis::<T>::owner(&0), None);
assert!(Receipts::<T>::get(0).is_none());
}
process_queues {
@@ -197,6 +218,9 @@ benchmarks! {
process_bid {
let who = account::<T::AccountId>("bidder", 0, SEED);
let min_bid = T::MinBid::get().max(One::one());
let ed = T::Currency::minimum_balance();
T::Currency::set_balance(&who, ed + min_bid);
let bid = Bid {
amount: T::MinBid::get(),
who,
@@ -216,5 +240,5 @@ benchmarks! {
)
}
impl_benchmark_test_suite!(Nis, crate::mock::new_test_ext(), crate::mock::Test);
impl_benchmark_test_suite!(Nis, crate::mock::new_test_ext_empty(), crate::mock::Test);
}
+121 -119
View File
@@ -71,21 +71,21 @@
//!
//! ## Terms
//!
//! - *Effective total issuance*: The total issuance of balances in the system, including all claims
//! of all outstanding receipts but excluding `IgnoredIssuance`.
//! - *Effective total issuance*: The total issuance of balances in the system, equal to the active
//! issuance plus the value of all outstanding receipts, less `IgnoredIssuance`.
#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::{
dispatch::{DispatchError, DispatchResult},
traits::fungible::{Inspect as FungibleInspect, Mutate as FungibleMutate},
use frame_support::traits::{
fungible::{self, Inspect as FunInspect, Mutate as FunMutate},
tokens::{DepositConsequence, Fortitude, Preservation, Provenance, WithdrawConsequence},
};
pub use pallet::*;
use sp_arithmetic::{traits::Unsigned, RationalArg};
use sp_core::TypedGet;
use sp_runtime::{
traits::{Convert, ConvertBack},
Perquintill,
DispatchError, Perquintill,
};
mod benchmarking;
@@ -117,7 +117,7 @@ where
}
pub struct NoCounterpart<T>(sp_std::marker::PhantomData<T>);
impl<T> FungibleInspect<T> for NoCounterpart<T> {
impl<T> FunInspect<T> for NoCounterpart<T> {
type Balance = u32;
fn total_issuance() -> u32 {
0
@@ -125,34 +125,30 @@ impl<T> FungibleInspect<T> for NoCounterpart<T> {
fn minimum_balance() -> u32 {
0
}
fn balance(_who: &T) -> u32 {
fn balance(_: &T) -> u32 {
0
}
fn reducible_balance(_who: &T, _keep_alive: bool) -> u32 {
fn total_balance(_: &T) -> u32 {
0
}
fn can_deposit(
_who: &T,
_amount: u32,
_mint: bool,
) -> frame_support::traits::tokens::DepositConsequence {
frame_support::traits::tokens::DepositConsequence::Success
fn reducible_balance(_: &T, _: Preservation, _: Fortitude) -> u32 {
0
}
fn can_withdraw(
_who: &T,
_amount: u32,
) -> frame_support::traits::tokens::WithdrawConsequence<u32> {
frame_support::traits::tokens::WithdrawConsequence::Success
fn can_deposit(_: &T, _: u32, _: Provenance) -> DepositConsequence {
DepositConsequence::Success
}
fn can_withdraw(_: &T, _: u32) -> WithdrawConsequence<u32> {
WithdrawConsequence::Success
}
}
impl<T> FungibleMutate<T> for NoCounterpart<T> {
fn mint_into(_who: &T, _amount: u32) -> DispatchResult {
Ok(())
}
fn burn_from(_who: &T, _amount: u32) -> Result<u32, DispatchError> {
Ok(0)
impl<T> fungible::Unbalanced<T> for NoCounterpart<T> {
fn handle_dust(_: fungible::Dust<T, Self>) {}
fn write_balance(_: &T, _: Self::Balance) -> Result<Option<Self::Balance>, DispatchError> {
Ok(None)
}
fn set_total_issuance(_: Self::Balance) {}
}
impl<T> FunMutate<T> for NoCounterpart<T> {}
impl<T> Convert<Perquintill, u32> for NoCounterpart<T> {
fn convert(_: Perquintill) -> u32 {
0
@@ -161,15 +157,24 @@ impl<T> Convert<Perquintill, u32> for NoCounterpart<T> {
#[frame_support::pallet]
pub mod pallet {
use super::{FungibleInspect, FungibleMutate};
use super::{FunInspect, FunMutate};
pub use crate::weights::WeightInfo;
use frame_support::{
pallet_prelude::*,
traits::{
nonfungible::{Inspect as NonfungibleInspect, Transfer as NonfungibleTransfer},
Currency, Defensive, DefensiveSaturating,
ExistenceRequirement::AllowDeath,
NamedReservableCurrency, OnUnbalanced,
fungible::{
self,
hold::{Inspect as FunHoldInspect, Mutate as FunHoldMutate},
Balanced as FunBalanced,
},
nonfungible::{Inspect as NftInspect, Transfer as NftTransfer},
tokens::{
Fortitude::Polite,
Precision::{BestEffort, Exact},
Preservation::Expendable,
Restriction::{Free, OnHold},
},
Defensive, DefensiveSaturating, OnUnbalanced,
},
PalletId,
};
@@ -177,15 +182,14 @@ pub mod pallet {
use sp_arithmetic::{PerThing, Perquintill};
use sp_runtime::{
traits::{AccountIdConversion, Bounded, Convert, ConvertBack, Saturating, Zero},
TokenError,
Rounding, TokenError,
};
use sp_std::prelude::*;
type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
type PositiveImbalanceOf<T> = <<T as Config>::Currency as Currency<
<T as frame_system::Config>::AccountId,
>>::PositiveImbalance;
<<T as Config>::Currency as FunInspect<<T as frame_system::Config>::AccountId>>::Balance;
type DebtOf<T> =
fungible::Debt<<T as frame_system::Config>::AccountId, <T as Config>::Currency>;
type ReceiptRecordOf<T> = ReceiptRecord<
<T as frame_system::Config>::AccountId,
<T as frame_system::Config>::BlockNumber,
@@ -209,7 +213,16 @@ pub mod pallet {
type PalletId: Get<PalletId>;
/// Currency type that this works on.
type Currency: NamedReservableCurrency<Self::AccountId, Balance = Self::CurrencyBalance>;
type Currency: FunInspect<Self::AccountId, Balance = Self::CurrencyBalance>
+ FunMutate<Self::AccountId>
+ FunBalanced<Self::AccountId>
+ FunHoldInspect<Self::AccountId>
+ FunHoldMutate<Self::AccountId>;
/// The identifier of the hold reason.
#[pallet::constant]
type HoldReason: Get<<Self::Currency as FunHoldInspect<Self::AccountId>>::Reason>;
/// Just the `Currency::Balance` type; we have this item to allow us to constrain it to
/// `From<u64>`.
@@ -231,7 +244,7 @@ pub mod pallet {
type IgnoredIssuance: Get<BalanceOf<Self>>;
/// The accounting system for the fungible counterpart tokens.
type Counterpart: FungibleMutate<Self::AccountId>;
type Counterpart: FunMutate<Self::AccountId>;
/// The system to convert an overall proportion of issuance into a number of fungible
/// counterpart tokens.
@@ -239,12 +252,12 @@ pub mod pallet {
/// In general it's best to use `WithMaximumOf`.
type CounterpartAmount: ConvertBack<
Perquintill,
<Self::Counterpart as FungibleInspect<Self::AccountId>>::Balance,
<Self::Counterpart as FunInspect<Self::AccountId>>::Balance,
>;
/// Unbalanced handler to account for funds created (in case of a higher total issuance over
/// freezing period).
type Deficit: OnUnbalanced<PositiveImbalanceOf<Self>>;
type Deficit: OnUnbalanced<DebtOf<Self>>;
/// The target sum of all receipts' proportions.
type Target: Get<Perquintill>;
@@ -301,12 +314,6 @@ pub mod pallet {
/// The maximum proportion which may be thawed and the period over which it is reset.
#[pallet::constant]
type ThawThrottle: Get<(Perquintill, Self::BlockNumber)>;
/// The name for the reserve ID.
#[pallet::constant]
type ReserveId: Get<
<Self::Currency as NamedReservableCurrency<Self::AccountId>>::ReserveIdentifier,
>;
}
#[pallet::pallet]
@@ -348,7 +355,7 @@ pub mod pallet {
///
/// `issuance - frozen + proportion * issuance`
///
/// where `issuance = total_issuance - IgnoredIssuance`
/// where `issuance = active_issuance - IgnoredIssuance`
#[derive(
Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen,
)]
@@ -473,6 +480,9 @@ pub mod pallet {
AlreadyCommunal,
/// The receipt is already private.
AlreadyPrivate,
Release1,
Release2,
Tah,
}
pub(crate) struct WeightCounter {
@@ -554,16 +564,17 @@ pub mod pallet {
|q| -> Result<(u32, BalanceOf<T>), DispatchError> {
let queue_full = q.len() == T::MaxQueueLen::get() as usize;
ensure!(!queue_full || q[0].amount < amount, Error::<T>::BidTooLow);
T::Currency::reserve_named(&T::ReserveId::get(), &who, amount)?;
T::Currency::hold(&T::HoldReason::get(), &who, amount)?;
// queue is <Ordered: Lowest ... Highest><Fifo: Last ... First>
let mut bid = Bid { amount, who: who.clone() };
let net = if queue_full {
sp_std::mem::swap(&mut q[0], &mut bid);
let _ = T::Currency::unreserve_named(
&T::ReserveId::get(),
let _ = T::Currency::release(
&T::HoldReason::get(),
&bid.who,
bid.amount,
BestEffort,
);
Self::deposit_event(Event::<T>::BidDropped {
who: bid.who,
@@ -615,19 +626,21 @@ pub mod pallet {
ensure!(queue_index < queue_count, Error::<T>::DurationTooBig);
let bid = Bid { amount, who };
let new_len = Queues::<T>::try_mutate(duration, |q| -> Result<u32, DispatchError> {
let pos = q.iter().position(|i| i == &bid).ok_or(Error::<T>::UnknownBid)?;
q.remove(pos);
Ok(q.len() as u32)
})?;
let mut queue = Queues::<T>::get(duration);
let pos = queue.iter().position(|i| i == &bid).ok_or(Error::<T>::UnknownBid)?;
queue.remove(pos);
let new_len = queue.len() as u32;
T::Currency::release(&T::HoldReason::get(), &bid.who, bid.amount, BestEffort)?;
Queues::<T>::insert(duration, queue);
QueueTotals::<T>::mutate(|qs| {
qs.bounded_resize(queue_count, (0, Zero::zero()));
qs[queue_index].0 = new_len;
qs[queue_index].1.saturating_reduce(bid.amount);
});
T::Currency::unreserve_named(&T::ReserveId::get(), &bid.who, bid.amount);
Self::deposit_event(Event::BidRetracted { who: bid.who, amount: bid.amount, duration });
Ok(())
@@ -645,7 +658,7 @@ pub mod pallet {
let issuance = Self::issuance_with(&our_account, &summary);
let deficit = issuance.required.saturating_sub(issuance.holdings);
ensure!(!deficit.is_zero(), Error::<T>::AlreadyFunded);
T::Deficit::on_unbalanced(T::Currency::deposit_creating(&our_account, deficit));
T::Deficit::on_unbalanced(T::Currency::deposit(&our_account, deficit, Exact)?);
Self::deposit_event(Event::<T>::Funded { deficit });
Ok(())
}
@@ -702,6 +715,7 @@ pub mod pallet {
// Multiply the proportion it is by the total issued.
let our_account = Self::account_id();
let effective_issuance = Self::issuance_with(&our_account, &summary).effective;
// let amount = proportion.mul_ceil(effective_issuance);
let amount = proportion * effective_issuance;
receipt.proportion.saturating_reduce(proportion);
@@ -710,46 +724,40 @@ pub mod pallet {
let dropped = receipt.proportion.is_zero();
if amount > on_hold {
T::Currency::unreserve_named(&T::ReserveId::get(), &who, on_hold);
T::Currency::release(&T::HoldReason::get(), &who, on_hold, Exact)
.map_err(|_| Error::<T>::Release1)?;
let deficit = amount - on_hold;
// Try to transfer deficit from pot to receipt owner.
summary.receipts_on_hold.saturating_reduce(on_hold);
on_hold = Zero::zero();
T::Currency::transfer(&our_account, &who, deficit, AllowDeath)
T::Currency::transfer(&our_account, &who, deficit, Expendable)
.map_err(|_| Error::<T>::Unfunded)?;
} else {
T::Currency::unreserve_named(&T::ReserveId::get(), &who, amount);
on_hold.saturating_reduce(amount);
summary.receipts_on_hold.saturating_reduce(amount);
if dropped && !on_hold.is_zero() {
// Reclaim any remainder:
// Transfer `excess` to the pot if we have now fully compensated for the
// receipt.
//
// This will legitimately fail if there is no pot account in existance.
// There's nothing we can do about this so we just swallow the error.
// This code is not ideal and could fail in the second phase leaving
// the system in an invalid state. It can be fixed properly with the
// new API in https://github.com/paritytech/substrate/pull/12951
//
// Below is what it should look like then:
// let _ = T::Currency::repatriate_reserved_named(
// &T::ReserveId::get(),
// &who,
// &our_account,
// excess,
// BalanceStatus::Free,
// ).defensive();
T::Currency::unreserve_named(&T::ReserveId::get(), &who, on_hold);
// It could theoretically be locked, so really we should be using a more
// forceful variant. But the alternative `repatriate_reserved_named` will
// fail if the destination account doesn't exist. This should be fixed when
// we move to the `fungible::*` traits, which should include a force
// transfer function to transfer the reserved balance into free balance in
// the destination regardless of locks and create it if it doesn't exist.
let _ = T::Currency::transfer(&who, &Self::account_id(), on_hold, AllowDeath);
// Transfer excess of `on_hold` to the pot if we have now fully compensated for
// the receipt.
T::Currency::transfer_on_hold(
&T::HoldReason::get(),
&who,
&our_account,
on_hold,
Exact,
Free,
Polite,
)
.map(|_| ())
// We ignore this error as it just means the amount we're trying to deposit is
// dust and the beneficiary account doesn't exist.
.or_else(
|e| if e == TokenError::CannotCreate.into() { Ok(()) } else { Err(e) },
)?;
summary.receipts_on_hold.saturating_reduce(on_hold);
}
T::Currency::release(&T::HoldReason::get(), &who, amount, Exact)
.map_err(|_| Error::<T>::Release2)?;
}
if dropped {
@@ -797,7 +805,8 @@ pub mod pallet {
summary.thawed.saturating_accrue(receipt.proportion);
ensure!(summary.thawed <= throttle, Error::<T>::Throttled);
T::Counterpart::burn_from(&who, T::CounterpartAmount::convert(receipt.proportion))?;
let cp_amount = T::CounterpartAmount::convert(receipt.proportion);
T::Counterpart::burn_from(&who, cp_amount, Exact, Polite)?;
// Multiply the proportion it is by the total issued.
let our_account = Self::account_id();
@@ -807,7 +816,7 @@ pub mod pallet {
summary.proportion_owed.saturating_reduce(receipt.proportion);
// Try to transfer amount owed from pot to receipt owner.
T::Currency::transfer(&our_account, &who, amount, AllowDeath)
T::Currency::transfer(&our_account, &who, amount, Expendable)
.map_err(|_| Error::<T>::Unfunded)?;
Receipts::<T>::remove(index);
@@ -840,11 +849,10 @@ pub mod pallet {
ensure!(owner == who, Error::<T>::NotOwner);
// Unreserve and transfer the funds to the pot.
T::Currency::unreserve_named(&T::ReserveId::get(), &who, on_hold);
// Transfer `excess` to the pot if we have now fully compensated for the receipt.
T::Currency::transfer(&who, &Self::account_id(), on_hold, AllowDeath)
let reason = T::HoldReason::get();
let us = Self::account_id();
T::Currency::transfer_on_hold(&reason, &who, &us, on_hold, Exact, Free, Polite)
.map_err(|_| Error::<T>::Unfunded)?;
// TODO #12951: ^^^ The above should be done in a single operation `transfer_on_hold`.
// Record that we've moved the amount reserved.
let mut summary: SummaryRecordOf<T> = Summary::<T>::get();
@@ -881,16 +889,20 @@ pub mod pallet {
let effective_issuance = Self::issuance_with(&our_account, &summary).effective;
let max_amount = receipt.proportion * effective_issuance;
// Avoid trying to place more in the account's reserve than we have available in the pot
let amount = max_amount.min(T::Currency::free_balance(&our_account));
let amount = max_amount.min(T::Currency::balance(&our_account));
// Burn fungible counterparts.
T::Counterpart::burn_from(&who, T::CounterpartAmount::convert(receipt.proportion))?;
T::Counterpart::burn_from(
&who,
T::CounterpartAmount::convert(receipt.proportion),
Exact,
Polite,
)?;
// Transfer the funds from the pot to the owner and reserve
T::Currency::transfer(&Self::account_id(), &who, amount, AllowDeath)
.map_err(|_| Error::<T>::Unfunded)?;
T::Currency::reserve_named(&T::ReserveId::get(), &who, amount)?;
// TODO: ^^^ The above should be done in a single operation `transfer_and_hold`.
let reason = T::HoldReason::get();
let us = Self::account_id();
T::Currency::transfer_and_hold(&reason, &us, &who, amount, Exact, Expendable, Polite)?;
// Record that we've moved the amount reserved.
summary.receipts_on_hold.saturating_accrue(amount);
@@ -920,7 +932,7 @@ pub mod pallet {
pub required: Balance,
}
impl<T: Config> NonfungibleInspect<T::AccountId> for Pallet<T> {
impl<T: Config> NftInspect<T::AccountId> for Pallet<T> {
type ItemId = ReceiptIndex;
fn owner(item: &ReceiptIndex) -> Option<T::AccountId> {
@@ -939,31 +951,19 @@ pub mod pallet {
}
}
impl<T: Config> NonfungibleTransfer<T::AccountId> for Pallet<T> {
fn transfer(index: &ReceiptIndex, destination: &T::AccountId) -> DispatchResult {
impl<T: Config> NftTransfer<T::AccountId> for Pallet<T> {
fn transfer(index: &ReceiptIndex, dest: &T::AccountId) -> DispatchResult {
let mut item = Receipts::<T>::get(index).ok_or(TokenError::UnknownAsset)?;
let (owner, on_hold) = item.owner.take().ok_or(Error::<T>::AlreadyCommunal)?;
// TODO: This should all be replaced by a single call `transfer_held`.
let shortfall = T::Currency::unreserve_named(&T::ReserveId::get(), &owner, on_hold);
if !shortfall.is_zero() {
let _ =
T::Currency::reserve_named(&T::ReserveId::get(), &owner, on_hold - shortfall);
return Err(TokenError::NoFunds.into())
}
if let Err(e) = T::Currency::transfer(&owner, destination, on_hold, AllowDeath) {
let _ = T::Currency::reserve_named(&T::ReserveId::get(), &owner, on_hold);
return Err(e)
}
// This can never fail, and if it somehow does, then we can't handle this gracefully.
let _ =
T::Currency::reserve_named(&T::ReserveId::get(), destination, on_hold).defensive();
let reason = T::HoldReason::get();
T::Currency::transfer_on_hold(&reason, &owner, dest, on_hold, Exact, OnHold, Polite)?;
item.owner = Some((destination.clone(), on_hold));
item.owner = Some((dest.clone(), on_hold));
Receipts::<T>::insert(&index, &item);
Pallet::<T>::deposit_event(Event::<T>::Transferred {
from: owner,
to: destination.clone(),
to: dest.clone(),
index: *index,
});
Ok(())
@@ -997,9 +997,9 @@ pub mod pallet {
summary: &SummaryRecordOf<T>,
) -> IssuanceInfo<BalanceOf<T>> {
let total_issuance =
T::Currency::total_issuance().saturating_sub(T::IgnoredIssuance::get());
T::Currency::active_issuance().saturating_sub(T::IgnoredIssuance::get());
let holdings =
T::Currency::free_balance(our_account).saturating_add(summary.receipts_on_hold);
T::Currency::balance(our_account).saturating_add(summary.receipts_on_hold);
let other = total_issuance.saturating_sub(holdings);
let effective =
summary.proportion_owed.left_from_one().saturating_reciprocal_mul(other);
@@ -1136,7 +1136,9 @@ pub mod pallet {
// Now to activate the bid...
let n = amount;
let d = issuance.effective;
let proportion = Perquintill::from_rational(n, d);
let proportion =
Perquintill::from_rational_with_rounding(n, d, Rounding::NearestPrefDown)
.defensive_unwrap_or_default();
let who = bid.who;
let index = summary.index;
summary.proportion_owed.defensive_saturating_accrue(proportion);
+39 -11
View File
@@ -19,13 +19,18 @@
use crate::{self as pallet_nis, Perquintill, WithMaximumOf};
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{
ord_parameter_types, parameter_types,
traits::{ConstU16, ConstU32, ConstU64, Currency, OnFinalize, OnInitialize, StorageMapShim},
traits::{
fungible::Inspect, ConstU16, ConstU32, ConstU64, Everything, OnFinalize, OnInitialize,
StorageMapShim,
},
weights::Weight,
PalletId,
};
use pallet_balances::{Instance1, Instance2};
use scale_info::TypeInfo;
use sp_core::{ConstU128, H256};
use sp_runtime::{
testing::Header,
@@ -35,6 +40,8 @@ use sp_runtime::{
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
pub type Balance = u64;
// Configure a mock runtime to test the pallet.
frame_support::construct_runtime!(
pub enum Test where
@@ -50,7 +57,7 @@ frame_support::construct_runtime!(
);
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BaseCallFilter = Everything;
type BlockWeights = ();
type BlockLength = ();
type RuntimeOrigin = RuntimeOrigin;
@@ -67,35 +74,45 @@ impl frame_system::Config for Test {
type DbWeight = ();
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<u64>;
type AccountData = pallet_balances::AccountData<Balance>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ConstU16<42>;
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
type MaxConsumers = ConstU32<16>;
}
impl pallet_balances::Config<Instance1> for Test {
type Balance = u64;
type Balance = Balance;
type DustRemoval = ();
type RuntimeEvent = RuntimeEvent;
type ExistentialDeposit = frame_support::traits::ConstU64<1>;
type ExistentialDeposit = ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type MaxLocks = ();
type MaxReserves = ConstU32<1>;
type ReserveIdentifier = [u8; 8];
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = HoldIdentifier;
type MaxHolds = ConstU32<1>;
}
#[derive(
Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, Debug, TypeInfo,
)]
pub enum HoldIdentifier {
Nis,
}
impl pallet_balances::Config<Instance2> for Test {
type Balance = u128;
type DustRemoval = ();
type RuntimeEvent = RuntimeEvent;
type ExistentialDeposit = frame_support::traits::ConstU128<1>;
type ExistentialDeposit = ConstU128<1>;
type AccountStore = StorageMapShim<
pallet_balances::Account<Test, Instance2>,
frame_system::Provider<Test>,
u64,
pallet_balances::AccountData<u128>,
>;
@@ -103,16 +120,20 @@ impl pallet_balances::Config<Instance2> for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
parameter_types! {
pub IgnoredIssuance: u64 = Balances::total_balance(&0); // Account zero is ignored.
pub IgnoredIssuance: Balance = Balances::total_balance(&0); // Account zero is ignored.
pub const NisPalletId: PalletId = PalletId(*b"py/nis ");
pub static Target: Perquintill = Perquintill::zero();
pub const MinReceipt: Perquintill = Perquintill::from_percent(1);
pub const ThawThrottle: (Perquintill, u64) = (Perquintill::from_percent(25), 5);
pub static MaxIntakeWeight: Weight = Weight::from_parts(2_000_000_000_000, 0);
pub const ReserveId: [u8; 8] = *b"py/nis ";
pub const HoldReason: HoldIdentifier = HoldIdentifier::Nis;
}
ord_parameter_types! {
@@ -140,7 +161,7 @@ impl pallet_nis::Config for Test {
type MaxIntakeWeight = MaxIntakeWeight;
type MinReceipt = MinReceipt;
type ThawThrottle = ThawThrottle;
type ReserveId = ReserveId;
type HoldReason = HoldReason;
}
// This function basically just builds a genesis storage key/value store according to
@@ -155,6 +176,13 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
t.into()
}
// This function basically just builds a genesis storage key/value store according to
// our desired mockup, but without any balances.
#[cfg(feature = "runtime-benchmarks")]
pub fn new_test_ext_empty() -> sp_io::TestExternalities {
frame_system::GenesisConfig::default().build_storage::<Test>().unwrap().into()
}
pub fn run_to_block(n: u64) {
while System::block_number() < n {
Nis::on_finalize(System::block_number());
+66 -44
View File
@@ -22,19 +22,22 @@ use crate::{mock::*, Error};
use frame_support::{
assert_noop, assert_ok,
traits::{
fungible::{hold::Inspect as InspectHold, Inspect as FunInspect, Mutate as FunMutate},
nonfungible::{Inspect, Transfer},
Currency,
tokens::{Fortitude::Force, Precision::Exact},
},
};
use pallet_balances::{Error as BalancesError, Instance1};
use sp_arithmetic::Perquintill;
use sp_runtime::{Saturating, TokenError};
use sp_runtime::{
Saturating,
TokenError::{self, FundsUnavailable},
};
fn pot() -> u64 {
fn pot() -> Balance {
Balances::free_balance(&Nis::account_id())
}
fn holdings() -> u64 {
fn holdings() -> Balance {
Nis::issuance().holdings
}
@@ -42,8 +45,8 @@ fn signed(who: u64) -> RuntimeOrigin {
RuntimeOrigin::signed(who)
}
fn enlarge(amount: u64, max_bids: u32) {
let summary: SummaryRecord<u64, u64> = Summary::<Test>::get();
fn enlarge(amount: Balance, max_bids: u32) {
let summary: SummaryRecord<u64, Balance> = Summary::<Test>::get();
let increase_in_proportion_owed = Perquintill::from_rational(amount, Nis::issuance().effective);
let target = summary.proportion_owed.saturating_add(increase_in_proportion_owed);
Nis::process_queues(target, u32::max_value(), max_bids, &mut WeightCounter::unlimited());
@@ -75,10 +78,7 @@ fn place_bid_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_noop!(Nis::place_bid(signed(1), 1, 2), Error::<Test>::AmountTooSmall);
assert_noop!(
Nis::place_bid(signed(1), 101, 2),
BalancesError::<Test, Instance1>::InsufficientBalance
);
assert_noop!(Nis::place_bid(signed(1), 101, 2), FundsUnavailable);
assert_noop!(Nis::place_bid(signed(1), 10, 4), Error::<Test>::DurationTooBig);
assert_ok!(Nis::place_bid(signed(1), 10, 2));
assert_eq!(Balances::reserved_balance(1), 10);
@@ -451,16 +451,16 @@ fn communify_works() {
assert_noop!(Nis::thaw_communal(signed(1), 1), Error::<Test>::UnknownReceipt);
// Transfer some of the fungibles away.
assert_ok!(NisBalances::transfer(signed(1), 2, 100_000));
assert_ok!(NisBalances::transfer_allow_death(signed(1), 2, 100_000));
assert_eq!(NisBalances::free_balance(&1), 2_000_000);
assert_eq!(NisBalances::free_balance(&2), 100_000);
// Communal thawing with the correct index is not possible now.
assert_noop!(Nis::thaw_communal(signed(1), 0), TokenError::NoFunds);
assert_noop!(Nis::thaw_communal(signed(2), 0), TokenError::NoFunds);
assert_noop!(Nis::thaw_communal(signed(1), 0), TokenError::FundsUnavailable);
assert_noop!(Nis::thaw_communal(signed(2), 0), TokenError::FundsUnavailable);
// Transfer the rest to 2...
assert_ok!(NisBalances::transfer(signed(1), 2, 2_000_000));
assert_ok!(NisBalances::transfer_allow_death(signed(1), 2, 2_000_000));
assert_eq!(NisBalances::free_balance(&1), 0);
assert_eq!(NisBalances::free_balance(&2), 2_100_000);
@@ -487,8 +487,8 @@ fn privatize_works() {
assert_ok!(Nis::communify(signed(1), 0));
// Transfer the fungibles to #2
assert_ok!(NisBalances::transfer(signed(1), 2, 2_100_000));
assert_noop!(Nis::privatize(signed(1), 0), TokenError::NoFunds);
assert_ok!(NisBalances::transfer_allow_death(signed(1), 2, 2_100_000));
assert_noop!(Nis::privatize(signed(1), 0), TokenError::FundsUnavailable);
// Privatize
assert_ok!(Nis::privatize(signed(2), 0));
@@ -513,16 +513,16 @@ fn privatize_and_thaw_with_another_receipt_works() {
assert_ok!(Nis::communify(signed(2), 1));
// Transfer half of fungibles to #3 from each of #1 and #2, and the other half from #2 to #4
assert_ok!(NisBalances::transfer(signed(1), 3, 1_050_000));
assert_ok!(NisBalances::transfer(signed(2), 3, 1_050_000));
assert_ok!(NisBalances::transfer(signed(2), 4, 1_050_000));
assert_ok!(NisBalances::transfer_allow_death(signed(1), 3, 1_050_000));
assert_ok!(NisBalances::transfer_allow_death(signed(2), 3, 1_050_000));
assert_ok!(NisBalances::transfer_allow_death(signed(2), 4, 1_050_000));
// #3 privatizes, partially thaws, then re-communifies with #0, then transfers the fungibles
// to #2
assert_ok!(Nis::privatize(signed(3), 0));
assert_ok!(Nis::thaw_private(signed(3), 0, Some(Perquintill::from_percent(5))));
assert_ok!(Nis::communify(signed(3), 0));
assert_ok!(NisBalances::transfer(signed(3), 1, 1_050_000));
assert_ok!(NisBalances::transfer_allow_death(signed(3), 1, 1_050_000));
// #1 now has enough to thaw using receipt 1
assert_ok!(Nis::thaw_communal(signed(1), 1));
@@ -536,17 +536,21 @@ fn privatize_and_thaw_with_another_receipt_works() {
fn communal_thaw_when_issuance_higher_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1));
assert_ok!(Nis::place_bid(signed(1), 100, 1));
enlarge(100, 1);
assert_eq!(Balances::total_balance(&1), 101);
assert_ok!(Nis::communify(signed(1), 0));
assert_eq!(Balances::total_balance_on_hold(&1), 0);
assert_eq!(Balances::total_balance(&1), 1);
assert_eq!(NisBalances::free_balance(1), 5_250_000); // (25% of 21m)
assert_eq!(NisBalances::free_balance(1), 5_250_000); // (12.5% of 21m)
// Everybody else's balances goes up by 50%
Balances::make_free_balance_be(&2, 150);
Balances::make_free_balance_be(&3, 150);
Balances::make_free_balance_be(&4, 150);
assert_ok!(Balances::mint_into(&2, 50));
assert_ok!(Balances::mint_into(&3, 50));
assert_ok!(Balances::mint_into(&4, 50));
run_to_block(4);
@@ -556,16 +560,20 @@ fn communal_thaw_when_issuance_higher_works() {
assert_ok!(Nis::fund_deficit(signed(1)));
// Transfer counterparts away...
assert_ok!(NisBalances::transfer(signed(1), 2, 250_000));
assert_ok!(NisBalances::transfer_allow_death(signed(1), 2, 125_000));
// ...and it's not thawable.
assert_noop!(Nis::thaw_communal(signed(1), 0), TokenError::NoFunds);
assert_noop!(Nis::thaw_communal(signed(1), 0), TokenError::FundsUnavailable);
// Transfer counterparts back...
assert_ok!(NisBalances::transfer(signed(2), 1, 250_000));
assert_ok!(NisBalances::transfer_allow_death(signed(2), 1, 125_000));
// ...and it is.
assert_ok!(Nis::thaw_communal(signed(1), 0));
assert_eq!(Balances::total_balance(&1), 151);
assert_ok!(Balances::transfer_allow_death(signed(1), 2, 1));
assert_eq!(Balances::total_balance(&1), 150);
assert_eq!(Balances::free_balance(1), 150);
assert_eq!(Balances::total_balance_on_hold(&1), 0);
assert_eq!(Balances::reserved_balance(1), 0);
});
}
@@ -574,13 +582,14 @@ fn communal_thaw_when_issuance_higher_works() {
fn private_thaw_when_issuance_higher_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1));
assert_ok!(Nis::place_bid(signed(1), 100, 1));
enlarge(100, 1);
// Everybody else's balances goes up by 50%
Balances::make_free_balance_be(&2, 150);
Balances::make_free_balance_be(&3, 150);
Balances::make_free_balance_be(&4, 150);
assert_ok!(Balances::mint_into(&2, 50));
assert_ok!(Balances::mint_into(&3, 50));
assert_ok!(Balances::mint_into(&4, 50));
run_to_block(4);
@@ -591,6 +600,7 @@ fn private_thaw_when_issuance_higher_works() {
assert_ok!(Nis::thaw_private(signed(1), 0, None));
assert_ok!(Balances::transfer_allow_death(signed(1), 2, 1));
assert_eq!(Balances::free_balance(1), 150);
assert_eq!(Balances::reserved_balance(1), 0);
});
@@ -601,15 +611,16 @@ fn thaw_with_ignored_issuance_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
// Give account zero some balance.
Balances::make_free_balance_be(&0, 200);
assert_ok!(Balances::mint_into(&0, 200));
assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1));
assert_ok!(Nis::place_bid(signed(1), 100, 1));
enlarge(100, 1);
// Account zero transfers 50 into everyone else's accounts.
assert_ok!(Balances::transfer(signed(0), 2, 50));
assert_ok!(Balances::transfer(signed(0), 3, 50));
assert_ok!(Balances::transfer(signed(0), 4, 50));
assert_ok!(Balances::transfer_allow_death(signed(0), 2, 50));
assert_ok!(Balances::transfer_allow_death(signed(0), 3, 50));
assert_ok!(Balances::transfer_allow_death(signed(0), 4, 50));
run_to_block(4);
// Unfunded initially...
@@ -620,6 +631,7 @@ fn thaw_with_ignored_issuance_works() {
assert_ok!(Nis::thaw_private(signed(1), 0, None));
// Account zero changes have been ignored.
assert_ok!(Balances::transfer_allow_death(signed(1), 2, 1));
assert_eq!(Balances::free_balance(1), 150);
assert_eq!(Balances::reserved_balance(1), 0);
});
@@ -629,17 +641,19 @@ fn thaw_with_ignored_issuance_works() {
fn thaw_when_issuance_lower_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1));
assert_ok!(Nis::place_bid(signed(1), 100, 1));
enlarge(100, 1);
// Everybody else's balances goes down by 25%
Balances::make_free_balance_be(&2, 75);
Balances::make_free_balance_be(&3, 75);
Balances::make_free_balance_be(&4, 75);
assert_ok!(Balances::burn_from(&2, 25, Exact, Force));
assert_ok!(Balances::burn_from(&3, 25, Exact, Force));
assert_ok!(Balances::burn_from(&4, 25, Exact, Force));
run_to_block(4);
assert_ok!(Nis::thaw_private(signed(1), 0, None));
assert_ok!(Balances::transfer_allow_death(signed(1), 2, 1));
assert_eq!(Balances::free_balance(1), 75);
assert_eq!(Balances::reserved_balance(1), 0);
});
@@ -649,15 +663,16 @@ fn thaw_when_issuance_lower_works() {
fn multiple_thaws_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Balances::transfer_allow_death(signed(3), 1, 1));
assert_ok!(Nis::place_bid(signed(1), 40, 1));
assert_ok!(Nis::place_bid(signed(1), 60, 1));
assert_ok!(Nis::place_bid(signed(2), 50, 1));
enlarge(200, 3);
// Double everyone's free balances.
Balances::make_free_balance_be(&2, 100);
Balances::make_free_balance_be(&3, 200);
Balances::make_free_balance_be(&4, 200);
assert_ok!(Balances::mint_into(&2, 50));
assert_ok!(Balances::mint_into(&3, 100));
assert_ok!(Balances::mint_into(&4, 100));
assert_ok!(Nis::fund_deficit(signed(1)));
run_to_block(4);
@@ -667,8 +682,11 @@ fn multiple_thaws_works() {
run_to_block(5);
assert_ok!(Nis::thaw_private(signed(2), 2, None));
assert_ok!(Balances::transfer_allow_death(signed(1), 3, 1));
assert_eq!(Balances::free_balance(1), 200);
assert_eq!(Balances::free_balance(2), 200);
assert_eq!(Balances::total_balance(&1), 200);
assert_eq!(Balances::total_balance(&2), 200);
});
}
@@ -676,15 +694,16 @@ fn multiple_thaws_works() {
fn multiple_thaws_works_in_alternative_thaw_order() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Balances::transfer_allow_death(signed(3), 1, 1));
assert_ok!(Nis::place_bid(signed(1), 40, 1));
assert_ok!(Nis::place_bid(signed(1), 60, 1));
assert_ok!(Nis::place_bid(signed(2), 50, 1));
enlarge(200, 3);
// Double everyone's free balances.
Balances::make_free_balance_be(&2, 100);
Balances::make_free_balance_be(&3, 200);
Balances::make_free_balance_be(&4, 200);
assert_ok!(Balances::mint_into(&2, 50));
assert_ok!(Balances::mint_into(&3, 100));
assert_ok!(Balances::mint_into(&4, 100));
assert_ok!(Nis::fund_deficit(signed(1)));
run_to_block(4);
@@ -695,8 +714,11 @@ fn multiple_thaws_works_in_alternative_thaw_order() {
run_to_block(5);
assert_ok!(Nis::thaw_private(signed(1), 1, None));
assert_ok!(Balances::transfer_allow_death(signed(1), 3, 1));
assert_eq!(Balances::free_balance(1), 200);
assert_eq!(Balances::free_balance(2), 200);
assert_eq!(Balances::total_balance(&1), 200);
assert_eq!(Balances::total_balance(&2), 200);
});
}
@@ -75,6 +75,10 @@ impl pallet_balances::Config for Runtime {
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
pallet_staking_reward_curve::build! {
@@ -196,6 +196,10 @@ impl pallet_balances::Config for Runtime {
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
pub struct BalanceToU256;
+83 -94
View File
@@ -39,6 +39,16 @@ macro_rules! member_unbonding_eras {
pub const DEFAULT_ROLES: PoolRoles<AccountId> =
PoolRoles { depositor: 10, root: Some(900), nominator: Some(901), bouncer: Some(902) };
fn deposit_rewards(r: u128) {
let b = Balances::free_balance(&default_reward_account()).checked_add(r).unwrap();
Balances::make_free_balance_be(&default_reward_account(), b);
}
fn remove_rewards(r: u128) {
let b = Balances::free_balance(&default_reward_account()).checked_sub(r).unwrap();
Balances::make_free_balance_be(&default_reward_account(), b);
}
#[test]
fn test_setup_works() {
ExtBuilder::default().build_and_execute(|| {
@@ -469,6 +479,8 @@ mod sub_pools {
}
mod join {
use sp_runtime::TokenError;
use super::*;
#[test]
@@ -592,7 +604,7 @@ mod join {
// Balance needs to be gt Balance::MAX / `MaxPointsToBalance`
assert_noop!(
Pools::join(RuntimeOrigin::signed(11), 5, 123),
pallet_balances::Error::<Runtime>::InsufficientBalance,
TokenError::FundsUnavailable,
);
StakingMock::set_bonded_balance(Pools::create_bonded_account(1), max_points_to_balance);
@@ -749,7 +761,7 @@ mod claim_payout {
// and the reward pool has earned 100 in rewards
assert_eq!(Balances::free_balance(default_reward_account()), ed);
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 100));
deposit_rewards(100);
let _ = pool_events_since_last_call();
@@ -796,7 +808,7 @@ mod claim_payout {
assert_eq!(Balances::free_balance(&default_reward_account()), ed);
// Given the reward pool has some new rewards
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50));
deposit_rewards(50);
// When
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
@@ -825,7 +837,7 @@ mod claim_payout {
assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25);
// Given del 50 hasn't claimed and the reward pools has just earned 50
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50));
deposit_rewards(50);
assert_eq!(Balances::free_balance(&default_reward_account()), ed + 75);
// When
@@ -855,7 +867,7 @@ mod claim_payout {
assert_eq!(Balances::free_balance(&default_reward_account()), ed + 20);
// Given del 40 hasn't claimed and the reward pool has just earned 400
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 400));
deposit_rewards(400);
assert_eq!(Balances::free_balance(&default_reward_account()), ed + 420);
// When
@@ -874,7 +886,7 @@ mod claim_payout {
assert_eq!(Balances::free_balance(&default_reward_account()), ed + 380);
// Given del 40 + del 50 haven't claimed and the reward pool has earned 20
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 20));
deposit_rewards(20);
assert_eq!(Balances::free_balance(&default_reward_account()), ed + 400);
// When
@@ -974,7 +986,7 @@ mod claim_payout {
);
// The pool earns 10 points
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 10));
deposit_rewards(10);
assert_ok!(Pools::do_reward_payout(
&10,
@@ -1010,7 +1022,7 @@ mod claim_payout {
assert_eq!(reward_pool, rew(0, 0, 0));
// Given the pool has earned some rewards for the first time
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 5));
deposit_rewards(5);
// When
let payout =
@@ -1031,7 +1043,7 @@ mod claim_payout {
assert_eq!(member, del(0.5));
// Given the pool has earned rewards again
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 10));
deposit_rewards(10);
// When
let payout =
@@ -1090,7 +1102,7 @@ mod claim_payout {
assert_eq!(bonded_pool.points, 100);
// and the reward pool has earned 100 in rewards
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 100));
deposit_rewards(100);
// When
let payout =
@@ -1135,7 +1147,7 @@ mod claim_payout {
assert_eq!(reward_pool, rew(0, 0, 100));
// Given the reward pool has some new rewards
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50));
deposit_rewards(50);
// When
let payout =
@@ -1166,7 +1178,7 @@ mod claim_payout {
assert_eq!(reward_pool, rew(0, 0, 125));
// Given del_50 hasn't claimed and the reward pools has just earned 50
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50));
deposit_rewards(50);
// When
let payout =
@@ -1197,7 +1209,7 @@ mod claim_payout {
assert_eq!(reward_pool, rew(0, 0, 180));
// Given del_40 hasn't claimed and the reward pool has just earned 400
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 400));
deposit_rewards(400);
// When
let payout =
@@ -1214,7 +1226,7 @@ mod claim_payout {
assert_eq!(reward_pool, rew(0, 0, 220));
// Given del_40 + del_50 haven't claimed and the reward pool has earned 20
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 20));
deposit_rewards(20);
// When
let payout =
@@ -1252,14 +1264,14 @@ mod claim_payout {
fn rewards_distribution_is_fair_basic() {
ExtBuilder::default().build_and_execute(|| {
// reward pool by 10.
Balances::mutate_account(&default_reward_account(), |f| f.free += 10).unwrap();
deposit_rewards(10);
// 20 joins afterwards.
Balances::make_free_balance_be(&20, Balances::minimum_balance() + 10);
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
// reward by another 20
Balances::mutate_account(&default_reward_account(), |f| f.free += 20).unwrap();
deposit_rewards(20);
// 10 should claim 10 + 10, 20 should claim 20 / 2.
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
@@ -1276,7 +1288,7 @@ mod claim_payout {
);
// any upcoming rewards are shared equally.
Balances::mutate_account(&default_reward_account(), |f| f.free += 20).unwrap();
deposit_rewards(20);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
@@ -1296,12 +1308,12 @@ mod claim_payout {
// basically checks the case where the amount of rewards is less than the pool shares. for
// this, we have to rely on fixed point arithmetic.
ExtBuilder::default().build_and_execute(|| {
Balances::mutate_account(&default_reward_account(), |f| f.free += 3).unwrap();
deposit_rewards(3);
Balances::make_free_balance_be(&20, Balances::minimum_balance() + 10);
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
Balances::mutate_account(&default_reward_account(), |f| f.free += 6).unwrap();
deposit_rewards(6);
// 10 should claim 3, 20 should claim 3 + 3.
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
@@ -1319,7 +1331,7 @@ mod claim_payout {
);
// any upcoming rewards are shared equally.
Balances::mutate_account(&default_reward_account(), |f| f.free += 8).unwrap();
deposit_rewards(8);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
@@ -1333,7 +1345,7 @@ mod claim_payout {
);
// uneven upcoming rewards are shared equally, rounded down.
Balances::mutate_account(&default_reward_account(), |f| f.free += 7).unwrap();
deposit_rewards(7);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
@@ -1353,17 +1365,17 @@ mod claim_payout {
ExtBuilder::default().build_and_execute(|| {
let ed = Balances::minimum_balance();
Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap();
deposit_rewards(30);
Balances::make_free_balance_be(&20, ed + 10);
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap();
deposit_rewards(100);
Balances::make_free_balance_be(&30, ed + 10);
assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1));
Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap();
deposit_rewards(60);
// 10 should claim 10, 20 should claim nothing.
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
@@ -1384,7 +1396,7 @@ mod claim_payout {
);
// any upcoming rewards are shared equally.
Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap();
deposit_rewards(30);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
@@ -1407,7 +1419,7 @@ mod claim_payout {
let ed = Balances::minimum_balance();
assert_eq!(Pools::api_pending_rewards(10), Some(0));
Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap();
deposit_rewards(30);
assert_eq!(Pools::api_pending_rewards(10), Some(30));
assert_eq!(Pools::api_pending_rewards(20), None);
@@ -1417,7 +1429,7 @@ mod claim_payout {
assert_eq!(Pools::api_pending_rewards(10), Some(30));
assert_eq!(Pools::api_pending_rewards(20), Some(0));
Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap();
deposit_rewards(100);
assert_eq!(Pools::api_pending_rewards(10), Some(30 + 50));
assert_eq!(Pools::api_pending_rewards(20), Some(50));
@@ -1430,7 +1442,7 @@ mod claim_payout {
assert_eq!(Pools::api_pending_rewards(20), Some(50));
assert_eq!(Pools::api_pending_rewards(30), Some(0));
Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap();
deposit_rewards(60);
assert_eq!(Pools::api_pending_rewards(10), Some(30 + 50 + 20));
assert_eq!(Pools::api_pending_rewards(20), Some(50 + 20));
@@ -1464,7 +1476,7 @@ mod claim_payout {
Balances::make_free_balance_be(&30, ed + 20);
assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1));
Balances::mutate_account(&default_reward_account(), |f| f.free += 40).unwrap();
deposit_rewards(40);
// everyone claims.
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
@@ -1488,7 +1500,7 @@ mod claim_payout {
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(30), BondExtra::FreeBalance(10)));
// more rewards come in.
Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap();
deposit_rewards(100);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
@@ -1514,7 +1526,7 @@ mod claim_payout {
Balances::make_free_balance_be(&20, ed + 20);
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1));
Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap();
deposit_rewards(30);
// everyone claims.
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
@@ -1535,7 +1547,7 @@ mod claim_payout {
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10));
// more rewards come in.
Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap();
deposit_rewards(100);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
@@ -1562,7 +1574,7 @@ mod claim_payout {
assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1));
// 10 gets 10, 20 gets 20, 30 gets 10
Balances::mutate_account(&default_reward_account(), |f| f.free += 40).unwrap();
deposit_rewards(40);
// some claim.
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
@@ -1581,7 +1593,7 @@ mod claim_payout {
);
// 10 gets 20, 20 gets 40, 30 gets 20
Balances::mutate_account(&default_reward_account(), |f| f.free += 80).unwrap();
deposit_rewards(80);
// some claim.
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
@@ -1596,7 +1608,7 @@ mod claim_payout {
);
// 10 gets 20, 20 gets 40, 30 gets 20
Balances::mutate_account(&default_reward_account(), |f| f.free += 80).unwrap();
deposit_rewards(80);
// some claim.
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
@@ -1629,7 +1641,7 @@ mod claim_payout {
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1));
// 10 gets 10, 20 gets 20, 30 gets 10
Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap();
deposit_rewards(30);
// some claim.
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
@@ -1645,7 +1657,7 @@ mod claim_payout {
);
// 20 has not claimed yet, more reward comes
Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap();
deposit_rewards(60);
// and 20 bonds more -- they should not have more share of this reward.
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(20), BondExtra::FreeBalance(10)));
@@ -1665,7 +1677,7 @@ mod claim_payout {
);
// but in the next round of rewards, the extra10 they bonded has an impact.
Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap();
deposit_rewards(60);
// everyone claim.
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
@@ -1695,7 +1707,7 @@ mod claim_payout {
assert_eq!(member_10.last_recorded_reward_counter, 0.into());
// transfer some reward to pool 1.
Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap();
deposit_rewards(60);
// create pool 2
Balances::make_free_balance_be(&20, 100);
@@ -1779,7 +1791,7 @@ mod claim_payout {
}
// transfer some reward to pool 1.
Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap();
deposit_rewards(60);
{
join(30, 10);
@@ -1849,7 +1861,7 @@ mod claim_payout {
// 10 bonds extra again with some rewards. This reward should be split equally between
// 10 and 20, as they both have equal points now.
Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap();
deposit_rewards(30);
{
assert_ok!(Pools::bond_extra(
@@ -1905,7 +1917,7 @@ mod claim_payout {
MaxPoolMembersPerPool::<Runtime>::set(None);
// pool receives some rewards.
Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap();
deposit_rewards(30);
System::reset_events();
// 10 cashes it out, and bonds it.
@@ -1975,7 +1987,7 @@ mod claim_payout {
}
// some rewards come in.
Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap();
deposit_rewards(30);
// and 30 also unbonds half.
{
@@ -2058,7 +2070,7 @@ mod claim_payout {
);
// some rewards come in.
Balances::mutate_account(&default_reward_account(), |f| f.free += 40).unwrap();
deposit_rewards(40);
// everyone claims
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
@@ -2120,8 +2132,7 @@ mod claim_payout {
.build_and_execute(|| {
// some rewards come in.
assert_eq!(Balances::free_balance(&default_reward_account()), unit);
Balances::mutate_account(&default_reward_account(), |f| f.free += unit / 1000)
.unwrap();
deposit_rewards(unit / 1000);
// everyone claims
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
@@ -3329,11 +3340,7 @@ mod withdraw_unbonded {
);
assert_eq!(
balances_events_since_last_call(),
vec![BEvent::BalanceSet {
who: default_bonded_account(),
free: 300,
reserved: 0
}]
vec![BEvent::BalanceSet { who: default_bonded_account(), free: 300 }]
);
// When
@@ -3447,11 +3454,7 @@ mod withdraw_unbonded {
);
assert_eq!(
balances_events_since_last_call(),
vec![BEvent::BalanceSet {
who: default_bonded_account(),
free: 300,
reserved: 0
},]
vec![BEvent::BalanceSet { who: default_bonded_account(), free: 300 },]
);
CurrentEra::set(StakingMock::bonding_duration());
@@ -5078,18 +5081,15 @@ mod reward_counter_precision {
let expected_smallest_reward = inflation(50) / 10u128.pow(18);
// tad bit less. cannot be paid out.
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free +=
expected_smallest_reward - 1));
deposit_rewards(expected_smallest_reward - 1);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(pool_events_since_last_call(), vec![]);
// revert it.
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -=
expected_smallest_reward - 1));
remove_rewards(expected_smallest_reward - 1);
// tad bit more. can be claimed.
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free +=
expected_smallest_reward + 1));
deposit_rewards(expected_smallest_reward + 1);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(
pool_events_since_last_call(),
@@ -5114,9 +5114,7 @@ mod reward_counter_precision {
assert_ok!(Pools::join(RuntimeOrigin::signed(20), tiny_bond / 2, 1));
// Suddenly, add a shit ton of rewards.
assert_ok!(
Balances::mutate_account(&default_reward_account(), |a| a.free += inflation(1))
);
deposit_rewards(inflation(1));
// now claim.
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
@@ -5155,8 +5153,7 @@ mod reward_counter_precision {
// is earning all of the inflation per year (which is really unrealistic, but worse
// case), that will be:
let pool_total_earnings_10_years = inflation(10) - POLKADOT_TOTAL_ISSUANCE_GENESIS;
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free +=
pool_total_earnings_10_years));
deposit_rewards(pool_total_earnings_10_years);
// some whale now joins with the other half ot the total issuance. This will bloat all
// the calculation regarding current reward counter.
@@ -5186,7 +5183,7 @@ mod reward_counter_precision {
assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10 * DOT, 1));
// and give a reasonably small reward to the pool.
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += DOT));
deposit_rewards(DOT);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30)));
assert_eq!(
@@ -5258,9 +5255,7 @@ mod reward_counter_precision {
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10 * DOT, 1));
// earn some small rewards
assert_ok!(
Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000)
);
deposit_rewards(DOT / 1000);
// no point in claiming for 20 (nonetheless, it should be harmless)
assert!(pending_rewards(20).unwrap().is_zero());
@@ -5280,9 +5275,7 @@ mod reward_counter_precision {
// earn some small more, still nothing can be claimed for 20, but 10 claims their
// share.
assert_ok!(
Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000)
);
deposit_rewards(DOT / 1000);
assert!(pending_rewards(20).unwrap().is_zero());
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(
@@ -5291,9 +5284,7 @@ mod reward_counter_precision {
);
// earn some more rewards, this time 20 can also claim.
assert_ok!(
Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000)
);
deposit_rewards(DOT / 1000);
assert_eq!(pending_rewards(20).unwrap(), 1);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
@@ -5332,9 +5323,7 @@ mod reward_counter_precision {
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10 * DOT, 1));
// earn some small rewards
assert_ok!(
Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000)
);
deposit_rewards(DOT / 1000);
// if 20 claims now, their reward counter should stay the same, so that they have a
// chance of claiming this if they let it accumulate. Also see
@@ -5442,7 +5431,7 @@ mod commission {
// Pool earns 80 points and a payout is triggered.
// Given:
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 80));
deposit_rewards(80);
assert_eq!(
PoolMembers::<Runtime>::get(10).unwrap(),
PoolMember::<Runtime> { pool_id, points: 10, ..Default::default() }
@@ -5489,7 +5478,7 @@ mod commission {
// is next called, which is not done in this test segment..
// Given:
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 100));
deposit_rewards(100);
// When:
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
@@ -5625,7 +5614,7 @@ mod commission {
1,
Some((Perbill::from_percent(10), root)),
));
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 40));
deposit_rewards(40);
// When:
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
@@ -6377,7 +6366,7 @@ mod commission {
);
// The pool earns 10 points
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 10));
deposit_rewards(10);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
// Then:
@@ -6387,7 +6376,7 @@ mod commission {
);
// The pool earns 17 points
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 17));
deposit_rewards(17);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
// Then:
@@ -6397,7 +6386,7 @@ mod commission {
);
// The pool earns 50 points
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50));
deposit_rewards(50);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
// Then:
@@ -6407,7 +6396,7 @@ mod commission {
);
// The pool earns 10439 points
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 10439));
deposit_rewards(10439);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
// Then:
@@ -6427,7 +6416,7 @@ mod commission {
));
// Given:
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 200));
deposit_rewards(200);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
// Then:
@@ -6469,7 +6458,7 @@ mod commission {
// When:
// The pool earns 100 points
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 100));
deposit_rewards(100);
// Change commission to 20%
assert_ok!(Pools::set_commission(
@@ -6486,7 +6475,7 @@ mod commission {
);
// The pool earns 100 points
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 100));
deposit_rewards(100);
// Then:
@@ -6535,7 +6524,7 @@ mod commission {
// When:
// The pool earns 100 points
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 100));
deposit_rewards(100);
// Claim payout:
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
@@ -6592,7 +6581,7 @@ mod commission {
);
// The pool earns 10 points
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 10));
deposit_rewards(10);
// execute the payout
assert_ok!(Pools::do_reward_payout(
@@ -6634,7 +6623,7 @@ mod commission {
);
// The pool earns 10 points
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 10));
deposit_rewards(10);
// execute the payout
assert_ok!(Pools::do_reward_payout(
@@ -6677,7 +6666,7 @@ mod commission {
);
// Pool earns 80 points, payout is triggered.
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 80));
deposit_rewards(80);
assert_eq!(
PoolMembers::<Runtime>::get(10).unwrap(),
PoolMember::<Runtime> { pool_id, points: 10, ..Default::default() }
@@ -86,6 +86,10 @@ impl pallet_balances::Config for Runtime {
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
pallet_staking_reward_curve::build! {
@@ -74,6 +74,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU64<10>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
impl pallet_timestamp::Config for Test {
+4
View File
@@ -84,6 +84,10 @@ impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ConstU32<50>;
type ReserveIdentifier = [u8; 8];
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
ord_parameter_types! {
+18 -10
View File
@@ -88,6 +88,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
impl pallet_utility::Config for Test {
type RuntimeEvent = RuntimeEvent;
@@ -124,7 +128,10 @@ impl InstanceFilter<RuntimeCall> for ProxyType {
match self {
ProxyType::Any => true,
ProxyType::JustTransfer => {
matches!(c, RuntimeCall::Balances(pallet_balances::Call::transfer { .. }))
matches!(
c,
RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { .. })
)
},
ProxyType::JustUtility => matches!(c, RuntimeCall::Utility { .. }),
}
@@ -161,7 +168,7 @@ impl Config for Test {
use super::{Call as ProxyCall, Event as ProxyEvent};
use frame_system::Call as SystemCall;
use pallet_balances::{Call as BalancesCall, Error as BalancesError, Event as BalancesEvent};
use pallet_balances::{Call as BalancesCall, Event as BalancesEvent};
use pallet_utility::{Call as UtilityCall, Event as UtilityEvent};
type SystemError = frame_system::Error<Test>;
@@ -169,7 +176,7 @@ type SystemError = frame_system::Error<Test>;
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
pallet_balances::GenesisConfig::<Test> {
balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 2)],
balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 3)],
}
.assimilate_storage(&mut t)
.unwrap();
@@ -193,7 +200,7 @@ fn expect_events(e: Vec<RuntimeEvent>) {
}
fn call_transfer(dest: u64, value: u64) -> RuntimeCall {
RuntimeCall::Balances(BalancesCall::transfer { dest, value })
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest, value })
}
#[test]
@@ -345,7 +352,7 @@ fn proxy_announced_removes_announcement_and_returns_deposit() {
#[test]
fn filtering_works() {
new_test_ext().execute_with(|| {
assert!(Balances::mutate_account(&1, |a| a.free = 1000).is_ok());
Balances::make_free_balance_be(&1, 1000);
assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 2, ProxyType::Any, 0));
assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 3, ProxyType::JustTransfer, 0));
assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 4, ProxyType::JustUtility, 0));
@@ -361,7 +368,7 @@ fn filtering_works() {
);
let derivative_id = Utility::derivative_account_id(1, 0);
assert!(Balances::mutate_account(&derivative_id, |a| a.free = 1000).is_ok());
Balances::make_free_balance_be(&derivative_id, 1000);
let inner = Box::new(call_transfer(6, 1));
let call = Box::new(RuntimeCall::Utility(UtilityCall::as_derivative {
@@ -516,7 +523,7 @@ fn cannot_add_proxy_without_balance() {
assert_eq!(Balances::reserved_balance(5), 2);
assert_noop!(
Proxy::add_proxy(RuntimeOrigin::signed(5), 4, ProxyType::Any, 0),
BalancesError::<Test, _>::InsufficientBalance
DispatchError::ConsumerRemaining,
);
});
}
@@ -564,6 +571,7 @@ fn proxying_works() {
#[test]
fn pure_works() {
new_test_ext().execute_with(|| {
Balances::make_free_balance_be(&1, 11); // An extra one for the ED.
assert_ok!(Proxy::create_pure(RuntimeOrigin::signed(1), ProxyType::Any, 0, 0));
let anon = Proxy::pure_account(&1, &ProxyType::Any, 0, None);
System::assert_last_event(
@@ -592,7 +600,7 @@ fn pure_works() {
assert_ok!(Proxy::create_pure(RuntimeOrigin::signed(1), ProxyType::Any, 0, 0));
let call = Box::new(call_transfer(6, 1));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), anon, 5));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), anon, 5));
assert_ok!(Proxy::proxy(RuntimeOrigin::signed(1), anon, None, call));
System::assert_last_event(ProxyEvent::ProxyExecuted { result: Ok(()) }.into());
assert_eq!(Balances::free_balance(6), 1);
@@ -611,9 +619,9 @@ fn pure_works() {
Proxy::kill_pure(RuntimeOrigin::signed(1), 1, ProxyType::Any, 0, 1, 0),
Error::<Test>::NoPermission
);
assert_eq!(Balances::free_balance(1), 0);
assert_eq!(Balances::free_balance(1), 1);
assert_ok!(Proxy::proxy(RuntimeOrigin::signed(1), anon, None, call.clone()));
assert_eq!(Balances::free_balance(1), 2);
assert_eq!(Balances::free_balance(1), 3);
assert_noop!(
Proxy::proxy(RuntimeOrigin::signed(1), anon, None, call.clone()),
Error::<Test>::NotProxy
+4
View File
@@ -86,6 +86,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
parameter_types! {
+8 -2
View File
@@ -45,7 +45,10 @@ fn set_recovered_works() {
// Root can set a recovered account though
assert_ok!(Recovery::set_recovered(RuntimeOrigin::root(), 5, 1));
// Account 1 should now be able to make a call through account 5
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 1, value: 100 }));
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death {
dest: 1,
value: 100,
}));
assert_ok!(Recovery::as_recovered(RuntimeOrigin::signed(1), 5, call));
// Account 1 has successfully drained the funds from account 5
assert_eq!(Balances::free_balance(1), 200);
@@ -93,7 +96,10 @@ fn recovery_life_cycle_works() {
assert_ok!(Recovery::as_recovered(RuntimeOrigin::signed(1), 5, call));
// Account 1 should now be able to make a call through account 5 to get all of their funds
assert_eq!(Balances::free_balance(5), 110);
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 1, value: 110 }));
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death {
dest: 1,
value: 110,
}));
assert_ok!(Recovery::as_recovered(RuntimeOrigin::signed(1), 5, call));
// All funds have been fully recovered!
assert_eq!(Balances::free_balance(1), 200);
+1 -1
View File
@@ -191,7 +191,7 @@ pub mod test {
#[test]
pub fn referendum_status_v0() {
// make sure the bytes of the encoded referendum v0 is decodable.
let ongoing_encoded = sp_core::Bytes::from_str("0x00000000013001012a000000000000000400000100000000000000010000000000000001000000000000000a00000000000000000000000000000000000100").unwrap();
let ongoing_encoded = sp_core::Bytes::from_str("0x00000000012c01082a0000000000000004000100000000000000010000000000000001000000000000000a00000000000000000000000000000000000100").unwrap();
let ongoing_dec = v0::ReferendumInfoOf::<T, ()>::decode(&mut &*ongoing_encoded).unwrap();
let ongoing = v0::ReferendumInfoOf::<T, ()>::Ongoing(create_status_v0());
assert_eq!(ongoing, ongoing_dec);
+8 -9
View File
@@ -57,7 +57,7 @@ frame_support::construct_runtime!(
pub struct BaseFilter;
impl Contains<RuntimeCall> for BaseFilter {
fn contains(call: &RuntimeCall) -> bool {
!matches!(call, &RuntimeCall::Balances(pallet_balances::Call::set_balance { .. }))
!matches!(call, &RuntimeCall::Balances(pallet_balances::Call::force_set_balance { .. }))
}
}
@@ -120,6 +120,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
parameter_types! {
pub static AlarmInterval: u64 = 1;
@@ -295,19 +299,14 @@ impl<Class> VoteTally<u32, Class> for Tally {
}
pub fn set_balance_proposal(value: u64) -> Vec<u8> {
RuntimeCall::Balances(pallet_balances::Call::set_balance {
who: 42,
new_free: value,
new_reserved: 0,
})
.encode()
RuntimeCall::Balances(pallet_balances::Call::force_set_balance { who: 42, new_free: value })
.encode()
}
pub fn set_balance_proposal_bounded(value: u64) -> BoundedCallOf<Test, ()> {
let c = RuntimeCall::Balances(pallet_balances::Call::set_balance {
let c = RuntimeCall::Balances(pallet_balances::Call::force_set_balance {
who: 42,
new_free: value,
new_reserved: 0,
});
<Preimage as StorePreimage>::bound(c).unwrap()
}
@@ -121,6 +121,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
pallet_staking_reward_curve::build! {
+4
View File
@@ -91,6 +91,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
parameter_types! {
@@ -84,6 +84,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU64<10>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
impl pallet_timestamp::Config for Test {
+4
View File
@@ -93,6 +93,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
impl Config for Test {
+4
View File
@@ -157,6 +157,10 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
sp_runtime::impl_opaque_keys! {
+11 -11
View File
@@ -30,7 +30,7 @@ use pallet_balances::Error as BalancesError;
use sp_runtime::{
assert_eq_error_rate,
traits::{BadOrigin, Dispatchable},
Perbill, Percent, Rounding,
Perbill, Percent, Rounding, TokenError,
};
use sp_staking::{
offence::{DisableStrategy, OffenceDetails, OnOffenceHandler},
@@ -98,8 +98,8 @@ fn force_unstake_works() {
add_slash(&11);
// Cant transfer
assert_noop!(
Balances::transfer(RuntimeOrigin::signed(11), 1, 10),
BalancesError::<Test, _>::LiquidityRestrictions
Balances::transfer_allow_death(RuntimeOrigin::signed(11), 1, 10),
TokenError::Frozen,
);
// Force unstake requires root.
assert_noop!(Staking::force_unstake(RuntimeOrigin::signed(11), 11, 2), BadOrigin);
@@ -113,7 +113,7 @@ fn force_unstake_works() {
// No longer bonded.
assert_eq!(Staking::bonded(&11), None);
// Transfer works.
assert_ok!(Balances::transfer(RuntimeOrigin::signed(11), 1, 10));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(11), 1, 10));
});
}
@@ -960,14 +960,14 @@ fn cannot_transfer_staked_balance() {
assert_eq!(Staking::eras_stakers(active_era(), 11).total, 1000);
// Confirm account 11 cannot transfer as a result
assert_noop!(
Balances::transfer(RuntimeOrigin::signed(11), 20, 1),
BalancesError::<Test, _>::LiquidityRestrictions
Balances::transfer_allow_death(RuntimeOrigin::signed(11), 20, 1),
TokenError::Frozen,
);
// Give account 11 extra free balance
let _ = Balances::make_free_balance_be(&11, 10000);
// Confirm that account 11 can now transfer some balance
assert_ok!(Balances::transfer(RuntimeOrigin::signed(11), 20, 1));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(11), 20, 1));
});
}
@@ -985,10 +985,10 @@ fn cannot_transfer_staked_balance_2() {
assert_eq!(Staking::eras_stakers(active_era(), 21).total, 1000);
// Confirm account 21 can transfer at most 1000
assert_noop!(
Balances::transfer(RuntimeOrigin::signed(21), 20, 1001),
BalancesError::<Test, _>::LiquidityRestrictions
Balances::transfer_allow_death(RuntimeOrigin::signed(21), 20, 1001),
TokenError::Frozen,
);
assert_ok!(Balances::transfer(RuntimeOrigin::signed(21), 20, 1000));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(21), 20, 1000));
});
}
@@ -4187,7 +4187,7 @@ fn payout_creates_controller() {
bond_nominator(1234, 1337, 100, vec![11]);
// kill controller
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1337), 1234, 100));
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1337), 1234, 100));
assert_eq!(Balances::free_balance(1337), 0);
mock::start_active_era(1);
@@ -1128,6 +1128,10 @@ mod mock {
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type WeightInfo = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type HoldIdentifier = ();
type MaxHolds = ();
}
/// Test only Weights for state migration.
@@ -17,7 +17,7 @@
//! Traits and associated datatypes for managing abstract stored values.
use crate::{storage::StorageMap, traits::misc::HandleLifetime};
use crate::storage::StorageMap;
use codec::FullCodec;
use sp_runtime::DispatchError;
@@ -81,48 +81,29 @@ pub trait StoredMap<K, T: Default> {
/// be the default value), or where the account is being removed or reset back to the default value
/// where previously it did exist (though may have been in a default state). This works well with
/// system module's `CallOnCreatedAccount` and `CallKillAccount`.
pub struct StorageMapShim<S, L, K, T>(sp_std::marker::PhantomData<(S, L, K, T)>);
impl<
S: StorageMap<K, T, Query = T>,
L: HandleLifetime<K>,
K: FullCodec,
T: FullCodec + Default,
> StoredMap<K, T> for StorageMapShim<S, L, K, T>
pub struct StorageMapShim<S, K, T>(sp_std::marker::PhantomData<(S, K, T)>);
impl<S: StorageMap<K, T, Query = T>, K: FullCodec, T: FullCodec + Default> StoredMap<K, T>
for StorageMapShim<S, K, T>
{
fn get(k: &K) -> T {
S::get(k)
}
fn insert(k: &K, t: T) -> Result<(), DispatchError> {
if !S::contains_key(&k) {
L::created(k)?;
}
S::insert(k, t);
Ok(())
}
fn remove(k: &K) -> Result<(), DispatchError> {
if S::contains_key(&k) {
L::killed(k)?;
S::remove(k);
}
Ok(())
}
fn mutate<R>(k: &K, f: impl FnOnce(&mut T) -> R) -> Result<R, DispatchError> {
if !S::contains_key(&k) {
L::created(k)?;
}
Ok(S::mutate(k, f))
}
fn mutate_exists<R>(k: &K, f: impl FnOnce(&mut Option<T>) -> R) -> Result<R, DispatchError> {
S::try_mutate_exists(k, |maybe_value| {
let existed = maybe_value.is_some();
let r = f(maybe_value);
let exists = maybe_value.is_some();
if !existed && exists {
L::created(k)?;
} else if existed && !exists {
L::killed(k)?;
}
Ok(r)
})
}
@@ -131,15 +112,7 @@ impl<
f: impl FnOnce(&mut Option<T>) -> Result<R, E>,
) -> Result<R, E> {
S::try_mutate_exists(k, |maybe_value| {
let existed = maybe_value.is_some();
let r = f(maybe_value)?;
let exists = maybe_value.is_some();
if !existed && exists {
L::created(k).map_err(E::from)?;
} else if existed && !exists {
L::killed(k).map_err(E::from)?;
}
Ok(r)
})
}
+2 -1
View File
@@ -30,6 +30,7 @@ pub use imbalance::Imbalance;
pub mod pay;
pub use misc::{
AssetId, Balance, BalanceConversion, BalanceStatus, ConvertRank, DepositConsequence,
ExistenceRequirement, GetSalary, Locker, WithdrawConsequence, WithdrawReasons,
ExistenceRequirement, Fortitude, GetSalary, Locker, Precision, Preservation, Provenance,
Restriction, WithdrawConsequence, WithdrawReasons,
};
pub use pay::{Pay, PayFromAccount, PaymentStatus};
@@ -1,367 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 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.
//! The traits for dealing with a single fungible token class and any associated types.
use super::{
misc::{Balance, DepositConsequence, WithdrawConsequence},
*,
};
use crate::{
dispatch::{DispatchError, DispatchResult},
traits::misc::Get,
};
use sp_runtime::traits::Saturating;
mod balanced;
mod imbalance;
pub use balanced::{Balanced, Unbalanced};
pub use imbalance::{CreditOf, DebtOf, HandleImbalanceDrop, Imbalance};
/// Trait for providing balance-inspection access to a fungible asset.
pub trait Inspect<AccountId> {
/// Scalar type for representing balance of an account.
type Balance: Balance;
/// The total amount of issuance in the system.
fn total_issuance() -> Self::Balance;
/// The total amount of issuance in the system excluding those which are controlled by the
/// system.
fn active_issuance() -> Self::Balance {
Self::total_issuance()
}
/// The minimum balance any single account may have.
fn minimum_balance() -> Self::Balance;
/// Get the balance of `who`.
fn balance(who: &AccountId) -> Self::Balance;
/// Get the maximum amount that `who` can withdraw/transfer successfully.
fn reducible_balance(who: &AccountId, keep_alive: bool) -> Self::Balance;
/// Returns `true` if the balance of `who` may be increased by `amount`.
///
/// - `who`: The account of which the balance should be increased by `amount`.
/// - `amount`: How much should the balance be increased?
/// - `mint`: Will `amount` be minted to deposit it into `account`?
fn can_deposit(who: &AccountId, amount: Self::Balance, mint: bool) -> DepositConsequence;
/// Returns `Failed` if the balance of `who` may not be decreased by `amount`, otherwise
/// the consequence.
fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence<Self::Balance>;
}
/// Trait for providing an ERC-20 style fungible asset.
pub trait Mutate<AccountId>: Inspect<AccountId> {
/// Increase the balance of `who` by exactly `amount`, minting new tokens. If that isn't
/// possible then an `Err` is returned and nothing is changed.
fn mint_into(who: &AccountId, amount: Self::Balance) -> DispatchResult;
/// Decrease the balance of `who` by at least `amount`, possibly slightly more in the case of
/// minimum_balance requirements, burning the tokens. If that isn't possible then an `Err` is
/// returned and nothing is changed. If successful, the amount of tokens reduced is returned.
fn burn_from(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError>;
// TODO: Remove.
/// Attempt to reduce the balance of `who` by as much as possible up to `amount`, and possibly
/// slightly more due to minimum_balance requirements. If no decrease is possible then an `Err`
/// is returned and nothing is changed. If successful, the amount of tokens reduced is returned.
///
/// The default implementation just uses `withdraw` along with `reducible_balance` to ensure
/// that it doesn't fail.
fn slash(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
Self::burn_from(who, Self::reducible_balance(who, false).min(amount))
}
/// Transfer funds from one account into another. The default implementation uses `mint_into`
/// and `burn_from` and may generate unwanted events.
fn teleport(
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
let extra = Self::can_withdraw(&source, amount).into_result()?;
// As we first burn and then mint, we don't need to check if `mint` fits into the supply.
// If we can withdraw/burn it, we can also mint it again.
Self::can_deposit(dest, amount.saturating_add(extra), false).into_result()?;
let actual = Self::burn_from(source, amount)?;
debug_assert!(
actual == amount.saturating_add(extra),
"can_withdraw must agree with withdraw; qed"
);
match Self::mint_into(dest, actual) {
Ok(_) => Ok(actual),
Err(err) => {
debug_assert!(false, "can_deposit returned true previously; qed");
// attempt to return the funds back to source
let revert = Self::mint_into(source, actual);
debug_assert!(revert.is_ok(), "withdrew funds previously; qed");
Err(err)
},
}
}
}
/// Trait for providing a fungible asset which can only be transferred.
pub trait Transfer<AccountId>: Inspect<AccountId> {
/// Transfer funds from one account into another.
fn transfer(
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
keep_alive: bool,
) -> Result<Self::Balance, DispatchError>;
/// Reduce the active issuance by some amount.
fn deactivate(_: Self::Balance) {}
/// Increase the active issuance by some amount, up to the outstanding amount reduced.
fn reactivate(_: Self::Balance) {}
}
/// Trait for inspecting a fungible asset which can be reserved.
pub trait InspectHold<AccountId>: Inspect<AccountId> {
/// Amount of funds held in reserve by `who`.
fn balance_on_hold(who: &AccountId) -> Self::Balance;
/// Check to see if some `amount` of funds of `who` may be placed on hold.
fn can_hold(who: &AccountId, amount: Self::Balance) -> bool;
}
// TODO: Introduce `HoldReason`.
/// Trait for mutating a fungible asset which can be reserved.
pub trait MutateHold<AccountId>: InspectHold<AccountId> + Transfer<AccountId> {
/// Hold some funds in an account.
fn hold(who: &AccountId, amount: Self::Balance) -> DispatchResult;
/// Release up to `amount` held funds in an account.
///
/// The actual amount released is returned with `Ok`.
///
/// If `best_effort` is `true`, then the amount actually unreserved and returned as the inner
/// value of `Ok` may be smaller than the `amount` passed.
fn release(
who: &AccountId,
amount: Self::Balance,
best_effort: bool,
) -> Result<Self::Balance, DispatchError>;
// TODO: Introduce repatriate_held
/// Transfer held funds into a destination account.
///
/// If `on_hold` is `true`, then the destination account must already exist and the assets
/// transferred will still be on hold in the destination account. If not, then the destination
/// account need not already exist, but must be creatable.
///
/// If `best_effort` is `true`, then an amount less than `amount` may be transferred without
/// error.
///
/// The actual amount transferred is returned, or `Err` in the case of error and nothing is
/// changed.
fn transfer_held(
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
best_effort: bool,
on_held: bool,
) -> Result<Self::Balance, DispatchError>;
}
/// Trait for slashing a fungible asset which can be reserved.
pub trait BalancedHold<AccountId>: Balanced<AccountId> + MutateHold<AccountId> {
/// Reduce the balance of some funds on hold in an account.
///
/// The resulting imbalance is the first item of the tuple returned.
///
/// As much funds that are on hold up to `amount` will be deducted as possible. If this is less
/// than `amount`, then a non-zero second item will be returned.
fn slash_held(
who: &AccountId,
amount: Self::Balance,
) -> (CreditOf<AccountId, Self>, Self::Balance);
}
impl<AccountId, T: Balanced<AccountId> + MutateHold<AccountId>> BalancedHold<AccountId> for T {
// TODO: This should be implemented properly, and `slash` should be removed.
fn slash_held(
who: &AccountId,
amount: Self::Balance,
) -> (CreditOf<AccountId, Self>, Self::Balance) {
let actual = match Self::release(who, amount, true) {
Ok(x) => x,
Err(_) => return (Imbalance::default(), amount),
};
<Self as fungible::Balanced<AccountId>>::slash(who, actual)
}
}
/// Convert a `fungibles` trait implementation into a `fungible` trait implementation by identifying
/// a single item.
pub struct ItemOf<
F: fungibles::Inspect<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
>(sp_std::marker::PhantomData<(F, A, AccountId)>);
impl<
F: fungibles::Inspect<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> Inspect<AccountId> for ItemOf<F, A, AccountId>
{
type Balance = <F as fungibles::Inspect<AccountId>>::Balance;
fn total_issuance() -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::total_issuance(A::get())
}
fn active_issuance() -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::active_issuance(A::get())
}
fn minimum_balance() -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::minimum_balance(A::get())
}
fn balance(who: &AccountId) -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::balance(A::get(), who)
}
fn reducible_balance(who: &AccountId, keep_alive: bool) -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::reducible_balance(A::get(), who, keep_alive)
}
fn can_deposit(who: &AccountId, amount: Self::Balance, mint: bool) -> DepositConsequence {
<F as fungibles::Inspect<AccountId>>::can_deposit(A::get(), who, amount, mint)
}
fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence<Self::Balance> {
<F as fungibles::Inspect<AccountId>>::can_withdraw(A::get(), who, amount)
}
}
impl<
F: fungibles::Mutate<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> Mutate<AccountId> for ItemOf<F, A, AccountId>
{
fn mint_into(who: &AccountId, amount: Self::Balance) -> DispatchResult {
<F as fungibles::Mutate<AccountId>>::mint_into(A::get(), who, amount)
}
fn burn_from(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Mutate<AccountId>>::burn_from(A::get(), who, amount)
}
}
impl<
F: fungibles::Transfer<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> Transfer<AccountId> for ItemOf<F, A, AccountId>
{
fn transfer(
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
keep_alive: bool,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Transfer<AccountId>>::transfer(A::get(), source, dest, amount, keep_alive)
}
fn deactivate(amount: Self::Balance) {
<F as fungibles::Transfer<AccountId>>::deactivate(A::get(), amount)
}
fn reactivate(amount: Self::Balance) {
<F as fungibles::Transfer<AccountId>>::reactivate(A::get(), amount)
}
}
impl<
F: fungibles::InspectHold<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> InspectHold<AccountId> for ItemOf<F, A, AccountId>
{
fn balance_on_hold(who: &AccountId) -> Self::Balance {
<F as fungibles::InspectHold<AccountId>>::balance_on_hold(A::get(), who)
}
fn can_hold(who: &AccountId, amount: Self::Balance) -> bool {
<F as fungibles::InspectHold<AccountId>>::can_hold(A::get(), who, amount)
}
}
impl<
F: fungibles::MutateHold<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> MutateHold<AccountId> for ItemOf<F, A, AccountId>
{
fn hold(who: &AccountId, amount: Self::Balance) -> DispatchResult {
<F as fungibles::MutateHold<AccountId>>::hold(A::get(), who, amount)
}
fn release(
who: &AccountId,
amount: Self::Balance,
best_effort: bool,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::MutateHold<AccountId>>::release(A::get(), who, amount, best_effort)
}
fn transfer_held(
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
best_effort: bool,
on_hold: bool,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::MutateHold<AccountId>>::transfer_held(
A::get(),
source,
dest,
amount,
best_effort,
on_hold,
)
}
}
impl<
F: fungibles::Unbalanced<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> Unbalanced<AccountId> for ItemOf<F, A, AccountId>
{
fn set_balance(who: &AccountId, amount: Self::Balance) -> DispatchResult {
<F as fungibles::Unbalanced<AccountId>>::set_balance(A::get(), who, amount)
}
fn set_total_issuance(amount: Self::Balance) -> () {
<F as fungibles::Unbalanced<AccountId>>::set_total_issuance(A::get(), amount)
}
fn decrease_balance(
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Unbalanced<AccountId>>::decrease_balance(A::get(), who, amount)
}
fn decrease_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance {
<F as fungibles::Unbalanced<AccountId>>::decrease_balance_at_most(A::get(), who, amount)
}
fn increase_balance(
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Unbalanced<AccountId>>::increase_balance(A::get(), who, amount)
}
fn increase_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance {
<F as fungibles::Unbalanced<AccountId>>::increase_balance_at_most(A::get(), who, amount)
}
}
@@ -1,354 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 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.
//! The trait and associated types for sets of fungible tokens that manage total issuance without
//! requiring atomic balanced operations.
use super::{super::Imbalance as ImbalanceT, *};
use crate::{
dispatch::{DispatchError, DispatchResult},
traits::misc::{SameOrOther, TryDrop},
};
use sp_runtime::{
traits::{CheckedAdd, Zero},
ArithmeticError, TokenError,
};
use sp_std::marker::PhantomData;
/// A fungible token class where any creation and deletion of tokens is semi-explicit and where the
/// total supply is maintained automatically.
///
/// This is auto-implemented when a token class has `Unbalanced` implemented.
pub trait Balanced<AccountId>: Inspect<AccountId> {
/// The type for managing what happens when an instance of `Debt` is dropped without being used.
type OnDropDebt: HandleImbalanceDrop<Self::Balance>;
/// The type for managing what happens when an instance of `Credit` is dropped without being
/// used.
type OnDropCredit: HandleImbalanceDrop<Self::Balance>;
/// Reduce the total issuance by `amount` and return the according imbalance. The imbalance will
/// typically be used to reduce an account by the same amount with e.g. `settle`.
///
/// This is infallible, but doesn't guarantee that the entire `amount` is burnt, for example
/// in the case of underflow.
fn rescind(amount: Self::Balance) -> DebtOf<AccountId, Self>;
/// Increase the total issuance by `amount` and return the according imbalance. The imbalance
/// will typically be used to increase an account by the same amount with e.g.
/// `resolve_into_existing` or `resolve_creating`.
///
/// This is infallible, but doesn't guarantee that the entire `amount` is issued, for example
/// in the case of overflow.
fn issue(amount: Self::Balance) -> CreditOf<AccountId, Self>;
/// Produce a pair of imbalances that cancel each other out exactly.
///
/// This is just the same as burning and issuing the same amount and has no effect on the
/// total issuance.
fn pair(amount: Self::Balance) -> (DebtOf<AccountId, Self>, CreditOf<AccountId, Self>) {
(Self::rescind(amount), Self::issue(amount))
}
/// Deducts up to `value` from the combined balance of `who`. This function cannot fail.
///
/// The resulting imbalance is the first item of the tuple returned.
///
/// As much funds up to `value` will be deducted as possible. If this is less than `value`,
/// then a non-zero second item will be returned.
fn slash(who: &AccountId, amount: Self::Balance) -> (CreditOf<AccountId, Self>, Self::Balance);
/// Mints exactly `value` into the account of `who`.
///
/// If `who` doesn't exist, nothing is done and an `Err` returned. This could happen because it
/// the account doesn't yet exist and it isn't possible to create it under the current
/// circumstances and with `value` in it.
fn deposit(
who: &AccountId,
value: Self::Balance,
) -> Result<DebtOf<AccountId, Self>, DispatchError>;
/// Removes `value` balance from `who` account if possible.
///
/// If the removal is not possible, then it returns `Err` and nothing is changed.
///
/// If the operation is successful, this will return `Ok` with a `NegativeImbalance` whose value
/// is no less than `value`. It may be more in the case that removing it reduced it below
/// `Self::minimum_balance()`.
fn withdraw(
who: &AccountId,
value: Self::Balance,
// TODO: liveness: ExistenceRequirement,
) -> Result<CreditOf<AccountId, Self>, DispatchError>;
/// The balance of `who` is increased in order to counter `credit`. If the whole of `credit`
/// cannot be countered, then nothing is changed and the original `credit` is returned in an
/// `Err`.
///
/// Please note: If `credit.peek()` is less than `Self::minimum_balance()`, then `who` must
/// already exist for this to succeed.
fn resolve(
who: &AccountId,
credit: CreditOf<AccountId, Self>,
) -> Result<(), CreditOf<AccountId, Self>> {
let v = credit.peek();
let debt = match Self::deposit(who, v) {
Err(_) => return Err(credit),
Ok(d) => d,
};
let result = credit.offset(debt).try_drop();
debug_assert!(result.is_ok(), "ok deposit return must be equal to credit value; qed");
Ok(())
}
/// The balance of `who` is decreased in order to counter `debt`. If the whole of `debt`
/// cannot be countered, then nothing is changed and the original `debt` is returned in an
/// `Err`.
fn settle(
who: &AccountId,
debt: DebtOf<AccountId, Self>,
// TODO: liveness: ExistenceRequirement,
) -> Result<CreditOf<AccountId, Self>, DebtOf<AccountId, Self>> {
let amount = debt.peek();
let credit = match Self::withdraw(who, amount) {
Err(_) => return Err(debt),
Ok(d) => d,
};
match credit.offset(debt) {
SameOrOther::None => Ok(CreditOf::<AccountId, Self>::zero()),
SameOrOther::Same(dust) => Ok(dust),
SameOrOther::Other(rest) => {
debug_assert!(false, "ok withdraw return must be at least debt value; qed");
Err(rest)
},
}
}
}
/// A fungible token class where the balance can be set arbitrarily.
///
/// **WARNING**
/// Do not use this directly unless you want trouble, since it allows you to alter account balances
/// without keeping the issuance up to date. It has no safeguards against accidentally creating
/// token imbalances in your system leading to accidental imflation or deflation. It's really just
/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to
/// use.
pub trait Unbalanced<AccountId>: Inspect<AccountId> {
/// Set the balance of `who` to `amount`. If this cannot be done for some reason (e.g.
/// because the account cannot be created or an overflow) then an `Err` is returned.
fn set_balance(who: &AccountId, amount: Self::Balance) -> DispatchResult;
/// Set the total issuance to `amount`.
fn set_total_issuance(amount: Self::Balance);
/// Reduce the balance of `who` by `amount`. If it cannot be reduced by that amount for
/// some reason, return `Err` and don't reduce it at all. If Ok, return the imbalance.
///
/// Minimum balance will be respected and the returned imbalance may be up to
/// `Self::minimum_balance() - 1` greater than `amount`.
fn decrease_balance(
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance(who);
let (mut new_balance, mut amount) = if Self::reducible_balance(who, false) < amount {
return Err(TokenError::NoFunds.into())
} else {
(old_balance - amount, amount)
};
if new_balance < Self::minimum_balance() {
amount = amount.saturating_add(new_balance);
new_balance = Zero::zero();
}
// Defensive only - this should not fail now.
Self::set_balance(who, new_balance)?;
Ok(amount)
}
/// Reduce the balance of `who` by the most that is possible, up to `amount`.
///
/// Minimum balance will be respected and the returned imbalance may be up to
/// `Self::minimum_balance() - 1` greater than `amount`.
///
/// Return the imbalance by which the account was reduced.
fn decrease_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance {
let old_balance = Self::balance(who);
let old_free_balance = Self::reducible_balance(who, false);
let (mut new_balance, mut amount) = if old_free_balance < amount {
(old_balance.saturating_sub(old_free_balance), old_free_balance)
} else {
(old_balance - amount, amount)
};
let minimum_balance = Self::minimum_balance();
if new_balance < minimum_balance {
amount = amount.saturating_add(new_balance);
new_balance = Zero::zero();
}
let mut r = Self::set_balance(who, new_balance);
if r.is_err() {
// Some error, probably because we tried to destroy an account which cannot be
// destroyed.
if new_balance.is_zero() && amount >= minimum_balance {
new_balance = minimum_balance;
amount -= minimum_balance;
r = Self::set_balance(who, new_balance);
}
if r.is_err() {
// Still an error. Apparently it's not possible to reduce at all.
amount = Zero::zero();
}
}
amount
}
/// Increase the balance of `who` by `amount`. If it cannot be increased by that amount
/// for some reason, return `Err` and don't increase it at all. If Ok, return the imbalance.
///
/// Minimum balance will be respected and an error will be returned if
/// `amount < Self::minimum_balance()` when the account of `who` is zero.
fn increase_balance(
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance(who);
let new_balance = old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?;
if new_balance < Self::minimum_balance() {
return Err(TokenError::BelowMinimum.into())
}
if old_balance != new_balance {
Self::set_balance(who, new_balance)?;
}
Ok(amount)
}
/// Increase the balance of `who` by the most that is possible, up to `amount`.
///
/// Minimum balance will be respected and the returned imbalance will be zero in the case that
/// `amount < Self::minimum_balance()`.
///
/// Return the imbalance by which the account was increased.
fn increase_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance {
let old_balance = Self::balance(who);
let mut new_balance = old_balance.saturating_add(amount);
let mut amount = new_balance - old_balance;
if new_balance < Self::minimum_balance() {
new_balance = Zero::zero();
amount = Zero::zero();
}
if old_balance == new_balance || Self::set_balance(who, new_balance).is_ok() {
amount
} else {
Zero::zero()
}
}
}
/// Simple handler for an imbalance drop which increases the total issuance of the system by the
/// imbalance amount. Used for leftover debt.
pub struct IncreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::Balance>
for IncreaseIssuance<AccountId, U>
{
fn handle(amount: U::Balance) {
U::set_total_issuance(U::total_issuance().saturating_add(amount))
}
}
/// Simple handler for an imbalance drop which decreases the total issuance of the system by the
/// imbalance amount. Used for leftover credit.
pub struct DecreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::Balance>
for DecreaseIssuance<AccountId, U>
{
fn handle(amount: U::Balance) {
U::set_total_issuance(U::total_issuance().saturating_sub(amount))
}
}
/// An imbalance type which uses `DecreaseIssuance` to deal with anything `Drop`ed.
///
/// Basically means that funds in someone's account have been removed and not yet placed anywhere
/// else. If it gets dropped, then those funds will be assumed to be "burned" and the total supply
/// will be accordingly decreased to ensure it equals the sum of the balances of all accounts.
type Credit<AccountId, U> = Imbalance<
<U as Inspect<AccountId>>::Balance,
DecreaseIssuance<AccountId, U>,
IncreaseIssuance<AccountId, U>,
>;
/// An imbalance type which uses `IncreaseIssuance` to deal with anything `Drop`ed.
///
/// Basically means that there are funds in someone's account whose origin is as yet unaccounted
/// for. If it gets dropped, then those funds will be assumed to be "minted" and the total supply
/// will be accordingly increased to ensure it equals the sum of the balances of all accounts.
type Debt<AccountId, U> = Imbalance<
<U as Inspect<AccountId>>::Balance,
IncreaseIssuance<AccountId, U>,
DecreaseIssuance<AccountId, U>,
>;
/// Create some `Credit` item. Only for internal use.
fn credit<AccountId, U: Unbalanced<AccountId>>(amount: U::Balance) -> Credit<AccountId, U> {
Imbalance::new(amount)
}
/// Create some `Debt` item. Only for internal use.
fn debt<AccountId, U: Unbalanced<AccountId>>(amount: U::Balance) -> Debt<AccountId, U> {
Imbalance::new(amount)
}
impl<AccountId, U: Unbalanced<AccountId>> Balanced<AccountId> for U {
type OnDropCredit = DecreaseIssuance<AccountId, U>;
type OnDropDebt = IncreaseIssuance<AccountId, U>;
fn rescind(amount: Self::Balance) -> Debt<AccountId, Self> {
let old = U::total_issuance();
let new = old.saturating_sub(amount);
U::set_total_issuance(new);
debt(old - new)
}
fn issue(amount: Self::Balance) -> Credit<AccountId, Self> {
let old = U::total_issuance();
let new = old.saturating_add(amount);
U::set_total_issuance(new);
credit(new - old)
}
fn slash(who: &AccountId, amount: Self::Balance) -> (Credit<AccountId, Self>, Self::Balance) {
let slashed = U::decrease_balance_at_most(who, amount);
// `slashed` could be less than, greater than or equal to `amount`.
// If slashed == amount, it means the account had at least amount in it and it could all be
// removed without a problem.
// If slashed > amount, it means the account had more than amount in it, but not enough more
// to push it over minimum_balance.
// If slashed < amount, it means the account didn't have enough in it to be reduced by
// `amount` without being destroyed.
(credit(slashed), amount.saturating_sub(slashed))
}
fn deposit(
who: &AccountId,
amount: Self::Balance,
) -> Result<Debt<AccountId, Self>, DispatchError> {
let increase = U::increase_balance(who, amount)?;
Ok(debt(increase))
}
fn withdraw(
who: &AccountId,
amount: Self::Balance,
// TODO: liveness: ExistenceRequirement,
) -> Result<Credit<AccountId, Self>, DispatchError> {
let decrease = U::decrease_balance(who, amount)?;
Ok(credit(decrease))
}
}
@@ -0,0 +1,68 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 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.
//! The traits for putting freezes within a single fungible token class.
use scale_info::TypeInfo;
use sp_runtime::DispatchResult;
/// Trait for inspecting a fungible asset which can be frozen. Freezing is essentially setting a
/// minimum balance bellow which the total balance (inclusive of any funds placed on hold) may not
/// be normally allowed to drop. Generally, freezers will provide an "update" function such that
/// if the total balance does drop below the limit, then the freezer can update their housekeeping
/// accordingly.
pub trait Inspect<AccountId>: super::Inspect<AccountId> {
/// An identifier for a freeze.
type Id: codec::Encode + TypeInfo + 'static;
/// Amount of funds held in reserve by `who` for the given `id`.
fn balance_frozen(id: &Self::Id, who: &AccountId) -> Self::Balance;
/// The amount of the balance which can become frozen. Defaults to `total_balance()`.
fn balance_freezable(who: &AccountId) -> Self::Balance {
Self::total_balance(who)
}
/// Returns `true` if it's possible to introduce a freeze for the given `id` onto the
/// account of `who`. This will be true as long as the implementor supports as many
/// concurrent freeze locks as there are possible values of `id`.
fn can_freeze(id: &Self::Id, who: &AccountId) -> bool;
}
/// Trait for introducing, altering and removing locks to freeze an account's funds so they never
/// go below a set minimum.
pub trait Mutate<AccountId>: Inspect<AccountId> {
/// Prevent actions which would reduce the balance of the account of `who` below the given
/// `amount` and identify this restriction though the given `id`. Unlike `extend_freeze`, any
/// outstanding freeze in place for `who` under the `id` are dropped.
///
/// If `amount` is zero, it is equivalent to using `thaw`.
///
/// Note that `amount` can be greater than the total balance, if desired.
fn set_freeze(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult;
/// Prevent the balance of the account of `who` from being reduced below the given `amount` and
/// identify this restriction though the given `id`. Unlike `set_freeze`, this does not
/// counteract any pre-existing freezes in place for `who` under the `id`. Also unlike
/// `set_freeze`, in the case that `amount` is zero, this is no-op and never fails.
///
/// Note that more funds can be locked than the total balance, if desired.
fn extend_freeze(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult;
/// Remove an existing lock.
fn thaw(id: &Self::Id, who: &AccountId) -> DispatchResult;
}
@@ -0,0 +1,393 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 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.
//! The traits for putting holds within a single fungible token class.
use crate::{
ensure,
traits::tokens::{
DepositConsequence::Success,
Fortitude::{self, Force},
Precision::{self, BestEffort, Exact},
Preservation::{self, Protect},
Provenance::Extant,
Restriction::{self, Free, OnHold},
},
};
use scale_info::TypeInfo;
use sp_arithmetic::{
traits::{CheckedAdd, CheckedSub, Zero},
ArithmeticError,
};
use sp_runtime::{DispatchError, DispatchResult, Saturating, TokenError};
use super::*;
/// Trait for inspecting a fungible asset whose accounts support partitioning and slashing.
pub trait Inspect<AccountId>: super::Inspect<AccountId> {
/// An identifier for a hold. Used for disambiguating different holds so that
/// they can be individually replaced or removed and funds from one hold don't accidentally
/// become unreserved or slashed for another.
type Reason: codec::Encode + TypeInfo + 'static;
/// Amount of funds on hold (for all hold reasons) of `who`.
fn total_balance_on_hold(who: &AccountId) -> Self::Balance;
/// Get the maximum amount that the `total_balance_on_hold` of `who` can be reduced successfully
/// based on whether we are willing to force the reduction and potentially go below user-level
/// restrictions on the minimum amount of the account. Note: This cannot bring the account into
/// an inconsistent state with regards any required existential deposit.
///
/// Always less than `total_balance_on_hold()`.
fn reducible_total_balance_on_hold(who: &AccountId, force: Fortitude) -> Self::Balance;
/// Amount of funds on hold (for the given reason) of `who`.
fn balance_on_hold(reason: &Self::Reason, who: &AccountId) -> Self::Balance;
/// Returns `true` if it's possible to place (additional) funds under a hold of a given
/// `reason`. This may fail if the account has exhausted a limited number of concurrent
/// holds or if it cannot be made to exist (e.g. there is no provider reference).
///
/// NOTE: This does not take into account changes which could be made to the account of `who`
/// (such as removing a provider reference) after this call is made. Any usage of this should
/// therefore ensure the account is already in the appropriate state prior to calling it.
fn hold_available(reason: &Self::Reason, who: &AccountId) -> bool;
/// Check to see if some `amount` of funds of `who` may be placed on hold with the given
/// `reason`. Reasons why this may not be true:
///
/// - The implementor supports only a limited number of concurrent holds on an account which is
/// the possible values of `reason`;
/// - The total balance of the account is less than `amount`;
/// - Removing `amount` from the total balance would kill the account and remove the only
/// provider reference.
///
/// Note: we pass `true` as the third argument to `reducible_balance` since we assume that if
/// needed the balance can slashed. If we are using a simple non-forcing reserve-transfer, then
/// we really ought to check that we are not reducing the funds below the freeze-limit (if any).
///
/// NOTE: This does not take into account changes which could be made to the account of `who`
/// (such as removing a provider reference) after this call is made. Any usage of this should
/// therefore ensure the account is already in the appropriate state prior to calling it.
fn ensure_can_hold(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
) -> DispatchResult {
ensure!(Self::hold_available(reason, who), TokenError::CannotCreateHold);
ensure!(
amount <= Self::reducible_balance(who, Protect, Force),
TokenError::FundsUnavailable
);
Ok(())
}
/// Check to see if some `amount` of funds of `who` may be placed on hold for the given
/// `reason`. Reasons why this may not be true:
///
/// - The implementor supports only a limited number of concurrernt holds on an account which is
/// the possible values of `reason`;
/// - The main balance of the account is less than `amount`;
/// - Removing `amount` from the main balance would kill the account and remove the only
/// provider reference.
///
/// NOTE: This does not take into account changes which could be made to the account of `who`
/// (such as removing a provider reference) after this call is made. Any usage of this should
/// therefore ensure the account is already in the appropriate state prior to calling it.
fn can_hold(reason: &Self::Reason, who: &AccountId, amount: Self::Balance) -> bool {
Self::ensure_can_hold(reason, who, amount).is_ok()
}
}
/// A fungible, holdable token class where the balance on hold can be set arbitrarily.
///
/// **WARNING**
/// Do not use this directly unless you want trouble, since it allows you to alter account balances
/// without keeping the issuance up to date. It has no safeguards against accidentally creating
/// token imbalances in your system leading to accidental imflation or deflation. It's really just
/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to
/// use.
pub trait Unbalanced<AccountId>: Inspect<AccountId> {
/// Forcefully set the balance on hold of `who` to `amount`. This is independent of any other
/// balances on hold or the main ("free") balance.
///
/// If this call executes successfully, you can `assert_eq!(Self::balance_on_hold(), amount);`.
///
/// This function does its best to force the balance change through, but will not break system
/// invariants such as any Existential Deposits needed or overflows/underflows.
/// If this cannot be done for some reason (e.g. because the account doesn't exist) then an
/// `Err` is returned.
// Implmentation note: This should increment the consumer refs if it moves total on hold from
// zero to non-zero and decrement in the opposite direction.
//
// Since this was not done in the previous logic, this will need either a migration or a
// state item which tracks whether the account is on the old logic or new.
fn set_balance_on_hold(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
) -> DispatchResult;
/// Reduce the balance on hold of `who` by `amount`.
///
/// If `precision` is `Exact` and it cannot be reduced by that amount for
/// some reason, return `Err` and don't reduce it at all. If `precision` is `BestEffort`, then
/// reduce the balance of `who` by the most that is possible, up to `amount`.
///
/// In either case, if `Ok` is returned then the inner is the amount by which is was reduced.
fn decrease_balance_on_hold(
reason: &Self::Reason,
who: &AccountId,
mut amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance_on_hold(reason, who);
if let BestEffort = precision {
amount = amount.min(old_balance);
}
let new_balance = old_balance.checked_sub(&amount).ok_or(TokenError::FundsUnavailable)?;
Self::set_balance_on_hold(reason, who, new_balance)?;
Ok(amount)
}
/// Increase the balance on hold of `who` by `amount`.
///
/// If it cannot be increased by that amount for some reason, return `Err` and don't increase
/// it at all. If Ok, return the imbalance.
fn increase_balance_on_hold(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance_on_hold(reason, who);
let new_balance = if let BestEffort = precision {
old_balance.saturating_add(amount)
} else {
old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?
};
let amount = new_balance.saturating_sub(old_balance);
if !amount.is_zero() {
Self::set_balance_on_hold(reason, who, new_balance)?;
}
Ok(amount)
}
}
/// Trait for mutating a fungible asset which can be placed on hold.
pub trait Mutate<AccountId>:
Inspect<AccountId> + super::Unbalanced<AccountId> + Unbalanced<AccountId>
{
/// Hold some funds in an account. If a hold for `reason` is already in place, then this
/// will increase it.
fn hold(reason: &Self::Reason, who: &AccountId, amount: Self::Balance) -> DispatchResult {
// NOTE: This doesn't change the total balance of the account so there's no need to
// check liquidity.
Self::ensure_can_hold(reason, who, amount)?;
// Should be infallible now, but we proceed softly anyway.
Self::decrease_balance(who, amount, Exact, Protect, Force)?;
Self::increase_balance_on_hold(reason, who, amount, BestEffort)?;
Self::done_hold(reason, who, amount);
Ok(())
}
/// Release up to `amount` held funds in an account.
///
/// The actual amount released is returned with `Ok`.
///
/// If `precision` is `BestEffort`, then the amount actually unreserved and returned as the
/// inner value of `Ok` may be smaller than the `amount` passed.
///
/// NOTE! The inner of the `Ok` result variant returns the *actual* amount released. This is the
/// opposite of the `ReservableCurrency::unreserve()` result, which gives the amount not able
/// to be released!
fn release(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
// NOTE: This doesn't change the total balance of the account so there's no need to
// check liquidity.
// We want to make sure we can deposit the amount in advance. If we can't then something is
// very wrong.
ensure!(Self::can_deposit(who, amount, Extant) == Success, TokenError::CannotCreate);
// Get the amount we can actually take from the hold. This might be less than what we want
// if we're only doing a best-effort.
let amount = Self::decrease_balance_on_hold(reason, who, amount, precision)?;
// Increase the main balance by what we took. We always do a best-effort here because we
// already checked that we can deposit before.
let actual = Self::increase_balance(who, amount, BestEffort)?;
Self::done_release(reason, who, actual);
Ok(actual)
}
/// Attempt to decrease the balance of `who` which is held for the given `reason` by `amount`.
///
/// If `precision` is `BestEffort`, then as much as possible is reduced, up to `amount`, and the
/// amount of tokens reduced is returned. Otherwise, if the total amount can be reduced, then it
/// is and the amount returned, and if not, then nothing changes and `Err` is returned.
///
/// If `force` is `Force`, then locks/freezes will be ignored. This should only be used when
/// conducting slashing or other activity which materially disadvantages the account holder
/// since it could provide a means of circumventing freezes.
fn burn_held(
reason: &Self::Reason,
who: &AccountId,
mut amount: Self::Balance,
precision: Precision,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
// We must check total-balance requirements if `!force`.
let liquid = Self::reducible_total_balance_on_hold(who, force);
if let BestEffort = precision {
amount = amount.min(liquid);
} else {
ensure!(amount <= liquid, TokenError::Frozen);
}
let amount = Self::decrease_balance_on_hold(reason, who, amount, precision)?;
Self::set_total_issuance(Self::total_issuance().saturating_sub(amount));
Self::done_burn_held(reason, who, amount);
Ok(amount)
}
/// Transfer held funds into a destination account.
///
/// If `on_hold` is `true`, then the destination account must already exist and the assets
/// transferred will still be on hold in the destination account. If not, then the destination
/// account need not already exist, but must be creatable.
///
/// If `precision` is `BestEffort`, then an amount less than `amount` may be transferred without
/// error.
///
/// If `force` is `Force`, then other fund-locking mechanisms may be disregarded. It should be
/// left as `Polite` in most circumstances, but when you want the same power as a `slash`, it
/// may be `Force`.
///
/// The actual amount transferred is returned, or `Err` in the case of error and nothing is
/// changed.
fn transfer_on_hold(
reason: &Self::Reason,
source: &AccountId,
dest: &AccountId,
mut amount: Self::Balance,
precision: Precision,
mode: Restriction,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
// We must check total-balance requirements if `force` is `Fortitude::Polite`.
let have = Self::balance_on_hold(reason, source);
let liquid = Self::reducible_total_balance_on_hold(source, force);
if let BestEffort = precision {
amount = amount.min(liquid).min(have);
} else {
ensure!(amount <= liquid, TokenError::Frozen);
ensure!(amount <= have, TokenError::FundsUnavailable);
}
// We want to make sure we can deposit the amount in advance. If we can't then something is
// very wrong.
ensure!(Self::can_deposit(dest, amount, Extant) == Success, TokenError::CannotCreate);
ensure!(mode == Free || Self::hold_available(reason, dest), TokenError::CannotCreateHold);
let amount = Self::decrease_balance_on_hold(reason, source, amount, precision)?;
let actual = if mode == OnHold {
Self::increase_balance_on_hold(reason, dest, amount, precision)?
} else {
Self::increase_balance(dest, amount, precision)?
};
Self::done_transfer_on_hold(reason, source, dest, actual);
Ok(actual)
}
/// Transfer some `amount` of free balance from `source` to become owned by `dest` but on hold
/// for `reason`.
///
/// If `precision` is `BestEffort`, then an amount less than `amount` may be transferred without
/// error.
///
/// `source` must obey the requirements of `keep_alive`.
///
/// If `force` is `Force`, then other fund-locking mechanisms may be disregarded. It should be
/// left as `Polite` in most circumstances, but when you want the same power as a `slash`, it
/// may be `Force`.
///
/// The amount placed on hold is returned or `Err` in the case of error and nothing is changed.
///
/// WARNING: This may return an error after a partial storage mutation. It should be used only
/// inside a transactional storage context and an `Err` result must imply a storage rollback.
fn transfer_and_hold(
reason: &Self::Reason,
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
precision: Precision,
expendability: Preservation,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
ensure!(Self::hold_available(reason, dest), TokenError::CannotCreateHold);
ensure!(Self::can_deposit(dest, amount, Extant) == Success, TokenError::CannotCreate);
let actual = Self::decrease_balance(source, amount, precision, expendability, force)?;
Self::increase_balance_on_hold(reason, dest, actual, precision)?;
Self::done_transfer_on_hold(reason, source, dest, actual);
Ok(actual)
}
fn done_hold(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {}
fn done_release(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {}
fn done_burn_held(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {}
fn done_transfer_on_hold(
_reason: &Self::Reason,
_source: &AccountId,
_dest: &AccountId,
_amount: Self::Balance,
) {
}
fn done_transfer_and_hold(
_reason: &Self::Reason,
_source: &AccountId,
_dest: &AccountId,
_transferred: Self::Balance,
) {
}
}
/// Trait for slashing a fungible asset which can be place on hold.
pub trait Balanced<AccountId>: super::Balanced<AccountId> + Unbalanced<AccountId> {
/// Reduce the balance of some funds on hold in an account.
///
/// The resulting imbalance is the first item of the tuple returned.
///
/// As much funds that are on hold up to `amount` will be deducted as possible. If this is less
/// than `amount`, then a non-zero second item will be returned.
fn slash(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
) -> (Credit<AccountId, Self>, Self::Balance) {
let decrease = Self::decrease_balance_on_hold(reason, who, amount, BestEffort)
.unwrap_or(Default::default());
let credit =
Imbalance::<Self::Balance, Self::OnDropCredit, Self::OnDropDebt>::new(decrease);
Self::done_slash(reason, who, decrease);
(credit, amount.saturating_sub(decrease))
}
fn done_slash(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {}
}
@@ -18,8 +18,11 @@
//! The imbalance type and its associates, which handles keeps everything adding up properly with
//! unbalanced operations.
use super::{super::Imbalance as ImbalanceT, balanced::Balanced, misc::Balance, *};
use crate::traits::misc::{SameOrOther, TryDrop};
use super::{super::Imbalance as ImbalanceT, Balanced, *};
use crate::traits::{
misc::{SameOrOther, TryDrop},
tokens::Balance,
};
use sp_runtime::{traits::Zero, RuntimeDebug};
use sp_std::marker::PhantomData;
@@ -30,6 +33,10 @@ pub trait HandleImbalanceDrop<Balance> {
fn handle(amount: Balance);
}
impl<Balance> HandleImbalanceDrop<Balance> for () {
fn handle(_: Balance) {}
}
/// An imbalance in the system, representing a divergence of recorded token supply from the sum of
/// the balances of all accounts. This is `must_use` in order to ensure it gets handled (placing
/// into an account, settling from an account or altering the supply).
@@ -135,7 +142,7 @@ impl<B: Balance, OnDrop: HandleImbalanceDrop<B>, OppositeOnDrop: HandleImbalance
}
/// Imbalance implying that the total_issuance value is less than the sum of all account balances.
pub type DebtOf<AccountId, B> = Imbalance<
pub type Debt<AccountId, B> = Imbalance<
<B as Inspect<AccountId>>::Balance,
// This will generally be implemented by increasing the total_issuance value.
<B as Balanced<AccountId>>::OnDropDebt,
@@ -144,7 +151,7 @@ pub type DebtOf<AccountId, B> = Imbalance<
/// Imbalance implying that the total_issuance value is greater than the sum of all account
/// balances.
pub type CreditOf<AccountId, B> = Imbalance<
pub type Credit<AccountId, B> = Imbalance<
<B as Inspect<AccountId>>::Balance,
// This will generally be implemented by decreasing the total_issuance value.
<B as Balanced<AccountId>>::OnDropCredit,
@@ -0,0 +1,451 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 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.
//! Adapter to use `fungibles::*` implementations as `fungible::*`.
use sp_core::Get;
use sp_runtime::{DispatchError, DispatchResult};
use super::*;
use crate::traits::tokens::{
fungibles, DepositConsequence, Fortitude, Imbalance as ImbalanceT, Precision, Preservation,
Provenance, Restriction, WithdrawConsequence,
};
/// Convert a `fungibles` trait implementation into a `fungible` trait implementation by identifying
/// a single item.
pub struct ItemOf<
F: fungibles::Inspect<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
>(sp_std::marker::PhantomData<(F, A, AccountId)>);
impl<
F: fungibles::Inspect<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> Inspect<AccountId> for ItemOf<F, A, AccountId>
{
type Balance = <F as fungibles::Inspect<AccountId>>::Balance;
fn total_issuance() -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::total_issuance(A::get())
}
fn active_issuance() -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::active_issuance(A::get())
}
fn minimum_balance() -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::minimum_balance(A::get())
}
fn balance(who: &AccountId) -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::balance(A::get(), who)
}
fn total_balance(who: &AccountId) -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::total_balance(A::get(), who)
}
fn reducible_balance(
who: &AccountId,
preservation: Preservation,
force: Fortitude,
) -> Self::Balance {
<F as fungibles::Inspect<AccountId>>::reducible_balance(A::get(), who, preservation, force)
}
fn can_deposit(
who: &AccountId,
amount: Self::Balance,
provenance: Provenance,
) -> DepositConsequence {
<F as fungibles::Inspect<AccountId>>::can_deposit(A::get(), who, amount, provenance)
}
fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence<Self::Balance> {
<F as fungibles::Inspect<AccountId>>::can_withdraw(A::get(), who, amount)
}
}
impl<
F: fungibles::InspectHold<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> InspectHold<AccountId> for ItemOf<F, A, AccountId>
{
type Reason = F::Reason;
fn reducible_total_balance_on_hold(who: &AccountId, force: Fortitude) -> Self::Balance {
<F as fungibles::InspectHold<AccountId>>::reducible_total_balance_on_hold(
A::get(),
who,
force,
)
}
fn hold_available(reason: &Self::Reason, who: &AccountId) -> bool {
<F as fungibles::InspectHold<AccountId>>::hold_available(A::get(), reason, who)
}
fn total_balance_on_hold(who: &AccountId) -> Self::Balance {
<F as fungibles::InspectHold<AccountId>>::total_balance_on_hold(A::get(), who)
}
fn balance_on_hold(reason: &Self::Reason, who: &AccountId) -> Self::Balance {
<F as fungibles::InspectHold<AccountId>>::balance_on_hold(A::get(), reason, who)
}
fn can_hold(reason: &Self::Reason, who: &AccountId, amount: Self::Balance) -> bool {
<F as fungibles::InspectHold<AccountId>>::can_hold(A::get(), reason, who, amount)
}
}
impl<
F: fungibles::InspectFreeze<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> InspectFreeze<AccountId> for ItemOf<F, A, AccountId>
{
type Id = F::Id;
fn balance_frozen(id: &Self::Id, who: &AccountId) -> Self::Balance {
<F as fungibles::InspectFreeze<AccountId>>::balance_frozen(A::get(), id, who)
}
fn balance_freezable(who: &AccountId) -> Self::Balance {
<F as fungibles::InspectFreeze<AccountId>>::balance_freezable(A::get(), who)
}
fn can_freeze(id: &Self::Id, who: &AccountId) -> bool {
<F as fungibles::InspectFreeze<AccountId>>::can_freeze(A::get(), id, who)
}
}
impl<
F: fungibles::Unbalanced<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> Unbalanced<AccountId> for ItemOf<F, A, AccountId>
{
fn handle_dust(dust: regular::Dust<AccountId, Self>)
where
Self: Sized,
{
<F as fungibles::Unbalanced<AccountId>>::handle_dust(fungibles::Dust(A::get(), dust.0))
}
fn write_balance(
who: &AccountId,
amount: Self::Balance,
) -> Result<Option<Self::Balance>, DispatchError> {
<F as fungibles::Unbalanced<AccountId>>::write_balance(A::get(), who, amount)
}
fn set_total_issuance(amount: Self::Balance) -> () {
<F as fungibles::Unbalanced<AccountId>>::set_total_issuance(A::get(), amount)
}
fn decrease_balance(
who: &AccountId,
amount: Self::Balance,
precision: Precision,
preservation: Preservation,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Unbalanced<AccountId>>::decrease_balance(
A::get(),
who,
amount,
precision,
preservation,
force,
)
}
fn increase_balance(
who: &AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Unbalanced<AccountId>>::increase_balance(A::get(), who, amount, precision)
}
}
impl<
F: fungibles::UnbalancedHold<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> UnbalancedHold<AccountId> for ItemOf<F, A, AccountId>
{
fn set_balance_on_hold(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
) -> DispatchResult {
<F as fungibles::UnbalancedHold<AccountId>>::set_balance_on_hold(
A::get(),
reason,
who,
amount,
)
}
fn decrease_balance_on_hold(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::UnbalancedHold<AccountId>>::decrease_balance_on_hold(
A::get(),
reason,
who,
amount,
precision,
)
}
fn increase_balance_on_hold(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::UnbalancedHold<AccountId>>::increase_balance_on_hold(
A::get(),
reason,
who,
amount,
precision,
)
}
}
impl<
F: fungibles::Mutate<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> Mutate<AccountId> for ItemOf<F, A, AccountId>
{
fn mint_into(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Mutate<AccountId>>::mint_into(A::get(), who, amount)
}
fn burn_from(
who: &AccountId,
amount: Self::Balance,
precision: Precision,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Mutate<AccountId>>::burn_from(A::get(), who, amount, precision, force)
}
fn shelve(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Mutate<AccountId>>::shelve(A::get(), who, amount)
}
fn restore(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Mutate<AccountId>>::restore(A::get(), who, amount)
}
fn transfer(
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
preservation: Preservation,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::Mutate<AccountId>>::transfer(A::get(), source, dest, amount, preservation)
}
fn set_balance(who: &AccountId, amount: Self::Balance) -> Self::Balance {
<F as fungibles::Mutate<AccountId>>::set_balance(A::get(), who, amount)
}
}
impl<
F: fungibles::MutateHold<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> MutateHold<AccountId> for ItemOf<F, A, AccountId>
{
fn hold(reason: &Self::Reason, who: &AccountId, amount: Self::Balance) -> DispatchResult {
<F as fungibles::MutateHold<AccountId>>::hold(A::get(), reason, who, amount)
}
fn release(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::MutateHold<AccountId>>::release(A::get(), reason, who, amount, precision)
}
fn burn_held(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
precision: Precision,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::MutateHold<AccountId>>::burn_held(
A::get(),
reason,
who,
amount,
precision,
force,
)
}
fn transfer_on_hold(
reason: &Self::Reason,
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
precision: Precision,
mode: Restriction,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::MutateHold<AccountId>>::transfer_on_hold(
A::get(),
reason,
source,
dest,
amount,
precision,
mode,
force,
)
}
fn transfer_and_hold(
reason: &Self::Reason,
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
precision: Precision,
preservation: Preservation,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
<F as fungibles::MutateHold<AccountId>>::transfer_and_hold(
A::get(),
reason,
source,
dest,
amount,
precision,
preservation,
force,
)
}
}
impl<
F: fungibles::MutateFreeze<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> MutateFreeze<AccountId> for ItemOf<F, A, AccountId>
{
fn set_freeze(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult {
<F as fungibles::MutateFreeze<AccountId>>::set_freeze(A::get(), id, who, amount)
}
fn extend_freeze(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult {
<F as fungibles::MutateFreeze<AccountId>>::extend_freeze(A::get(), id, who, amount)
}
fn thaw(id: &Self::Id, who: &AccountId) -> DispatchResult {
<F as fungibles::MutateFreeze<AccountId>>::thaw(A::get(), id, who)
}
}
pub struct ConvertImbalanceDropHandler<AccountId, Balance, AssetIdType, AssetId, Handler>(
sp_std::marker::PhantomData<(AccountId, Balance, AssetIdType, AssetId, Handler)>,
);
impl<
AccountId,
Balance,
AssetIdType,
AssetId: Get<AssetIdType>,
Handler: crate::traits::tokens::fungibles::HandleImbalanceDrop<AssetIdType, Balance>,
> HandleImbalanceDrop<Balance>
for ConvertImbalanceDropHandler<AccountId, Balance, AssetIdType, AssetId, Handler>
{
fn handle(amount: Balance) {
Handler::handle(AssetId::get(), amount)
}
}
impl<
F: fungibles::Inspect<AccountId>
+ fungibles::Unbalanced<AccountId>
+ fungibles::Balanced<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> Balanced<AccountId> for ItemOf<F, A, AccountId>
{
type OnDropDebt =
ConvertImbalanceDropHandler<AccountId, Self::Balance, F::AssetId, A, F::OnDropDebt>;
type OnDropCredit =
ConvertImbalanceDropHandler<AccountId, Self::Balance, F::AssetId, A, F::OnDropCredit>;
fn deposit(
who: &AccountId,
value: Self::Balance,
precision: Precision,
) -> Result<Debt<AccountId, Self>, DispatchError> {
<F as fungibles::Balanced<AccountId>>::deposit(A::get(), who, value, precision)
.map(|debt| Imbalance::new(debt.peek()))
}
fn issue(amount: Self::Balance) -> Credit<AccountId, Self> {
Imbalance::new(<F as fungibles::Balanced<AccountId>>::issue(A::get(), amount).peek())
}
fn pair(amount: Self::Balance) -> (Debt<AccountId, Self>, Credit<AccountId, Self>) {
let (a, b) = <F as fungibles::Balanced<AccountId>>::pair(A::get(), amount);
(Imbalance::new(a.peek()), Imbalance::new(b.peek()))
}
fn rescind(amount: Self::Balance) -> Debt<AccountId, Self> {
Imbalance::new(<F as fungibles::Balanced<AccountId>>::rescind(A::get(), amount).peek())
}
fn resolve(
who: &AccountId,
credit: Credit<AccountId, Self>,
) -> Result<(), Credit<AccountId, Self>> {
let credit = fungibles::Imbalance::new(A::get(), credit.peek());
<F as fungibles::Balanced<AccountId>>::resolve(who, credit)
.map_err(|credit| Imbalance::new(credit.peek()))
}
fn settle(
who: &AccountId,
debt: Debt<AccountId, Self>,
preservation: Preservation,
) -> Result<Credit<AccountId, Self>, Debt<AccountId, Self>> {
let debt = fungibles::Imbalance::new(A::get(), debt.peek());
<F as fungibles::Balanced<AccountId>>::settle(who, debt, preservation)
.map(|credit| Imbalance::new(credit.peek()))
.map_err(|debt| Imbalance::new(debt.peek()))
}
fn withdraw(
who: &AccountId,
value: Self::Balance,
precision: Precision,
preservation: Preservation,
force: Fortitude,
) -> Result<Credit<AccountId, Self>, DispatchError> {
<F as fungibles::Balanced<AccountId>>::withdraw(
A::get(),
who,
value,
precision,
preservation,
force,
)
.map(|credit| Imbalance::new(credit.peek()))
}
}
impl<
F: fungibles::BalancedHold<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
> BalancedHold<AccountId> for ItemOf<F, A, AccountId>
{
fn slash(
reason: &Self::Reason,
who: &AccountId,
amount: Self::Balance,
) -> (Credit<AccountId, Self>, Self::Balance) {
let (credit, amount) =
<F as fungibles::BalancedHold<AccountId>>::slash(A::get(), reason, who, amount);
(Imbalance::new(credit.peek()), amount)
}
}
#[test]
fn test() {}
@@ -0,0 +1,56 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 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.
//! The traits for dealing with a single fungible token class and any associated types.
//!
//! ### User-implememted traits
//! - `Inspect`: Regular balance inspector functions.
//! - `Unbalanced`: Low-level balance mutating functions. Does not guarantee proper book-keeping and
//! so should not be called into directly from application code. Other traits depend on this and
//! provide default implementations based on it.
//! - `UnbalancedHold`: Low-level balance mutating functions for balances placed on hold. Does not
//! guarantee proper book-keeping and so should not be called into directly from application code.
//! Other traits depend on this and provide default implementations based on it.
//! - `Mutate`: Regular balance mutator functions. Pre-implemented using `Unbalanced`, though the
//! `done_*` functions should likely be reimplemented in case you want to do something following
//! the operation such as emit events.
//! - `InspectHold`: Inspector functions for balances on hold.
//! - `MutateHold`: Mutator functions for balances on hold. Mostly pre-implemented using
//! `UnbalancedHold`.
//! - `InspectFreeze`: Inspector functions for frozen balance.
//! - `MutateFreeze`: Mutator functions for frozen balance.
//! - `Balanced`: One-sided mutator functions for regular balances, which return imbalance objects
//! which guarantee eventual book-keeping. May be useful for some sophisticated operations where
//! funds must be removed from an account before it is known precisely what should be done with
//! them.
pub mod freeze;
pub mod hold;
mod imbalance;
mod item_of;
mod regular;
pub use freeze::{Inspect as InspectFreeze, Mutate as MutateFreeze};
pub use hold::{
Balanced as BalancedHold, Inspect as InspectHold, Mutate as MutateHold,
Unbalanced as UnbalancedHold,
};
pub use imbalance::{Credit, Debt, HandleImbalanceDrop, Imbalance};
pub use item_of::ItemOf;
pub use regular::{
Balanced, DecreaseIssuance, Dust, IncreaseIssuance, Inspect, Mutate, Unbalanced,
};
@@ -0,0 +1,506 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 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.
//! `Inspect` and `Mutate` traits for working with regular balances.
use crate::{
dispatch::DispatchError,
ensure,
traits::{
tokens::{
misc::{
Balance, DepositConsequence,
Fortitude::{self, Force, Polite},
Precision::{self, BestEffort, Exact},
Preservation::{self, Expendable},
Provenance::{self, Extant},
WithdrawConsequence,
},
Imbalance as ImbalanceT,
},
SameOrOther, TryDrop,
},
};
use sp_arithmetic::traits::{CheckedAdd, CheckedSub, One};
use sp_runtime::{traits::Saturating, ArithmeticError, TokenError};
use sp_std::marker::PhantomData;
use super::{Credit, Debt, HandleImbalanceDrop, Imbalance};
/// Trait for providing balance-inspection access to a fungible asset.
pub trait Inspect<AccountId>: Sized {
/// Scalar type for representing balance of an account.
type Balance: Balance;
/// The total amount of issuance in the system.
fn total_issuance() -> Self::Balance;
/// The total amount of issuance in the system excluding those which are controlled by the
/// system.
fn active_issuance() -> Self::Balance {
Self::total_issuance()
}
/// The minimum balance any single account may have.
fn minimum_balance() -> Self::Balance;
/// Get the total amount of funds whose ultimate bneficial ownership can be determined as `who`.
///
/// This may include funds which are wholly inaccessible to `who`, either temporarily or even
/// indefinitely.
///
/// For the amount of the balance which is currently free to be removed from the account without
/// error, use `reducible_balance`.
///
/// For the amount of the balance which may eventually be free to be removed from the account,
/// use `balance()`.
fn total_balance(who: &AccountId) -> Self::Balance;
/// Get the balance of `who` which does not include funds which are exclusively allocated to
/// subsystems of the chain ("on hold" or "reserved").
///
/// In general this isn't especially useful outside of tests, and for practical purposes, you'll
/// want to use `reducible_balance()`.
fn balance(who: &AccountId) -> Self::Balance;
/// Get the maximum amount that `who` can withdraw/transfer successfully based on whether the
/// account should be kept alive (`preservation`) or whether we are willing to force the
/// reduction and potentially go below user-level restrictions on the minimum amount of the
/// account.
///
/// Always less than or equal to `balance()`.
fn reducible_balance(
who: &AccountId,
preservation: Preservation,
force: Fortitude,
) -> Self::Balance;
/// Returns `true` if the balance of `who` may be increased by `amount`.
///
/// - `who`: The account of which the balance should be increased by `amount`.
/// - `amount`: How much should the balance be increased?
/// - `provenance`: Will `amount` be minted to deposit it into `account` or is it already in the
/// system?
fn can_deposit(
who: &AccountId,
amount: Self::Balance,
provenance: Provenance,
) -> DepositConsequence;
/// Returns `Success` if the balance of `who` may be decreased by `amount`, otherwise
/// the consequence.
fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence<Self::Balance>;
}
/// Special dust type which can be type-safely converted into a `Credit`.
#[must_use]
pub struct Dust<A, T: Inspect<A>>(pub(crate) T::Balance);
impl<A, T: Balanced<A>> Dust<A, T> {
/// Convert `Dust` into an instance of `Credit`.
pub fn into_credit(self) -> Credit<A, T> {
Credit::<A, T>::new(self.0)
}
}
/// A fungible token class where the balance can be set arbitrarily.
///
/// **WARNING**
/// Do not use this directly unless you want trouble, since it allows you to alter account balances
/// without keeping the issuance up to date. It has no safeguards against accidentally creating
/// token imbalances in your system leading to accidental imflation or deflation. It's really just
/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to
/// use.
pub trait Unbalanced<AccountId>: Inspect<AccountId> {
/// Create some dust and handle it with `Self::handle_dust`. This is an unbalanced operation
/// and it must only be used when an account is modified in a raw fashion, outside of the entire
/// fungibles API. The `amount` is capped at `Self::minimum_balance() - 1`.
///
/// This should not be reimplemented.
fn handle_raw_dust(amount: Self::Balance) {
Self::handle_dust(Dust(amount.min(Self::minimum_balance().saturating_sub(One::one()))))
}
/// Do something with the dust which has been destroyed from the system. `Dust` can be converted
/// into a `Credit` with the `Balanced` trait impl.
fn handle_dust(dust: Dust<AccountId, Self>);
/// Forcefully set the balance of `who` to `amount`.
///
/// If this call executes successfully, you can `assert_eq!(Self::balance(), amount);`.
///
/// For implementations which include one or more balances on hold, then these are *not*
/// included in the `amount`.
///
/// This function does its best to force the balance change through, but will not break system
/// invariants such as any Existential Deposits needed or overflows/underflows.
/// If this cannot be done for some reason (e.g. because the account cannot be created, deleted
/// or would overflow) then an `Err` is returned.
///
/// If `Ok` is returned then its inner, if `Some` is the amount which was discarded as dust due
/// to existential deposit requirements. The default implementation of `decrease_balance` and
/// `increase_balance` converts this into an `Imbalance` and then passes it into `handle_dust`.
fn write_balance(
who: &AccountId,
amount: Self::Balance,
) -> Result<Option<Self::Balance>, DispatchError>;
/// Set the total issuance to `amount`.
fn set_total_issuance(amount: Self::Balance);
/// Reduce the balance of `who` by `amount`.
///
/// If `precision` is `Exact` and it cannot be reduced by that amount for
/// some reason, return `Err` and don't reduce it at all. If `precision` is `BestEffort`, then
/// reduce the balance of `who` by the most that is possible, up to `amount`.
///
/// In either case, if `Ok` is returned then the inner is the amount by which is was reduced.
/// Minimum balance will be respected and thus the returned amount may be up to
/// `Self::minimum_balance() - 1` greater than `amount` in the case that the reduction caused
/// the account to be deleted.
fn decrease_balance(
who: &AccountId,
mut amount: Self::Balance,
precision: Precision,
preservation: Preservation,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance(who);
let free = Self::reducible_balance(who, preservation, force);
if let BestEffort = precision {
amount = amount.min(free);
}
let new_balance = old_balance.checked_sub(&amount).ok_or(TokenError::FundsUnavailable)?;
if let Some(dust) = Self::write_balance(who, new_balance)? {
Self::handle_dust(Dust(dust));
}
Ok(old_balance.saturating_sub(new_balance))
}
/// Increase the balance of `who` by `amount`.
///
/// If it cannot be increased by that amount for some reason, return `Err` and don't increase
/// it at all. If Ok, return the imbalance.
/// Minimum balance will be respected and an error will be returned if
/// `amount < Self::minimum_balance()` when the account of `who` is zero.
fn increase_balance(
who: &AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance(who);
let new_balance = if let BestEffort = precision {
old_balance.saturating_add(amount)
} else {
old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?
};
if new_balance < Self::minimum_balance() {
// Attempt to increase from 0 to below minimum -> stays at zero.
if let BestEffort = precision {
Ok(Default::default())
} else {
Err(TokenError::BelowMinimum.into())
}
} else {
if new_balance == old_balance {
Ok(Default::default())
} else {
if let Some(dust) = Self::write_balance(who, new_balance)? {
Self::handle_dust(Dust(dust));
}
Ok(new_balance.saturating_sub(old_balance))
}
}
}
/// Reduce the active issuance by some amount.
fn deactivate(_: Self::Balance) {}
/// Increase the active issuance by some amount, up to the outstanding amount reduced.
fn reactivate(_: Self::Balance) {}
}
/// Trait for providing a basic fungible asset.
pub trait Mutate<AccountId>: Inspect<AccountId> + Unbalanced<AccountId> {
/// Increase the balance of `who` by exactly `amount`, minting new tokens. If that isn't
/// possible then an `Err` is returned and nothing is changed.
fn mint_into(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
Self::total_issuance().checked_add(&amount).ok_or(ArithmeticError::Overflow)?;
let actual = Self::increase_balance(who, amount, Exact)?;
Self::set_total_issuance(Self::total_issuance().saturating_add(actual));
Self::done_mint_into(who, amount);
Ok(actual)
}
/// Decrease the balance of `who` by at least `amount`, possibly slightly more in the case of
/// minimum-balance requirements, burning the tokens. If that isn't possible then an `Err` is
/// returned and nothing is changed. If successful, the amount of tokens reduced is returned.
fn burn_from(
who: &AccountId,
amount: Self::Balance,
precision: Precision,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
let actual = Self::reducible_balance(who, Expendable, force).min(amount);
ensure!(actual == amount || precision == BestEffort, TokenError::FundsUnavailable);
Self::total_issuance().checked_sub(&actual).ok_or(ArithmeticError::Overflow)?;
let actual = Self::decrease_balance(who, actual, BestEffort, Expendable, force)?;
Self::set_total_issuance(Self::total_issuance().saturating_sub(actual));
Self::done_burn_from(who, actual);
Ok(actual)
}
/// Attempt to decrease the `asset` balance of `who` by `amount`.
///
/// Equivalent to `burn_from`, except with an expectation that within the bounds of some
/// universal issuance, the total assets `suspend`ed and `resume`d will be equivalent. The
/// implementation may be configured such that the total assets suspended may never be less than
/// the total assets resumed (which is the invariant for an issuing system), or the reverse
/// (which the invariant in a non-issuing system).
///
/// Because of this expectation, any metadata associated with the asset is expected to survive
/// the suspect-resume cycle.
fn shelve(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
let actual = Self::reducible_balance(who, Expendable, Polite).min(amount);
ensure!(actual == amount, TokenError::FundsUnavailable);
Self::total_issuance().checked_sub(&actual).ok_or(ArithmeticError::Overflow)?;
let actual = Self::decrease_balance(who, actual, BestEffort, Expendable, Polite)?;
Self::set_total_issuance(Self::total_issuance().saturating_sub(actual));
Self::done_shelve(who, actual);
Ok(actual)
}
/// Attempt to increase the `asset` balance of `who` by `amount`.
///
/// Equivalent to `mint_into`, except with an expectation that within the bounds of some
/// universal issuance, the total assets `suspend`ed and `resume`d will be equivalent. The
/// implementation may be configured such that the total assets suspended may never be less than
/// the total assets resumed (which is the invariant for an issuing system), or the reverse
/// (which the invariant in a non-issuing system).
///
/// Because of this expectation, any metadata associated with the asset is expected to survive
/// the suspect-resume cycle.
fn restore(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
Self::total_issuance().checked_add(&amount).ok_or(ArithmeticError::Overflow)?;
let actual = Self::increase_balance(who, amount, Exact)?;
Self::set_total_issuance(Self::total_issuance().saturating_add(actual));
Self::done_restore(who, amount);
Ok(actual)
}
/// Transfer funds from one account into another.
fn transfer(
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
preservation: Preservation,
) -> Result<Self::Balance, DispatchError> {
let _extra = Self::can_withdraw(source, amount).into_result(preservation != Expendable)?;
Self::can_deposit(dest, amount, Extant).into_result()?;
Self::decrease_balance(source, amount, BestEffort, preservation, Polite)?;
// This should never fail as we checked `can_deposit` earlier. But we do a best-effort
// anyway.
let _ = Self::increase_balance(dest, amount, BestEffort);
Self::done_transfer(source, dest, amount);
Ok(amount)
}
/// Simple infallible function to force an account to have a particular balance, good for use
/// in tests and benchmarks but not recommended for production code owing to the lack of
/// error reporting.
///
/// Returns the new balance.
fn set_balance(who: &AccountId, amount: Self::Balance) -> Self::Balance {
let b = Self::balance(who);
if b > amount {
Self::burn_from(who, b - amount, BestEffort, Force).map(|d| amount.saturating_sub(d))
} else {
Self::mint_into(who, amount - b).map(|d| amount.saturating_add(d))
}
.unwrap_or(b)
}
fn done_mint_into(_who: &AccountId, _amount: Self::Balance) {}
fn done_burn_from(_who: &AccountId, _amount: Self::Balance) {}
fn done_shelve(_who: &AccountId, _amount: Self::Balance) {}
fn done_restore(_who: &AccountId, _amount: Self::Balance) {}
fn done_transfer(_source: &AccountId, _dest: &AccountId, _amount: Self::Balance) {}
}
/// Simple handler for an imbalance drop which increases the total issuance of the system by the
/// imbalance amount. Used for leftover debt.
pub struct IncreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::Balance>
for IncreaseIssuance<AccountId, U>
{
fn handle(amount: U::Balance) {
U::set_total_issuance(U::total_issuance().saturating_add(amount))
}
}
/// Simple handler for an imbalance drop which decreases the total issuance of the system by the
/// imbalance amount. Used for leftover credit.
pub struct DecreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::Balance>
for DecreaseIssuance<AccountId, U>
{
fn handle(amount: U::Balance) {
U::set_total_issuance(U::total_issuance().saturating_sub(amount))
}
}
/// A fungible token class where any creation and deletion of tokens is semi-explicit and where the
/// total supply is maintained automatically.
///
/// This is auto-implemented when a token class has `Unbalanced` implemented.
pub trait Balanced<AccountId>: Inspect<AccountId> + Unbalanced<AccountId> {
/// The type for managing what happens when an instance of `Debt` is dropped without being used.
type OnDropDebt: HandleImbalanceDrop<Self::Balance>;
/// The type for managing what happens when an instance of `Credit` is dropped without being
/// used.
type OnDropCredit: HandleImbalanceDrop<Self::Balance>;
/// Reduce the total issuance by `amount` and return the according imbalance. The imbalance will
/// typically be used to reduce an account by the same amount with e.g. `settle`.
///
/// This is infallible, but doesn't guarantee that the entire `amount` is burnt, for example
/// in the case of underflow.
fn rescind(amount: Self::Balance) -> Debt<AccountId, Self> {
let old = Self::total_issuance();
let new = old.saturating_sub(amount);
Self::set_total_issuance(new);
let delta = old - new;
Self::done_rescind(delta);
Imbalance::<Self::Balance, Self::OnDropDebt, Self::OnDropCredit>::new(delta)
}
/// Increase the total issuance by `amount` and return the according imbalance. The imbalance
/// will typically be used to increase an account by the same amount with e.g.
/// `resolve_into_existing` or `resolve_creating`.
///
/// This is infallible, but doesn't guarantee that the entire `amount` is issued, for example
/// in the case of overflow.
fn issue(amount: Self::Balance) -> Credit<AccountId, Self> {
let old = Self::total_issuance();
let new = old.saturating_add(amount);
Self::set_total_issuance(new);
let delta = new - old;
Self::done_issue(delta);
Imbalance::<Self::Balance, Self::OnDropCredit, Self::OnDropDebt>::new(delta)
}
/// Produce a pair of imbalances that cancel each other out exactly.
///
/// This is just the same as burning and issuing the same amount and has no effect on the
/// total issuance.
fn pair(amount: Self::Balance) -> (Debt<AccountId, Self>, Credit<AccountId, Self>) {
(Self::rescind(amount), Self::issue(amount))
}
/// Mints `value` into the account of `who`, creating it as needed.
///
/// If `precision` is `BestEffort` and `value` in full could not be minted (e.g. due to
/// overflow), then the maximum is minted, up to `value`. If `precision` is `Exact`, then
/// exactly `value` must be minted into the account of `who` or the operation will fail with an
/// `Err` and nothing will change.
///
/// If the operation is successful, this will return `Ok` with a `Debt` of the total value
/// added to the account.
fn deposit(
who: &AccountId,
value: Self::Balance,
precision: Precision,
) -> Result<Debt<AccountId, Self>, DispatchError> {
let increase = Self::increase_balance(who, value, precision)?;
Self::done_deposit(who, increase);
Ok(Imbalance::<Self::Balance, Self::OnDropDebt, Self::OnDropCredit>::new(increase))
}
/// Removes `value` balance from `who` account if possible.
///
/// If `precision` is `BestEffort` and `value` in full could not be removed (e.g. due to
/// underflow), then the maximum is removed, up to `value`. If `precision` is `Exact`, then
/// exactly `value` must be removed from the account of `who` or the operation will fail with an
/// `Err` and nothing will change.
///
/// If the removal is needed but not possible, then it returns `Err` and nothing is changed.
/// If the account needed to be deleted, then slightly more than `value` may be removed from the
/// account owning since up to (but not including) minimum balance may also need to be removed.
///
/// If the operation is successful, this will return `Ok` with a `Credit` of the total value
/// removed from the account.
fn withdraw(
who: &AccountId,
value: Self::Balance,
precision: Precision,
preservation: Preservation,
force: Fortitude,
) -> Result<Credit<AccountId, Self>, DispatchError> {
let decrease = Self::decrease_balance(who, value, precision, preservation, force)?;
Self::done_withdraw(who, decrease);
Ok(Imbalance::<Self::Balance, Self::OnDropCredit, Self::OnDropDebt>::new(decrease))
}
/// The balance of `who` is increased in order to counter `credit`. If the whole of `credit`
/// cannot be countered, then nothing is changed and the original `credit` is returned in an
/// `Err`.
///
/// Please note: If `credit.peek()` is less than `Self::minimum_balance()`, then `who` must
/// already exist for this to succeed.
fn resolve(
who: &AccountId,
credit: Credit<AccountId, Self>,
) -> Result<(), Credit<AccountId, Self>> {
let v = credit.peek();
let debt = match Self::deposit(who, v, Exact) {
Err(_) => return Err(credit),
Ok(d) => d,
};
let result = credit.offset(debt).try_drop();
debug_assert!(result.is_ok(), "ok deposit return must be equal to credit value; qed");
Ok(())
}
/// The balance of `who` is decreased in order to counter `debt`. If the whole of `debt`
/// cannot be countered, then nothing is changed and the original `debt` is returned in an
/// `Err`.
fn settle(
who: &AccountId,
debt: Debt<AccountId, Self>,
preservation: Preservation,
) -> Result<Credit<AccountId, Self>, Debt<AccountId, Self>> {
let amount = debt.peek();
let credit = match Self::withdraw(who, amount, Exact, preservation, Polite) {
Err(_) => return Err(debt),
Ok(d) => d,
};
match credit.offset(debt) {
SameOrOther::None => Ok(Credit::<AccountId, Self>::zero()),
SameOrOther::Same(dust) => Ok(dust),
SameOrOther::Other(rest) => {
debug_assert!(false, "ok withdraw return must be at least debt value; qed");
Err(rest)
},
}
}
fn done_rescind(_amount: Self::Balance) {}
fn done_issue(_amount: Self::Balance) {}
fn done_deposit(_who: &AccountId, _amount: Self::Balance) {}
fn done_withdraw(_who: &AccountId, _amount: Self::Balance) {}
}
@@ -1,332 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 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.
//! The traits for sets of fungible tokens and any associated types.
use super::{
misc::{AssetId, Balance},
*,
};
use crate::dispatch::{DispatchError, DispatchResult};
use sp_runtime::traits::Saturating;
use sp_std::vec::Vec;
pub mod approvals;
mod balanced;
pub mod enumerable;
pub use enumerable::InspectEnumerable;
pub mod metadata;
pub use balanced::{Balanced, Unbalanced};
mod imbalance;
pub use imbalance::{CreditOf, DebtOf, HandleImbalanceDrop, Imbalance};
pub mod roles;
/// Trait for providing balance-inspection access to a set of named fungible assets.
pub trait Inspect<AccountId> {
/// Means of identifying one asset class from another.
type AssetId: AssetId;
/// Scalar type for representing balance of an account.
type Balance: Balance;
/// The total amount of issuance in the system.
fn total_issuance(asset: Self::AssetId) -> Self::Balance;
/// The total amount of issuance in the system excluding those which are controlled by the
/// system.
fn active_issuance(asset: Self::AssetId) -> Self::Balance {
Self::total_issuance(asset)
}
/// The minimum balance any single account may have.
fn minimum_balance(asset: Self::AssetId) -> Self::Balance;
/// Get the `asset` balance of `who`.
fn balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance;
/// Get the maximum amount of `asset` that `who` can withdraw/transfer successfully.
fn reducible_balance(asset: Self::AssetId, who: &AccountId, keep_alive: bool) -> Self::Balance;
/// Returns `true` if the `asset` balance of `who` may be increased by `amount`.
///
/// - `asset`: The asset that should be deposited.
/// - `who`: The account of which the balance should be increased by `amount`.
/// - `amount`: How much should the balance be increased?
/// - `mint`: Will `amount` be minted to deposit it into `account`?
fn can_deposit(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
mint: bool,
) -> DepositConsequence;
/// Returns `Failed` if the `asset` balance of `who` may not be decreased by `amount`, otherwise
/// the consequence.
fn can_withdraw(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> WithdrawConsequence<Self::Balance>;
/// Returns `true` if an `asset` exists.
fn asset_exists(asset: Self::AssetId) -> bool;
}
/// Trait for reading metadata from a fungible asset.
pub trait InspectMetadata<AccountId>: Inspect<AccountId> {
/// Return the name of an asset.
fn name(asset: &Self::AssetId) -> Vec<u8>;
/// Return the symbol of an asset.
fn symbol(asset: &Self::AssetId) -> Vec<u8>;
/// Return the decimals of an asset.
fn decimals(asset: &Self::AssetId) -> u8;
}
/// Trait for providing a set of named fungible assets which can be created and destroyed.
pub trait Mutate<AccountId>: Inspect<AccountId> {
/// Attempt to increase the `asset` balance of `who` by `amount`.
///
/// If not possible then don't do anything. Possible reasons for failure include:
/// - Minimum balance not met.
/// - Account cannot be created (e.g. because there is no provider reference and/or the asset
/// isn't considered worth anything).
///
/// Since this is an operation which should be possible to take alone, if successful it will
/// increase the overall supply of the underlying token.
fn mint_into(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> DispatchResult;
/// Attempt to reduce the `asset` balance of `who` by `amount`.
///
/// If not possible then don't do anything. Possible reasons for failure include:
/// - Less funds in the account than `amount`
/// - Liquidity requirements (locks, reservations) prevent the funds from being removed
/// - Operation would require destroying the account and it is required to stay alive (e.g.
/// because it's providing a needed provider reference).
///
/// Since this is an operation which should be possible to take alone, if successful it will
/// reduce the overall supply of the underlying token.
///
/// Due to minimum balance requirements, it's possible that the amount withdrawn could be up to
/// `Self::minimum_balance() - 1` more than the `amount`. The total amount withdrawn is returned
/// in an `Ok` result. This may be safely ignored if you don't mind the overall supply reducing.
fn burn_from(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError>;
/// Attempt to reduce the `asset` balance of `who` by as much as possible up to `amount`, and
/// possibly slightly more due to minimum_balance requirements. If no decrease is possible then
/// an `Err` is returned and nothing is changed. If successful, the amount of tokens reduced is
/// returned.
///
/// The default implementation just uses `withdraw` along with `reducible_balance` to ensure
/// that is doesn't fail.
fn slash(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
Self::burn_from(asset, who, Self::reducible_balance(asset, who, false).min(amount))
}
/// Transfer funds from one account into another. The default implementation uses `mint_into`
/// and `burn_from` and may generate unwanted events.
fn teleport(
asset: Self::AssetId,
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
let extra = Self::can_withdraw(asset, &source, amount).into_result()?;
// As we first burn and then mint, we don't need to check if `mint` fits into the supply.
// If we can withdraw/burn it, we can also mint it again.
Self::can_deposit(asset, dest, amount.saturating_add(extra), false).into_result()?;
let actual = Self::burn_from(asset, source, amount)?;
debug_assert!(
actual == amount.saturating_add(extra),
"can_withdraw must agree with withdraw; qed"
);
match Self::mint_into(asset, dest, actual) {
Ok(_) => Ok(actual),
Err(err) => {
debug_assert!(false, "can_deposit returned true previously; qed");
// attempt to return the funds back to source
let revert = Self::mint_into(asset, source, actual);
debug_assert!(revert.is_ok(), "withdrew funds previously; qed");
Err(err)
},
}
}
}
/// Trait for providing a set of named fungible assets which can only be transferred.
pub trait Transfer<AccountId>: Inspect<AccountId> {
/// Transfer funds from one account into another.
fn transfer(
asset: Self::AssetId,
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
keep_alive: bool,
) -> Result<Self::Balance, DispatchError>;
/// Reduce the active issuance by some amount.
fn deactivate(_: Self::AssetId, _: Self::Balance) {}
/// Increase the active issuance by some amount, up to the outstanding amount reduced.
fn reactivate(_: Self::AssetId, _: Self::Balance) {}
}
/// Trait for inspecting a set of named fungible assets which can be placed on hold.
pub trait InspectHold<AccountId>: Inspect<AccountId> {
/// Amount of funds held in hold.
fn balance_on_hold(asset: Self::AssetId, who: &AccountId) -> Self::Balance;
/// Check to see if some `amount` of `asset` may be held on the account of `who`.
fn can_hold(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> bool;
}
/// Trait for mutating a set of named fungible assets which can be placed on hold.
pub trait MutateHold<AccountId>: InspectHold<AccountId> + Transfer<AccountId> {
/// Hold some funds in an account.
fn hold(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> DispatchResult;
/// Release some funds in an account from being on hold.
///
/// If `best_effort` is `true`, then the amount actually released and returned as the inner
/// value of `Ok` may be smaller than the `amount` passed.
fn release(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
best_effort: bool,
) -> Result<Self::Balance, DispatchError>;
/// Transfer held funds into a destination account.
///
/// If `on_hold` is `true`, then the destination account must already exist and the assets
/// transferred will still be on hold in the destination account. If not, then the destination
/// account need not already exist, but must be creatable.
///
/// If `best_effort` is `true`, then an amount less than `amount` may be transferred without
/// error.
///
/// The actual amount transferred is returned, or `Err` in the case of error and nothing is
/// changed.
fn transfer_held(
asset: Self::AssetId,
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
best_effort: bool,
on_hold: bool,
) -> Result<Self::Balance, DispatchError>;
}
/// Trait for mutating one of several types of fungible assets which can be held.
pub trait BalancedHold<AccountId>: Balanced<AccountId> + MutateHold<AccountId> {
/// Release and slash some funds in an account.
///
/// The resulting imbalance is the first item of the tuple returned.
///
/// As much funds up to `amount` will be deducted as possible. If this is less than `amount`,
/// then a non-zero second item will be returned.
fn slash_held(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> (CreditOf<AccountId, Self>, Self::Balance);
}
impl<AccountId, T: Balanced<AccountId> + MutateHold<AccountId>> BalancedHold<AccountId> for T {
fn slash_held(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> (CreditOf<AccountId, Self>, Self::Balance) {
let actual = match Self::release(asset, who, amount, true) {
Ok(x) => x,
Err(_) => return (Imbalance::zero(asset), amount),
};
<Self as fungibles::Balanced<AccountId>>::slash(asset, who, actual)
}
}
/// Trait for providing the ability to create new fungible assets.
pub trait Create<AccountId>: Inspect<AccountId> {
/// Create a new fungible asset.
fn create(
id: Self::AssetId,
admin: AccountId,
is_sufficient: bool,
min_balance: Self::Balance,
) -> DispatchResult;
}
/// Trait for providing the ability to destroy existing fungible assets.
pub trait Destroy<AccountId>: Inspect<AccountId> {
/// Start the destruction an existing fungible asset.
/// * `id`: The `AssetId` to be destroyed. successfully.
/// * `maybe_check_owner`: An optional account id that can be used to authorize the destroy
/// command. If not provided, no authorization checks will be performed before destroying
/// asset.
fn start_destroy(id: Self::AssetId, maybe_check_owner: Option<AccountId>) -> DispatchResult;
/// Destroy all accounts associated with a given asset.
/// `destroy_accounts` should only be called after `start_destroy` has been called, and the
/// asset is in a `Destroying` state
///
/// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset.
/// * `max_items`: The maximum number of accounts to be destroyed for a given call of the
/// function. This value should be small enough to allow the operation fit into a logical
/// block.
///
/// Response:
/// * u32: Total number of approvals which were actually destroyed
///
/// Due to weight restrictions, this function may need to be called multiple
/// times to fully destroy all approvals. It will destroy `max_items` approvals at a
/// time.
fn destroy_accounts(id: Self::AssetId, max_items: u32) -> Result<u32, DispatchError>;
/// Destroy all approvals associated with a given asset up to the `max_items`
/// `destroy_approvals` should only be called after `start_destroy` has been called, and the
/// asset is in a `Destroying` state
///
/// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset.
/// * `max_items`: The maximum number of accounts to be destroyed for a given call of the
/// function. This value should be small enough to allow the operation fit into a logical
/// block.
///
/// Response:
/// * u32: Total number of approvals which were actually destroyed
///
/// Due to weight restrictions, this function may need to be called multiple
/// times to fully destroy all approvals. It will destroy `max_items` approvals at a
/// time.
fn destroy_approvals(id: Self::AssetId, max_items: u32) -> Result<u32, DispatchError>;
/// Complete destroying asset and unreserve currency.
/// `finish_destroy` should only be called after `start_destroy` has been called, and the
/// asset is in a `Destroying` state. All accounts or approvals should be destroyed before
/// hand.
///
/// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset.
fn finish_destroy(id: Self::AssetId) -> DispatchResult;
}

Some files were not shown because too many files have changed in this diff Show More