Files
pezkuwi-subxt/substrate/frame/balances/src/impl_fungible.rs
T
Gavin Wood 5d81f23f8f 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 <>
2023-03-18 14:47:55 +00:00

359 lines
12 KiB
Rust

// 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> {}