feat: Rebrand Polkadot/Substrate references to PezkuwiChain

This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
+66
View File
@@ -0,0 +1,66 @@
[package]
name = "pezpallet-balances"
version = "28.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "FRAME pallet to manage balances"
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { features = ["derive", "max-encoded-len"], workspace = true }
docify = { workspace = true }
pezframe-benchmarking = { optional = true, workspace = true }
pezframe-support = { workspace = true }
pezframe-system = { workspace = true }
log = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
pezsp-core = { workspace = true }
pezsp-runtime = { workspace = true }
[dev-dependencies]
pezframe-support = { features = [
"experimental",
], workspace = true, default-features = true }
pezpallet-transaction-payment = { workspace = true, default-features = true }
paste = { workspace = true, default-features = true }
pezsp-io = { workspace = true, default-features = true }
[features]
default = ["std"]
std = [
"codec/std",
"pezframe-benchmarking?/std",
"pezframe-support/std",
"pezframe-system/std",
"log/std",
"pezpallet-transaction-payment/std",
"scale-info/std",
"pezsp-core/std",
"pezsp-io/std",
"pezsp-runtime/std",
]
# Enable support for setting the existential deposit to zero.
insecure_zero_ed = []
runtime-benchmarks = [
"pezframe-benchmarking/runtime-benchmarks",
"pezframe-support/runtime-benchmarks",
"pezframe-system/runtime-benchmarks",
"pezpallet-transaction-payment/runtime-benchmarks",
"pezsp-io/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
]
try-runtime = [
"pezframe-support/try-runtime",
"pezframe-system/try-runtime",
"pezpallet-transaction-payment/try-runtime",
"pezsp-runtime/try-runtime",
]
+122
View File
@@ -0,0 +1,122 @@
# Balances Module
The Balances module provides functionality for handling accounts and balances.
- [`Config`](https://docs.rs/pezpallet-balances/latest/pallet_balances/pallet/trait.Config.html)
- [`Call`](https://docs.rs/pezpallet-balances/latest/pallet_balances/pallet/enum.Call.html)
- [`Pallet`](https://docs.rs/pezpallet-balances/latest/pallet_balances/pallet/struct.Pallet.html)
## Overview
The Balances module provides functions for:
- Getting and setting free balances.
- Retrieving total, reserved and unreserved balances.
- Repatriating a reserved balance to a beneficiary account that exists.
- Transferring a balance between accounts (when not reserved).
- Slashing an account balance.
- Account creation and removal.
- Managing total issuance.
- Setting and managing locks.
### Terminology
- **Existential Deposit:** The minimum balance required to create or keep an account open. This prevents "dust accounts"
from filling storage. When the free plus the reserved balance (i.e. the total balance) fall below this, then the account
is said to be dead; and it loses its functionality as well as any prior history and all information on it is removed
from the chain's state. No account should ever have a total balance that is strictly between 0 and the existential
deposit (exclusive). If this ever happens, it indicates either a bug in this module or an erroneous raw mutation of
storage.
- **Total Issuance:** The total number of units in existence in a system.
- **Reaping an account:** The act of removing an account by resetting its nonce. Happens after its total balance has
become zero (or, strictly speaking, less than the Existential Deposit).
- **Free Balance:** The portion of a balance that is not reserved. The free balance is the only balance that matters for
most operations.
- **Reserved Balance:** Reserved balance still belongs to the account holder, but is suspended. Reserved balance can
still be slashed, but only after all the free balance has been slashed.
- **Imbalance:** A condition when some funds were credited or debited without equal and opposite accounting (i.e. a
difference between total issuance and account balances). Functions that result in an imbalance will return an object of
the `Imbalance` trait that can be managed within your runtime logic. (If an imbalance is simply dropped, it should
automatically maintain any book-keeping such as total issuance.)
- **Lock:** A freeze on a specified amount of an account's free balance until a specified block number. Multiple locks
always operate over the same funds, so they "overlay" rather than "stack".
### Implementations
The Balances module provides implementations for the following traits. If these traits provide the functionality that
you need, then you can avoid coupling with the Balances module.
- [`Currency`](https://docs.rs/pezframe-support/latest/frame_support/traits/trait.Currency.html): Functions for dealing
with a fungible assets system.
- [`ReservableCurrency`](https://docs.rs/pezframe-support/latest/frame_support/traits/trait.ReservableCurrency.html):
Functions for dealing with assets that can be reserved from an account.
- [`LockableCurrency`](https://docs.rs/pezframe-support/latest/frame_support/traits/trait.LockableCurrency.html): Functions
for dealing with accounts that allow liquidity restrictions.
- [`Imbalance`](https://docs.rs/pezframe-support/latest/frame_support/traits/trait.Imbalance.html): Functions for handling
imbalances between total issuance in the system and account balances. Must be used when a function creates new funds
(e.g. a reward) or destroys some funds (e.g. a system fee).
- [`IsDeadAccount`](https://docs.rs/pezframe-support/latest/frame_support/traits/trait.IsDeadAccount.html): Determiner to
say whether a given account is unused.
## Interface
### Dispatchable Functions
- `transfer` - Transfer some liquid free balance to another account.
- `force_set_balance` - Set the balances of a given account. The origin of this call must be root.
## Usage
The following examples show how to use the Balances module in your custom module.
### Examples from the FRAME
The Contract module uses the `Currency` trait to handle gas payment, and its types inherit from `Currency`:
```rust
use frame_support::traits::Currency;
pub type BalanceOf<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
pub type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;
```
The Staking module uses the `LockableCurrency` trait to lock a stash account's funds:
```rust
use frame_support::traits::{WithdrawReasons, LockableCurrency};
use sp_runtime::traits::Bounded;
pub trait Config: frame_system::Config {
type Currency: LockableCurrency<Self::AccountId, Moment=frame_system::pallet_prelude::BlockNumberFor<Self>>;
}
fn update_ledger<T: Config>(
controller: &T::AccountId,
ledger: &StakingLedger<T>
) {
T::Currency::set_lock(
STAKING_ID,
&ledger.stash,
ledger.total,
WithdrawReasons::all()
);
// <Ledger<T>>::insert(controller, ledger); // Commented out as we don't have access to Staking's storage here.
}
```
## Genesis config
The Balances module depends on the
[`GenesisConfig`](https://docs.rs/pezpallet-balances/latest/pallet_balances/pallet/struct.GenesisConfig.html).
## Assumptions
- Total issued balanced of all accounts should be less than `Config::Balance::max_value()`.
License: Apache-2.0
@@ -0,0 +1,368 @@
// This file is part of Bizinikiwi.
// 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.
//! Balances pallet benchmarking.
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use crate::Pallet as Balances;
use pezframe_benchmarking::v2::*;
use pezframe_system::RawOrigin;
use pezsp_runtime::traits::Bounded;
use types::ExtraFlags;
const SEED: u32 = 0;
// existential deposit multiplier
const ED_MULTIPLIER: u32 = 10;
fn minimum_balance<T: Config<I>, I: 'static>() -> T::Balance {
if cfg!(feature = "insecure_zero_ed") {
100u32.into()
} else {
T::ExistentialDeposit::get()
}
}
#[instance_benchmarks]
mod benchmarks {
use super::*;
// Benchmark `transfer` extrinsic with the worst possible conditions:
// * Transfer will kill the sender account.
// * Transfer will create the recipient account.
#[benchmark]
fn transfer_allow_death() {
let existential_deposit: T::Balance = minimum_balance::<T, I>();
let caller = whitelisted_caller();
// Give some multiple of the existential deposit
let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()).max(1u32.into());
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, balance);
// Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account,
// and reap this user.
let recipient: T::AccountId = account("recipient", 0, SEED);
let recipient_lookup = T::Lookup::unlookup(recipient.clone());
let transfer_amount =
existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into();
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
if cfg!(feature = "insecure_zero_ed") {
assert_eq!(Balances::<T, I>::free_balance(&caller), balance - transfer_amount);
} else {
assert_eq!(Balances::<T, I>::free_balance(&caller), Zero::zero());
}
assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount);
}
// Benchmark `transfer` with the best possible condition:
// * Both accounts exist and will continue to exist.
#[benchmark(extra)]
fn transfer_best_case() {
let caller = whitelisted_caller();
let recipient: T::AccountId = account("recipient", 0, SEED);
let recipient_lookup = T::Lookup::unlookup(recipient.clone());
// Give the sender account max funds for transfer (their account will never reasonably be
// killed).
let _ =
<Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value());
// Give the recipient account existential deposit (thus their account already exists).
let existential_deposit: T::Balance = minimum_balance::<T, I>();
let _ =
<Balances<T, I> as Currency<_>>::make_free_balance_be(&recipient, existential_deposit);
let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
#[extrinsic_call]
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());
}
// Benchmark `transfer_keep_alive` with the worst possible condition:
// * The recipient account is created.
#[benchmark]
fn transfer_keep_alive() {
let caller = whitelisted_caller();
let recipient: T::AccountId = account("recipient", 0, SEED);
let recipient_lookup = T::Lookup::unlookup(recipient.clone());
// Give the sender account max funds, thus a transfer will not kill account.
let _ =
<Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value());
let existential_deposit: T::Balance = minimum_balance::<T, I>();
let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
assert!(!Balances::<T, I>::free_balance(&caller).is_zero());
assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount);
}
// Benchmark `force_set_balance` coming from ROOT account. This always creates an account.
#[benchmark]
fn force_set_balance_creating() {
let user: T::AccountId = account("user", 0, SEED);
let user_lookup = T::Lookup::unlookup(user.clone());
let existential_deposit: T::Balance = minimum_balance::<T, I>();
let balance_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
#[extrinsic_call]
force_set_balance(RawOrigin::Root, user_lookup, balance_amount);
assert_eq!(Balances::<T, I>::free_balance(&user), balance_amount);
}
// Benchmark `force_set_balance` coming from ROOT account. This always kills an account.
#[benchmark]
fn force_set_balance_killing() {
let user: T::AccountId = account("user", 0, SEED);
let user_lookup = T::Lookup::unlookup(user.clone());
// Give the user some initial balance.
let existential_deposit: T::Balance = minimum_balance::<T, I>();
let balance_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&user, balance_amount);
#[extrinsic_call]
force_set_balance(RawOrigin::Root, user_lookup, Zero::zero());
assert!(Balances::<T, I>::free_balance(&user).is_zero());
}
// Benchmark `force_transfer` extrinsic with the worst possible conditions:
// * Transfer will kill the sender account.
// * Transfer will create the recipient account.
#[benchmark]
fn force_transfer() {
let existential_deposit: T::Balance = minimum_balance::<T, I>();
let source: T::AccountId = account("source", 0, SEED);
let source_lookup = T::Lookup::unlookup(source.clone());
// Give some multiple of the existential deposit
let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&source, balance);
// Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account,
// and reap this user.
let recipient: T::AccountId = account("recipient", 0, SEED);
let recipient_lookup = T::Lookup::unlookup(recipient.clone());
let transfer_amount =
existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into();
#[extrinsic_call]
_(RawOrigin::Root, source_lookup, recipient_lookup, transfer_amount);
if cfg!(feature = "insecure_zero_ed") {
assert_eq!(Balances::<T, I>::free_balance(&source), balance - transfer_amount);
} else {
assert_eq!(Balances::<T, I>::free_balance(&source), Zero::zero());
}
assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount);
}
// This benchmark performs the same operation as `transfer` in the worst case scenario,
// but additionally introduces many new users into the storage, increasing the the merkle
// trie and PoV size.
#[benchmark(extra)]
fn transfer_increasing_users(u: Linear<0, 1_000>) {
// 1_000 is not very much, but this upper bound can be controlled by the CLI.
let existential_deposit: T::Balance = minimum_balance::<T, I>();
let caller = whitelisted_caller();
// Give some multiple of the existential deposit
let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, balance);
// Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account,
// and reap this user.
let recipient: T::AccountId = account("recipient", 0, SEED);
let recipient_lookup = T::Lookup::unlookup(recipient.clone());
let transfer_amount =
existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into();
// Create a bunch of users in storage.
for i in 0..u {
// The `account` function uses `blake2_256` to generate unique accounts, so these
// should be quite random and evenly distributed in the trie.
let new_user: T::AccountId = account("new_user", i, SEED);
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&new_user, balance);
}
#[extrinsic_call]
transfer_allow_death(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
if cfg!(feature = "insecure_zero_ed") {
assert_eq!(Balances::<T, I>::free_balance(&caller), balance - transfer_amount);
} else {
assert_eq!(Balances::<T, I>::free_balance(&caller), Zero::zero());
}
assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount);
}
// Benchmark `transfer_all` with the worst possible condition:
// * The recipient account is created
// * The sender is killed
#[benchmark]
fn transfer_all() {
let caller = whitelisted_caller();
let recipient: T::AccountId = account("recipient", 0, SEED);
let recipient_lookup = T::Lookup::unlookup(recipient.clone());
// Give some multiple of the existential deposit
let existential_deposit: T::Balance = minimum_balance::<T, I>();
let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, balance);
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), recipient_lookup, false);
assert!(Balances::<T, I>::free_balance(&caller).is_zero());
assert_eq!(Balances::<T, I>::free_balance(&recipient), balance);
}
#[benchmark]
fn force_unreserve() -> Result<(), BenchmarkError> {
let user: T::AccountId = account("user", 0, SEED);
let user_lookup = T::Lookup::unlookup(user.clone());
// Give some multiple of the existential deposit
let ed = minimum_balance::<T, I>();
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, 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), 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: minimum_balance::<T, I>(),
reserved: minimum_balance::<T, I>(),
frozen: Zero::zero(),
flags: ExtraFlags::old_logic(),
};
pezframe_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!(pezframe_system::Pallet::<T>::providers(&user), 1);
assert_eq!(pezframe_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!(pezframe_system::Pallet::<T>::providers(&user), 1);
assert_eq!(pezframe_system::Pallet::<T>::consumers(&user), 1);
}
}
#[benchmark]
fn force_adjust_total_issuance() {
let ti = TotalIssuance::<T, I>::get();
let delta = 123u32.into();
#[extrinsic_call]
_(RawOrigin::Root, AdjustmentDirection::Increase, delta);
assert_eq!(TotalIssuance::<T, I>::get(), ti + delta);
}
/// Benchmark `burn` extrinsic with the worst possible condition - burn kills the account.
#[benchmark]
fn burn_allow_death() {
let existential_deposit: T::Balance = minimum_balance::<T, I>();
let caller = whitelisted_caller();
// Give some multiple of the existential deposit
let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, balance);
// Burn enough to kill the account.
let burn_amount = balance - existential_deposit + 1u32.into();
#[extrinsic_call]
burn(RawOrigin::Signed(caller.clone()), burn_amount, false);
if cfg!(feature = "insecure_zero_ed") {
assert_eq!(Balances::<T, I>::free_balance(&caller), balance - burn_amount);
} else {
assert_eq!(Balances::<T, I>::free_balance(&caller), Zero::zero());
}
}
// Benchmark `burn` extrinsic with the case where account is kept alive.
#[benchmark]
fn burn_keep_alive() {
let existential_deposit: T::Balance = minimum_balance::<T, I>();
let caller = whitelisted_caller();
// Give some multiple of the existential deposit
let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, balance);
// Burn minimum possible amount which should not kill the account.
let burn_amount = 1u32.into();
#[extrinsic_call]
burn(RawOrigin::Signed(caller.clone()), burn_amount, true);
assert_eq!(Balances::<T, I>::free_balance(&caller), balance - burn_amount);
}
impl_benchmark_test_suite! {
Balances,
crate::tests::ExtBuilder::default().build(),
crate::tests::Test,
}
}
@@ -0,0 +1,998 @@
// This file is part of Bizinikiwi.
// 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.
//! Implementations for the `Currency` family of traits.
//!
//! Note that `WithdrawReasons` are intentionally not used for anything in this implementation and
//! are expected to be removed in the near future, once migration to `fungible::*` traits is done.
use super::*;
use pezframe_support::{
ensure,
pezpallet_prelude::DispatchResult,
traits::{
tokens::{fungible, BalanceStatus as Status, Fortitude::Polite, Precision::BestEffort},
Currency, DefensiveSaturating, ExistenceRequirement,
ExistenceRequirement::AllowDeath,
Get, Imbalance, InspectLockableCurrency, LockIdentifier, LockableCurrency,
NamedReservableCurrency, ReservableCurrency, SignedImbalance, TryDrop, WithdrawReasons,
},
};
use pezframe_system::pezpallet_prelude::BlockNumberFor;
pub use imbalances::{NegativeImbalance, PositiveImbalance};
use pezsp_runtime::traits::Bounded;
// wrapping these imbalances in a private module is necessary to ensure absolute privacy
// of the inner member.
mod imbalances {
use super::*;
use core::mem;
use pezframe_support::traits::{tokens::imbalance::TryMerge, SameOrOther};
/// 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 extract(&mut self, amount: T::Balance) -> Self {
let new = self.0.min(amount);
self.0 = self.0 - new;
Self(new)
}
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> TryMerge for PositiveImbalance<T, I> {
fn try_merge(self, other: Self) -> Result<Self, (Self, Self)> {
Ok(self.merge(other))
}
}
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 extract(&mut self, amount: T::Balance) -> Self {
let new = self.0.min(amount);
self.0 = self.0 - new;
Self(new)
}
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> TryMerge for NegativeImbalance<T, I> {
fn try_merge(self, other: Self) -> Result<Self, (Self, Self)> {
Ok(self.merge(other))
}
}
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) {
if !self.0.is_zero() {
<super::TotalIssuance<T, I>>::mutate(|v| *v = v.saturating_add(self.0));
Pallet::<T, I>::deposit_event(Event::<T, I>::Issued { amount: 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) {
if !self.0.is_zero() {
<super::TotalIssuance<T, I>>::mutate(|v| *v = v.saturating_sub(self.0));
Pallet::<T, I>::deposit_event(Event::<T, I>::Rescinded { amount: self.0 });
}
}
}
impl<T: Config<I>, I: 'static> fungible::HandleImbalanceDrop<T::Balance>
for NegativeImbalance<T, I>
{
fn handle(amount: T::Balance) {
<super::TotalIssuance<T, I>>::mutate(|v| *v = v.saturating_sub(amount));
Pallet::<T, I>::deposit_event(Event::<T, I>::BurnedDebt { amount });
}
}
impl<T: Config<I>, I: 'static> fungible::HandleImbalanceDrop<T::Balance>
for PositiveImbalance<T, I>
{
fn handle(amount: T::Balance) {
<super::TotalIssuance<T, I>>::mutate(|v| *v = v.saturating_add(amount));
Pallet::<T, I>::deposit_event(Event::<T, I>::MintedCredit { amount });
}
}
}
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()
});
});
Pallet::<T, I>::deposit_event(Event::<T, I>::Rescinded { amount });
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()
})
});
Pallet::<T, I>::deposit_event(Event::<T, I>::Issued { amount });
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,
false,
|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,
false,
|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,
false,
|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,
false,
|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,
false,
|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()))
}
}
/// Validates whether an account can create a reserve without violating
/// liquidity constraints.
///
/// This method performs liquidity checks without modifying the account state.
fn ensure_can_reserve<T: Config<I>, I: 'static>(
who: &T::AccountId,
value: T::Balance,
check_existential_deposit: bool,
) -> DispatchResult {
let AccountData { free, .. } = Pallet::<T, I>::account(who);
// Early validation: Check sufficient free balance
let new_free_balance = free.checked_sub(&value).ok_or(Error::<T, I>::InsufficientBalance)?;
// Conditionally validate existential deposit preservation
if check_existential_deposit {
let existential_deposit = T::ExistentialDeposit::get();
ensure!(new_free_balance >= existential_deposit, Error::<T, I>::Expendability);
}
Ok(())
}
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;
}
ensure_can_reserve::<T, I>(who, value, true).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, false, |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)?;
// Check if it is possible to reserve before trying to mutate the account
ensure_can_reserve::<T, I>(who, value, false)
})?;
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, false, |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, false, |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`.
///
/// This is `Polite` and thus will not repatriate any funds which would lead the total balance
/// to be less than the frozen amount. Returns `Ok` with the actual amount of funds moved,
/// which may be less than `value` since the operation is done on a `BestEffort` basis.
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, BestEffort, Polite, 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) => {
reserves[index].amount = reserves[index]
.amount
.checked_add(&value)
.ok_or(ArithmeticError::Overflow)?;
},
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 = BlockNumberFor<T>;
type MaxLocks = T::MaxLocks;
// Set or alter a lock on the balance of `who`.
fn set_lock(
id: LockIdentifier,
who: &T::AccountId,
amount: T::Balance,
reasons: WithdrawReasons,
) {
if reasons.is_empty() || amount.is_zero() {
Self::remove_lock(id, who);
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[..]);
}
}
impl<T: Config<I>, I: 'static> InspectLockableCurrency<T::AccountId> for Pallet<T, I> {
fn balance_locked(id: LockIdentifier, who: &T::AccountId) -> Self::Balance {
Self::locks(who)
.into_iter()
.filter(|l| l.id == id)
.fold(Zero::zero(), |acc, l| acc + l.amount)
}
}
@@ -0,0 +1,429 @@
// This file is part of Bizinikiwi.
// 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.
//! Implementation of `fungible` traits for Balances pallet.
use super::*;
use pezframe_support::traits::{
tokens::{
Fortitude,
Preservation::{self, Preserve, Protect},
Provenance::{self, Minted},
},
AccountTouch,
};
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() &&
pezframe_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() &&
!pezframe_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 pezframe_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, false, |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| {
// InactiveIssuance cannot be greater than TotalIssuance.
*b = b.saturating_add(amount).min(TotalIssuance::<T, I>::get());
});
}
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> {
fn done_hold(reason: &Self::Reason, who: &T::AccountId, amount: Self::Balance) {
Self::deposit_event(Event::<T, I>::Held { reason: *reason, who: who.clone(), amount });
}
fn done_release(reason: &Self::Reason, who: &T::AccountId, amount: Self::Balance) {
Self::deposit_event(Event::<T, I>::Released { reason: *reason, who: who.clone(), amount });
}
fn done_burn_held(reason: &Self::Reason, who: &T::AccountId, amount: Self::Balance) {
Self::deposit_event(Event::<T, I>::BurnedHeld {
reason: *reason,
who: who.clone(),
amount,
});
}
fn done_transfer_on_hold(
reason: &Self::Reason,
source: &T::AccountId,
dest: &T::AccountId,
amount: Self::Balance,
) {
// Emit on-hold transfer event
Self::deposit_event(Event::<T, I>::TransferOnHold {
reason: *reason,
source: source.clone(),
dest: dest.clone(),
amount,
});
}
fn done_transfer_and_hold(
reason: &Self::Reason,
source: &T::AccountId,
dest: &T::AccountId,
transferred: Self::Balance,
) {
Self::deposit_event(Event::<T, I>::TransferAndHold {
reason: *reason,
source: source.clone(),
dest: dest.clone(),
transferred,
})
}
}
impl<T: Config<I>, I: 'static> fungible::InspectHold<T::AccountId> for Pallet<T, I> {
type Reason = T::RuntimeHoldReason;
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 pezframe_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, false, |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 = NegativeImbalance<T, I>;
type OnDropDebt = PositiveImbalance<T, I>;
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) {
if !amount.is_zero() {
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> {}
impl<T: Config<I>, I: 'static>
fungible::hold::DoneSlash<T::RuntimeHoldReason, T::AccountId, T::Balance> for Pallet<T, I>
{
fn done_slash(reason: &T::RuntimeHoldReason, who: &T::AccountId, amount: T::Balance) {
T::DoneSlashHandler::done_slash(reason, who, amount);
}
}
impl<T: Config<I>, I: 'static> AccountTouch<(), T::AccountId> for Pallet<T, I> {
type Balance = T::Balance;
fn deposit_required(_: ()) -> Self::Balance {
Self::Balance::zero()
}
fn should_touch(_: (), _: &T::AccountId) -> bool {
false
}
fn touch(_: (), _: &T::AccountId, _: &T::AccountId) -> DispatchResult {
Ok(())
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,103 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Bizinikiwi.
// 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.
use super::*;
use pezframe_support::{
pezpallet_prelude::*,
traits::{OnRuntimeUpgrade, PalletInfoAccess},
weights::Weight,
};
fn migrate_v0_to_v1<T: Config<I>, I: 'static>(accounts: &[T::AccountId]) -> Weight {
let on_chain_version = Pallet::<T, I>::on_chain_storage_version();
if on_chain_version == 0 {
let total = accounts
.iter()
.map(|a| Pallet::<T, I>::total_balance(a))
.fold(T::Balance::zero(), |a, e| a.saturating_add(e));
Pallet::<T, I>::deactivate(total);
// Remove the old `StorageVersion` type.
pezframe_support::storage::unhashed::kill(&pezframe_support::storage::storage_prefix(
Pallet::<T, I>::name().as_bytes(),
"StorageVersion".as_bytes(),
));
// Set storage version to `1`.
StorageVersion::new(1).put::<Pallet<T, I>>();
log::info!(target: LOG_TARGET, "Storage to version 1");
T::DbWeight::get().reads_writes(2 + accounts.len() as u64, 3)
} else {
log::info!(
target: LOG_TARGET,
"Migration did not execute. This probably should be removed"
);
T::DbWeight::get().reads(1)
}
}
// NOTE: This must be used alongside the account whose balance is expected to be inactive.
// Generally this will be used for the XCM teleport checking account.
pub struct MigrateToTrackInactive<T, A, I = ()>(PhantomData<(T, A, I)>);
impl<T: Config<I>, A: Get<T::AccountId>, I: 'static> OnRuntimeUpgrade
for MigrateToTrackInactive<T, A, I>
{
fn on_runtime_upgrade() -> Weight {
migrate_v0_to_v1::<T, I>(&[A::get()])
}
}
// NOTE: This must be used alongside the accounts whose balance is expected to be inactive.
// Generally this will be used for the XCM teleport checking accounts.
pub struct MigrateManyToTrackInactive<T, A, I = ()>(PhantomData<(T, A, I)>);
impl<T: Config<I>, A: Get<Vec<T::AccountId>>, I: 'static> OnRuntimeUpgrade
for MigrateManyToTrackInactive<T, A, I>
{
fn on_runtime_upgrade() -> Weight {
migrate_v0_to_v1::<T, I>(&A::get())
}
}
pub struct ResetInactive<T, I = ()>(PhantomData<(T, I)>);
impl<T: Config<I>, I: 'static> OnRuntimeUpgrade for ResetInactive<T, I> {
fn on_runtime_upgrade() -> Weight {
let on_chain_version = Pallet::<T, I>::on_chain_storage_version();
if on_chain_version == 1 {
// Remove the old `StorageVersion` type.
pezframe_support::storage::unhashed::kill(&pezframe_support::storage::storage_prefix(
Pallet::<T, I>::name().as_bytes(),
"StorageVersion".as_bytes(),
));
InactiveIssuance::<T, I>::kill();
// Set storage version to `0`.
StorageVersion::new(0).put::<Pallet<T, I>>();
log::info!(target: LOG_TARGET, "Storage to version 0");
T::DbWeight::get().reads_writes(1, 3)
} else {
log::info!(
target: LOG_TARGET,
"Migration did not execute. This probably should be removed"
);
T::DbWeight::get().reads(1)
}
}
}
@@ -0,0 +1,153 @@
// This file is part of Bizinikiwi.
// 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.
//! Tests for consumer limit behavior in balance locks.
use super::*;
use pezframe_support::traits::{
fungible::{InspectFreeze, MutateFreeze, MutateHold},
Currency, Get, LockIdentifier, LockableCurrency, ReservableCurrency, WithdrawReasons,
};
const ID_1: LockIdentifier = *b"1 ";
#[test]
fn lock_behavior_when_consumer_limit_fully_exhausted() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build()
.execute_with(|| {
// Account 1 starts with 100 balance
Balances::make_free_balance_be(&1, 100);
assert_eq!(System::providers(&1), 1);
assert_eq!(System::consumers(&1), 0);
// Fill up all consumer refs.
// Note: asset-pallets prevents all the consumers to be filled and leaves one untouched.
// But other operations in the runtime, notably `uniques::set_accept_ownership` might
// overrule it.
let max_consumers: u32 = <Test as pezframe_system::Config>::MaxConsumers::get();
for _ in 0..max_consumers {
assert_ok!(System::inc_consumers(&1));
}
assert_eq!(System::consumers(&1), max_consumers);
// We cannot manually increment consumers beyond the limit
assert_noop!(System::inc_consumers(&1), DispatchError::TooManyConsumers);
// Although without limits it would work
pezframe_support::hypothetically!({
assert_ok!(System::inc_consumers_without_limit(&1));
});
// Now try to set a lock - this will still work because we use
// `inc_consumers_without_limit` in `update_lock`.
Balances::set_lock(ID_1, &1, 20, WithdrawReasons::all());
assert_eq!(Balances::locks(&1).len(), 1);
assert_eq!(Balances::locks(&1)[0].amount, 20);
// frozen amount is also updated
assert_eq!(get_test_account_data(1).frozen, 20);
// now this account has 1 more consumer reference for the lock
assert_eq!(System::consumers(&1), max_consumers + 1);
// And this account cannot transfer any funds out.
assert_noop!(
Balances::transfer_allow_death(pezframe_system::RawOrigin::Signed(1).into(), 2, 90),
DispatchError::Token(TokenError::Frozen)
);
});
}
#[test]
fn freeze_behavior_when_consumer_limit_fully_exhausted() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build()
.execute_with(|| {
// Account 1 starts with 100 balance
Balances::make_free_balance_be(&1, 100);
// Fill up all consumer refs.
let max_consumers: u32 = <Test as pezframe_system::Config>::MaxConsumers::get();
for _ in 0..max_consumers {
assert_ok!(System::inc_consumers(&1));
}
assert_eq!(System::consumers(&1), max_consumers);
// Now try to set a freeze - this will FAIL because freezes don't force consumer
// increment and we've exhausted the consumer limit.
assert_noop!(
Balances::set_freeze(&TestId::Foo, &1, 20),
DispatchError::TooManyConsumers
);
// Verify no freeze was created
assert_eq!(Balances::balance_frozen(&TestId::Foo, &1), 0);
// frozen amount is not updated
assert_eq!(get_test_account_data(1).frozen, 0);
});
}
#[test]
fn hold_behavior_when_consumer_limit_fully_exhausted() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build()
.execute_with(|| {
// Account 1 starts with 100 balance
Balances::make_free_balance_be(&1, 100);
// Fill up all consumer refs.
let max_consumers: u32 = <Test as pezframe_system::Config>::MaxConsumers::get();
for _ in 0..max_consumers {
assert_ok!(System::inc_consumers(&1));
}
assert_eq!(System::consumers(&1), max_consumers);
// Hold is similar to freeze -- it will successfully fail and report an error.
// Note: we use `assert_err` instead of `assert_noop` as this is not a dispatchable --
// when this is executed as a part of any transaction, it will revert.
assert_err!(Balances::hold(&TestId::Foo, &1, 50), DispatchError::TooManyConsumers);
});
}
#[test]
fn reserve_behavior_when_consumer_limit_fully_exhausted() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build()
.execute_with(|| {
// Account 1 starts with 100 balance
Balances::make_free_balance_be(&1, 100);
// Fill up all 16 consumer refs.
let max_consumers: u32 = <Test as pezframe_system::Config>::MaxConsumers::get();
for _ in 0..max_consumers {
assert_ok!(System::inc_consumers(&1));
}
assert_eq!(System::consumers(&1), max_consumers);
// Reserve is similar to freeze -- it will successfully fail and report an error.
assert_noop!(Balances::reserve(&1, 20), DispatchError::TooManyConsumers);
});
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,384 @@
// This file is part of Bizinikiwi.
// 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.
//! Tests regarding the functionality of the dispatchables/extrinsics.
use super::*;
use crate::{
AdjustmentDirection::{Decrease as Dec, Increase as Inc},
Event,
};
use pezframe_support::traits::{fungible::Unbalanced, tokens::Preservation::Expendable};
use fungible::{hold::Mutate as HoldMutate, Inspect, Mutate};
/// Alice account ID for more readable tests.
const ALICE: u64 = 1;
#[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!(pezframe_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 = pezpallet_balances::TotalIssuance::<Test>::get();
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1337, 69));
assert_eq!(pezpallet_balances::TotalIssuance::<Test>::get(), 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!(!get_test_account_data(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!(get_test_account_data(7).flags.is_new_logic());
assert_eq!(System::providers(&7), 1);
assert_eq!(System::consumers(&7), 1);
<Balances as pezframe_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);
});
}
#[test]
#[docify::export]
fn force_adjust_total_issuance_example() {
ExtBuilder::default().build_and_execute_with(|| {
// First we set the TotalIssuance to 64 by giving Alice a balance of 64.
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), ALICE, 64));
let old_ti = pezpallet_balances::TotalIssuance::<Test>::get();
assert_eq!(old_ti, 64, "TI should be 64");
// Now test the increase:
assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, 32));
let new_ti = pezpallet_balances::TotalIssuance::<Test>::get();
assert_eq!(old_ti + 32, new_ti, "Should increase by 32");
// If Alice tries to call it, it errors:
assert_noop!(
Balances::force_adjust_total_issuance(RawOrigin::Signed(ALICE).into(), Inc, 32),
BadOrigin,
);
});
}
#[test]
fn force_adjust_total_issuance_works() {
ExtBuilder::default().build_and_execute_with(|| {
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1337, 64));
let ti = pezpallet_balances::TotalIssuance::<Test>::get();
// Increase works:
assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, 32));
assert_eq!(pezpallet_balances::TotalIssuance::<Test>::get(), ti + 32);
System::assert_last_event(RuntimeEvent::Balances(Event::TotalIssuanceForced {
old: 64,
new: 96,
}));
// Decrease works:
assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 64));
assert_eq!(pezpallet_balances::TotalIssuance::<Test>::get(), ti - 32);
System::assert_last_event(RuntimeEvent::Balances(Event::TotalIssuanceForced {
old: 96,
new: 32,
}));
});
}
#[test]
fn force_adjust_total_issuance_saturates() {
ExtBuilder::default().build_and_execute_with(|| {
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1337, 64));
let ti = pezpallet_balances::TotalIssuance::<Test>::get();
let max = <Test as Config>::Balance::max_value();
assert_eq!(ti, 64);
// Increment saturates:
assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, max));
assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, 123));
assert_eq!(pezpallet_balances::TotalIssuance::<Test>::get(), max);
// Decrement saturates:
assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, max));
assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 123));
assert_eq!(pezpallet_balances::TotalIssuance::<Test>::get(), 0);
});
}
#[test]
fn force_adjust_total_issuance_rejects_zero_delta() {
ExtBuilder::default().build_and_execute_with(|| {
assert_noop!(
Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, 0),
Error::<Test>::DeltaZero,
);
assert_noop!(
Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 0),
Error::<Test>::DeltaZero,
);
});
}
#[test]
fn force_adjust_total_issuance_rejects_more_than_inactive() {
ExtBuilder::default().build_and_execute_with(|| {
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1337, 64));
Balances::deactivate(16u32.into());
assert_eq!(pezpallet_balances::TotalIssuance::<Test>::get(), 64);
assert_eq!(Balances::active_issuance(), 48);
// Works with up to 48:
assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 40),);
assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 8),);
assert_eq!(pezpallet_balances::TotalIssuance::<Test>::get(), 16);
assert_eq!(Balances::active_issuance(), 0);
// Errors with more than 48:
assert_noop!(
Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 1),
Error::<Test>::IssuanceDeactivated,
);
// Increasing again increases the inactive issuance:
assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, 10),);
assert_eq!(pezpallet_balances::TotalIssuance::<Test>::get(), 26);
assert_eq!(Balances::active_issuance(), 10);
});
}
#[test]
fn burn_works() {
ExtBuilder::default().build().execute_with(|| {
// Prepare account with initial balance
let (account, init_balance) = (1, 37);
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), account, init_balance));
let init_issuance = pezpallet_balances::TotalIssuance::<Test>::get();
let (keep_alive, allow_death) = (true, false);
// 1. Cannot burn more than what's available
assert_noop!(
Balances::burn(Some(account).into(), init_balance + 1, allow_death),
TokenError::FundsUnavailable,
);
// 2. Burn some funds, without reaping the account
let burn_amount_1 = 1;
assert_ok!(Balances::burn(Some(account).into(), burn_amount_1, allow_death));
System::assert_last_event(RuntimeEvent::Balances(Event::Burned {
who: account,
amount: burn_amount_1,
}));
assert_eq!(pezpallet_balances::TotalIssuance::<Test>::get(), init_issuance - burn_amount_1);
assert_eq!(Balances::total_balance(&account), init_balance - burn_amount_1);
// 3. Cannot burn funds below existential deposit if `keep_alive` is `true`
let burn_amount_2 =
init_balance - burn_amount_1 - <Test as Config>::ExistentialDeposit::get() + 1;
assert_noop!(
Balances::burn(Some(account).into(), init_balance + 1, keep_alive),
TokenError::FundsUnavailable,
);
// 4. Burn some more funds, this time reaping the account
assert_ok!(Balances::burn(Some(account).into(), burn_amount_2, allow_death));
System::assert_last_event(RuntimeEvent::Balances(Event::Burned {
who: account,
amount: burn_amount_2,
}));
assert_eq!(
pezpallet_balances::TotalIssuance::<Test>::get(),
init_issuance - burn_amount_1 - burn_amount_2
);
assert!(Balances::total_balance(&account).is_zero());
});
}
@@ -0,0 +1,419 @@
// This file is part of Bizinikiwi.
// 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 the behavior of a runtime when both `Fungible` and `Currency` traits are in use and are
//! being mixed.
//!
//! The primitives that we have and can mix are:
//!
//! * locks
//! * reserves
//! * holds
//! * freezes
//!
//! All permutations of which are:
//!
//! * Two primitives combined
//! * locks + reserves
//! * locks + holds
//! * locks + freezes
//! * reserves + holds
//! * reserves + freezes
//! * holds + freezes
//!
//! * Three primitives combined
//! * locks + reserves + holds
//! * locks + reserves + freezes
//! * locks + holds + freezes
//! * reserves + holds + freezes
//!
//! * All four primitives combined
//! * locks + reserves + holds + freezes
//!
//! For each test, after creating the primitive, we check:
//!
//! * The account data triplet.
//! * What `can_reserve` returns and where is the boundary.
//! * What `can_hold` returns and where is the boundary.
use super::*;
use pezframe_support::traits::{
fungible::{InspectHold, MutateFreeze, MutateHold},
Currency, LockIdentifier, LockableCurrency, ReservableCurrency, WithdrawReasons,
};
fn subject() -> AccountId {
let subject = 1;
Balances::make_free_balance_be(&subject, 100);
subject
}
const ID: LockIdentifier = *b"1 ";
fn b(x: AccountId) -> (Balance, Balance, Balance) {
let a = get_test_account_data(x);
(a.free, a.reserved, a.frozen)
}
fn ensure_max_reserve(who: AccountId, amount: Balance) {
assert!(!<Balances as ReservableCurrency<_>>::can_reserve(&who, amount.max(1) * 2));
assert!(!<Balances as ReservableCurrency<_>>::can_reserve(&who, amount + 1));
assert!(<Balances as ReservableCurrency<_>>::can_reserve(&who, amount));
assert!(<Balances as ReservableCurrency<_>>::can_reserve(&who, amount.saturating_sub(1)));
assert!(<Balances as ReservableCurrency<_>>::can_reserve(&who, amount / 2));
}
fn ensure_max_hold(who: AccountId, amount: Balance) {
assert!(<Balances as InspectHold<_>>::ensure_can_hold(&TestId::Foo, &who, amount.max(1) * 2)
.is_err());
assert!(<Balances as InspectHold<_>>::ensure_can_hold(&TestId::Foo, &who, amount + 1).is_err());
assert!(<Balances as InspectHold<_>>::ensure_can_hold(&TestId::Foo, &who, amount).is_ok());
assert!(<Balances as InspectHold<_>>::ensure_can_hold(
&TestId::Foo,
&who,
amount.saturating_sub(1)
)
.is_ok());
assert!(<Balances as InspectHold<_>>::ensure_can_hold(&TestId::Foo, &who, amount / 2).is_ok());
}
// Two primitives combined
#[test]
fn locks_and_reserves() {
ExtBuilder::default()
.monied(false)
.existential_deposit(1)
.build_and_execute_with(|| {
let who = subject();
<Balances as LockableCurrency<_>>::set_lock(ID, &who, 50, WithdrawReasons::all());
assert_eq!(b(who), (100, 0, 50));
// Can reserve up to 99 (leaving 1 for ED)
ensure_max_reserve(who, 99);
// Can hold up to 99 (leaving 1 for ED)
ensure_max_hold(who, 99);
assert_ok!(<Balances as ReservableCurrency<_>>::reserve(&who, 30));
assert_eq!(b(who), (70, 30, 50));
// Can hold or reserve up to 69 more (leaving 1 for ED)
let expected = 69;
ensure_max_reserve(who, expected);
ensure_max_hold(who, expected);
});
}
#[test]
fn locks_and_holds() {
ExtBuilder::default()
.monied(false)
.existential_deposit(1)
.build_and_execute_with(|| {
let who = subject();
// Lock 60 tokens
<Balances as LockableCurrency<_>>::set_lock(ID, &who, 60, WithdrawReasons::all());
assert_eq!(b(who), (100, 0, 60));
ensure_max_hold(who, 99);
ensure_max_reserve(who, 99);
// Hold 40 tokens
assert_ok!(<Balances as MutateHold<_>>::hold(&TestId::Foo, &who, 40));
assert_eq!(b(who), (60, 40, 60));
// Can hold or reserve up to 59 more (leaving 1 for ED)
let expected = 59;
ensure_max_hold(who, expected);
ensure_max_reserve(who, expected);
});
}
#[test]
fn locks_and_freezes() {
ExtBuilder::default()
.monied(false)
.existential_deposit(1)
.build_and_execute_with(|| {
let who = subject();
// Lock 40 tokens
<Balances as LockableCurrency<_>>::set_lock(ID, &who, 40, WithdrawReasons::all());
assert_eq!(b(who), (100, 0, 40));
// Freeze 70 tokens
assert_ok!(<Balances as MutateFreeze<_>>::set_freeze(&TestId::Foo, &who, 70));
// Frozen takes the max of lock (40) and freeze (70)
assert_eq!(b(who), (100, 0, 70));
// Can hold or reserve up to 64 more (leaving 1 for ED)
let expected = 99;
ensure_max_hold(who, expected);
ensure_max_reserve(who, expected);
});
}
#[test]
fn reserves_and_holds() {
ExtBuilder::default()
.monied(false)
.existential_deposit(1)
.build_and_execute_with(|| {
let who = subject();
// Reserve 30 tokens
assert_ok!(<Balances as ReservableCurrency<_>>::reserve(&who, 30));
assert_eq!(b(who), (70, 30, 0));
ensure_max_reserve(who, 69);
ensure_max_hold(who, 69);
// Hold 25 tokens (accumulates with reserve)
assert_ok!(<Balances as MutateHold<_>>::hold(&TestId::Foo, &who, 25));
assert_eq!(b(who), (45, 55, 0)); // reserved = 30 + 25 = 55
// Can hold or reserve up to 44 more (leaving 1 for ED)
let expected = 44;
ensure_max_reserve(who, expected);
ensure_max_hold(who, expected);
});
}
#[test]
fn reserves_and_freezes() {
ExtBuilder::default()
.monied(false)
.existential_deposit(1)
.build_and_execute_with(|| {
let who = subject();
// Reserve 25 tokens
assert_ok!(<Balances as ReservableCurrency<_>>::reserve(&who, 25));
assert_eq!(b(who), (75, 25, 0));
// Freeze 80 tokens
assert_ok!(<Balances as MutateFreeze<_>>::set_freeze(&TestId::Foo, &who, 80));
assert_eq!(b(who), (75, 25, 80));
// Can hold or reserve up to 74 more (leaving 1 for ED)
let expected = 74;
ensure_max_reserve(who, expected);
ensure_max_hold(who, expected);
});
}
#[test]
fn holds_and_freezes() {
ExtBuilder::default()
.monied(false)
.existential_deposit(1)
.build_and_execute_with(|| {
let who = subject();
// Hold 35 tokens
assert_ok!(<Balances as MutateHold<_>>::hold(&TestId::Foo, &who, 35));
assert_eq!(b(who), (65, 35, 0));
// Can hold or reserve up to 64 more (leaving 1 for ED)
let expected = 64;
ensure_max_reserve(who, expected);
ensure_max_hold(who, expected);
// Freeze 90 tokens
assert_ok!(<Balances as MutateFreeze<_>>::set_freeze(&TestId::Foo, &who, 90));
assert_eq!(b(who), (65, 35, 90));
// Can hold or reserve up to 64 more (leaving 1 for ED)
let expected = 64;
ensure_max_hold(who, expected);
ensure_max_reserve(who, expected);
});
}
// Three primitives combined
#[test]
fn locks_reserves_and_holds() {
ExtBuilder::default()
.monied(false)
.existential_deposit(1)
.build_and_execute_with(|| {
let who = subject();
// Lock 60 tokens
<Balances as LockableCurrency<_>>::set_lock(ID, &who, 60, WithdrawReasons::all());
assert_eq!(b(who), (100, 0, 60));
// Reserve 20 tokens
assert_ok!(<Balances as ReservableCurrency<_>>::reserve(&who, 20));
assert_eq!(b(who), (80, 20, 60));
// Can hold or reserve up to 79 more (leaving 1 for ED)
let expected = 79;
ensure_max_reserve(who, expected);
ensure_max_hold(who, expected);
// Hold 15 tokens (accumulates with reserve)
assert_ok!(<Balances as MutateHold<_>>::hold(&TestId::Foo, &who, 15));
assert_eq!(b(who), (65, 35, 60)); // reserved = 20 + 15 = 35
// Can hold or reserve up to 64 more (leaving 1 for ED)
let expected = 64;
ensure_max_reserve(who, expected);
ensure_max_hold(who, expected);
});
}
#[test]
fn locks_reserves_and_freezes() {
ExtBuilder::default()
.monied(false)
.existential_deposit(1)
.build_and_execute_with(|| {
let who = subject();
// Lock 40 tokens
<Balances as LockableCurrency<_>>::set_lock(ID, &who, 40, WithdrawReasons::all());
assert_eq!(b(who), (100, 0, 40));
// Reserve 25 tokens
assert_ok!(<Balances as ReservableCurrency<_>>::reserve(&who, 25));
assert_eq!(b(who), (75, 25, 40));
// Freeze 80 tokens (max of lock 40 and freeze 80)
assert_ok!(<Balances as MutateFreeze<_>>::set_freeze(&TestId::Foo, &who, 80));
assert_eq!(b(who), (75, 25, 80));
// Can hold or reserve up to 74 more (leaving 1 for ED)
let expected = 74;
ensure_max_reserve(who, expected);
ensure_max_hold(who, expected);
});
}
#[test]
fn locks_holds_and_freezes() {
ExtBuilder::default()
.monied(false)
.existential_deposit(1)
.build_and_execute_with(|| {
let who = subject();
// Lock 50 tokens
<Balances as LockableCurrency<_>>::set_lock(ID, &who, 50, WithdrawReasons::all());
assert_eq!(b(who), (100, 0, 50));
// Hold 30 tokens
assert_ok!(<Balances as MutateHold<_>>::hold(&TestId::Foo, &who, 30));
assert_eq!(b(who), (70, 30, 50));
// Freeze 75 tokens (max of lock 50 and freeze 75)
assert_ok!(<Balances as MutateFreeze<_>>::set_freeze(&TestId::Foo, &who, 75));
assert_eq!(b(who), (70, 30, 75));
// Can hold or reserve up to 69 more (leaving 1 for ED)
let expected = 69;
ensure_max_hold(who, expected);
ensure_max_reserve(who, expected);
});
}
#[test]
fn reserves_holds_and_freezes() {
ExtBuilder::default()
.monied(false)
.existential_deposit(1)
.build_and_execute_with(|| {
let who = subject();
// Reserve 20 tokens
assert_ok!(<Balances as ReservableCurrency<_>>::reserve(&who, 20));
assert_eq!(b(who), (80, 20, 0));
// Can hold or reserve up to 79 more (leaving 1 for ED)
let expected = 79;
ensure_max_reserve(who, expected);
ensure_max_hold(who, expected);
// Hold 25 tokens (accumulates with reserve)
assert_ok!(<Balances as MutateHold<_>>::hold(&TestId::Foo, &who, 25));
assert_eq!(b(who), (55, 45, 0)); // reserved = 20 + 25 = 45
// Can hold or reserve up to 54 more (leaving 1 for ED)
let expected = 54;
ensure_max_reserve(who, expected);
ensure_max_hold(who, expected);
// Freeze 90 tokens
assert_ok!(<Balances as MutateFreeze<_>>::set_freeze(&TestId::Foo, &who, 90));
assert_eq!(b(who), (55, 45, 90));
// Can hold or reserve up to 54 more (leaving 1 for ED)
let expected = 54;
ensure_max_hold(who, expected);
ensure_max_reserve(who, expected);
});
}
// All four primitives combined
#[test]
fn locks_reserves_holds_and_freezes() {
ExtBuilder::default()
.monied(false)
.existential_deposit(1)
.build_and_execute_with(|| {
let who = subject();
// Lock 40 tokens
<Balances as LockableCurrency<_>>::set_lock(ID, &who, 40, WithdrawReasons::all());
assert_eq!(b(who), (100, 0, 40));
// Can hold or reserve up to 99 more (leaving 1 for ED)
let expected = 99;
ensure_max_reserve(who, expected);
ensure_max_hold(who, expected);
// Reserve 20 tokens
assert_ok!(<Balances as ReservableCurrency<_>>::reserve(&who, 20));
assert_eq!(b(who), (80, 20, 40));
// Can hold or reserve up to 79 more (leaving 1 for ED)
let expected = 79;
ensure_max_reserve(who, expected);
ensure_max_hold(who, expected);
// Hold 15 tokens (accumulates with reserve)
assert_ok!(<Balances as MutateHold<_>>::hold(&TestId::Foo, &who, 15));
assert_eq!(b(who), (65, 35, 40)); // reserved = 20 + 15 = 35
// Can hold or reserve up to 64 more (leaving 1 for ED)
let expected = 64;
ensure_max_reserve(who, expected);
ensure_max_hold(who, expected);
// Freeze 85 tokens (max of lock 40 and freeze 85)
assert_ok!(<Balances as MutateFreeze<_>>::set_freeze(&TestId::Foo, &who, 85));
assert_eq!(b(who), (65, 35, 85));
// Can hold or reserve up to 64 more (leaving 1 for ED)
let expected = 64;
ensure_max_hold(who, expected);
ensure_max_reserve(who, expected);
});
}
@@ -0,0 +1,141 @@
// This file is part of Bizinikiwi.
// 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.
use super::*;
use pezframe_support::traits::fungible::{conformance_tests, Inspect, Mutate};
use paste::paste;
macro_rules! generate_tests {
// Handle a conformance test that requires special testing with and without a dust trap.
(dust_trap_variation, $base_path:path, $scope:expr, $trait:ident, $ext_deposit:expr, $($test_name:ident),*) => {
$(
paste! {
#[test]
fn [<$trait _ $scope _ $test_name _existential_deposit_ $ext_deposit _dust_trap_on >]() {
// Some random trap account.
let trap_account = <Test as pezframe_system::Config>::AccountId::from(65174286u64);
let builder = ExtBuilder::default().existential_deposit($ext_deposit).dust_trap(trap_account);
builder.build_and_execute_with(|| {
Balances::set_balance(&trap_account, Balances::minimum_balance());
$base_path::$scope::$trait::$test_name::<
Balances,
<Test as pezframe_system::Config>::AccountId,
>(Some(trap_account));
});
}
#[test]
fn [< $trait _ $scope _ $test_name _existential_deposit_ $ext_deposit _dust_trap_off >]() {
let builder = ExtBuilder::default().existential_deposit($ext_deposit);
builder.build_and_execute_with(|| {
$base_path::$scope::$trait::$test_name::<
Balances,
<Test as pezframe_system::Config>::AccountId,
>(None);
});
}
}
)*
};
// Regular conformance test
($base_path:path, $scope:expr, $trait:ident, $ext_deposit:expr, $($test_name:ident),*) => {
$(
paste! {
#[test]
fn [< $trait _ $scope _ $test_name _existential_deposit_ $ext_deposit>]() {
let builder = ExtBuilder::default().existential_deposit($ext_deposit);
builder.build_and_execute_with(|| {
$base_path::$scope::$trait::$test_name::<
Balances,
<Test as pezframe_system::Config>::AccountId,
>();
});
}
}
)*
};
($base_path:path, $ext_deposit:expr) => {
// regular::mutate
generate_tests!(
dust_trap_variation,
$base_path,
regular,
mutate,
$ext_deposit,
transfer_expendable_dust
);
generate_tests!(
$base_path,
regular,
mutate,
$ext_deposit,
mint_into_success,
mint_into_overflow,
mint_into_below_minimum,
burn_from_exact_success,
burn_from_best_effort_success,
burn_from_exact_insufficient_funds,
restore_success,
restore_overflow,
restore_below_minimum,
shelve_success,
shelve_insufficient_funds,
transfer_success,
transfer_expendable_all,
transfer_protect_preserve,
set_balance_mint_success,
set_balance_burn_success,
can_deposit_success,
can_deposit_below_minimum,
can_deposit_overflow,
can_withdraw_success,
can_withdraw_reduced_to_zero,
can_withdraw_balance_low,
reducible_balance_expendable,
reducible_balance_protect_preserve
);
// regular::unbalanced
generate_tests!(
$base_path,
regular,
unbalanced,
$ext_deposit,
write_balance,
decrease_balance_expendable,
decrease_balance_preserve,
increase_balance,
set_total_issuance,
deactivate_and_reactivate
);
// regular::balanced
generate_tests!(
$base_path,
regular,
balanced,
$ext_deposit,
issue_and_resolve_credit,
rescind_and_settle_debt,
deposit,
withdraw,
pair
);
};
}
generate_tests!(conformance_tests, 1);
generate_tests!(conformance_tests, 5);
generate_tests!(conformance_tests, 1000);
@@ -0,0 +1,853 @@
// This file is part of Bizinikiwi.
// 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.
//! Tests regarding the functionality of the `fungible` trait set implementations.
use super::*;
use pezframe_support::traits::{
tokens::{
Fortitude::{Force, Polite},
Precision::{BestEffort, Exact},
Preservation::{Expendable, Preserve, Protect},
Restriction::Free,
},
Consideration, Footprint, LinearStoragePrice, MaybeConsideration,
};
use fungible::{
FreezeConsideration, HoldConsideration, Inspect, InspectFreeze, InspectHold,
LoneFreezeConsideration, LoneHoldConsideration, Mutate, MutateFreeze, MutateHold, Unbalanced,
};
use pezsp_core::ConstU64;
#[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));
System::assert_last_event(RuntimeEvent::Balances(crate::Event::Released {
reason: TestId::Foo,
who: 1337,
amount: 60,
}));
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),
TokenError::FundsUnavailable
);
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!(get_test_account_data(1).free, 1);
assert_eq!(System::consumers(&1), 1);
assert_eq!(get_test_account_data(1).free, 1);
assert_eq!(get_test_account_data(1).frozen, 10);
assert_eq!(get_test_account_data(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));
assert_eq!(
events(),
[
RuntimeEvent::Balances(crate::Event::Frozen { who: 1, amount: 10 }),
RuntimeEvent::Balances(crate::Event::Held {
reason: TestId::Foo,
who: 1,
amount: 9
}),
RuntimeEvent::Balances(crate::Event::TransferOnHold {
reason: TestId::Foo,
source: 1,
dest: 2,
amount: 1
})
]
);
assert_eq!(Balances::total_balance(&2), 21);
});
}
#[test]
fn transfer_and_hold() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
// Freeze 7 units in source account (Account 1)
assert_ok!(Balances::hold(&TestId::Foo, &1, 7));
assert_ok!(Balances::hold(&TestId::Foo, &2, 2));
// Verify reducible balance
assert_eq!(Balances::reducible_total_balance_on_hold(&1, Force), 7);
// Force transfer_and_hold should succeed
assert_ok!(Balances::transfer_and_hold(
&TestId::Foo,
&1,
&2,
1,
Exact,
Preserve,
Polite
));
// Verify state changes
assert_eq!(Balances::free_balance(1), 2);
assert_eq!(Balances::balance_on_hold(&TestId::Foo, &2), 3);
assert_eq!(Balances::total_balance(&2), 21);
assert_eq!(
events(),
[
RuntimeEvent::Balances(crate::Event::Held {
reason: TestId::Foo,
who: 1,
amount: 7
}),
RuntimeEvent::Balances(crate::Event::Held {
reason: TestId::Foo,
who: 2,
amount: 2
}),
RuntimeEvent::Balances(crate::Event::TransferAndHold {
reason: TestId::Foo,
source: 1,
dest: 2,
transferred: 1
})
]
);
});
}
#[test]
fn burn_held() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
let account = 1;
assert_ok!(Balances::hold(&TestId::Foo, &account, 5));
// Burn the held funds
assert_ok!(Balances::burn_held(&TestId::Foo, &account, 4, Exact, Polite));
// Check that the BurnedHeld event is emitted with correct parameters
System::assert_last_event(RuntimeEvent::Balances(crate::Event::BurnedHeld {
reason: TestId::Foo,
who: account,
amount: 4,
}));
// Verify the held balance is removed and total balance is updated
assert_eq!(Balances::balance_on_hold(&TestId::Foo, &account), 1);
assert_eq!(Balances::total_balance(&account), 6);
assert_eq!(Balances::total_issuance(), 106);
});
}
#[test]
fn negative_imbalance_drop_handled_correctly() {
ExtBuilder::default().build_and_execute_with(|| {
let account = 1;
let initial_balance = 100;
let withdraw_amount = 50;
// Set initial balance and total issuance
Balances::set_balance(&account, initial_balance);
assert_eq!(Balances::total_issuance(), initial_balance);
// Withdraw using fungible::Balanced to create a NegativeImbalance
let negative_imb = <Balances as fungible::Balanced<_>>::withdraw(
&account,
withdraw_amount,
Exact,
Expendable,
Polite,
)
.expect("Withdraw failed");
// Verify balance decreased but total issuance remains unchanged
assert_eq!(Balances::free_balance(&account), initial_balance - withdraw_amount);
assert_eq!(Balances::total_issuance(), initial_balance);
// Drop the NegativeImbalance, triggering HandleImbalanceDrop
drop(negative_imb);
// Check total issuance decreased and event emitted
assert_eq!(Balances::total_issuance(), initial_balance - withdraw_amount);
System::assert_last_event(RuntimeEvent::Balances(crate::Event::BurnedDebt {
amount: withdraw_amount,
}));
});
}
#[test]
fn positive_imbalance_drop_handled_correctly() {
ExtBuilder::default().build_and_execute_with(|| {
let account = 1;
let deposit_amount = 50;
let initial_issuance = Balances::total_issuance();
// Deposit using fungible::Balanced to create a PositiveImbalance
let positive_imb =
<Balances as fungible::Balanced<_>>::deposit(&account, deposit_amount, Exact)
.expect("Deposit failed");
// Verify balance increased but total issuance remains unchanged
assert_eq!(Balances::free_balance(&account), deposit_amount);
assert_eq!(Balances::total_issuance(), initial_issuance);
// Drop the PositiveImbalance, triggering HandleImbalanceDrop
drop(positive_imb);
// Check total issuance increased and event emitted
assert_eq!(Balances::total_issuance(), initial_issuance + deposit_amount);
System::assert_last_event(RuntimeEvent::Balances(crate::Event::MintedCredit {
amount: deposit_amount,
}));
});
}
#[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!(get_test_account_data(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!(get_test_account_data(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!(get_test_account_data(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));
});
}
#[test]
fn sufficients_work_properly_with_reference_counting() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
// Only run PoC when the system pallet is enabled, since the underlying bug is in the
// system pallet it won't work with BalancesAccountStore
if UseSystem::get() {
// Start with a balance of 100
<Balances as fungible::Mutate<_>>::set_balance(&1, 100);
// Emulate a sufficient, in reality this could be reached by transferring a
// sufficient asset to the account
System::inc_sufficients(&1);
// Spend the same balance multiple times
assert_ok!(<Balances as fungible::Mutate<_>>::transfer(&1, &1337, 100, Expendable));
assert_eq!(Balances::free_balance(&1), 0);
assert_noop!(
<Balances as fungible::Mutate<_>>::transfer(&1, &1337, 100, Expendable),
TokenError::FundsUnavailable
);
}
});
}
#[test]
fn emit_events_with_changing_freezes() {
ExtBuilder::default().build_and_execute_with(|| {
let _ = Balances::set_balance(&1, 100);
System::reset_events();
// Freeze = [] --> [10]
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10));
assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Frozen { who: 1, amount: 10 })]);
// Freeze = [10] --> [15]
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 15));
assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Frozen { who: 1, amount: 5 })]);
// Freeze = [15] --> [15, 20]
assert_ok!(Balances::set_freeze(&TestId::Bar, &1, 20));
assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Frozen { who: 1, amount: 5 })]);
// Freeze = [15, 20] --> [17, 20]
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 17));
for event in events() {
match event {
RuntimeEvent::Balances(crate::Event::Frozen { .. }) => {
assert!(false, "unexpected freeze event")
},
RuntimeEvent::Balances(crate::Event::Thawed { .. }) => {
assert!(false, "unexpected thaw event")
},
_ => continue,
}
}
// Freeze = [17, 20] --> [17, 15]
assert_ok!(Balances::set_freeze(&TestId::Bar, &1, 15));
assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Thawed { who: 1, amount: 3 })]);
// Freeze = [17, 15] --> [15]
assert_ok!(Balances::thaw(&TestId::Foo, &1));
assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Thawed { who: 1, amount: 2 })]);
// Freeze = [15] --> []
assert_ok!(Balances::thaw(&TestId::Bar, &1));
assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Thawed { who: 1, amount: 15 })]);
});
}
#[test]
fn withdraw_precision_exact_works() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10));
assert_eq!(get_test_account_data(1).free, 10);
assert_eq!(get_test_account_data(1).frozen, 10);
// `BestEffort` will not reduce anything
assert_ok!(<Balances as fungible::Balanced<_>>::withdraw(
&1, 5, BestEffort, Preserve, Polite
));
assert_eq!(get_test_account_data(1).free, 10);
assert_eq!(get_test_account_data(1).frozen, 10);
assert_noop!(
<Balances as fungible::Balanced<_>>::withdraw(&1, 5, Exact, Preserve, Polite),
TokenError::FundsUnavailable
);
});
}
#[test]
fn freeze_consideration_works() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
type Consideration = FreezeConsideration<
u64,
Balances,
FooReason,
LinearStoragePrice<ConstU64<0>, ConstU64<1>, u64>,
Footprint,
>;
let who = 4;
// freeze amount taken somewhere outside of our (Consideration) scope.
let extend_freeze = 15;
let ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap();
assert!(ticket.is_none());
assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0);
let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap();
assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10);
let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap();
assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 4);
assert_ok!(Balances::increase_frozen(&TestId::Foo, &who, extend_freeze));
assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 4 + extend_freeze);
let ticket = ticket.update(&who, Footprint::from_parts(8, 1)).unwrap();
assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 8 + extend_freeze);
let ticket = ticket.update(&who, Footprint::from_parts(0, 0)).unwrap();
assert!(ticket.is_none());
assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0 + extend_freeze);
let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap();
assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10 + extend_freeze);
let _ = ticket.drop(&who).unwrap();
assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0 + extend_freeze);
});
}
#[test]
fn hold_consideration_works() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
type Consideration = HoldConsideration<
u64,
Balances,
FooReason,
LinearStoragePrice<ConstU64<0>, ConstU64<1>, u64>,
Footprint,
>;
let who = 4;
// hold amount taken somewhere outside of our (Consideration) scope.
let extend_hold = 15;
let ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap();
assert!(ticket.is_none());
assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0);
let ticket = ticket.update(&who, Footprint::from_parts(10, 1)).unwrap();
assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10);
let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap();
assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 4);
assert_ok!(Balances::hold(&TestId::Foo, &who, extend_hold));
assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 4 + extend_hold);
let ticket = ticket.update(&who, Footprint::from_parts(8, 1)).unwrap();
assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 8 + extend_hold);
let ticket = ticket.update(&who, Footprint::from_parts(0, 0)).unwrap();
assert!(ticket.is_none());
assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0 + extend_hold);
let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap();
assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10 + extend_hold);
let _ = ticket.drop(&who).unwrap();
assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0 + extend_hold);
});
}
#[test]
fn lone_freeze_consideration_works() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
type Consideration = LoneFreezeConsideration<
u64,
Balances,
FooReason,
LinearStoragePrice<ConstU64<0>, ConstU64<1>, u64>,
Footprint,
>;
let who = 4;
let zero_ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap();
assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0);
let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap();
assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10);
assert_ok!(Balances::increase_frozen(&TestId::Foo, &who, 5));
assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 15);
let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap();
assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 4);
assert_eq!(ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(), zero_ticket);
assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0);
let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap();
assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10);
let _ = ticket.drop(&who).unwrap();
assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0);
});
}
#[test]
fn lone_hold_consideration_works() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
type Consideration = LoneHoldConsideration<
u64,
Balances,
FooReason,
LinearStoragePrice<ConstU64<0>, ConstU64<1>, u64>,
Footprint,
>;
let who = 4;
let zero_ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap();
assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0);
let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap();
assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10);
assert_ok!(Balances::hold(&TestId::Foo, &who, 5));
assert_eq!(
events(),
[
RuntimeEvent::Balances(crate::Event::Held {
reason: TestId::Foo,
who,
amount: 10
}),
RuntimeEvent::Balances(crate::Event::Held {
reason: TestId::Foo,
who,
amount: 5
})
]
);
assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 15);
let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap();
assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 4);
assert_eq!(ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(), zero_ticket);
assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0);
let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap();
assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10);
let _ = ticket.drop(&who).unwrap();
assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0);
});
}
@@ -0,0 +1,145 @@
// This file is part of Bizinikiwi.
// 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.
#![cfg(test)]
use crate::{
system::AccountInfo,
tests::{
ensure_ti_valid, get_test_account, Balances, ExtBuilder, System, Test, TestId, UseSystem,
},
AccountData, ExtraFlags, TotalIssuance,
};
use pezframe_support::{
assert_noop, assert_ok, hypothetically,
traits::{
fungible::{Mutate, MutateHold},
tokens::Precision,
},
};
use pezsp_runtime::DispatchError;
/// There are some accounts that have one consumer ref too few. These accounts are at risk of losing
/// their held (reserved) balance. They do not just lose it - it is also not accounted for in the
/// Total Issuance. Here we test the case that the account does not reap in such a case, but gets
/// one consumer ref for its reserved balance.
#[test]
fn regression_historic_acc_does_not_evaporate_reserve() {
ExtBuilder::default().build_and_execute_with(|| {
UseSystem::set(true);
let (alice, bob) = (0, 1);
// Alice is in a bad state with consumer == 0 && reserved > 0:
Balances::set_balance(&alice, 100);
TotalIssuance::<Test>::put(100);
ensure_ti_valid();
assert_ok!(Balances::hold(&TestId::Foo, &alice, 10));
// This is the issue of the account:
System::dec_consumers(&alice);
assert_eq!(
get_test_account(alice),
AccountInfo {
data: AccountData {
free: 90,
reserved: 10,
frozen: 0,
flags: ExtraFlags(1u128 << 127),
},
nonce: 0,
consumers: 0, // should be 1 on a good acc
providers: 1,
sufficients: 0,
}
);
ensure_ti_valid();
// Reaping the account is prevented by the new logic:
assert_noop!(
Balances::transfer_allow_death(Some(alice).into(), bob, 90),
DispatchError::ConsumerRemaining
);
assert_noop!(
Balances::transfer_all(Some(alice).into(), bob, false),
DispatchError::ConsumerRemaining
);
// normal transfers still work:
hypothetically!({
assert_ok!(Balances::transfer_keep_alive(Some(alice).into(), bob, 40));
// Alice got back her consumer ref:
assert_eq!(System::consumers(&alice), 1);
ensure_ti_valid();
});
hypothetically!({
assert_ok!(Balances::transfer_all(Some(alice).into(), bob, true));
// Alice got back her consumer ref:
assert_eq!(System::consumers(&alice), 1);
ensure_ti_valid();
});
// un-reserving all does not add a consumer ref:
hypothetically!({
assert_ok!(Balances::release(&TestId::Foo, &alice, 10, Precision::Exact));
assert_eq!(System::consumers(&alice), 0);
assert_ok!(Balances::transfer_keep_alive(Some(alice).into(), bob, 40));
assert_eq!(System::consumers(&alice), 0);
ensure_ti_valid();
});
// un-reserving some does add a consumer ref:
hypothetically!({
assert_ok!(Balances::release(&TestId::Foo, &alice, 5, Precision::Exact));
assert_eq!(System::consumers(&alice), 1);
assert_ok!(Balances::transfer_keep_alive(Some(alice).into(), bob, 40));
assert_eq!(System::consumers(&alice), 1);
ensure_ti_valid();
});
});
}
#[cfg(feature = "try-runtime")]
#[test]
fn try_state_works() {
use crate::{Config, Freezes, Holds};
use pezframe_support::{
storage,
traits::{Get, Hooks, VariantCount},
};
ExtBuilder::default().auto_try_state(false).build_and_execute_with(|| {
storage::unhashed::put(
&Holds::<Test>::hashed_key_for(1),
&vec![0u8; <Test as Config>::RuntimeHoldReason::VARIANT_COUNT as usize + 1],
);
assert!(format!("{:?}", Balances::try_state(0).unwrap_err())
.contains("Found `Hold` with too many elements"));
});
ExtBuilder::default().auto_try_state(false).build_and_execute_with(|| {
let max_freezes: u32 = <Test as Config>::MaxFreezes::get();
storage::unhashed::put(
&Freezes::<Test>::hashed_key_for(1),
&vec![0u8; max_freezes as usize + 1],
);
assert!(format!("{:?}", Balances::try_state(0).unwrap_err())
.contains("Found `Freeze` with too many elements"));
});
}
@@ -0,0 +1,393 @@
// This file is part of Bizinikiwi.
// 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.
//! Tests.
#![cfg(test)]
use crate::{
self as pezpallet_balances, AccountData, Config, CreditOf, Error, Pallet, TotalIssuance,
DEFAULT_ADDRESS_URI,
};
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use pezframe_support::{
assert_err, assert_noop, assert_ok, assert_storage_noop, derive_impl,
dispatch::{DispatchInfo, GetDispatchInfo},
parameter_types,
traits::{
fungible, ConstU32, ConstU8, Imbalance as ImbalanceT, OnUnbalanced, StorageMapShim,
StoredMap, VariantCount, VariantCountOf, WhitelistedStorageKeys,
},
weights::{IdentityFee, Weight},
};
use pezframe_system::{self as system, RawOrigin};
use pezpallet_transaction_payment::{ChargeTransactionPayment, FungibleAdapter, Multiplier};
use scale_info::TypeInfo;
use pezsp_core::{hexdisplay::HexDisplay, sr25519::Pair as SrPair, Pair};
use pezsp_io;
use pezsp_runtime::{
traits::{BadOrigin, Zero},
ArithmeticError, BuildStorage, DispatchError, DispatchResult, FixedPointNumber, RuntimeDebug,
TokenError,
};
use std::collections::BTreeSet;
mod consumer_limit_tests;
mod currency_tests;
mod dispatchable_tests;
mod fungible_and_currency;
mod fungible_conformance_tests;
mod fungible_tests;
mod general_tests;
mod reentrancy_tests;
type Block = pezframe_system::mocking::MockBlock<Test>;
#[derive(
Encode,
Decode,
DecodeWithMemTracking,
Copy,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
MaxEncodedLen,
TypeInfo,
RuntimeDebug,
)]
pub enum TestId {
Foo,
Bar,
Baz,
}
impl VariantCount for TestId {
const VARIANT_COUNT: u32 = 3;
}
pub(crate) type AccountId = <Test as pezframe_system::Config>::AccountId;
pub(crate) type Balance = <Test as Config>::Balance;
pezframe_support::construct_runtime!(
pub enum Test {
System: pezframe_system,
Balances: pezpallet_balances,
TransactionPayment: pezpallet_transaction_payment,
}
);
parameter_types! {
pub BlockWeights: pezframe_system::limits::BlockWeights =
pezframe_system::limits::BlockWeights::simple_max(
pezframe_support::weights::Weight::from_parts(1024, u64::MAX),
);
pub static ExistentialDeposit: u64 = 1;
}
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl pezframe_system::Config for Test {
type Block = Block;
type AccountData = super::AccountData<u64>;
}
#[derive_impl(pezpallet_transaction_payment::config_preludes::TestDefaultConfig)]
impl pezpallet_transaction_payment::Config for Test {
type RuntimeEvent = RuntimeEvent;
type OnChargeTransaction = FungibleAdapter<Pallet<Test>, ()>;
type OperationalFeeMultiplier = ConstU8<5>;
type WeightToFee = IdentityFee<u64>;
type LengthToFee = IdentityFee<u64>;
}
parameter_types! {
pub FooReason: TestId = TestId::Foo;
}
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
impl Config for Test {
type DustRemoval = DustTrap;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = TestAccountStore;
type MaxReserves = ConstU32<2>;
type ReserveIdentifier = TestId;
type RuntimeHoldReason = TestId;
type RuntimeFreezeReason = TestId;
type FreezeIdentifier = TestId;
type MaxFreezes = VariantCountOf<TestId>;
}
#[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
}
#[cfg(feature = "try-runtime")]
pub fn auto_try_state(self, auto_try_state: bool) -> Self {
AutoTryState::set(auto_try_state);
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) -> pezsp_io::TestExternalities {
self.set_associated_consts();
let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
pezpallet_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![]
},
dev_accounts: Some((
1000,
self.existential_deposit,
Some(DEFAULT_ADDRESS_URI.to_string()),
)),
}
.assimilate_storage(&mut t)
.unwrap();
let mut ext = pezsp_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();
if AutoTryState::get() {
Balances::do_try_state(System::block_number()).unwrap();
}
});
UseSystem::set(true);
self.build().execute_with(|| {
f();
if AutoTryState::get() {
Balances::do_try_state(System::block_number()).unwrap();
}
});
}
}
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;
pub static AutoTryState: bool = true;
}
type BalancesAccountStore = StorageMapShim<super::Account<Test>, u64, super::AccountData<u64>>;
type SystemAccountStore = pezframe_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 { call_weight: w, ..Default::default() }
}
/// Check that the total-issuance matches the sum of all accounts' total balances.
pub fn ensure_ti_valid() {
let mut sum = 0;
// Fetch the dev accounts from Account Storage.
let dev_accounts = (1000, EXISTENTIAL_DEPOSIT, DEFAULT_ADDRESS_URI.to_string());
let (num_accounts, _balance, ref derivation) = dev_accounts;
// Generate the dev account public keys.
let dev_account_ids: Vec<_> = (0..num_accounts)
.map(|index| {
let derivation_string = derivation.replace("{}", &index.to_string());
let pair: SrPair =
Pair::from_string(&derivation_string, None).expect("Invalid derivation string");
<crate::tests::Test as pezframe_system::Config>::AccountId::decode(
&mut &pair.public().encode()[..],
)
.unwrap()
})
.collect();
// Iterate over all account keys (i.e., the account IDs).
for acc in pezframe_system::Account::<Test>::iter_keys() {
// Skip dev accounts by checking if the account is in the dev_account_ids list.
// This also proves dev_accounts exists in storage.
if dev_account_ids.contains(&acc) {
continue;
}
// Check if we are using the system pallet or some other custom storage for accounts.
if UseSystem::get() {
let data = pezframe_system::Pallet::<Test>::account(acc);
sum += data.data.total();
} else {
let data = crate::Account::<Test>::get(acc);
sum += data.total();
}
}
// Ensure the total issuance matches the sum of the account balances
assert_eq!(TotalIssuance::<Test>::get(), sum, "Total Issuance is incorrect");
}
#[test]
fn weights_sane() {
let info = crate::Call::<Test>::transfer_allow_death { dest: 10, value: 4 }.get_dispatch_info();
assert_eq!(<() as crate::WeightInfo>::transfer_allow_death(), info.call_weight);
let info = crate::Call::<Test>::force_unreserve { who: 10, amount: 4 }.get_dispatch_info();
assert_eq!(<() as crate::WeightInfo>::force_unreserve(), info.call_weight);
}
#[test]
fn check_whitelist() {
let whitelist: BTreeSet<String> = AllPalletsWithSystem::whitelisted_storage_keys()
.iter()
.map(|s| HexDisplay::from(&s.key).to_string())
.collect();
// Inactive Issuance
assert!(whitelist.contains("c2261276cc9d1f8598ea4b6a74b15c2f1ccde6872881f893a21de93dfe970cd5"));
// Total Issuance
assert!(whitelist.contains("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80"));
}
/// This pallet runs tests twice, once with system as `type AccountStore` and once this pallet. This
/// function will return the right value based on the `UseSystem` flag.
pub(crate) fn get_test_account_data(who: AccountId) -> AccountData<Balance> {
if UseSystem::get() {
<SystemAccountStore as StoredMap<_, _>>::get(&who)
} else {
<BalancesAccountStore as StoredMap<_, _>>::get(&who)
}
}
/// Same as `get_test_account_data`, but returns a `pezframe_system::AccountInfo` with the data filled
/// in.
pub(crate) fn get_test_account(
who: AccountId,
) -> pezframe_system::AccountInfo<u32, AccountData<Balance>> {
let mut system_account = pezframe_system::Account::<Test>::get(&who);
let account_data = get_test_account_data(who);
system_account.data = account_data;
system_account
}
@@ -0,0 +1,201 @@
// This file is part of Bizinikiwi.
// 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.
//! Tests regarding the reentrancy functionality.
use super::*;
use pezframe_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 transferred 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(), 14);
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(), 12);
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
// Existential 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(), 12);
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::Issued { amount: 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 }),
RuntimeEvent::Balances(crate::Event::BurnedDebt { 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::BurnedDebt { amount: 1 }),
RuntimeEvent::Balances(crate::Event::Withdraw { who: 1, amount: 1 }),
RuntimeEvent::Balances(crate::Event::BurnedDebt { amount: 1 })
]
);
});
}
+216
View File
@@ -0,0 +1,216 @@
// This file is part of Bizinikiwi.
// 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.
//! Types used in the pallet.
use crate::{Config, CreditOf, Event, Pallet};
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use core::ops::BitOr;
use pezframe_support::traits::{Imbalance, LockIdentifier, OnUnbalanced, WithdrawReasons};
use scale_info::TypeInfo;
use pezsp_runtime::{RuntimeDebug, Saturating};
/// Simplified reasons for withdrawing balance.
#[derive(
Encode,
Decode,
DecodeWithMemTracking,
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,
DecodeWithMemTracking,
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,
DecodeWithMemTracking,
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,
}
/// All balance information for an account.
#[derive(
Encode,
Decode,
DecodeWithMemTracking,
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 + reserved` may not drop below when reducing the balance, except for
/// actions where the account owner cannot reasonably benefit from the 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,
DecodeWithMemTracking,
Clone,
PartialEq,
Eq,
RuntimeDebug,
MaxEncodedLen,
TypeInfo,
)]
pub struct ExtraFlags(pub(crate) 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);
}
}
}
/// Whether something should be interpreted as an increase or a decrease.
#[derive(
Encode,
Decode,
DecodeWithMemTracking,
Clone,
PartialEq,
Eq,
RuntimeDebug,
MaxEncodedLen,
TypeInfo,
)]
pub enum AdjustmentDirection {
/// Increase the amount.
Increase,
/// Decrease the amount.
Decrease,
}
+321
View File
@@ -0,0 +1,321 @@
// This file is part of Bizinikiwi.
// 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.
// This file is part of Bizinikiwi.
// 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.
//! Autogenerated weights for `pezpallet_balances`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.0
//! DATE: 2025-02-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `4563561839a5`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
// Executed Command:
// frame-omni-bencher
// v1
// benchmark
// pallet
// --extrinsic=*
// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm
// --pallet=pezpallet_balances
// --header=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/HEADER-APACHE2
// --output=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/pezframe/balances/src/weights.rs
// --wasm-execution=compiled
// --steps=50
// --repeat=20
// --heap-pages=4096
// --template=bizinikiwi/.maintain/frame-weight-template.hbs
// --no-storage-info
// --no-min-squares
// --no-median-slopes
// --genesis-builder-policy=none
// --exclude-pallets=pezpallet_xcm,pezpallet_xcm_benchmarks::fungible,pezpallet_xcm_benchmarks::generic,pezpallet_nomination_pools,pezpallet_remark,pezpallet_transaction_storage,pezpallet_election_provider_multi_block,pezpallet_election_provider_multi_block::signed,pezpallet_election_provider_multi_block::unsigned,pezpallet_election_provider_multi_block::verifier
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
#![allow(dead_code)]
use pezframe_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use core::marker::PhantomData;
/// Weight functions needed for `pezpallet_balances`.
pub trait WeightInfo {
fn transfer_allow_death() -> Weight;
fn transfer_keep_alive() -> 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;
fn force_adjust_total_issuance() -> Weight;
fn burn_allow_death() -> Weight;
fn burn_keep_alive() -> Weight;
}
/// Weights for `pezpallet_balances` using the Bizinikiwi node and recommended hardware.
pub struct BizinikiwiWeight<T>(PhantomData<T>);
impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn transfer_allow_death() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `3593`
// Minimum execution time: 48_203_000 picoseconds.
Weight::from_parts(48_834_000, 3593)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn transfer_keep_alive() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `3593`
// Minimum execution time: 38_647_000 picoseconds.
Weight::from_parts(39_051_000, 3593)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn force_set_balance_creating() -> Weight {
// Proof Size summary in bytes:
// Measured: `52`
// Estimated: `3593`
// Minimum execution time: 12_191_000 picoseconds.
Weight::from_parts(12_547_000, 3593)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn force_set_balance_killing() -> Weight {
// Proof Size summary in bytes:
// Measured: `52`
// Estimated: `3593`
// Minimum execution time: 18_636_000 picoseconds.
Weight::from_parts(19_206_000, 3593)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `System::Account` (r:2 w:2)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn force_transfer() -> Weight {
// Proof Size summary in bytes:
// Measured: `52`
// Estimated: `6196`
// Minimum execution time: 49_073_000 picoseconds.
Weight::from_parts(49_519_000, 6196)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(2_u64))
}
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn transfer_all() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `3593`
// Minimum execution time: 47_572_000 picoseconds.
Weight::from_parts(48_209_000, 3593)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn force_unreserve() -> Weight {
// Proof Size summary in bytes:
// Measured: `52`
// Estimated: `3593`
// Minimum execution time: 15_290_000 picoseconds.
Weight::from_parts(15_515_000, 3593)
.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: 14_546_000 picoseconds.
Weight::from_parts(14_674_000, 990)
// Standard Error: 11_734
.saturating_add(Weight::from_parts(14_648_188, 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()))
}
fn force_adjust_total_issuance() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 5_096_000 picoseconds.
Weight::from_parts(5_351_000, 0)
}
fn burn_allow_death() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 29_641_000 picoseconds.
Weight::from_parts(30_219_000, 0)
}
fn burn_keep_alive() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 20_462_000 picoseconds.
Weight::from_parts(20_720_000, 0)
}
}
// 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_allow_death() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `3593`
// Minimum execution time: 48_203_000 picoseconds.
Weight::from_parts(48_834_000, 3593)
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn transfer_keep_alive() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `3593`
// Minimum execution time: 38_647_000 picoseconds.
Weight::from_parts(39_051_000, 3593)
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn force_set_balance_creating() -> Weight {
// Proof Size summary in bytes:
// Measured: `52`
// Estimated: `3593`
// Minimum execution time: 12_191_000 picoseconds.
Weight::from_parts(12_547_000, 3593)
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn force_set_balance_killing() -> Weight {
// Proof Size summary in bytes:
// Measured: `52`
// Estimated: `3593`
// Minimum execution time: 18_636_000 picoseconds.
Weight::from_parts(19_206_000, 3593)
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `System::Account` (r:2 w:2)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn force_transfer() -> Weight {
// Proof Size summary in bytes:
// Measured: `52`
// Estimated: `6196`
// Minimum execution time: 49_073_000 picoseconds.
Weight::from_parts(49_519_000, 6196)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(2_u64))
}
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn transfer_all() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `3593`
// Minimum execution time: 47_572_000 picoseconds.
Weight::from_parts(48_209_000, 3593)
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn force_unreserve() -> Weight {
// Proof Size summary in bytes:
// Measured: `52`
// Estimated: `3593`
// Minimum execution time: 15_290_000 picoseconds.
Weight::from_parts(15_515_000, 3593)
.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: 14_546_000 picoseconds.
Weight::from_parts(14_674_000, 990)
// Standard Error: 11_734
.saturating_add(Weight::from_parts(14_648_188, 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()))
}
fn force_adjust_total_issuance() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 5_096_000 picoseconds.
Weight::from_parts(5_351_000, 0)
}
fn burn_allow_death() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 29_641_000 picoseconds.
Weight::from_parts(30_219_000, 0)
}
fn burn_keep_alive() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 20_462_000 picoseconds.
Weight::from_parts(20_720_000, 0)
}
}