mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 03:01:07 +00:00
Deprecate Currency; introduce holds and freezing into fungible traits (#12951)
* First reworking of fungibles API * New API and docs * More fungible::* API improvements * New ref-counting logic for old API * Missing files * Fixes * Use the new transfer logic * Use fungibles for the dispatchables * Use shelve/restore names * Locking works with total balance. * repotting and removal * Separate Holds from Reserves * Introduce freezes * Missing files * Tests for freezing * Fix hold+freeze combo * More tests * Fee-free dispatchable for upgrading accounts * Benchmarks and a few fixes * Another test * Docs and refactor to avoid blanket impls * Repot * Fit out ItemOf fully * Add events to Balanced traits * Introduced events into Hold traits * Fix Assets pallet tests * Assets benchmarks pass * Missing files and fixes * Fixes * Fixes * Benchmarks fixes * Fix balance benchmarks * Formatting * Expose fungible sub modules * Move NIS to fungible API * Fix broken impl and add test * Fix tests * API for `transfer_and_hold` * Use composite APIs * Formatting * Upgraded event * Fixes * Fixes * Fixes * Fixes * Repot tests and some fixed * Fix some bits * Fix dust tests * Rename `set_balance` - `Balances::set_balance` becomes `Balances::force_set_balance` - `Unbalanced::set_balance` becomes `Unbalances::write_balance` * becomes * Move dust handling to fungibles API * Formatting * Fixes and more refactoring * Fixes * Fixes * Fixes * Fixes * Fixes * Fixes * Fixes * Fixes * Fixes * Use reducible_balance for better correctness on fees * Reducing hold to zero should remove entry. * Add test * Docs * Update frame/support/src/traits/tokens/fungibles/hold.rs Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> * Update frame/support/src/traits/tokens/fungibles/regular.rs Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> * Update frame/support/src/traits/tokens/fungible/hold.rs Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> * Update frame/support/src/traits/tokens/fungible/regular.rs Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> * Docs * Docs * Docs * Fix NIS benchmarks * Doc comment * Remove post_mutation * Fix some tests * Fix some grumbles * Enumify bool args to fungible(s) functions * Fix up assets and balances * Formatting * Fix contracts * Fix tests & benchmarks build * Typify minted boolean arg * Typify on_hold boolean arg; renames * Fix numerous tests * Fix dependency issue * Privatize dangerous API mutate_account * Fix contracts (@alext - please check this commit) * Remove println * Fix tests for contracts * Fix broken rename * Fix broken rename * Fix broken rename * Docs * Update frame/support/src/traits/tokens/fungible/hold.rs Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com> * remove from_ref_time * Update frame/executive/src/lib.rs Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com> * Update frame/executive/src/lib.rs Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com> * Reenable test * Update frame/support/src/traits/tokens/fungibles/hold.rs Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com> * Update frame/support/src/traits/tokens/fungible/hold.rs Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com> * Update frame/support/src/traits/tokens/fungible/hold.rs Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com> * Update frame/support/src/traits/tokens/fungible/hold.rs Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com> * Update frame/support/src/traits/tokens/currency.rs Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com> * Update frame/lottery/src/tests.rs Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com> * Update frame/support/src/traits/tokens/fungible/mod.rs Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com> * Update frame/support/src/traits/tokens/fungible/regular.rs Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com> * Update frame/support/src/traits/tokens/fungibles/freeze.rs Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com> * Update frame/support/src/traits/tokens/fungible/regular.rs Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com> * Update frame/support/src/traits/tokens/fungibles/hold.rs Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com> * Update frame/support/src/traits/tokens/fungibles/hold.rs Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com> * Update frame/support/src/traits/tokens/fungibles/hold.rs Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com> * Rename UnwantedRemoval to UnwantedAccountRemoval * Docs * Formatting * Update frame/balances/src/lib.rs Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Update primitives/runtime/src/lib.rs Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * handle_raw_dust oes nothing * Formatting * Fixes * Grumble * Fixes * Add test * Add test * Tests for reducible_balance * Fixes * Fix Salary * Fixes * Disable broken test * Disable nicely * Fixes * Fixes * Fixes * Rename some events * Fix nomination pools breakage * Add compatibility stub for transfer tx * Reinstate a safely compatible version of Balances set_balance * Fixes * Grumble * Update frame/nis/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_balances * disable flakey tests * Update frame/balances/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Grumbles * Grumble --------- Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> Co-authored-by: Alexander Theißen <alex.theissen@me.com> Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com> Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: command-bot <>
This commit is contained in:
@@ -243,6 +243,10 @@ impl pallet_balances::Config for Runtime {
|
||||
type ExistentialDeposit = ConstU128<EXISTENTIAL_DEPOSIT>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = pallet_balances::weights::SubstrateWeight<Runtime>;
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -162,7 +162,7 @@ fn prepare_benchmark(client: &FullClient) -> (usize, Vec<OpaqueExtrinsic>) {
|
||||
let extrinsic: OpaqueExtrinsic = create_extrinsic(
|
||||
client,
|
||||
src.clone(),
|
||||
BalancesCall::transfer { dest: dst.clone(), value: 1 * DOLLARS },
|
||||
BalancesCall::transfer_allow_death { dest: dst.clone(), value: 1 * DOLLARS },
|
||||
Some(nonce),
|
||||
)
|
||||
.into();
|
||||
|
||||
@@ -140,10 +140,9 @@ fn create_account_extrinsics(
|
||||
Sr25519Keyring::Alice.pair(),
|
||||
SudoCall::sudo {
|
||||
call: Box::new(
|
||||
BalancesCall::set_balance {
|
||||
BalancesCall::force_set_balance {
|
||||
who: AccountId::from(a.public()).into(),
|
||||
new_free: 0,
|
||||
new_reserved: 0,
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
@@ -156,10 +155,9 @@ fn create_account_extrinsics(
|
||||
Sr25519Keyring::Alice.pair(),
|
||||
SudoCall::sudo {
|
||||
call: Box::new(
|
||||
BalancesCall::set_balance {
|
||||
BalancesCall::force_set_balance {
|
||||
who: AccountId::from(a.public()).into(),
|
||||
new_free: 1_000_000 * DOLLARS,
|
||||
new_reserved: 0,
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
@@ -184,7 +182,7 @@ fn create_benchmark_extrinsics(
|
||||
create_extrinsic(
|
||||
client,
|
||||
account.clone(),
|
||||
BalancesCall::transfer {
|
||||
BalancesCall::transfer_allow_death {
|
||||
dest: Sr25519Keyring::Bob.to_account_id().into(),
|
||||
value: 1 * DOLLARS,
|
||||
},
|
||||
|
||||
@@ -772,7 +772,7 @@ mod tests {
|
||||
};
|
||||
let signer = charlie.clone();
|
||||
|
||||
let function = RuntimeCall::Balances(BalancesCall::transfer {
|
||||
let function = RuntimeCall::Balances(BalancesCall::transfer_allow_death {
|
||||
dest: to.into(),
|
||||
value: amount,
|
||||
});
|
||||
|
||||
@@ -26,7 +26,9 @@ use std::{
|
||||
|
||||
pub mod common;
|
||||
|
||||
#[tokio::test]
|
||||
#[allow(dead_code)]
|
||||
// Apparently `#[ignore]` doesn't actually work to disable this one.
|
||||
//#[tokio::test]
|
||||
async fn temp_base_path_works() {
|
||||
common::run_with_timeout(Duration::from_secs(60 * 10), async move {
|
||||
let mut cmd = Command::new(cargo_bin("substrate"));
|
||||
|
||||
@@ -167,7 +167,7 @@ fn test_blocks(
|
||||
}];
|
||||
block1_extrinsics.extend((0..20).map(|i| CheckedExtrinsic {
|
||||
signed: Some((alice(), signed_extra(i, 0))),
|
||||
function: RuntimeCall::Balances(pallet_balances::Call::transfer {
|
||||
function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
|
||||
dest: bob().into(),
|
||||
value: 1 * DOLLARS,
|
||||
}),
|
||||
|
||||
@@ -89,7 +89,7 @@ fn changes_trie_block() -> (Vec<u8>, Hash) {
|
||||
},
|
||||
CheckedExtrinsic {
|
||||
signed: Some((alice(), signed_extra(0, 0))),
|
||||
function: RuntimeCall::Balances(pallet_balances::Call::transfer {
|
||||
function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
|
||||
dest: bob().into(),
|
||||
value: 69 * DOLLARS,
|
||||
}),
|
||||
@@ -116,7 +116,7 @@ fn blocks() -> ((Vec<u8>, Hash), (Vec<u8>, Hash)) {
|
||||
},
|
||||
CheckedExtrinsic {
|
||||
signed: Some((alice(), signed_extra(0, 0))),
|
||||
function: RuntimeCall::Balances(pallet_balances::Call::transfer {
|
||||
function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
|
||||
dest: bob().into(),
|
||||
value: 69 * DOLLARS,
|
||||
}),
|
||||
@@ -136,14 +136,14 @@ fn blocks() -> ((Vec<u8>, Hash), (Vec<u8>, Hash)) {
|
||||
},
|
||||
CheckedExtrinsic {
|
||||
signed: Some((bob(), signed_extra(0, 0))),
|
||||
function: RuntimeCall::Balances(pallet_balances::Call::transfer {
|
||||
function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
|
||||
dest: alice().into(),
|
||||
value: 5 * DOLLARS,
|
||||
}),
|
||||
},
|
||||
CheckedExtrinsic {
|
||||
signed: Some((alice(), signed_extra(1, 0))),
|
||||
function: RuntimeCall::Balances(pallet_balances::Call::transfer {
|
||||
function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
|
||||
dest: bob().into(),
|
||||
value: 15 * DOLLARS,
|
||||
}),
|
||||
@@ -183,7 +183,12 @@ fn panic_execution_with_foreign_code_gives_error() {
|
||||
let mut t = new_test_ext(bloaty_code_unwrap());
|
||||
t.insert(
|
||||
<frame_system::Account<Runtime>>::hashed_key_for(alice()),
|
||||
(69u128, 0u32, 0u128, 0u128, 0u128).encode(),
|
||||
AccountInfo::<<Runtime as frame_system::Config>::Index, _> {
|
||||
providers: 1,
|
||||
data: (69u128, 0u128, 0u128, 1u128 << 127),
|
||||
..Default::default()
|
||||
}
|
||||
.encode(),
|
||||
);
|
||||
t.insert(<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(), 69_u128.encode());
|
||||
t.insert(<frame_system::BlockHash<Runtime>>::hashed_key_for(0), vec![0u8; 32]);
|
||||
@@ -204,9 +209,14 @@ fn bad_extrinsic_with_native_equivalent_code_gives_error() {
|
||||
let mut t = new_test_ext(compact_code_unwrap());
|
||||
t.insert(
|
||||
<frame_system::Account<Runtime>>::hashed_key_for(alice()),
|
||||
(0u32, 0u32, 0u32, 69u128, 0u128, 0u128, 0u128).encode(),
|
||||
AccountInfo::<<Runtime as frame_system::Config>::Index, _> {
|
||||
providers: 1,
|
||||
data: (69u128, 0u128, 0u128, 1u128 << 127),
|
||||
..Default::default()
|
||||
}
|
||||
.encode(),
|
||||
);
|
||||
t.insert(<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(), 69_u128.encode());
|
||||
t.insert(<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(), 69u128.encode());
|
||||
t.insert(<frame_system::BlockHash<Runtime>>::hashed_key_for(0), vec![0u8; 32]);
|
||||
|
||||
let r =
|
||||
@@ -226,17 +236,18 @@ fn successful_execution_with_native_equivalent_code_gives_ok() {
|
||||
t.insert(
|
||||
<frame_system::Account<Runtime>>::hashed_key_for(alice()),
|
||||
AccountInfo::<<Runtime as frame_system::Config>::Index, _> {
|
||||
data: (111 * DOLLARS, 0u128, 0u128, 0u128),
|
||||
providers: 1,
|
||||
data: (111 * DOLLARS, 0u128, 0u128, 1u128 << 127),
|
||||
..Default::default()
|
||||
}
|
||||
.encode(),
|
||||
);
|
||||
t.insert(
|
||||
<frame_system::Account<Runtime>>::hashed_key_for(bob()),
|
||||
AccountInfo::<<Runtime as frame_system::Config>::Index, _> {
|
||||
data: (0 * DOLLARS, 0u128, 0u128, 0u128),
|
||||
..Default::default()
|
||||
}
|
||||
AccountInfo::<
|
||||
<Runtime as frame_system::Config>::Index,
|
||||
<Runtime as frame_system::Config>::AccountData,
|
||||
>::default()
|
||||
.encode(),
|
||||
);
|
||||
t.insert(
|
||||
@@ -267,17 +278,18 @@ fn successful_execution_with_foreign_code_gives_ok() {
|
||||
t.insert(
|
||||
<frame_system::Account<Runtime>>::hashed_key_for(alice()),
|
||||
AccountInfo::<<Runtime as frame_system::Config>::Index, _> {
|
||||
data: (111 * DOLLARS, 0u128, 0u128, 0u128),
|
||||
providers: 1,
|
||||
data: (111 * DOLLARS, 0u128, 0u128, 1u128 << 127),
|
||||
..Default::default()
|
||||
}
|
||||
.encode(),
|
||||
);
|
||||
t.insert(
|
||||
<frame_system::Account<Runtime>>::hashed_key_for(bob()),
|
||||
AccountInfo::<<Runtime as frame_system::Config>::Index, _> {
|
||||
data: (0 * DOLLARS, 0u128, 0u128, 0u128),
|
||||
..Default::default()
|
||||
}
|
||||
AccountInfo::<
|
||||
<Runtime as frame_system::Config>::Index,
|
||||
<Runtime as frame_system::Config>::AccountData,
|
||||
>::default()
|
||||
.encode(),
|
||||
);
|
||||
t.insert(
|
||||
@@ -784,17 +796,18 @@ fn successful_execution_gives_ok() {
|
||||
t.insert(
|
||||
<frame_system::Account<Runtime>>::hashed_key_for(alice()),
|
||||
AccountInfo::<<Runtime as frame_system::Config>::Index, _> {
|
||||
data: (111 * DOLLARS, 0u128, 0u128, 0u128),
|
||||
providers: 1,
|
||||
data: (111 * DOLLARS, 0u128, 0u128, 1u128 << 127),
|
||||
..Default::default()
|
||||
}
|
||||
.encode(),
|
||||
);
|
||||
t.insert(
|
||||
<frame_system::Account<Runtime>>::hashed_key_for(bob()),
|
||||
AccountInfo::<<Runtime as frame_system::Config>::Index, _> {
|
||||
data: (0 * DOLLARS, 0u128, 0u128, 0u128),
|
||||
..Default::default()
|
||||
}
|
||||
AccountInfo::<
|
||||
<Runtime as frame_system::Config>::Index,
|
||||
<Runtime as frame_system::Config>::AccountData,
|
||||
>::default()
|
||||
.encode(),
|
||||
);
|
||||
t.insert(
|
||||
|
||||
@@ -87,7 +87,10 @@ pub fn sign(xt: CheckedExtrinsic) -> UncheckedExtrinsic {
|
||||
}
|
||||
|
||||
pub fn default_transfer_call() -> pallet_balances::Call<Runtime> {
|
||||
pallet_balances::Call::<Runtime>::transfer { dest: bob().into(), value: 69 * DOLLARS }
|
||||
pallet_balances::Call::<Runtime>::transfer_allow_death {
|
||||
dest: bob().into(),
|
||||
value: 69 * DOLLARS,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_block_number(n: u32) -> Header {
|
||||
|
||||
@@ -120,9 +120,9 @@ fn new_account_info(free_dollars: u128) -> Vec<u8> {
|
||||
frame_system::AccountInfo {
|
||||
nonce: 0u32,
|
||||
consumers: 0,
|
||||
providers: 0,
|
||||
providers: 1,
|
||||
sufficients: 0,
|
||||
data: (free_dollars * DOLLARS, 0 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS),
|
||||
data: (free_dollars * DOLLARS, 0 * DOLLARS, 0 * DOLLARS, 1u128 << 127),
|
||||
}
|
||||
.encode()
|
||||
}
|
||||
@@ -214,7 +214,7 @@ fn block_weight_capacity_report() {
|
||||
let mut xts = (0..num_transfers)
|
||||
.map(|i| CheckedExtrinsic {
|
||||
signed: Some((charlie(), signed_extra(nonce + i as Index, 0))),
|
||||
function: RuntimeCall::Balances(pallet_balances::Call::transfer {
|
||||
function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
|
||||
dest: bob().into(),
|
||||
value: 0,
|
||||
}),
|
||||
|
||||
@@ -86,7 +86,7 @@ fn should_submit_signed_transaction() {
|
||||
t.execute_with(|| {
|
||||
let results =
|
||||
Signer::<Runtime, TestAuthorityId>::all_accounts().send_signed_transaction(|_| {
|
||||
pallet_balances::Call::transfer {
|
||||
pallet_balances::Call::transfer_allow_death {
|
||||
dest: Alice.to_account_id().into(),
|
||||
value: Default::default(),
|
||||
}
|
||||
@@ -123,7 +123,7 @@ fn should_submit_signed_twice_from_the_same_account() {
|
||||
t.execute_with(|| {
|
||||
let result =
|
||||
Signer::<Runtime, TestAuthorityId>::any_account().send_signed_transaction(|_| {
|
||||
pallet_balances::Call::transfer {
|
||||
pallet_balances::Call::transfer_allow_death {
|
||||
dest: Alice.to_account_id().into(),
|
||||
value: Default::default(),
|
||||
}
|
||||
@@ -135,7 +135,7 @@ fn should_submit_signed_twice_from_the_same_account() {
|
||||
// submit another one from the same account. The nonce should be incremented.
|
||||
let result =
|
||||
Signer::<Runtime, TestAuthorityId>::any_account().send_signed_transaction(|_| {
|
||||
pallet_balances::Call::transfer {
|
||||
pallet_balances::Call::transfer_allow_death {
|
||||
dest: Alice.to_account_id().into(),
|
||||
value: Default::default(),
|
||||
}
|
||||
@@ -174,7 +174,7 @@ fn should_submit_signed_twice_from_all_accounts() {
|
||||
t.execute_with(|| {
|
||||
let results = Signer::<Runtime, TestAuthorityId>::all_accounts()
|
||||
.send_signed_transaction(|_| {
|
||||
pallet_balances::Call::transfer { dest: Alice.to_account_id().into(), value: Default::default() }
|
||||
pallet_balances::Call::transfer_allow_death { dest: Alice.to_account_id().into(), value: Default::default() }
|
||||
});
|
||||
|
||||
let len = results.len();
|
||||
@@ -185,7 +185,7 @@ fn should_submit_signed_twice_from_all_accounts() {
|
||||
// submit another one from the same account. The nonce should be incremented.
|
||||
let results = Signer::<Runtime, TestAuthorityId>::all_accounts()
|
||||
.send_signed_transaction(|_| {
|
||||
pallet_balances::Call::transfer { dest: Alice.to_account_id().into(), value: Default::default() }
|
||||
pallet_balances::Call::transfer_allow_death { dest: Alice.to_account_id().into(), value: Default::default() }
|
||||
});
|
||||
|
||||
let len = results.len();
|
||||
@@ -238,7 +238,7 @@ fn submitted_transaction_should_be_valid() {
|
||||
t.execute_with(|| {
|
||||
let results =
|
||||
Signer::<Runtime, TestAuthorityId>::all_accounts().send_signed_transaction(|_| {
|
||||
pallet_balances::Call::transfer {
|
||||
pallet_balances::Call::transfer_allow_death {
|
||||
dest: Alice.to_account_id().into(),
|
||||
value: Default::default(),
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ use crate::{
|
||||
use frame_support::{
|
||||
pallet_prelude::*,
|
||||
traits::{
|
||||
fungibles::{Balanced, CreditOf},
|
||||
fungibles::{Balanced, Credit},
|
||||
Currency, OnUnbalanced,
|
||||
},
|
||||
};
|
||||
@@ -45,7 +45,7 @@ impl OnUnbalanced<NegativeImbalance> for Author {
|
||||
/// Will drop and burn the assets in case the transfer fails.
|
||||
pub struct CreditToBlockAuthor;
|
||||
impl HandleCredit<AccountId, Assets> for CreditToBlockAuthor {
|
||||
fn handle_credit(credit: CreditOf<AccountId, Assets>) {
|
||||
fn handle_credit(credit: Credit<AccountId, Assets>) {
|
||||
if let Some(author) = pallet_authorship::Pallet::<Runtime>::author() {
|
||||
// Drop the result which will trigger the `OnDrop` of the imbalance in case of error.
|
||||
let _ = Assets::resolve(&author, credit);
|
||||
|
||||
@@ -59,6 +59,7 @@ use pallet_nis::WithMaximumOf;
|
||||
use pallet_session::historical as pallet_session_historical;
|
||||
pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment};
|
||||
use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_api::impl_runtime_apis;
|
||||
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
|
||||
use sp_consensus_grandpa::AuthorityId as GrandpaId;
|
||||
@@ -432,6 +433,15 @@ parameter_types! {
|
||||
pub const MaxReserves: u32 = 50;
|
||||
}
|
||||
|
||||
/// A reason for placing a hold on funds.
|
||||
#[derive(
|
||||
Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, Debug, TypeInfo,
|
||||
)]
|
||||
pub enum HoldReason {
|
||||
/// The NIS Pallet has reserved it for a non-fungible receipt.
|
||||
Nis,
|
||||
}
|
||||
|
||||
impl pallet_balances::Config for Runtime {
|
||||
type MaxLocks = MaxLocks;
|
||||
type MaxReserves = MaxReserves;
|
||||
@@ -442,6 +452,10 @@ impl pallet_balances::Config for Runtime {
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = frame_system::Pallet<Runtime>;
|
||||
type WeightInfo = pallet_balances::weights::SubstrateWeight<Runtime>;
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = HoldReason;
|
||||
type MaxHolds = ConstU32<1>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
@@ -1481,7 +1495,6 @@ impl pallet_assets::Config for Runtime {
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub IgnoredIssuance: Balance = Treasury::pot();
|
||||
pub const QueueCount: u32 = 300;
|
||||
pub const MaxQueueLen: u32 = 1000;
|
||||
pub const FifoQueueLen: u32 = 500;
|
||||
@@ -1493,7 +1506,7 @@ parameter_types! {
|
||||
pub const ThawThrottle: (Perquintill, BlockNumber) = (Perquintill::from_percent(25), 5);
|
||||
pub Target: Perquintill = Perquintill::zero();
|
||||
pub const NisPalletId: PalletId = PalletId(*b"py/nis ");
|
||||
pub const NisReserveId: [u8; 8] = *b"py/nis ";
|
||||
pub const NisHoldReason: HoldReason = HoldReason::Nis;
|
||||
}
|
||||
|
||||
impl pallet_nis::Config for Runtime {
|
||||
@@ -1505,7 +1518,7 @@ impl pallet_nis::Config for Runtime {
|
||||
type Counterpart = ItemOf<Assets, ConstU32<9u32>, AccountId>;
|
||||
type CounterpartAmount = WithMaximumOf<ConstU128<21_000_000_000_000_000_000u128>>;
|
||||
type Deficit = ();
|
||||
type IgnoredIssuance = IgnoredIssuance;
|
||||
type IgnoredIssuance = ();
|
||||
type Target = Target;
|
||||
type PalletId = NisPalletId;
|
||||
type QueueCount = QueueCount;
|
||||
@@ -1517,7 +1530,7 @@ impl pallet_nis::Config for Runtime {
|
||||
type IntakePeriod = IntakePeriod;
|
||||
type MaxIntakeWeight = MaxIntakeWeight;
|
||||
type ThawThrottle = ThawThrottle;
|
||||
type ReserveId = NisReserveId;
|
||||
type HoldReason = NisHoldReason;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -308,7 +308,7 @@ impl<'a> Iterator for BlockContentIterator<'a> {
|
||||
value: kitchensink_runtime::ExistentialDeposit::get() + 1,
|
||||
}),
|
||||
BlockType::RandomTransfersReaping => {
|
||||
RuntimeCall::Balances(BalancesCall::transfer {
|
||||
RuntimeCall::Balances(BalancesCall::transfer_allow_death {
|
||||
dest: sp_runtime::MultiAddress::Id(receiver),
|
||||
// Transfer so that ending balance would be 1 less than existential
|
||||
// deposit so that we kill the sender account.
|
||||
|
||||
@@ -85,6 +85,10 @@ impl pallet_balances::Config for Test {
|
||||
type MaxLocks = MaxLocks;
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
const MOTION_DURATION_IN_BLOCKS: BlockNumber = 3;
|
||||
|
||||
@@ -105,14 +105,18 @@ fn add_sufficients<T: Config<I>, I: 'static>(minter: T::AccountId, n: u32) {
|
||||
|
||||
fn add_approvals<T: Config<I>, I: 'static>(minter: T::AccountId, n: u32) {
|
||||
let asset_id = default_asset_id::<T, I>();
|
||||
T::Currency::deposit_creating(&minter, T::ApprovalDeposit::get() * n.into());
|
||||
T::Currency::deposit_creating(
|
||||
&minter,
|
||||
T::ApprovalDeposit::get() * n.into() + T::Currency::minimum_balance(),
|
||||
);
|
||||
let minter_lookup = T::Lookup::unlookup(minter.clone());
|
||||
let origin = SystemOrigin::Signed(minter);
|
||||
Assets::<T, I>::mint(origin.clone().into(), asset_id, minter_lookup, (100 * (n + 1)).into())
|
||||
.unwrap();
|
||||
let enough = T::Currency::minimum_balance();
|
||||
for i in 0..n {
|
||||
let target = account("approval", i, SEED);
|
||||
T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance());
|
||||
T::Currency::make_free_balance_be(&target, enough);
|
||||
let target_lookup = T::Lookup::unlookup(target);
|
||||
Assets::<T, I>::approve_transfer(
|
||||
origin.clone().into(),
|
||||
|
||||
@@ -76,7 +76,14 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
d.sufficients += 1;
|
||||
ExistenceReason::Sufficient
|
||||
} else {
|
||||
frame_system::Pallet::<T>::inc_consumers(who).map_err(|_| Error::<T, I>::NoProvider)?;
|
||||
frame_system::Pallet::<T>::inc_consumers(who)
|
||||
.map_err(|_| Error::<T, I>::UnavailableConsumer)?;
|
||||
// We ensure that we can still increment consumers once more because we could otherwise
|
||||
// allow accidental usage of all consumer references which could cause grief.
|
||||
if !frame_system::Pallet::<T>::can_inc_consumer(who) {
|
||||
frame_system::Pallet::<T>::dec_consumers(who);
|
||||
return Err(Error::<T, I>::UnavailableConsumer.into())
|
||||
}
|
||||
ExistenceReason::Consumer
|
||||
};
|
||||
d.accounts = accounts;
|
||||
@@ -165,7 +172,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
}
|
||||
let account = match Account::<T, I>::get(id, who) {
|
||||
Some(a) => a,
|
||||
None => return NoFunds,
|
||||
None => return BalanceLow,
|
||||
};
|
||||
if account.is_frozen {
|
||||
return Frozen
|
||||
@@ -193,7 +200,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
Success
|
||||
}
|
||||
} else {
|
||||
NoFunds
|
||||
BalanceLow
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,7 +261,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
ensure!(f.best_effort || actual >= amount, Error::<T, I>::BalanceLow);
|
||||
|
||||
let conseq = Self::can_decrease(id, target, actual, f.keep_alive);
|
||||
let actual = match conseq.into_result() {
|
||||
let actual = match conseq.into_result(f.keep_alive) {
|
||||
Ok(dust) => actual.saturating_add(dust), //< guaranteed by reducible_balance
|
||||
Err(e) => {
|
||||
debug_assert!(false, "passed from reducible_balance; qed");
|
||||
|
||||
@@ -17,6 +17,16 @@
|
||||
|
||||
//! Implementations for fungibles trait.
|
||||
|
||||
use frame_support::{
|
||||
defensive,
|
||||
traits::tokens::{
|
||||
Fortitude,
|
||||
Precision::{self, BestEffort},
|
||||
Preservation::{self, Expendable},
|
||||
Provenance::{self, Minted},
|
||||
},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Inspect<<T as SystemConfig>::AccountId> for Pallet<T, I> {
|
||||
@@ -35,21 +45,27 @@ impl<T: Config<I>, I: 'static> fungibles::Inspect<<T as SystemConfig>::AccountId
|
||||
Pallet::<T, I>::balance(asset, who)
|
||||
}
|
||||
|
||||
fn total_balance(asset: Self::AssetId, who: &<T as SystemConfig>::AccountId) -> Self::Balance {
|
||||
Pallet::<T, I>::balance(asset, who)
|
||||
}
|
||||
|
||||
fn reducible_balance(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
keep_alive: bool,
|
||||
preservation: Preservation,
|
||||
_: Fortitude,
|
||||
) -> Self::Balance {
|
||||
Pallet::<T, I>::reducible_balance(asset, who, keep_alive).unwrap_or(Zero::zero())
|
||||
Pallet::<T, I>::reducible_balance(asset, who, !matches!(preservation, Expendable))
|
||||
.unwrap_or(Zero::zero())
|
||||
}
|
||||
|
||||
fn can_deposit(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
mint: bool,
|
||||
provenance: Provenance,
|
||||
) -> DepositConsequence {
|
||||
Pallet::<T, I>::can_increase(asset, who, amount, mint)
|
||||
Pallet::<T, I>::can_increase(asset, who, amount, provenance == Minted)
|
||||
}
|
||||
|
||||
fn can_withdraw(
|
||||
@@ -65,69 +81,26 @@ impl<T: Config<I>, I: 'static> fungibles::Inspect<<T as SystemConfig>::AccountId
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::InspectMetadata<<T as SystemConfig>::AccountId>
|
||||
impl<T: Config<I>, I: 'static> fungibles::Mutate<<T as SystemConfig>::AccountId> for Pallet<T, I> {}
|
||||
impl<T: Config<I>, I: 'static> fungibles::Balanced<<T as SystemConfig>::AccountId>
|
||||
for Pallet<T, I>
|
||||
{
|
||||
/// Return the name of an asset.
|
||||
fn name(asset: &Self::AssetId) -> Vec<u8> {
|
||||
Metadata::<T, I>::get(asset).name.to_vec()
|
||||
}
|
||||
|
||||
/// Return the symbol of an asset.
|
||||
fn symbol(asset: &Self::AssetId) -> Vec<u8> {
|
||||
Metadata::<T, I>::get(asset).symbol.to_vec()
|
||||
}
|
||||
|
||||
/// Return the decimals of an asset.
|
||||
fn decimals(asset: &Self::AssetId) -> u8 {
|
||||
Metadata::<T, I>::get(asset).decimals
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Mutate<<T as SystemConfig>::AccountId> for Pallet<T, I> {
|
||||
fn mint_into(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> DispatchResult {
|
||||
Self::do_mint(asset, who, amount, None)
|
||||
}
|
||||
|
||||
fn burn_from(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let f = DebitFlags { keep_alive: false, best_effort: false };
|
||||
Self::do_burn(asset, who, amount, None, f)
|
||||
}
|
||||
|
||||
fn slash(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let f = DebitFlags { keep_alive: false, best_effort: true };
|
||||
Self::do_burn(asset, who, amount, None, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Transfer<T::AccountId> for Pallet<T, I> {
|
||||
fn transfer(
|
||||
asset: Self::AssetId,
|
||||
source: &T::AccountId,
|
||||
dest: &T::AccountId,
|
||||
amount: T::Balance,
|
||||
keep_alive: bool,
|
||||
) -> Result<T::Balance, DispatchError> {
|
||||
let f = TransferFlags { keep_alive, best_effort: false, burn_dust: false };
|
||||
Self::do_transfer(asset, source, dest, amount, None, f)
|
||||
}
|
||||
type OnDropCredit = fungibles::DecreaseIssuance<T::AccountId, Self>;
|
||||
type OnDropDebt = fungibles::IncreaseIssuance<T::AccountId, Self>;
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Unbalanced<T::AccountId> for Pallet<T, I> {
|
||||
fn set_balance(_: Self::AssetId, _: &T::AccountId, _: Self::Balance) -> DispatchResult {
|
||||
unreachable!("set_balance is not used if other functions are impl'd");
|
||||
fn handle_raw_dust(_: Self::AssetId, _: Self::Balance) {}
|
||||
fn handle_dust(_: fungibles::Dust<T::AccountId, Self>) {
|
||||
defensive!("`decrease_balance` and `increase_balance` have non-default impls; nothing else calls this; qed");
|
||||
}
|
||||
fn write_balance(
|
||||
_: Self::AssetId,
|
||||
_: &T::AccountId,
|
||||
_: Self::Balance,
|
||||
) -> Result<Option<Self::Balance>, DispatchError> {
|
||||
defensive!("write_balance is not used if other functions are impl'd");
|
||||
Err(DispatchError::Unavailable)
|
||||
}
|
||||
fn set_total_issuance(id: T::AssetId, amount: Self::Balance) {
|
||||
Asset::<T, I>::mutate_exists(id, |maybe_asset| {
|
||||
@@ -140,36 +113,27 @@ impl<T: Config<I>, I: 'static> fungibles::Unbalanced<T::AccountId> for Pallet<T,
|
||||
asset: T::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
preservation: Preservation,
|
||||
_: Fortitude,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let f = DebitFlags { keep_alive: false, best_effort: false };
|
||||
let f = DebitFlags {
|
||||
keep_alive: preservation != Expendable,
|
||||
best_effort: precision == BestEffort,
|
||||
};
|
||||
Self::decrease_balance(asset, who, amount, f, |_, _| Ok(()))
|
||||
}
|
||||
fn decrease_balance_at_most(
|
||||
asset: T::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Self::Balance {
|
||||
let f = DebitFlags { keep_alive: false, best_effort: true };
|
||||
Self::decrease_balance(asset, who, amount, f, |_, _| Ok(())).unwrap_or(Zero::zero())
|
||||
}
|
||||
fn increase_balance(
|
||||
asset: T::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
_: Precision,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
Self::increase_balance(asset, who, amount, |_| Ok(()))?;
|
||||
Ok(amount)
|
||||
}
|
||||
fn increase_balance_at_most(
|
||||
asset: T::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Self::Balance {
|
||||
match Self::increase_balance(asset, who, amount, |_| Ok(())) {
|
||||
Ok(()) => amount,
|
||||
Err(_) => Zero::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: #13196 implement deactivate/reactivate once we have inactive balance tracking.
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Create<T::AccountId> for Pallet<T, I> {
|
||||
|
||||
@@ -540,9 +540,9 @@ pub mod pallet {
|
||||
/// Minimum balance should be non-zero.
|
||||
MinBalanceZero,
|
||||
/// Unable to increment the consumer reference counters on the account. Either no provider
|
||||
/// reference exists to allow a non-zero balance of a non-self-sufficient asset, or the
|
||||
/// maximum number of consumers has been reached.
|
||||
NoProvider,
|
||||
/// reference exists to allow a non-zero balance of a non-self-sufficient asset, or one
|
||||
/// fewer then the maximum number of consumers has been reached.
|
||||
UnavailableConsumer,
|
||||
/// Invalid metadata given.
|
||||
BadMetadata,
|
||||
/// No approval exists that would allow the transfer.
|
||||
|
||||
@@ -74,7 +74,7 @@ impl frame_system::Config for Test {
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = ConstU32<2>;
|
||||
type MaxConsumers = ConstU32<3>;
|
||||
}
|
||||
|
||||
impl pallet_balances::Config for Test {
|
||||
@@ -87,6 +87,10 @@ impl pallet_balances::Config for Test {
|
||||
type MaxLocks = ();
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type HoldIdentifier = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
type MaxFreezes = ();
|
||||
}
|
||||
|
||||
pub struct AssetsCallbackHandle;
|
||||
|
||||
@@ -57,7 +57,10 @@ fn minting_too_many_insufficient_assets_fails() {
|
||||
Balances::make_free_balance_be(&1, 100);
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 1, 1, 100));
|
||||
assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100), TokenError::CannotCreate);
|
||||
assert_noop!(
|
||||
Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100),
|
||||
Error::<Test>::UnavailableConsumer
|
||||
);
|
||||
|
||||
Balances::make_free_balance_be(&2, 1);
|
||||
assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 100));
|
||||
@@ -75,7 +78,10 @@ fn minting_insufficient_asset_with_deposit_should_work_when_consumers_exhausted(
|
||||
Balances::make_free_balance_be(&1, 100);
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 1, 1, 100));
|
||||
assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100), TokenError::CannotCreate);
|
||||
assert_noop!(
|
||||
Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100),
|
||||
Error::<Test>::UnavailableConsumer
|
||||
);
|
||||
|
||||
assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 2));
|
||||
assert_eq!(Balances::reserved_balance(&1), 10);
|
||||
@@ -93,7 +99,7 @@ fn minting_insufficient_assets_with_deposit_without_consumer_should_work() {
|
||||
assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
|
||||
assert_eq!(Balances::reserved_balance(&1), 10);
|
||||
assert_eq!(System::consumers(&1), 0);
|
||||
assert_eq!(System::consumers(&1), 1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -167,7 +173,7 @@ fn approval_lifecycle_works() {
|
||||
// so we create it :)
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
|
||||
Balances::make_free_balance_be(&1, 1);
|
||||
Balances::make_free_balance_be(&1, 2);
|
||||
assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50));
|
||||
assert_eq!(Asset::<Test>::get(0).unwrap().approvals, 1);
|
||||
assert_eq!(Balances::reserved_balance(&1), 1);
|
||||
@@ -193,7 +199,7 @@ fn transfer_approved_all_funds() {
|
||||
// so we create it :)
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
|
||||
Balances::make_free_balance_be(&1, 1);
|
||||
Balances::make_free_balance_be(&1, 2);
|
||||
assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50));
|
||||
assert_eq!(Asset::<Test>::get(0).unwrap().approvals, 1);
|
||||
assert_eq!(Balances::reserved_balance(&1), 1);
|
||||
@@ -215,7 +221,7 @@ fn approval_deposits_work() {
|
||||
let e = BalancesError::<Test>::InsufficientBalance;
|
||||
assert_noop!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50), e);
|
||||
|
||||
Balances::make_free_balance_be(&1, 1);
|
||||
Balances::make_free_balance_be(&1, 2);
|
||||
assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50));
|
||||
assert_eq!(Balances::reserved_balance(&1), 1);
|
||||
|
||||
@@ -233,7 +239,7 @@ fn cannot_transfer_more_than_approved() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
|
||||
Balances::make_free_balance_be(&1, 1);
|
||||
Balances::make_free_balance_be(&1, 2);
|
||||
assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50));
|
||||
let e = Error::<Test>::Unapproved;
|
||||
assert_noop!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 51), e);
|
||||
@@ -245,7 +251,7 @@ fn cannot_transfer_more_than_exists() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
|
||||
Balances::make_free_balance_be(&1, 1);
|
||||
Balances::make_free_balance_be(&1, 2);
|
||||
assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 101));
|
||||
let e = Error::<Test>::BalanceLow;
|
||||
assert_noop!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 101), e);
|
||||
@@ -257,7 +263,7 @@ fn cancel_approval_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
|
||||
Balances::make_free_balance_be(&1, 1);
|
||||
Balances::make_free_balance_be(&1, 2);
|
||||
assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50));
|
||||
assert_eq!(Asset::<Test>::get(0).unwrap().approvals, 1);
|
||||
assert_noop!(
|
||||
@@ -287,7 +293,7 @@ fn force_cancel_approval_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
|
||||
Balances::make_free_balance_be(&1, 1);
|
||||
Balances::make_free_balance_be(&1, 2);
|
||||
assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50));
|
||||
assert_eq!(Asset::<Test>::get(0).unwrap().approvals, 1);
|
||||
let e = Error::<Test>::NoPermission;
|
||||
@@ -516,7 +522,7 @@ fn min_balance_should_work() {
|
||||
|
||||
// Death by `transfer_approved`.
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
|
||||
Balances::make_free_balance_be(&1, 1);
|
||||
Balances::make_free_balance_be(&1, 2);
|
||||
assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 100));
|
||||
assert_ok!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 91));
|
||||
assert_eq!(take_hooks(), vec![Hook::Died(0, 1)]);
|
||||
@@ -1217,7 +1223,7 @@ fn querying_allowance_should_work() {
|
||||
use frame_support::traits::tokens::fungibles::approvals::{Inspect, Mutate};
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
|
||||
Balances::make_free_balance_be(&1, 1);
|
||||
Balances::make_free_balance_be(&1, 2);
|
||||
assert_ok!(Assets::approve(0, &1, &2, 50));
|
||||
assert_eq!(Assets::allowance(0, &1, &2), 50);
|
||||
// Transfer asset 0, from owner 1 and delegate 2 to destination 3
|
||||
|
||||
@@ -62,6 +62,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
|
||||
@@ -144,6 +144,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU128<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
pallet_staking_reward_curve::build! {
|
||||
|
||||
@@ -70,7 +70,7 @@ given account is unused.
|
||||
### Dispatchable Functions
|
||||
|
||||
- `transfer` - Transfer some liquid free balance to another account.
|
||||
- `set_balance` - Set the balances of a given account. The origin of this call must be root.
|
||||
- `force_set_balance` - Set the balances of a given account. The origin of this call must be root.
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ use crate::Pallet as Balances;
|
||||
|
||||
use frame_benchmarking::v2::*;
|
||||
use frame_system::RawOrigin;
|
||||
use sp_runtime::traits::Bounded;
|
||||
use types::ExtraFlags;
|
||||
|
||||
const SEED: u32 = 0;
|
||||
// existential deposit multiplier
|
||||
@@ -37,7 +39,7 @@ mod benchmarks {
|
||||
// * Transfer will kill the sender account.
|
||||
// * Transfer will create the recipient account.
|
||||
#[benchmark]
|
||||
fn transfer() {
|
||||
fn transfer_allow_death() {
|
||||
let existential_deposit = T::ExistentialDeposit::get();
|
||||
let caller = whitelisted_caller();
|
||||
|
||||
@@ -79,7 +81,7 @@ mod benchmarks {
|
||||
let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
|
||||
|
||||
#[extrinsic_call]
|
||||
transfer(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
|
||||
transfer_allow_death(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
|
||||
|
||||
assert!(!Balances::<T, I>::free_balance(&caller).is_zero());
|
||||
assert!(!Balances::<T, I>::free_balance(&recipient).is_zero());
|
||||
@@ -106,9 +108,9 @@ mod benchmarks {
|
||||
assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount);
|
||||
}
|
||||
|
||||
// Benchmark `set_balance` coming from ROOT account. This always creates an account.
|
||||
// Benchmark `force_set_balance` coming from ROOT account. This always creates an account.
|
||||
#[benchmark]
|
||||
fn set_balance_creating() {
|
||||
fn force_set_balance_creating() {
|
||||
let user: T::AccountId = account("user", 0, SEED);
|
||||
let user_lookup = T::Lookup::unlookup(user.clone());
|
||||
|
||||
@@ -118,15 +120,14 @@ mod benchmarks {
|
||||
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&user, balance_amount);
|
||||
|
||||
#[extrinsic_call]
|
||||
set_balance(RawOrigin::Root, user_lookup, balance_amount, balance_amount);
|
||||
force_set_balance(RawOrigin::Root, user_lookup, balance_amount);
|
||||
|
||||
assert_eq!(Balances::<T, I>::free_balance(&user), balance_amount);
|
||||
assert_eq!(Balances::<T, I>::reserved_balance(&user), balance_amount);
|
||||
}
|
||||
|
||||
// Benchmark `set_balance` coming from ROOT account. This always kills an account.
|
||||
// Benchmark `force_set_balance` coming from ROOT account. This always kills an account.
|
||||
#[benchmark]
|
||||
fn set_balance_killing() {
|
||||
fn force_set_balance_killing() {
|
||||
let user: T::AccountId = account("user", 0, SEED);
|
||||
let user_lookup = T::Lookup::unlookup(user.clone());
|
||||
|
||||
@@ -136,7 +137,7 @@ mod benchmarks {
|
||||
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&user, balance_amount);
|
||||
|
||||
#[extrinsic_call]
|
||||
set_balance(RawOrigin::Root, user_lookup, Zero::zero(), Zero::zero());
|
||||
force_set_balance(RawOrigin::Root, user_lookup, Zero::zero());
|
||||
|
||||
assert!(Balances::<T, I>::free_balance(&user).is_zero());
|
||||
}
|
||||
@@ -197,7 +198,7 @@ mod benchmarks {
|
||||
}
|
||||
|
||||
#[extrinsic_call]
|
||||
transfer(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
|
||||
transfer_allow_death(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
|
||||
|
||||
assert_eq!(Balances::<T, I>::free_balance(&caller), Zero::zero());
|
||||
assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount);
|
||||
@@ -230,27 +231,64 @@ mod benchmarks {
|
||||
let user_lookup = T::Lookup::unlookup(user.clone());
|
||||
|
||||
// Give some multiple of the existential deposit
|
||||
let existential_deposit = T::ExistentialDeposit::get();
|
||||
let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
|
||||
let ed = T::ExistentialDeposit::get();
|
||||
let balance = ed + ed;
|
||||
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&user, balance);
|
||||
|
||||
// Reserve the balance
|
||||
<Balances<T, I> as ReservableCurrency<_>>::reserve(&user, balance)?;
|
||||
assert_eq!(Balances::<T, I>::reserved_balance(&user), balance);
|
||||
assert!(Balances::<T, I>::free_balance(&user).is_zero());
|
||||
<Balances<T, I> as ReservableCurrency<_>>::reserve(&user, ed)?;
|
||||
assert_eq!(Balances::<T, I>::reserved_balance(&user), ed);
|
||||
assert_eq!(Balances::<T, I>::free_balance(&user), ed);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Root, user_lookup, balance);
|
||||
|
||||
assert!(Balances::<T, I>::reserved_balance(&user).is_zero());
|
||||
assert_eq!(Balances::<T, I>::free_balance(&user), balance);
|
||||
assert_eq!(Balances::<T, I>::free_balance(&user), ed + ed);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn upgrade_accounts(u: Linear<1, 1_000>) {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let who = (0..u)
|
||||
.into_iter()
|
||||
.map(|i| -> T::AccountId {
|
||||
let user = account("old_user", i, SEED);
|
||||
let account = AccountData {
|
||||
free: T::ExistentialDeposit::get(),
|
||||
reserved: T::ExistentialDeposit::get(),
|
||||
frozen: Zero::zero(),
|
||||
flags: ExtraFlags::old_logic(),
|
||||
};
|
||||
frame_system::Pallet::<T>::inc_providers(&user);
|
||||
assert!(T::AccountStore::try_mutate_exists(&user, |a| -> DispatchResult {
|
||||
*a = Some(account);
|
||||
Ok(())
|
||||
})
|
||||
.is_ok());
|
||||
assert!(!Balances::<T, I>::account(&user).flags.is_new_logic());
|
||||
assert_eq!(frame_system::Pallet::<T>::providers(&user), 1);
|
||||
assert_eq!(frame_system::Pallet::<T>::consumers(&user), 0);
|
||||
user
|
||||
})
|
||||
.collect();
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()), who);
|
||||
|
||||
for i in 0..u {
|
||||
let user: T::AccountId = account("old_user", i, SEED);
|
||||
assert!(Balances::<T, I>::account(&user).flags.is_new_logic());
|
||||
assert_eq!(frame_system::Pallet::<T>::providers(&user), 1);
|
||||
assert_eq!(frame_system::Pallet::<T>::consumers(&user), 1);
|
||||
}
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite! {
|
||||
Balances,
|
||||
crate::tests_composite::ExtBuilder::default().build(),
|
||||
crate::tests_composite::Test,
|
||||
crate::tests::ExtBuilder::default().build(),
|
||||
crate::tests::Test,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,898 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Implementations for the `Currency` family of traits.
|
||||
|
||||
use super::*;
|
||||
use frame_support::{
|
||||
ensure,
|
||||
pallet_prelude::DispatchResult,
|
||||
traits::{
|
||||
tokens::{fungible, BalanceStatus as Status},
|
||||
Currency, DefensiveSaturating, ExistenceRequirement,
|
||||
ExistenceRequirement::AllowDeath,
|
||||
Get, Imbalance, LockIdentifier, LockableCurrency, NamedReservableCurrency,
|
||||
ReservableCurrency, SignedImbalance, TryDrop, WithdrawReasons,
|
||||
},
|
||||
};
|
||||
pub use imbalances::{NegativeImbalance, PositiveImbalance};
|
||||
|
||||
// wrapping these imbalances in a private module is necessary to ensure absolute privacy
|
||||
// of the inner member.
|
||||
mod imbalances {
|
||||
use super::{result, Config, Imbalance, RuntimeDebug, Saturating, TryDrop, Zero};
|
||||
use frame_support::traits::SameOrOther;
|
||||
use sp_std::mem;
|
||||
|
||||
/// Opaque, move-only struct with private fields that serves as a token denoting that
|
||||
/// funds have been created without any equal and opposite accounting.
|
||||
#[must_use]
|
||||
#[derive(RuntimeDebug, PartialEq, Eq)]
|
||||
pub struct PositiveImbalance<T: Config<I>, I: 'static = ()>(T::Balance);
|
||||
|
||||
impl<T: Config<I>, I: 'static> PositiveImbalance<T, I> {
|
||||
/// Create a new positive imbalance from a balance.
|
||||
pub fn new(amount: T::Balance) -> Self {
|
||||
PositiveImbalance(amount)
|
||||
}
|
||||
}
|
||||
|
||||
/// Opaque, move-only struct with private fields that serves as a token denoting that
|
||||
/// funds have been destroyed without any equal and opposite accounting.
|
||||
#[must_use]
|
||||
#[derive(RuntimeDebug, PartialEq, Eq)]
|
||||
pub struct NegativeImbalance<T: Config<I>, I: 'static = ()>(T::Balance);
|
||||
|
||||
impl<T: Config<I>, I: 'static> NegativeImbalance<T, I> {
|
||||
/// Create a new negative imbalance from a balance.
|
||||
pub fn new(amount: T::Balance) -> Self {
|
||||
NegativeImbalance(amount)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> TryDrop for PositiveImbalance<T, I> {
|
||||
fn try_drop(self) -> result::Result<(), Self> {
|
||||
self.drop_zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Default for PositiveImbalance<T, I> {
|
||||
fn default() -> Self {
|
||||
Self::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Imbalance<T::Balance> for PositiveImbalance<T, I> {
|
||||
type Opposite = NegativeImbalance<T, I>;
|
||||
|
||||
fn zero() -> Self {
|
||||
Self(Zero::zero())
|
||||
}
|
||||
fn drop_zero(self) -> result::Result<(), Self> {
|
||||
if self.0.is_zero() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
fn split(self, amount: T::Balance) -> (Self, Self) {
|
||||
let first = self.0.min(amount);
|
||||
let second = self.0 - first;
|
||||
|
||||
mem::forget(self);
|
||||
(Self(first), Self(second))
|
||||
}
|
||||
fn merge(mut self, other: Self) -> Self {
|
||||
self.0 = self.0.saturating_add(other.0);
|
||||
mem::forget(other);
|
||||
|
||||
self
|
||||
}
|
||||
fn subsume(&mut self, other: Self) {
|
||||
self.0 = self.0.saturating_add(other.0);
|
||||
mem::forget(other);
|
||||
}
|
||||
fn offset(self, other: Self::Opposite) -> SameOrOther<Self, Self::Opposite> {
|
||||
let (a, b) = (self.0, other.0);
|
||||
mem::forget((self, other));
|
||||
|
||||
if a > b {
|
||||
SameOrOther::Same(Self(a - b))
|
||||
} else if b > a {
|
||||
SameOrOther::Other(NegativeImbalance::new(b - a))
|
||||
} else {
|
||||
SameOrOther::None
|
||||
}
|
||||
}
|
||||
fn peek(&self) -> T::Balance {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> TryDrop for NegativeImbalance<T, I> {
|
||||
fn try_drop(self) -> result::Result<(), Self> {
|
||||
self.drop_zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Default for NegativeImbalance<T, I> {
|
||||
fn default() -> Self {
|
||||
Self::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Imbalance<T::Balance> for NegativeImbalance<T, I> {
|
||||
type Opposite = PositiveImbalance<T, I>;
|
||||
|
||||
fn zero() -> Self {
|
||||
Self(Zero::zero())
|
||||
}
|
||||
fn drop_zero(self) -> result::Result<(), Self> {
|
||||
if self.0.is_zero() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
fn split(self, amount: T::Balance) -> (Self, Self) {
|
||||
let first = self.0.min(amount);
|
||||
let second = self.0 - first;
|
||||
|
||||
mem::forget(self);
|
||||
(Self(first), Self(second))
|
||||
}
|
||||
fn merge(mut self, other: Self) -> Self {
|
||||
self.0 = self.0.saturating_add(other.0);
|
||||
mem::forget(other);
|
||||
|
||||
self
|
||||
}
|
||||
fn subsume(&mut self, other: Self) {
|
||||
self.0 = self.0.saturating_add(other.0);
|
||||
mem::forget(other);
|
||||
}
|
||||
fn offset(self, other: Self::Opposite) -> SameOrOther<Self, Self::Opposite> {
|
||||
let (a, b) = (self.0, other.0);
|
||||
mem::forget((self, other));
|
||||
|
||||
if a > b {
|
||||
SameOrOther::Same(Self(a - b))
|
||||
} else if b > a {
|
||||
SameOrOther::Other(PositiveImbalance::new(b - a))
|
||||
} else {
|
||||
SameOrOther::None
|
||||
}
|
||||
}
|
||||
fn peek(&self) -> T::Balance {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Drop for PositiveImbalance<T, I> {
|
||||
/// Basic drop handler will just square up the total issuance.
|
||||
fn drop(&mut self) {
|
||||
<super::TotalIssuance<T, I>>::mutate(|v| *v = v.saturating_add(self.0));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Drop for NegativeImbalance<T, I> {
|
||||
/// Basic drop handler will just square up the total issuance.
|
||||
fn drop(&mut self) {
|
||||
<super::TotalIssuance<T, I>>::mutate(|v| *v = v.saturating_sub(self.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Currency<T::AccountId> for Pallet<T, I>
|
||||
where
|
||||
T::Balance: MaybeSerializeDeserialize + Debug,
|
||||
{
|
||||
type Balance = T::Balance;
|
||||
type PositiveImbalance = PositiveImbalance<T, I>;
|
||||
type NegativeImbalance = NegativeImbalance<T, I>;
|
||||
|
||||
fn total_balance(who: &T::AccountId) -> Self::Balance {
|
||||
Self::account(who).total()
|
||||
}
|
||||
|
||||
// Check if `value` amount of free balance can be slashed from `who`.
|
||||
fn can_slash(who: &T::AccountId, value: Self::Balance) -> bool {
|
||||
if value.is_zero() {
|
||||
return true
|
||||
}
|
||||
Self::free_balance(who) >= value
|
||||
}
|
||||
|
||||
fn total_issuance() -> Self::Balance {
|
||||
TotalIssuance::<T, I>::get()
|
||||
}
|
||||
|
||||
fn active_issuance() -> Self::Balance {
|
||||
<Self as fungible::Inspect<_>>::active_issuance()
|
||||
}
|
||||
|
||||
fn deactivate(amount: Self::Balance) {
|
||||
<Self as fungible::Unbalanced<_>>::deactivate(amount);
|
||||
}
|
||||
|
||||
fn reactivate(amount: Self::Balance) {
|
||||
<Self as fungible::Unbalanced<_>>::reactivate(amount);
|
||||
}
|
||||
|
||||
fn minimum_balance() -> Self::Balance {
|
||||
T::ExistentialDeposit::get()
|
||||
}
|
||||
|
||||
// Burn funds from the total issuance, returning a positive imbalance for the amount burned.
|
||||
// Is a no-op if amount to be burned is zero.
|
||||
fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance {
|
||||
if amount.is_zero() {
|
||||
return PositiveImbalance::zero()
|
||||
}
|
||||
<TotalIssuance<T, I>>::mutate(|issued| {
|
||||
*issued = issued.checked_sub(&amount).unwrap_or_else(|| {
|
||||
amount = *issued;
|
||||
Zero::zero()
|
||||
});
|
||||
});
|
||||
PositiveImbalance::new(amount)
|
||||
}
|
||||
|
||||
// Create new funds into the total issuance, returning a negative imbalance
|
||||
// for the amount issued.
|
||||
// Is a no-op if amount to be issued it zero.
|
||||
fn issue(mut amount: Self::Balance) -> Self::NegativeImbalance {
|
||||
if amount.is_zero() {
|
||||
return NegativeImbalance::zero()
|
||||
}
|
||||
<TotalIssuance<T, I>>::mutate(|issued| {
|
||||
*issued = issued.checked_add(&amount).unwrap_or_else(|| {
|
||||
amount = Self::Balance::max_value() - *issued;
|
||||
Self::Balance::max_value()
|
||||
})
|
||||
});
|
||||
NegativeImbalance::new(amount)
|
||||
}
|
||||
|
||||
fn free_balance(who: &T::AccountId) -> Self::Balance {
|
||||
Self::account(who).free
|
||||
}
|
||||
|
||||
// Ensure that an account can withdraw from their free balance given any existing withdrawal
|
||||
// restrictions like locks and vesting balance.
|
||||
// Is a no-op if amount to be withdrawn is zero.
|
||||
fn ensure_can_withdraw(
|
||||
who: &T::AccountId,
|
||||
amount: T::Balance,
|
||||
_reasons: WithdrawReasons,
|
||||
new_balance: T::Balance,
|
||||
) -> DispatchResult {
|
||||
if amount.is_zero() {
|
||||
return Ok(())
|
||||
}
|
||||
ensure!(new_balance >= Self::account(who).frozen, Error::<T, I>::LiquidityRestrictions);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Transfer some free balance from `transactor` to `dest`, respecting existence requirements.
|
||||
// Is a no-op if value to be transferred is zero or the `transactor` is the same as `dest`.
|
||||
fn transfer(
|
||||
transactor: &T::AccountId,
|
||||
dest: &T::AccountId,
|
||||
value: Self::Balance,
|
||||
existence_requirement: ExistenceRequirement,
|
||||
) -> DispatchResult {
|
||||
if value.is_zero() || transactor == dest {
|
||||
return Ok(())
|
||||
}
|
||||
let keep_alive = match existence_requirement {
|
||||
ExistenceRequirement::KeepAlive => Preserve,
|
||||
ExistenceRequirement::AllowDeath => Expendable,
|
||||
};
|
||||
<Self as fungible::Mutate<_>>::transfer(transactor, dest, value, keep_alive)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Slash a target account `who`, returning the negative imbalance created and any left over
|
||||
/// amount that could not be slashed.
|
||||
///
|
||||
/// Is a no-op if `value` to be slashed is zero or the account does not exist.
|
||||
///
|
||||
/// NOTE: `slash()` prefers free balance, but assumes that reserve balance can be drawn
|
||||
/// from in extreme circumstances. `can_slash()` should be used prior to `slash()` to avoid
|
||||
/// having to draw from reserved funds, however we err on the side of punishment if things are
|
||||
/// inconsistent or `can_slash` wasn't used appropriately.
|
||||
fn slash(who: &T::AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) {
|
||||
if value.is_zero() {
|
||||
return (NegativeImbalance::zero(), Zero::zero())
|
||||
}
|
||||
if Self::total_balance(who).is_zero() {
|
||||
return (NegativeImbalance::zero(), value)
|
||||
}
|
||||
|
||||
let result = match Self::try_mutate_account_handling_dust(
|
||||
who,
|
||||
|account, _is_new| -> Result<(Self::NegativeImbalance, Self::Balance), DispatchError> {
|
||||
// Best value is the most amount we can slash following liveness rules.
|
||||
let ed = T::ExistentialDeposit::get();
|
||||
let actual = match system::Pallet::<T>::can_dec_provider(who) {
|
||||
true => value.min(account.free),
|
||||
false => value.min(account.free.saturating_sub(ed)),
|
||||
};
|
||||
account.free.saturating_reduce(actual);
|
||||
let remaining = value.saturating_sub(actual);
|
||||
Ok((NegativeImbalance::new(actual), remaining))
|
||||
},
|
||||
) {
|
||||
Ok((imbalance, remaining)) => {
|
||||
Self::deposit_event(Event::Slashed {
|
||||
who: who.clone(),
|
||||
amount: value.saturating_sub(remaining),
|
||||
});
|
||||
(imbalance, remaining)
|
||||
},
|
||||
Err(_) => (Self::NegativeImbalance::zero(), value),
|
||||
};
|
||||
result
|
||||
}
|
||||
|
||||
/// Deposit some `value` into the free balance of an existing target account `who`.
|
||||
///
|
||||
/// Is a no-op if the `value` to be deposited is zero.
|
||||
fn deposit_into_existing(
|
||||
who: &T::AccountId,
|
||||
value: Self::Balance,
|
||||
) -> Result<Self::PositiveImbalance, DispatchError> {
|
||||
if value.is_zero() {
|
||||
return Ok(PositiveImbalance::zero())
|
||||
}
|
||||
|
||||
Self::try_mutate_account_handling_dust(
|
||||
who,
|
||||
|account, is_new| -> Result<Self::PositiveImbalance, DispatchError> {
|
||||
ensure!(!is_new, Error::<T, I>::DeadAccount);
|
||||
account.free = account.free.checked_add(&value).ok_or(ArithmeticError::Overflow)?;
|
||||
Self::deposit_event(Event::Deposit { who: who.clone(), amount: value });
|
||||
Ok(PositiveImbalance::new(value))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Deposit some `value` into the free balance of `who`, possibly creating a new account.
|
||||
///
|
||||
/// This function is a no-op if:
|
||||
/// - the `value` to be deposited is zero; or
|
||||
/// - the `value` to be deposited is less than the required ED and the account does not yet
|
||||
/// exist; or
|
||||
/// - the deposit would necessitate the account to exist and there are no provider references;
|
||||
/// or
|
||||
/// - `value` is so large it would cause the balance of `who` to overflow.
|
||||
fn deposit_creating(who: &T::AccountId, value: Self::Balance) -> Self::PositiveImbalance {
|
||||
if value.is_zero() {
|
||||
return Self::PositiveImbalance::zero()
|
||||
}
|
||||
|
||||
Self::try_mutate_account_handling_dust(
|
||||
who,
|
||||
|account, is_new| -> Result<Self::PositiveImbalance, DispatchError> {
|
||||
let ed = T::ExistentialDeposit::get();
|
||||
ensure!(value >= ed || !is_new, Error::<T, I>::ExistentialDeposit);
|
||||
|
||||
// defensive only: overflow should never happen, however in case it does, then this
|
||||
// operation is a no-op.
|
||||
account.free = match account.free.checked_add(&value) {
|
||||
Some(x) => x,
|
||||
None => return Ok(Self::PositiveImbalance::zero()),
|
||||
};
|
||||
|
||||
Self::deposit_event(Event::Deposit { who: who.clone(), amount: value });
|
||||
Ok(PositiveImbalance::new(value))
|
||||
},
|
||||
)
|
||||
.unwrap_or_else(|_| Self::PositiveImbalance::zero())
|
||||
}
|
||||
|
||||
/// Withdraw some free balance from an account, respecting existence requirements.
|
||||
///
|
||||
/// Is a no-op if value to be withdrawn is zero.
|
||||
fn withdraw(
|
||||
who: &T::AccountId,
|
||||
value: Self::Balance,
|
||||
reasons: WithdrawReasons,
|
||||
liveness: ExistenceRequirement,
|
||||
) -> result::Result<Self::NegativeImbalance, DispatchError> {
|
||||
if value.is_zero() {
|
||||
return Ok(NegativeImbalance::zero())
|
||||
}
|
||||
|
||||
Self::try_mutate_account_handling_dust(
|
||||
who,
|
||||
|account, _| -> Result<Self::NegativeImbalance, DispatchError> {
|
||||
let new_free_account =
|
||||
account.free.checked_sub(&value).ok_or(Error::<T, I>::InsufficientBalance)?;
|
||||
|
||||
// bail if we need to keep the account alive and this would kill it.
|
||||
let ed = T::ExistentialDeposit::get();
|
||||
let would_be_dead = new_free_account < ed;
|
||||
let would_kill = would_be_dead && account.free >= ed;
|
||||
ensure!(liveness == AllowDeath || !would_kill, Error::<T, I>::Expendability);
|
||||
|
||||
Self::ensure_can_withdraw(who, value, reasons, new_free_account)?;
|
||||
|
||||
account.free = new_free_account;
|
||||
|
||||
Self::deposit_event(Event::Withdraw { who: who.clone(), amount: value });
|
||||
Ok(NegativeImbalance::new(value))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Force the new free balance of a target account `who` to some new value `balance`.
|
||||
fn make_free_balance_be(
|
||||
who: &T::AccountId,
|
||||
value: Self::Balance,
|
||||
) -> SignedImbalance<Self::Balance, Self::PositiveImbalance> {
|
||||
Self::try_mutate_account_handling_dust(
|
||||
who,
|
||||
|account,
|
||||
is_new|
|
||||
-> Result<SignedImbalance<Self::Balance, Self::PositiveImbalance>, DispatchError> {
|
||||
let ed = T::ExistentialDeposit::get();
|
||||
// If we're attempting to set an existing account to less than ED, then
|
||||
// bypass the entire operation. It's a no-op if you follow it through, but
|
||||
// since this is an instance where we might account for a negative imbalance
|
||||
// (in the dust cleaner of set_account) before we account for its actual
|
||||
// equal and opposite cause (returned as an Imbalance), then in the
|
||||
// instance that there's no other accounts on the system at all, we might
|
||||
// underflow the issuance and our arithmetic will be off.
|
||||
ensure!(value >= ed || !is_new, Error::<T, I>::ExistentialDeposit);
|
||||
|
||||
let imbalance = if account.free <= value {
|
||||
SignedImbalance::Positive(PositiveImbalance::new(value - account.free))
|
||||
} else {
|
||||
SignedImbalance::Negative(NegativeImbalance::new(account.free - value))
|
||||
};
|
||||
account.free = value;
|
||||
Self::deposit_event(Event::BalanceSet { who: who.clone(), free: account.free });
|
||||
Ok(imbalance)
|
||||
},
|
||||
)
|
||||
.unwrap_or_else(|_| SignedImbalance::Positive(Self::PositiveImbalance::zero()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> ReservableCurrency<T::AccountId> for Pallet<T, I>
|
||||
where
|
||||
T::Balance: MaybeSerializeDeserialize + Debug,
|
||||
{
|
||||
/// Check if `who` can reserve `value` from their free balance.
|
||||
///
|
||||
/// Always `true` if value to be reserved is zero.
|
||||
fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool {
|
||||
if value.is_zero() {
|
||||
return true
|
||||
}
|
||||
Self::account(who).free.checked_sub(&value).map_or(false, |new_balance| {
|
||||
Self::ensure_can_withdraw(who, value, WithdrawReasons::RESERVE, new_balance).is_ok()
|
||||
})
|
||||
}
|
||||
|
||||
fn reserved_balance(who: &T::AccountId) -> Self::Balance {
|
||||
Self::account(who).reserved
|
||||
}
|
||||
|
||||
/// Move `value` from the free balance from `who` to their reserved balance.
|
||||
///
|
||||
/// Is a no-op if value to be reserved is zero.
|
||||
fn reserve(who: &T::AccountId, value: Self::Balance) -> DispatchResult {
|
||||
if value.is_zero() {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
Self::try_mutate_account_handling_dust(who, |account, _| -> DispatchResult {
|
||||
account.free =
|
||||
account.free.checked_sub(&value).ok_or(Error::<T, I>::InsufficientBalance)?;
|
||||
account.reserved =
|
||||
account.reserved.checked_add(&value).ok_or(ArithmeticError::Overflow)?;
|
||||
Self::ensure_can_withdraw(&who, value, WithdrawReasons::RESERVE, account.free)
|
||||
})?;
|
||||
|
||||
Self::deposit_event(Event::Reserved { who: who.clone(), amount: value });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unreserve some funds, returning any amount that was unable to be unreserved.
|
||||
///
|
||||
/// Is a no-op if the value to be unreserved is zero or the account does not exist.
|
||||
///
|
||||
/// NOTE: returns amount value which wasn't successfully unreserved.
|
||||
fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance {
|
||||
if value.is_zero() {
|
||||
return Zero::zero()
|
||||
}
|
||||
if Self::total_balance(who).is_zero() {
|
||||
return value
|
||||
}
|
||||
|
||||
let actual = match Self::mutate_account_handling_dust(who, |account| {
|
||||
let actual = cmp::min(account.reserved, value);
|
||||
account.reserved -= actual;
|
||||
// defensive only: this can never fail since total issuance which is at least
|
||||
// free+reserved fits into the same data type.
|
||||
account.free = account.free.defensive_saturating_add(actual);
|
||||
actual
|
||||
}) {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
// This should never happen since we don't alter the total amount in the account.
|
||||
// If it ever does, then we should fail gracefully though, indicating that nothing
|
||||
// could be done.
|
||||
return value
|
||||
},
|
||||
};
|
||||
|
||||
Self::deposit_event(Event::Unreserved { who: who.clone(), amount: actual });
|
||||
value - actual
|
||||
}
|
||||
|
||||
/// Slash from reserved balance, returning the negative imbalance created,
|
||||
/// and any amount that was unable to be slashed.
|
||||
///
|
||||
/// Is a no-op if the value to be slashed is zero or the account does not exist.
|
||||
fn slash_reserved(
|
||||
who: &T::AccountId,
|
||||
value: Self::Balance,
|
||||
) -> (Self::NegativeImbalance, Self::Balance) {
|
||||
if value.is_zero() {
|
||||
return (NegativeImbalance::zero(), Zero::zero())
|
||||
}
|
||||
if Self::total_balance(who).is_zero() {
|
||||
return (NegativeImbalance::zero(), value)
|
||||
}
|
||||
|
||||
// NOTE: `mutate_account` may fail if it attempts to reduce the balance to the point that an
|
||||
// account is attempted to be illegally destroyed.
|
||||
|
||||
match Self::mutate_account_handling_dust(who, |account| {
|
||||
let actual = value.min(account.reserved);
|
||||
account.reserved.saturating_reduce(actual);
|
||||
|
||||
// underflow should never happen, but it if does, there's nothing to be done here.
|
||||
(NegativeImbalance::new(actual), value.saturating_sub(actual))
|
||||
}) {
|
||||
Ok((imbalance, not_slashed)) => {
|
||||
Self::deposit_event(Event::Slashed {
|
||||
who: who.clone(),
|
||||
amount: value.saturating_sub(not_slashed),
|
||||
});
|
||||
(imbalance, not_slashed)
|
||||
},
|
||||
Err(_) => (Self::NegativeImbalance::zero(), value),
|
||||
}
|
||||
}
|
||||
|
||||
/// Move the reserved balance of one account into the balance of another, according to `status`.
|
||||
///
|
||||
/// Is a no-op if:
|
||||
/// - the value to be moved is zero; or
|
||||
/// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`.
|
||||
fn repatriate_reserved(
|
||||
slashed: &T::AccountId,
|
||||
beneficiary: &T::AccountId,
|
||||
value: Self::Balance,
|
||||
status: Status,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let actual = Self::do_transfer_reserved(slashed, beneficiary, value, true, status)?;
|
||||
Ok(value.saturating_sub(actual))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> NamedReservableCurrency<T::AccountId> for Pallet<T, I>
|
||||
where
|
||||
T::Balance: MaybeSerializeDeserialize + Debug,
|
||||
{
|
||||
type ReserveIdentifier = T::ReserveIdentifier;
|
||||
|
||||
fn reserved_balance_named(id: &Self::ReserveIdentifier, who: &T::AccountId) -> Self::Balance {
|
||||
let reserves = Self::reserves(who);
|
||||
reserves
|
||||
.binary_search_by_key(id, |data| data.id)
|
||||
.map(|index| reserves[index].amount)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Move `value` from the free balance from `who` to a named reserve balance.
|
||||
///
|
||||
/// Is a no-op if value to be reserved is zero.
|
||||
fn reserve_named(
|
||||
id: &Self::ReserveIdentifier,
|
||||
who: &T::AccountId,
|
||||
value: Self::Balance,
|
||||
) -> DispatchResult {
|
||||
if value.is_zero() {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
Reserves::<T, I>::try_mutate(who, |reserves| -> DispatchResult {
|
||||
match reserves.binary_search_by_key(id, |data| data.id) {
|
||||
Ok(index) => {
|
||||
// this add can't overflow but just to be defensive.
|
||||
reserves[index].amount = reserves[index].amount.defensive_saturating_add(value);
|
||||
},
|
||||
Err(index) => {
|
||||
reserves
|
||||
.try_insert(index, ReserveData { id: *id, amount: value })
|
||||
.map_err(|_| Error::<T, I>::TooManyReserves)?;
|
||||
},
|
||||
};
|
||||
<Self as ReservableCurrency<_>>::reserve(who, value)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Unreserve some funds, returning any amount that was unable to be unreserved.
|
||||
///
|
||||
/// Is a no-op if the value to be unreserved is zero.
|
||||
fn unreserve_named(
|
||||
id: &Self::ReserveIdentifier,
|
||||
who: &T::AccountId,
|
||||
value: Self::Balance,
|
||||
) -> Self::Balance {
|
||||
if value.is_zero() {
|
||||
return Zero::zero()
|
||||
}
|
||||
|
||||
Reserves::<T, I>::mutate_exists(who, |maybe_reserves| -> Self::Balance {
|
||||
if let Some(reserves) = maybe_reserves.as_mut() {
|
||||
match reserves.binary_search_by_key(id, |data| data.id) {
|
||||
Ok(index) => {
|
||||
let to_change = cmp::min(reserves[index].amount, value);
|
||||
|
||||
let remain = <Self as ReservableCurrency<_>>::unreserve(who, to_change);
|
||||
|
||||
// remain should always be zero but just to be defensive here.
|
||||
let actual = to_change.defensive_saturating_sub(remain);
|
||||
|
||||
// `actual <= to_change` and `to_change <= amount`; qed;
|
||||
reserves[index].amount -= actual;
|
||||
|
||||
if reserves[index].amount.is_zero() {
|
||||
if reserves.len() == 1 {
|
||||
// no more named reserves
|
||||
*maybe_reserves = None;
|
||||
} else {
|
||||
// remove this named reserve
|
||||
reserves.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
value - actual
|
||||
},
|
||||
Err(_) => value,
|
||||
}
|
||||
} else {
|
||||
value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Slash from reserved balance, returning the negative imbalance created,
|
||||
/// and any amount that was unable to be slashed.
|
||||
///
|
||||
/// Is a no-op if the value to be slashed is zero.
|
||||
fn slash_reserved_named(
|
||||
id: &Self::ReserveIdentifier,
|
||||
who: &T::AccountId,
|
||||
value: Self::Balance,
|
||||
) -> (Self::NegativeImbalance, Self::Balance) {
|
||||
if value.is_zero() {
|
||||
return (NegativeImbalance::zero(), Zero::zero())
|
||||
}
|
||||
|
||||
Reserves::<T, I>::mutate(who, |reserves| -> (Self::NegativeImbalance, Self::Balance) {
|
||||
match reserves.binary_search_by_key(id, |data| data.id) {
|
||||
Ok(index) => {
|
||||
let to_change = cmp::min(reserves[index].amount, value);
|
||||
|
||||
let (imb, remain) =
|
||||
<Self as ReservableCurrency<_>>::slash_reserved(who, to_change);
|
||||
|
||||
// remain should always be zero but just to be defensive here.
|
||||
let actual = to_change.defensive_saturating_sub(remain);
|
||||
|
||||
// `actual <= to_change` and `to_change <= amount`; qed;
|
||||
reserves[index].amount -= actual;
|
||||
|
||||
Self::deposit_event(Event::Slashed { who: who.clone(), amount: actual });
|
||||
(imb, value - actual)
|
||||
},
|
||||
Err(_) => (NegativeImbalance::zero(), value),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Move the reserved balance of one account into the balance of another, according to `status`.
|
||||
/// If `status` is `Reserved`, the balance will be reserved with given `id`.
|
||||
///
|
||||
/// Is a no-op if:
|
||||
/// - the value to be moved is zero; or
|
||||
/// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`.
|
||||
fn repatriate_reserved_named(
|
||||
id: &Self::ReserveIdentifier,
|
||||
slashed: &T::AccountId,
|
||||
beneficiary: &T::AccountId,
|
||||
value: Self::Balance,
|
||||
status: Status,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
if value.is_zero() {
|
||||
return Ok(Zero::zero())
|
||||
}
|
||||
|
||||
if slashed == beneficiary {
|
||||
return match status {
|
||||
Status::Free => Ok(Self::unreserve_named(id, slashed, value)),
|
||||
Status::Reserved =>
|
||||
Ok(value.saturating_sub(Self::reserved_balance_named(id, slashed))),
|
||||
}
|
||||
}
|
||||
|
||||
Reserves::<T, I>::try_mutate(slashed, |reserves| -> Result<Self::Balance, DispatchError> {
|
||||
match reserves.binary_search_by_key(id, |data| data.id) {
|
||||
Ok(index) => {
|
||||
let to_change = cmp::min(reserves[index].amount, value);
|
||||
|
||||
let actual = if status == Status::Reserved {
|
||||
// make it the reserved under same identifier
|
||||
Reserves::<T, I>::try_mutate(
|
||||
beneficiary,
|
||||
|reserves| -> Result<T::Balance, DispatchError> {
|
||||
match reserves.binary_search_by_key(id, |data| data.id) {
|
||||
Ok(index) => {
|
||||
let remain =
|
||||
<Self as ReservableCurrency<_>>::repatriate_reserved(
|
||||
slashed,
|
||||
beneficiary,
|
||||
to_change,
|
||||
status,
|
||||
)?;
|
||||
|
||||
// remain should always be zero but just to be defensive
|
||||
// here.
|
||||
let actual = to_change.defensive_saturating_sub(remain);
|
||||
|
||||
// this add can't overflow but just to be defensive.
|
||||
reserves[index].amount =
|
||||
reserves[index].amount.defensive_saturating_add(actual);
|
||||
|
||||
Ok(actual)
|
||||
},
|
||||
Err(index) => {
|
||||
let remain =
|
||||
<Self as ReservableCurrency<_>>::repatriate_reserved(
|
||||
slashed,
|
||||
beneficiary,
|
||||
to_change,
|
||||
status,
|
||||
)?;
|
||||
|
||||
// remain should always be zero but just to be defensive
|
||||
// here
|
||||
let actual = to_change.defensive_saturating_sub(remain);
|
||||
|
||||
reserves
|
||||
.try_insert(
|
||||
index,
|
||||
ReserveData { id: *id, amount: actual },
|
||||
)
|
||||
.map_err(|_| Error::<T, I>::TooManyReserves)?;
|
||||
|
||||
Ok(actual)
|
||||
},
|
||||
}
|
||||
},
|
||||
)?
|
||||
} else {
|
||||
let remain = <Self as ReservableCurrency<_>>::repatriate_reserved(
|
||||
slashed,
|
||||
beneficiary,
|
||||
to_change,
|
||||
status,
|
||||
)?;
|
||||
|
||||
// remain should always be zero but just to be defensive here
|
||||
to_change.defensive_saturating_sub(remain)
|
||||
};
|
||||
|
||||
// `actual <= to_change` and `to_change <= amount`; qed;
|
||||
reserves[index].amount -= actual;
|
||||
|
||||
Ok(value - actual)
|
||||
},
|
||||
Err(_) => Ok(value),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> LockableCurrency<T::AccountId> for Pallet<T, I>
|
||||
where
|
||||
T::Balance: MaybeSerializeDeserialize + Debug,
|
||||
{
|
||||
type Moment = T::BlockNumber;
|
||||
|
||||
type MaxLocks = T::MaxLocks;
|
||||
|
||||
// Set a lock on the balance of `who`.
|
||||
// Is a no-op if lock amount is zero or `reasons` `is_none()`.
|
||||
fn set_lock(
|
||||
id: LockIdentifier,
|
||||
who: &T::AccountId,
|
||||
amount: T::Balance,
|
||||
reasons: WithdrawReasons,
|
||||
) {
|
||||
if amount.is_zero() || reasons.is_empty() {
|
||||
return
|
||||
}
|
||||
let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() });
|
||||
let mut locks = Self::locks(who)
|
||||
.into_iter()
|
||||
.filter_map(|l| if l.id == id { new_lock.take() } else { Some(l) })
|
||||
.collect::<Vec<_>>();
|
||||
if let Some(lock) = new_lock {
|
||||
locks.push(lock)
|
||||
}
|
||||
Self::update_locks(who, &locks[..]);
|
||||
}
|
||||
|
||||
// Extend a lock on the balance of `who`.
|
||||
// Is a no-op if lock amount is zero or `reasons` `is_none()`.
|
||||
fn extend_lock(
|
||||
id: LockIdentifier,
|
||||
who: &T::AccountId,
|
||||
amount: T::Balance,
|
||||
reasons: WithdrawReasons,
|
||||
) {
|
||||
if amount.is_zero() || reasons.is_empty() {
|
||||
return
|
||||
}
|
||||
let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() });
|
||||
let mut locks = Self::locks(who)
|
||||
.into_iter()
|
||||
.filter_map(|l| {
|
||||
if l.id == id {
|
||||
new_lock.take().map(|nl| BalanceLock {
|
||||
id: l.id,
|
||||
amount: l.amount.max(nl.amount),
|
||||
reasons: l.reasons | nl.reasons,
|
||||
})
|
||||
} else {
|
||||
Some(l)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if let Some(lock) = new_lock {
|
||||
locks.push(lock)
|
||||
}
|
||||
Self::update_locks(who, &locks[..]);
|
||||
}
|
||||
|
||||
fn remove_lock(id: LockIdentifier, who: &T::AccountId) {
|
||||
let mut locks = Self::locks(who);
|
||||
locks.retain(|l| l.id != id);
|
||||
Self::update_locks(who, &locks[..]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Implementation of `fungible` traits for Balances pallet.
|
||||
use super::*;
|
||||
use frame_support::traits::tokens::{
|
||||
Fortitude,
|
||||
Preservation::{self, Preserve, Protect},
|
||||
Provenance::{self, Minted},
|
||||
};
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungible::Inspect<T::AccountId> for Pallet<T, I> {
|
||||
type Balance = T::Balance;
|
||||
|
||||
fn total_issuance() -> Self::Balance {
|
||||
TotalIssuance::<T, I>::get()
|
||||
}
|
||||
fn active_issuance() -> Self::Balance {
|
||||
TotalIssuance::<T, I>::get().saturating_sub(InactiveIssuance::<T, I>::get())
|
||||
}
|
||||
fn minimum_balance() -> Self::Balance {
|
||||
T::ExistentialDeposit::get()
|
||||
}
|
||||
fn total_balance(who: &T::AccountId) -> Self::Balance {
|
||||
Self::account(who).total()
|
||||
}
|
||||
fn balance(who: &T::AccountId) -> Self::Balance {
|
||||
Self::account(who).free
|
||||
}
|
||||
fn reducible_balance(
|
||||
who: &T::AccountId,
|
||||
preservation: Preservation,
|
||||
force: Fortitude,
|
||||
) -> Self::Balance {
|
||||
let a = Self::account(who);
|
||||
let mut untouchable = Zero::zero();
|
||||
if force == Polite {
|
||||
// Frozen balance applies to total. Anything on hold therefore gets discounted from the
|
||||
// limit given by the freezes.
|
||||
untouchable = a.frozen.saturating_sub(a.reserved);
|
||||
}
|
||||
// If we want to keep our provider ref..
|
||||
if preservation == Preserve
|
||||
// ..or we don't want the account to die and our provider ref is needed for it to live..
|
||||
|| preservation == Protect && !a.free.is_zero() &&
|
||||
frame_system::Pallet::<T>::providers(who) == 1
|
||||
// ..or we don't care about the account dying but our provider ref is required..
|
||||
|| preservation == Expendable && !a.free.is_zero() &&
|
||||
!frame_system::Pallet::<T>::can_dec_provider(who)
|
||||
{
|
||||
// ..then the ED needed..
|
||||
untouchable = untouchable.max(T::ExistentialDeposit::get());
|
||||
}
|
||||
// Liquid balance is what is neither on hold nor frozen/required for provider.
|
||||
a.free.saturating_sub(untouchable)
|
||||
}
|
||||
fn can_deposit(
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
provenance: Provenance,
|
||||
) -> DepositConsequence {
|
||||
if amount.is_zero() {
|
||||
return DepositConsequence::Success
|
||||
}
|
||||
|
||||
if provenance == Minted && TotalIssuance::<T, I>::get().checked_add(&amount).is_none() {
|
||||
return DepositConsequence::Overflow
|
||||
}
|
||||
|
||||
let account = Self::account(who);
|
||||
let new_free = match account.free.checked_add(&amount) {
|
||||
None => return DepositConsequence::Overflow,
|
||||
Some(x) if x < T::ExistentialDeposit::get() => return DepositConsequence::BelowMinimum,
|
||||
Some(x) => x,
|
||||
};
|
||||
|
||||
match account.reserved.checked_add(&new_free) {
|
||||
Some(_) => {},
|
||||
None => return DepositConsequence::Overflow,
|
||||
};
|
||||
|
||||
// NOTE: We assume that we are a provider, so don't need to do any checks in the
|
||||
// case of account creation.
|
||||
|
||||
DepositConsequence::Success
|
||||
}
|
||||
fn can_withdraw(
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> WithdrawConsequence<Self::Balance> {
|
||||
if amount.is_zero() {
|
||||
return WithdrawConsequence::Success
|
||||
}
|
||||
|
||||
if TotalIssuance::<T, I>::get().checked_sub(&amount).is_none() {
|
||||
return WithdrawConsequence::Underflow
|
||||
}
|
||||
|
||||
let account = Self::account(who);
|
||||
let new_free_balance = match account.free.checked_sub(&amount) {
|
||||
Some(x) => x,
|
||||
None => return WithdrawConsequence::BalanceLow,
|
||||
};
|
||||
|
||||
let liquid = Self::reducible_balance(who, Expendable, Polite);
|
||||
if amount > liquid {
|
||||
return WithdrawConsequence::Frozen
|
||||
}
|
||||
|
||||
// Provider restriction - total account balance cannot be reduced to zero if it cannot
|
||||
// sustain the loss of a provider reference.
|
||||
// NOTE: This assumes that the pallet is a provider (which is true). Is this ever changes,
|
||||
// then this will need to adapt accordingly.
|
||||
let ed = T::ExistentialDeposit::get();
|
||||
let success = if new_free_balance < ed {
|
||||
if frame_system::Pallet::<T>::can_dec_provider(who) {
|
||||
WithdrawConsequence::ReducedToZero(new_free_balance)
|
||||
} else {
|
||||
return WithdrawConsequence::WouldDie
|
||||
}
|
||||
} else {
|
||||
WithdrawConsequence::Success
|
||||
};
|
||||
|
||||
let new_total_balance = new_free_balance.saturating_add(account.reserved);
|
||||
|
||||
// Eventual free funds must be no less than the frozen balance.
|
||||
if new_total_balance < account.frozen {
|
||||
return WithdrawConsequence::Frozen
|
||||
}
|
||||
|
||||
success
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungible::Unbalanced<T::AccountId> for Pallet<T, I> {
|
||||
fn handle_dust(dust: fungible::Dust<T::AccountId, Self>) {
|
||||
T::DustRemoval::on_unbalanced(dust.into_credit());
|
||||
}
|
||||
fn write_balance(
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Option<Self::Balance>, DispatchError> {
|
||||
let max_reduction =
|
||||
<Self as fungible::Inspect<_>>::reducible_balance(who, Expendable, Force);
|
||||
let (result, maybe_dust) = Self::mutate_account(who, |account| -> DispatchResult {
|
||||
// Make sure the reduction (if there is one) is no more than the maximum allowed.
|
||||
let reduction = account.free.saturating_sub(amount);
|
||||
ensure!(reduction <= max_reduction, Error::<T, I>::InsufficientBalance);
|
||||
|
||||
account.free = amount;
|
||||
Ok(())
|
||||
})?;
|
||||
result?;
|
||||
Ok(maybe_dust)
|
||||
}
|
||||
|
||||
fn set_total_issuance(amount: Self::Balance) {
|
||||
TotalIssuance::<T, I>::mutate(|t| *t = amount);
|
||||
}
|
||||
|
||||
fn deactivate(amount: Self::Balance) {
|
||||
InactiveIssuance::<T, I>::mutate(|b| b.saturating_accrue(amount));
|
||||
}
|
||||
|
||||
fn reactivate(amount: Self::Balance) {
|
||||
InactiveIssuance::<T, I>::mutate(|b| b.saturating_reduce(amount));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungible::Mutate<T::AccountId> for Pallet<T, I> {
|
||||
fn done_mint_into(who: &T::AccountId, amount: Self::Balance) {
|
||||
Self::deposit_event(Event::<T, I>::Minted { who: who.clone(), amount });
|
||||
}
|
||||
fn done_burn_from(who: &T::AccountId, amount: Self::Balance) {
|
||||
Self::deposit_event(Event::<T, I>::Burned { who: who.clone(), amount });
|
||||
}
|
||||
fn done_shelve(who: &T::AccountId, amount: Self::Balance) {
|
||||
Self::deposit_event(Event::<T, I>::Suspended { who: who.clone(), amount });
|
||||
}
|
||||
fn done_restore(who: &T::AccountId, amount: Self::Balance) {
|
||||
Self::deposit_event(Event::<T, I>::Restored { who: who.clone(), amount });
|
||||
}
|
||||
fn done_transfer(source: &T::AccountId, dest: &T::AccountId, amount: Self::Balance) {
|
||||
Self::deposit_event(Event::<T, I>::Transfer {
|
||||
from: source.clone(),
|
||||
to: dest.clone(),
|
||||
amount,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungible::MutateHold<T::AccountId> for Pallet<T, I> {}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungible::InspectHold<T::AccountId> for Pallet<T, I> {
|
||||
type Reason = T::HoldIdentifier;
|
||||
|
||||
fn total_balance_on_hold(who: &T::AccountId) -> T::Balance {
|
||||
Self::account(who).reserved
|
||||
}
|
||||
fn reducible_total_balance_on_hold(who: &T::AccountId, force: Fortitude) -> Self::Balance {
|
||||
// The total balance must never drop below the freeze requirements if we're not forcing:
|
||||
let a = Self::account(who);
|
||||
let unavailable = if force == Force {
|
||||
Self::Balance::zero()
|
||||
} else {
|
||||
// The freeze lock applies to the total balance, so we can discount the free balance
|
||||
// from the amount which the total reserved balance must provide to satisfy it.
|
||||
a.frozen.saturating_sub(a.free)
|
||||
};
|
||||
a.reserved.saturating_sub(unavailable)
|
||||
}
|
||||
fn balance_on_hold(reason: &Self::Reason, who: &T::AccountId) -> T::Balance {
|
||||
Holds::<T, I>::get(who)
|
||||
.iter()
|
||||
.find(|x| &x.id == reason)
|
||||
.map_or_else(Zero::zero, |x| x.amount)
|
||||
}
|
||||
fn hold_available(reason: &Self::Reason, who: &T::AccountId) -> bool {
|
||||
if frame_system::Pallet::<T>::providers(who) == 0 {
|
||||
return false
|
||||
}
|
||||
let holds = Holds::<T, I>::get(who);
|
||||
if holds.is_full() && !holds.iter().any(|x| &x.id == reason) {
|
||||
return false
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungible::UnbalancedHold<T::AccountId> for Pallet<T, I> {
|
||||
fn set_balance_on_hold(
|
||||
reason: &Self::Reason,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> DispatchResult {
|
||||
let mut new_account = Self::account(who);
|
||||
let mut holds = Holds::<T, I>::get(who);
|
||||
let mut increase = true;
|
||||
let mut delta = amount;
|
||||
|
||||
if let Some(item) = holds.iter_mut().find(|x| &x.id == reason) {
|
||||
delta = item.amount.max(amount) - item.amount.min(amount);
|
||||
increase = amount > item.amount;
|
||||
item.amount = amount;
|
||||
holds.retain(|x| !x.amount.is_zero());
|
||||
} else {
|
||||
if !amount.is_zero() {
|
||||
holds
|
||||
.try_push(IdAmount { id: *reason, amount })
|
||||
.map_err(|_| Error::<T, I>::TooManyHolds)?;
|
||||
}
|
||||
}
|
||||
|
||||
new_account.reserved = if increase {
|
||||
new_account.reserved.checked_add(&delta).ok_or(ArithmeticError::Overflow)?
|
||||
} else {
|
||||
new_account.reserved.checked_sub(&delta).ok_or(ArithmeticError::Underflow)?
|
||||
};
|
||||
|
||||
let (result, maybe_dust) = Self::try_mutate_account(who, |a, _| -> DispatchResult {
|
||||
*a = new_account;
|
||||
Ok(())
|
||||
})?;
|
||||
debug_assert!(
|
||||
maybe_dust.is_none(),
|
||||
"Does not alter main balance; dust only happens when it is altered; qed"
|
||||
);
|
||||
Holds::<T, I>::insert(who, holds);
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungible::InspectFreeze<T::AccountId> for Pallet<T, I> {
|
||||
type Id = T::FreezeIdentifier;
|
||||
|
||||
fn balance_frozen(id: &Self::Id, who: &T::AccountId) -> Self::Balance {
|
||||
let locks = Freezes::<T, I>::get(who);
|
||||
locks.into_iter().find(|l| &l.id == id).map_or(Zero::zero(), |l| l.amount)
|
||||
}
|
||||
|
||||
fn can_freeze(id: &Self::Id, who: &T::AccountId) -> bool {
|
||||
let l = Freezes::<T, I>::get(who);
|
||||
!l.is_full() || l.iter().any(|x| &x.id == id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungible::MutateFreeze<T::AccountId> for Pallet<T, I> {
|
||||
fn set_freeze(id: &Self::Id, who: &T::AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
if amount.is_zero() {
|
||||
return Self::thaw(id, who)
|
||||
}
|
||||
let mut locks = Freezes::<T, I>::get(who);
|
||||
if let Some(i) = locks.iter_mut().find(|x| &x.id == id) {
|
||||
i.amount = amount;
|
||||
} else {
|
||||
locks
|
||||
.try_push(IdAmount { id: *id, amount })
|
||||
.map_err(|_| Error::<T, I>::TooManyFreezes)?;
|
||||
}
|
||||
Self::update_freezes(who, locks.as_bounded_slice())
|
||||
}
|
||||
|
||||
fn extend_freeze(id: &Self::Id, who: &T::AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
if amount.is_zero() {
|
||||
return Ok(())
|
||||
}
|
||||
let mut locks = Freezes::<T, I>::get(who);
|
||||
if let Some(i) = locks.iter_mut().find(|x| &x.id == id) {
|
||||
i.amount = i.amount.max(amount);
|
||||
} else {
|
||||
locks
|
||||
.try_push(IdAmount { id: *id, amount })
|
||||
.map_err(|_| Error::<T, I>::TooManyFreezes)?;
|
||||
}
|
||||
Self::update_freezes(who, locks.as_bounded_slice())
|
||||
}
|
||||
|
||||
fn thaw(id: &Self::Id, who: &T::AccountId) -> DispatchResult {
|
||||
let mut locks = Freezes::<T, I>::get(who);
|
||||
locks.retain(|l| &l.id != id);
|
||||
Self::update_freezes(who, locks.as_bounded_slice())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungible::Balanced<T::AccountId> for Pallet<T, I> {
|
||||
type OnDropCredit = fungible::DecreaseIssuance<T::AccountId, Self>;
|
||||
type OnDropDebt = fungible::IncreaseIssuance<T::AccountId, Self>;
|
||||
|
||||
fn done_deposit(who: &T::AccountId, amount: Self::Balance) {
|
||||
Self::deposit_event(Event::<T, I>::Deposit { who: who.clone(), amount });
|
||||
}
|
||||
fn done_withdraw(who: &T::AccountId, amount: Self::Balance) {
|
||||
Self::deposit_event(Event::<T, I>::Withdraw { who: who.clone(), amount });
|
||||
}
|
||||
fn done_issue(amount: Self::Balance) {
|
||||
Self::deposit_event(Event::<T, I>::Issued { amount });
|
||||
}
|
||||
fn done_rescind(amount: Self::Balance) {
|
||||
Self::deposit_event(Event::<T, I>::Rescinded { amount });
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungible::BalancedHold<T::AccountId> for Pallet<T, I> {}
|
||||
+692
-1781
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,224 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Tests regarding the functionality of the dispatchables/extrinsics.
|
||||
|
||||
use super::*;
|
||||
use frame_support::traits::tokens::Preservation::Expendable;
|
||||
use fungible::{hold::Mutate as HoldMutate, Inspect, Mutate};
|
||||
|
||||
#[test]
|
||||
fn default_indexing_on_new_accounts_should_not_work2() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(10)
|
||||
.monied(true)
|
||||
.build_and_execute_with(|| {
|
||||
// account 5 should not exist
|
||||
// ext_deposit is 10, value is 9, not satisfies for ext_deposit
|
||||
assert_noop!(
|
||||
Balances::transfer_allow_death(Some(1).into(), 5, 9),
|
||||
TokenError::BelowMinimum,
|
||||
);
|
||||
assert_eq!(Balances::free_balance(1), 100);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dust_account_removal_should_work() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(100)
|
||||
.monied(true)
|
||||
.build_and_execute_with(|| {
|
||||
System::inc_account_nonce(&2);
|
||||
assert_eq!(System::account_nonce(&2), 1);
|
||||
assert_eq!(Balances::total_balance(&2), 2000);
|
||||
// index 1 (account 2) becomes zombie
|
||||
assert_ok!(Balances::transfer_allow_death(Some(2).into(), 5, 1901));
|
||||
assert_eq!(Balances::total_balance(&2), 0);
|
||||
assert_eq!(Balances::total_balance(&5), 1901);
|
||||
assert_eq!(System::account_nonce(&2), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn balance_transfer_works() {
|
||||
ExtBuilder::default().build_and_execute_with(|| {
|
||||
let _ = Balances::mint_into(&1, 111);
|
||||
assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 69));
|
||||
assert_eq!(Balances::total_balance(&1), 42);
|
||||
assert_eq!(Balances::total_balance(&2), 69);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn force_transfer_works() {
|
||||
ExtBuilder::default().build_and_execute_with(|| {
|
||||
let _ = Balances::mint_into(&1, 111);
|
||||
assert_noop!(Balances::force_transfer(Some(2).into(), 1, 2, 69), BadOrigin,);
|
||||
assert_ok!(Balances::force_transfer(RawOrigin::Root.into(), 1, 2, 69));
|
||||
assert_eq!(Balances::total_balance(&1), 42);
|
||||
assert_eq!(Balances::total_balance(&2), 69);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn balance_transfer_when_on_hold_should_not_work() {
|
||||
ExtBuilder::default().build_and_execute_with(|| {
|
||||
let _ = Balances::mint_into(&1, 111);
|
||||
assert_ok!(Balances::hold(&TestId::Foo, &1, 69));
|
||||
assert_noop!(
|
||||
Balances::transfer_allow_death(Some(1).into(), 2, 69),
|
||||
TokenError::FundsUnavailable,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_keep_alive_works() {
|
||||
ExtBuilder::default().existential_deposit(1).build_and_execute_with(|| {
|
||||
let _ = Balances::mint_into(&1, 100);
|
||||
assert_noop!(
|
||||
Balances::transfer_keep_alive(Some(1).into(), 2, 100),
|
||||
TokenError::NotExpendable
|
||||
);
|
||||
assert_eq!(Balances::total_balance(&1), 100);
|
||||
assert_eq!(Balances::total_balance(&2), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_keep_alive_all_free_succeed() {
|
||||
ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| {
|
||||
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 300));
|
||||
assert_ok!(Balances::hold(&TestId::Foo, &1, 100));
|
||||
assert_ok!(Balances::transfer_keep_alive(Some(1).into(), 2, 100));
|
||||
assert_eq!(Balances::total_balance(&1), 200);
|
||||
assert_eq!(Balances::total_balance(&2), 100);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_all_works_1() {
|
||||
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
|
||||
// setup
|
||||
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 200));
|
||||
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0));
|
||||
// transfer all and allow death
|
||||
assert_ok!(Balances::transfer_all(Some(1).into(), 2, false));
|
||||
assert_eq!(Balances::total_balance(&1), 0);
|
||||
assert_eq!(Balances::total_balance(&2), 200);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_all_works_2() {
|
||||
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
|
||||
// setup
|
||||
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 200));
|
||||
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0));
|
||||
// transfer all and keep alive
|
||||
assert_ok!(Balances::transfer_all(Some(1).into(), 2, true));
|
||||
assert_eq!(Balances::total_balance(&1), 100);
|
||||
assert_eq!(Balances::total_balance(&2), 100);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_all_works_3() {
|
||||
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
|
||||
// setup
|
||||
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 210));
|
||||
assert_ok!(Balances::hold(&TestId::Foo, &1, 10));
|
||||
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0));
|
||||
// transfer all and allow death w/ reserved
|
||||
assert_ok!(Balances::transfer_all(Some(1).into(), 2, false));
|
||||
assert_eq!(Balances::total_balance(&1), 110);
|
||||
assert_eq!(Balances::total_balance(&2), 100);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_all_works_4() {
|
||||
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
|
||||
// setup
|
||||
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 210));
|
||||
assert_ok!(Balances::hold(&TestId::Foo, &1, 10));
|
||||
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0));
|
||||
// transfer all and keep alive w/ reserved
|
||||
assert_ok!(Balances::transfer_all(Some(1).into(), 2, true));
|
||||
assert_eq!(Balances::total_balance(&1), 110);
|
||||
assert_eq!(Balances::total_balance(&2), 100);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_balance_handles_killing_account() {
|
||||
ExtBuilder::default().build_and_execute_with(|| {
|
||||
let _ = Balances::mint_into(&1, 111);
|
||||
assert_ok!(frame_system::Pallet::<Test>::inc_consumers(&1));
|
||||
assert_noop!(
|
||||
Balances::force_set_balance(RuntimeOrigin::root(), 1, 0),
|
||||
DispatchError::ConsumerRemaining,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_balance_handles_total_issuance() {
|
||||
ExtBuilder::default().build_and_execute_with(|| {
|
||||
let old_total_issuance = Balances::total_issuance();
|
||||
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1337, 69));
|
||||
assert_eq!(Balances::total_issuance(), old_total_issuance + 69);
|
||||
assert_eq!(Balances::total_balance(&1337), 69);
|
||||
assert_eq!(Balances::free_balance(&1337), 69);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_accounts_should_work() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(1)
|
||||
.monied(true)
|
||||
.build_and_execute_with(|| {
|
||||
System::inc_providers(&7);
|
||||
assert_ok!(<Test as Config>::AccountStore::try_mutate_exists(
|
||||
&7,
|
||||
|a| -> DispatchResult {
|
||||
*a = Some(AccountData {
|
||||
free: 5,
|
||||
reserved: 5,
|
||||
frozen: Zero::zero(),
|
||||
flags: crate::types::ExtraFlags::old_logic(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
));
|
||||
assert!(!Balances::account(&7).flags.is_new_logic());
|
||||
assert_eq!(System::providers(&7), 1);
|
||||
assert_eq!(System::consumers(&7), 0);
|
||||
assert_ok!(Balances::upgrade_accounts(Some(1).into(), vec![7]));
|
||||
assert!(Balances::account(&7).flags.is_new_logic());
|
||||
assert_eq!(System::providers(&7), 1);
|
||||
assert_eq!(System::consumers(&7), 1);
|
||||
|
||||
<Balances as frame_support::traits::ReservableCurrency<_>>::unreserve(&7, 5);
|
||||
assert_ok!(<Balances as fungible::Mutate<_>>::transfer(&7, &1, 10, Expendable));
|
||||
assert_eq!(Balances::total_balance(&7), 0);
|
||||
assert_eq!(System::providers(&7), 0);
|
||||
assert_eq!(System::consumers(&7), 0);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Tests regarding the functionality of the `fungible` trait set implementations.
|
||||
|
||||
use super::*;
|
||||
use frame_support::traits::tokens::{
|
||||
Fortitude::{Force, Polite},
|
||||
Precision::{BestEffort, Exact},
|
||||
Preservation::{Expendable, Preserve, Protect},
|
||||
Restriction::Free,
|
||||
};
|
||||
use fungible::{Inspect, InspectFreeze, InspectHold, Mutate, MutateFreeze, MutateHold, Unbalanced};
|
||||
|
||||
#[test]
|
||||
fn inspect_trait_reducible_balance_basic_works() {
|
||||
ExtBuilder::default().existential_deposit(10).build_and_execute_with(|| {
|
||||
Balances::set_balance(&1, 100);
|
||||
assert_eq!(Balances::reducible_balance(&1, Expendable, Polite), 100);
|
||||
assert_eq!(Balances::reducible_balance(&1, Protect, Polite), 90);
|
||||
assert_eq!(Balances::reducible_balance(&1, Preserve, Polite), 90);
|
||||
assert_eq!(Balances::reducible_balance(&1, Expendable, Force), 100);
|
||||
assert_eq!(Balances::reducible_balance(&1, Protect, Force), 90);
|
||||
assert_eq!(Balances::reducible_balance(&1, Preserve, Force), 90);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inspect_trait_reducible_balance_other_provide_works() {
|
||||
ExtBuilder::default().existential_deposit(10).build_and_execute_with(|| {
|
||||
Balances::set_balance(&1, 100);
|
||||
System::inc_providers(&1);
|
||||
assert_eq!(Balances::reducible_balance(&1, Expendable, Polite), 100);
|
||||
assert_eq!(Balances::reducible_balance(&1, Protect, Polite), 100);
|
||||
assert_eq!(Balances::reducible_balance(&1, Preserve, Polite), 90);
|
||||
assert_eq!(Balances::reducible_balance(&1, Expendable, Force), 100);
|
||||
assert_eq!(Balances::reducible_balance(&1, Protect, Force), 100);
|
||||
assert_eq!(Balances::reducible_balance(&1, Preserve, Force), 90);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inspect_trait_reducible_balance_frozen_works() {
|
||||
ExtBuilder::default().existential_deposit(10).build_and_execute_with(|| {
|
||||
Balances::set_balance(&1, 100);
|
||||
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 50));
|
||||
assert_eq!(Balances::reducible_balance(&1, Expendable, Polite), 50);
|
||||
assert_eq!(Balances::reducible_balance(&1, Protect, Polite), 50);
|
||||
assert_eq!(Balances::reducible_balance(&1, Preserve, Polite), 50);
|
||||
assert_eq!(Balances::reducible_balance(&1, Expendable, Force), 90);
|
||||
assert_eq!(Balances::reducible_balance(&1, Protect, Force), 90);
|
||||
assert_eq!(Balances::reducible_balance(&1, Preserve, Force), 90);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbalanced_trait_set_balance_works() {
|
||||
ExtBuilder::default().build_and_execute_with(|| {
|
||||
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 0);
|
||||
assert_ok!(Balances::write_balance(&1337, 100));
|
||||
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 100);
|
||||
|
||||
assert_ok!(<Balances as fungible::MutateHold<_>>::hold(&TestId::Foo, &1337, 60));
|
||||
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 40);
|
||||
assert_eq!(<Balances as fungible::InspectHold<_>>::total_balance_on_hold(&1337), 60);
|
||||
assert_eq!(
|
||||
<Balances as fungible::InspectHold<_>>::balance_on_hold(&TestId::Foo, &1337),
|
||||
60
|
||||
);
|
||||
|
||||
assert_noop!(Balances::write_balance(&1337, 0), Error::<Test>::InsufficientBalance);
|
||||
|
||||
assert_ok!(Balances::write_balance(&1337, 1));
|
||||
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 1);
|
||||
assert_eq!(
|
||||
<Balances as fungible::InspectHold<_>>::balance_on_hold(&TestId::Foo, &1337),
|
||||
60
|
||||
);
|
||||
|
||||
assert_ok!(<Balances as fungible::MutateHold<_>>::release(&TestId::Foo, &1337, 60, Exact));
|
||||
assert_eq!(<Balances as fungible::InspectHold<_>>::balance_on_hold(&TestId::Foo, &1337), 0);
|
||||
assert_eq!(<Balances as fungible::InspectHold<_>>::total_balance_on_hold(&1337), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbalanced_trait_set_total_issuance_works() {
|
||||
ExtBuilder::default().build_and_execute_with(|| {
|
||||
assert_eq!(<Balances as fungible::Inspect<_>>::total_issuance(), 0);
|
||||
Balances::set_total_issuance(100);
|
||||
assert_eq!(<Balances as fungible::Inspect<_>>::total_issuance(), 100);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbalanced_trait_decrease_balance_simple_works() {
|
||||
ExtBuilder::default().build_and_execute_with(|| {
|
||||
// An Account that starts at 100
|
||||
assert_ok!(Balances::write_balance(&1337, 100));
|
||||
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 100);
|
||||
// and reserves 50
|
||||
assert_ok!(<Balances as fungible::MutateHold<_>>::hold(&TestId::Foo, &1337, 50));
|
||||
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 50);
|
||||
// and is decreased by 20
|
||||
assert_ok!(Balances::decrease_balance(&1337, 20, Exact, Expendable, Polite));
|
||||
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 30);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbalanced_trait_decrease_balance_works_1() {
|
||||
ExtBuilder::default().build_and_execute_with(|| {
|
||||
assert_ok!(Balances::write_balance(&1337, 100));
|
||||
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 100);
|
||||
|
||||
assert_noop!(
|
||||
Balances::decrease_balance(&1337, 101, Exact, Expendable, Polite),
|
||||
TokenError::FundsUnavailable
|
||||
);
|
||||
assert_eq!(Balances::decrease_balance(&1337, 100, Exact, Expendable, Polite), Ok(100));
|
||||
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbalanced_trait_decrease_balance_works_2() {
|
||||
ExtBuilder::default().build_and_execute_with(|| {
|
||||
// free: 40, reserved: 60
|
||||
assert_ok!(Balances::write_balance(&1337, 100));
|
||||
assert_ok!(Balances::hold(&TestId::Foo, &1337, 60));
|
||||
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 40);
|
||||
assert_eq!(Balances::total_balance_on_hold(&1337), 60);
|
||||
assert_noop!(
|
||||
Balances::decrease_balance(&1337, 40, Exact, Expendable, Polite),
|
||||
Error::<Test>::InsufficientBalance
|
||||
);
|
||||
assert_eq!(Balances::decrease_balance(&1337, 39, Exact, Expendable, Polite), Ok(39));
|
||||
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 1);
|
||||
assert_eq!(Balances::total_balance_on_hold(&1337), 60);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbalanced_trait_decrease_balance_at_most_works_1() {
|
||||
ExtBuilder::default().build_and_execute_with(|| {
|
||||
assert_ok!(Balances::write_balance(&1337, 100));
|
||||
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 100);
|
||||
|
||||
assert_eq!(Balances::decrease_balance(&1337, 101, BestEffort, Expendable, Polite), Ok(100));
|
||||
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbalanced_trait_decrease_balance_at_most_works_2() {
|
||||
ExtBuilder::default().build_and_execute_with(|| {
|
||||
assert_ok!(Balances::write_balance(&1337, 99));
|
||||
assert_eq!(Balances::decrease_balance(&1337, 99, BestEffort, Expendable, Polite), Ok(99));
|
||||
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbalanced_trait_decrease_balance_at_most_works_3() {
|
||||
ExtBuilder::default().build_and_execute_with(|| {
|
||||
// free: 40, reserved: 60
|
||||
assert_ok!(Balances::write_balance(&1337, 100));
|
||||
assert_ok!(Balances::hold(&TestId::Foo, &1337, 60));
|
||||
assert_eq!(Balances::free_balance(1337), 40);
|
||||
assert_eq!(Balances::total_balance_on_hold(&1337), 60);
|
||||
assert_eq!(Balances::decrease_balance(&1337, 0, BestEffort, Expendable, Polite), Ok(0));
|
||||
assert_eq!(Balances::free_balance(1337), 40);
|
||||
assert_eq!(Balances::total_balance_on_hold(&1337), 60);
|
||||
assert_eq!(Balances::decrease_balance(&1337, 10, BestEffort, Expendable, Polite), Ok(10));
|
||||
assert_eq!(Balances::free_balance(1337), 30);
|
||||
assert_eq!(Balances::decrease_balance(&1337, 200, BestEffort, Expendable, Polite), Ok(29));
|
||||
assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 1);
|
||||
assert_eq!(Balances::free_balance(1337), 1);
|
||||
assert_eq!(Balances::total_balance_on_hold(&1337), 60);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbalanced_trait_increase_balance_works() {
|
||||
ExtBuilder::default().build_and_execute_with(|| {
|
||||
assert_noop!(Balances::increase_balance(&1337, 0, Exact), TokenError::BelowMinimum);
|
||||
assert_eq!(Balances::increase_balance(&1337, 1, Exact), Ok(1));
|
||||
assert_noop!(Balances::increase_balance(&1337, u64::MAX, Exact), ArithmeticError::Overflow);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbalanced_trait_increase_balance_at_most_works() {
|
||||
ExtBuilder::default().build_and_execute_with(|| {
|
||||
assert_eq!(Balances::increase_balance(&1337, 0, BestEffort), Ok(0));
|
||||
assert_eq!(Balances::increase_balance(&1337, 1, BestEffort), Ok(1));
|
||||
assert_eq!(Balances::increase_balance(&1337, u64::MAX, BestEffort), Ok(u64::MAX - 1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn freezing_and_holds_should_overlap() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(1)
|
||||
.monied(true)
|
||||
.build_and_execute_with(|| {
|
||||
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10));
|
||||
assert_ok!(Balances::hold(&TestId::Foo, &1, 9));
|
||||
assert_eq!(Balances::account(&1).free, 1);
|
||||
assert_eq!(System::consumers(&1), 1);
|
||||
assert_eq!(Balances::account(&1).free, 1);
|
||||
assert_eq!(Balances::account(&1).frozen, 10);
|
||||
assert_eq!(Balances::account(&1).reserved, 9);
|
||||
assert_eq!(Balances::total_balance_on_hold(&1), 9);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frozen_hold_balance_cannot_be_moved_without_force() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(1)
|
||||
.monied(true)
|
||||
.build_and_execute_with(|| {
|
||||
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10));
|
||||
assert_ok!(Balances::hold(&TestId::Foo, &1, 9));
|
||||
assert_eq!(Balances::reducible_total_balance_on_hold(&1, Force), 9);
|
||||
assert_eq!(Balances::reducible_total_balance_on_hold(&1, Polite), 0);
|
||||
let e = TokenError::Frozen;
|
||||
assert_noop!(
|
||||
Balances::transfer_on_hold(&TestId::Foo, &1, &2, 1, Exact, Free, Polite),
|
||||
e
|
||||
);
|
||||
assert_ok!(Balances::transfer_on_hold(&TestId::Foo, &1, &2, 1, Exact, Free, Force));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frozen_hold_balance_best_effort_transfer_works() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(1)
|
||||
.monied(true)
|
||||
.build_and_execute_with(|| {
|
||||
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5));
|
||||
assert_ok!(Balances::hold(&TestId::Foo, &1, 9));
|
||||
assert_eq!(Balances::reducible_total_balance_on_hold(&1, Force), 9);
|
||||
assert_eq!(Balances::reducible_total_balance_on_hold(&1, Polite), 5);
|
||||
assert_ok!(Balances::transfer_on_hold(
|
||||
&TestId::Foo,
|
||||
&1,
|
||||
&2,
|
||||
10,
|
||||
BestEffort,
|
||||
Free,
|
||||
Polite
|
||||
));
|
||||
assert_eq!(Balances::total_balance(&1), 5);
|
||||
assert_eq!(Balances::total_balance(&2), 25);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_freezing_should_work() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(1)
|
||||
.monied(true)
|
||||
.build_and_execute_with(|| {
|
||||
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5));
|
||||
assert_eq!(System::consumers(&1), 1);
|
||||
assert_ok!(<Balances as fungible::Mutate<_>>::transfer(&1, &2, 5, Expendable));
|
||||
assert_noop!(
|
||||
<Balances as fungible::Mutate<_>>::transfer(&1, &2, 1, Expendable),
|
||||
TokenError::Frozen
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thaw_should_work() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(1)
|
||||
.monied(true)
|
||||
.build_and_execute_with(|| {
|
||||
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, u64::MAX));
|
||||
assert_ok!(Balances::thaw(&TestId::Foo, &1));
|
||||
assert_eq!(System::consumers(&1), 0);
|
||||
assert_eq!(Balances::balance_frozen(&TestId::Foo, &1), 0);
|
||||
assert_eq!(Balances::account(&1).frozen, 0);
|
||||
assert_ok!(<Balances as fungible::Mutate<_>>::transfer(&1, &2, 10, Expendable));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_freeze_zero_should_work() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(1)
|
||||
.monied(true)
|
||||
.build_and_execute_with(|| {
|
||||
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, u64::MAX));
|
||||
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 0));
|
||||
assert_eq!(System::consumers(&1), 0);
|
||||
assert_eq!(Balances::balance_frozen(&TestId::Foo, &1), 0);
|
||||
assert_eq!(Balances::account(&1).frozen, 0);
|
||||
assert_ok!(<Balances as fungible::Mutate<_>>::transfer(&1, &2, 10, Expendable));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_freeze_should_work() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(1)
|
||||
.monied(true)
|
||||
.build_and_execute_with(|| {
|
||||
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, u64::MAX));
|
||||
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5));
|
||||
assert_ok!(<Balances as fungible::Mutate<_>>::transfer(&1, &2, 5, Expendable));
|
||||
assert_noop!(
|
||||
<Balances as fungible::Mutate<_>>::transfer(&1, &2, 1, Expendable),
|
||||
TokenError::Frozen
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extend_freeze_should_work() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(1)
|
||||
.monied(true)
|
||||
.build_and_execute_with(|| {
|
||||
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5));
|
||||
assert_ok!(Balances::extend_freeze(&TestId::Foo, &1, 10));
|
||||
assert_eq!(Balances::account(&1).frozen, 10);
|
||||
assert_eq!(Balances::balance_frozen(&TestId::Foo, &1), 10);
|
||||
assert_noop!(
|
||||
<Balances as fungible::Mutate<_>>::transfer(&1, &2, 1, Expendable),
|
||||
TokenError::Frozen
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_freezing_should_work() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(1)
|
||||
.monied(true)
|
||||
.build_and_execute_with(|| {
|
||||
assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5));
|
||||
assert_ok!(Balances::set_freeze(&TestId::Bar, &1, 5));
|
||||
assert_eq!(System::consumers(&1), 1);
|
||||
assert_ok!(<Balances as fungible::Mutate<_>>::transfer(&1, &2, 5, Expendable));
|
||||
assert_noop!(
|
||||
<Balances as fungible::Mutate<_>>::transfer(&1, &2, 1, Expendable),
|
||||
TokenError::Frozen
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_hold_entire_balance_when_second_provider() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(1)
|
||||
.monied(false)
|
||||
.build_and_execute_with(|| {
|
||||
<Balances as fungible::Mutate<_>>::set_balance(&1, 100);
|
||||
assert_noop!(Balances::hold(&TestId::Foo, &1, 100), TokenError::FundsUnavailable);
|
||||
System::inc_providers(&1);
|
||||
assert_eq!(System::providers(&1), 2);
|
||||
assert_ok!(Balances::hold(&TestId::Foo, &1, 100));
|
||||
assert_eq!(System::providers(&1), 1);
|
||||
assert_noop!(System::dec_providers(&1), DispatchError::ConsumerRemaining);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unholding_frees_hold_slot() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(1)
|
||||
.monied(false)
|
||||
.build_and_execute_with(|| {
|
||||
<Balances as fungible::Mutate<_>>::set_balance(&1, 100);
|
||||
assert_ok!(Balances::hold(&TestId::Foo, &1, 10));
|
||||
assert_ok!(Balances::hold(&TestId::Bar, &1, 10));
|
||||
assert_ok!(Balances::release(&TestId::Foo, &1, 10, Exact));
|
||||
assert_ok!(Balances::hold(&TestId::Baz, &1, 10));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Tests.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate::{self as pallet_balances, AccountData, Config, CreditOf, Error, Pallet};
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use frame_support::{
|
||||
assert_err, assert_noop, assert_ok, assert_storage_noop,
|
||||
dispatch::DispatchInfo,
|
||||
parameter_types,
|
||||
traits::{
|
||||
tokens::fungible, ConstU32, ConstU64, ConstU8, Imbalance as ImbalanceT, OnUnbalanced,
|
||||
StorageMapShim, StoredMap,
|
||||
},
|
||||
weights::{IdentityFee, Weight},
|
||||
RuntimeDebug,
|
||||
};
|
||||
use frame_system::{self as system, RawOrigin};
|
||||
use pallet_transaction_payment::{ChargeTransactionPayment, CurrencyAdapter, Multiplier};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_core::H256;
|
||||
use sp_io;
|
||||
use sp_runtime::{
|
||||
testing::Header,
|
||||
traits::{BadOrigin, IdentityLookup, SignedExtension, Zero},
|
||||
ArithmeticError, DispatchError, DispatchResult, FixedPointNumber, TokenError,
|
||||
};
|
||||
|
||||
mod currency_tests;
|
||||
mod dispatchable_tests;
|
||||
mod fungible_tests;
|
||||
mod reentrancy_tests;
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
#[derive(
|
||||
Encode,
|
||||
Decode,
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
MaxEncodedLen,
|
||||
TypeInfo,
|
||||
RuntimeDebug,
|
||||
)]
|
||||
pub enum TestId {
|
||||
Foo,
|
||||
Bar,
|
||||
Baz,
|
||||
}
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub struct Test where
|
||||
Block = Block,
|
||||
NodeBlock = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic,
|
||||
{
|
||||
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
|
||||
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
|
||||
TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event<T>},
|
||||
}
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub BlockWeights: frame_system::limits::BlockWeights =
|
||||
frame_system::limits::BlockWeights::simple_max(
|
||||
frame_support::weights::Weight::from_parts(1024, u64::MAX),
|
||||
);
|
||||
pub static ExistentialDeposit: u64 = 0;
|
||||
}
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type BlockWeights = BlockWeights;
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Hash = H256;
|
||||
type Hashing = ::sp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = ConstU64<250>;
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = super::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
}
|
||||
|
||||
impl pallet_transaction_payment::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type OnChargeTransaction = CurrencyAdapter<Pallet<Test>, ()>;
|
||||
type OperationalFeeMultiplier = ConstU8<5>;
|
||||
type WeightToFee = IdentityFee<u64>;
|
||||
type LengthToFee = IdentityFee<u64>;
|
||||
type FeeMultiplierUpdate = ();
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type Balance = u64;
|
||||
type DustRemoval = DustTrap;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = TestAccountStore;
|
||||
type MaxLocks = ConstU32<50>;
|
||||
type MaxReserves = ConstU32<2>;
|
||||
type ReserveIdentifier = TestId;
|
||||
type WeightInfo = ();
|
||||
type HoldIdentifier = TestId;
|
||||
type FreezeIdentifier = TestId;
|
||||
type MaxFreezes = ConstU32<2>;
|
||||
type MaxHolds = ConstU32<2>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExtBuilder {
|
||||
existential_deposit: u64,
|
||||
monied: bool,
|
||||
dust_trap: Option<u64>,
|
||||
}
|
||||
impl Default for ExtBuilder {
|
||||
fn default() -> Self {
|
||||
Self { existential_deposit: 1, monied: false, dust_trap: None }
|
||||
}
|
||||
}
|
||||
impl ExtBuilder {
|
||||
pub fn existential_deposit(mut self, existential_deposit: u64) -> Self {
|
||||
self.existential_deposit = existential_deposit;
|
||||
self
|
||||
}
|
||||
pub fn monied(mut self, monied: bool) -> Self {
|
||||
self.monied = monied;
|
||||
if self.existential_deposit == 0 {
|
||||
self.existential_deposit = 1;
|
||||
}
|
||||
self
|
||||
}
|
||||
pub fn dust_trap(mut self, account: u64) -> Self {
|
||||
self.dust_trap = Some(account);
|
||||
self
|
||||
}
|
||||
pub fn set_associated_consts(&self) {
|
||||
DUST_TRAP_TARGET.with(|v| v.replace(self.dust_trap));
|
||||
EXISTENTIAL_DEPOSIT.with(|v| v.replace(self.existential_deposit));
|
||||
}
|
||||
pub fn build(self) -> sp_io::TestExternalities {
|
||||
self.set_associated_consts();
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
pallet_balances::GenesisConfig::<Test> {
|
||||
balances: if self.monied {
|
||||
vec![
|
||||
(1, 10 * self.existential_deposit),
|
||||
(2, 20 * self.existential_deposit),
|
||||
(3, 30 * self.existential_deposit),
|
||||
(4, 40 * self.existential_deposit),
|
||||
(12, 10 * self.existential_deposit),
|
||||
]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
pub fn build_and_execute_with(self, f: impl Fn()) {
|
||||
let other = self.clone();
|
||||
UseSystem::set(false);
|
||||
other.build().execute_with(|| f());
|
||||
UseSystem::set(true);
|
||||
self.build().execute_with(|| f());
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
static DustTrapTarget: Option<u64> = None;
|
||||
}
|
||||
|
||||
pub struct DustTrap;
|
||||
|
||||
impl OnUnbalanced<CreditOf<Test, ()>> for DustTrap {
|
||||
fn on_nonzero_unbalanced(amount: CreditOf<Test, ()>) {
|
||||
match DustTrapTarget::get() {
|
||||
None => drop(amount),
|
||||
Some(a) => {
|
||||
let result = <Balances as fungible::Balanced<_>>::resolve(&a, amount);
|
||||
debug_assert!(result.is_ok());
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static UseSystem: bool = false;
|
||||
}
|
||||
|
||||
type BalancesAccountStore = StorageMapShim<super::Account<Test>, u64, super::AccountData<u64>>;
|
||||
type SystemAccountStore = frame_system::Pallet<Test>;
|
||||
|
||||
pub struct TestAccountStore;
|
||||
impl StoredMap<u64, super::AccountData<u64>> for TestAccountStore {
|
||||
fn get(k: &u64) -> super::AccountData<u64> {
|
||||
if UseSystem::get() {
|
||||
<SystemAccountStore as StoredMap<_, _>>::get(k)
|
||||
} else {
|
||||
<BalancesAccountStore as StoredMap<_, _>>::get(k)
|
||||
}
|
||||
}
|
||||
fn try_mutate_exists<R, E: From<DispatchError>>(
|
||||
k: &u64,
|
||||
f: impl FnOnce(&mut Option<super::AccountData<u64>>) -> Result<R, E>,
|
||||
) -> Result<R, E> {
|
||||
if UseSystem::get() {
|
||||
<SystemAccountStore as StoredMap<_, _>>::try_mutate_exists(k, f)
|
||||
} else {
|
||||
<BalancesAccountStore as StoredMap<_, _>>::try_mutate_exists(k, f)
|
||||
}
|
||||
}
|
||||
fn mutate<R>(
|
||||
k: &u64,
|
||||
f: impl FnOnce(&mut super::AccountData<u64>) -> R,
|
||||
) -> Result<R, DispatchError> {
|
||||
if UseSystem::get() {
|
||||
<SystemAccountStore as StoredMap<_, _>>::mutate(k, f)
|
||||
} else {
|
||||
<BalancesAccountStore as StoredMap<_, _>>::mutate(k, f)
|
||||
}
|
||||
}
|
||||
fn mutate_exists<R>(
|
||||
k: &u64,
|
||||
f: impl FnOnce(&mut Option<super::AccountData<u64>>) -> R,
|
||||
) -> Result<R, DispatchError> {
|
||||
if UseSystem::get() {
|
||||
<SystemAccountStore as StoredMap<_, _>>::mutate_exists(k, f)
|
||||
} else {
|
||||
<BalancesAccountStore as StoredMap<_, _>>::mutate_exists(k, f)
|
||||
}
|
||||
}
|
||||
fn insert(k: &u64, t: super::AccountData<u64>) -> Result<(), DispatchError> {
|
||||
if UseSystem::get() {
|
||||
<SystemAccountStore as StoredMap<_, _>>::insert(k, t)
|
||||
} else {
|
||||
<BalancesAccountStore as StoredMap<_, _>>::insert(k, t)
|
||||
}
|
||||
}
|
||||
fn remove(k: &u64) -> Result<(), DispatchError> {
|
||||
if UseSystem::get() {
|
||||
<SystemAccountStore as StoredMap<_, _>>::remove(k)
|
||||
} else {
|
||||
<BalancesAccountStore as StoredMap<_, _>>::remove(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn events() -> Vec<RuntimeEvent> {
|
||||
let evt = System::events().into_iter().map(|evt| evt.event).collect::<Vec<_>>();
|
||||
System::reset_events();
|
||||
evt
|
||||
}
|
||||
|
||||
/// create a transaction info struct from weight. Handy to avoid building the whole struct.
|
||||
pub fn info_from_weight(w: Weight) -> DispatchInfo {
|
||||
DispatchInfo { weight: w, ..Default::default() }
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Tests regarding the reentrancy functionality.
|
||||
|
||||
use super::*;
|
||||
use frame_support::traits::tokens::{
|
||||
Fortitude::Force,
|
||||
Precision::BestEffort,
|
||||
Preservation::{Expendable, Protect},
|
||||
};
|
||||
use fungible::Balanced;
|
||||
|
||||
#[test]
|
||||
fn transfer_dust_removal_tst1_should_work() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(100)
|
||||
.dust_trap(1)
|
||||
.build_and_execute_with(|| {
|
||||
// Verification of reentrancy in dust removal
|
||||
assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 1000));
|
||||
assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 2, 500));
|
||||
|
||||
// In this transaction, account 2 free balance
|
||||
// drops below existential balance
|
||||
// and dust balance is removed from account 2
|
||||
assert_ok!(Balances::transfer_allow_death(RawOrigin::Signed(2).into(), 3, 450));
|
||||
|
||||
// As expected dust balance is removed.
|
||||
assert_eq!(Balances::free_balance(&2), 0);
|
||||
|
||||
// As expected beneficiary account 3
|
||||
// received the transfered fund.
|
||||
assert_eq!(Balances::free_balance(&3), 450);
|
||||
|
||||
// Dust balance is deposited to account 1
|
||||
// during the process of dust removal.
|
||||
assert_eq!(Balances::free_balance(&1), 1050);
|
||||
|
||||
// Verify the events
|
||||
assert_eq!(System::events().len(), 12);
|
||||
|
||||
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer {
|
||||
from: 2,
|
||||
to: 3,
|
||||
amount: 450,
|
||||
}));
|
||||
System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost {
|
||||
account: 2,
|
||||
amount: 50,
|
||||
}));
|
||||
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit {
|
||||
who: 1,
|
||||
amount: 50,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_dust_removal_tst2_should_work() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(100)
|
||||
.dust_trap(1)
|
||||
.build_and_execute_with(|| {
|
||||
// Verification of reentrancy in dust removal
|
||||
assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 1000));
|
||||
assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 2, 500));
|
||||
|
||||
// In this transaction, account 2 free balance
|
||||
// drops below existential balance
|
||||
// and dust balance is removed from account 2
|
||||
assert_ok!(Balances::transfer_allow_death(RawOrigin::Signed(2).into(), 1, 450));
|
||||
|
||||
// As expected dust balance is removed.
|
||||
assert_eq!(Balances::free_balance(&2), 0);
|
||||
|
||||
// Dust balance is deposited to account 1
|
||||
// during the process of dust removal.
|
||||
assert_eq!(Balances::free_balance(&1), 1500);
|
||||
|
||||
// Verify the events
|
||||
assert_eq!(System::events().len(), 10);
|
||||
|
||||
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer {
|
||||
from: 2,
|
||||
to: 1,
|
||||
amount: 450,
|
||||
}));
|
||||
System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost {
|
||||
account: 2,
|
||||
amount: 50,
|
||||
}));
|
||||
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit {
|
||||
who: 1,
|
||||
amount: 50,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repatriating_reserved_balance_dust_removal_should_work() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(100)
|
||||
.dust_trap(1)
|
||||
.build_and_execute_with(|| {
|
||||
// Verification of reentrancy in dust removal
|
||||
assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 1000));
|
||||
assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 2, 500));
|
||||
|
||||
// Reserve a value on account 2,
|
||||
// Such that free balance is lower than
|
||||
// Exestintial deposit.
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), 1, 450));
|
||||
|
||||
// Since free balance of account 2 is lower than
|
||||
// existential deposit, dust amount is
|
||||
// removed from the account 2
|
||||
assert_eq!(Balances::reserved_balance(2), 0);
|
||||
assert_eq!(Balances::free_balance(2), 0);
|
||||
|
||||
// account 1 is credited with reserved amount
|
||||
// together with dust balance during dust
|
||||
// removal.
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
assert_eq!(Balances::free_balance(1), 1500);
|
||||
|
||||
// Verify the events
|
||||
assert_eq!(System::events().len(), 10);
|
||||
|
||||
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer {
|
||||
from: 2,
|
||||
to: 1,
|
||||
amount: 450,
|
||||
}));
|
||||
System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost {
|
||||
account: 2,
|
||||
amount: 50,
|
||||
}));
|
||||
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit {
|
||||
who: 1,
|
||||
amount: 50,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_events_with_no_existential_deposit_suicide_with_dust() {
|
||||
ExtBuilder::default().existential_deposit(2).build_and_execute_with(|| {
|
||||
assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 100));
|
||||
|
||||
assert_eq!(
|
||||
events(),
|
||||
[
|
||||
RuntimeEvent::System(system::Event::NewAccount { account: 1 }),
|
||||
RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }),
|
||||
RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100 }),
|
||||
]
|
||||
);
|
||||
|
||||
let res = Balances::withdraw(&1, 98, BestEffort, Protect, Force);
|
||||
assert_eq!(res.unwrap().peek(), 98);
|
||||
|
||||
// no events
|
||||
assert_eq!(
|
||||
events(),
|
||||
[RuntimeEvent::Balances(crate::Event::Withdraw { who: 1, amount: 98 })]
|
||||
);
|
||||
|
||||
let res = Balances::withdraw(&1, 1, BestEffort, Expendable, Force);
|
||||
assert_eq!(res.unwrap().peek(), 1);
|
||||
|
||||
assert_eq!(
|
||||
events(),
|
||||
[
|
||||
RuntimeEvent::System(system::Event::KilledAccount { account: 1 }),
|
||||
RuntimeEvent::Balances(crate::Event::DustLost { account: 1, amount: 1 }),
|
||||
RuntimeEvent::Balances(crate::Event::Withdraw { who: 1, amount: 1 })
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test utilities
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate::{self as pallet_balances, decl_tests, Config, Pallet};
|
||||
use frame_support::{
|
||||
dispatch::DispatchInfo,
|
||||
parameter_types,
|
||||
traits::{ConstU32, ConstU64, ConstU8},
|
||||
weights::{IdentityFee, Weight},
|
||||
};
|
||||
use pallet_transaction_payment::CurrencyAdapter;
|
||||
use sp_core::H256;
|
||||
use sp_io;
|
||||
use sp_runtime::{testing::Header, traits::IdentityLookup};
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Test where
|
||||
Block = Block,
|
||||
NodeBlock = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic,
|
||||
{
|
||||
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
|
||||
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
|
||||
TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event<T>},
|
||||
}
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub BlockWeights: frame_system::limits::BlockWeights =
|
||||
frame_system::limits::BlockWeights::simple_max(
|
||||
frame_support::weights::Weight::from_parts(1024, u64::MAX),
|
||||
);
|
||||
pub static ExistentialDeposit: u64 = 0;
|
||||
}
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type BlockWeights = BlockWeights;
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Hash = H256;
|
||||
type Hashing = ::sp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = ConstU64<250>;
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = super::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
}
|
||||
|
||||
impl pallet_transaction_payment::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type OnChargeTransaction = CurrencyAdapter<Pallet<Test>, ()>;
|
||||
type OperationalFeeMultiplier = ConstU8<5>;
|
||||
type WeightToFee = IdentityFee<u64>;
|
||||
type LengthToFee = IdentityFee<u64>;
|
||||
type FeeMultiplierUpdate = ();
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type Balance = u64;
|
||||
type DustRemoval = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = frame_system::Pallet<Test>;
|
||||
type MaxLocks = ();
|
||||
type MaxReserves = ConstU32<2>;
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
pub struct ExtBuilder {
|
||||
existential_deposit: u64,
|
||||
monied: bool,
|
||||
}
|
||||
impl Default for ExtBuilder {
|
||||
fn default() -> Self {
|
||||
Self { existential_deposit: 1, monied: false }
|
||||
}
|
||||
}
|
||||
impl ExtBuilder {
|
||||
pub fn existential_deposit(mut self, existential_deposit: u64) -> Self {
|
||||
self.existential_deposit = existential_deposit;
|
||||
self
|
||||
}
|
||||
pub fn monied(mut self, monied: bool) -> Self {
|
||||
self.monied = monied;
|
||||
self
|
||||
}
|
||||
pub fn set_associated_consts(&self) {
|
||||
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
|
||||
}
|
||||
pub fn build(self) -> sp_io::TestExternalities {
|
||||
self.set_associated_consts();
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
pallet_balances::GenesisConfig::<Test> {
|
||||
balances: if self.monied {
|
||||
vec![
|
||||
(1, 10 * self.existential_deposit),
|
||||
(2, 20 * self.existential_deposit),
|
||||
(3, 30 * self.existential_deposit),
|
||||
(4, 40 * self.existential_deposit),
|
||||
(12, 10 * self.existential_deposit),
|
||||
]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
}
|
||||
|
||||
decl_tests! { Test, ExtBuilder, EXISTENTIAL_DEPOSIT }
|
||||
@@ -1,191 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test utilities
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate::{self as pallet_balances, decl_tests, Config, Pallet};
|
||||
use frame_support::{
|
||||
dispatch::DispatchInfo,
|
||||
parameter_types,
|
||||
traits::{ConstU32, ConstU64, ConstU8, StorageMapShim},
|
||||
weights::{IdentityFee, Weight},
|
||||
};
|
||||
use pallet_transaction_payment::CurrencyAdapter;
|
||||
use sp_core::H256;
|
||||
use sp_io;
|
||||
use sp_runtime::{testing::Header, traits::IdentityLookup};
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub struct Test where
|
||||
Block = Block,
|
||||
NodeBlock = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic,
|
||||
{
|
||||
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
|
||||
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
|
||||
TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event<T>},
|
||||
}
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub BlockWeights: frame_system::limits::BlockWeights =
|
||||
frame_system::limits::BlockWeights::simple_max(
|
||||
frame_support::weights::Weight::from_parts(1024, u64::MAX),
|
||||
);
|
||||
pub static ExistentialDeposit: u64 = 0;
|
||||
}
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type BlockWeights = BlockWeights;
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Hash = H256;
|
||||
type Hashing = ::sp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = ConstU64<250>;
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
}
|
||||
|
||||
impl pallet_transaction_payment::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type OnChargeTransaction = CurrencyAdapter<Pallet<Test>, ()>;
|
||||
type OperationalFeeMultiplier = ConstU8<5>;
|
||||
type WeightToFee = IdentityFee<u64>;
|
||||
type LengthToFee = IdentityFee<u64>;
|
||||
type FeeMultiplierUpdate = ();
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type Balance = u64;
|
||||
type DustRemoval = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore =
|
||||
StorageMapShim<super::Account<Test>, system::Provider<Test>, u64, super::AccountData<u64>>;
|
||||
type MaxLocks = ConstU32<50>;
|
||||
type MaxReserves = ConstU32<2>;
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
pub struct ExtBuilder {
|
||||
existential_deposit: u64,
|
||||
monied: bool,
|
||||
}
|
||||
impl Default for ExtBuilder {
|
||||
fn default() -> Self {
|
||||
Self { existential_deposit: 1, monied: false }
|
||||
}
|
||||
}
|
||||
impl ExtBuilder {
|
||||
pub fn existential_deposit(mut self, existential_deposit: u64) -> Self {
|
||||
self.existential_deposit = existential_deposit;
|
||||
self
|
||||
}
|
||||
pub fn monied(mut self, monied: bool) -> Self {
|
||||
self.monied = monied;
|
||||
if self.existential_deposit == 0 {
|
||||
self.existential_deposit = 1;
|
||||
}
|
||||
self
|
||||
}
|
||||
pub fn set_associated_consts(&self) {
|
||||
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
|
||||
}
|
||||
pub fn build(self) -> sp_io::TestExternalities {
|
||||
self.set_associated_consts();
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
pallet_balances::GenesisConfig::<Test> {
|
||||
balances: if self.monied {
|
||||
vec![
|
||||
(1, 10 * self.existential_deposit),
|
||||
(2, 20 * self.existential_deposit),
|
||||
(3, 30 * self.existential_deposit),
|
||||
(4, 40 * self.existential_deposit),
|
||||
(12, 10 * self.existential_deposit),
|
||||
]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
}
|
||||
|
||||
decl_tests! { Test, ExtBuilder, EXISTENTIAL_DEPOSIT }
|
||||
|
||||
#[test]
|
||||
fn emit_events_with_no_existential_deposit_suicide_with_dust() {
|
||||
<ExtBuilder>::default().existential_deposit(2).build().execute_with(|| {
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 100, 0));
|
||||
|
||||
assert_eq!(
|
||||
events(),
|
||||
[
|
||||
RuntimeEvent::System(system::Event::NewAccount { account: 1 }),
|
||||
RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }),
|
||||
RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100, reserved: 0 }),
|
||||
]
|
||||
);
|
||||
|
||||
let res = Balances::slash(&1, 98);
|
||||
assert_eq!(res, (NegativeImbalance::new(98), 0));
|
||||
|
||||
// no events
|
||||
assert_eq!(
|
||||
events(),
|
||||
[RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 98 })]
|
||||
);
|
||||
|
||||
let res = Balances::slash(&1, 1);
|
||||
assert_eq!(res, (NegativeImbalance::new(1), 0));
|
||||
|
||||
assert_eq!(
|
||||
events(),
|
||||
[
|
||||
RuntimeEvent::System(system::Event::KilledAccount { account: 1 }),
|
||||
RuntimeEvent::Balances(crate::Event::DustLost { account: 1, amount: 1 }),
|
||||
RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 1 })
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test setup for potential reentracy and lost updates of nested mutations.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate::{self as pallet_balances, Config};
|
||||
use frame_support::{
|
||||
parameter_types,
|
||||
traits::{ConstU32, ConstU64, StorageMapShim},
|
||||
};
|
||||
use sp_core::H256;
|
||||
use sp_io;
|
||||
use sp_runtime::{testing::Header, traits::IdentityLookup};
|
||||
|
||||
use crate::*;
|
||||
use frame_support::{
|
||||
assert_ok,
|
||||
traits::{Currency, ReservableCurrency},
|
||||
};
|
||||
use frame_system::RawOrigin;
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Test where
|
||||
Block = Block,
|
||||
NodeBlock = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic,
|
||||
{
|
||||
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
|
||||
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
|
||||
}
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub BlockWeights: frame_system::limits::BlockWeights =
|
||||
frame_system::limits::BlockWeights::simple_max(
|
||||
frame_support::weights::Weight::from_parts(1024, u64::MAX),
|
||||
);
|
||||
pub static ExistentialDeposit: u64 = 0;
|
||||
}
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type BlockWeights = BlockWeights;
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Hash = H256;
|
||||
type Hashing = ::sp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = ConstU64<250>;
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
}
|
||||
|
||||
pub struct OnDustRemoval;
|
||||
impl OnUnbalanced<NegativeImbalance<Test>> for OnDustRemoval {
|
||||
fn on_nonzero_unbalanced(amount: NegativeImbalance<Test>) {
|
||||
assert_ok!(Balances::resolve_into_existing(&1, amount));
|
||||
}
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type Balance = u64;
|
||||
type DustRemoval = OnDustRemoval;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore =
|
||||
StorageMapShim<super::Account<Test>, system::Provider<Test>, u64, super::AccountData<u64>>;
|
||||
type MaxLocks = ConstU32<50>;
|
||||
type MaxReserves = ConstU32<2>;
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
pub struct ExtBuilder {
|
||||
existential_deposit: u64,
|
||||
}
|
||||
impl Default for ExtBuilder {
|
||||
fn default() -> Self {
|
||||
Self { existential_deposit: 1 }
|
||||
}
|
||||
}
|
||||
impl ExtBuilder {
|
||||
pub fn existential_deposit(mut self, existential_deposit: u64) -> Self {
|
||||
self.existential_deposit = existential_deposit;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_associated_consts(&self) {
|
||||
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
|
||||
}
|
||||
|
||||
pub fn build(self) -> sp_io::TestExternalities {
|
||||
self.set_associated_consts();
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
pallet_balances::GenesisConfig::<Test> { balances: vec![] }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_dust_removal_tst1_should_work() {
|
||||
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
|
||||
// Verification of reentrancy in dust removal
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 1000, 0));
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 2, 500, 0));
|
||||
|
||||
// In this transaction, account 2 free balance
|
||||
// drops below existential balance
|
||||
// and dust balance is removed from account 2
|
||||
assert_ok!(Balances::transfer(RawOrigin::Signed(2).into(), 3, 450));
|
||||
|
||||
// As expected dust balance is removed.
|
||||
assert_eq!(Balances::free_balance(&2), 0);
|
||||
|
||||
// As expected beneficiary account 3
|
||||
// received the transfered fund.
|
||||
assert_eq!(Balances::free_balance(&3), 450);
|
||||
|
||||
// Dust balance is deposited to account 1
|
||||
// during the process of dust removal.
|
||||
assert_eq!(Balances::free_balance(&1), 1050);
|
||||
|
||||
// Verify the events
|
||||
assert_eq!(System::events().len(), 12);
|
||||
|
||||
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer {
|
||||
from: 2,
|
||||
to: 3,
|
||||
amount: 450,
|
||||
}));
|
||||
System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost {
|
||||
account: 2,
|
||||
amount: 50,
|
||||
}));
|
||||
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit {
|
||||
who: 1,
|
||||
amount: 50,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_dust_removal_tst2_should_work() {
|
||||
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
|
||||
// Verification of reentrancy in dust removal
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 1000, 0));
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 2, 500, 0));
|
||||
|
||||
// In this transaction, account 2 free balance
|
||||
// drops below existential balance
|
||||
// and dust balance is removed from account 2
|
||||
assert_ok!(Balances::transfer(RawOrigin::Signed(2).into(), 1, 450));
|
||||
|
||||
// As expected dust balance is removed.
|
||||
assert_eq!(Balances::free_balance(&2), 0);
|
||||
|
||||
// Dust balance is deposited to account 1
|
||||
// during the process of dust removal.
|
||||
assert_eq!(Balances::free_balance(&1), 1500);
|
||||
|
||||
// Verify the events
|
||||
assert_eq!(System::events().len(), 10);
|
||||
|
||||
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer {
|
||||
from: 2,
|
||||
to: 1,
|
||||
amount: 450,
|
||||
}));
|
||||
System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost {
|
||||
account: 2,
|
||||
amount: 50,
|
||||
}));
|
||||
System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit {
|
||||
who: 1,
|
||||
amount: 50,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repatriating_reserved_balance_dust_removal_should_work() {
|
||||
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
|
||||
// Verification of reentrancy in dust removal
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 1000, 0));
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 2, 500, 0));
|
||||
|
||||
// Reserve a value on account 2,
|
||||
// Such that free balance is lower than
|
||||
// Exestintial deposit.
|
||||
assert_ok!(Balances::reserve(&2, 450));
|
||||
|
||||
// Transfer of reserved fund from slashed account 2 to
|
||||
// beneficiary account 1
|
||||
assert_ok!(Balances::repatriate_reserved(&2, &1, 450, Status::Free), 0);
|
||||
|
||||
// Since free balance of account 2 is lower than
|
||||
// existential deposit, dust amount is
|
||||
// removed from the account 2
|
||||
assert_eq!(Balances::reserved_balance(2), 0);
|
||||
assert_eq!(Balances::free_balance(2), 0);
|
||||
|
||||
// account 1 is credited with reserved amount
|
||||
// together with dust balance during dust
|
||||
// removal.
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
assert_eq!(Balances::free_balance(1), 1500);
|
||||
|
||||
// Verify the events
|
||||
assert_eq!(System::events().len(), 11);
|
||||
|
||||
System::assert_has_event(RuntimeEvent::Balances(crate::Event::ReserveRepatriated {
|
||||
from: 2,
|
||||
to: 1,
|
||||
amount: 450,
|
||||
destination_status: Status::Free,
|
||||
}));
|
||||
|
||||
System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost {
|
||||
account: 2,
|
||||
amount: 50,
|
||||
}));
|
||||
|
||||
System::assert_last_event(RuntimeEvent::Balances(crate::Event::Deposit {
|
||||
who: 1,
|
||||
amount: 50,
|
||||
}));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Types used in the pallet.
|
||||
|
||||
use crate::{Config, CreditOf, Event, Pallet};
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use core::ops::BitOr;
|
||||
use frame_support::{
|
||||
traits::{Imbalance, LockIdentifier, OnUnbalanced, WithdrawReasons},
|
||||
RuntimeDebug,
|
||||
};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::Saturating;
|
||||
|
||||
/// Simplified reasons for withdrawing balance.
|
||||
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub enum Reasons {
|
||||
/// Paying system transaction fees.
|
||||
Fee = 0,
|
||||
/// Any reason other than paying system transaction fees.
|
||||
Misc = 1,
|
||||
/// Any reason at all.
|
||||
All = 2,
|
||||
}
|
||||
|
||||
impl From<WithdrawReasons> for Reasons {
|
||||
fn from(r: WithdrawReasons) -> Reasons {
|
||||
if r == WithdrawReasons::TRANSACTION_PAYMENT {
|
||||
Reasons::Fee
|
||||
} else if r.contains(WithdrawReasons::TRANSACTION_PAYMENT) {
|
||||
Reasons::All
|
||||
} else {
|
||||
Reasons::Misc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for Reasons {
|
||||
type Output = Reasons;
|
||||
fn bitor(self, other: Reasons) -> Reasons {
|
||||
if self == other {
|
||||
return self
|
||||
}
|
||||
Reasons::All
|
||||
}
|
||||
}
|
||||
|
||||
/// A single lock on a balance. There can be many of these on an account and they "overlap", so the
|
||||
/// same balance is frozen by multiple locks.
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub struct BalanceLock<Balance> {
|
||||
/// An identifier for this lock. Only one lock may be in existence for each identifier.
|
||||
pub id: LockIdentifier,
|
||||
/// The amount which the free balance may not drop below when this lock is in effect.
|
||||
pub amount: Balance,
|
||||
/// If true, then the lock remains in effect even for payment of transaction fees.
|
||||
pub reasons: Reasons,
|
||||
}
|
||||
|
||||
/// Store named reserved balance.
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub struct ReserveData<ReserveIdentifier, Balance> {
|
||||
/// The identifier for the named reserve.
|
||||
pub id: ReserveIdentifier,
|
||||
/// The amount of the named reserve.
|
||||
pub amount: Balance,
|
||||
}
|
||||
|
||||
/// An identifier and balance.
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub struct IdAmount<Id, Balance> {
|
||||
/// An identifier for this item.
|
||||
pub id: Id,
|
||||
/// Some amount for this item.
|
||||
pub amount: Balance,
|
||||
}
|
||||
|
||||
/// All balance information for an account.
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub struct AccountData<Balance> {
|
||||
/// Non-reserved part of the balance which the account holder may be able to control.
|
||||
///
|
||||
/// This is the only balance that matters in terms of most operations on tokens.
|
||||
pub free: Balance,
|
||||
/// Balance which is has active holds on it and may not be used at all.
|
||||
///
|
||||
/// This is the sum of all individual holds together with any sums still under the (deprecated)
|
||||
/// reserves API.
|
||||
pub reserved: Balance,
|
||||
/// The amount that `free` may not drop below when reducing the balance, except for actions
|
||||
/// where the account owner cannot reasonably benefit from thr balance reduction, such as
|
||||
/// slashing.
|
||||
pub frozen: Balance,
|
||||
/// Extra information about this account. The MSB is a flag indicating whether the new ref-
|
||||
/// counting logic is in place for this account.
|
||||
pub flags: ExtraFlags,
|
||||
}
|
||||
|
||||
const IS_NEW_LOGIC: u128 = 0x80000000_00000000_00000000_00000000u128;
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub struct ExtraFlags(u128);
|
||||
impl Default for ExtraFlags {
|
||||
fn default() -> Self {
|
||||
Self(IS_NEW_LOGIC)
|
||||
}
|
||||
}
|
||||
impl ExtraFlags {
|
||||
pub fn old_logic() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
pub fn set_new_logic(&mut self) {
|
||||
self.0 = self.0 | IS_NEW_LOGIC
|
||||
}
|
||||
pub fn is_new_logic(&self) -> bool {
|
||||
(self.0 & IS_NEW_LOGIC) == IS_NEW_LOGIC
|
||||
}
|
||||
}
|
||||
|
||||
impl<Balance: Saturating + Copy + Ord> AccountData<Balance> {
|
||||
pub fn usable(&self) -> Balance {
|
||||
self.free.saturating_sub(self.frozen)
|
||||
}
|
||||
|
||||
/// The total balance in this account including any that is reserved and ignoring any frozen.
|
||||
pub fn total(&self) -> Balance {
|
||||
self.free.saturating_add(self.reserved)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DustCleaner<T: Config<I>, I: 'static = ()>(
|
||||
pub(crate) Option<(T::AccountId, CreditOf<T, I>)>,
|
||||
);
|
||||
|
||||
impl<T: Config<I>, I: 'static> Drop for DustCleaner<T, I> {
|
||||
fn drop(&mut self) {
|
||||
if let Some((who, dust)) = self.0.take() {
|
||||
Pallet::<T, I>::deposit_event(Event::DustLost { account: who, amount: dust.peek() });
|
||||
T::DustRemoval::on_unbalanced(dust);
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+40
-9
@@ -49,13 +49,14 @@ use sp_std::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for pallet_balances.
|
||||
pub trait WeightInfo {
|
||||
fn transfer() -> Weight;
|
||||
fn transfer_allow_death() -> Weight;
|
||||
fn transfer_keep_alive() -> Weight;
|
||||
fn set_balance_creating() -> Weight;
|
||||
fn set_balance_killing() -> Weight;
|
||||
fn force_set_balance_creating() -> Weight;
|
||||
fn force_set_balance_killing() -> Weight;
|
||||
fn force_transfer() -> Weight;
|
||||
fn transfer_all() -> Weight;
|
||||
fn force_unreserve() -> Weight;
|
||||
fn upgrade_accounts(u: u32, ) -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for pallet_balances using the Substrate node and recommended hardware.
|
||||
@@ -63,7 +64,7 @@ pub struct SubstrateWeight<T>(PhantomData<T>);
|
||||
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
/// Storage: System Account (r:1 w:1)
|
||||
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
|
||||
fn transfer() -> Weight {
|
||||
fn transfer_allow_death() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `3593`
|
||||
@@ -85,7 +86,7 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
}
|
||||
/// Storage: System Account (r:1 w:1)
|
||||
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
|
||||
fn set_balance_creating() -> Weight {
|
||||
fn force_set_balance_creating() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `174`
|
||||
// Estimated: `3593`
|
||||
@@ -96,7 +97,7 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
}
|
||||
/// Storage: System Account (r:1 w:1)
|
||||
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
|
||||
fn set_balance_killing() -> Weight {
|
||||
fn force_set_balance_killing() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `174`
|
||||
// Estimated: `3593`
|
||||
@@ -138,13 +139,28 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: System Account (r:999 w:999)
|
||||
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
|
||||
/// The range of component `u` is `[1, 1000]`.
|
||||
fn upgrade_accounts(u: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0 + u * (135 ±0)`
|
||||
// Estimated: `990 + u * (2603 ±0)`
|
||||
// Minimum execution time: 19_851_000 picoseconds.
|
||||
Weight::from_parts(20_099_000, 990)
|
||||
// Standard Error: 15_586
|
||||
.saturating_add(Weight::from_parts(14_892_860, 0).saturating_mul(u.into()))
|
||||
.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(u.into())))
|
||||
.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(u.into())))
|
||||
.saturating_add(Weight::from_parts(0, 2603).saturating_mul(u.into()))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
/// Storage: System Account (r:1 w:1)
|
||||
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
|
||||
fn transfer() -> Weight {
|
||||
fn transfer_allow_death() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `3593`
|
||||
@@ -166,7 +182,7 @@ impl WeightInfo for () {
|
||||
}
|
||||
/// Storage: System Account (r:1 w:1)
|
||||
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
|
||||
fn set_balance_creating() -> Weight {
|
||||
fn force_set_balance_creating() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `174`
|
||||
// Estimated: `3593`
|
||||
@@ -177,7 +193,7 @@ impl WeightInfo for () {
|
||||
}
|
||||
/// Storage: System Account (r:1 w:1)
|
||||
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
|
||||
fn set_balance_killing() -> Weight {
|
||||
fn force_set_balance_killing() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `174`
|
||||
// Estimated: `3593`
|
||||
@@ -219,4 +235,19 @@ impl WeightInfo for () {
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: System Account (r:999 w:999)
|
||||
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
|
||||
/// The range of component `u` is `[1, 1000]`.
|
||||
fn upgrade_accounts(u: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0 + u * (135 ±0)`
|
||||
// Estimated: `990 + u * (2603 ±0)`
|
||||
// Minimum execution time: 19_851_000 picoseconds.
|
||||
Weight::from_parts(20_099_000, 990)
|
||||
// Standard Error: 15_586
|
||||
.saturating_add(Weight::from_parts(14_892_860, 0).saturating_mul(u.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(u.into())))
|
||||
.saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(u.into())))
|
||||
.saturating_add(Weight::from_parts(0, 2603).saturating_mul(u.into()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +161,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU128<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Config for Test {
|
||||
|
||||
@@ -57,9 +57,12 @@ fn setup_bounty<T: Config<I>, I: 'static>(
|
||||
let fee = value / 2u32.into();
|
||||
let deposit = T::BountyDepositBase::get() +
|
||||
T::DataDepositPerByte::get() * T::MaximumReasonLength::get().into();
|
||||
let _ = T::Currency::make_free_balance_be(&caller, deposit);
|
||||
let _ = T::Currency::make_free_balance_be(&caller, deposit + T::Currency::minimum_balance());
|
||||
let curator = account("curator", u, SEED);
|
||||
let _ = T::Currency::make_free_balance_be(&curator, fee / 2u32.into());
|
||||
let _ = T::Currency::make_free_balance_be(
|
||||
&curator,
|
||||
fee / 2u32.into() + T::Currency::minimum_balance(),
|
||||
);
|
||||
let reason = vec![0; d as usize];
|
||||
(caller, curator, fee, value, reason)
|
||||
}
|
||||
|
||||
@@ -100,6 +100,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ProposalBond: Permill = Permill::from_percent(5);
|
||||
@@ -754,7 +758,7 @@ fn award_and_claim_bounty_works() {
|
||||
System::set_block_number(5);
|
||||
<Treasury as OnInitialize<u64>>::on_initialize(5);
|
||||
|
||||
assert_ok!(Balances::transfer(
|
||||
assert_ok!(Balances::transfer_allow_death(
|
||||
RuntimeOrigin::signed(0),
|
||||
Bounties::bounty_account_id(0),
|
||||
10
|
||||
@@ -832,7 +836,7 @@ fn cancel_and_refund() {
|
||||
System::set_block_number(2);
|
||||
<Treasury as OnInitialize<u64>>::on_initialize(2);
|
||||
|
||||
assert_ok!(Balances::transfer(
|
||||
assert_ok!(Balances::transfer_allow_death(
|
||||
RuntimeOrigin::signed(0),
|
||||
Bounties::bounty_account_id(0),
|
||||
10
|
||||
|
||||
@@ -63,9 +63,12 @@ fn setup_bounty<T: Config>(
|
||||
let fee = value / 2u32.into();
|
||||
let deposit = T::BountyDepositBase::get() +
|
||||
T::DataDepositPerByte::get() * T::MaximumReasonLength::get().into();
|
||||
let _ = T::Currency::make_free_balance_be(&caller, deposit);
|
||||
let _ = T::Currency::make_free_balance_be(&caller, deposit + T::Currency::minimum_balance());
|
||||
let curator = account("curator", user, SEED);
|
||||
let _ = T::Currency::make_free_balance_be(&curator, fee / 2u32.into());
|
||||
let _ = T::Currency::make_free_balance_be(
|
||||
&curator,
|
||||
fee / 2u32.into() + T::Currency::minimum_balance(),
|
||||
);
|
||||
let reason = vec![0; description as usize];
|
||||
(caller, curator, fee, value, reason)
|
||||
}
|
||||
@@ -73,7 +76,10 @@ fn setup_bounty<T: Config>(
|
||||
fn setup_child_bounty<T: Config>(user: u32, description: u32) -> BenchmarkChildBounty<T> {
|
||||
let (caller, curator, fee, value, reason) = setup_bounty::<T>(user, description);
|
||||
let child_curator = account("child-curator", user, SEED);
|
||||
let _ = T::Currency::make_free_balance_be(&child_curator, fee / 2u32.into());
|
||||
let _ = T::Currency::make_free_balance_be(
|
||||
&child_curator,
|
||||
fee / 2u32.into() + T::Currency::minimum_balance(),
|
||||
);
|
||||
let child_bounty_value = (value - fee) / 4u32.into();
|
||||
let child_bounty_fee = child_bounty_value / 2u32.into();
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
testing::Header,
|
||||
traits::{BadOrigin, BlakeTwo256, IdentityLookup},
|
||||
Perbill, Permill,
|
||||
Perbill, Permill, TokenError,
|
||||
};
|
||||
|
||||
use super::Event as ChildBountiesEvent;
|
||||
@@ -103,6 +103,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ProposalBond: Permill = Permill::from_percent(5);
|
||||
@@ -248,7 +252,7 @@ fn add_child_bounty() {
|
||||
|
||||
assert_noop!(
|
||||
ChildBounties::add_child_bounty(RuntimeOrigin::signed(4), 0, 50, b"12345-p1".to_vec()),
|
||||
pallet_balances::Error::<Test>::KeepAlive,
|
||||
TokenError::NotExpendable,
|
||||
);
|
||||
|
||||
assert_noop!(
|
||||
|
||||
@@ -798,15 +798,15 @@ benchmarks! {
|
||||
let instance = Contract::<T>::new(code, vec![])?;
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
let deposit_account = instance.info()?.deposit_account().clone();
|
||||
assert_eq!(T::Currency::total_balance(&beneficiary), 0u32.into());
|
||||
assert_eq!(<T::Currency as Currency<_>>::total_balance(&beneficiary), 0u32.into());
|
||||
assert_eq!(T::Currency::free_balance(&instance.account_id), Pallet::<T>::min_balance() * 2u32.into());
|
||||
assert_ne!(T::Currency::free_balance(&deposit_account), 0u32.into());
|
||||
}: call(origin, instance.addr.clone(), 0u32.into(), Weight::MAX, None, vec![])
|
||||
verify {
|
||||
if r > 0 {
|
||||
assert_eq!(T::Currency::total_balance(&instance.account_id), 0u32.into());
|
||||
assert_eq!(T::Currency::total_balance(&deposit_account), 0u32.into());
|
||||
assert_eq!(T::Currency::total_balance(&beneficiary), Pallet::<T>::min_balance() * 2u32.into());
|
||||
assert_eq!(<T::Currency as Currency<_>>::total_balance(&instance.account_id), 0u32.into());
|
||||
assert_eq!(<T::Currency as Currency<_>>::total_balance(&deposit_account), 0u32.into());
|
||||
assert_eq!(<T::Currency as Currency<_>>::total_balance(&beneficiary), Pallet::<T>::min_balance() * 2u32.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1573,12 +1573,12 @@ benchmarks! {
|
||||
instance.set_balance(value * (r + 1).into());
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
for account in &accounts {
|
||||
assert_eq!(T::Currency::total_balance(account), 0u32.into());
|
||||
assert_eq!(<T::Currency as Currency<_>>::total_balance(account), 0u32.into());
|
||||
}
|
||||
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
||||
verify {
|
||||
for account in &accounts {
|
||||
assert_eq!(T::Currency::total_balance(account), value);
|
||||
assert_eq!(<T::Currency as Currency<_>>::total_balance(account), value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,10 @@ use frame_support::{
|
||||
crypto::ecdsa::ECDSAExt,
|
||||
dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable},
|
||||
storage::{with_transaction, TransactionOutcome},
|
||||
traits::{Contains, Currency, ExistenceRequirement, OriginTrait, Randomness, Time},
|
||||
traits::{
|
||||
tokens::{Fortitude::Polite, Preservation::Expendable},
|
||||
Contains, Currency, ExistenceRequirement, OriginTrait, Randomness, Time,
|
||||
},
|
||||
weights::Weight,
|
||||
Blake2_128Concat, BoundedVec, StorageHasher,
|
||||
};
|
||||
@@ -1198,7 +1201,7 @@ where
|
||||
T::Currency::transfer(
|
||||
&frame.account_id,
|
||||
beneficiary,
|
||||
T::Currency::reducible_balance(&frame.account_id, false),
|
||||
T::Currency::reducible_balance(&frame.account_id, Expendable, Polite),
|
||||
ExistenceRequirement::AllowDeath,
|
||||
)?;
|
||||
info.queue_trie_for_deletion()?;
|
||||
@@ -2751,8 +2754,10 @@ mod tests {
|
||||
RuntimeCall::System(SysCall::remark_with_event { remark: b"Hello".to_vec() });
|
||||
|
||||
// transfers are disallowed by the `TestFiler` (see below)
|
||||
let forbidden_call =
|
||||
RuntimeCall::Balances(BalanceCall::transfer { dest: CHARLIE, value: 22 });
|
||||
let forbidden_call = RuntimeCall::Balances(BalanceCall::transfer_allow_death {
|
||||
dest: CHARLIE,
|
||||
value: 22,
|
||||
});
|
||||
|
||||
// simple cases: direct call
|
||||
assert_err!(
|
||||
@@ -2772,7 +2777,7 @@ mod tests {
|
||||
});
|
||||
|
||||
TestFilter::set_filter(|call| match call {
|
||||
RuntimeCall::Balances(pallet_balances::Call::transfer { .. }) => false,
|
||||
RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { .. }) => false,
|
||||
_ => true,
|
||||
});
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@ pub mod pallet {
|
||||
type Randomness: Randomness<Self::Hash, Self::BlockNumber>;
|
||||
|
||||
/// The currency in which fees are paid and contract balances are held.
|
||||
type Currency: ReservableCurrency<Self::AccountId>
|
||||
type Currency: ReservableCurrency<Self::AccountId> // TODO: Move to fungible traits
|
||||
+ Inspect<Self::AccountId, Balance = BalanceOf<Self>>;
|
||||
|
||||
/// The overarching event type.
|
||||
|
||||
@@ -25,7 +25,10 @@ use codec::Encode;
|
||||
use frame_support::{
|
||||
dispatch::DispatchError,
|
||||
ensure,
|
||||
traits::{tokens::WithdrawConsequence, Currency, ExistenceRequirement, Get},
|
||||
traits::{
|
||||
tokens::{Fortitude::Polite, Preservation::Protect, WithdrawConsequence},
|
||||
Currency, ExistenceRequirement, Get,
|
||||
},
|
||||
DefaultNoBound, RuntimeDebugNoBound,
|
||||
};
|
||||
use pallet_contracts_primitives::StorageDeposit as Deposit;
|
||||
@@ -456,7 +459,7 @@ impl<T: Config> Ext<T> for ReservingExt {
|
||||
// We are sending the `min_leftover` and the `min_balance` from the origin
|
||||
// account as part of a contract call. Hence origin needs to have those left over
|
||||
// as free balance after accounting for all deposits.
|
||||
let max = T::Currency::reducible_balance(origin, true)
|
||||
let max = T::Currency::reducible_balance(origin, Protect, Polite)
|
||||
.saturating_sub(min_leftover)
|
||||
.saturating_sub(Pallet::<T>::min_balance());
|
||||
let limit = limit.unwrap_or(max);
|
||||
|
||||
@@ -37,7 +37,7 @@ use frame_support::{
|
||||
storage::child,
|
||||
traits::{
|
||||
ConstU32, ConstU64, Contains, Currency, ExistenceRequirement, Get, LockableCurrency,
|
||||
OnIdle, OnInitialize, ReservableCurrency, WithdrawReasons,
|
||||
OnIdle, OnInitialize, WithdrawReasons,
|
||||
},
|
||||
weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight},
|
||||
};
|
||||
@@ -48,7 +48,7 @@ use sp_keystore::{testing::MemoryKeystore, KeystoreExt};
|
||||
use sp_runtime::{
|
||||
testing::{Header, H256},
|
||||
traits::{BlakeTwo256, Convert, Hash, IdentityLookup},
|
||||
AccountId32,
|
||||
AccountId32, TokenError,
|
||||
};
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
@@ -318,6 +318,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Config for Test {
|
||||
@@ -1054,7 +1058,7 @@ fn transfer_allow_death_cannot_kill_account() {
|
||||
total_balance,
|
||||
ExistenceRequirement::AllowDeath,
|
||||
),
|
||||
pallet_balances::Error::<Test>::KeepAlive,
|
||||
TokenError::Frozen,
|
||||
);
|
||||
|
||||
assert_eq!(<Test as Config>::Currency::total_balance(&addr), total_balance);
|
||||
@@ -1474,25 +1478,6 @@ fn transfer_return_code() {
|
||||
.result
|
||||
.unwrap();
|
||||
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
|
||||
|
||||
// Contract has enough total balance in order to not go below the min balance
|
||||
// threshold when transfering 100 balance but this balance is reserved so
|
||||
// the transfer still fails.
|
||||
Balances::make_free_balance_be(&addr, min_balance + 100);
|
||||
Balances::reserve(&addr, min_balance + 100).unwrap();
|
||||
let result = Contracts::bare_call(
|
||||
ALICE,
|
||||
addr,
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
vec![],
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1569,29 +1554,6 @@ fn call_return_code() {
|
||||
.unwrap();
|
||||
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
|
||||
|
||||
// Contract has enough total balance in order to not go below the min balance
|
||||
// threshold when transfering 100 balance but this balance is reserved so
|
||||
// the transfer still fails.
|
||||
Balances::make_free_balance_be(&addr_bob, min_balance + 100);
|
||||
Balances::reserve(&addr_bob, min_balance + 100).unwrap();
|
||||
let result = Contracts::bare_call(
|
||||
ALICE,
|
||||
addr_bob.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
AsRef::<[u8]>::as_ref(&addr_django)
|
||||
.iter()
|
||||
.chain(&0u32.to_le_bytes())
|
||||
.cloned()
|
||||
.collect(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
|
||||
|
||||
// Contract has enough balance but callee reverts because "1" is passed.
|
||||
Balances::make_free_balance_be(&addr_bob, min_balance + 1000);
|
||||
let result = Contracts::bare_call(
|
||||
@@ -1683,25 +1645,6 @@ fn instantiate_return_code() {
|
||||
.unwrap();
|
||||
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
|
||||
|
||||
// Contract has enough total balance in order to not go below the min_balance
|
||||
// threshold when transfering the balance but this balance is reserved so
|
||||
// the transfer still fails.
|
||||
Balances::make_free_balance_be(&addr, min_balance + 10_000);
|
||||
Balances::reserve(&addr, min_balance + 10_000).unwrap();
|
||||
let result = Contracts::bare_call(
|
||||
ALICE,
|
||||
addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
callee_hash.clone(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
|
||||
|
||||
// Contract has enough balance but the passed code hash is invalid
|
||||
Balances::make_free_balance_be(&addr, min_balance + 10_000);
|
||||
let result = Contracts::bare_call(
|
||||
@@ -2805,7 +2748,7 @@ fn gas_estimation_call_runtime() {
|
||||
|
||||
// Call something trivial with a huge gas limit so that we can observe the effects
|
||||
// of pre-charging. This should create a difference between consumed and required.
|
||||
let call = RuntimeCall::Balances(pallet_balances::Call::transfer {
|
||||
let call = RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
|
||||
dest: addr_callee,
|
||||
value: min_balance * 10,
|
||||
});
|
||||
|
||||
@@ -23,7 +23,11 @@ use assert_matches::assert_matches;
|
||||
use frame_benchmarking::v1::{account, benchmarks_instance_pallet, whitelist_account};
|
||||
use frame_support::{
|
||||
dispatch::RawOrigin,
|
||||
traits::{fungible, Currency, Get},
|
||||
traits::{
|
||||
fungible,
|
||||
tokens::{Fortitude::Polite, Preservation::Expendable},
|
||||
Currency, Get,
|
||||
},
|
||||
};
|
||||
use sp_runtime::traits::Bounded;
|
||||
use sp_std::collections::btree_map::BTreeMap;
|
||||
@@ -257,13 +261,13 @@ benchmarks_instance_pallet! {
|
||||
}
|
||||
}
|
||||
|
||||
let orig_usable = <T::Currency as fungible::Inspect<T::AccountId>>::reducible_balance(&caller, false);
|
||||
let orig_usable = <T::Currency as fungible::Inspect<T::AccountId>>::reducible_balance(&caller, Expendable, Polite);
|
||||
let polls = &all_polls[&class];
|
||||
|
||||
// Vote big on the class with the most ongoing votes of them to bump the lock and make it
|
||||
// hard to recompute when removed.
|
||||
ConvictionVoting::<T, I>::vote(RawOrigin::Signed(caller.clone()).into(), polls[0], big_account_vote)?;
|
||||
let now_usable = <T::Currency as fungible::Inspect<T::AccountId>>::reducible_balance(&caller, false);
|
||||
let now_usable = <T::Currency as fungible::Inspect<T::AccountId>>::reducible_balance(&caller, Expendable, Polite);
|
||||
assert_eq!(orig_usable - now_usable, 100u32.into());
|
||||
|
||||
// Remove the vote
|
||||
@@ -272,7 +276,7 @@ benchmarks_instance_pallet! {
|
||||
// We can now unlock on `class` from 200 to 100...
|
||||
}: _(RawOrigin::Signed(caller.clone()), class, caller_lookup)
|
||||
verify {
|
||||
assert_eq!(orig_usable, <T::Currency as fungible::Inspect<T::AccountId>>::reducible_balance(&caller, false));
|
||||
assert_eq!(orig_usable, <T::Currency as fungible::Inspect<T::AccountId>>::reducible_balance(&caller, Expendable, Polite));
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
|
||||
@@ -388,7 +388,10 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
poll_index: PollIndexOf<T, I>,
|
||||
vote: AccountVote<BalanceOf<T, I>>,
|
||||
) -> DispatchResult {
|
||||
ensure!(vote.balance() <= T::Currency::free_balance(who), Error::<T, I>::InsufficientFunds);
|
||||
ensure!(
|
||||
vote.balance() <= T::Currency::total_balance(who),
|
||||
Error::<T, I>::InsufficientFunds
|
||||
);
|
||||
T::Polls::try_access_poll(poll_index, |poll_status| {
|
||||
let (tally, class) = poll_status.ensure_ongoing().ok_or(Error::<T, I>::NotOngoing)?;
|
||||
VotingFor::<T, I>::try_mutate(who, &class, |voting| {
|
||||
@@ -548,7 +551,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
) -> Result<u32, DispatchError> {
|
||||
ensure!(who != target, Error::<T, I>::Nonsense);
|
||||
T::Polls::classes().binary_search(&class).map_err(|_| Error::<T, I>::BadClass)?;
|
||||
ensure!(balance <= T::Currency::free_balance(&who), Error::<T, I>::InsufficientFunds);
|
||||
ensure!(balance <= T::Currency::total_balance(&who), Error::<T, I>::InsufficientFunds);
|
||||
let votes =
|
||||
VotingFor::<T, I>::try_mutate(&who, &class, |voting| -> Result<u32, DispatchError> {
|
||||
let old = sp_std::mem::replace(
|
||||
|
||||
@@ -51,7 +51,7 @@ frame_support::construct_runtime!(
|
||||
pub struct BaseFilter;
|
||||
impl Contains<RuntimeCall> for BaseFilter {
|
||||
fn contains(call: &RuntimeCall) -> bool {
|
||||
!matches!(call, &RuntimeCall::Balances(pallet_balances::Call::set_balance { .. }))
|
||||
!matches!(call, &RuntimeCall::Balances(pallet_balances::Call::force_set_balance { .. }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
|
||||
@@ -72,7 +72,7 @@ frame_support::construct_runtime!(
|
||||
pub struct BaseFilter;
|
||||
impl Contains<RuntimeCall> for BaseFilter {
|
||||
fn contains(call: &RuntimeCall) -> bool {
|
||||
!matches!(call, &RuntimeCall::Balances(pallet_balances::Call::set_balance { .. }))
|
||||
!matches!(call, &RuntimeCall::Balances(pallet_balances::Call::force_set_balance { .. }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +144,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub static PreimageByteDeposit: u64 = 0;
|
||||
@@ -223,7 +227,7 @@ fn params_should_work() {
|
||||
}
|
||||
|
||||
fn set_balance_proposal(value: u64) -> BoundedCallOf<Test> {
|
||||
let inner = pallet_balances::Call::set_balance { who: 42, new_free: value, new_reserved: 0 };
|
||||
let inner = pallet_balances::Call::force_set_balance { who: 42, new_free: value };
|
||||
let outer = RuntimeCall::Balances(inner);
|
||||
Preimage::bound(outer).unwrap()
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ frame_benchmarking::benchmarks! {
|
||||
|
||||
finalize_signed_phase_accept_solution {
|
||||
let receiver = account("receiver", 0, SEED);
|
||||
let initial_balance = T::Currency::minimum_balance() * 10u32.into();
|
||||
let initial_balance = T::Currency::minimum_balance() + 10u32.into();
|
||||
T::Currency::make_free_balance_be(&receiver, initial_balance);
|
||||
let ready = Default::default();
|
||||
let deposit: BalanceOf<T> = 10u32.into();
|
||||
@@ -228,7 +228,7 @@ frame_benchmarking::benchmarks! {
|
||||
let call_fee: BalanceOf<T> = 30u32.into();
|
||||
|
||||
assert_ok!(T::Currency::reserve(&receiver, deposit));
|
||||
assert_eq!(T::Currency::free_balance(&receiver), initial_balance - 10u32.into());
|
||||
assert_eq!(T::Currency::free_balance(&receiver), T::Currency::minimum_balance());
|
||||
}: {
|
||||
<MultiPhase<T>>::finalize_signed_phase_accept_solution(
|
||||
ready,
|
||||
@@ -246,17 +246,17 @@ frame_benchmarking::benchmarks! {
|
||||
|
||||
finalize_signed_phase_reject_solution {
|
||||
let receiver = account("receiver", 0, SEED);
|
||||
let initial_balance = T::Currency::minimum_balance().max(One::one()) * 10u32.into();
|
||||
let initial_balance = T::Currency::minimum_balance() + 10u32.into();
|
||||
let deposit: BalanceOf<T> = 10u32.into();
|
||||
T::Currency::make_free_balance_be(&receiver, initial_balance);
|
||||
assert_ok!(T::Currency::reserve(&receiver, deposit));
|
||||
|
||||
assert_eq!(T::Currency::free_balance(&receiver), initial_balance - 10u32.into());
|
||||
assert_eq!(T::Currency::free_balance(&receiver), T::Currency::minimum_balance());
|
||||
assert_eq!(T::Currency::reserved_balance(&receiver), 10u32.into());
|
||||
}: {
|
||||
<MultiPhase<T>>::finalize_signed_phase_reject_solution(&receiver, deposit)
|
||||
} verify {
|
||||
assert_eq!(T::Currency::free_balance(&receiver), initial_balance - 10u32.into());
|
||||
assert_eq!(T::Currency::free_balance(&receiver), T::Currency::minimum_balance());
|
||||
assert_eq!(T::Currency::reserved_balance(&receiver), 0u32.into());
|
||||
}
|
||||
|
||||
|
||||
@@ -254,6 +254,10 @@ impl pallet_balances::Config for Runtime {
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Debug, Clone, Copy)]
|
||||
|
||||
@@ -1248,6 +1248,10 @@ mod tests {
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
frame_support::parameter_types! {
|
||||
@@ -2121,7 +2125,8 @@ mod tests {
|
||||
assert_ok!(submit_candidacy(RuntimeOrigin::signed(4)));
|
||||
|
||||
// User has 100 free and 50 reserved.
|
||||
assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 2, 100, 50));
|
||||
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 150));
|
||||
assert_ok!(Balances::reserve(&2, 50));
|
||||
// User tries to vote with 150 tokens.
|
||||
assert_ok!(vote(RuntimeOrigin::signed(2), vec![4, 5], 150));
|
||||
// We truncate to only their free balance, after reserving additional for voting.
|
||||
|
||||
@@ -87,6 +87,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
|
||||
@@ -688,13 +688,10 @@ mod tests {
|
||||
|
||||
use frame_support::{
|
||||
assert_err, parameter_types,
|
||||
traits::{
|
||||
ConstU32, ConstU64, ConstU8, Currency, LockIdentifier, LockableCurrency,
|
||||
WithdrawReasons,
|
||||
},
|
||||
traits::{fungible, ConstU32, ConstU64, ConstU8, Currency},
|
||||
weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight, WeightToFee},
|
||||
};
|
||||
use frame_system::{Call as SystemCall, ChainContext, LastRuntimeUpgradeInfo};
|
||||
use frame_system::{ChainContext, LastRuntimeUpgradeInfo};
|
||||
use pallet_balances::Call as BalancesCall;
|
||||
use pallet_transaction_payment::CurrencyAdapter;
|
||||
|
||||
@@ -899,6 +896,10 @@ mod tests {
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ConstU32<1>;
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ConstU32<1>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
@@ -972,7 +973,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn call_transfer(dest: u64, value: u64) -> RuntimeCall {
|
||||
RuntimeCall::Balances(BalancesCall::transfer { dest, value })
|
||||
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest, value })
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1025,13 +1026,13 @@ mod tests {
|
||||
block_import_works_inner(
|
||||
new_test_ext_v0(1),
|
||||
array_bytes::hex_n_into_unchecked(
|
||||
"216e61b2689d1243eb56d89c9084db48e50ebebc4871d758db131432c675d7c0",
|
||||
"65e953676859e7a33245908af7ad3637d6861eb90416d433d485e95e2dd174a1",
|
||||
),
|
||||
);
|
||||
block_import_works_inner(
|
||||
new_test_ext(1),
|
||||
array_bytes::hex_n_into_unchecked(
|
||||
"4738b4c0aab02d6ddfa62a2a6831ccc975a9f978f7db8d7ea8e68eba8639530a",
|
||||
"5a19b3d6fdb7241836349fdcbe2d9df4d4f945b949d979e31ad50bff1cbcd1c2",
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1116,7 +1117,7 @@ mod tests {
|
||||
let mut t = new_test_ext(10000);
|
||||
// given: TestXt uses the encoded len as fixed Len:
|
||||
let xt = TestXt::new(
|
||||
RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
|
||||
sign_extra(1, 0, 0),
|
||||
);
|
||||
let encoded = xt.encode();
|
||||
@@ -1139,7 +1140,10 @@ mod tests {
|
||||
|
||||
for nonce in 0..=num_to_exhaust_block {
|
||||
let xt = TestXt::new(
|
||||
RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer_allow_death {
|
||||
dest: 33,
|
||||
value: 0,
|
||||
}),
|
||||
sign_extra(1, nonce.into(), 0),
|
||||
);
|
||||
let res = Executive::apply_extrinsic(xt);
|
||||
@@ -1164,15 +1168,15 @@ mod tests {
|
||||
#[test]
|
||||
fn block_weight_and_size_is_stored_per_tx() {
|
||||
let xt = TestXt::new(
|
||||
RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
|
||||
sign_extra(1, 0, 0),
|
||||
);
|
||||
let x1 = TestXt::new(
|
||||
RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
|
||||
sign_extra(1, 1, 0),
|
||||
);
|
||||
let x2 = TestXt::new(
|
||||
RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
|
||||
sign_extra(1, 2, 0),
|
||||
);
|
||||
let len = xt.clone().encode().len() as u32;
|
||||
@@ -1258,50 +1262,30 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_pay_for_tx_fee_on_full_lock() {
|
||||
let id: LockIdentifier = *b"0 ";
|
||||
let execute_with_lock = |lock: WithdrawReasons| {
|
||||
let mut t = new_test_ext(1);
|
||||
t.execute_with(|| {
|
||||
<pallet_balances::Pallet<Runtime> as LockableCurrency<Balance>>::set_lock(
|
||||
id, &1, 110, lock,
|
||||
);
|
||||
let xt = TestXt::new(
|
||||
RuntimeCall::System(SystemCall::remark { remark: vec![1u8] }),
|
||||
sign_extra(1, 0, 0),
|
||||
);
|
||||
let weight = xt.get_dispatch_info().weight +
|
||||
<Runtime as frame_system::Config>::BlockWeights::get()
|
||||
.get(DispatchClass::Normal)
|
||||
.base_extrinsic;
|
||||
let fee: Balance =
|
||||
<Runtime as pallet_transaction_payment::Config>::WeightToFee::weight_to_fee(
|
||||
&weight,
|
||||
);
|
||||
Executive::initialize_block(&Header::new(
|
||||
1,
|
||||
H256::default(),
|
||||
H256::default(),
|
||||
[69u8; 32].into(),
|
||||
Digest::default(),
|
||||
));
|
||||
fn can_not_pay_for_tx_fee_on_full_lock() {
|
||||
let mut t = new_test_ext(1);
|
||||
t.execute_with(|| {
|
||||
<pallet_balances::Pallet<Runtime> as fungible::MutateFreeze<u64>>::set_freeze(
|
||||
&(),
|
||||
&1,
|
||||
110,
|
||||
)
|
||||
.unwrap();
|
||||
let xt = TestXt::new(
|
||||
RuntimeCall::System(frame_system::Call::remark { remark: vec![1u8] }),
|
||||
sign_extra(1, 0, 0),
|
||||
);
|
||||
Executive::initialize_block(&Header::new(
|
||||
1,
|
||||
H256::default(),
|
||||
H256::default(),
|
||||
[69u8; 32].into(),
|
||||
Digest::default(),
|
||||
));
|
||||
|
||||
if lock == WithdrawReasons::except(WithdrawReasons::TRANSACTION_PAYMENT) {
|
||||
assert!(Executive::apply_extrinsic(xt).unwrap().is_ok());
|
||||
// tx fee has been deducted.
|
||||
assert_eq!(<pallet_balances::Pallet<Runtime>>::total_balance(&1), 111 - fee);
|
||||
} else {
|
||||
assert_eq!(
|
||||
Executive::apply_extrinsic(xt),
|
||||
Err(InvalidTransaction::Payment.into()),
|
||||
);
|
||||
assert_eq!(<pallet_balances::Pallet<Runtime>>::total_balance(&1), 111);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
execute_with_lock(WithdrawReasons::all());
|
||||
execute_with_lock(WithdrawReasons::except(WithdrawReasons::TRANSACTION_PAYMENT));
|
||||
assert_eq!(Executive::apply_extrinsic(xt), Err(InvalidTransaction::Payment.into()),);
|
||||
assert_eq!(<pallet_balances::Pallet<Runtime>>::total_balance(&1), 111);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1443,7 +1427,7 @@ mod tests {
|
||||
#[test]
|
||||
fn custom_runtime_upgrade_is_called_when_using_execute_block_trait() {
|
||||
let xt = TestXt::new(
|
||||
RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
|
||||
sign_extra(1, 0, 0),
|
||||
);
|
||||
|
||||
@@ -1569,7 +1553,7 @@ mod tests {
|
||||
#[should_panic(expected = "Invalid inherent position for extrinsic at index 1")]
|
||||
fn invalid_inherent_position_fail() {
|
||||
let xt1 = TestXt::new(
|
||||
RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
|
||||
sign_extra(1, 0, 0),
|
||||
);
|
||||
let xt2 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent_call {}), None);
|
||||
|
||||
@@ -89,6 +89,10 @@ impl pallet_balances::Config for Runtime {
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
pallet_staking_reward_curve::build! {
|
||||
|
||||
@@ -142,6 +142,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU128<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Config for Test {
|
||||
|
||||
@@ -85,6 +85,10 @@ impl pallet_balances::Config for Test {
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -76,6 +76,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
|
||||
@@ -89,6 +89,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -23,8 +23,7 @@ use mock::{
|
||||
new_test_ext, run_to_block, Balances, BalancesCall, Lottery, RuntimeCall, RuntimeOrigin,
|
||||
SystemCall, Test,
|
||||
};
|
||||
use pallet_balances::Error as BalancesError;
|
||||
use sp_runtime::traits::BadOrigin;
|
||||
use sp_runtime::{traits::BadOrigin, TokenError};
|
||||
|
||||
#[test]
|
||||
fn initial_state() {
|
||||
@@ -45,7 +44,7 @@ fn basic_end_to_end_works() {
|
||||
let delay = 5;
|
||||
let calls = vec![
|
||||
RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }),
|
||||
];
|
||||
|
||||
// Set calls for the lottery
|
||||
@@ -56,7 +55,10 @@ fn basic_end_to_end_works() {
|
||||
assert!(crate::Lottery::<Test>::get().is_some());
|
||||
|
||||
assert_eq!(Balances::free_balance(&1), 100);
|
||||
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 20 }));
|
||||
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death {
|
||||
dest: 2,
|
||||
value: 20,
|
||||
}));
|
||||
assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call.clone()));
|
||||
// 20 from the transfer, 10 from buying a ticket
|
||||
assert_eq!(Balances::free_balance(&1), 100 - 20 - 10);
|
||||
@@ -129,7 +131,7 @@ fn set_calls_works() {
|
||||
|
||||
let calls = vec![
|
||||
RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }),
|
||||
];
|
||||
|
||||
assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls));
|
||||
@@ -137,7 +139,7 @@ fn set_calls_works() {
|
||||
|
||||
let too_many_calls = vec![
|
||||
RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }),
|
||||
RuntimeCall::System(SystemCall::remark { remark: vec![] }),
|
||||
];
|
||||
|
||||
@@ -157,7 +159,7 @@ fn call_to_indices_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let calls = vec![
|
||||
RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }),
|
||||
];
|
||||
let indices = Lottery::calls_to_indices(&calls).unwrap().into_inner();
|
||||
// Only comparing the length since it is otherwise dependant on the API
|
||||
@@ -166,7 +168,7 @@ fn call_to_indices_works() {
|
||||
|
||||
let too_many_calls = vec![
|
||||
RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }),
|
||||
RuntimeCall::System(SystemCall::remark { remark: vec![] }),
|
||||
];
|
||||
assert_noop!(Lottery::calls_to_indices(&too_many_calls), Error::<Test>::TooManyCalls);
|
||||
@@ -203,7 +205,10 @@ fn buy_ticket_works_as_simple_passthrough() {
|
||||
// as a simple passthrough to the real call.
|
||||
new_test_ext().execute_with(|| {
|
||||
// No lottery set up
|
||||
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 20 }));
|
||||
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death {
|
||||
dest: 2,
|
||||
value: 20,
|
||||
}));
|
||||
// This is just a basic transfer then
|
||||
assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call.clone()));
|
||||
assert_eq!(Balances::free_balance(&1), 100 - 20);
|
||||
@@ -212,7 +217,7 @@ fn buy_ticket_works_as_simple_passthrough() {
|
||||
// Lottery is set up, but too expensive to enter, so `do_buy_ticket` fails.
|
||||
let calls = vec![
|
||||
RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }),
|
||||
];
|
||||
assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls));
|
||||
|
||||
@@ -223,11 +228,13 @@ fn buy_ticket_works_as_simple_passthrough() {
|
||||
assert_eq!(TicketsCount::<Test>::get(), 0);
|
||||
|
||||
// If call would fail, the whole thing still fails the same
|
||||
let fail_call =
|
||||
Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 1000 }));
|
||||
let fail_call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death {
|
||||
dest: 2,
|
||||
value: 1000,
|
||||
}));
|
||||
assert_noop!(
|
||||
Lottery::buy_ticket(RuntimeOrigin::signed(1), fail_call),
|
||||
BalancesError::<Test, _>::InsufficientBalance,
|
||||
ArithmeticError::Underflow,
|
||||
);
|
||||
|
||||
let bad_origin_call = Box::new(RuntimeCall::Balances(BalancesCall::force_transfer {
|
||||
@@ -243,8 +250,10 @@ fn buy_ticket_works_as_simple_passthrough() {
|
||||
assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(2), remark_call));
|
||||
assert_eq!(TicketsCount::<Test>::get(), 0);
|
||||
|
||||
let successful_call =
|
||||
Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 1 }));
|
||||
let successful_call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death {
|
||||
dest: 2,
|
||||
value: 1,
|
||||
}));
|
||||
assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(2), successful_call));
|
||||
assert_eq!(TicketsCount::<Test>::get(), 1);
|
||||
});
|
||||
@@ -256,12 +265,15 @@ fn buy_ticket_works() {
|
||||
// Set calls for the lottery.
|
||||
let calls = vec![
|
||||
RuntimeCall::System(SystemCall::remark { remark: vec![] }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }),
|
||||
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }),
|
||||
];
|
||||
assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls));
|
||||
|
||||
// Can't buy ticket before start
|
||||
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 1 }));
|
||||
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death {
|
||||
dest: 2,
|
||||
value: 1,
|
||||
}));
|
||||
assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call.clone()));
|
||||
assert_eq!(TicketsCount::<Test>::get(), 0);
|
||||
|
||||
@@ -274,7 +286,10 @@ fn buy_ticket_works() {
|
||||
assert_eq!(TicketsCount::<Test>::get(), 1);
|
||||
|
||||
// Can't buy another of the same ticket (even if call is slightly changed)
|
||||
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 3, value: 30 }));
|
||||
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death {
|
||||
dest: 3,
|
||||
value: 30,
|
||||
}));
|
||||
assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call));
|
||||
assert_eq!(TicketsCount::<Test>::get(), 1);
|
||||
|
||||
@@ -302,7 +317,8 @@ fn buy_ticket_works() {
|
||||
#[test]
|
||||
fn do_buy_ticket_already_participating() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })];
|
||||
let calls =
|
||||
vec![RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 })];
|
||||
assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone()));
|
||||
assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 1, 10, 10, false));
|
||||
|
||||
@@ -317,7 +333,8 @@ fn do_buy_ticket_already_participating() {
|
||||
#[test]
|
||||
fn buy_ticket_already_participating() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })];
|
||||
let calls =
|
||||
vec![RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 })];
|
||||
assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone()));
|
||||
assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 1, 10, 10, false));
|
||||
|
||||
@@ -337,7 +354,8 @@ fn buy_ticket_already_participating() {
|
||||
#[test]
|
||||
fn buy_ticket_insufficient_balance() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })];
|
||||
let calls =
|
||||
vec![RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 })];
|
||||
assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone()));
|
||||
// Price set to 100.
|
||||
assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 100, 10, 10, false));
|
||||
@@ -352,16 +370,14 @@ fn buy_ticket_insufficient_balance() {
|
||||
#[test]
|
||||
fn do_buy_ticket_insufficient_balance() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })];
|
||||
let calls =
|
||||
vec![RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 })];
|
||||
assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone()));
|
||||
// Price set to 101.
|
||||
assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 101, 10, 10, false));
|
||||
|
||||
// Buying fails with InsufficientBalance.
|
||||
assert_noop!(
|
||||
Lottery::do_buy_ticket(&1, &calls[0]),
|
||||
BalancesError::<Test, _>::InsufficientBalance
|
||||
);
|
||||
assert_noop!(Lottery::do_buy_ticket(&1, &calls[0]), TokenError::FundsUnavailable,);
|
||||
assert!(TicketsCount::<Test>::get().is_zero());
|
||||
});
|
||||
}
|
||||
@@ -369,13 +385,13 @@ fn do_buy_ticket_insufficient_balance() {
|
||||
#[test]
|
||||
fn do_buy_ticket_keep_alive() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })];
|
||||
let calls =
|
||||
vec![RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 })];
|
||||
assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone()));
|
||||
// Price set to 100.
|
||||
assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 100, 10, 10, false));
|
||||
|
||||
// Buying fails with KeepAlive.
|
||||
assert_noop!(Lottery::do_buy_ticket(&1, &calls[0]), BalancesError::<Test, _>::KeepAlive);
|
||||
assert_noop!(Lottery::do_buy_ticket(&1, &calls[0]), TokenError::NotExpendable);
|
||||
assert!(TicketsCount::<Test>::get().is_zero());
|
||||
});
|
||||
}
|
||||
@@ -421,7 +437,8 @@ fn choose_ticket_trivial_cases() {
|
||||
#[test]
|
||||
fn choose_account_one_participant() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })];
|
||||
let calls =
|
||||
vec![RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 })];
|
||||
assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone()));
|
||||
assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 10, 10, 10, false));
|
||||
let call = Box::new(calls[0].clone());
|
||||
|
||||
@@ -30,6 +30,7 @@ use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
testing::Header,
|
||||
traits::{BlakeTwo256, IdentityLookup},
|
||||
TokenError,
|
||||
};
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
@@ -84,6 +85,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
pub struct TestBaseCallFilter;
|
||||
@@ -107,7 +112,7 @@ impl Config for Test {
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
use pallet_balances::{Call as BalancesCall, Error as BalancesError};
|
||||
use pallet_balances::Call as BalancesCall;
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
@@ -126,16 +131,16 @@ fn now() -> Timepoint<u64> {
|
||||
}
|
||||
|
||||
fn call_transfer(dest: u64, value: u64) -> Box<RuntimeCall> {
|
||||
Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest, value }))
|
||||
Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest, value }))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multisig_deposit_is_taken_and_returned() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2);
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
|
||||
|
||||
let call = call_transfer(6, 15);
|
||||
let call_weight = call.get_dispatch_info().weight;
|
||||
@@ -196,9 +201,9 @@ fn cancel_multisig_returns_deposit() {
|
||||
fn timepoint_checking_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2);
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
|
||||
|
||||
let call = call_transfer(6, 15);
|
||||
let hash = blake2_256(&call.encode());
|
||||
@@ -254,9 +259,9 @@ fn timepoint_checking_works() {
|
||||
fn multisig_2_of_3_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2);
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
|
||||
|
||||
let call = call_transfer(6, 15);
|
||||
let call_weight = call.get_dispatch_info().weight;
|
||||
@@ -287,9 +292,9 @@ fn multisig_2_of_3_works() {
|
||||
fn multisig_3_of_3_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 3);
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
|
||||
|
||||
let call = call_transfer(6, 15);
|
||||
let call_weight = call.get_dispatch_info().weight;
|
||||
@@ -357,9 +362,9 @@ fn cancel_multisig_works() {
|
||||
fn multisig_2_of_3_as_multi_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2);
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
|
||||
|
||||
let call = call_transfer(6, 15);
|
||||
let call_weight = call.get_dispatch_info().weight;
|
||||
@@ -389,9 +394,9 @@ fn multisig_2_of_3_as_multi_works() {
|
||||
fn multisig_2_of_3_as_multi_with_many_calls_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2);
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
|
||||
|
||||
let call1 = call_transfer(6, 10);
|
||||
let call1_weight = call1.get_dispatch_info().weight;
|
||||
@@ -440,9 +445,9 @@ fn multisig_2_of_3_as_multi_with_many_calls_works() {
|
||||
fn multisig_2_of_3_cannot_reissue_same_call() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2);
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
|
||||
|
||||
let call = call_transfer(6, 10);
|
||||
let call_weight = call.get_dispatch_info().weight;
|
||||
@@ -482,14 +487,13 @@ fn multisig_2_of_3_cannot_reissue_same_call() {
|
||||
call_weight
|
||||
));
|
||||
|
||||
let err = DispatchError::from(BalancesError::<Test, _>::InsufficientBalance).stripped();
|
||||
System::assert_last_event(
|
||||
pallet_multisig::Event::MultisigExecuted {
|
||||
approving: 3,
|
||||
timepoint: now(),
|
||||
multisig: multi,
|
||||
call_hash: hash,
|
||||
result: Err(err),
|
||||
result: Err(TokenError::FundsUnavailable.into()),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
@@ -593,9 +597,9 @@ fn duplicate_approvals_are_ignored() {
|
||||
fn multisig_1_of_3_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 1);
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
|
||||
|
||||
let call = call_transfer(6, 15);
|
||||
let hash = blake2_256(&call.encode());
|
||||
@@ -646,9 +650,9 @@ fn multisig_filters() {
|
||||
fn weight_check_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2);
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
|
||||
|
||||
let call = call_transfer(6, 15);
|
||||
assert_ok!(Multisig::as_multi(
|
||||
@@ -682,9 +686,9 @@ fn multisig_handles_no_preimage_after_all_approve() {
|
||||
// the call will go through.
|
||||
new_test_ext().execute_with(|| {
|
||||
let multi = Multisig::multi_account_id(&[1, 2, 3][..], 3);
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5));
|
||||
|
||||
let call = call_transfer(6, 15);
|
||||
let call_weight = call.get_dispatch_info().weight;
|
||||
|
||||
@@ -89,6 +89,10 @@ impl pallet_balances::Config for Test {
|
||||
type MaxLocks = ();
|
||||
type MaxReserves = ConstU32<50>;
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -295,6 +295,10 @@ mod tests {
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
ord_parameter_types! {
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
# NIS Module
|
||||
|
||||
License: Apache-2.0
|
||||
Provides a non-interactiove variant of staking.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -21,7 +21,9 @@
|
||||
|
||||
use super::*;
|
||||
use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller, BenchmarkError};
|
||||
use frame_support::traits::{nonfungible::Inspect, Currency, EnsureOrigin, Get};
|
||||
use frame_support::traits::{
|
||||
fungible::Inspect as FunInspect, nonfungible::Inspect, EnsureOrigin, Get,
|
||||
};
|
||||
use frame_system::RawOrigin;
|
||||
use sp_arithmetic::Perquintill;
|
||||
use sp_runtime::{
|
||||
@@ -35,7 +37,7 @@ use crate::Pallet as Nis;
|
||||
const SEED: u32 = 0;
|
||||
|
||||
type BalanceOf<T> =
|
||||
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
<<T as Config>::Currency as FunInspect<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
|
||||
fn fill_queues<T: Config>() -> Result<(), DispatchError> {
|
||||
// filling queues involves filling the first queue entirely and placing a single item in all
|
||||
@@ -45,10 +47,7 @@ fn fill_queues<T: Config>() -> Result<(), DispatchError> {
|
||||
let bids = T::MaxQueueLen::get();
|
||||
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
T::Currency::make_free_balance_be(
|
||||
&caller,
|
||||
T::MinBid::get() * BalanceOf::<T>::from(queues + bids),
|
||||
);
|
||||
T::Currency::set_balance(&caller, T::MinBid::get() * BalanceOf::<T>::from(queues + bids));
|
||||
|
||||
for _ in 0..bids {
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
|
||||
@@ -63,7 +62,9 @@ benchmarks! {
|
||||
place_bid {
|
||||
let l in 0..(T::MaxQueueLen::get() - 1);
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
|
||||
let ed = T::Currency::minimum_balance();
|
||||
let bid = T::MinBid::get();
|
||||
T::Currency::set_balance(&caller, (ed + bid) * BalanceOf::<T>::from(l + 1) + bid);
|
||||
for i in 0..l {
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
|
||||
}
|
||||
@@ -75,7 +76,10 @@ benchmarks! {
|
||||
place_bid_max {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let origin = RawOrigin::Signed(caller.clone());
|
||||
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
|
||||
let ed = T::Currency::minimum_balance();
|
||||
let bid = T::MinBid::get();
|
||||
let ql = T::MaxQueueLen::get();
|
||||
T::Currency::set_balance(&caller, (ed + bid) * BalanceOf::<T>::from(ql + 1) + bid);
|
||||
for i in 0..T::MaxQueueLen::get() {
|
||||
Nis::<T>::place_bid(origin.clone().into(), T::MinBid::get(), 1)?;
|
||||
}
|
||||
@@ -90,7 +94,9 @@ benchmarks! {
|
||||
retract_bid {
|
||||
let l in 1..T::MaxQueueLen::get();
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
|
||||
let ed = T::Currency::minimum_balance();
|
||||
let bid = T::MinBid::get();
|
||||
T::Currency::set_balance(&caller, (ed + bid) * BalanceOf::<T>::from(l + 1) + bid);
|
||||
for i in 0..l {
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
|
||||
}
|
||||
@@ -104,50 +110,41 @@ benchmarks! {
|
||||
T::FundOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let bid = T::MinBid::get().max(One::one());
|
||||
T::Currency::make_free_balance_be(&caller, bid);
|
||||
let ed = T::Currency::minimum_balance();
|
||||
T::Currency::set_balance(&caller, ed + bid);
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
|
||||
Nis::<T>::process_queues(Perquintill::one(), 1, 1, &mut WeightCounter::unlimited());
|
||||
Nis::<T>::communify(RawOrigin::Signed(caller.clone()).into(), 0)?;
|
||||
let original = T::Currency::free_balance(&Nis::<T>::account_id());
|
||||
T::Currency::make_free_balance_be(&Nis::<T>::account_id(), BalanceOf::<T>::min_value());
|
||||
let original = T::Currency::balance(&Nis::<T>::account_id());
|
||||
T::Currency::set_balance(&Nis::<T>::account_id(), BalanceOf::<T>::min_value());
|
||||
}: _<T::RuntimeOrigin>(origin)
|
||||
verify {
|
||||
// Must fund at least 99.999% of the required amount.
|
||||
let missing = Perquintill::from_rational(
|
||||
T::Currency::free_balance(&Nis::<T>::account_id()), original).left_from_one();
|
||||
T::Currency::balance(&Nis::<T>::account_id()), original).left_from_one();
|
||||
assert!(missing <= Perquintill::one() / 100_000);
|
||||
}
|
||||
|
||||
thaw_private {
|
||||
communify {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
T::Currency::make_free_balance_be(&caller, T::MinBid::get() * BalanceOf::<T>::from(3u32));
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
|
||||
let bid = T::MinBid::get().max(One::one()) * 100u32.into();
|
||||
let ed = T::Currency::minimum_balance();
|
||||
T::Currency::set_balance(&caller, ed + bid + bid);
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
|
||||
Nis::<T>::process_queues(Perquintill::one(), 1, 2, &mut WeightCounter::unlimited());
|
||||
Receipts::<T>::mutate(0, |m_g| if let Some(ref mut g) = m_g { g.expiry = Zero::zero() });
|
||||
}: _(RawOrigin::Signed(caller.clone()), 0, None)
|
||||
verify {
|
||||
assert!(Receipts::<T>::get(0).is_none());
|
||||
}
|
||||
|
||||
thaw_communal {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
T::Currency::make_free_balance_be(&caller, T::MinBid::get() * BalanceOf::<T>::from(3u32));
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
|
||||
Nis::<T>::process_queues(Perquintill::one(), 1, 2, &mut WeightCounter::unlimited());
|
||||
Receipts::<T>::mutate(0, |m_g| if let Some(ref mut g) = m_g { g.expiry = Zero::zero() });
|
||||
Nis::<T>::communify(RawOrigin::Signed(caller.clone()).into(), 0)?;
|
||||
}: _(RawOrigin::Signed(caller.clone()), 0)
|
||||
verify {
|
||||
assert!(Receipts::<T>::get(0).is_none());
|
||||
assert_eq!(Nis::<T>::owner(&0), None);
|
||||
}
|
||||
|
||||
privatize {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
T::Currency::make_free_balance_be(&caller, T::MinBid::get() * BalanceOf::<T>::from(3u32));
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
|
||||
let bid = T::MinBid::get().max(One::one());
|
||||
let ed = T::Currency::minimum_balance();
|
||||
T::Currency::set_balance(&caller, ed + bid + bid);
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
|
||||
Nis::<T>::process_queues(Perquintill::one(), 1, 2, &mut WeightCounter::unlimited());
|
||||
Nis::<T>::communify(RawOrigin::Signed(caller.clone()).into(), 0)?;
|
||||
}: _(RawOrigin::Signed(caller.clone()), 0)
|
||||
@@ -155,15 +152,39 @@ benchmarks! {
|
||||
assert_eq!(Nis::<T>::owner(&0), Some(caller));
|
||||
}
|
||||
|
||||
communify {
|
||||
thaw_private {
|
||||
let whale: T::AccountId = account("whale", 0, SEED);
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
T::Currency::make_free_balance_be(&caller, T::MinBid::get() * BalanceOf::<T>::from(3u32));
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
|
||||
let bid = T::MinBid::get().max(One::one());
|
||||
let ed = T::Currency::minimum_balance();
|
||||
T::Currency::set_balance(&caller, ed + bid + bid);
|
||||
// Ensure we don't get throttled.
|
||||
T::Currency::set_balance(&whale, T::ThawThrottle::get().0.saturating_reciprocal_mul_ceil(T::Currency::balance(&caller)));
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
|
||||
Nis::<T>::process_queues(Perquintill::one(), 1, 2, &mut WeightCounter::unlimited());
|
||||
frame_system::Pallet::<T>::set_block_number(Receipts::<T>::get(0).unwrap().expiry);
|
||||
}: _(RawOrigin::Signed(caller.clone()), 0, None)
|
||||
verify {
|
||||
assert!(Receipts::<T>::get(0).is_none());
|
||||
}
|
||||
|
||||
thaw_communal {
|
||||
let whale: T::AccountId = account("whale", 0, SEED);
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let bid = T::MinBid::get().max(One::one());
|
||||
let ed = T::Currency::minimum_balance();
|
||||
T::Currency::set_balance(&caller, ed + bid + bid);
|
||||
// Ensure we don't get throttled.
|
||||
T::Currency::set_balance(&whale, T::ThawThrottle::get().0.saturating_reciprocal_mul_ceil(T::Currency::balance(&caller)));
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
|
||||
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
|
||||
Nis::<T>::process_queues(Perquintill::one(), 1, 2, &mut WeightCounter::unlimited());
|
||||
frame_system::Pallet::<T>::set_block_number(Receipts::<T>::get(0).unwrap().expiry);
|
||||
Nis::<T>::communify(RawOrigin::Signed(caller.clone()).into(), 0)?;
|
||||
}: _(RawOrigin::Signed(caller.clone()), 0)
|
||||
verify {
|
||||
assert_eq!(Nis::<T>::owner(&0), None);
|
||||
assert!(Receipts::<T>::get(0).is_none());
|
||||
}
|
||||
|
||||
process_queues {
|
||||
@@ -197,6 +218,9 @@ benchmarks! {
|
||||
|
||||
process_bid {
|
||||
let who = account::<T::AccountId>("bidder", 0, SEED);
|
||||
let min_bid = T::MinBid::get().max(One::one());
|
||||
let ed = T::Currency::minimum_balance();
|
||||
T::Currency::set_balance(&who, ed + min_bid);
|
||||
let bid = Bid {
|
||||
amount: T::MinBid::get(),
|
||||
who,
|
||||
@@ -216,5 +240,5 @@ benchmarks! {
|
||||
)
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Nis, crate::mock::new_test_ext(), crate::mock::Test);
|
||||
impl_benchmark_test_suite!(Nis, crate::mock::new_test_ext_empty(), crate::mock::Test);
|
||||
}
|
||||
|
||||
+121
-119
@@ -71,21 +71,21 @@
|
||||
//!
|
||||
//! ## Terms
|
||||
//!
|
||||
//! - *Effective total issuance*: The total issuance of balances in the system, including all claims
|
||||
//! of all outstanding receipts but excluding `IgnoredIssuance`.
|
||||
//! - *Effective total issuance*: The total issuance of balances in the system, equal to the active
|
||||
//! issuance plus the value of all outstanding receipts, less `IgnoredIssuance`.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use frame_support::{
|
||||
dispatch::{DispatchError, DispatchResult},
|
||||
traits::fungible::{Inspect as FungibleInspect, Mutate as FungibleMutate},
|
||||
use frame_support::traits::{
|
||||
fungible::{self, Inspect as FunInspect, Mutate as FunMutate},
|
||||
tokens::{DepositConsequence, Fortitude, Preservation, Provenance, WithdrawConsequence},
|
||||
};
|
||||
pub use pallet::*;
|
||||
use sp_arithmetic::{traits::Unsigned, RationalArg};
|
||||
use sp_core::TypedGet;
|
||||
use sp_runtime::{
|
||||
traits::{Convert, ConvertBack},
|
||||
Perquintill,
|
||||
DispatchError, Perquintill,
|
||||
};
|
||||
|
||||
mod benchmarking;
|
||||
@@ -117,7 +117,7 @@ where
|
||||
}
|
||||
|
||||
pub struct NoCounterpart<T>(sp_std::marker::PhantomData<T>);
|
||||
impl<T> FungibleInspect<T> for NoCounterpart<T> {
|
||||
impl<T> FunInspect<T> for NoCounterpart<T> {
|
||||
type Balance = u32;
|
||||
fn total_issuance() -> u32 {
|
||||
0
|
||||
@@ -125,34 +125,30 @@ impl<T> FungibleInspect<T> for NoCounterpart<T> {
|
||||
fn minimum_balance() -> u32 {
|
||||
0
|
||||
}
|
||||
fn balance(_who: &T) -> u32 {
|
||||
fn balance(_: &T) -> u32 {
|
||||
0
|
||||
}
|
||||
fn reducible_balance(_who: &T, _keep_alive: bool) -> u32 {
|
||||
fn total_balance(_: &T) -> u32 {
|
||||
0
|
||||
}
|
||||
fn can_deposit(
|
||||
_who: &T,
|
||||
_amount: u32,
|
||||
_mint: bool,
|
||||
) -> frame_support::traits::tokens::DepositConsequence {
|
||||
frame_support::traits::tokens::DepositConsequence::Success
|
||||
fn reducible_balance(_: &T, _: Preservation, _: Fortitude) -> u32 {
|
||||
0
|
||||
}
|
||||
fn can_withdraw(
|
||||
_who: &T,
|
||||
_amount: u32,
|
||||
) -> frame_support::traits::tokens::WithdrawConsequence<u32> {
|
||||
frame_support::traits::tokens::WithdrawConsequence::Success
|
||||
fn can_deposit(_: &T, _: u32, _: Provenance) -> DepositConsequence {
|
||||
DepositConsequence::Success
|
||||
}
|
||||
fn can_withdraw(_: &T, _: u32) -> WithdrawConsequence<u32> {
|
||||
WithdrawConsequence::Success
|
||||
}
|
||||
}
|
||||
impl<T> FungibleMutate<T> for NoCounterpart<T> {
|
||||
fn mint_into(_who: &T, _amount: u32) -> DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
fn burn_from(_who: &T, _amount: u32) -> Result<u32, DispatchError> {
|
||||
Ok(0)
|
||||
impl<T> fungible::Unbalanced<T> for NoCounterpart<T> {
|
||||
fn handle_dust(_: fungible::Dust<T, Self>) {}
|
||||
fn write_balance(_: &T, _: Self::Balance) -> Result<Option<Self::Balance>, DispatchError> {
|
||||
Ok(None)
|
||||
}
|
||||
fn set_total_issuance(_: Self::Balance) {}
|
||||
}
|
||||
impl<T> FunMutate<T> for NoCounterpart<T> {}
|
||||
impl<T> Convert<Perquintill, u32> for NoCounterpart<T> {
|
||||
fn convert(_: Perquintill) -> u32 {
|
||||
0
|
||||
@@ -161,15 +157,24 @@ impl<T> Convert<Perquintill, u32> for NoCounterpart<T> {
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::{FungibleInspect, FungibleMutate};
|
||||
use super::{FunInspect, FunMutate};
|
||||
pub use crate::weights::WeightInfo;
|
||||
use frame_support::{
|
||||
pallet_prelude::*,
|
||||
traits::{
|
||||
nonfungible::{Inspect as NonfungibleInspect, Transfer as NonfungibleTransfer},
|
||||
Currency, Defensive, DefensiveSaturating,
|
||||
ExistenceRequirement::AllowDeath,
|
||||
NamedReservableCurrency, OnUnbalanced,
|
||||
fungible::{
|
||||
self,
|
||||
hold::{Inspect as FunHoldInspect, Mutate as FunHoldMutate},
|
||||
Balanced as FunBalanced,
|
||||
},
|
||||
nonfungible::{Inspect as NftInspect, Transfer as NftTransfer},
|
||||
tokens::{
|
||||
Fortitude::Polite,
|
||||
Precision::{BestEffort, Exact},
|
||||
Preservation::Expendable,
|
||||
Restriction::{Free, OnHold},
|
||||
},
|
||||
Defensive, DefensiveSaturating, OnUnbalanced,
|
||||
},
|
||||
PalletId,
|
||||
};
|
||||
@@ -177,15 +182,14 @@ pub mod pallet {
|
||||
use sp_arithmetic::{PerThing, Perquintill};
|
||||
use sp_runtime::{
|
||||
traits::{AccountIdConversion, Bounded, Convert, ConvertBack, Saturating, Zero},
|
||||
TokenError,
|
||||
Rounding, TokenError,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
type BalanceOf<T> =
|
||||
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
type PositiveImbalanceOf<T> = <<T as Config>::Currency as Currency<
|
||||
<T as frame_system::Config>::AccountId,
|
||||
>>::PositiveImbalance;
|
||||
<<T as Config>::Currency as FunInspect<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
type DebtOf<T> =
|
||||
fungible::Debt<<T as frame_system::Config>::AccountId, <T as Config>::Currency>;
|
||||
type ReceiptRecordOf<T> = ReceiptRecord<
|
||||
<T as frame_system::Config>::AccountId,
|
||||
<T as frame_system::Config>::BlockNumber,
|
||||
@@ -209,7 +213,16 @@ pub mod pallet {
|
||||
type PalletId: Get<PalletId>;
|
||||
|
||||
/// Currency type that this works on.
|
||||
type Currency: NamedReservableCurrency<Self::AccountId, Balance = Self::CurrencyBalance>;
|
||||
type Currency: FunInspect<Self::AccountId, Balance = Self::CurrencyBalance>
|
||||
+ FunMutate<Self::AccountId>
|
||||
+ FunBalanced<Self::AccountId>
|
||||
+ FunHoldInspect<Self::AccountId>
|
||||
+ FunHoldMutate<Self::AccountId>;
|
||||
|
||||
/// The identifier of the hold reason.
|
||||
|
||||
#[pallet::constant]
|
||||
type HoldReason: Get<<Self::Currency as FunHoldInspect<Self::AccountId>>::Reason>;
|
||||
|
||||
/// Just the `Currency::Balance` type; we have this item to allow us to constrain it to
|
||||
/// `From<u64>`.
|
||||
@@ -231,7 +244,7 @@ pub mod pallet {
|
||||
type IgnoredIssuance: Get<BalanceOf<Self>>;
|
||||
|
||||
/// The accounting system for the fungible counterpart tokens.
|
||||
type Counterpart: FungibleMutate<Self::AccountId>;
|
||||
type Counterpart: FunMutate<Self::AccountId>;
|
||||
|
||||
/// The system to convert an overall proportion of issuance into a number of fungible
|
||||
/// counterpart tokens.
|
||||
@@ -239,12 +252,12 @@ pub mod pallet {
|
||||
/// In general it's best to use `WithMaximumOf`.
|
||||
type CounterpartAmount: ConvertBack<
|
||||
Perquintill,
|
||||
<Self::Counterpart as FungibleInspect<Self::AccountId>>::Balance,
|
||||
<Self::Counterpart as FunInspect<Self::AccountId>>::Balance,
|
||||
>;
|
||||
|
||||
/// Unbalanced handler to account for funds created (in case of a higher total issuance over
|
||||
/// freezing period).
|
||||
type Deficit: OnUnbalanced<PositiveImbalanceOf<Self>>;
|
||||
type Deficit: OnUnbalanced<DebtOf<Self>>;
|
||||
|
||||
/// The target sum of all receipts' proportions.
|
||||
type Target: Get<Perquintill>;
|
||||
@@ -301,12 +314,6 @@ pub mod pallet {
|
||||
/// The maximum proportion which may be thawed and the period over which it is reset.
|
||||
#[pallet::constant]
|
||||
type ThawThrottle: Get<(Perquintill, Self::BlockNumber)>;
|
||||
|
||||
/// The name for the reserve ID.
|
||||
#[pallet::constant]
|
||||
type ReserveId: Get<
|
||||
<Self::Currency as NamedReservableCurrency<Self::AccountId>>::ReserveIdentifier,
|
||||
>;
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
@@ -348,7 +355,7 @@ pub mod pallet {
|
||||
///
|
||||
/// `issuance - frozen + proportion * issuance`
|
||||
///
|
||||
/// where `issuance = total_issuance - IgnoredIssuance`
|
||||
/// where `issuance = active_issuance - IgnoredIssuance`
|
||||
#[derive(
|
||||
Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen,
|
||||
)]
|
||||
@@ -473,6 +480,9 @@ pub mod pallet {
|
||||
AlreadyCommunal,
|
||||
/// The receipt is already private.
|
||||
AlreadyPrivate,
|
||||
Release1,
|
||||
Release2,
|
||||
Tah,
|
||||
}
|
||||
|
||||
pub(crate) struct WeightCounter {
|
||||
@@ -554,16 +564,17 @@ pub mod pallet {
|
||||
|q| -> Result<(u32, BalanceOf<T>), DispatchError> {
|
||||
let queue_full = q.len() == T::MaxQueueLen::get() as usize;
|
||||
ensure!(!queue_full || q[0].amount < amount, Error::<T>::BidTooLow);
|
||||
T::Currency::reserve_named(&T::ReserveId::get(), &who, amount)?;
|
||||
T::Currency::hold(&T::HoldReason::get(), &who, amount)?;
|
||||
|
||||
// queue is <Ordered: Lowest ... Highest><Fifo: Last ... First>
|
||||
let mut bid = Bid { amount, who: who.clone() };
|
||||
let net = if queue_full {
|
||||
sp_std::mem::swap(&mut q[0], &mut bid);
|
||||
let _ = T::Currency::unreserve_named(
|
||||
&T::ReserveId::get(),
|
||||
let _ = T::Currency::release(
|
||||
&T::HoldReason::get(),
|
||||
&bid.who,
|
||||
bid.amount,
|
||||
BestEffort,
|
||||
);
|
||||
Self::deposit_event(Event::<T>::BidDropped {
|
||||
who: bid.who,
|
||||
@@ -615,19 +626,21 @@ pub mod pallet {
|
||||
ensure!(queue_index < queue_count, Error::<T>::DurationTooBig);
|
||||
|
||||
let bid = Bid { amount, who };
|
||||
let new_len = Queues::<T>::try_mutate(duration, |q| -> Result<u32, DispatchError> {
|
||||
let pos = q.iter().position(|i| i == &bid).ok_or(Error::<T>::UnknownBid)?;
|
||||
q.remove(pos);
|
||||
Ok(q.len() as u32)
|
||||
})?;
|
||||
|
||||
let mut queue = Queues::<T>::get(duration);
|
||||
let pos = queue.iter().position(|i| i == &bid).ok_or(Error::<T>::UnknownBid)?;
|
||||
queue.remove(pos);
|
||||
let new_len = queue.len() as u32;
|
||||
|
||||
T::Currency::release(&T::HoldReason::get(), &bid.who, bid.amount, BestEffort)?;
|
||||
|
||||
Queues::<T>::insert(duration, queue);
|
||||
QueueTotals::<T>::mutate(|qs| {
|
||||
qs.bounded_resize(queue_count, (0, Zero::zero()));
|
||||
qs[queue_index].0 = new_len;
|
||||
qs[queue_index].1.saturating_reduce(bid.amount);
|
||||
});
|
||||
|
||||
T::Currency::unreserve_named(&T::ReserveId::get(), &bid.who, bid.amount);
|
||||
Self::deposit_event(Event::BidRetracted { who: bid.who, amount: bid.amount, duration });
|
||||
|
||||
Ok(())
|
||||
@@ -645,7 +658,7 @@ pub mod pallet {
|
||||
let issuance = Self::issuance_with(&our_account, &summary);
|
||||
let deficit = issuance.required.saturating_sub(issuance.holdings);
|
||||
ensure!(!deficit.is_zero(), Error::<T>::AlreadyFunded);
|
||||
T::Deficit::on_unbalanced(T::Currency::deposit_creating(&our_account, deficit));
|
||||
T::Deficit::on_unbalanced(T::Currency::deposit(&our_account, deficit, Exact)?);
|
||||
Self::deposit_event(Event::<T>::Funded { deficit });
|
||||
Ok(())
|
||||
}
|
||||
@@ -702,6 +715,7 @@ pub mod pallet {
|
||||
// Multiply the proportion it is by the total issued.
|
||||
let our_account = Self::account_id();
|
||||
let effective_issuance = Self::issuance_with(&our_account, &summary).effective;
|
||||
// let amount = proportion.mul_ceil(effective_issuance);
|
||||
let amount = proportion * effective_issuance;
|
||||
|
||||
receipt.proportion.saturating_reduce(proportion);
|
||||
@@ -710,46 +724,40 @@ pub mod pallet {
|
||||
let dropped = receipt.proportion.is_zero();
|
||||
|
||||
if amount > on_hold {
|
||||
T::Currency::unreserve_named(&T::ReserveId::get(), &who, on_hold);
|
||||
T::Currency::release(&T::HoldReason::get(), &who, on_hold, Exact)
|
||||
.map_err(|_| Error::<T>::Release1)?;
|
||||
let deficit = amount - on_hold;
|
||||
// Try to transfer deficit from pot to receipt owner.
|
||||
summary.receipts_on_hold.saturating_reduce(on_hold);
|
||||
on_hold = Zero::zero();
|
||||
T::Currency::transfer(&our_account, &who, deficit, AllowDeath)
|
||||
T::Currency::transfer(&our_account, &who, deficit, Expendable)
|
||||
.map_err(|_| Error::<T>::Unfunded)?;
|
||||
} else {
|
||||
T::Currency::unreserve_named(&T::ReserveId::get(), &who, amount);
|
||||
on_hold.saturating_reduce(amount);
|
||||
summary.receipts_on_hold.saturating_reduce(amount);
|
||||
if dropped && !on_hold.is_zero() {
|
||||
// Reclaim any remainder:
|
||||
// Transfer `excess` to the pot if we have now fully compensated for the
|
||||
// receipt.
|
||||
//
|
||||
// This will legitimately fail if there is no pot account in existance.
|
||||
// There's nothing we can do about this so we just swallow the error.
|
||||
// This code is not ideal and could fail in the second phase leaving
|
||||
// the system in an invalid state. It can be fixed properly with the
|
||||
// new API in https://github.com/paritytech/substrate/pull/12951
|
||||
//
|
||||
// Below is what it should look like then:
|
||||
// let _ = T::Currency::repatriate_reserved_named(
|
||||
// &T::ReserveId::get(),
|
||||
// &who,
|
||||
// &our_account,
|
||||
// excess,
|
||||
// BalanceStatus::Free,
|
||||
// ).defensive();
|
||||
T::Currency::unreserve_named(&T::ReserveId::get(), &who, on_hold);
|
||||
// It could theoretically be locked, so really we should be using a more
|
||||
// forceful variant. But the alternative `repatriate_reserved_named` will
|
||||
// fail if the destination account doesn't exist. This should be fixed when
|
||||
// we move to the `fungible::*` traits, which should include a force
|
||||
// transfer function to transfer the reserved balance into free balance in
|
||||
// the destination regardless of locks and create it if it doesn't exist.
|
||||
let _ = T::Currency::transfer(&who, &Self::account_id(), on_hold, AllowDeath);
|
||||
// Transfer excess of `on_hold` to the pot if we have now fully compensated for
|
||||
// the receipt.
|
||||
T::Currency::transfer_on_hold(
|
||||
&T::HoldReason::get(),
|
||||
&who,
|
||||
&our_account,
|
||||
on_hold,
|
||||
Exact,
|
||||
Free,
|
||||
Polite,
|
||||
)
|
||||
.map(|_| ())
|
||||
// We ignore this error as it just means the amount we're trying to deposit is
|
||||
// dust and the beneficiary account doesn't exist.
|
||||
.or_else(
|
||||
|e| if e == TokenError::CannotCreate.into() { Ok(()) } else { Err(e) },
|
||||
)?;
|
||||
summary.receipts_on_hold.saturating_reduce(on_hold);
|
||||
}
|
||||
T::Currency::release(&T::HoldReason::get(), &who, amount, Exact)
|
||||
.map_err(|_| Error::<T>::Release2)?;
|
||||
}
|
||||
|
||||
if dropped {
|
||||
@@ -797,7 +805,8 @@ pub mod pallet {
|
||||
summary.thawed.saturating_accrue(receipt.proportion);
|
||||
ensure!(summary.thawed <= throttle, Error::<T>::Throttled);
|
||||
|
||||
T::Counterpart::burn_from(&who, T::CounterpartAmount::convert(receipt.proportion))?;
|
||||
let cp_amount = T::CounterpartAmount::convert(receipt.proportion);
|
||||
T::Counterpart::burn_from(&who, cp_amount, Exact, Polite)?;
|
||||
|
||||
// Multiply the proportion it is by the total issued.
|
||||
let our_account = Self::account_id();
|
||||
@@ -807,7 +816,7 @@ pub mod pallet {
|
||||
summary.proportion_owed.saturating_reduce(receipt.proportion);
|
||||
|
||||
// Try to transfer amount owed from pot to receipt owner.
|
||||
T::Currency::transfer(&our_account, &who, amount, AllowDeath)
|
||||
T::Currency::transfer(&our_account, &who, amount, Expendable)
|
||||
.map_err(|_| Error::<T>::Unfunded)?;
|
||||
|
||||
Receipts::<T>::remove(index);
|
||||
@@ -840,11 +849,10 @@ pub mod pallet {
|
||||
ensure!(owner == who, Error::<T>::NotOwner);
|
||||
|
||||
// Unreserve and transfer the funds to the pot.
|
||||
T::Currency::unreserve_named(&T::ReserveId::get(), &who, on_hold);
|
||||
// Transfer `excess` to the pot if we have now fully compensated for the receipt.
|
||||
T::Currency::transfer(&who, &Self::account_id(), on_hold, AllowDeath)
|
||||
let reason = T::HoldReason::get();
|
||||
let us = Self::account_id();
|
||||
T::Currency::transfer_on_hold(&reason, &who, &us, on_hold, Exact, Free, Polite)
|
||||
.map_err(|_| Error::<T>::Unfunded)?;
|
||||
// TODO #12951: ^^^ The above should be done in a single operation `transfer_on_hold`.
|
||||
|
||||
// Record that we've moved the amount reserved.
|
||||
let mut summary: SummaryRecordOf<T> = Summary::<T>::get();
|
||||
@@ -881,16 +889,20 @@ pub mod pallet {
|
||||
let effective_issuance = Self::issuance_with(&our_account, &summary).effective;
|
||||
let max_amount = receipt.proportion * effective_issuance;
|
||||
// Avoid trying to place more in the account's reserve than we have available in the pot
|
||||
let amount = max_amount.min(T::Currency::free_balance(&our_account));
|
||||
let amount = max_amount.min(T::Currency::balance(&our_account));
|
||||
|
||||
// Burn fungible counterparts.
|
||||
T::Counterpart::burn_from(&who, T::CounterpartAmount::convert(receipt.proportion))?;
|
||||
T::Counterpart::burn_from(
|
||||
&who,
|
||||
T::CounterpartAmount::convert(receipt.proportion),
|
||||
Exact,
|
||||
Polite,
|
||||
)?;
|
||||
|
||||
// Transfer the funds from the pot to the owner and reserve
|
||||
T::Currency::transfer(&Self::account_id(), &who, amount, AllowDeath)
|
||||
.map_err(|_| Error::<T>::Unfunded)?;
|
||||
T::Currency::reserve_named(&T::ReserveId::get(), &who, amount)?;
|
||||
// TODO: ^^^ The above should be done in a single operation `transfer_and_hold`.
|
||||
let reason = T::HoldReason::get();
|
||||
let us = Self::account_id();
|
||||
T::Currency::transfer_and_hold(&reason, &us, &who, amount, Exact, Expendable, Polite)?;
|
||||
|
||||
// Record that we've moved the amount reserved.
|
||||
summary.receipts_on_hold.saturating_accrue(amount);
|
||||
@@ -920,7 +932,7 @@ pub mod pallet {
|
||||
pub required: Balance,
|
||||
}
|
||||
|
||||
impl<T: Config> NonfungibleInspect<T::AccountId> for Pallet<T> {
|
||||
impl<T: Config> NftInspect<T::AccountId> for Pallet<T> {
|
||||
type ItemId = ReceiptIndex;
|
||||
|
||||
fn owner(item: &ReceiptIndex) -> Option<T::AccountId> {
|
||||
@@ -939,31 +951,19 @@ pub mod pallet {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> NonfungibleTransfer<T::AccountId> for Pallet<T> {
|
||||
fn transfer(index: &ReceiptIndex, destination: &T::AccountId) -> DispatchResult {
|
||||
impl<T: Config> NftTransfer<T::AccountId> for Pallet<T> {
|
||||
fn transfer(index: &ReceiptIndex, dest: &T::AccountId) -> DispatchResult {
|
||||
let mut item = Receipts::<T>::get(index).ok_or(TokenError::UnknownAsset)?;
|
||||
let (owner, on_hold) = item.owner.take().ok_or(Error::<T>::AlreadyCommunal)?;
|
||||
|
||||
// TODO: This should all be replaced by a single call `transfer_held`.
|
||||
let shortfall = T::Currency::unreserve_named(&T::ReserveId::get(), &owner, on_hold);
|
||||
if !shortfall.is_zero() {
|
||||
let _ =
|
||||
T::Currency::reserve_named(&T::ReserveId::get(), &owner, on_hold - shortfall);
|
||||
return Err(TokenError::NoFunds.into())
|
||||
}
|
||||
if let Err(e) = T::Currency::transfer(&owner, destination, on_hold, AllowDeath) {
|
||||
let _ = T::Currency::reserve_named(&T::ReserveId::get(), &owner, on_hold);
|
||||
return Err(e)
|
||||
}
|
||||
// This can never fail, and if it somehow does, then we can't handle this gracefully.
|
||||
let _ =
|
||||
T::Currency::reserve_named(&T::ReserveId::get(), destination, on_hold).defensive();
|
||||
let reason = T::HoldReason::get();
|
||||
T::Currency::transfer_on_hold(&reason, &owner, dest, on_hold, Exact, OnHold, Polite)?;
|
||||
|
||||
item.owner = Some((destination.clone(), on_hold));
|
||||
item.owner = Some((dest.clone(), on_hold));
|
||||
Receipts::<T>::insert(&index, &item);
|
||||
Pallet::<T>::deposit_event(Event::<T>::Transferred {
|
||||
from: owner,
|
||||
to: destination.clone(),
|
||||
to: dest.clone(),
|
||||
index: *index,
|
||||
});
|
||||
Ok(())
|
||||
@@ -997,9 +997,9 @@ pub mod pallet {
|
||||
summary: &SummaryRecordOf<T>,
|
||||
) -> IssuanceInfo<BalanceOf<T>> {
|
||||
let total_issuance =
|
||||
T::Currency::total_issuance().saturating_sub(T::IgnoredIssuance::get());
|
||||
T::Currency::active_issuance().saturating_sub(T::IgnoredIssuance::get());
|
||||
let holdings =
|
||||
T::Currency::free_balance(our_account).saturating_add(summary.receipts_on_hold);
|
||||
T::Currency::balance(our_account).saturating_add(summary.receipts_on_hold);
|
||||
let other = total_issuance.saturating_sub(holdings);
|
||||
let effective =
|
||||
summary.proportion_owed.left_from_one().saturating_reciprocal_mul(other);
|
||||
@@ -1136,7 +1136,9 @@ pub mod pallet {
|
||||
// Now to activate the bid...
|
||||
let n = amount;
|
||||
let d = issuance.effective;
|
||||
let proportion = Perquintill::from_rational(n, d);
|
||||
let proportion =
|
||||
Perquintill::from_rational_with_rounding(n, d, Rounding::NearestPrefDown)
|
||||
.defensive_unwrap_or_default();
|
||||
let who = bid.who;
|
||||
let index = summary.index;
|
||||
summary.proportion_owed.defensive_saturating_accrue(proportion);
|
||||
|
||||
@@ -19,13 +19,18 @@
|
||||
|
||||
use crate::{self as pallet_nis, Perquintill, WithMaximumOf};
|
||||
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use frame_support::{
|
||||
ord_parameter_types, parameter_types,
|
||||
traits::{ConstU16, ConstU32, ConstU64, Currency, OnFinalize, OnInitialize, StorageMapShim},
|
||||
traits::{
|
||||
fungible::Inspect, ConstU16, ConstU32, ConstU64, Everything, OnFinalize, OnInitialize,
|
||||
StorageMapShim,
|
||||
},
|
||||
weights::Weight,
|
||||
PalletId,
|
||||
};
|
||||
use pallet_balances::{Instance1, Instance2};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_core::{ConstU128, H256};
|
||||
use sp_runtime::{
|
||||
testing::Header,
|
||||
@@ -35,6 +40,8 @@ use sp_runtime::{
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
pub type Balance = u64;
|
||||
|
||||
// Configure a mock runtime to test the pallet.
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Test where
|
||||
@@ -50,7 +57,7 @@ frame_support::construct_runtime!(
|
||||
);
|
||||
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type BaseCallFilter = Everything;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
@@ -67,35 +74,45 @@ impl frame_system::Config for Test {
|
||||
type DbWeight = ();
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type AccountData = pallet_balances::AccountData<Balance>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ConstU16<42>;
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
type MaxConsumers = ConstU32<16>;
|
||||
}
|
||||
|
||||
impl pallet_balances::Config<Instance1> for Test {
|
||||
type Balance = u64;
|
||||
type Balance = Balance;
|
||||
type DustRemoval = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type ExistentialDeposit = frame_support::traits::ConstU64<1>;
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type MaxLocks = ();
|
||||
type MaxReserves = ConstU32<1>;
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = HoldIdentifier;
|
||||
type MaxHolds = ConstU32<1>;
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, Debug, TypeInfo,
|
||||
)]
|
||||
pub enum HoldIdentifier {
|
||||
Nis,
|
||||
}
|
||||
|
||||
impl pallet_balances::Config<Instance2> for Test {
|
||||
type Balance = u128;
|
||||
type DustRemoval = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type ExistentialDeposit = frame_support::traits::ConstU128<1>;
|
||||
type ExistentialDeposit = ConstU128<1>;
|
||||
type AccountStore = StorageMapShim<
|
||||
pallet_balances::Account<Test, Instance2>,
|
||||
frame_system::Provider<Test>,
|
||||
u64,
|
||||
pallet_balances::AccountData<u128>,
|
||||
>;
|
||||
@@ -103,16 +120,20 @@ impl pallet_balances::Config<Instance2> for Test {
|
||||
type MaxLocks = ();
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub IgnoredIssuance: u64 = Balances::total_balance(&0); // Account zero is ignored.
|
||||
pub IgnoredIssuance: Balance = Balances::total_balance(&0); // Account zero is ignored.
|
||||
pub const NisPalletId: PalletId = PalletId(*b"py/nis ");
|
||||
pub static Target: Perquintill = Perquintill::zero();
|
||||
pub const MinReceipt: Perquintill = Perquintill::from_percent(1);
|
||||
pub const ThawThrottle: (Perquintill, u64) = (Perquintill::from_percent(25), 5);
|
||||
pub static MaxIntakeWeight: Weight = Weight::from_parts(2_000_000_000_000, 0);
|
||||
pub const ReserveId: [u8; 8] = *b"py/nis ";
|
||||
pub const HoldReason: HoldIdentifier = HoldIdentifier::Nis;
|
||||
}
|
||||
|
||||
ord_parameter_types! {
|
||||
@@ -140,7 +161,7 @@ impl pallet_nis::Config for Test {
|
||||
type MaxIntakeWeight = MaxIntakeWeight;
|
||||
type MinReceipt = MinReceipt;
|
||||
type ThawThrottle = ThawThrottle;
|
||||
type ReserveId = ReserveId;
|
||||
type HoldReason = HoldReason;
|
||||
}
|
||||
|
||||
// This function basically just builds a genesis storage key/value store according to
|
||||
@@ -155,6 +176,13 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
t.into()
|
||||
}
|
||||
|
||||
// This function basically just builds a genesis storage key/value store according to
|
||||
// our desired mockup, but without any balances.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn new_test_ext_empty() -> sp_io::TestExternalities {
|
||||
frame_system::GenesisConfig::default().build_storage::<Test>().unwrap().into()
|
||||
}
|
||||
|
||||
pub fn run_to_block(n: u64) {
|
||||
while System::block_number() < n {
|
||||
Nis::on_finalize(System::block_number());
|
||||
|
||||
@@ -22,19 +22,22 @@ use crate::{mock::*, Error};
|
||||
use frame_support::{
|
||||
assert_noop, assert_ok,
|
||||
traits::{
|
||||
fungible::{hold::Inspect as InspectHold, Inspect as FunInspect, Mutate as FunMutate},
|
||||
nonfungible::{Inspect, Transfer},
|
||||
Currency,
|
||||
tokens::{Fortitude::Force, Precision::Exact},
|
||||
},
|
||||
};
|
||||
use pallet_balances::{Error as BalancesError, Instance1};
|
||||
use sp_arithmetic::Perquintill;
|
||||
use sp_runtime::{Saturating, TokenError};
|
||||
use sp_runtime::{
|
||||
Saturating,
|
||||
TokenError::{self, FundsUnavailable},
|
||||
};
|
||||
|
||||
fn pot() -> u64 {
|
||||
fn pot() -> Balance {
|
||||
Balances::free_balance(&Nis::account_id())
|
||||
}
|
||||
|
||||
fn holdings() -> u64 {
|
||||
fn holdings() -> Balance {
|
||||
Nis::issuance().holdings
|
||||
}
|
||||
|
||||
@@ -42,8 +45,8 @@ fn signed(who: u64) -> RuntimeOrigin {
|
||||
RuntimeOrigin::signed(who)
|
||||
}
|
||||
|
||||
fn enlarge(amount: u64, max_bids: u32) {
|
||||
let summary: SummaryRecord<u64, u64> = Summary::<Test>::get();
|
||||
fn enlarge(amount: Balance, max_bids: u32) {
|
||||
let summary: SummaryRecord<u64, Balance> = Summary::<Test>::get();
|
||||
let increase_in_proportion_owed = Perquintill::from_rational(amount, Nis::issuance().effective);
|
||||
let target = summary.proportion_owed.saturating_add(increase_in_proportion_owed);
|
||||
Nis::process_queues(target, u32::max_value(), max_bids, &mut WeightCounter::unlimited());
|
||||
@@ -75,10 +78,7 @@ fn place_bid_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
assert_noop!(Nis::place_bid(signed(1), 1, 2), Error::<Test>::AmountTooSmall);
|
||||
assert_noop!(
|
||||
Nis::place_bid(signed(1), 101, 2),
|
||||
BalancesError::<Test, Instance1>::InsufficientBalance
|
||||
);
|
||||
assert_noop!(Nis::place_bid(signed(1), 101, 2), FundsUnavailable);
|
||||
assert_noop!(Nis::place_bid(signed(1), 10, 4), Error::<Test>::DurationTooBig);
|
||||
assert_ok!(Nis::place_bid(signed(1), 10, 2));
|
||||
assert_eq!(Balances::reserved_balance(1), 10);
|
||||
@@ -451,16 +451,16 @@ fn communify_works() {
|
||||
assert_noop!(Nis::thaw_communal(signed(1), 1), Error::<Test>::UnknownReceipt);
|
||||
|
||||
// Transfer some of the fungibles away.
|
||||
assert_ok!(NisBalances::transfer(signed(1), 2, 100_000));
|
||||
assert_ok!(NisBalances::transfer_allow_death(signed(1), 2, 100_000));
|
||||
assert_eq!(NisBalances::free_balance(&1), 2_000_000);
|
||||
assert_eq!(NisBalances::free_balance(&2), 100_000);
|
||||
|
||||
// Communal thawing with the correct index is not possible now.
|
||||
assert_noop!(Nis::thaw_communal(signed(1), 0), TokenError::NoFunds);
|
||||
assert_noop!(Nis::thaw_communal(signed(2), 0), TokenError::NoFunds);
|
||||
assert_noop!(Nis::thaw_communal(signed(1), 0), TokenError::FundsUnavailable);
|
||||
assert_noop!(Nis::thaw_communal(signed(2), 0), TokenError::FundsUnavailable);
|
||||
|
||||
// Transfer the rest to 2...
|
||||
assert_ok!(NisBalances::transfer(signed(1), 2, 2_000_000));
|
||||
assert_ok!(NisBalances::transfer_allow_death(signed(1), 2, 2_000_000));
|
||||
assert_eq!(NisBalances::free_balance(&1), 0);
|
||||
assert_eq!(NisBalances::free_balance(&2), 2_100_000);
|
||||
|
||||
@@ -487,8 +487,8 @@ fn privatize_works() {
|
||||
assert_ok!(Nis::communify(signed(1), 0));
|
||||
|
||||
// Transfer the fungibles to #2
|
||||
assert_ok!(NisBalances::transfer(signed(1), 2, 2_100_000));
|
||||
assert_noop!(Nis::privatize(signed(1), 0), TokenError::NoFunds);
|
||||
assert_ok!(NisBalances::transfer_allow_death(signed(1), 2, 2_100_000));
|
||||
assert_noop!(Nis::privatize(signed(1), 0), TokenError::FundsUnavailable);
|
||||
|
||||
// Privatize
|
||||
assert_ok!(Nis::privatize(signed(2), 0));
|
||||
@@ -513,16 +513,16 @@ fn privatize_and_thaw_with_another_receipt_works() {
|
||||
assert_ok!(Nis::communify(signed(2), 1));
|
||||
|
||||
// Transfer half of fungibles to #3 from each of #1 and #2, and the other half from #2 to #4
|
||||
assert_ok!(NisBalances::transfer(signed(1), 3, 1_050_000));
|
||||
assert_ok!(NisBalances::transfer(signed(2), 3, 1_050_000));
|
||||
assert_ok!(NisBalances::transfer(signed(2), 4, 1_050_000));
|
||||
assert_ok!(NisBalances::transfer_allow_death(signed(1), 3, 1_050_000));
|
||||
assert_ok!(NisBalances::transfer_allow_death(signed(2), 3, 1_050_000));
|
||||
assert_ok!(NisBalances::transfer_allow_death(signed(2), 4, 1_050_000));
|
||||
|
||||
// #3 privatizes, partially thaws, then re-communifies with #0, then transfers the fungibles
|
||||
// to #2
|
||||
assert_ok!(Nis::privatize(signed(3), 0));
|
||||
assert_ok!(Nis::thaw_private(signed(3), 0, Some(Perquintill::from_percent(5))));
|
||||
assert_ok!(Nis::communify(signed(3), 0));
|
||||
assert_ok!(NisBalances::transfer(signed(3), 1, 1_050_000));
|
||||
assert_ok!(NisBalances::transfer_allow_death(signed(3), 1, 1_050_000));
|
||||
|
||||
// #1 now has enough to thaw using receipt 1
|
||||
assert_ok!(Nis::thaw_communal(signed(1), 1));
|
||||
@@ -536,17 +536,21 @@ fn privatize_and_thaw_with_another_receipt_works() {
|
||||
fn communal_thaw_when_issuance_higher_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1));
|
||||
assert_ok!(Nis::place_bid(signed(1), 100, 1));
|
||||
enlarge(100, 1);
|
||||
assert_eq!(Balances::total_balance(&1), 101);
|
||||
|
||||
assert_ok!(Nis::communify(signed(1), 0));
|
||||
assert_eq!(Balances::total_balance_on_hold(&1), 0);
|
||||
assert_eq!(Balances::total_balance(&1), 1);
|
||||
|
||||
assert_eq!(NisBalances::free_balance(1), 5_250_000); // (25% of 21m)
|
||||
assert_eq!(NisBalances::free_balance(1), 5_250_000); // (12.5% of 21m)
|
||||
|
||||
// Everybody else's balances goes up by 50%
|
||||
Balances::make_free_balance_be(&2, 150);
|
||||
Balances::make_free_balance_be(&3, 150);
|
||||
Balances::make_free_balance_be(&4, 150);
|
||||
assert_ok!(Balances::mint_into(&2, 50));
|
||||
assert_ok!(Balances::mint_into(&3, 50));
|
||||
assert_ok!(Balances::mint_into(&4, 50));
|
||||
|
||||
run_to_block(4);
|
||||
|
||||
@@ -556,16 +560,20 @@ fn communal_thaw_when_issuance_higher_works() {
|
||||
assert_ok!(Nis::fund_deficit(signed(1)));
|
||||
|
||||
// Transfer counterparts away...
|
||||
assert_ok!(NisBalances::transfer(signed(1), 2, 250_000));
|
||||
assert_ok!(NisBalances::transfer_allow_death(signed(1), 2, 125_000));
|
||||
// ...and it's not thawable.
|
||||
assert_noop!(Nis::thaw_communal(signed(1), 0), TokenError::NoFunds);
|
||||
assert_noop!(Nis::thaw_communal(signed(1), 0), TokenError::FundsUnavailable);
|
||||
|
||||
// Transfer counterparts back...
|
||||
assert_ok!(NisBalances::transfer(signed(2), 1, 250_000));
|
||||
assert_ok!(NisBalances::transfer_allow_death(signed(2), 1, 125_000));
|
||||
// ...and it is.
|
||||
assert_ok!(Nis::thaw_communal(signed(1), 0));
|
||||
assert_eq!(Balances::total_balance(&1), 151);
|
||||
|
||||
assert_ok!(Balances::transfer_allow_death(signed(1), 2, 1));
|
||||
assert_eq!(Balances::total_balance(&1), 150);
|
||||
assert_eq!(Balances::free_balance(1), 150);
|
||||
assert_eq!(Balances::total_balance_on_hold(&1), 0);
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
});
|
||||
}
|
||||
@@ -574,13 +582,14 @@ fn communal_thaw_when_issuance_higher_works() {
|
||||
fn private_thaw_when_issuance_higher_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1));
|
||||
assert_ok!(Nis::place_bid(signed(1), 100, 1));
|
||||
enlarge(100, 1);
|
||||
|
||||
// Everybody else's balances goes up by 50%
|
||||
Balances::make_free_balance_be(&2, 150);
|
||||
Balances::make_free_balance_be(&3, 150);
|
||||
Balances::make_free_balance_be(&4, 150);
|
||||
assert_ok!(Balances::mint_into(&2, 50));
|
||||
assert_ok!(Balances::mint_into(&3, 50));
|
||||
assert_ok!(Balances::mint_into(&4, 50));
|
||||
|
||||
run_to_block(4);
|
||||
|
||||
@@ -591,6 +600,7 @@ fn private_thaw_when_issuance_higher_works() {
|
||||
|
||||
assert_ok!(Nis::thaw_private(signed(1), 0, None));
|
||||
|
||||
assert_ok!(Balances::transfer_allow_death(signed(1), 2, 1));
|
||||
assert_eq!(Balances::free_balance(1), 150);
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
});
|
||||
@@ -601,15 +611,16 @@ fn thaw_with_ignored_issuance_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
// Give account zero some balance.
|
||||
Balances::make_free_balance_be(&0, 200);
|
||||
assert_ok!(Balances::mint_into(&0, 200));
|
||||
|
||||
assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1));
|
||||
assert_ok!(Nis::place_bid(signed(1), 100, 1));
|
||||
enlarge(100, 1);
|
||||
|
||||
// Account zero transfers 50 into everyone else's accounts.
|
||||
assert_ok!(Balances::transfer(signed(0), 2, 50));
|
||||
assert_ok!(Balances::transfer(signed(0), 3, 50));
|
||||
assert_ok!(Balances::transfer(signed(0), 4, 50));
|
||||
assert_ok!(Balances::transfer_allow_death(signed(0), 2, 50));
|
||||
assert_ok!(Balances::transfer_allow_death(signed(0), 3, 50));
|
||||
assert_ok!(Balances::transfer_allow_death(signed(0), 4, 50));
|
||||
|
||||
run_to_block(4);
|
||||
// Unfunded initially...
|
||||
@@ -620,6 +631,7 @@ fn thaw_with_ignored_issuance_works() {
|
||||
assert_ok!(Nis::thaw_private(signed(1), 0, None));
|
||||
|
||||
// Account zero changes have been ignored.
|
||||
assert_ok!(Balances::transfer_allow_death(signed(1), 2, 1));
|
||||
assert_eq!(Balances::free_balance(1), 150);
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
});
|
||||
@@ -629,17 +641,19 @@ fn thaw_with_ignored_issuance_works() {
|
||||
fn thaw_when_issuance_lower_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1));
|
||||
assert_ok!(Nis::place_bid(signed(1), 100, 1));
|
||||
enlarge(100, 1);
|
||||
|
||||
// Everybody else's balances goes down by 25%
|
||||
Balances::make_free_balance_be(&2, 75);
|
||||
Balances::make_free_balance_be(&3, 75);
|
||||
Balances::make_free_balance_be(&4, 75);
|
||||
assert_ok!(Balances::burn_from(&2, 25, Exact, Force));
|
||||
assert_ok!(Balances::burn_from(&3, 25, Exact, Force));
|
||||
assert_ok!(Balances::burn_from(&4, 25, Exact, Force));
|
||||
|
||||
run_to_block(4);
|
||||
assert_ok!(Nis::thaw_private(signed(1), 0, None));
|
||||
|
||||
assert_ok!(Balances::transfer_allow_death(signed(1), 2, 1));
|
||||
assert_eq!(Balances::free_balance(1), 75);
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
});
|
||||
@@ -649,15 +663,16 @@ fn thaw_when_issuance_lower_works() {
|
||||
fn multiple_thaws_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
assert_ok!(Balances::transfer_allow_death(signed(3), 1, 1));
|
||||
assert_ok!(Nis::place_bid(signed(1), 40, 1));
|
||||
assert_ok!(Nis::place_bid(signed(1), 60, 1));
|
||||
assert_ok!(Nis::place_bid(signed(2), 50, 1));
|
||||
enlarge(200, 3);
|
||||
|
||||
// Double everyone's free balances.
|
||||
Balances::make_free_balance_be(&2, 100);
|
||||
Balances::make_free_balance_be(&3, 200);
|
||||
Balances::make_free_balance_be(&4, 200);
|
||||
assert_ok!(Balances::mint_into(&2, 50));
|
||||
assert_ok!(Balances::mint_into(&3, 100));
|
||||
assert_ok!(Balances::mint_into(&4, 100));
|
||||
assert_ok!(Nis::fund_deficit(signed(1)));
|
||||
|
||||
run_to_block(4);
|
||||
@@ -667,8 +682,11 @@ fn multiple_thaws_works() {
|
||||
run_to_block(5);
|
||||
assert_ok!(Nis::thaw_private(signed(2), 2, None));
|
||||
|
||||
assert_ok!(Balances::transfer_allow_death(signed(1), 3, 1));
|
||||
assert_eq!(Balances::free_balance(1), 200);
|
||||
assert_eq!(Balances::free_balance(2), 200);
|
||||
assert_eq!(Balances::total_balance(&1), 200);
|
||||
assert_eq!(Balances::total_balance(&2), 200);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -676,15 +694,16 @@ fn multiple_thaws_works() {
|
||||
fn multiple_thaws_works_in_alternative_thaw_order() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
assert_ok!(Balances::transfer_allow_death(signed(3), 1, 1));
|
||||
assert_ok!(Nis::place_bid(signed(1), 40, 1));
|
||||
assert_ok!(Nis::place_bid(signed(1), 60, 1));
|
||||
assert_ok!(Nis::place_bid(signed(2), 50, 1));
|
||||
enlarge(200, 3);
|
||||
|
||||
// Double everyone's free balances.
|
||||
Balances::make_free_balance_be(&2, 100);
|
||||
Balances::make_free_balance_be(&3, 200);
|
||||
Balances::make_free_balance_be(&4, 200);
|
||||
assert_ok!(Balances::mint_into(&2, 50));
|
||||
assert_ok!(Balances::mint_into(&3, 100));
|
||||
assert_ok!(Balances::mint_into(&4, 100));
|
||||
assert_ok!(Nis::fund_deficit(signed(1)));
|
||||
|
||||
run_to_block(4);
|
||||
@@ -695,8 +714,11 @@ fn multiple_thaws_works_in_alternative_thaw_order() {
|
||||
run_to_block(5);
|
||||
assert_ok!(Nis::thaw_private(signed(1), 1, None));
|
||||
|
||||
assert_ok!(Balances::transfer_allow_death(signed(1), 3, 1));
|
||||
assert_eq!(Balances::free_balance(1), 200);
|
||||
assert_eq!(Balances::free_balance(2), 200);
|
||||
assert_eq!(Balances::total_balance(&1), 200);
|
||||
assert_eq!(Balances::total_balance(&2), 200);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,10 @@ impl pallet_balances::Config for Runtime {
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
pallet_staking_reward_curve::build! {
|
||||
|
||||
@@ -196,6 +196,10 @@ impl pallet_balances::Config for Runtime {
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
pub struct BalanceToU256;
|
||||
|
||||
@@ -39,6 +39,16 @@ macro_rules! member_unbonding_eras {
|
||||
pub const DEFAULT_ROLES: PoolRoles<AccountId> =
|
||||
PoolRoles { depositor: 10, root: Some(900), nominator: Some(901), bouncer: Some(902) };
|
||||
|
||||
fn deposit_rewards(r: u128) {
|
||||
let b = Balances::free_balance(&default_reward_account()).checked_add(r).unwrap();
|
||||
Balances::make_free_balance_be(&default_reward_account(), b);
|
||||
}
|
||||
|
||||
fn remove_rewards(r: u128) {
|
||||
let b = Balances::free_balance(&default_reward_account()).checked_sub(r).unwrap();
|
||||
Balances::make_free_balance_be(&default_reward_account(), b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_setup_works() {
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
@@ -469,6 +479,8 @@ mod sub_pools {
|
||||
}
|
||||
|
||||
mod join {
|
||||
use sp_runtime::TokenError;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -592,7 +604,7 @@ mod join {
|
||||
// Balance needs to be gt Balance::MAX / `MaxPointsToBalance`
|
||||
assert_noop!(
|
||||
Pools::join(RuntimeOrigin::signed(11), 5, 123),
|
||||
pallet_balances::Error::<Runtime>::InsufficientBalance,
|
||||
TokenError::FundsUnavailable,
|
||||
);
|
||||
|
||||
StakingMock::set_bonded_balance(Pools::create_bonded_account(1), max_points_to_balance);
|
||||
@@ -749,7 +761,7 @@ mod claim_payout {
|
||||
|
||||
// and the reward pool has earned 100 in rewards
|
||||
assert_eq!(Balances::free_balance(default_reward_account()), ed);
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 100));
|
||||
deposit_rewards(100);
|
||||
|
||||
let _ = pool_events_since_last_call();
|
||||
|
||||
@@ -796,7 +808,7 @@ mod claim_payout {
|
||||
assert_eq!(Balances::free_balance(&default_reward_account()), ed);
|
||||
|
||||
// Given the reward pool has some new rewards
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50));
|
||||
deposit_rewards(50);
|
||||
|
||||
// When
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
@@ -825,7 +837,7 @@ mod claim_payout {
|
||||
assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25);
|
||||
|
||||
// Given del 50 hasn't claimed and the reward pools has just earned 50
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50));
|
||||
deposit_rewards(50);
|
||||
assert_eq!(Balances::free_balance(&default_reward_account()), ed + 75);
|
||||
|
||||
// When
|
||||
@@ -855,7 +867,7 @@ mod claim_payout {
|
||||
assert_eq!(Balances::free_balance(&default_reward_account()), ed + 20);
|
||||
|
||||
// Given del 40 hasn't claimed and the reward pool has just earned 400
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 400));
|
||||
deposit_rewards(400);
|
||||
assert_eq!(Balances::free_balance(&default_reward_account()), ed + 420);
|
||||
|
||||
// When
|
||||
@@ -874,7 +886,7 @@ mod claim_payout {
|
||||
assert_eq!(Balances::free_balance(&default_reward_account()), ed + 380);
|
||||
|
||||
// Given del 40 + del 50 haven't claimed and the reward pool has earned 20
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 20));
|
||||
deposit_rewards(20);
|
||||
assert_eq!(Balances::free_balance(&default_reward_account()), ed + 400);
|
||||
|
||||
// When
|
||||
@@ -974,7 +986,7 @@ mod claim_payout {
|
||||
);
|
||||
|
||||
// The pool earns 10 points
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 10));
|
||||
deposit_rewards(10);
|
||||
|
||||
assert_ok!(Pools::do_reward_payout(
|
||||
&10,
|
||||
@@ -1010,7 +1022,7 @@ mod claim_payout {
|
||||
assert_eq!(reward_pool, rew(0, 0, 0));
|
||||
|
||||
// Given the pool has earned some rewards for the first time
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 5));
|
||||
deposit_rewards(5);
|
||||
|
||||
// When
|
||||
let payout =
|
||||
@@ -1031,7 +1043,7 @@ mod claim_payout {
|
||||
assert_eq!(member, del(0.5));
|
||||
|
||||
// Given the pool has earned rewards again
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 10));
|
||||
deposit_rewards(10);
|
||||
|
||||
// When
|
||||
let payout =
|
||||
@@ -1090,7 +1102,7 @@ mod claim_payout {
|
||||
assert_eq!(bonded_pool.points, 100);
|
||||
|
||||
// and the reward pool has earned 100 in rewards
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 100));
|
||||
deposit_rewards(100);
|
||||
|
||||
// When
|
||||
let payout =
|
||||
@@ -1135,7 +1147,7 @@ mod claim_payout {
|
||||
assert_eq!(reward_pool, rew(0, 0, 100));
|
||||
|
||||
// Given the reward pool has some new rewards
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50));
|
||||
deposit_rewards(50);
|
||||
|
||||
// When
|
||||
let payout =
|
||||
@@ -1166,7 +1178,7 @@ mod claim_payout {
|
||||
assert_eq!(reward_pool, rew(0, 0, 125));
|
||||
|
||||
// Given del_50 hasn't claimed and the reward pools has just earned 50
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50));
|
||||
deposit_rewards(50);
|
||||
|
||||
// When
|
||||
let payout =
|
||||
@@ -1197,7 +1209,7 @@ mod claim_payout {
|
||||
assert_eq!(reward_pool, rew(0, 0, 180));
|
||||
|
||||
// Given del_40 hasn't claimed and the reward pool has just earned 400
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 400));
|
||||
deposit_rewards(400);
|
||||
|
||||
// When
|
||||
let payout =
|
||||
@@ -1214,7 +1226,7 @@ mod claim_payout {
|
||||
assert_eq!(reward_pool, rew(0, 0, 220));
|
||||
|
||||
// Given del_40 + del_50 haven't claimed and the reward pool has earned 20
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 20));
|
||||
deposit_rewards(20);
|
||||
|
||||
// When
|
||||
let payout =
|
||||
@@ -1252,14 +1264,14 @@ mod claim_payout {
|
||||
fn rewards_distribution_is_fair_basic() {
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
// reward pool by 10.
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 10).unwrap();
|
||||
deposit_rewards(10);
|
||||
|
||||
// 20 joins afterwards.
|
||||
Balances::make_free_balance_be(&20, Balances::minimum_balance() + 10);
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
|
||||
|
||||
// reward by another 20
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 20).unwrap();
|
||||
deposit_rewards(20);
|
||||
|
||||
// 10 should claim 10 + 10, 20 should claim 20 / 2.
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
@@ -1276,7 +1288,7 @@ mod claim_payout {
|
||||
);
|
||||
|
||||
// any upcoming rewards are shared equally.
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 20).unwrap();
|
||||
deposit_rewards(20);
|
||||
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
|
||||
@@ -1296,12 +1308,12 @@ mod claim_payout {
|
||||
// basically checks the case where the amount of rewards is less than the pool shares. for
|
||||
// this, we have to rely on fixed point arithmetic.
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 3).unwrap();
|
||||
deposit_rewards(3);
|
||||
|
||||
Balances::make_free_balance_be(&20, Balances::minimum_balance() + 10);
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
|
||||
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 6).unwrap();
|
||||
deposit_rewards(6);
|
||||
|
||||
// 10 should claim 3, 20 should claim 3 + 3.
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
@@ -1319,7 +1331,7 @@ mod claim_payout {
|
||||
);
|
||||
|
||||
// any upcoming rewards are shared equally.
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 8).unwrap();
|
||||
deposit_rewards(8);
|
||||
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
|
||||
@@ -1333,7 +1345,7 @@ mod claim_payout {
|
||||
);
|
||||
|
||||
// uneven upcoming rewards are shared equally, rounded down.
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 7).unwrap();
|
||||
deposit_rewards(7);
|
||||
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
|
||||
@@ -1353,17 +1365,17 @@ mod claim_payout {
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
let ed = Balances::minimum_balance();
|
||||
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap();
|
||||
deposit_rewards(30);
|
||||
|
||||
Balances::make_free_balance_be(&20, ed + 10);
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
|
||||
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap();
|
||||
deposit_rewards(100);
|
||||
|
||||
Balances::make_free_balance_be(&30, ed + 10);
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1));
|
||||
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap();
|
||||
deposit_rewards(60);
|
||||
|
||||
// 10 should claim 10, 20 should claim nothing.
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
@@ -1384,7 +1396,7 @@ mod claim_payout {
|
||||
);
|
||||
|
||||
// any upcoming rewards are shared equally.
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap();
|
||||
deposit_rewards(30);
|
||||
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
|
||||
@@ -1407,7 +1419,7 @@ mod claim_payout {
|
||||
let ed = Balances::minimum_balance();
|
||||
|
||||
assert_eq!(Pools::api_pending_rewards(10), Some(0));
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap();
|
||||
deposit_rewards(30);
|
||||
assert_eq!(Pools::api_pending_rewards(10), Some(30));
|
||||
assert_eq!(Pools::api_pending_rewards(20), None);
|
||||
|
||||
@@ -1417,7 +1429,7 @@ mod claim_payout {
|
||||
assert_eq!(Pools::api_pending_rewards(10), Some(30));
|
||||
assert_eq!(Pools::api_pending_rewards(20), Some(0));
|
||||
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap();
|
||||
deposit_rewards(100);
|
||||
|
||||
assert_eq!(Pools::api_pending_rewards(10), Some(30 + 50));
|
||||
assert_eq!(Pools::api_pending_rewards(20), Some(50));
|
||||
@@ -1430,7 +1442,7 @@ mod claim_payout {
|
||||
assert_eq!(Pools::api_pending_rewards(20), Some(50));
|
||||
assert_eq!(Pools::api_pending_rewards(30), Some(0));
|
||||
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap();
|
||||
deposit_rewards(60);
|
||||
|
||||
assert_eq!(Pools::api_pending_rewards(10), Some(30 + 50 + 20));
|
||||
assert_eq!(Pools::api_pending_rewards(20), Some(50 + 20));
|
||||
@@ -1464,7 +1476,7 @@ mod claim_payout {
|
||||
Balances::make_free_balance_be(&30, ed + 20);
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1));
|
||||
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 40).unwrap();
|
||||
deposit_rewards(40);
|
||||
|
||||
// everyone claims.
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
@@ -1488,7 +1500,7 @@ mod claim_payout {
|
||||
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(30), BondExtra::FreeBalance(10)));
|
||||
|
||||
// more rewards come in.
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap();
|
||||
deposit_rewards(100);
|
||||
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
|
||||
@@ -1514,7 +1526,7 @@ mod claim_payout {
|
||||
Balances::make_free_balance_be(&20, ed + 20);
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1));
|
||||
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap();
|
||||
deposit_rewards(30);
|
||||
|
||||
// everyone claims.
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
@@ -1535,7 +1547,7 @@ mod claim_payout {
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10));
|
||||
|
||||
// more rewards come in.
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap();
|
||||
deposit_rewards(100);
|
||||
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
|
||||
@@ -1562,7 +1574,7 @@ mod claim_payout {
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1));
|
||||
|
||||
// 10 gets 10, 20 gets 20, 30 gets 10
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 40).unwrap();
|
||||
deposit_rewards(40);
|
||||
|
||||
// some claim.
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
@@ -1581,7 +1593,7 @@ mod claim_payout {
|
||||
);
|
||||
|
||||
// 10 gets 20, 20 gets 40, 30 gets 20
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 80).unwrap();
|
||||
deposit_rewards(80);
|
||||
|
||||
// some claim.
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
@@ -1596,7 +1608,7 @@ mod claim_payout {
|
||||
);
|
||||
|
||||
// 10 gets 20, 20 gets 40, 30 gets 20
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 80).unwrap();
|
||||
deposit_rewards(80);
|
||||
|
||||
// some claim.
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
@@ -1629,7 +1641,7 @@ mod claim_payout {
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1));
|
||||
|
||||
// 10 gets 10, 20 gets 20, 30 gets 10
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap();
|
||||
deposit_rewards(30);
|
||||
|
||||
// some claim.
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
@@ -1645,7 +1657,7 @@ mod claim_payout {
|
||||
);
|
||||
|
||||
// 20 has not claimed yet, more reward comes
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap();
|
||||
deposit_rewards(60);
|
||||
|
||||
// and 20 bonds more -- they should not have more share of this reward.
|
||||
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(20), BondExtra::FreeBalance(10)));
|
||||
@@ -1665,7 +1677,7 @@ mod claim_payout {
|
||||
);
|
||||
|
||||
// but in the next round of rewards, the extra10 they bonded has an impact.
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap();
|
||||
deposit_rewards(60);
|
||||
|
||||
// everyone claim.
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
@@ -1695,7 +1707,7 @@ mod claim_payout {
|
||||
assert_eq!(member_10.last_recorded_reward_counter, 0.into());
|
||||
|
||||
// transfer some reward to pool 1.
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap();
|
||||
deposit_rewards(60);
|
||||
|
||||
// create pool 2
|
||||
Balances::make_free_balance_be(&20, 100);
|
||||
@@ -1779,7 +1791,7 @@ mod claim_payout {
|
||||
}
|
||||
|
||||
// transfer some reward to pool 1.
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap();
|
||||
deposit_rewards(60);
|
||||
|
||||
{
|
||||
join(30, 10);
|
||||
@@ -1849,7 +1861,7 @@ mod claim_payout {
|
||||
|
||||
// 10 bonds extra again with some rewards. This reward should be split equally between
|
||||
// 10 and 20, as they both have equal points now.
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap();
|
||||
deposit_rewards(30);
|
||||
|
||||
{
|
||||
assert_ok!(Pools::bond_extra(
|
||||
@@ -1905,7 +1917,7 @@ mod claim_payout {
|
||||
MaxPoolMembersPerPool::<Runtime>::set(None);
|
||||
|
||||
// pool receives some rewards.
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap();
|
||||
deposit_rewards(30);
|
||||
System::reset_events();
|
||||
|
||||
// 10 cashes it out, and bonds it.
|
||||
@@ -1975,7 +1987,7 @@ mod claim_payout {
|
||||
}
|
||||
|
||||
// some rewards come in.
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap();
|
||||
deposit_rewards(30);
|
||||
|
||||
// and 30 also unbonds half.
|
||||
{
|
||||
@@ -2058,7 +2070,7 @@ mod claim_payout {
|
||||
);
|
||||
|
||||
// some rewards come in.
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += 40).unwrap();
|
||||
deposit_rewards(40);
|
||||
|
||||
// everyone claims
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
@@ -2120,8 +2132,7 @@ mod claim_payout {
|
||||
.build_and_execute(|| {
|
||||
// some rewards come in.
|
||||
assert_eq!(Balances::free_balance(&default_reward_account()), unit);
|
||||
Balances::mutate_account(&default_reward_account(), |f| f.free += unit / 1000)
|
||||
.unwrap();
|
||||
deposit_rewards(unit / 1000);
|
||||
|
||||
// everyone claims
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
@@ -3329,11 +3340,7 @@ mod withdraw_unbonded {
|
||||
);
|
||||
assert_eq!(
|
||||
balances_events_since_last_call(),
|
||||
vec![BEvent::BalanceSet {
|
||||
who: default_bonded_account(),
|
||||
free: 300,
|
||||
reserved: 0
|
||||
}]
|
||||
vec![BEvent::BalanceSet { who: default_bonded_account(), free: 300 }]
|
||||
);
|
||||
|
||||
// When
|
||||
@@ -3447,11 +3454,7 @@ mod withdraw_unbonded {
|
||||
);
|
||||
assert_eq!(
|
||||
balances_events_since_last_call(),
|
||||
vec![BEvent::BalanceSet {
|
||||
who: default_bonded_account(),
|
||||
free: 300,
|
||||
reserved: 0
|
||||
},]
|
||||
vec![BEvent::BalanceSet { who: default_bonded_account(), free: 300 },]
|
||||
);
|
||||
|
||||
CurrentEra::set(StakingMock::bonding_duration());
|
||||
@@ -5078,18 +5081,15 @@ mod reward_counter_precision {
|
||||
let expected_smallest_reward = inflation(50) / 10u128.pow(18);
|
||||
|
||||
// tad bit less. cannot be paid out.
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free +=
|
||||
expected_smallest_reward - 1));
|
||||
deposit_rewards(expected_smallest_reward - 1);
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
assert_eq!(pool_events_since_last_call(), vec![]);
|
||||
// revert it.
|
||||
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -=
|
||||
expected_smallest_reward - 1));
|
||||
remove_rewards(expected_smallest_reward - 1);
|
||||
|
||||
// tad bit more. can be claimed.
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free +=
|
||||
expected_smallest_reward + 1));
|
||||
deposit_rewards(expected_smallest_reward + 1);
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
@@ -5114,9 +5114,7 @@ mod reward_counter_precision {
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), tiny_bond / 2, 1));
|
||||
|
||||
// Suddenly, add a shit ton of rewards.
|
||||
assert_ok!(
|
||||
Balances::mutate_account(&default_reward_account(), |a| a.free += inflation(1))
|
||||
);
|
||||
deposit_rewards(inflation(1));
|
||||
|
||||
// now claim.
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
@@ -5155,8 +5153,7 @@ mod reward_counter_precision {
|
||||
// is earning all of the inflation per year (which is really unrealistic, but worse
|
||||
// case), that will be:
|
||||
let pool_total_earnings_10_years = inflation(10) - POLKADOT_TOTAL_ISSUANCE_GENESIS;
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free +=
|
||||
pool_total_earnings_10_years));
|
||||
deposit_rewards(pool_total_earnings_10_years);
|
||||
|
||||
// some whale now joins with the other half ot the total issuance. This will bloat all
|
||||
// the calculation regarding current reward counter.
|
||||
@@ -5186,7 +5183,7 @@ mod reward_counter_precision {
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10 * DOT, 1));
|
||||
|
||||
// and give a reasonably small reward to the pool.
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += DOT));
|
||||
deposit_rewards(DOT);
|
||||
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30)));
|
||||
assert_eq!(
|
||||
@@ -5258,9 +5255,7 @@ mod reward_counter_precision {
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10 * DOT, 1));
|
||||
|
||||
// earn some small rewards
|
||||
assert_ok!(
|
||||
Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000)
|
||||
);
|
||||
deposit_rewards(DOT / 1000);
|
||||
|
||||
// no point in claiming for 20 (nonetheless, it should be harmless)
|
||||
assert!(pending_rewards(20).unwrap().is_zero());
|
||||
@@ -5280,9 +5275,7 @@ mod reward_counter_precision {
|
||||
|
||||
// earn some small more, still nothing can be claimed for 20, but 10 claims their
|
||||
// share.
|
||||
assert_ok!(
|
||||
Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000)
|
||||
);
|
||||
deposit_rewards(DOT / 1000);
|
||||
assert!(pending_rewards(20).unwrap().is_zero());
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
assert_eq!(
|
||||
@@ -5291,9 +5284,7 @@ mod reward_counter_precision {
|
||||
);
|
||||
|
||||
// earn some more rewards, this time 20 can also claim.
|
||||
assert_ok!(
|
||||
Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000)
|
||||
);
|
||||
deposit_rewards(DOT / 1000);
|
||||
assert_eq!(pending_rewards(20).unwrap(), 1);
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
|
||||
@@ -5332,9 +5323,7 @@ mod reward_counter_precision {
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10 * DOT, 1));
|
||||
|
||||
// earn some small rewards
|
||||
assert_ok!(
|
||||
Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000)
|
||||
);
|
||||
deposit_rewards(DOT / 1000);
|
||||
|
||||
// if 20 claims now, their reward counter should stay the same, so that they have a
|
||||
// chance of claiming this if they let it accumulate. Also see
|
||||
@@ -5442,7 +5431,7 @@ mod commission {
|
||||
// Pool earns 80 points and a payout is triggered.
|
||||
|
||||
// Given:
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 80));
|
||||
deposit_rewards(80);
|
||||
assert_eq!(
|
||||
PoolMembers::<Runtime>::get(10).unwrap(),
|
||||
PoolMember::<Runtime> { pool_id, points: 10, ..Default::default() }
|
||||
@@ -5489,7 +5478,7 @@ mod commission {
|
||||
// is next called, which is not done in this test segment..
|
||||
|
||||
// Given:
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 100));
|
||||
deposit_rewards(100);
|
||||
|
||||
// When:
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
@@ -5625,7 +5614,7 @@ mod commission {
|
||||
1,
|
||||
Some((Perbill::from_percent(10), root)),
|
||||
));
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 40));
|
||||
deposit_rewards(40);
|
||||
|
||||
// When:
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
@@ -6377,7 +6366,7 @@ mod commission {
|
||||
);
|
||||
|
||||
// The pool earns 10 points
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 10));
|
||||
deposit_rewards(10);
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
|
||||
// Then:
|
||||
@@ -6387,7 +6376,7 @@ mod commission {
|
||||
);
|
||||
|
||||
// The pool earns 17 points
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 17));
|
||||
deposit_rewards(17);
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
|
||||
// Then:
|
||||
@@ -6397,7 +6386,7 @@ mod commission {
|
||||
);
|
||||
|
||||
// The pool earns 50 points
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50));
|
||||
deposit_rewards(50);
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
|
||||
// Then:
|
||||
@@ -6407,7 +6396,7 @@ mod commission {
|
||||
);
|
||||
|
||||
// The pool earns 10439 points
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 10439));
|
||||
deposit_rewards(10439);
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
|
||||
// Then:
|
||||
@@ -6427,7 +6416,7 @@ mod commission {
|
||||
));
|
||||
|
||||
// Given:
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 200));
|
||||
deposit_rewards(200);
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
|
||||
// Then:
|
||||
@@ -6469,7 +6458,7 @@ mod commission {
|
||||
// When:
|
||||
|
||||
// The pool earns 100 points
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 100));
|
||||
deposit_rewards(100);
|
||||
|
||||
// Change commission to 20%
|
||||
assert_ok!(Pools::set_commission(
|
||||
@@ -6486,7 +6475,7 @@ mod commission {
|
||||
);
|
||||
|
||||
// The pool earns 100 points
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 100));
|
||||
deposit_rewards(100);
|
||||
|
||||
// Then:
|
||||
|
||||
@@ -6535,7 +6524,7 @@ mod commission {
|
||||
// When:
|
||||
|
||||
// The pool earns 100 points
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 100));
|
||||
deposit_rewards(100);
|
||||
|
||||
// Claim payout:
|
||||
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
|
||||
@@ -6592,7 +6581,7 @@ mod commission {
|
||||
);
|
||||
|
||||
// The pool earns 10 points
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 10));
|
||||
deposit_rewards(10);
|
||||
|
||||
// execute the payout
|
||||
assert_ok!(Pools::do_reward_payout(
|
||||
@@ -6634,7 +6623,7 @@ mod commission {
|
||||
);
|
||||
|
||||
// The pool earns 10 points
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 10));
|
||||
deposit_rewards(10);
|
||||
|
||||
// execute the payout
|
||||
assert_ok!(Pools::do_reward_payout(
|
||||
@@ -6677,7 +6666,7 @@ mod commission {
|
||||
);
|
||||
|
||||
// Pool earns 80 points, payout is triggered.
|
||||
assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 80));
|
||||
deposit_rewards(80);
|
||||
assert_eq!(
|
||||
PoolMembers::<Runtime>::get(10).unwrap(),
|
||||
PoolMember::<Runtime> { pool_id, points: 10, ..Default::default() }
|
||||
|
||||
@@ -86,6 +86,10 @@ impl pallet_balances::Config for Runtime {
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
pallet_staking_reward_curve::build! {
|
||||
|
||||
@@ -74,6 +74,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU64<10>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Config for Test {
|
||||
|
||||
@@ -84,6 +84,10 @@ impl pallet_balances::Config for Test {
|
||||
type MaxLocks = ();
|
||||
type MaxReserves = ConstU32<50>;
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
ord_parameter_types! {
|
||||
|
||||
@@ -88,6 +88,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
impl pallet_utility::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
@@ -124,7 +128,10 @@ impl InstanceFilter<RuntimeCall> for ProxyType {
|
||||
match self {
|
||||
ProxyType::Any => true,
|
||||
ProxyType::JustTransfer => {
|
||||
matches!(c, RuntimeCall::Balances(pallet_balances::Call::transfer { .. }))
|
||||
matches!(
|
||||
c,
|
||||
RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { .. })
|
||||
)
|
||||
},
|
||||
ProxyType::JustUtility => matches!(c, RuntimeCall::Utility { .. }),
|
||||
}
|
||||
@@ -161,7 +168,7 @@ impl Config for Test {
|
||||
|
||||
use super::{Call as ProxyCall, Event as ProxyEvent};
|
||||
use frame_system::Call as SystemCall;
|
||||
use pallet_balances::{Call as BalancesCall, Error as BalancesError, Event as BalancesEvent};
|
||||
use pallet_balances::{Call as BalancesCall, Event as BalancesEvent};
|
||||
use pallet_utility::{Call as UtilityCall, Event as UtilityEvent};
|
||||
|
||||
type SystemError = frame_system::Error<Test>;
|
||||
@@ -169,7 +176,7 @@ type SystemError = frame_system::Error<Test>;
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
pallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 2)],
|
||||
balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 3)],
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
@@ -193,7 +200,7 @@ fn expect_events(e: Vec<RuntimeEvent>) {
|
||||
}
|
||||
|
||||
fn call_transfer(dest: u64, value: u64) -> RuntimeCall {
|
||||
RuntimeCall::Balances(BalancesCall::transfer { dest, value })
|
||||
RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest, value })
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -345,7 +352,7 @@ fn proxy_announced_removes_announcement_and_returns_deposit() {
|
||||
#[test]
|
||||
fn filtering_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert!(Balances::mutate_account(&1, |a| a.free = 1000).is_ok());
|
||||
Balances::make_free_balance_be(&1, 1000);
|
||||
assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 2, ProxyType::Any, 0));
|
||||
assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 3, ProxyType::JustTransfer, 0));
|
||||
assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 4, ProxyType::JustUtility, 0));
|
||||
@@ -361,7 +368,7 @@ fn filtering_works() {
|
||||
);
|
||||
|
||||
let derivative_id = Utility::derivative_account_id(1, 0);
|
||||
assert!(Balances::mutate_account(&derivative_id, |a| a.free = 1000).is_ok());
|
||||
Balances::make_free_balance_be(&derivative_id, 1000);
|
||||
let inner = Box::new(call_transfer(6, 1));
|
||||
|
||||
let call = Box::new(RuntimeCall::Utility(UtilityCall::as_derivative {
|
||||
@@ -516,7 +523,7 @@ fn cannot_add_proxy_without_balance() {
|
||||
assert_eq!(Balances::reserved_balance(5), 2);
|
||||
assert_noop!(
|
||||
Proxy::add_proxy(RuntimeOrigin::signed(5), 4, ProxyType::Any, 0),
|
||||
BalancesError::<Test, _>::InsufficientBalance
|
||||
DispatchError::ConsumerRemaining,
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -564,6 +571,7 @@ fn proxying_works() {
|
||||
#[test]
|
||||
fn pure_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Balances::make_free_balance_be(&1, 11); // An extra one for the ED.
|
||||
assert_ok!(Proxy::create_pure(RuntimeOrigin::signed(1), ProxyType::Any, 0, 0));
|
||||
let anon = Proxy::pure_account(&1, &ProxyType::Any, 0, None);
|
||||
System::assert_last_event(
|
||||
@@ -592,7 +600,7 @@ fn pure_works() {
|
||||
assert_ok!(Proxy::create_pure(RuntimeOrigin::signed(1), ProxyType::Any, 0, 0));
|
||||
|
||||
let call = Box::new(call_transfer(6, 1));
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), anon, 5));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), anon, 5));
|
||||
assert_ok!(Proxy::proxy(RuntimeOrigin::signed(1), anon, None, call));
|
||||
System::assert_last_event(ProxyEvent::ProxyExecuted { result: Ok(()) }.into());
|
||||
assert_eq!(Balances::free_balance(6), 1);
|
||||
@@ -611,9 +619,9 @@ fn pure_works() {
|
||||
Proxy::kill_pure(RuntimeOrigin::signed(1), 1, ProxyType::Any, 0, 1, 0),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_eq!(Balances::free_balance(1), 0);
|
||||
assert_eq!(Balances::free_balance(1), 1);
|
||||
assert_ok!(Proxy::proxy(RuntimeOrigin::signed(1), anon, None, call.clone()));
|
||||
assert_eq!(Balances::free_balance(1), 2);
|
||||
assert_eq!(Balances::free_balance(1), 3);
|
||||
assert_noop!(
|
||||
Proxy::proxy(RuntimeOrigin::signed(1), anon, None, call.clone()),
|
||||
Error::<Test>::NotProxy
|
||||
|
||||
@@ -86,6 +86,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -45,7 +45,10 @@ fn set_recovered_works() {
|
||||
// Root can set a recovered account though
|
||||
assert_ok!(Recovery::set_recovered(RuntimeOrigin::root(), 5, 1));
|
||||
// Account 1 should now be able to make a call through account 5
|
||||
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 1, value: 100 }));
|
||||
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death {
|
||||
dest: 1,
|
||||
value: 100,
|
||||
}));
|
||||
assert_ok!(Recovery::as_recovered(RuntimeOrigin::signed(1), 5, call));
|
||||
// Account 1 has successfully drained the funds from account 5
|
||||
assert_eq!(Balances::free_balance(1), 200);
|
||||
@@ -93,7 +96,10 @@ fn recovery_life_cycle_works() {
|
||||
assert_ok!(Recovery::as_recovered(RuntimeOrigin::signed(1), 5, call));
|
||||
// Account 1 should now be able to make a call through account 5 to get all of their funds
|
||||
assert_eq!(Balances::free_balance(5), 110);
|
||||
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 1, value: 110 }));
|
||||
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death {
|
||||
dest: 1,
|
||||
value: 110,
|
||||
}));
|
||||
assert_ok!(Recovery::as_recovered(RuntimeOrigin::signed(1), 5, call));
|
||||
// All funds have been fully recovered!
|
||||
assert_eq!(Balances::free_balance(1), 200);
|
||||
|
||||
@@ -191,7 +191,7 @@ pub mod test {
|
||||
#[test]
|
||||
pub fn referendum_status_v0() {
|
||||
// make sure the bytes of the encoded referendum v0 is decodable.
|
||||
let ongoing_encoded = sp_core::Bytes::from_str("0x00000000013001012a000000000000000400000100000000000000010000000000000001000000000000000a00000000000000000000000000000000000100").unwrap();
|
||||
let ongoing_encoded = sp_core::Bytes::from_str("0x00000000012c01082a0000000000000004000100000000000000010000000000000001000000000000000a00000000000000000000000000000000000100").unwrap();
|
||||
let ongoing_dec = v0::ReferendumInfoOf::<T, ()>::decode(&mut &*ongoing_encoded).unwrap();
|
||||
let ongoing = v0::ReferendumInfoOf::<T, ()>::Ongoing(create_status_v0());
|
||||
assert_eq!(ongoing, ongoing_dec);
|
||||
|
||||
@@ -57,7 +57,7 @@ frame_support::construct_runtime!(
|
||||
pub struct BaseFilter;
|
||||
impl Contains<RuntimeCall> for BaseFilter {
|
||||
fn contains(call: &RuntimeCall) -> bool {
|
||||
!matches!(call, &RuntimeCall::Balances(pallet_balances::Call::set_balance { .. }))
|
||||
!matches!(call, &RuntimeCall::Balances(pallet_balances::Call::force_set_balance { .. }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub static AlarmInterval: u64 = 1;
|
||||
@@ -295,19 +299,14 @@ impl<Class> VoteTally<u32, Class> for Tally {
|
||||
}
|
||||
|
||||
pub fn set_balance_proposal(value: u64) -> Vec<u8> {
|
||||
RuntimeCall::Balances(pallet_balances::Call::set_balance {
|
||||
who: 42,
|
||||
new_free: value,
|
||||
new_reserved: 0,
|
||||
})
|
||||
.encode()
|
||||
RuntimeCall::Balances(pallet_balances::Call::force_set_balance { who: 42, new_free: value })
|
||||
.encode()
|
||||
}
|
||||
|
||||
pub fn set_balance_proposal_bounded(value: u64) -> BoundedCallOf<Test, ()> {
|
||||
let c = RuntimeCall::Balances(pallet_balances::Call::set_balance {
|
||||
let c = RuntimeCall::Balances(pallet_balances::Call::force_set_balance {
|
||||
who: 42,
|
||||
new_free: value,
|
||||
new_reserved: 0,
|
||||
});
|
||||
<Preimage as StorePreimage>::bound(c).unwrap()
|
||||
}
|
||||
|
||||
@@ -121,6 +121,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
pallet_staking_reward_curve::build! {
|
||||
|
||||
@@ -91,6 +91,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -84,6 +84,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU64<10>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Config for Test {
|
||||
|
||||
@@ -93,6 +93,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
|
||||
@@ -157,6 +157,10 @@ impl pallet_balances::Config for Test {
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
sp_runtime::impl_opaque_keys! {
|
||||
|
||||
@@ -30,7 +30,7 @@ use pallet_balances::Error as BalancesError;
|
||||
use sp_runtime::{
|
||||
assert_eq_error_rate,
|
||||
traits::{BadOrigin, Dispatchable},
|
||||
Perbill, Percent, Rounding,
|
||||
Perbill, Percent, Rounding, TokenError,
|
||||
};
|
||||
use sp_staking::{
|
||||
offence::{DisableStrategy, OffenceDetails, OnOffenceHandler},
|
||||
@@ -98,8 +98,8 @@ fn force_unstake_works() {
|
||||
add_slash(&11);
|
||||
// Cant transfer
|
||||
assert_noop!(
|
||||
Balances::transfer(RuntimeOrigin::signed(11), 1, 10),
|
||||
BalancesError::<Test, _>::LiquidityRestrictions
|
||||
Balances::transfer_allow_death(RuntimeOrigin::signed(11), 1, 10),
|
||||
TokenError::Frozen,
|
||||
);
|
||||
// Force unstake requires root.
|
||||
assert_noop!(Staking::force_unstake(RuntimeOrigin::signed(11), 11, 2), BadOrigin);
|
||||
@@ -113,7 +113,7 @@ fn force_unstake_works() {
|
||||
// No longer bonded.
|
||||
assert_eq!(Staking::bonded(&11), None);
|
||||
// Transfer works.
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(11), 1, 10));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(11), 1, 10));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -960,14 +960,14 @@ fn cannot_transfer_staked_balance() {
|
||||
assert_eq!(Staking::eras_stakers(active_era(), 11).total, 1000);
|
||||
// Confirm account 11 cannot transfer as a result
|
||||
assert_noop!(
|
||||
Balances::transfer(RuntimeOrigin::signed(11), 20, 1),
|
||||
BalancesError::<Test, _>::LiquidityRestrictions
|
||||
Balances::transfer_allow_death(RuntimeOrigin::signed(11), 20, 1),
|
||||
TokenError::Frozen,
|
||||
);
|
||||
|
||||
// Give account 11 extra free balance
|
||||
let _ = Balances::make_free_balance_be(&11, 10000);
|
||||
// Confirm that account 11 can now transfer some balance
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(11), 20, 1));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(11), 20, 1));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -985,10 +985,10 @@ fn cannot_transfer_staked_balance_2() {
|
||||
assert_eq!(Staking::eras_stakers(active_era(), 21).total, 1000);
|
||||
// Confirm account 21 can transfer at most 1000
|
||||
assert_noop!(
|
||||
Balances::transfer(RuntimeOrigin::signed(21), 20, 1001),
|
||||
BalancesError::<Test, _>::LiquidityRestrictions
|
||||
Balances::transfer_allow_death(RuntimeOrigin::signed(21), 20, 1001),
|
||||
TokenError::Frozen,
|
||||
);
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(21), 20, 1000));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(21), 20, 1000));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4187,7 +4187,7 @@ fn payout_creates_controller() {
|
||||
bond_nominator(1234, 1337, 100, vec![11]);
|
||||
|
||||
// kill controller
|
||||
assert_ok!(Balances::transfer(RuntimeOrigin::signed(1337), 1234, 100));
|
||||
assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1337), 1234, 100));
|
||||
assert_eq!(Balances::free_balance(1337), 0);
|
||||
|
||||
mock::start_active_era(1);
|
||||
|
||||
@@ -1128,6 +1128,10 @@ mod mock {
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type HoldIdentifier = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
/// Test only Weights for state migration.
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
//! Traits and associated datatypes for managing abstract stored values.
|
||||
|
||||
use crate::{storage::StorageMap, traits::misc::HandleLifetime};
|
||||
use crate::storage::StorageMap;
|
||||
use codec::FullCodec;
|
||||
use sp_runtime::DispatchError;
|
||||
|
||||
@@ -81,48 +81,29 @@ pub trait StoredMap<K, T: Default> {
|
||||
/// be the default value), or where the account is being removed or reset back to the default value
|
||||
/// where previously it did exist (though may have been in a default state). This works well with
|
||||
/// system module's `CallOnCreatedAccount` and `CallKillAccount`.
|
||||
pub struct StorageMapShim<S, L, K, T>(sp_std::marker::PhantomData<(S, L, K, T)>);
|
||||
impl<
|
||||
S: StorageMap<K, T, Query = T>,
|
||||
L: HandleLifetime<K>,
|
||||
K: FullCodec,
|
||||
T: FullCodec + Default,
|
||||
> StoredMap<K, T> for StorageMapShim<S, L, K, T>
|
||||
pub struct StorageMapShim<S, K, T>(sp_std::marker::PhantomData<(S, K, T)>);
|
||||
impl<S: StorageMap<K, T, Query = T>, K: FullCodec, T: FullCodec + Default> StoredMap<K, T>
|
||||
for StorageMapShim<S, K, T>
|
||||
{
|
||||
fn get(k: &K) -> T {
|
||||
S::get(k)
|
||||
}
|
||||
fn insert(k: &K, t: T) -> Result<(), DispatchError> {
|
||||
if !S::contains_key(&k) {
|
||||
L::created(k)?;
|
||||
}
|
||||
S::insert(k, t);
|
||||
Ok(())
|
||||
}
|
||||
fn remove(k: &K) -> Result<(), DispatchError> {
|
||||
if S::contains_key(&k) {
|
||||
L::killed(k)?;
|
||||
S::remove(k);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn mutate<R>(k: &K, f: impl FnOnce(&mut T) -> R) -> Result<R, DispatchError> {
|
||||
if !S::contains_key(&k) {
|
||||
L::created(k)?;
|
||||
}
|
||||
Ok(S::mutate(k, f))
|
||||
}
|
||||
fn mutate_exists<R>(k: &K, f: impl FnOnce(&mut Option<T>) -> R) -> Result<R, DispatchError> {
|
||||
S::try_mutate_exists(k, |maybe_value| {
|
||||
let existed = maybe_value.is_some();
|
||||
let r = f(maybe_value);
|
||||
let exists = maybe_value.is_some();
|
||||
|
||||
if !existed && exists {
|
||||
L::created(k)?;
|
||||
} else if existed && !exists {
|
||||
L::killed(k)?;
|
||||
}
|
||||
Ok(r)
|
||||
})
|
||||
}
|
||||
@@ -131,15 +112,7 @@ impl<
|
||||
f: impl FnOnce(&mut Option<T>) -> Result<R, E>,
|
||||
) -> Result<R, E> {
|
||||
S::try_mutate_exists(k, |maybe_value| {
|
||||
let existed = maybe_value.is_some();
|
||||
let r = f(maybe_value)?;
|
||||
let exists = maybe_value.is_some();
|
||||
|
||||
if !existed && exists {
|
||||
L::created(k).map_err(E::from)?;
|
||||
} else if existed && !exists {
|
||||
L::killed(k).map_err(E::from)?;
|
||||
}
|
||||
Ok(r)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ pub use imbalance::Imbalance;
|
||||
pub mod pay;
|
||||
pub use misc::{
|
||||
AssetId, Balance, BalanceConversion, BalanceStatus, ConvertRank, DepositConsequence,
|
||||
ExistenceRequirement, GetSalary, Locker, WithdrawConsequence, WithdrawReasons,
|
||||
ExistenceRequirement, Fortitude, GetSalary, Locker, Precision, Preservation, Provenance,
|
||||
Restriction, WithdrawConsequence, WithdrawReasons,
|
||||
};
|
||||
pub use pay::{Pay, PayFromAccount, PaymentStatus};
|
||||
|
||||
@@ -1,367 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The traits for dealing with a single fungible token class and any associated types.
|
||||
|
||||
use super::{
|
||||
misc::{Balance, DepositConsequence, WithdrawConsequence},
|
||||
*,
|
||||
};
|
||||
use crate::{
|
||||
dispatch::{DispatchError, DispatchResult},
|
||||
traits::misc::Get,
|
||||
};
|
||||
use sp_runtime::traits::Saturating;
|
||||
|
||||
mod balanced;
|
||||
mod imbalance;
|
||||
pub use balanced::{Balanced, Unbalanced};
|
||||
pub use imbalance::{CreditOf, DebtOf, HandleImbalanceDrop, Imbalance};
|
||||
|
||||
/// Trait for providing balance-inspection access to a fungible asset.
|
||||
pub trait Inspect<AccountId> {
|
||||
/// Scalar type for representing balance of an account.
|
||||
type Balance: Balance;
|
||||
|
||||
/// The total amount of issuance in the system.
|
||||
fn total_issuance() -> Self::Balance;
|
||||
|
||||
/// The total amount of issuance in the system excluding those which are controlled by the
|
||||
/// system.
|
||||
fn active_issuance() -> Self::Balance {
|
||||
Self::total_issuance()
|
||||
}
|
||||
|
||||
/// The minimum balance any single account may have.
|
||||
fn minimum_balance() -> Self::Balance;
|
||||
|
||||
/// Get the balance of `who`.
|
||||
fn balance(who: &AccountId) -> Self::Balance;
|
||||
|
||||
/// Get the maximum amount that `who` can withdraw/transfer successfully.
|
||||
fn reducible_balance(who: &AccountId, keep_alive: bool) -> Self::Balance;
|
||||
|
||||
/// Returns `true` if the balance of `who` may be increased by `amount`.
|
||||
///
|
||||
/// - `who`: The account of which the balance should be increased by `amount`.
|
||||
/// - `amount`: How much should the balance be increased?
|
||||
/// - `mint`: Will `amount` be minted to deposit it into `account`?
|
||||
fn can_deposit(who: &AccountId, amount: Self::Balance, mint: bool) -> DepositConsequence;
|
||||
|
||||
/// Returns `Failed` if the balance of `who` may not be decreased by `amount`, otherwise
|
||||
/// the consequence.
|
||||
fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence<Self::Balance>;
|
||||
}
|
||||
|
||||
/// Trait for providing an ERC-20 style fungible asset.
|
||||
pub trait Mutate<AccountId>: Inspect<AccountId> {
|
||||
/// Increase the balance of `who` by exactly `amount`, minting new tokens. If that isn't
|
||||
/// possible then an `Err` is returned and nothing is changed.
|
||||
fn mint_into(who: &AccountId, amount: Self::Balance) -> DispatchResult;
|
||||
|
||||
/// Decrease the balance of `who` by at least `amount`, possibly slightly more in the case of
|
||||
/// minimum_balance requirements, burning the tokens. If that isn't possible then an `Err` is
|
||||
/// returned and nothing is changed. If successful, the amount of tokens reduced is returned.
|
||||
fn burn_from(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError>;
|
||||
|
||||
// TODO: Remove.
|
||||
/// Attempt to reduce the balance of `who` by as much as possible up to `amount`, and possibly
|
||||
/// slightly more due to minimum_balance requirements. If no decrease is possible then an `Err`
|
||||
/// is returned and nothing is changed. If successful, the amount of tokens reduced is returned.
|
||||
///
|
||||
/// The default implementation just uses `withdraw` along with `reducible_balance` to ensure
|
||||
/// that it doesn't fail.
|
||||
fn slash(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
|
||||
Self::burn_from(who, Self::reducible_balance(who, false).min(amount))
|
||||
}
|
||||
|
||||
/// Transfer funds from one account into another. The default implementation uses `mint_into`
|
||||
/// and `burn_from` and may generate unwanted events.
|
||||
fn teleport(
|
||||
source: &AccountId,
|
||||
dest: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let extra = Self::can_withdraw(&source, amount).into_result()?;
|
||||
// As we first burn and then mint, we don't need to check if `mint` fits into the supply.
|
||||
// If we can withdraw/burn it, we can also mint it again.
|
||||
Self::can_deposit(dest, amount.saturating_add(extra), false).into_result()?;
|
||||
let actual = Self::burn_from(source, amount)?;
|
||||
debug_assert!(
|
||||
actual == amount.saturating_add(extra),
|
||||
"can_withdraw must agree with withdraw; qed"
|
||||
);
|
||||
match Self::mint_into(dest, actual) {
|
||||
Ok(_) => Ok(actual),
|
||||
Err(err) => {
|
||||
debug_assert!(false, "can_deposit returned true previously; qed");
|
||||
// attempt to return the funds back to source
|
||||
let revert = Self::mint_into(source, actual);
|
||||
debug_assert!(revert.is_ok(), "withdrew funds previously; qed");
|
||||
Err(err)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for providing a fungible asset which can only be transferred.
|
||||
pub trait Transfer<AccountId>: Inspect<AccountId> {
|
||||
/// Transfer funds from one account into another.
|
||||
fn transfer(
|
||||
source: &AccountId,
|
||||
dest: &AccountId,
|
||||
amount: Self::Balance,
|
||||
keep_alive: bool,
|
||||
) -> Result<Self::Balance, DispatchError>;
|
||||
|
||||
/// Reduce the active issuance by some amount.
|
||||
fn deactivate(_: Self::Balance) {}
|
||||
|
||||
/// Increase the active issuance by some amount, up to the outstanding amount reduced.
|
||||
fn reactivate(_: Self::Balance) {}
|
||||
}
|
||||
|
||||
/// Trait for inspecting a fungible asset which can be reserved.
|
||||
pub trait InspectHold<AccountId>: Inspect<AccountId> {
|
||||
/// Amount of funds held in reserve by `who`.
|
||||
fn balance_on_hold(who: &AccountId) -> Self::Balance;
|
||||
|
||||
/// Check to see if some `amount` of funds of `who` may be placed on hold.
|
||||
fn can_hold(who: &AccountId, amount: Self::Balance) -> bool;
|
||||
}
|
||||
|
||||
// TODO: Introduce `HoldReason`.
|
||||
/// Trait for mutating a fungible asset which can be reserved.
|
||||
pub trait MutateHold<AccountId>: InspectHold<AccountId> + Transfer<AccountId> {
|
||||
/// Hold some funds in an account.
|
||||
fn hold(who: &AccountId, amount: Self::Balance) -> DispatchResult;
|
||||
|
||||
/// Release up to `amount` held funds in an account.
|
||||
///
|
||||
/// The actual amount released is returned with `Ok`.
|
||||
///
|
||||
/// If `best_effort` is `true`, then the amount actually unreserved and returned as the inner
|
||||
/// value of `Ok` may be smaller than the `amount` passed.
|
||||
fn release(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
best_effort: bool,
|
||||
) -> Result<Self::Balance, DispatchError>;
|
||||
|
||||
// TODO: Introduce repatriate_held
|
||||
|
||||
/// Transfer held funds into a destination account.
|
||||
///
|
||||
/// If `on_hold` is `true`, then the destination account must already exist and the assets
|
||||
/// transferred will still be on hold in the destination account. If not, then the destination
|
||||
/// account need not already exist, but must be creatable.
|
||||
///
|
||||
/// If `best_effort` is `true`, then an amount less than `amount` may be transferred without
|
||||
/// error.
|
||||
///
|
||||
/// The actual amount transferred is returned, or `Err` in the case of error and nothing is
|
||||
/// changed.
|
||||
fn transfer_held(
|
||||
source: &AccountId,
|
||||
dest: &AccountId,
|
||||
amount: Self::Balance,
|
||||
best_effort: bool,
|
||||
on_held: bool,
|
||||
) -> Result<Self::Balance, DispatchError>;
|
||||
}
|
||||
|
||||
/// Trait for slashing a fungible asset which can be reserved.
|
||||
pub trait BalancedHold<AccountId>: Balanced<AccountId> + MutateHold<AccountId> {
|
||||
/// Reduce the balance of some funds on hold in an account.
|
||||
///
|
||||
/// The resulting imbalance is the first item of the tuple returned.
|
||||
///
|
||||
/// As much funds that are on hold up to `amount` will be deducted as possible. If this is less
|
||||
/// than `amount`, then a non-zero second item will be returned.
|
||||
fn slash_held(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> (CreditOf<AccountId, Self>, Self::Balance);
|
||||
}
|
||||
|
||||
impl<AccountId, T: Balanced<AccountId> + MutateHold<AccountId>> BalancedHold<AccountId> for T {
|
||||
// TODO: This should be implemented properly, and `slash` should be removed.
|
||||
fn slash_held(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> (CreditOf<AccountId, Self>, Self::Balance) {
|
||||
let actual = match Self::release(who, amount, true) {
|
||||
Ok(x) => x,
|
||||
Err(_) => return (Imbalance::default(), amount),
|
||||
};
|
||||
<Self as fungible::Balanced<AccountId>>::slash(who, actual)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a `fungibles` trait implementation into a `fungible` trait implementation by identifying
|
||||
/// a single item.
|
||||
pub struct ItemOf<
|
||||
F: fungibles::Inspect<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
>(sp_std::marker::PhantomData<(F, A, AccountId)>);
|
||||
|
||||
impl<
|
||||
F: fungibles::Inspect<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
> Inspect<AccountId> for ItemOf<F, A, AccountId>
|
||||
{
|
||||
type Balance = <F as fungibles::Inspect<AccountId>>::Balance;
|
||||
fn total_issuance() -> Self::Balance {
|
||||
<F as fungibles::Inspect<AccountId>>::total_issuance(A::get())
|
||||
}
|
||||
fn active_issuance() -> Self::Balance {
|
||||
<F as fungibles::Inspect<AccountId>>::active_issuance(A::get())
|
||||
}
|
||||
fn minimum_balance() -> Self::Balance {
|
||||
<F as fungibles::Inspect<AccountId>>::minimum_balance(A::get())
|
||||
}
|
||||
fn balance(who: &AccountId) -> Self::Balance {
|
||||
<F as fungibles::Inspect<AccountId>>::balance(A::get(), who)
|
||||
}
|
||||
fn reducible_balance(who: &AccountId, keep_alive: bool) -> Self::Balance {
|
||||
<F as fungibles::Inspect<AccountId>>::reducible_balance(A::get(), who, keep_alive)
|
||||
}
|
||||
fn can_deposit(who: &AccountId, amount: Self::Balance, mint: bool) -> DepositConsequence {
|
||||
<F as fungibles::Inspect<AccountId>>::can_deposit(A::get(), who, amount, mint)
|
||||
}
|
||||
fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence<Self::Balance> {
|
||||
<F as fungibles::Inspect<AccountId>>::can_withdraw(A::get(), who, amount)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
F: fungibles::Mutate<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
> Mutate<AccountId> for ItemOf<F, A, AccountId>
|
||||
{
|
||||
fn mint_into(who: &AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
<F as fungibles::Mutate<AccountId>>::mint_into(A::get(), who, amount)
|
||||
}
|
||||
fn burn_from(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::Mutate<AccountId>>::burn_from(A::get(), who, amount)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
F: fungibles::Transfer<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
> Transfer<AccountId> for ItemOf<F, A, AccountId>
|
||||
{
|
||||
fn transfer(
|
||||
source: &AccountId,
|
||||
dest: &AccountId,
|
||||
amount: Self::Balance,
|
||||
keep_alive: bool,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::Transfer<AccountId>>::transfer(A::get(), source, dest, amount, keep_alive)
|
||||
}
|
||||
fn deactivate(amount: Self::Balance) {
|
||||
<F as fungibles::Transfer<AccountId>>::deactivate(A::get(), amount)
|
||||
}
|
||||
fn reactivate(amount: Self::Balance) {
|
||||
<F as fungibles::Transfer<AccountId>>::reactivate(A::get(), amount)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
F: fungibles::InspectHold<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
> InspectHold<AccountId> for ItemOf<F, A, AccountId>
|
||||
{
|
||||
fn balance_on_hold(who: &AccountId) -> Self::Balance {
|
||||
<F as fungibles::InspectHold<AccountId>>::balance_on_hold(A::get(), who)
|
||||
}
|
||||
fn can_hold(who: &AccountId, amount: Self::Balance) -> bool {
|
||||
<F as fungibles::InspectHold<AccountId>>::can_hold(A::get(), who, amount)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
F: fungibles::MutateHold<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
> MutateHold<AccountId> for ItemOf<F, A, AccountId>
|
||||
{
|
||||
fn hold(who: &AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
<F as fungibles::MutateHold<AccountId>>::hold(A::get(), who, amount)
|
||||
}
|
||||
fn release(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
best_effort: bool,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::MutateHold<AccountId>>::release(A::get(), who, amount, best_effort)
|
||||
}
|
||||
fn transfer_held(
|
||||
source: &AccountId,
|
||||
dest: &AccountId,
|
||||
amount: Self::Balance,
|
||||
best_effort: bool,
|
||||
on_hold: bool,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::MutateHold<AccountId>>::transfer_held(
|
||||
A::get(),
|
||||
source,
|
||||
dest,
|
||||
amount,
|
||||
best_effort,
|
||||
on_hold,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
F: fungibles::Unbalanced<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
> Unbalanced<AccountId> for ItemOf<F, A, AccountId>
|
||||
{
|
||||
fn set_balance(who: &AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
<F as fungibles::Unbalanced<AccountId>>::set_balance(A::get(), who, amount)
|
||||
}
|
||||
fn set_total_issuance(amount: Self::Balance) -> () {
|
||||
<F as fungibles::Unbalanced<AccountId>>::set_total_issuance(A::get(), amount)
|
||||
}
|
||||
fn decrease_balance(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::Unbalanced<AccountId>>::decrease_balance(A::get(), who, amount)
|
||||
}
|
||||
fn decrease_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance {
|
||||
<F as fungibles::Unbalanced<AccountId>>::decrease_balance_at_most(A::get(), who, amount)
|
||||
}
|
||||
fn increase_balance(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::Unbalanced<AccountId>>::increase_balance(A::get(), who, amount)
|
||||
}
|
||||
fn increase_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance {
|
||||
<F as fungibles::Unbalanced<AccountId>>::increase_balance_at_most(A::get(), who, amount)
|
||||
}
|
||||
}
|
||||
@@ -1,354 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The trait and associated types for sets of fungible tokens that manage total issuance without
|
||||
//! requiring atomic balanced operations.
|
||||
|
||||
use super::{super::Imbalance as ImbalanceT, *};
|
||||
use crate::{
|
||||
dispatch::{DispatchError, DispatchResult},
|
||||
traits::misc::{SameOrOther, TryDrop},
|
||||
};
|
||||
use sp_runtime::{
|
||||
traits::{CheckedAdd, Zero},
|
||||
ArithmeticError, TokenError,
|
||||
};
|
||||
use sp_std::marker::PhantomData;
|
||||
|
||||
/// A fungible token class where any creation and deletion of tokens is semi-explicit and where the
|
||||
/// total supply is maintained automatically.
|
||||
///
|
||||
/// This is auto-implemented when a token class has `Unbalanced` implemented.
|
||||
pub trait Balanced<AccountId>: Inspect<AccountId> {
|
||||
/// The type for managing what happens when an instance of `Debt` is dropped without being used.
|
||||
type OnDropDebt: HandleImbalanceDrop<Self::Balance>;
|
||||
/// The type for managing what happens when an instance of `Credit` is dropped without being
|
||||
/// used.
|
||||
type OnDropCredit: HandleImbalanceDrop<Self::Balance>;
|
||||
|
||||
/// Reduce the total issuance by `amount` and return the according imbalance. The imbalance will
|
||||
/// typically be used to reduce an account by the same amount with e.g. `settle`.
|
||||
///
|
||||
/// This is infallible, but doesn't guarantee that the entire `amount` is burnt, for example
|
||||
/// in the case of underflow.
|
||||
fn rescind(amount: Self::Balance) -> DebtOf<AccountId, Self>;
|
||||
|
||||
/// Increase the total issuance by `amount` and return the according imbalance. The imbalance
|
||||
/// will typically be used to increase an account by the same amount with e.g.
|
||||
/// `resolve_into_existing` or `resolve_creating`.
|
||||
///
|
||||
/// This is infallible, but doesn't guarantee that the entire `amount` is issued, for example
|
||||
/// in the case of overflow.
|
||||
fn issue(amount: Self::Balance) -> CreditOf<AccountId, Self>;
|
||||
|
||||
/// Produce a pair of imbalances that cancel each other out exactly.
|
||||
///
|
||||
/// This is just the same as burning and issuing the same amount and has no effect on the
|
||||
/// total issuance.
|
||||
fn pair(amount: Self::Balance) -> (DebtOf<AccountId, Self>, CreditOf<AccountId, Self>) {
|
||||
(Self::rescind(amount), Self::issue(amount))
|
||||
}
|
||||
|
||||
/// Deducts up to `value` from the combined balance of `who`. This function cannot fail.
|
||||
///
|
||||
/// The resulting imbalance is the first item of the tuple returned.
|
||||
///
|
||||
/// As much funds up to `value` will be deducted as possible. If this is less than `value`,
|
||||
/// then a non-zero second item will be returned.
|
||||
fn slash(who: &AccountId, amount: Self::Balance) -> (CreditOf<AccountId, Self>, Self::Balance);
|
||||
|
||||
/// Mints exactly `value` into the account of `who`.
|
||||
///
|
||||
/// If `who` doesn't exist, nothing is done and an `Err` returned. This could happen because it
|
||||
/// the account doesn't yet exist and it isn't possible to create it under the current
|
||||
/// circumstances and with `value` in it.
|
||||
fn deposit(
|
||||
who: &AccountId,
|
||||
value: Self::Balance,
|
||||
) -> Result<DebtOf<AccountId, Self>, DispatchError>;
|
||||
|
||||
/// Removes `value` balance from `who` account if possible.
|
||||
///
|
||||
/// If the removal is not possible, then it returns `Err` and nothing is changed.
|
||||
///
|
||||
/// If the operation is successful, this will return `Ok` with a `NegativeImbalance` whose value
|
||||
/// is no less than `value`. It may be more in the case that removing it reduced it below
|
||||
/// `Self::minimum_balance()`.
|
||||
fn withdraw(
|
||||
who: &AccountId,
|
||||
value: Self::Balance,
|
||||
// TODO: liveness: ExistenceRequirement,
|
||||
) -> Result<CreditOf<AccountId, Self>, DispatchError>;
|
||||
|
||||
/// The balance of `who` is increased in order to counter `credit`. If the whole of `credit`
|
||||
/// cannot be countered, then nothing is changed and the original `credit` is returned in an
|
||||
/// `Err`.
|
||||
///
|
||||
/// Please note: If `credit.peek()` is less than `Self::minimum_balance()`, then `who` must
|
||||
/// already exist for this to succeed.
|
||||
fn resolve(
|
||||
who: &AccountId,
|
||||
credit: CreditOf<AccountId, Self>,
|
||||
) -> Result<(), CreditOf<AccountId, Self>> {
|
||||
let v = credit.peek();
|
||||
let debt = match Self::deposit(who, v) {
|
||||
Err(_) => return Err(credit),
|
||||
Ok(d) => d,
|
||||
};
|
||||
let result = credit.offset(debt).try_drop();
|
||||
debug_assert!(result.is_ok(), "ok deposit return must be equal to credit value; qed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The balance of `who` is decreased in order to counter `debt`. If the whole of `debt`
|
||||
/// cannot be countered, then nothing is changed and the original `debt` is returned in an
|
||||
/// `Err`.
|
||||
fn settle(
|
||||
who: &AccountId,
|
||||
debt: DebtOf<AccountId, Self>,
|
||||
// TODO: liveness: ExistenceRequirement,
|
||||
) -> Result<CreditOf<AccountId, Self>, DebtOf<AccountId, Self>> {
|
||||
let amount = debt.peek();
|
||||
let credit = match Self::withdraw(who, amount) {
|
||||
Err(_) => return Err(debt),
|
||||
Ok(d) => d,
|
||||
};
|
||||
match credit.offset(debt) {
|
||||
SameOrOther::None => Ok(CreditOf::<AccountId, Self>::zero()),
|
||||
SameOrOther::Same(dust) => Ok(dust),
|
||||
SameOrOther::Other(rest) => {
|
||||
debug_assert!(false, "ok withdraw return must be at least debt value; qed");
|
||||
Err(rest)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A fungible token class where the balance can be set arbitrarily.
|
||||
///
|
||||
/// **WARNING**
|
||||
/// Do not use this directly unless you want trouble, since it allows you to alter account balances
|
||||
/// without keeping the issuance up to date. It has no safeguards against accidentally creating
|
||||
/// token imbalances in your system leading to accidental imflation or deflation. It's really just
|
||||
/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to
|
||||
/// use.
|
||||
pub trait Unbalanced<AccountId>: Inspect<AccountId> {
|
||||
/// Set the balance of `who` to `amount`. If this cannot be done for some reason (e.g.
|
||||
/// because the account cannot be created or an overflow) then an `Err` is returned.
|
||||
fn set_balance(who: &AccountId, amount: Self::Balance) -> DispatchResult;
|
||||
|
||||
/// Set the total issuance to `amount`.
|
||||
fn set_total_issuance(amount: Self::Balance);
|
||||
|
||||
/// Reduce the balance of `who` by `amount`. If it cannot be reduced by that amount for
|
||||
/// some reason, return `Err` and don't reduce it at all. If Ok, return the imbalance.
|
||||
///
|
||||
/// Minimum balance will be respected and the returned imbalance may be up to
|
||||
/// `Self::minimum_balance() - 1` greater than `amount`.
|
||||
fn decrease_balance(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let old_balance = Self::balance(who);
|
||||
let (mut new_balance, mut amount) = if Self::reducible_balance(who, false) < amount {
|
||||
return Err(TokenError::NoFunds.into())
|
||||
} else {
|
||||
(old_balance - amount, amount)
|
||||
};
|
||||
if new_balance < Self::minimum_balance() {
|
||||
amount = amount.saturating_add(new_balance);
|
||||
new_balance = Zero::zero();
|
||||
}
|
||||
// Defensive only - this should not fail now.
|
||||
Self::set_balance(who, new_balance)?;
|
||||
Ok(amount)
|
||||
}
|
||||
|
||||
/// Reduce the balance of `who` by the most that is possible, up to `amount`.
|
||||
///
|
||||
/// Minimum balance will be respected and the returned imbalance may be up to
|
||||
/// `Self::minimum_balance() - 1` greater than `amount`.
|
||||
///
|
||||
/// Return the imbalance by which the account was reduced.
|
||||
fn decrease_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance {
|
||||
let old_balance = Self::balance(who);
|
||||
let old_free_balance = Self::reducible_balance(who, false);
|
||||
let (mut new_balance, mut amount) = if old_free_balance < amount {
|
||||
(old_balance.saturating_sub(old_free_balance), old_free_balance)
|
||||
} else {
|
||||
(old_balance - amount, amount)
|
||||
};
|
||||
let minimum_balance = Self::minimum_balance();
|
||||
if new_balance < minimum_balance {
|
||||
amount = amount.saturating_add(new_balance);
|
||||
new_balance = Zero::zero();
|
||||
}
|
||||
let mut r = Self::set_balance(who, new_balance);
|
||||
if r.is_err() {
|
||||
// Some error, probably because we tried to destroy an account which cannot be
|
||||
// destroyed.
|
||||
if new_balance.is_zero() && amount >= minimum_balance {
|
||||
new_balance = minimum_balance;
|
||||
amount -= minimum_balance;
|
||||
r = Self::set_balance(who, new_balance);
|
||||
}
|
||||
if r.is_err() {
|
||||
// Still an error. Apparently it's not possible to reduce at all.
|
||||
amount = Zero::zero();
|
||||
}
|
||||
}
|
||||
amount
|
||||
}
|
||||
|
||||
/// Increase the balance of `who` by `amount`. If it cannot be increased by that amount
|
||||
/// for some reason, return `Err` and don't increase it at all. If Ok, return the imbalance.
|
||||
///
|
||||
/// Minimum balance will be respected and an error will be returned if
|
||||
/// `amount < Self::minimum_balance()` when the account of `who` is zero.
|
||||
fn increase_balance(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let old_balance = Self::balance(who);
|
||||
let new_balance = old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?;
|
||||
if new_balance < Self::minimum_balance() {
|
||||
return Err(TokenError::BelowMinimum.into())
|
||||
}
|
||||
if old_balance != new_balance {
|
||||
Self::set_balance(who, new_balance)?;
|
||||
}
|
||||
Ok(amount)
|
||||
}
|
||||
|
||||
/// Increase the balance of `who` by the most that is possible, up to `amount`.
|
||||
///
|
||||
/// Minimum balance will be respected and the returned imbalance will be zero in the case that
|
||||
/// `amount < Self::minimum_balance()`.
|
||||
///
|
||||
/// Return the imbalance by which the account was increased.
|
||||
fn increase_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance {
|
||||
let old_balance = Self::balance(who);
|
||||
let mut new_balance = old_balance.saturating_add(amount);
|
||||
let mut amount = new_balance - old_balance;
|
||||
if new_balance < Self::minimum_balance() {
|
||||
new_balance = Zero::zero();
|
||||
amount = Zero::zero();
|
||||
}
|
||||
if old_balance == new_balance || Self::set_balance(who, new_balance).is_ok() {
|
||||
amount
|
||||
} else {
|
||||
Zero::zero()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple handler for an imbalance drop which increases the total issuance of the system by the
|
||||
/// imbalance amount. Used for leftover debt.
|
||||
pub struct IncreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
|
||||
impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::Balance>
|
||||
for IncreaseIssuance<AccountId, U>
|
||||
{
|
||||
fn handle(amount: U::Balance) {
|
||||
U::set_total_issuance(U::total_issuance().saturating_add(amount))
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple handler for an imbalance drop which decreases the total issuance of the system by the
|
||||
/// imbalance amount. Used for leftover credit.
|
||||
pub struct DecreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
|
||||
impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::Balance>
|
||||
for DecreaseIssuance<AccountId, U>
|
||||
{
|
||||
fn handle(amount: U::Balance) {
|
||||
U::set_total_issuance(U::total_issuance().saturating_sub(amount))
|
||||
}
|
||||
}
|
||||
|
||||
/// An imbalance type which uses `DecreaseIssuance` to deal with anything `Drop`ed.
|
||||
///
|
||||
/// Basically means that funds in someone's account have been removed and not yet placed anywhere
|
||||
/// else. If it gets dropped, then those funds will be assumed to be "burned" and the total supply
|
||||
/// will be accordingly decreased to ensure it equals the sum of the balances of all accounts.
|
||||
type Credit<AccountId, U> = Imbalance<
|
||||
<U as Inspect<AccountId>>::Balance,
|
||||
DecreaseIssuance<AccountId, U>,
|
||||
IncreaseIssuance<AccountId, U>,
|
||||
>;
|
||||
|
||||
/// An imbalance type which uses `IncreaseIssuance` to deal with anything `Drop`ed.
|
||||
///
|
||||
/// Basically means that there are funds in someone's account whose origin is as yet unaccounted
|
||||
/// for. If it gets dropped, then those funds will be assumed to be "minted" and the total supply
|
||||
/// will be accordingly increased to ensure it equals the sum of the balances of all accounts.
|
||||
type Debt<AccountId, U> = Imbalance<
|
||||
<U as Inspect<AccountId>>::Balance,
|
||||
IncreaseIssuance<AccountId, U>,
|
||||
DecreaseIssuance<AccountId, U>,
|
||||
>;
|
||||
|
||||
/// Create some `Credit` item. Only for internal use.
|
||||
fn credit<AccountId, U: Unbalanced<AccountId>>(amount: U::Balance) -> Credit<AccountId, U> {
|
||||
Imbalance::new(amount)
|
||||
}
|
||||
|
||||
/// Create some `Debt` item. Only for internal use.
|
||||
fn debt<AccountId, U: Unbalanced<AccountId>>(amount: U::Balance) -> Debt<AccountId, U> {
|
||||
Imbalance::new(amount)
|
||||
}
|
||||
|
||||
impl<AccountId, U: Unbalanced<AccountId>> Balanced<AccountId> for U {
|
||||
type OnDropCredit = DecreaseIssuance<AccountId, U>;
|
||||
type OnDropDebt = IncreaseIssuance<AccountId, U>;
|
||||
fn rescind(amount: Self::Balance) -> Debt<AccountId, Self> {
|
||||
let old = U::total_issuance();
|
||||
let new = old.saturating_sub(amount);
|
||||
U::set_total_issuance(new);
|
||||
debt(old - new)
|
||||
}
|
||||
fn issue(amount: Self::Balance) -> Credit<AccountId, Self> {
|
||||
let old = U::total_issuance();
|
||||
let new = old.saturating_add(amount);
|
||||
U::set_total_issuance(new);
|
||||
credit(new - old)
|
||||
}
|
||||
fn slash(who: &AccountId, amount: Self::Balance) -> (Credit<AccountId, Self>, Self::Balance) {
|
||||
let slashed = U::decrease_balance_at_most(who, amount);
|
||||
// `slashed` could be less than, greater than or equal to `amount`.
|
||||
// If slashed == amount, it means the account had at least amount in it and it could all be
|
||||
// removed without a problem.
|
||||
// If slashed > amount, it means the account had more than amount in it, but not enough more
|
||||
// to push it over minimum_balance.
|
||||
// If slashed < amount, it means the account didn't have enough in it to be reduced by
|
||||
// `amount` without being destroyed.
|
||||
(credit(slashed), amount.saturating_sub(slashed))
|
||||
}
|
||||
fn deposit(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Debt<AccountId, Self>, DispatchError> {
|
||||
let increase = U::increase_balance(who, amount)?;
|
||||
Ok(debt(increase))
|
||||
}
|
||||
fn withdraw(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
// TODO: liveness: ExistenceRequirement,
|
||||
) -> Result<Credit<AccountId, Self>, DispatchError> {
|
||||
let decrease = U::decrease_balance(who, amount)?;
|
||||
Ok(credit(decrease))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The traits for putting freezes within a single fungible token class.
|
||||
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::DispatchResult;
|
||||
|
||||
/// Trait for inspecting a fungible asset which can be frozen. Freezing is essentially setting a
|
||||
/// minimum balance bellow which the total balance (inclusive of any funds placed on hold) may not
|
||||
/// be normally allowed to drop. Generally, freezers will provide an "update" function such that
|
||||
/// if the total balance does drop below the limit, then the freezer can update their housekeeping
|
||||
/// accordingly.
|
||||
pub trait Inspect<AccountId>: super::Inspect<AccountId> {
|
||||
/// An identifier for a freeze.
|
||||
type Id: codec::Encode + TypeInfo + 'static;
|
||||
|
||||
/// Amount of funds held in reserve by `who` for the given `id`.
|
||||
fn balance_frozen(id: &Self::Id, who: &AccountId) -> Self::Balance;
|
||||
|
||||
/// The amount of the balance which can become frozen. Defaults to `total_balance()`.
|
||||
fn balance_freezable(who: &AccountId) -> Self::Balance {
|
||||
Self::total_balance(who)
|
||||
}
|
||||
|
||||
/// Returns `true` if it's possible to introduce a freeze for the given `id` onto the
|
||||
/// account of `who`. This will be true as long as the implementor supports as many
|
||||
/// concurrent freeze locks as there are possible values of `id`.
|
||||
fn can_freeze(id: &Self::Id, who: &AccountId) -> bool;
|
||||
}
|
||||
|
||||
/// Trait for introducing, altering and removing locks to freeze an account's funds so they never
|
||||
/// go below a set minimum.
|
||||
pub trait Mutate<AccountId>: Inspect<AccountId> {
|
||||
/// Prevent actions which would reduce the balance of the account of `who` below the given
|
||||
/// `amount` and identify this restriction though the given `id`. Unlike `extend_freeze`, any
|
||||
/// outstanding freeze in place for `who` under the `id` are dropped.
|
||||
///
|
||||
/// If `amount` is zero, it is equivalent to using `thaw`.
|
||||
///
|
||||
/// Note that `amount` can be greater than the total balance, if desired.
|
||||
fn set_freeze(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult;
|
||||
|
||||
/// Prevent the balance of the account of `who` from being reduced below the given `amount` and
|
||||
/// identify this restriction though the given `id`. Unlike `set_freeze`, this does not
|
||||
/// counteract any pre-existing freezes in place for `who` under the `id`. Also unlike
|
||||
/// `set_freeze`, in the case that `amount` is zero, this is no-op and never fails.
|
||||
///
|
||||
/// Note that more funds can be locked than the total balance, if desired.
|
||||
fn extend_freeze(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult;
|
||||
|
||||
/// Remove an existing lock.
|
||||
fn thaw(id: &Self::Id, who: &AccountId) -> DispatchResult;
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The traits for putting holds within a single fungible token class.
|
||||
|
||||
use crate::{
|
||||
ensure,
|
||||
traits::tokens::{
|
||||
DepositConsequence::Success,
|
||||
Fortitude::{self, Force},
|
||||
Precision::{self, BestEffort, Exact},
|
||||
Preservation::{self, Protect},
|
||||
Provenance::Extant,
|
||||
Restriction::{self, Free, OnHold},
|
||||
},
|
||||
};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_arithmetic::{
|
||||
traits::{CheckedAdd, CheckedSub, Zero},
|
||||
ArithmeticError,
|
||||
};
|
||||
use sp_runtime::{DispatchError, DispatchResult, Saturating, TokenError};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Trait for inspecting a fungible asset whose accounts support partitioning and slashing.
|
||||
pub trait Inspect<AccountId>: super::Inspect<AccountId> {
|
||||
/// An identifier for a hold. Used for disambiguating different holds so that
|
||||
/// they can be individually replaced or removed and funds from one hold don't accidentally
|
||||
/// become unreserved or slashed for another.
|
||||
type Reason: codec::Encode + TypeInfo + 'static;
|
||||
|
||||
/// Amount of funds on hold (for all hold reasons) of `who`.
|
||||
fn total_balance_on_hold(who: &AccountId) -> Self::Balance;
|
||||
|
||||
/// Get the maximum amount that the `total_balance_on_hold` of `who` can be reduced successfully
|
||||
/// based on whether we are willing to force the reduction and potentially go below user-level
|
||||
/// restrictions on the minimum amount of the account. Note: This cannot bring the account into
|
||||
/// an inconsistent state with regards any required existential deposit.
|
||||
///
|
||||
/// Always less than `total_balance_on_hold()`.
|
||||
fn reducible_total_balance_on_hold(who: &AccountId, force: Fortitude) -> Self::Balance;
|
||||
|
||||
/// Amount of funds on hold (for the given reason) of `who`.
|
||||
fn balance_on_hold(reason: &Self::Reason, who: &AccountId) -> Self::Balance;
|
||||
|
||||
/// Returns `true` if it's possible to place (additional) funds under a hold of a given
|
||||
/// `reason`. This may fail if the account has exhausted a limited number of concurrent
|
||||
/// holds or if it cannot be made to exist (e.g. there is no provider reference).
|
||||
///
|
||||
/// NOTE: This does not take into account changes which could be made to the account of `who`
|
||||
/// (such as removing a provider reference) after this call is made. Any usage of this should
|
||||
/// therefore ensure the account is already in the appropriate state prior to calling it.
|
||||
fn hold_available(reason: &Self::Reason, who: &AccountId) -> bool;
|
||||
|
||||
/// Check to see if some `amount` of funds of `who` may be placed on hold with the given
|
||||
/// `reason`. Reasons why this may not be true:
|
||||
///
|
||||
/// - The implementor supports only a limited number of concurrent holds on an account which is
|
||||
/// the possible values of `reason`;
|
||||
/// - The total balance of the account is less than `amount`;
|
||||
/// - Removing `amount` from the total balance would kill the account and remove the only
|
||||
/// provider reference.
|
||||
///
|
||||
/// Note: we pass `true` as the third argument to `reducible_balance` since we assume that if
|
||||
/// needed the balance can slashed. If we are using a simple non-forcing reserve-transfer, then
|
||||
/// we really ought to check that we are not reducing the funds below the freeze-limit (if any).
|
||||
///
|
||||
/// NOTE: This does not take into account changes which could be made to the account of `who`
|
||||
/// (such as removing a provider reference) after this call is made. Any usage of this should
|
||||
/// therefore ensure the account is already in the appropriate state prior to calling it.
|
||||
fn ensure_can_hold(
|
||||
reason: &Self::Reason,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> DispatchResult {
|
||||
ensure!(Self::hold_available(reason, who), TokenError::CannotCreateHold);
|
||||
ensure!(
|
||||
amount <= Self::reducible_balance(who, Protect, Force),
|
||||
TokenError::FundsUnavailable
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check to see if some `amount` of funds of `who` may be placed on hold for the given
|
||||
/// `reason`. Reasons why this may not be true:
|
||||
///
|
||||
/// - The implementor supports only a limited number of concurrernt holds on an account which is
|
||||
/// the possible values of `reason`;
|
||||
/// - The main balance of the account is less than `amount`;
|
||||
/// - Removing `amount` from the main balance would kill the account and remove the only
|
||||
/// provider reference.
|
||||
///
|
||||
/// NOTE: This does not take into account changes which could be made to the account of `who`
|
||||
/// (such as removing a provider reference) after this call is made. Any usage of this should
|
||||
/// therefore ensure the account is already in the appropriate state prior to calling it.
|
||||
fn can_hold(reason: &Self::Reason, who: &AccountId, amount: Self::Balance) -> bool {
|
||||
Self::ensure_can_hold(reason, who, amount).is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
/// A fungible, holdable token class where the balance on hold can be set arbitrarily.
|
||||
///
|
||||
/// **WARNING**
|
||||
/// Do not use this directly unless you want trouble, since it allows you to alter account balances
|
||||
/// without keeping the issuance up to date. It has no safeguards against accidentally creating
|
||||
/// token imbalances in your system leading to accidental imflation or deflation. It's really just
|
||||
/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to
|
||||
/// use.
|
||||
pub trait Unbalanced<AccountId>: Inspect<AccountId> {
|
||||
/// Forcefully set the balance on hold of `who` to `amount`. This is independent of any other
|
||||
/// balances on hold or the main ("free") balance.
|
||||
///
|
||||
/// If this call executes successfully, you can `assert_eq!(Self::balance_on_hold(), amount);`.
|
||||
///
|
||||
/// This function does its best to force the balance change through, but will not break system
|
||||
/// invariants such as any Existential Deposits needed or overflows/underflows.
|
||||
/// If this cannot be done for some reason (e.g. because the account doesn't exist) then an
|
||||
/// `Err` is returned.
|
||||
// Implmentation note: This should increment the consumer refs if it moves total on hold from
|
||||
// zero to non-zero and decrement in the opposite direction.
|
||||
//
|
||||
// Since this was not done in the previous logic, this will need either a migration or a
|
||||
// state item which tracks whether the account is on the old logic or new.
|
||||
fn set_balance_on_hold(
|
||||
reason: &Self::Reason,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> DispatchResult;
|
||||
|
||||
/// Reduce the balance on hold of `who` by `amount`.
|
||||
///
|
||||
/// If `precision` is `Exact` and it cannot be reduced by that amount for
|
||||
/// some reason, return `Err` and don't reduce it at all. If `precision` is `BestEffort`, then
|
||||
/// reduce the balance of `who` by the most that is possible, up to `amount`.
|
||||
///
|
||||
/// In either case, if `Ok` is returned then the inner is the amount by which is was reduced.
|
||||
fn decrease_balance_on_hold(
|
||||
reason: &Self::Reason,
|
||||
who: &AccountId,
|
||||
mut amount: Self::Balance,
|
||||
precision: Precision,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let old_balance = Self::balance_on_hold(reason, who);
|
||||
if let BestEffort = precision {
|
||||
amount = amount.min(old_balance);
|
||||
}
|
||||
let new_balance = old_balance.checked_sub(&amount).ok_or(TokenError::FundsUnavailable)?;
|
||||
Self::set_balance_on_hold(reason, who, new_balance)?;
|
||||
Ok(amount)
|
||||
}
|
||||
|
||||
/// Increase the balance on hold of `who` by `amount`.
|
||||
///
|
||||
/// If it cannot be increased by that amount for some reason, return `Err` and don't increase
|
||||
/// it at all. If Ok, return the imbalance.
|
||||
fn increase_balance_on_hold(
|
||||
reason: &Self::Reason,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let old_balance = Self::balance_on_hold(reason, who);
|
||||
let new_balance = if let BestEffort = precision {
|
||||
old_balance.saturating_add(amount)
|
||||
} else {
|
||||
old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?
|
||||
};
|
||||
let amount = new_balance.saturating_sub(old_balance);
|
||||
if !amount.is_zero() {
|
||||
Self::set_balance_on_hold(reason, who, new_balance)?;
|
||||
}
|
||||
Ok(amount)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for mutating a fungible asset which can be placed on hold.
|
||||
pub trait Mutate<AccountId>:
|
||||
Inspect<AccountId> + super::Unbalanced<AccountId> + Unbalanced<AccountId>
|
||||
{
|
||||
/// Hold some funds in an account. If a hold for `reason` is already in place, then this
|
||||
/// will increase it.
|
||||
fn hold(reason: &Self::Reason, who: &AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
// NOTE: This doesn't change the total balance of the account so there's no need to
|
||||
// check liquidity.
|
||||
|
||||
Self::ensure_can_hold(reason, who, amount)?;
|
||||
// Should be infallible now, but we proceed softly anyway.
|
||||
Self::decrease_balance(who, amount, Exact, Protect, Force)?;
|
||||
Self::increase_balance_on_hold(reason, who, amount, BestEffort)?;
|
||||
Self::done_hold(reason, who, amount);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Release up to `amount` held funds in an account.
|
||||
///
|
||||
/// The actual amount released is returned with `Ok`.
|
||||
///
|
||||
/// If `precision` is `BestEffort`, then the amount actually unreserved and returned as the
|
||||
/// inner value of `Ok` may be smaller than the `amount` passed.
|
||||
///
|
||||
/// NOTE! The inner of the `Ok` result variant returns the *actual* amount released. This is the
|
||||
/// opposite of the `ReservableCurrency::unreserve()` result, which gives the amount not able
|
||||
/// to be released!
|
||||
fn release(
|
||||
reason: &Self::Reason,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
// NOTE: This doesn't change the total balance of the account so there's no need to
|
||||
// check liquidity.
|
||||
|
||||
// We want to make sure we can deposit the amount in advance. If we can't then something is
|
||||
// very wrong.
|
||||
ensure!(Self::can_deposit(who, amount, Extant) == Success, TokenError::CannotCreate);
|
||||
// Get the amount we can actually take from the hold. This might be less than what we want
|
||||
// if we're only doing a best-effort.
|
||||
let amount = Self::decrease_balance_on_hold(reason, who, amount, precision)?;
|
||||
// Increase the main balance by what we took. We always do a best-effort here because we
|
||||
// already checked that we can deposit before.
|
||||
let actual = Self::increase_balance(who, amount, BestEffort)?;
|
||||
Self::done_release(reason, who, actual);
|
||||
Ok(actual)
|
||||
}
|
||||
|
||||
/// Attempt to decrease the balance of `who` which is held for the given `reason` by `amount`.
|
||||
///
|
||||
/// If `precision` is `BestEffort`, then as much as possible is reduced, up to `amount`, and the
|
||||
/// amount of tokens reduced is returned. Otherwise, if the total amount can be reduced, then it
|
||||
/// is and the amount returned, and if not, then nothing changes and `Err` is returned.
|
||||
///
|
||||
/// If `force` is `Force`, then locks/freezes will be ignored. This should only be used when
|
||||
/// conducting slashing or other activity which materially disadvantages the account holder
|
||||
/// since it could provide a means of circumventing freezes.
|
||||
fn burn_held(
|
||||
reason: &Self::Reason,
|
||||
who: &AccountId,
|
||||
mut amount: Self::Balance,
|
||||
precision: Precision,
|
||||
force: Fortitude,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
// We must check total-balance requirements if `!force`.
|
||||
let liquid = Self::reducible_total_balance_on_hold(who, force);
|
||||
if let BestEffort = precision {
|
||||
amount = amount.min(liquid);
|
||||
} else {
|
||||
ensure!(amount <= liquid, TokenError::Frozen);
|
||||
}
|
||||
let amount = Self::decrease_balance_on_hold(reason, who, amount, precision)?;
|
||||
Self::set_total_issuance(Self::total_issuance().saturating_sub(amount));
|
||||
Self::done_burn_held(reason, who, amount);
|
||||
Ok(amount)
|
||||
}
|
||||
|
||||
/// Transfer held funds into a destination account.
|
||||
///
|
||||
/// If `on_hold` is `true`, then the destination account must already exist and the assets
|
||||
/// transferred will still be on hold in the destination account. If not, then the destination
|
||||
/// account need not already exist, but must be creatable.
|
||||
///
|
||||
/// If `precision` is `BestEffort`, then an amount less than `amount` may be transferred without
|
||||
/// error.
|
||||
///
|
||||
/// If `force` is `Force`, then other fund-locking mechanisms may be disregarded. It should be
|
||||
/// left as `Polite` in most circumstances, but when you want the same power as a `slash`, it
|
||||
/// may be `Force`.
|
||||
///
|
||||
/// The actual amount transferred is returned, or `Err` in the case of error and nothing is
|
||||
/// changed.
|
||||
fn transfer_on_hold(
|
||||
reason: &Self::Reason,
|
||||
source: &AccountId,
|
||||
dest: &AccountId,
|
||||
mut amount: Self::Balance,
|
||||
precision: Precision,
|
||||
mode: Restriction,
|
||||
force: Fortitude,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
// We must check total-balance requirements if `force` is `Fortitude::Polite`.
|
||||
let have = Self::balance_on_hold(reason, source);
|
||||
let liquid = Self::reducible_total_balance_on_hold(source, force);
|
||||
if let BestEffort = precision {
|
||||
amount = amount.min(liquid).min(have);
|
||||
} else {
|
||||
ensure!(amount <= liquid, TokenError::Frozen);
|
||||
ensure!(amount <= have, TokenError::FundsUnavailable);
|
||||
}
|
||||
|
||||
// We want to make sure we can deposit the amount in advance. If we can't then something is
|
||||
// very wrong.
|
||||
ensure!(Self::can_deposit(dest, amount, Extant) == Success, TokenError::CannotCreate);
|
||||
ensure!(mode == Free || Self::hold_available(reason, dest), TokenError::CannotCreateHold);
|
||||
|
||||
let amount = Self::decrease_balance_on_hold(reason, source, amount, precision)?;
|
||||
let actual = if mode == OnHold {
|
||||
Self::increase_balance_on_hold(reason, dest, amount, precision)?
|
||||
} else {
|
||||
Self::increase_balance(dest, amount, precision)?
|
||||
};
|
||||
Self::done_transfer_on_hold(reason, source, dest, actual);
|
||||
Ok(actual)
|
||||
}
|
||||
|
||||
/// Transfer some `amount` of free balance from `source` to become owned by `dest` but on hold
|
||||
/// for `reason`.
|
||||
///
|
||||
/// If `precision` is `BestEffort`, then an amount less than `amount` may be transferred without
|
||||
/// error.
|
||||
///
|
||||
/// `source` must obey the requirements of `keep_alive`.
|
||||
///
|
||||
/// If `force` is `Force`, then other fund-locking mechanisms may be disregarded. It should be
|
||||
/// left as `Polite` in most circumstances, but when you want the same power as a `slash`, it
|
||||
/// may be `Force`.
|
||||
///
|
||||
/// The amount placed on hold is returned or `Err` in the case of error and nothing is changed.
|
||||
///
|
||||
/// WARNING: This may return an error after a partial storage mutation. It should be used only
|
||||
/// inside a transactional storage context and an `Err` result must imply a storage rollback.
|
||||
fn transfer_and_hold(
|
||||
reason: &Self::Reason,
|
||||
source: &AccountId,
|
||||
dest: &AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
expendability: Preservation,
|
||||
force: Fortitude,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
ensure!(Self::hold_available(reason, dest), TokenError::CannotCreateHold);
|
||||
ensure!(Self::can_deposit(dest, amount, Extant) == Success, TokenError::CannotCreate);
|
||||
let actual = Self::decrease_balance(source, amount, precision, expendability, force)?;
|
||||
Self::increase_balance_on_hold(reason, dest, actual, precision)?;
|
||||
Self::done_transfer_on_hold(reason, source, dest, actual);
|
||||
Ok(actual)
|
||||
}
|
||||
|
||||
fn done_hold(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {}
|
||||
fn done_release(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {}
|
||||
fn done_burn_held(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {}
|
||||
fn done_transfer_on_hold(
|
||||
_reason: &Self::Reason,
|
||||
_source: &AccountId,
|
||||
_dest: &AccountId,
|
||||
_amount: Self::Balance,
|
||||
) {
|
||||
}
|
||||
fn done_transfer_and_hold(
|
||||
_reason: &Self::Reason,
|
||||
_source: &AccountId,
|
||||
_dest: &AccountId,
|
||||
_transferred: Self::Balance,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for slashing a fungible asset which can be place on hold.
|
||||
pub trait Balanced<AccountId>: super::Balanced<AccountId> + Unbalanced<AccountId> {
|
||||
/// Reduce the balance of some funds on hold in an account.
|
||||
///
|
||||
/// The resulting imbalance is the first item of the tuple returned.
|
||||
///
|
||||
/// As much funds that are on hold up to `amount` will be deducted as possible. If this is less
|
||||
/// than `amount`, then a non-zero second item will be returned.
|
||||
fn slash(
|
||||
reason: &Self::Reason,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> (Credit<AccountId, Self>, Self::Balance) {
|
||||
let decrease = Self::decrease_balance_on_hold(reason, who, amount, BestEffort)
|
||||
.unwrap_or(Default::default());
|
||||
let credit =
|
||||
Imbalance::<Self::Balance, Self::OnDropCredit, Self::OnDropDebt>::new(decrease);
|
||||
Self::done_slash(reason, who, decrease);
|
||||
(credit, amount.saturating_sub(decrease))
|
||||
}
|
||||
|
||||
fn done_slash(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {}
|
||||
}
|
||||
@@ -18,8 +18,11 @@
|
||||
//! The imbalance type and its associates, which handles keeps everything adding up properly with
|
||||
//! unbalanced operations.
|
||||
|
||||
use super::{super::Imbalance as ImbalanceT, balanced::Balanced, misc::Balance, *};
|
||||
use crate::traits::misc::{SameOrOther, TryDrop};
|
||||
use super::{super::Imbalance as ImbalanceT, Balanced, *};
|
||||
use crate::traits::{
|
||||
misc::{SameOrOther, TryDrop},
|
||||
tokens::Balance,
|
||||
};
|
||||
use sp_runtime::{traits::Zero, RuntimeDebug};
|
||||
use sp_std::marker::PhantomData;
|
||||
|
||||
@@ -30,6 +33,10 @@ pub trait HandleImbalanceDrop<Balance> {
|
||||
fn handle(amount: Balance);
|
||||
}
|
||||
|
||||
impl<Balance> HandleImbalanceDrop<Balance> for () {
|
||||
fn handle(_: Balance) {}
|
||||
}
|
||||
|
||||
/// An imbalance in the system, representing a divergence of recorded token supply from the sum of
|
||||
/// the balances of all accounts. This is `must_use` in order to ensure it gets handled (placing
|
||||
/// into an account, settling from an account or altering the supply).
|
||||
@@ -135,7 +142,7 @@ impl<B: Balance, OnDrop: HandleImbalanceDrop<B>, OppositeOnDrop: HandleImbalance
|
||||
}
|
||||
|
||||
/// Imbalance implying that the total_issuance value is less than the sum of all account balances.
|
||||
pub type DebtOf<AccountId, B> = Imbalance<
|
||||
pub type Debt<AccountId, B> = Imbalance<
|
||||
<B as Inspect<AccountId>>::Balance,
|
||||
// This will generally be implemented by increasing the total_issuance value.
|
||||
<B as Balanced<AccountId>>::OnDropDebt,
|
||||
@@ -144,7 +151,7 @@ pub type DebtOf<AccountId, B> = Imbalance<
|
||||
|
||||
/// Imbalance implying that the total_issuance value is greater than the sum of all account
|
||||
/// balances.
|
||||
pub type CreditOf<AccountId, B> = Imbalance<
|
||||
pub type Credit<AccountId, B> = Imbalance<
|
||||
<B as Inspect<AccountId>>::Balance,
|
||||
// This will generally be implemented by decreasing the total_issuance value.
|
||||
<B as Balanced<AccountId>>::OnDropCredit,
|
||||
|
||||
@@ -0,0 +1,451 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Adapter to use `fungibles::*` implementations as `fungible::*`.
|
||||
|
||||
use sp_core::Get;
|
||||
use sp_runtime::{DispatchError, DispatchResult};
|
||||
|
||||
use super::*;
|
||||
use crate::traits::tokens::{
|
||||
fungibles, DepositConsequence, Fortitude, Imbalance as ImbalanceT, Precision, Preservation,
|
||||
Provenance, Restriction, WithdrawConsequence,
|
||||
};
|
||||
|
||||
/// Convert a `fungibles` trait implementation into a `fungible` trait implementation by identifying
|
||||
/// a single item.
|
||||
pub struct ItemOf<
|
||||
F: fungibles::Inspect<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
>(sp_std::marker::PhantomData<(F, A, AccountId)>);
|
||||
|
||||
impl<
|
||||
F: fungibles::Inspect<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
> Inspect<AccountId> for ItemOf<F, A, AccountId>
|
||||
{
|
||||
type Balance = <F as fungibles::Inspect<AccountId>>::Balance;
|
||||
fn total_issuance() -> Self::Balance {
|
||||
<F as fungibles::Inspect<AccountId>>::total_issuance(A::get())
|
||||
}
|
||||
fn active_issuance() -> Self::Balance {
|
||||
<F as fungibles::Inspect<AccountId>>::active_issuance(A::get())
|
||||
}
|
||||
fn minimum_balance() -> Self::Balance {
|
||||
<F as fungibles::Inspect<AccountId>>::minimum_balance(A::get())
|
||||
}
|
||||
fn balance(who: &AccountId) -> Self::Balance {
|
||||
<F as fungibles::Inspect<AccountId>>::balance(A::get(), who)
|
||||
}
|
||||
fn total_balance(who: &AccountId) -> Self::Balance {
|
||||
<F as fungibles::Inspect<AccountId>>::total_balance(A::get(), who)
|
||||
}
|
||||
fn reducible_balance(
|
||||
who: &AccountId,
|
||||
preservation: Preservation,
|
||||
force: Fortitude,
|
||||
) -> Self::Balance {
|
||||
<F as fungibles::Inspect<AccountId>>::reducible_balance(A::get(), who, preservation, force)
|
||||
}
|
||||
fn can_deposit(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
provenance: Provenance,
|
||||
) -> DepositConsequence {
|
||||
<F as fungibles::Inspect<AccountId>>::can_deposit(A::get(), who, amount, provenance)
|
||||
}
|
||||
fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence<Self::Balance> {
|
||||
<F as fungibles::Inspect<AccountId>>::can_withdraw(A::get(), who, amount)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
F: fungibles::InspectHold<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
> InspectHold<AccountId> for ItemOf<F, A, AccountId>
|
||||
{
|
||||
type Reason = F::Reason;
|
||||
|
||||
fn reducible_total_balance_on_hold(who: &AccountId, force: Fortitude) -> Self::Balance {
|
||||
<F as fungibles::InspectHold<AccountId>>::reducible_total_balance_on_hold(
|
||||
A::get(),
|
||||
who,
|
||||
force,
|
||||
)
|
||||
}
|
||||
fn hold_available(reason: &Self::Reason, who: &AccountId) -> bool {
|
||||
<F as fungibles::InspectHold<AccountId>>::hold_available(A::get(), reason, who)
|
||||
}
|
||||
fn total_balance_on_hold(who: &AccountId) -> Self::Balance {
|
||||
<F as fungibles::InspectHold<AccountId>>::total_balance_on_hold(A::get(), who)
|
||||
}
|
||||
fn balance_on_hold(reason: &Self::Reason, who: &AccountId) -> Self::Balance {
|
||||
<F as fungibles::InspectHold<AccountId>>::balance_on_hold(A::get(), reason, who)
|
||||
}
|
||||
fn can_hold(reason: &Self::Reason, who: &AccountId, amount: Self::Balance) -> bool {
|
||||
<F as fungibles::InspectHold<AccountId>>::can_hold(A::get(), reason, who, amount)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
F: fungibles::InspectFreeze<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
> InspectFreeze<AccountId> for ItemOf<F, A, AccountId>
|
||||
{
|
||||
type Id = F::Id;
|
||||
fn balance_frozen(id: &Self::Id, who: &AccountId) -> Self::Balance {
|
||||
<F as fungibles::InspectFreeze<AccountId>>::balance_frozen(A::get(), id, who)
|
||||
}
|
||||
fn balance_freezable(who: &AccountId) -> Self::Balance {
|
||||
<F as fungibles::InspectFreeze<AccountId>>::balance_freezable(A::get(), who)
|
||||
}
|
||||
fn can_freeze(id: &Self::Id, who: &AccountId) -> bool {
|
||||
<F as fungibles::InspectFreeze<AccountId>>::can_freeze(A::get(), id, who)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
F: fungibles::Unbalanced<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
> Unbalanced<AccountId> for ItemOf<F, A, AccountId>
|
||||
{
|
||||
fn handle_dust(dust: regular::Dust<AccountId, Self>)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
<F as fungibles::Unbalanced<AccountId>>::handle_dust(fungibles::Dust(A::get(), dust.0))
|
||||
}
|
||||
fn write_balance(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Option<Self::Balance>, DispatchError> {
|
||||
<F as fungibles::Unbalanced<AccountId>>::write_balance(A::get(), who, amount)
|
||||
}
|
||||
fn set_total_issuance(amount: Self::Balance) -> () {
|
||||
<F as fungibles::Unbalanced<AccountId>>::set_total_issuance(A::get(), amount)
|
||||
}
|
||||
fn decrease_balance(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
preservation: Preservation,
|
||||
force: Fortitude,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::Unbalanced<AccountId>>::decrease_balance(
|
||||
A::get(),
|
||||
who,
|
||||
amount,
|
||||
precision,
|
||||
preservation,
|
||||
force,
|
||||
)
|
||||
}
|
||||
fn increase_balance(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::Unbalanced<AccountId>>::increase_balance(A::get(), who, amount, precision)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
F: fungibles::UnbalancedHold<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
> UnbalancedHold<AccountId> for ItemOf<F, A, AccountId>
|
||||
{
|
||||
fn set_balance_on_hold(
|
||||
reason: &Self::Reason,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> DispatchResult {
|
||||
<F as fungibles::UnbalancedHold<AccountId>>::set_balance_on_hold(
|
||||
A::get(),
|
||||
reason,
|
||||
who,
|
||||
amount,
|
||||
)
|
||||
}
|
||||
fn decrease_balance_on_hold(
|
||||
reason: &Self::Reason,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::UnbalancedHold<AccountId>>::decrease_balance_on_hold(
|
||||
A::get(),
|
||||
reason,
|
||||
who,
|
||||
amount,
|
||||
precision,
|
||||
)
|
||||
}
|
||||
fn increase_balance_on_hold(
|
||||
reason: &Self::Reason,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::UnbalancedHold<AccountId>>::increase_balance_on_hold(
|
||||
A::get(),
|
||||
reason,
|
||||
who,
|
||||
amount,
|
||||
precision,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
F: fungibles::Mutate<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
> Mutate<AccountId> for ItemOf<F, A, AccountId>
|
||||
{
|
||||
fn mint_into(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::Mutate<AccountId>>::mint_into(A::get(), who, amount)
|
||||
}
|
||||
fn burn_from(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
force: Fortitude,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::Mutate<AccountId>>::burn_from(A::get(), who, amount, precision, force)
|
||||
}
|
||||
fn shelve(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::Mutate<AccountId>>::shelve(A::get(), who, amount)
|
||||
}
|
||||
fn restore(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::Mutate<AccountId>>::restore(A::get(), who, amount)
|
||||
}
|
||||
fn transfer(
|
||||
source: &AccountId,
|
||||
dest: &AccountId,
|
||||
amount: Self::Balance,
|
||||
preservation: Preservation,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::Mutate<AccountId>>::transfer(A::get(), source, dest, amount, preservation)
|
||||
}
|
||||
|
||||
fn set_balance(who: &AccountId, amount: Self::Balance) -> Self::Balance {
|
||||
<F as fungibles::Mutate<AccountId>>::set_balance(A::get(), who, amount)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
F: fungibles::MutateHold<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
> MutateHold<AccountId> for ItemOf<F, A, AccountId>
|
||||
{
|
||||
fn hold(reason: &Self::Reason, who: &AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
<F as fungibles::MutateHold<AccountId>>::hold(A::get(), reason, who, amount)
|
||||
}
|
||||
fn release(
|
||||
reason: &Self::Reason,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::MutateHold<AccountId>>::release(A::get(), reason, who, amount, precision)
|
||||
}
|
||||
fn burn_held(
|
||||
reason: &Self::Reason,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
force: Fortitude,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::MutateHold<AccountId>>::burn_held(
|
||||
A::get(),
|
||||
reason,
|
||||
who,
|
||||
amount,
|
||||
precision,
|
||||
force,
|
||||
)
|
||||
}
|
||||
fn transfer_on_hold(
|
||||
reason: &Self::Reason,
|
||||
source: &AccountId,
|
||||
dest: &AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
mode: Restriction,
|
||||
force: Fortitude,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::MutateHold<AccountId>>::transfer_on_hold(
|
||||
A::get(),
|
||||
reason,
|
||||
source,
|
||||
dest,
|
||||
amount,
|
||||
precision,
|
||||
mode,
|
||||
force,
|
||||
)
|
||||
}
|
||||
fn transfer_and_hold(
|
||||
reason: &Self::Reason,
|
||||
source: &AccountId,
|
||||
dest: &AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
preservation: Preservation,
|
||||
force: Fortitude,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
<F as fungibles::MutateHold<AccountId>>::transfer_and_hold(
|
||||
A::get(),
|
||||
reason,
|
||||
source,
|
||||
dest,
|
||||
amount,
|
||||
precision,
|
||||
preservation,
|
||||
force,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
F: fungibles::MutateFreeze<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
> MutateFreeze<AccountId> for ItemOf<F, A, AccountId>
|
||||
{
|
||||
fn set_freeze(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
<F as fungibles::MutateFreeze<AccountId>>::set_freeze(A::get(), id, who, amount)
|
||||
}
|
||||
fn extend_freeze(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
<F as fungibles::MutateFreeze<AccountId>>::extend_freeze(A::get(), id, who, amount)
|
||||
}
|
||||
fn thaw(id: &Self::Id, who: &AccountId) -> DispatchResult {
|
||||
<F as fungibles::MutateFreeze<AccountId>>::thaw(A::get(), id, who)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConvertImbalanceDropHandler<AccountId, Balance, AssetIdType, AssetId, Handler>(
|
||||
sp_std::marker::PhantomData<(AccountId, Balance, AssetIdType, AssetId, Handler)>,
|
||||
);
|
||||
|
||||
impl<
|
||||
AccountId,
|
||||
Balance,
|
||||
AssetIdType,
|
||||
AssetId: Get<AssetIdType>,
|
||||
Handler: crate::traits::tokens::fungibles::HandleImbalanceDrop<AssetIdType, Balance>,
|
||||
> HandleImbalanceDrop<Balance>
|
||||
for ConvertImbalanceDropHandler<AccountId, Balance, AssetIdType, AssetId, Handler>
|
||||
{
|
||||
fn handle(amount: Balance) {
|
||||
Handler::handle(AssetId::get(), amount)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
F: fungibles::Inspect<AccountId>
|
||||
+ fungibles::Unbalanced<AccountId>
|
||||
+ fungibles::Balanced<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
> Balanced<AccountId> for ItemOf<F, A, AccountId>
|
||||
{
|
||||
type OnDropDebt =
|
||||
ConvertImbalanceDropHandler<AccountId, Self::Balance, F::AssetId, A, F::OnDropDebt>;
|
||||
type OnDropCredit =
|
||||
ConvertImbalanceDropHandler<AccountId, Self::Balance, F::AssetId, A, F::OnDropCredit>;
|
||||
fn deposit(
|
||||
who: &AccountId,
|
||||
value: Self::Balance,
|
||||
precision: Precision,
|
||||
) -> Result<Debt<AccountId, Self>, DispatchError> {
|
||||
<F as fungibles::Balanced<AccountId>>::deposit(A::get(), who, value, precision)
|
||||
.map(|debt| Imbalance::new(debt.peek()))
|
||||
}
|
||||
fn issue(amount: Self::Balance) -> Credit<AccountId, Self> {
|
||||
Imbalance::new(<F as fungibles::Balanced<AccountId>>::issue(A::get(), amount).peek())
|
||||
}
|
||||
fn pair(amount: Self::Balance) -> (Debt<AccountId, Self>, Credit<AccountId, Self>) {
|
||||
let (a, b) = <F as fungibles::Balanced<AccountId>>::pair(A::get(), amount);
|
||||
(Imbalance::new(a.peek()), Imbalance::new(b.peek()))
|
||||
}
|
||||
fn rescind(amount: Self::Balance) -> Debt<AccountId, Self> {
|
||||
Imbalance::new(<F as fungibles::Balanced<AccountId>>::rescind(A::get(), amount).peek())
|
||||
}
|
||||
fn resolve(
|
||||
who: &AccountId,
|
||||
credit: Credit<AccountId, Self>,
|
||||
) -> Result<(), Credit<AccountId, Self>> {
|
||||
let credit = fungibles::Imbalance::new(A::get(), credit.peek());
|
||||
<F as fungibles::Balanced<AccountId>>::resolve(who, credit)
|
||||
.map_err(|credit| Imbalance::new(credit.peek()))
|
||||
}
|
||||
fn settle(
|
||||
who: &AccountId,
|
||||
debt: Debt<AccountId, Self>,
|
||||
preservation: Preservation,
|
||||
) -> Result<Credit<AccountId, Self>, Debt<AccountId, Self>> {
|
||||
let debt = fungibles::Imbalance::new(A::get(), debt.peek());
|
||||
<F as fungibles::Balanced<AccountId>>::settle(who, debt, preservation)
|
||||
.map(|credit| Imbalance::new(credit.peek()))
|
||||
.map_err(|debt| Imbalance::new(debt.peek()))
|
||||
}
|
||||
fn withdraw(
|
||||
who: &AccountId,
|
||||
value: Self::Balance,
|
||||
precision: Precision,
|
||||
preservation: Preservation,
|
||||
force: Fortitude,
|
||||
) -> Result<Credit<AccountId, Self>, DispatchError> {
|
||||
<F as fungibles::Balanced<AccountId>>::withdraw(
|
||||
A::get(),
|
||||
who,
|
||||
value,
|
||||
precision,
|
||||
preservation,
|
||||
force,
|
||||
)
|
||||
.map(|credit| Imbalance::new(credit.peek()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
F: fungibles::BalancedHold<AccountId>,
|
||||
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
|
||||
AccountId,
|
||||
> BalancedHold<AccountId> for ItemOf<F, A, AccountId>
|
||||
{
|
||||
fn slash(
|
||||
reason: &Self::Reason,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> (Credit<AccountId, Self>, Self::Balance) {
|
||||
let (credit, amount) =
|
||||
<F as fungibles::BalancedHold<AccountId>>::slash(A::get(), reason, who, amount);
|
||||
(Imbalance::new(credit.peek()), amount)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {}
|
||||
@@ -0,0 +1,56 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The traits for dealing with a single fungible token class and any associated types.
|
||||
//!
|
||||
//! ### User-implememted traits
|
||||
//! - `Inspect`: Regular balance inspector functions.
|
||||
//! - `Unbalanced`: Low-level balance mutating functions. Does not guarantee proper book-keeping and
|
||||
//! so should not be called into directly from application code. Other traits depend on this and
|
||||
//! provide default implementations based on it.
|
||||
//! - `UnbalancedHold`: Low-level balance mutating functions for balances placed on hold. Does not
|
||||
//! guarantee proper book-keeping and so should not be called into directly from application code.
|
||||
//! Other traits depend on this and provide default implementations based on it.
|
||||
//! - `Mutate`: Regular balance mutator functions. Pre-implemented using `Unbalanced`, though the
|
||||
//! `done_*` functions should likely be reimplemented in case you want to do something following
|
||||
//! the operation such as emit events.
|
||||
//! - `InspectHold`: Inspector functions for balances on hold.
|
||||
//! - `MutateHold`: Mutator functions for balances on hold. Mostly pre-implemented using
|
||||
//! `UnbalancedHold`.
|
||||
//! - `InspectFreeze`: Inspector functions for frozen balance.
|
||||
//! - `MutateFreeze`: Mutator functions for frozen balance.
|
||||
//! - `Balanced`: One-sided mutator functions for regular balances, which return imbalance objects
|
||||
//! which guarantee eventual book-keeping. May be useful for some sophisticated operations where
|
||||
//! funds must be removed from an account before it is known precisely what should be done with
|
||||
//! them.
|
||||
|
||||
pub mod freeze;
|
||||
pub mod hold;
|
||||
mod imbalance;
|
||||
mod item_of;
|
||||
mod regular;
|
||||
|
||||
pub use freeze::{Inspect as InspectFreeze, Mutate as MutateFreeze};
|
||||
pub use hold::{
|
||||
Balanced as BalancedHold, Inspect as InspectHold, Mutate as MutateHold,
|
||||
Unbalanced as UnbalancedHold,
|
||||
};
|
||||
pub use imbalance::{Credit, Debt, HandleImbalanceDrop, Imbalance};
|
||||
pub use item_of::ItemOf;
|
||||
pub use regular::{
|
||||
Balanced, DecreaseIssuance, Dust, IncreaseIssuance, Inspect, Mutate, Unbalanced,
|
||||
};
|
||||
@@ -0,0 +1,506 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! `Inspect` and `Mutate` traits for working with regular balances.
|
||||
|
||||
use crate::{
|
||||
dispatch::DispatchError,
|
||||
ensure,
|
||||
traits::{
|
||||
tokens::{
|
||||
misc::{
|
||||
Balance, DepositConsequence,
|
||||
Fortitude::{self, Force, Polite},
|
||||
Precision::{self, BestEffort, Exact},
|
||||
Preservation::{self, Expendable},
|
||||
Provenance::{self, Extant},
|
||||
WithdrawConsequence,
|
||||
},
|
||||
Imbalance as ImbalanceT,
|
||||
},
|
||||
SameOrOther, TryDrop,
|
||||
},
|
||||
};
|
||||
use sp_arithmetic::traits::{CheckedAdd, CheckedSub, One};
|
||||
use sp_runtime::{traits::Saturating, ArithmeticError, TokenError};
|
||||
use sp_std::marker::PhantomData;
|
||||
|
||||
use super::{Credit, Debt, HandleImbalanceDrop, Imbalance};
|
||||
|
||||
/// Trait for providing balance-inspection access to a fungible asset.
|
||||
pub trait Inspect<AccountId>: Sized {
|
||||
/// Scalar type for representing balance of an account.
|
||||
type Balance: Balance;
|
||||
|
||||
/// The total amount of issuance in the system.
|
||||
fn total_issuance() -> Self::Balance;
|
||||
|
||||
/// The total amount of issuance in the system excluding those which are controlled by the
|
||||
/// system.
|
||||
fn active_issuance() -> Self::Balance {
|
||||
Self::total_issuance()
|
||||
}
|
||||
|
||||
/// The minimum balance any single account may have.
|
||||
fn minimum_balance() -> Self::Balance;
|
||||
|
||||
/// Get the total amount of funds whose ultimate bneficial ownership can be determined as `who`.
|
||||
///
|
||||
/// This may include funds which are wholly inaccessible to `who`, either temporarily or even
|
||||
/// indefinitely.
|
||||
///
|
||||
/// For the amount of the balance which is currently free to be removed from the account without
|
||||
/// error, use `reducible_balance`.
|
||||
///
|
||||
/// For the amount of the balance which may eventually be free to be removed from the account,
|
||||
/// use `balance()`.
|
||||
fn total_balance(who: &AccountId) -> Self::Balance;
|
||||
|
||||
/// Get the balance of `who` which does not include funds which are exclusively allocated to
|
||||
/// subsystems of the chain ("on hold" or "reserved").
|
||||
///
|
||||
/// In general this isn't especially useful outside of tests, and for practical purposes, you'll
|
||||
/// want to use `reducible_balance()`.
|
||||
fn balance(who: &AccountId) -> Self::Balance;
|
||||
|
||||
/// Get the maximum amount that `who` can withdraw/transfer successfully based on whether the
|
||||
/// account should be kept alive (`preservation`) or whether we are willing to force the
|
||||
/// reduction and potentially go below user-level restrictions on the minimum amount of the
|
||||
/// account.
|
||||
///
|
||||
/// Always less than or equal to `balance()`.
|
||||
fn reducible_balance(
|
||||
who: &AccountId,
|
||||
preservation: Preservation,
|
||||
force: Fortitude,
|
||||
) -> Self::Balance;
|
||||
|
||||
/// Returns `true` if the balance of `who` may be increased by `amount`.
|
||||
///
|
||||
/// - `who`: The account of which the balance should be increased by `amount`.
|
||||
/// - `amount`: How much should the balance be increased?
|
||||
/// - `provenance`: Will `amount` be minted to deposit it into `account` or is it already in the
|
||||
/// system?
|
||||
fn can_deposit(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
provenance: Provenance,
|
||||
) -> DepositConsequence;
|
||||
|
||||
/// Returns `Success` if the balance of `who` may be decreased by `amount`, otherwise
|
||||
/// the consequence.
|
||||
fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence<Self::Balance>;
|
||||
}
|
||||
|
||||
/// Special dust type which can be type-safely converted into a `Credit`.
|
||||
#[must_use]
|
||||
pub struct Dust<A, T: Inspect<A>>(pub(crate) T::Balance);
|
||||
|
||||
impl<A, T: Balanced<A>> Dust<A, T> {
|
||||
/// Convert `Dust` into an instance of `Credit`.
|
||||
pub fn into_credit(self) -> Credit<A, T> {
|
||||
Credit::<A, T>::new(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A fungible token class where the balance can be set arbitrarily.
|
||||
///
|
||||
/// **WARNING**
|
||||
/// Do not use this directly unless you want trouble, since it allows you to alter account balances
|
||||
/// without keeping the issuance up to date. It has no safeguards against accidentally creating
|
||||
/// token imbalances in your system leading to accidental imflation or deflation. It's really just
|
||||
/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to
|
||||
/// use.
|
||||
pub trait Unbalanced<AccountId>: Inspect<AccountId> {
|
||||
/// Create some dust and handle it with `Self::handle_dust`. This is an unbalanced operation
|
||||
/// and it must only be used when an account is modified in a raw fashion, outside of the entire
|
||||
/// fungibles API. The `amount` is capped at `Self::minimum_balance() - 1`.
|
||||
///
|
||||
/// This should not be reimplemented.
|
||||
fn handle_raw_dust(amount: Self::Balance) {
|
||||
Self::handle_dust(Dust(amount.min(Self::minimum_balance().saturating_sub(One::one()))))
|
||||
}
|
||||
|
||||
/// Do something with the dust which has been destroyed from the system. `Dust` can be converted
|
||||
/// into a `Credit` with the `Balanced` trait impl.
|
||||
fn handle_dust(dust: Dust<AccountId, Self>);
|
||||
|
||||
/// Forcefully set the balance of `who` to `amount`.
|
||||
///
|
||||
/// If this call executes successfully, you can `assert_eq!(Self::balance(), amount);`.
|
||||
///
|
||||
/// For implementations which include one or more balances on hold, then these are *not*
|
||||
/// included in the `amount`.
|
||||
///
|
||||
/// This function does its best to force the balance change through, but will not break system
|
||||
/// invariants such as any Existential Deposits needed or overflows/underflows.
|
||||
/// If this cannot be done for some reason (e.g. because the account cannot be created, deleted
|
||||
/// or would overflow) then an `Err` is returned.
|
||||
///
|
||||
/// If `Ok` is returned then its inner, if `Some` is the amount which was discarded as dust due
|
||||
/// to existential deposit requirements. The default implementation of `decrease_balance` and
|
||||
/// `increase_balance` converts this into an `Imbalance` and then passes it into `handle_dust`.
|
||||
fn write_balance(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Option<Self::Balance>, DispatchError>;
|
||||
|
||||
/// Set the total issuance to `amount`.
|
||||
fn set_total_issuance(amount: Self::Balance);
|
||||
|
||||
/// Reduce the balance of `who` by `amount`.
|
||||
///
|
||||
/// If `precision` is `Exact` and it cannot be reduced by that amount for
|
||||
/// some reason, return `Err` and don't reduce it at all. If `precision` is `BestEffort`, then
|
||||
/// reduce the balance of `who` by the most that is possible, up to `amount`.
|
||||
///
|
||||
/// In either case, if `Ok` is returned then the inner is the amount by which is was reduced.
|
||||
/// Minimum balance will be respected and thus the returned amount may be up to
|
||||
/// `Self::minimum_balance() - 1` greater than `amount` in the case that the reduction caused
|
||||
/// the account to be deleted.
|
||||
fn decrease_balance(
|
||||
who: &AccountId,
|
||||
mut amount: Self::Balance,
|
||||
precision: Precision,
|
||||
preservation: Preservation,
|
||||
force: Fortitude,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let old_balance = Self::balance(who);
|
||||
let free = Self::reducible_balance(who, preservation, force);
|
||||
if let BestEffort = precision {
|
||||
amount = amount.min(free);
|
||||
}
|
||||
let new_balance = old_balance.checked_sub(&amount).ok_or(TokenError::FundsUnavailable)?;
|
||||
if let Some(dust) = Self::write_balance(who, new_balance)? {
|
||||
Self::handle_dust(Dust(dust));
|
||||
}
|
||||
Ok(old_balance.saturating_sub(new_balance))
|
||||
}
|
||||
|
||||
/// Increase the balance of `who` by `amount`.
|
||||
///
|
||||
/// If it cannot be increased by that amount for some reason, return `Err` and don't increase
|
||||
/// it at all. If Ok, return the imbalance.
|
||||
/// Minimum balance will be respected and an error will be returned if
|
||||
/// `amount < Self::minimum_balance()` when the account of `who` is zero.
|
||||
fn increase_balance(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let old_balance = Self::balance(who);
|
||||
let new_balance = if let BestEffort = precision {
|
||||
old_balance.saturating_add(amount)
|
||||
} else {
|
||||
old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?
|
||||
};
|
||||
if new_balance < Self::minimum_balance() {
|
||||
// Attempt to increase from 0 to below minimum -> stays at zero.
|
||||
if let BestEffort = precision {
|
||||
Ok(Default::default())
|
||||
} else {
|
||||
Err(TokenError::BelowMinimum.into())
|
||||
}
|
||||
} else {
|
||||
if new_balance == old_balance {
|
||||
Ok(Default::default())
|
||||
} else {
|
||||
if let Some(dust) = Self::write_balance(who, new_balance)? {
|
||||
Self::handle_dust(Dust(dust));
|
||||
}
|
||||
Ok(new_balance.saturating_sub(old_balance))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reduce the active issuance by some amount.
|
||||
fn deactivate(_: Self::Balance) {}
|
||||
|
||||
/// Increase the active issuance by some amount, up to the outstanding amount reduced.
|
||||
fn reactivate(_: Self::Balance) {}
|
||||
}
|
||||
|
||||
/// Trait for providing a basic fungible asset.
|
||||
pub trait Mutate<AccountId>: Inspect<AccountId> + Unbalanced<AccountId> {
|
||||
/// Increase the balance of `who` by exactly `amount`, minting new tokens. If that isn't
|
||||
/// possible then an `Err` is returned and nothing is changed.
|
||||
fn mint_into(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
|
||||
Self::total_issuance().checked_add(&amount).ok_or(ArithmeticError::Overflow)?;
|
||||
let actual = Self::increase_balance(who, amount, Exact)?;
|
||||
Self::set_total_issuance(Self::total_issuance().saturating_add(actual));
|
||||
Self::done_mint_into(who, amount);
|
||||
Ok(actual)
|
||||
}
|
||||
|
||||
/// Decrease the balance of `who` by at least `amount`, possibly slightly more in the case of
|
||||
/// minimum-balance requirements, burning the tokens. If that isn't possible then an `Err` is
|
||||
/// returned and nothing is changed. If successful, the amount of tokens reduced is returned.
|
||||
fn burn_from(
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
force: Fortitude,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let actual = Self::reducible_balance(who, Expendable, force).min(amount);
|
||||
ensure!(actual == amount || precision == BestEffort, TokenError::FundsUnavailable);
|
||||
Self::total_issuance().checked_sub(&actual).ok_or(ArithmeticError::Overflow)?;
|
||||
let actual = Self::decrease_balance(who, actual, BestEffort, Expendable, force)?;
|
||||
Self::set_total_issuance(Self::total_issuance().saturating_sub(actual));
|
||||
Self::done_burn_from(who, actual);
|
||||
Ok(actual)
|
||||
}
|
||||
|
||||
/// Attempt to decrease the `asset` balance of `who` by `amount`.
|
||||
///
|
||||
/// Equivalent to `burn_from`, except with an expectation that within the bounds of some
|
||||
/// universal issuance, the total assets `suspend`ed and `resume`d will be equivalent. The
|
||||
/// implementation may be configured such that the total assets suspended may never be less than
|
||||
/// the total assets resumed (which is the invariant for an issuing system), or the reverse
|
||||
/// (which the invariant in a non-issuing system).
|
||||
///
|
||||
/// Because of this expectation, any metadata associated with the asset is expected to survive
|
||||
/// the suspect-resume cycle.
|
||||
fn shelve(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
|
||||
let actual = Self::reducible_balance(who, Expendable, Polite).min(amount);
|
||||
ensure!(actual == amount, TokenError::FundsUnavailable);
|
||||
Self::total_issuance().checked_sub(&actual).ok_or(ArithmeticError::Overflow)?;
|
||||
let actual = Self::decrease_balance(who, actual, BestEffort, Expendable, Polite)?;
|
||||
Self::set_total_issuance(Self::total_issuance().saturating_sub(actual));
|
||||
Self::done_shelve(who, actual);
|
||||
Ok(actual)
|
||||
}
|
||||
|
||||
/// Attempt to increase the `asset` balance of `who` by `amount`.
|
||||
///
|
||||
/// Equivalent to `mint_into`, except with an expectation that within the bounds of some
|
||||
/// universal issuance, the total assets `suspend`ed and `resume`d will be equivalent. The
|
||||
/// implementation may be configured such that the total assets suspended may never be less than
|
||||
/// the total assets resumed (which is the invariant for an issuing system), or the reverse
|
||||
/// (which the invariant in a non-issuing system).
|
||||
///
|
||||
/// Because of this expectation, any metadata associated with the asset is expected to survive
|
||||
/// the suspect-resume cycle.
|
||||
fn restore(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
|
||||
Self::total_issuance().checked_add(&amount).ok_or(ArithmeticError::Overflow)?;
|
||||
let actual = Self::increase_balance(who, amount, Exact)?;
|
||||
Self::set_total_issuance(Self::total_issuance().saturating_add(actual));
|
||||
Self::done_restore(who, amount);
|
||||
Ok(actual)
|
||||
}
|
||||
|
||||
/// Transfer funds from one account into another.
|
||||
fn transfer(
|
||||
source: &AccountId,
|
||||
dest: &AccountId,
|
||||
amount: Self::Balance,
|
||||
preservation: Preservation,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let _extra = Self::can_withdraw(source, amount).into_result(preservation != Expendable)?;
|
||||
Self::can_deposit(dest, amount, Extant).into_result()?;
|
||||
Self::decrease_balance(source, amount, BestEffort, preservation, Polite)?;
|
||||
// This should never fail as we checked `can_deposit` earlier. But we do a best-effort
|
||||
// anyway.
|
||||
let _ = Self::increase_balance(dest, amount, BestEffort);
|
||||
Self::done_transfer(source, dest, amount);
|
||||
Ok(amount)
|
||||
}
|
||||
|
||||
/// Simple infallible function to force an account to have a particular balance, good for use
|
||||
/// in tests and benchmarks but not recommended for production code owing to the lack of
|
||||
/// error reporting.
|
||||
///
|
||||
/// Returns the new balance.
|
||||
fn set_balance(who: &AccountId, amount: Self::Balance) -> Self::Balance {
|
||||
let b = Self::balance(who);
|
||||
if b > amount {
|
||||
Self::burn_from(who, b - amount, BestEffort, Force).map(|d| amount.saturating_sub(d))
|
||||
} else {
|
||||
Self::mint_into(who, amount - b).map(|d| amount.saturating_add(d))
|
||||
}
|
||||
.unwrap_or(b)
|
||||
}
|
||||
|
||||
fn done_mint_into(_who: &AccountId, _amount: Self::Balance) {}
|
||||
fn done_burn_from(_who: &AccountId, _amount: Self::Balance) {}
|
||||
fn done_shelve(_who: &AccountId, _amount: Self::Balance) {}
|
||||
fn done_restore(_who: &AccountId, _amount: Self::Balance) {}
|
||||
fn done_transfer(_source: &AccountId, _dest: &AccountId, _amount: Self::Balance) {}
|
||||
}
|
||||
|
||||
/// Simple handler for an imbalance drop which increases the total issuance of the system by the
|
||||
/// imbalance amount. Used for leftover debt.
|
||||
pub struct IncreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
|
||||
impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::Balance>
|
||||
for IncreaseIssuance<AccountId, U>
|
||||
{
|
||||
fn handle(amount: U::Balance) {
|
||||
U::set_total_issuance(U::total_issuance().saturating_add(amount))
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple handler for an imbalance drop which decreases the total issuance of the system by the
|
||||
/// imbalance amount. Used for leftover credit.
|
||||
pub struct DecreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
|
||||
impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::Balance>
|
||||
for DecreaseIssuance<AccountId, U>
|
||||
{
|
||||
fn handle(amount: U::Balance) {
|
||||
U::set_total_issuance(U::total_issuance().saturating_sub(amount))
|
||||
}
|
||||
}
|
||||
|
||||
/// A fungible token class where any creation and deletion of tokens is semi-explicit and where the
|
||||
/// total supply is maintained automatically.
|
||||
///
|
||||
/// This is auto-implemented when a token class has `Unbalanced` implemented.
|
||||
pub trait Balanced<AccountId>: Inspect<AccountId> + Unbalanced<AccountId> {
|
||||
/// The type for managing what happens when an instance of `Debt` is dropped without being used.
|
||||
type OnDropDebt: HandleImbalanceDrop<Self::Balance>;
|
||||
/// The type for managing what happens when an instance of `Credit` is dropped without being
|
||||
/// used.
|
||||
type OnDropCredit: HandleImbalanceDrop<Self::Balance>;
|
||||
|
||||
/// Reduce the total issuance by `amount` and return the according imbalance. The imbalance will
|
||||
/// typically be used to reduce an account by the same amount with e.g. `settle`.
|
||||
///
|
||||
/// This is infallible, but doesn't guarantee that the entire `amount` is burnt, for example
|
||||
/// in the case of underflow.
|
||||
fn rescind(amount: Self::Balance) -> Debt<AccountId, Self> {
|
||||
let old = Self::total_issuance();
|
||||
let new = old.saturating_sub(amount);
|
||||
Self::set_total_issuance(new);
|
||||
let delta = old - new;
|
||||
Self::done_rescind(delta);
|
||||
Imbalance::<Self::Balance, Self::OnDropDebt, Self::OnDropCredit>::new(delta)
|
||||
}
|
||||
|
||||
/// Increase the total issuance by `amount` and return the according imbalance. The imbalance
|
||||
/// will typically be used to increase an account by the same amount with e.g.
|
||||
/// `resolve_into_existing` or `resolve_creating`.
|
||||
///
|
||||
/// This is infallible, but doesn't guarantee that the entire `amount` is issued, for example
|
||||
/// in the case of overflow.
|
||||
fn issue(amount: Self::Balance) -> Credit<AccountId, Self> {
|
||||
let old = Self::total_issuance();
|
||||
let new = old.saturating_add(amount);
|
||||
Self::set_total_issuance(new);
|
||||
let delta = new - old;
|
||||
Self::done_issue(delta);
|
||||
Imbalance::<Self::Balance, Self::OnDropCredit, Self::OnDropDebt>::new(delta)
|
||||
}
|
||||
|
||||
/// Produce a pair of imbalances that cancel each other out exactly.
|
||||
///
|
||||
/// This is just the same as burning and issuing the same amount and has no effect on the
|
||||
/// total issuance.
|
||||
fn pair(amount: Self::Balance) -> (Debt<AccountId, Self>, Credit<AccountId, Self>) {
|
||||
(Self::rescind(amount), Self::issue(amount))
|
||||
}
|
||||
|
||||
/// Mints `value` into the account of `who`, creating it as needed.
|
||||
///
|
||||
/// If `precision` is `BestEffort` and `value` in full could not be minted (e.g. due to
|
||||
/// overflow), then the maximum is minted, up to `value`. If `precision` is `Exact`, then
|
||||
/// exactly `value` must be minted into the account of `who` or the operation will fail with an
|
||||
/// `Err` and nothing will change.
|
||||
///
|
||||
/// If the operation is successful, this will return `Ok` with a `Debt` of the total value
|
||||
/// added to the account.
|
||||
fn deposit(
|
||||
who: &AccountId,
|
||||
value: Self::Balance,
|
||||
precision: Precision,
|
||||
) -> Result<Debt<AccountId, Self>, DispatchError> {
|
||||
let increase = Self::increase_balance(who, value, precision)?;
|
||||
Self::done_deposit(who, increase);
|
||||
Ok(Imbalance::<Self::Balance, Self::OnDropDebt, Self::OnDropCredit>::new(increase))
|
||||
}
|
||||
|
||||
/// Removes `value` balance from `who` account if possible.
|
||||
///
|
||||
/// If `precision` is `BestEffort` and `value` in full could not be removed (e.g. due to
|
||||
/// underflow), then the maximum is removed, up to `value`. If `precision` is `Exact`, then
|
||||
/// exactly `value` must be removed from the account of `who` or the operation will fail with an
|
||||
/// `Err` and nothing will change.
|
||||
///
|
||||
/// If the removal is needed but not possible, then it returns `Err` and nothing is changed.
|
||||
/// If the account needed to be deleted, then slightly more than `value` may be removed from the
|
||||
/// account owning since up to (but not including) minimum balance may also need to be removed.
|
||||
///
|
||||
/// If the operation is successful, this will return `Ok` with a `Credit` of the total value
|
||||
/// removed from the account.
|
||||
fn withdraw(
|
||||
who: &AccountId,
|
||||
value: Self::Balance,
|
||||
precision: Precision,
|
||||
preservation: Preservation,
|
||||
force: Fortitude,
|
||||
) -> Result<Credit<AccountId, Self>, DispatchError> {
|
||||
let decrease = Self::decrease_balance(who, value, precision, preservation, force)?;
|
||||
Self::done_withdraw(who, decrease);
|
||||
Ok(Imbalance::<Self::Balance, Self::OnDropCredit, Self::OnDropDebt>::new(decrease))
|
||||
}
|
||||
|
||||
/// The balance of `who` is increased in order to counter `credit`. If the whole of `credit`
|
||||
/// cannot be countered, then nothing is changed and the original `credit` is returned in an
|
||||
/// `Err`.
|
||||
///
|
||||
/// Please note: If `credit.peek()` is less than `Self::minimum_balance()`, then `who` must
|
||||
/// already exist for this to succeed.
|
||||
fn resolve(
|
||||
who: &AccountId,
|
||||
credit: Credit<AccountId, Self>,
|
||||
) -> Result<(), Credit<AccountId, Self>> {
|
||||
let v = credit.peek();
|
||||
let debt = match Self::deposit(who, v, Exact) {
|
||||
Err(_) => return Err(credit),
|
||||
Ok(d) => d,
|
||||
};
|
||||
let result = credit.offset(debt).try_drop();
|
||||
debug_assert!(result.is_ok(), "ok deposit return must be equal to credit value; qed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The balance of `who` is decreased in order to counter `debt`. If the whole of `debt`
|
||||
/// cannot be countered, then nothing is changed and the original `debt` is returned in an
|
||||
/// `Err`.
|
||||
fn settle(
|
||||
who: &AccountId,
|
||||
debt: Debt<AccountId, Self>,
|
||||
preservation: Preservation,
|
||||
) -> Result<Credit<AccountId, Self>, Debt<AccountId, Self>> {
|
||||
let amount = debt.peek();
|
||||
let credit = match Self::withdraw(who, amount, Exact, preservation, Polite) {
|
||||
Err(_) => return Err(debt),
|
||||
Ok(d) => d,
|
||||
};
|
||||
|
||||
match credit.offset(debt) {
|
||||
SameOrOther::None => Ok(Credit::<AccountId, Self>::zero()),
|
||||
SameOrOther::Same(dust) => Ok(dust),
|
||||
SameOrOther::Other(rest) => {
|
||||
debug_assert!(false, "ok withdraw return must be at least debt value; qed");
|
||||
Err(rest)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn done_rescind(_amount: Self::Balance) {}
|
||||
fn done_issue(_amount: Self::Balance) {}
|
||||
fn done_deposit(_who: &AccountId, _amount: Self::Balance) {}
|
||||
fn done_withdraw(_who: &AccountId, _amount: Self::Balance) {}
|
||||
}
|
||||
@@ -1,332 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The traits for sets of fungible tokens and any associated types.
|
||||
|
||||
use super::{
|
||||
misc::{AssetId, Balance},
|
||||
*,
|
||||
};
|
||||
use crate::dispatch::{DispatchError, DispatchResult};
|
||||
use sp_runtime::traits::Saturating;
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
pub mod approvals;
|
||||
mod balanced;
|
||||
pub mod enumerable;
|
||||
pub use enumerable::InspectEnumerable;
|
||||
pub mod metadata;
|
||||
pub use balanced::{Balanced, Unbalanced};
|
||||
mod imbalance;
|
||||
pub use imbalance::{CreditOf, DebtOf, HandleImbalanceDrop, Imbalance};
|
||||
pub mod roles;
|
||||
|
||||
/// Trait for providing balance-inspection access to a set of named fungible assets.
|
||||
pub trait Inspect<AccountId> {
|
||||
/// Means of identifying one asset class from another.
|
||||
type AssetId: AssetId;
|
||||
|
||||
/// Scalar type for representing balance of an account.
|
||||
type Balance: Balance;
|
||||
|
||||
/// The total amount of issuance in the system.
|
||||
fn total_issuance(asset: Self::AssetId) -> Self::Balance;
|
||||
|
||||
/// The total amount of issuance in the system excluding those which are controlled by the
|
||||
/// system.
|
||||
fn active_issuance(asset: Self::AssetId) -> Self::Balance {
|
||||
Self::total_issuance(asset)
|
||||
}
|
||||
|
||||
/// The minimum balance any single account may have.
|
||||
fn minimum_balance(asset: Self::AssetId) -> Self::Balance;
|
||||
|
||||
/// Get the `asset` balance of `who`.
|
||||
fn balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance;
|
||||
|
||||
/// Get the maximum amount of `asset` that `who` can withdraw/transfer successfully.
|
||||
fn reducible_balance(asset: Self::AssetId, who: &AccountId, keep_alive: bool) -> Self::Balance;
|
||||
|
||||
/// Returns `true` if the `asset` balance of `who` may be increased by `amount`.
|
||||
///
|
||||
/// - `asset`: The asset that should be deposited.
|
||||
/// - `who`: The account of which the balance should be increased by `amount`.
|
||||
/// - `amount`: How much should the balance be increased?
|
||||
/// - `mint`: Will `amount` be minted to deposit it into `account`?
|
||||
fn can_deposit(
|
||||
asset: Self::AssetId,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
mint: bool,
|
||||
) -> DepositConsequence;
|
||||
|
||||
/// Returns `Failed` if the `asset` balance of `who` may not be decreased by `amount`, otherwise
|
||||
/// the consequence.
|
||||
fn can_withdraw(
|
||||
asset: Self::AssetId,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> WithdrawConsequence<Self::Balance>;
|
||||
|
||||
/// Returns `true` if an `asset` exists.
|
||||
fn asset_exists(asset: Self::AssetId) -> bool;
|
||||
}
|
||||
|
||||
/// Trait for reading metadata from a fungible asset.
|
||||
pub trait InspectMetadata<AccountId>: Inspect<AccountId> {
|
||||
/// Return the name of an asset.
|
||||
fn name(asset: &Self::AssetId) -> Vec<u8>;
|
||||
|
||||
/// Return the symbol of an asset.
|
||||
fn symbol(asset: &Self::AssetId) -> Vec<u8>;
|
||||
|
||||
/// Return the decimals of an asset.
|
||||
fn decimals(asset: &Self::AssetId) -> u8;
|
||||
}
|
||||
|
||||
/// Trait for providing a set of named fungible assets which can be created and destroyed.
|
||||
pub trait Mutate<AccountId>: Inspect<AccountId> {
|
||||
/// Attempt to increase the `asset` balance of `who` by `amount`.
|
||||
///
|
||||
/// If not possible then don't do anything. Possible reasons for failure include:
|
||||
/// - Minimum balance not met.
|
||||
/// - Account cannot be created (e.g. because there is no provider reference and/or the asset
|
||||
/// isn't considered worth anything).
|
||||
///
|
||||
/// Since this is an operation which should be possible to take alone, if successful it will
|
||||
/// increase the overall supply of the underlying token.
|
||||
fn mint_into(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> DispatchResult;
|
||||
|
||||
/// Attempt to reduce the `asset` balance of `who` by `amount`.
|
||||
///
|
||||
/// If not possible then don't do anything. Possible reasons for failure include:
|
||||
/// - Less funds in the account than `amount`
|
||||
/// - Liquidity requirements (locks, reservations) prevent the funds from being removed
|
||||
/// - Operation would require destroying the account and it is required to stay alive (e.g.
|
||||
/// because it's providing a needed provider reference).
|
||||
///
|
||||
/// Since this is an operation which should be possible to take alone, if successful it will
|
||||
/// reduce the overall supply of the underlying token.
|
||||
///
|
||||
/// Due to minimum balance requirements, it's possible that the amount withdrawn could be up to
|
||||
/// `Self::minimum_balance() - 1` more than the `amount`. The total amount withdrawn is returned
|
||||
/// in an `Ok` result. This may be safely ignored if you don't mind the overall supply reducing.
|
||||
fn burn_from(
|
||||
asset: Self::AssetId,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Balance, DispatchError>;
|
||||
|
||||
/// Attempt to reduce the `asset` balance of `who` by as much as possible up to `amount`, and
|
||||
/// possibly slightly more due to minimum_balance requirements. If no decrease is possible then
|
||||
/// an `Err` is returned and nothing is changed. If successful, the amount of tokens reduced is
|
||||
/// returned.
|
||||
///
|
||||
/// The default implementation just uses `withdraw` along with `reducible_balance` to ensure
|
||||
/// that is doesn't fail.
|
||||
fn slash(
|
||||
asset: Self::AssetId,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
Self::burn_from(asset, who, Self::reducible_balance(asset, who, false).min(amount))
|
||||
}
|
||||
|
||||
/// Transfer funds from one account into another. The default implementation uses `mint_into`
|
||||
/// and `burn_from` and may generate unwanted events.
|
||||
fn teleport(
|
||||
asset: Self::AssetId,
|
||||
source: &AccountId,
|
||||
dest: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let extra = Self::can_withdraw(asset, &source, amount).into_result()?;
|
||||
// As we first burn and then mint, we don't need to check if `mint` fits into the supply.
|
||||
// If we can withdraw/burn it, we can also mint it again.
|
||||
Self::can_deposit(asset, dest, amount.saturating_add(extra), false).into_result()?;
|
||||
let actual = Self::burn_from(asset, source, amount)?;
|
||||
debug_assert!(
|
||||
actual == amount.saturating_add(extra),
|
||||
"can_withdraw must agree with withdraw; qed"
|
||||
);
|
||||
match Self::mint_into(asset, dest, actual) {
|
||||
Ok(_) => Ok(actual),
|
||||
Err(err) => {
|
||||
debug_assert!(false, "can_deposit returned true previously; qed");
|
||||
// attempt to return the funds back to source
|
||||
let revert = Self::mint_into(asset, source, actual);
|
||||
debug_assert!(revert.is_ok(), "withdrew funds previously; qed");
|
||||
Err(err)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for providing a set of named fungible assets which can only be transferred.
|
||||
pub trait Transfer<AccountId>: Inspect<AccountId> {
|
||||
/// Transfer funds from one account into another.
|
||||
fn transfer(
|
||||
asset: Self::AssetId,
|
||||
source: &AccountId,
|
||||
dest: &AccountId,
|
||||
amount: Self::Balance,
|
||||
keep_alive: bool,
|
||||
) -> Result<Self::Balance, DispatchError>;
|
||||
|
||||
/// Reduce the active issuance by some amount.
|
||||
fn deactivate(_: Self::AssetId, _: Self::Balance) {}
|
||||
|
||||
/// Increase the active issuance by some amount, up to the outstanding amount reduced.
|
||||
fn reactivate(_: Self::AssetId, _: Self::Balance) {}
|
||||
}
|
||||
|
||||
/// Trait for inspecting a set of named fungible assets which can be placed on hold.
|
||||
pub trait InspectHold<AccountId>: Inspect<AccountId> {
|
||||
/// Amount of funds held in hold.
|
||||
fn balance_on_hold(asset: Self::AssetId, who: &AccountId) -> Self::Balance;
|
||||
|
||||
/// Check to see if some `amount` of `asset` may be held on the account of `who`.
|
||||
fn can_hold(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> bool;
|
||||
}
|
||||
|
||||
/// Trait for mutating a set of named fungible assets which can be placed on hold.
|
||||
pub trait MutateHold<AccountId>: InspectHold<AccountId> + Transfer<AccountId> {
|
||||
/// Hold some funds in an account.
|
||||
fn hold(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> DispatchResult;
|
||||
|
||||
/// Release some funds in an account from being on hold.
|
||||
///
|
||||
/// If `best_effort` is `true`, then the amount actually released and returned as the inner
|
||||
/// value of `Ok` may be smaller than the `amount` passed.
|
||||
fn release(
|
||||
asset: Self::AssetId,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
best_effort: bool,
|
||||
) -> Result<Self::Balance, DispatchError>;
|
||||
|
||||
/// Transfer held funds into a destination account.
|
||||
///
|
||||
/// If `on_hold` is `true`, then the destination account must already exist and the assets
|
||||
/// transferred will still be on hold in the destination account. If not, then the destination
|
||||
/// account need not already exist, but must be creatable.
|
||||
///
|
||||
/// If `best_effort` is `true`, then an amount less than `amount` may be transferred without
|
||||
/// error.
|
||||
///
|
||||
/// The actual amount transferred is returned, or `Err` in the case of error and nothing is
|
||||
/// changed.
|
||||
fn transfer_held(
|
||||
asset: Self::AssetId,
|
||||
source: &AccountId,
|
||||
dest: &AccountId,
|
||||
amount: Self::Balance,
|
||||
best_effort: bool,
|
||||
on_hold: bool,
|
||||
) -> Result<Self::Balance, DispatchError>;
|
||||
}
|
||||
|
||||
/// Trait for mutating one of several types of fungible assets which can be held.
|
||||
pub trait BalancedHold<AccountId>: Balanced<AccountId> + MutateHold<AccountId> {
|
||||
/// Release and slash some funds in an account.
|
||||
///
|
||||
/// The resulting imbalance is the first item of the tuple returned.
|
||||
///
|
||||
/// As much funds up to `amount` will be deducted as possible. If this is less than `amount`,
|
||||
/// then a non-zero second item will be returned.
|
||||
fn slash_held(
|
||||
asset: Self::AssetId,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> (CreditOf<AccountId, Self>, Self::Balance);
|
||||
}
|
||||
|
||||
impl<AccountId, T: Balanced<AccountId> + MutateHold<AccountId>> BalancedHold<AccountId> for T {
|
||||
fn slash_held(
|
||||
asset: Self::AssetId,
|
||||
who: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> (CreditOf<AccountId, Self>, Self::Balance) {
|
||||
let actual = match Self::release(asset, who, amount, true) {
|
||||
Ok(x) => x,
|
||||
Err(_) => return (Imbalance::zero(asset), amount),
|
||||
};
|
||||
<Self as fungibles::Balanced<AccountId>>::slash(asset, who, actual)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for providing the ability to create new fungible assets.
|
||||
pub trait Create<AccountId>: Inspect<AccountId> {
|
||||
/// Create a new fungible asset.
|
||||
fn create(
|
||||
id: Self::AssetId,
|
||||
admin: AccountId,
|
||||
is_sufficient: bool,
|
||||
min_balance: Self::Balance,
|
||||
) -> DispatchResult;
|
||||
}
|
||||
|
||||
/// Trait for providing the ability to destroy existing fungible assets.
|
||||
pub trait Destroy<AccountId>: Inspect<AccountId> {
|
||||
/// Start the destruction an existing fungible asset.
|
||||
/// * `id`: The `AssetId` to be destroyed. successfully.
|
||||
/// * `maybe_check_owner`: An optional account id that can be used to authorize the destroy
|
||||
/// command. If not provided, no authorization checks will be performed before destroying
|
||||
/// asset.
|
||||
fn start_destroy(id: Self::AssetId, maybe_check_owner: Option<AccountId>) -> DispatchResult;
|
||||
|
||||
/// Destroy all accounts associated with a given asset.
|
||||
/// `destroy_accounts` should only be called after `start_destroy` has been called, and the
|
||||
/// asset is in a `Destroying` state
|
||||
///
|
||||
/// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset.
|
||||
/// * `max_items`: The maximum number of accounts to be destroyed for a given call of the
|
||||
/// function. This value should be small enough to allow the operation fit into a logical
|
||||
/// block.
|
||||
///
|
||||
/// Response:
|
||||
/// * u32: Total number of approvals which were actually destroyed
|
||||
///
|
||||
/// Due to weight restrictions, this function may need to be called multiple
|
||||
/// times to fully destroy all approvals. It will destroy `max_items` approvals at a
|
||||
/// time.
|
||||
fn destroy_accounts(id: Self::AssetId, max_items: u32) -> Result<u32, DispatchError>;
|
||||
/// Destroy all approvals associated with a given asset up to the `max_items`
|
||||
/// `destroy_approvals` should only be called after `start_destroy` has been called, and the
|
||||
/// asset is in a `Destroying` state
|
||||
///
|
||||
/// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset.
|
||||
/// * `max_items`: The maximum number of accounts to be destroyed for a given call of the
|
||||
/// function. This value should be small enough to allow the operation fit into a logical
|
||||
/// block.
|
||||
///
|
||||
/// Response:
|
||||
/// * u32: Total number of approvals which were actually destroyed
|
||||
///
|
||||
/// Due to weight restrictions, this function may need to be called multiple
|
||||
/// times to fully destroy all approvals. It will destroy `max_items` approvals at a
|
||||
/// time.
|
||||
fn destroy_approvals(id: Self::AssetId, max_items: u32) -> Result<u32, DispatchError>;
|
||||
|
||||
/// Complete destroying asset and unreserve currency.
|
||||
/// `finish_destroy` should only be called after `start_destroy` has been called, and the
|
||||
/// asset is in a `Destroying` state. All accounts or approvals should be destroyed before
|
||||
/// hand.
|
||||
///
|
||||
/// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset.
|
||||
fn finish_destroy(id: Self::AssetId) -> DispatchResult;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user