From c88d83f5d98fd36e2ae7b03d3d199da8b71ae3e2 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 28 Mar 2021 21:01:11 +0200 Subject: [PATCH 01/22] Work so far. --- Cargo.toml | 1 + frame/assets/src/functions.rs | 19 ++++---- frame/assets/src/impl_fungibles.rs | 40 +++++++++++++---- frame/assets/src/lib.rs | 12 +++-- frame/assets/src/mock.rs | 12 ----- frame/assets/src/tests.rs | 1 - frame/assets/src/types.rs | 47 +++++++++----------- frame/balances/src/lib.rs | 3 +- frame/support/src/traits/tokens.rs | 1 + frame/support/src/traits/tokens/fungible.rs | 27 ++++++++++- frame/support/src/traits/tokens/fungibles.rs | 36 ++++++++++++--- frame/support/src/traits/tokens/misc.rs | 31 +++++++++++-- 12 files changed, 157 insertions(+), 73 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 57052a8d38e05..7dd4743333103 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ members = [ "client/transaction-pool", "client/transaction-pool/graph", "frame/assets", + "frame/assets-freezer", "frame/atomic-swap", "frame/aura", "frame/authority-discovery", diff --git a/frame/assets/src/functions.rs b/frame/assets/src/functions.rs index 197b010b6eb87..54c7a779bea5d 100644 --- a/frame/assets/src/functions.rs +++ b/frame/assets/src/functions.rs @@ -58,7 +58,7 @@ impl Pallet { } pub(super) fn dead_account( - what: T::AssetId, + _what: T::AssetId, who: &T::AccountId, d: &mut AssetDetails>, sufficient: bool, @@ -70,7 +70,6 @@ impl Pallet { frame_system::Pallet::::dec_consumers(who); } d.accounts = d.accounts.saturating_sub(1); - T::Freezer::died(what, who) } pub(super) fn can_increase(id: T::AssetId, who: &T::AccountId, amount: T::Balance) -> DepositConsequence { @@ -105,7 +104,7 @@ impl Pallet { id: T::AssetId, who: &T::AccountId, amount: T::Balance, - keep_alive: bool, + f: DecreaseFlags, ) -> WithdrawConsequence { use WithdrawConsequence::*; let details = match Asset::::get(id) { @@ -123,7 +122,7 @@ impl Pallet { return Frozen } if let Some(rest) = account.balance.checked_sub(&amount) { - if let Some(frozen) = T::Freezer::frozen_balance(id, who) { + if let (Some(frozen), false) = (T::Freezer::frozen_balance(id, who), f.ignore_freezer) { match frozen.checked_add(&details.min_balance) { Some(required) if rest < required => return Frozen, None => return Overflow, @@ -133,7 +132,7 @@ impl Pallet { let is_provider = false; let is_required = is_provider && !frame_system::Pallet::::can_dec_provider(who); - let must_keep_alive = keep_alive || is_required; + let must_keep_alive = f.keep_alive || is_required; if rest < details.min_balance { if must_keep_alive { @@ -154,7 +153,7 @@ impl Pallet { pub(super) fn reducible_balance( id: T::AssetId, who: &T::AccountId, - keep_alive: bool, + f: DecreaseFlags, ) -> Result> { let details = match Asset::::get(id) { Some(details) => details, @@ -165,14 +164,14 @@ impl Pallet { let account = Account::::get(id, who); ensure!(!account.is_frozen, Error::::Frozen); - let amount = if let Some(frozen) = T::Freezer::frozen_balance(id, who) { + let amount = if let (Some(frozen), false) = (T::Freezer::frozen_balance(id, who), f.ignore_freezer) { // Frozen balance: account CANNOT be deleted let required = frozen.checked_add(&details.min_balance).ok_or(Error::::Overflow)?; account.balance.saturating_sub(required) } else { let is_provider = false; let is_required = is_provider && !frame_system::Pallet::::can_dec_provider(who); - if keep_alive || is_required { + if f.keep_alive || is_required { // We want to keep the account around. account.balance.saturating_sub(details.min_balance) } else { @@ -204,11 +203,11 @@ impl Pallet { amount: T::Balance, f: DebitFlags, ) -> Result { - let actual = Self::reducible_balance(id, target, f.keep_alive)? + let actual = Self::reducible_balance(id, target, f.into())? .min(amount); ensure!(f.best_effort || actual >= amount, Error::::BalanceLow); - let conseq = Self::can_decrease(id, target, actual, f.keep_alive); + let conseq = Self::can_decrease(id, target, actual, f.into()); let actual = match conseq.into_result() { Ok(dust) => actual.saturating_add(dust), //< guaranteed by reducible_balance Err(e) => { diff --git a/frame/assets/src/impl_fungibles.rs b/frame/assets/src/impl_fungibles.rs index a4cff9b7e9a62..1b06525a2cfdc 100644 --- a/frame/assets/src/impl_fungibles.rs +++ b/frame/assets/src/impl_fungibles.rs @@ -43,7 +43,8 @@ impl fungibles::Inspect<::AccountId> for Pallet who: &::AccountId, keep_alive: bool, ) -> Self::Balance { - Pallet::::reducible_balance(asset, who, keep_alive).unwrap_or(Zero::zero()) + let f = DecreaseFlags { keep_alive, ignore_freezer: false }; + Pallet::::reducible_balance(asset, who, f).unwrap_or(Zero::zero()) } fn can_deposit( @@ -59,7 +60,28 @@ impl fungibles::Inspect<::AccountId> for Pallet who: &::AccountId, amount: Self::Balance, ) -> WithdrawConsequence { - Pallet::::can_decrease(asset, who, amount, false) + let f = DecreaseFlags { keep_alive: false, ignore_freezer: false }; + Pallet::::can_decrease(asset, who, amount, f) + } +} + +impl fungibles::InspectWithoutFreezer<::AccountId> for Pallet { + fn reducible_balance( + asset: Self::AssetId, + who: &::AccountId, + keep_alive: bool, + ) -> Self::Balance { + let f = DecreaseFlags { keep_alive, ignore_freezer: true }; + Pallet::::reducible_balance(asset, who, f).unwrap_or(Zero::zero()) + } + + fn can_withdraw( + asset: Self::AssetId, + who: &::AccountId, + amount: Self::Balance, + ) -> WithdrawConsequence { + let f = DecreaseFlags { keep_alive: false, ignore_freezer: true }; + Pallet::::can_decrease(asset, who, amount, f) } } @@ -80,6 +102,7 @@ impl fungibles::Mutate<::AccountId> for Pallet let f = DebitFlags { keep_alive: false, best_effort: false, + ignore_freezer: false, }; Self::do_burn(asset, who, amount, None, f) } @@ -92,6 +115,7 @@ impl fungibles::Mutate<::AccountId> for Pallet let f = DebitFlags { keep_alive: false, best_effort: true, + ignore_freezer: false, }; Self::do_burn(asset, who, amount, None, f) } @@ -124,26 +148,26 @@ impl fungibles::Unbalanced for Pallet { }); } fn decrease_balance(asset: T::AssetId, who: &T::AccountId, amount: Self::Balance) - -> Result + -> Result { - let f = DebitFlags { keep_alive: false, best_effort: false }; + let f = DebitFlags { keep_alive: false, best_effort: false, ignore_freezer: false }; Self::decrease_balance(asset, who, amount, f, |_, _| Ok(())) } fn decrease_balance_at_most(asset: T::AssetId, who: &T::AccountId, amount: Self::Balance) - -> Self::Balance + -> Self::Balance { - let f = DebitFlags { keep_alive: false, best_effort: true }; + let f = DebitFlags { keep_alive: false, best_effort: true, ignore_freezer: false }; Self::decrease_balance(asset, who, amount, f, |_, _| Ok(())) .unwrap_or(Zero::zero()) } fn increase_balance(asset: T::AssetId, who: &T::AccountId, amount: Self::Balance) - -> Result + -> Result { 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 + -> Self::Balance { match Self::increase_balance(asset, who, amount, |_| Ok(())) { Ok(()) => amount, diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 2a162c2c936b1..1fee57d9591d8 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -146,9 +146,13 @@ use sp_runtime::{ } }; use codec::{Encode, Decode, HasCompact}; -use frame_support::{ensure, dispatch::{DispatchError, DispatchResult}}; -use frame_support::traits::{Currency, ReservableCurrency, BalanceStatus::Reserved, StoredMap}; -use frame_support::traits::tokens::{WithdrawConsequence, DepositConsequence, fungibles}; +use frame_support::{ + ensure, dispatch::{DispatchError, DispatchResult}, traits::{ + Currency, ReservableCurrency, BalanceStatus::Reserved, StoredMap, tokens::{ + WithdrawConsequence, DepositConsequence, fungibles, FrozenBalance, + }, + }, +}; use frame_system::Config as SystemConfig; pub use weights::WeightInfo; @@ -550,7 +554,7 @@ pub mod pallet { let origin = ensure_signed(origin)?; let who = T::Lookup::lookup(who)?; - let f = DebitFlags { keep_alive: false, best_effort: true }; + let f = DebitFlags { keep_alive: false, best_effort: true, ignore_freezer: false }; let burned = Self::do_burn(id, &who, amount, Some(origin), f)?; Self::deposit_event(Event::Burned(id, who, burned)); Ok(()) diff --git a/frame/assets/src/mock.rs b/frame/assets/src/mock.rs index 26ff938512a2f..e41029612824e 100644 --- a/frame/assets/src/mock.rs +++ b/frame/assets/src/mock.rs @@ -108,13 +108,8 @@ impl Config for Test { use std::cell::RefCell; use std::collections::HashMap; -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub(crate) enum Hook { - Died(u32, u64), -} thread_local! { static FROZEN: RefCell> = RefCell::new(Default::default()); - static HOOKS: RefCell> = RefCell::new(Default::default()); } pub struct TestFreezer; @@ -122,10 +117,6 @@ impl FrozenBalance for TestFreezer { fn frozen_balance(asset: u32, who: &u64) -> Option { FROZEN.with(|f| f.borrow().get(&(asset, who.clone())).cloned()) } - - fn died(asset: u32, who: &u64) { - HOOKS.with(|h| h.borrow_mut().push(Hook::Died(asset, who.clone()))); - } } pub(crate) fn set_frozen_balance(asset: u32, who: u64, amount: u64) { @@ -134,9 +125,6 @@ pub(crate) fn set_frozen_balance(asset: u32, who: u64, amount: u64) { pub(crate) fn clear_frozen_balance(asset: u32, who: u64) { FROZEN.with(|f| f.borrow_mut().remove(&(asset, who))); } -pub(crate) fn hooks() -> Vec { - HOOKS.with(|h| h.borrow().clone()) -} pub(crate) fn new_test_ext() -> sp_io::TestExternalities { let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); diff --git a/frame/assets/src/tests.rs b/frame/assets/src/tests.rs index 953164a0b9380..0bbdb6ae1445d 100644 --- a/frame/assets/src/tests.rs +++ b/frame/assets/src/tests.rs @@ -529,7 +529,6 @@ fn freezer_should_work() { // and if we clear it, we can remove the account completely. clear_frozen_balance(0, 1); assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); - assert_eq!(hooks(), vec![Hook::Died(0, 1)]); }); } diff --git a/frame/assets/src/types.rs b/frame/assets/src/types.rs index 7e0e235b1b7e6..1b0e71893abf7 100644 --- a/frame/assets/src/types.rs +++ b/frame/assets/src/types.rs @@ -125,32 +125,6 @@ pub struct DestroyWitness { pub(super) approvals: u32, } -/// Trait for allowing a minimum balance on the account to be specified, beyond the -/// `minimum_balance` of the asset. This is additive - the `minimum_balance` of the asset must be -/// met *and then* anything here in addition. -pub trait FrozenBalance { - /// Return the frozen balance. Under normal behaviour, this amount should always be - /// withdrawable. - /// - /// In reality, the balance of every account must be at least the sum of this (if `Some`) and - /// the asset's minimum_balance, since there may be complications to destroying an asset's - /// account completely. - /// - /// If `None` is returned, then nothing special is enforced. - /// - /// If any operation ever breaks this requirement (which will only happen through some sort of - /// privileged intervention), then `melted` is called to do any cleanup. - fn frozen_balance(asset: AssetId, who: &AccountId) -> Option; - - /// Called when an account has been removed. - fn died(asset: AssetId, who: &AccountId); -} - -impl FrozenBalance for () { - fn frozen_balance(_: AssetId, _: &AccountId) -> Option { None } - fn died(_: AssetId, _: &AccountId) {} -} - #[derive(Copy, Clone, PartialEq, Eq)] pub(super) struct TransferFlags { /// The debited account must stay alive at the end of the operation; an error is returned if @@ -174,6 +148,17 @@ pub(super) struct DebitFlags { /// successful. If `false`, then the amount debited will always be at least the amount /// specified. pub(super) best_effort: bool, + /// Ignore the freezer. Don't set this to true unless you actually are the underlying freezer. + pub(super) ignore_freezer: bool, +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub(super) struct DecreaseFlags { + /// The debited account must stay alive at the end of the operation; an error is returned if + /// this cannot be achieved legally. + pub(super) keep_alive: bool, + /// Ignore the freezer. Don't set this to true unless you actually are the underlying freezer. + pub(super) ignore_freezer: bool, } impl From for DebitFlags { @@ -181,6 +166,16 @@ impl From for DebitFlags { Self { keep_alive: f.keep_alive, best_effort: f.best_effort, + ignore_freezer: false, + } + } +} + +impl From for DecreaseFlags { + fn from(f: DebitFlags) -> Self { + Self { + keep_alive: f.keep_alive, + ignore_freezer: f.ignore_freezer, } } } diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index 35841c504adf9..89420e4fe99bd 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -954,7 +954,8 @@ impl, I: 'static> fungible::Inspect for Pallet fn can_deposit(who: &T::AccountId, amount: Self::Balance) -> DepositConsequence { Self::deposit_consequence(who, amount, &Self::account(who)) } - fn can_withdraw(who: &T::AccountId, amount: Self::Balance) -> WithdrawConsequence { + fn can_withdraw(who: &T::AccountId, amount: Self::Balance) -> WithdrawConsequence + { Self::withdraw_consequence(who, amount, &Self::account(who)) } } diff --git a/frame/support/src/traits/tokens.rs b/frame/support/src/traits/tokens.rs index 82af5dbade8f7..2007627d58c74 100644 --- a/frame/support/src/traits/tokens.rs +++ b/frame/support/src/traits/tokens.rs @@ -24,5 +24,6 @@ pub mod imbalance; mod misc; pub use misc::{ WithdrawConsequence, DepositConsequence, ExistenceRequirement, BalanceStatus, WithdrawReasons, + FrozenBalance, }; pub use imbalance::Imbalance; diff --git a/frame/support/src/traits/tokens/fungible.rs b/frame/support/src/traits/tokens/fungible.rs index 5472212aaa65e..432a5da0dc3b9 100644 --- a/frame/support/src/traits/tokens/fungible.rs +++ b/frame/support/src/traits/tokens/fungible.rs @@ -53,6 +53,16 @@ pub trait Inspect { fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence; } +/// Trait for providing balance-inspection access to a set of named fungible assets, ignoring any +/// per-balance freezing mechanism. +pub trait InspectWithoutFreezer: Inspect { + /// Get the maximum amount of `asset` that `who` can withdraw/transfer successfully. + fn reducible_balance(who: &AccountId, keep_alive: bool) -> Self::Balance; + + /// Returns `true` if the `asset` balance of `who` may be increased by `amount`. + fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence; +} + /// Trait for providing an ERC-20 style fungible asset. pub trait Mutate: Inspect { /// Increase the balance of `who` by exactly `amount`, minting new tokens. If that isn't @@ -119,7 +129,7 @@ pub trait InspectHold: Inspect { } /// Trait for mutating a fungible asset which can be reserved. -pub trait MutateHold: InspectHold + Transfer { +pub trait MutateHold: Inspect { /// Hold some funds in an account. fn hold(who: &AccountId, amount: Self::Balance) -> DispatchResult; @@ -153,7 +163,7 @@ pub trait MutateHold: InspectHold + Transfer { } /// Trait for slashing a fungible asset which can be reserved. -pub trait BalancedHold: Balanced + MutateHold { +pub trait BalancedHold: Balanced { /// Reduce the balance of some funds on hold in an account. /// /// The resulting imbalance is the first item of the tuple returned. @@ -215,6 +225,19 @@ impl< } } +impl< + F: fungibles::InspectWithoutFreezer, + A: Get<>::AssetId>, + AccountId, +> InspectWithoutFreezer for ItemOf { + fn reducible_balance(who: &AccountId, keep_alive: bool) -> Self::Balance { + >::reducible_balance(A::get(), who, keep_alive) + } + fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence { + >::can_withdraw(A::get(), who, amount) + } +} + impl< F: fungibles::Mutate, A: Get<>::AssetId>, diff --git a/frame/support/src/traits/tokens/fungibles.rs b/frame/support/src/traits/tokens/fungibles.rs index 490f28dfb453a..b2db85f2af8e8 100644 --- a/frame/support/src/traits/tokens/fungibles.rs +++ b/frame/support/src/traits/tokens/fungibles.rs @@ -45,11 +45,18 @@ pub trait Inspect { 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; + 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`. - fn can_deposit(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) - -> DepositConsequence; + fn can_deposit( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> DepositConsequence; /// Returns `Failed` if the `asset` balance of `who` may not be decreased by `amount`, otherwise /// the consequence. @@ -60,6 +67,24 @@ pub trait Inspect { ) -> WithdrawConsequence; } +/// Trait for providing balance-inspection access to a set of named fungible assets, ignoring any +/// per-balance freezing mechanism. +pub trait InspectWithoutFreezer: Inspect { + /// 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`. + fn can_withdraw( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> WithdrawConsequence; +} + /// Trait for providing a set of named fungible assets which can be created and destroyed. pub trait Mutate: Inspect { /// Attempt to increase the `asset` balance of `who` by `amount`. @@ -136,6 +161,7 @@ pub trait Transfer: Inspect { source: &AccountId, dest: &AccountId, amount: Self::Balance, +//TODO: best_effort: bool, keep_alive: bool, ) -> Result; } @@ -150,7 +176,7 @@ pub trait InspectHold: Inspect { } /// Trait for mutating a set of named fungible assets which can be placed on hold. -pub trait MutateHold: InspectHold + Transfer { +pub trait MutateHold: Inspect { /// Hold some funds in an account. fn hold(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> DispatchResult; @@ -183,7 +209,7 @@ pub trait MutateHold: InspectHold + Transfer { } /// Trait for mutating one of several types of fungible assets which can be held. -pub trait BalancedHold: Balanced + MutateHold { +pub trait BalancedHold: Balanced { /// Release and slash some funds in an account. /// /// The resulting imbalance is the first item of the tuple returned. diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index 02f7ba384bd00..827edda5ea51f 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -17,6 +17,7 @@ //! Miscellaneous types. +use sp_std::fmt::Debug; use codec::{Encode, Decode, FullCodec}; use sp_core::RuntimeDebug; use sp_arithmetic::traits::{Zero, AtLeast32BitUnsigned}; @@ -123,6 +124,28 @@ pub enum BalanceStatus { Reserved, } +/// Trait for allowing a minimum balance on the account to be specified, beyond the +/// `minimum_balance` of the asset. This is additive - the `minimum_balance` of the asset must be +/// met *and then* anything here in addition. +pub trait FrozenBalance { + /// Return the frozen balance. Under normal behaviour, this amount should always be + /// withdrawable. + /// + /// In reality, the balance of every account must be at least the sum of this (if `Some`) and + /// the asset's minimum_balance, since there may be complications to destroying an asset's + /// account completely. + /// + /// If `None` is returned, then nothing special is enforced. + /// + /// If any operation ever breaks this requirement (which will only happen through some sort of + /// privileged intervention), then `melted` is called to do any cleanup. + fn frozen_balance(asset: AssetId, who: &AccountId) -> Option; +} + +impl FrozenBalance for () { + fn frozen_balance(_: AssetId, _: &AccountId) -> Option { None } +} + bitflags::bitflags! { /// Reasons for moving funds out of an account. #[derive(Encode, Decode)] @@ -160,9 +183,9 @@ impl WithdrawReasons { } /// Simple amalgamation trait to collect together properties for an AssetId under one roof. -pub trait AssetId: FullCodec + Copy + Default + Eq + PartialEq {} -impl AssetId for T {} +pub trait AssetId: FullCodec + Copy + Default + Eq + PartialEq + Debug {} +impl AssetId for T {} /// Simple amalgamation trait to collect together properties for a Balance under one roof. -pub trait Balance: AtLeast32BitUnsigned + FullCodec + Copy + Default {} -impl Balance for T {} +pub trait Balance: AtLeast32BitUnsigned + FullCodec + Copy + Default + Debug {} +impl Balance for T {} From 0f63c240f5d455a44781a819ee3844fdb37aa77d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 28 Mar 2021 21:03:05 +0200 Subject: [PATCH 02/22] Work so far --- frame/assets-freezer/Cargo.toml | 50 +++ frame/assets-freezer/README.md | 21 + frame/assets-freezer/src/benchmarking.rs | 429 ++++++++++++++++++ frame/assets-freezer/src/lib.rs | 255 +++++++++++ frame/assets-freezer/src/mock.rs | 151 +++++++ frame/assets-freezer/src/tests.rs | 547 +++++++++++++++++++++++ frame/assets-freezer/src/weights.rs | 339 ++++++++++++++ 7 files changed, 1792 insertions(+) create mode 100644 frame/assets-freezer/Cargo.toml create mode 100644 frame/assets-freezer/README.md create mode 100644 frame/assets-freezer/src/benchmarking.rs create mode 100644 frame/assets-freezer/src/lib.rs create mode 100644 frame/assets-freezer/src/mock.rs create mode 100644 frame/assets-freezer/src/tests.rs create mode 100644 frame/assets-freezer/src/weights.rs diff --git a/frame/assets-freezer/Cargo.toml b/frame/assets-freezer/Cargo.toml new file mode 100644 index 0000000000000..d24e51d743033 --- /dev/null +++ b/frame/assets-freezer/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "pallet-assets-freezer" +version = "3.0.0" +authors = ["Parity Technologies "] +edition = "2018" +license = "Apache-2.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "Extension pallet for managing frozen assets" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +serde = { version = "1.0.101", optional = true } +codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } +sp-std = { version = "3.0.0", default-features = false, path = "../../primitives/std" } +# Needed for various traits. In our case, `OnFinalize`. +sp-runtime = { version = "3.0.0", default-features = false, path = "../../primitives/runtime" } +# Needed for type-safe access to storage DB. +frame-support = { version = "3.0.0", default-features = false, path = "../support" } +# `system` module provides us with all sorts of useful stuff and macros depend on it being around. +frame-system = { version = "3.0.0", default-features = false, path = "../system" } +frame-benchmarking = { version = "3.1.0", default-features = false, path = "../benchmarking", optional = true } + +[dev-dependencies] +sp-core = { version = "3.0.0", path = "../../primitives/core" } +sp-std = { version = "3.0.0", path = "../../primitives/std" } +sp-io = { version = "3.0.0", path = "../../primitives/io" } +pallet-assets = { version = "3.0.0", default-features = false, path = "../assets" } + +[features] +default = ["std"] +std = [ + "serde", + "codec/std", + "sp-std/std", + "sp-runtime/std", + "frame-support/std", + "frame-system/std", + "frame-benchmarking/std", + "pallet-assets/std", +] +runtime-benchmarks = [ + "frame-benchmarking", + "sp-runtime/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/assets-freezer/README.md b/frame/assets-freezer/README.md new file mode 100644 index 0000000000000..94b70026970ca --- /dev/null +++ b/frame/assets-freezer/README.md @@ -0,0 +1,21 @@ +# Assets Freezer Pallet + +## Overview +### Terminology +### Goals + +## Interface +### Dispatchable Functions +### Public Functions + +## Usage +### Prerequisites +### Simple Code Snippet + +## Assumptions + +## Related Modules + +* [`Assets`](https://docs.rs/frame-support/latest/pallet_assets/) + +License: Apache-2.0 diff --git a/frame/assets-freezer/src/benchmarking.rs b/frame/assets-freezer/src/benchmarking.rs new file mode 100644 index 0000000000000..227d45623d688 --- /dev/null +++ b/frame/assets-freezer/src/benchmarking.rs @@ -0,0 +1,429 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2021 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. + +//! Assets pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use sp_std::prelude::*; +use super::*; +use sp_runtime::traits::Bounded; +use frame_system::RawOrigin as SystemOrigin; +use frame_benchmarking::{ + benchmarks, account, whitelisted_caller, whitelist_account, impl_benchmark_test_suite +}; +use frame_support::traits::Get; +use frame_support::{traits::EnsureOrigin, dispatch::UnfilteredDispatchable}; + +use crate::Pallet as Assets; + +const SEED: u32 = 0; + +fn create_default_asset(is_sufficient: bool) + -> (T::AccountId, ::Source) +{ + let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + let root = SystemOrigin::Root.into(); + assert!(Assets::::force_create( + root, + Default::default(), + caller_lookup.clone(), + is_sufficient, + 1u32.into(), + ).is_ok()); + (caller, caller_lookup) +} + +fn create_default_minted_asset(is_sufficient: bool, amount: T::Balance) + -> (T::AccountId, ::Source) +{ + let (caller, caller_lookup) = create_default_asset::(is_sufficient); + if !is_sufficient { + T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance()); + } + assert!(Assets::::mint( + SystemOrigin::Signed(caller.clone()).into(), + Default::default(), + caller_lookup.clone(), + amount, + ).is_ok()); + (caller, caller_lookup) +} + +fn swap_is_sufficient(s: &mut bool) { + Asset::::mutate(&T::AssetId::default(), |maybe_a| + if let Some(ref mut a) = maybe_a { sp_std::mem::swap(s, &mut a.is_sufficient) } + ); +} + +fn add_consumers(minter: T::AccountId, n: u32) { + let origin = SystemOrigin::Signed(minter); + let mut s = false; + swap_is_sufficient::(&mut s); + for i in 0..n { + let target = account("consumer", i, SEED); + T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); + let target_lookup = T::Lookup::unlookup(target); + assert!(Assets::::mint(origin.clone().into(), Default::default(), target_lookup, 100u32.into()).is_ok()); + } + swap_is_sufficient::(&mut s); +} + +fn add_sufficients(minter: T::AccountId, n: u32) { + let origin = SystemOrigin::Signed(minter); + let mut s = true; + swap_is_sufficient::(&mut s); + for i in 0..n { + let target = account("sufficient", i, SEED); + let target_lookup = T::Lookup::unlookup(target); + assert!(Assets::::mint(origin.clone().into(), Default::default(), target_lookup, 100u32.into()).is_ok()); + } + swap_is_sufficient::(&mut s); +} + +fn add_approvals(minter: T::AccountId, n: u32) { + T::Currency::deposit_creating(&minter, T::ApprovalDeposit::get() * n.into()); + let minter_lookup = T::Lookup::unlookup(minter.clone()); + let origin = SystemOrigin::Signed(minter); + Assets::::mint( + origin.clone().into(), + Default::default(), + minter_lookup, + (100 * (n + 1)).into(), + ).unwrap(); + for i in 0..n { + let target = account("approval", i, SEED); + T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); + let target_lookup = T::Lookup::unlookup(target); + Assets::::approve_transfer( + origin.clone().into(), + Default::default(), + target_lookup, + 100u32.into(), + ).unwrap(); + } +} + +fn assert_last_event(generic_event: ::Event) { + let events = frame_system::Pallet::::events(); + let system_event: ::Event = generic_event.into(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + +fn assert_event(generic_event: ::Event) { + let system_event: ::Event = generic_event.into(); + let events = frame_system::Pallet::::events(); + assert!(events.iter().any(|event_record| { + matches!(&event_record, frame_system::EventRecord { event, .. } if &system_event == event) + })); +} + +benchmarks! { + create { + let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); + }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, 1u32.into()) + verify { + assert_last_event::(Event::Created(Default::default(), caller.clone(), caller).into()); + } + + force_create { + let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + }: _(SystemOrigin::Root, Default::default(), caller_lookup, true, 1u32.into()) + verify { + assert_last_event::(Event::ForceCreated(Default::default(), caller).into()); + } + + destroy { + let c in 0 .. 5_000; + let s in 0 .. 5_000; + let a in 0 .. 5_00; + let (caller, _) = create_default_asset::(true); + add_consumers::(caller.clone(), c); + add_sufficients::(caller.clone(), s); + add_approvals::(caller.clone(), a); + let witness = Asset::::get(T::AssetId::default()).unwrap().destroy_witness(); + }: _(SystemOrigin::Signed(caller), Default::default(), witness) + verify { + assert_last_event::(Event::Destroyed(Default::default()).into()); + } + + mint { + let (caller, caller_lookup) = create_default_asset::(true); + let amount = T::Balance::from(100u32); + }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, amount) + verify { + assert_last_event::(Event::Issued(Default::default(), caller, amount).into()); + } + + burn { + let amount = T::Balance::from(100u32); + let (caller, caller_lookup) = create_default_minted_asset::(true, amount); + }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, amount) + verify { + assert_last_event::(Event::Burned(Default::default(), caller, amount).into()); + } + + transfer { + let amount = T::Balance::from(100u32); + let (caller, caller_lookup) = create_default_minted_asset::(true, amount); + let target: T::AccountId = account("target", 0, SEED); + let target_lookup = T::Lookup::unlookup(target.clone()); + }: _(SystemOrigin::Signed(caller.clone()), Default::default(), target_lookup, amount) + verify { + assert_last_event::(Event::Transferred(Default::default(), caller, target, amount).into()); + } + + transfer_keep_alive { + let mint_amount = T::Balance::from(200u32); + let amount = T::Balance::from(100u32); + let (caller, caller_lookup) = create_default_minted_asset::(true, mint_amount); + let target: T::AccountId = account("target", 0, SEED); + let target_lookup = T::Lookup::unlookup(target.clone()); + }: _(SystemOrigin::Signed(caller.clone()), Default::default(), target_lookup, amount) + verify { + assert!(frame_system::Pallet::::account_exists(&caller)); + assert_last_event::(Event::Transferred(Default::default(), caller, target, amount).into()); + } + + force_transfer { + let amount = T::Balance::from(100u32); + let (caller, caller_lookup) = create_default_minted_asset::(true, amount); + let target: T::AccountId = account("target", 0, SEED); + let target_lookup = T::Lookup::unlookup(target.clone()); + }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, target_lookup, amount) + verify { + assert_last_event::( + Event::Transferred(Default::default(), caller, target, amount).into() + ); + } + + freeze { + let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); + }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup) + verify { + assert_last_event::(Event::Frozen(Default::default(), caller).into()); + } + + thaw { + let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); + Assets::::freeze( + SystemOrigin::Signed(caller.clone()).into(), + Default::default(), + caller_lookup.clone(), + )?; + }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup) + verify { + assert_last_event::(Event::Thawed(Default::default(), caller).into()); + } + + freeze_asset { + let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); + }: _(SystemOrigin::Signed(caller.clone()), Default::default()) + verify { + assert_last_event::(Event::AssetFrozen(Default::default()).into()); + } + + thaw_asset { + let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); + Assets::::freeze_asset( + SystemOrigin::Signed(caller.clone()).into(), + Default::default(), + )?; + }: _(SystemOrigin::Signed(caller.clone()), Default::default()) + verify { + assert_last_event::(Event::AssetThawed(Default::default()).into()); + } + + transfer_ownership { + let (caller, _) = create_default_asset::(true); + let target: T::AccountId = account("target", 0, SEED); + let target_lookup = T::Lookup::unlookup(target.clone()); + }: _(SystemOrigin::Signed(caller), Default::default(), target_lookup) + verify { + assert_last_event::(Event::OwnerChanged(Default::default(), target).into()); + } + + set_team { + let (caller, _) = create_default_asset::(true); + let target0 = T::Lookup::unlookup(account("target", 0, SEED)); + let target1 = T::Lookup::unlookup(account("target", 1, SEED)); + let target2 = T::Lookup::unlookup(account("target", 2, SEED)); + }: _(SystemOrigin::Signed(caller), Default::default(), target0.clone(), target1.clone(), target2.clone()) + verify { + assert_last_event::(Event::TeamChanged( + Default::default(), + account("target", 0, SEED), + account("target", 1, SEED), + account("target", 2, SEED), + ).into()); + } + + set_metadata { + let n in 0 .. T::StringLimit::get(); + let s in 0 .. T::StringLimit::get(); + + let name = vec![0u8; n as usize]; + let symbol = vec![0u8; s as usize]; + let decimals = 12; + + let (caller, _) = create_default_asset::(true); + T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); + }: _(SystemOrigin::Signed(caller), Default::default(), name.clone(), symbol.clone(), decimals) + verify { + let id = Default::default(); + assert_last_event::(Event::MetadataSet(id, name, symbol, decimals, false).into()); + } + + clear_metadata { + let (caller, _) = create_default_asset::(true); + T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); + let dummy = vec![0u8; T::StringLimit::get() as usize]; + let origin = SystemOrigin::Signed(caller.clone()).into(); + Assets::::set_metadata(origin, Default::default(), dummy.clone(), dummy, 12)?; + }: _(SystemOrigin::Signed(caller), Default::default()) + verify { + assert_last_event::(Event::MetadataCleared(Default::default()).into()); + } + + force_set_metadata { + let n in 0 .. T::StringLimit::get(); + let s in 0 .. T::StringLimit::get(); + + let name = vec![0u8; n as usize]; + let symbol = vec![0u8; s as usize]; + let decimals = 12; + + create_default_asset::(true); + + let origin = T::ForceOrigin::successful_origin(); + let call = Call::::force_set_metadata( + Default::default(), + name.clone(), + symbol.clone(), + decimals, + false, + ); + }: { call.dispatch_bypass_filter(origin)? } + verify { + let id = Default::default(); + assert_last_event::(Event::MetadataSet(id, name, symbol, decimals, false).into()); + } + + force_clear_metadata { + let (caller, _) = create_default_asset::(true); + T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); + let dummy = vec![0u8; T::StringLimit::get() as usize]; + let origin = SystemOrigin::Signed(caller.clone()).into(); + Assets::::set_metadata(origin, Default::default(), dummy.clone(), dummy, 12)?; + + let origin = T::ForceOrigin::successful_origin(); + let call = Call::::force_clear_metadata(Default::default()); + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_last_event::(Event::MetadataCleared(Default::default()).into()); + } + + force_asset_status { + let (caller, caller_lookup) = create_default_asset::(true); + + let origin = T::ForceOrigin::successful_origin(); + let call = Call::::force_asset_status( + Default::default(), + caller_lookup.clone(), + caller_lookup.clone(), + caller_lookup.clone(), + caller_lookup.clone(), + 100u32.into(), + true, + false, + ); + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_last_event::(Event::AssetStatusChanged(Default::default()).into()); + } + + approve_transfer { + let (caller, _) = create_default_minted_asset::(true, 100u32.into()); + T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); + + let id = Default::default(); + let delegate: T::AccountId = account("delegate", 0, SEED); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + let amount = 100u32.into(); + }: _(SystemOrigin::Signed(caller.clone()), id, delegate_lookup, amount) + verify { + assert_last_event::(Event::ApprovedTransfer(id, caller, delegate, amount).into()); + } + + transfer_approved { + let (owner, owner_lookup) = create_default_minted_asset::(true, 100u32.into()); + T::Currency::make_free_balance_be(&owner, DepositBalanceOf::::max_value()); + + let id = Default::default(); + let delegate: T::AccountId = account("delegate", 0, SEED); + whitelist_account!(delegate); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + let amount = 100u32.into(); + let origin = SystemOrigin::Signed(owner.clone()).into(); + Assets::::approve_transfer(origin, id, delegate_lookup.clone(), amount)?; + + let dest: T::AccountId = account("dest", 0, SEED); + let dest_lookup = T::Lookup::unlookup(dest.clone()); + }: _(SystemOrigin::Signed(delegate.clone()), id, owner_lookup, dest_lookup, amount) + verify { + assert!(T::Currency::reserved_balance(&owner).is_zero()); + assert_event::(Event::Transferred(id, owner, dest, amount).into()); + } + + cancel_approval { + let (caller, _) = create_default_minted_asset::(true, 100u32.into()); + T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); + + let id = Default::default(); + let delegate: T::AccountId = account("delegate", 0, SEED); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + let amount = 100u32.into(); + let origin = SystemOrigin::Signed(caller.clone()).into(); + Assets::::approve_transfer(origin, id, delegate_lookup.clone(), amount)?; + }: _(SystemOrigin::Signed(caller.clone()), id, delegate_lookup) + verify { + assert_last_event::(Event::ApprovalCancelled(id, caller, delegate).into()); + } + + force_cancel_approval { + let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); + T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); + + let id = Default::default(); + let delegate: T::AccountId = account("delegate", 0, SEED); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + let amount = 100u32.into(); + let origin = SystemOrigin::Signed(caller.clone()).into(); + Assets::::approve_transfer(origin, id, delegate_lookup.clone(), amount)?; + }: _(SystemOrigin::Signed(caller.clone()), id, caller_lookup, delegate_lookup) + verify { + assert_last_event::(Event::ApprovalCancelled(id, caller, delegate).into()); + } +} + +impl_benchmark_test_suite!(Assets, crate::mock::new_test_ext(), crate::mock::Test); diff --git a/frame/assets-freezer/src/lib.rs b/frame/assets-freezer/src/lib.rs new file mode 100644 index 0000000000000..3ad9d29a652e6 --- /dev/null +++ b/frame/assets-freezer/src/lib.rs @@ -0,0 +1,255 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2021 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. + +//! # Assets Freezer Pallet +//! +//! An extension pallet for use with the Assets pallet for allowing funds to be locked and reserved. + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] +/* +pub mod weights; +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +#[cfg(test)] +pub mod mock; +#[cfg(test)] +mod tests; +*/ +use sp_std::prelude::*; +use sp_runtime::{ + RuntimeDebug, TokenError, traits::{ + AtLeast32BitUnsigned, Zero, StaticLookup, Saturating, CheckedSub, CheckedAdd, + StoredMapError, + } +}; +use codec::{Encode, Decode, HasCompact}; +use frame_support::{ensure, dispatch::{DispatchError, DispatchResult}}; +use frame_support::traits::{Currency, ReservableCurrency, BalanceStatus::Reserved, StoredMap}; +use frame_support::traits::tokens::{WithdrawConsequence, DepositConsequence, fungibles, FrozenBalance}; +use frame_system::Config as SystemConfig; + +//pub use weights::WeightInfo; +pub use pallet::*; + +type BalanceOf = <::Assets as fungibles::Inspect<::AccountId>>::Balance; +type AssetIdOf = <::Assets as fungibles::Inspect<::AccountId>>::AssetId; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::{ + dispatch::DispatchResult, + pallet_prelude::*, + }; + use frame_system::pallet_prelude::*; + use super::*; + + /// The information concerning our freezing. + #[derive(Eq, PartialEq, Clone, Encode, Decode, RuntimeDebug, Default)] + pub struct FreezeData { + /// The amount of funds that have been reserved. The actual amount of funds held in reserve + /// (and thus guaranteed of being unreserved) is this amount less `melted`. + /// + /// If this `is_zero`, then the account may be deleted. If it is non-zero, then the assets + /// pallet will attempt to keep the account alive by retaining the `minimum_balance` *plus* + /// this number of funds in it. + pub(super) reserved: Balance, + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + /// The module configuration trait. + pub trait Config: frame_system::Config { + /// The overarching event type. + type Event: From> + IsType<::Event>; + + /// The fungibles trait impl whose assets this reserves. + type Assets: fungibles::Transfer + + fungibles::Inspect; + + fungibles::InspectWithoutFreezer; + + /// Place to store the fast-access freeze data for the given asset/account. + type Store: StoredMap<(AssetIdOf, Self::AccountId), FreezeData>>; + +// /// Weight information for extrinsics in this pallet. +// type WeightInfo: WeightInfo; + } + + // + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + #[pallet::metadata(T::AccountId = "AccountId", BalanceOf = "Balance", AssetIdOf = "AssetId")] + pub enum Event { + /// An asset has been reserved. + /// \[asset, who, amount\] + Held(AssetIdOf, T::AccountId, BalanceOf), + /// An asset has been unreserved. + /// \[asset, who, amount\] + Released(AssetIdOf, T::AccountId, BalanceOf), + } + + // No new errors? + #[pallet::error] + pub enum Error { + /// The origin account is frozen. + Frozen, + } + + // No hooks. + #[pallet::hooks] + impl Hooks> for Pallet {} + + // Only admin calls. + #[pallet::call] + impl Pallet {} +} + +// The main implementation block for the module. +impl Pallet { +} + +impl FrozenBalance, T::AccountId, BalanceOf> for Pallet { + fn frozen_balance(id: AssetIdOf, who: &T::AccountId) -> Option> { + let f = T::Store::get(&(id, who.clone())); + if f.reserved.is_zero() { None } else { Some(f.reserved) } + } + fn died(_: AssetIdOf, _: &T::AccountId) { + // Eventually need to remove lock named reserve/lock info. + } +} + +impl fungibles::Inspect<::AccountId> for Pallet { + type AssetId = AssetIdOf; + type Balance = BalanceOf; + fn total_issuance(asset: AssetIdOf) -> BalanceOf { + T::Assets::total_issuance(asset) + } + fn minimum_balance(asset: AssetIdOf) -> BalanceOf { + T::Assets::minimum_balance(asset) + } + fn balance(asset: AssetIdOf, who: &T::AccountId) -> BalanceOf { + T::Assets::balance(asset, who) + } + fn reducible_balance(asset: AssetIdOf, who: &T::AccountId, keep_alive: bool) -> BalanceOf { + T::Assets::reducible_balance(asset, who, keep_alive) + } + fn can_deposit(asset: AssetIdOf, who: &T::AccountId, amount: BalanceOf) + -> DepositConsequence + { + T::Assets::can_deposit(asset, who, amount) + } + fn can_withdraw( + asset: AssetIdOf, + who: &T::AccountId, + amount: BalanceOf, + ) -> WithdrawConsequence> { + T::Assets::can_withdraw(asset, who, amount) + } +} + +impl fungibles::Transfer<::AccountId> for Pallet { + +} + +impl fungibles::InspectHold<::AccountId> for Pallet { + fn balance_on_hold(asset: AssetIdOf, who: &T::AccountId) -> BalanceOf { + T::Store::get(&(asset, who.clone())).reserved + } + fn can_hold(asset: AssetIdOf, who: &T::AccountId, amount: BalanceOf) -> bool { + // If we can withdraw without destroying the account, then we're good. + >::can_withdraw(asset, who, amount) == WithdrawConsequence::Success + } +} + +impl fungibles::MutateHold<::AccountId> for Pallet { + fn hold(asset: AssetIdOf, who: &T::AccountId, amount: BalanceOf) -> DispatchResult { + use fungibles::InspectHold; + if !Self::can_hold(asset, who, amount) { + Err(TokenError::NoFunds)? + } + T::Store::mutate( + &(asset, who.clone()), + |extra| extra.reserved = extra.reserved.saturating_add(amount), + )?; + Ok(()) + } + + fn release(asset: AssetIdOf, who: &T::AccountId, amount: BalanceOf, best_effort: bool) + -> Result, DispatchError> + { + T::Store::try_mutate_exists( + &(asset, who.clone()), + |maybe_extra| if let Some(ref mut extra) = maybe_extra { + let old = extra.reserved; + extra.reserved = extra.reserved.saturating_sub(amount); + let actual = old - extra.reserved; + ensure!(best_effort || actual == amount, TokenError::NoFunds); + Ok(actual) + } else { + Err(TokenError::NoFunds)? + }, + ) + } + + fn transfer_held( + asset: AssetIdOf, + source: &T::AccountId, + dest: &T::AccountId, + amount: BalanceOf, + best_effort: bool, + on_hold: bool, + ) -> Result, DispatchError> { + use fungibles::{InspectHold, InspectWithoutFreezer}; + + // Can't create the account with just a chunk of held balance - there needs to already be + // the minimum deposit. + let min_balance = Self::min_balance(asset); + ensure!(!on_hold || Self::balance(asset, dest) < min_balance, TokenError::CannotCreate); + + + T::Store::try_mutate_exists(&(asset, who.clone()), |maybe_extra| { + if let Some(ref mut extra) = maybe_extra { + // Figure out the most we can unreserve. + let old = extra.reserved; + extra.reserved = extra.reserved.saturating_sub(amount); + let actual = old - extra.reserved; + ensure!(best_effort || actual == amount, TokenError::NoFunds); + + let balance_left = >::balance(asset, source).saturating_sub(min_balance); + ensure!(balance_left >= actual, TokenError::NoFunds); + + Self::can_deposit(asset, dest, actual).into_result()?; + + Ok(actual) + } else { + Err(TokenError::NoFunds)? + } + }); + + let e = Self::transfer(asset, source, dest, amount, best_effort); + debug_assert!(e.is_ok(), "Checked that the "); + + todo!(); +// T::Store:: + } +} + +//impl fungibles::MutateReserve<::AccountId> for Pallet { +//} diff --git a/frame/assets-freezer/src/mock.rs b/frame/assets-freezer/src/mock.rs new file mode 100644 index 0000000000000..96567537b9ff6 --- /dev/null +++ b/frame/assets-freezer/src/mock.rs @@ -0,0 +1,151 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2021 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 environment for Assets pallet. + +use super::*; +use crate as pallet_assets; + +use sp_core::H256; +use sp_runtime::{traits::{BlakeTwo256, IdentityLookup}, testing::Header}; +use frame_support::{parameter_types, construct_runtime}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Assets: pallet_assets::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} +impl frame_system::Config for Test { + type BaseCallFilter = (); + type BlockWeights = (); + type BlockLength = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); +} + +parameter_types! { + pub const ExistentialDeposit: u64 = 1; +} + +impl pallet_balances::Config for Test { + type Balance = u64; + type DustRemoval = (); + type Event = Event; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); +} + +parameter_types! { + pub const AssetDeposit: u64 = 1; + pub const ApprovalDeposit: u64 = 1; + pub const StringLimit: u32 = 50; + pub const MetadataDepositBase: u64 = 1; + pub const MetadataDepositPerByte: u64 = 1; +} + +impl Config for Test { + type Event = Event; + type Balance = u64; + type AssetId = u32; + type Currency = Balances; + type ForceOrigin = frame_system::EnsureRoot; + type AssetDeposit = AssetDeposit; + type MetadataDepositBase = MetadataDepositBase; + type MetadataDepositPerByte = MetadataDepositPerByte; + type ApprovalDeposit = ApprovalDeposit; + type StringLimit = StringLimit; + type Freezer = TestFreezer; + type WeightInfo = (); +} + +use std::cell::RefCell; +use std::collections::HashMap; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub(crate) enum Hook { + Melted(u32, u64, u64), + Died(u32, u64), +} +thread_local! { + static FROZEN: RefCell> = RefCell::new(Default::default()); + static HOOKS: RefCell> = RefCell::new(Default::default()); +} + +pub struct TestFreezer; +impl FrozenBalance for TestFreezer { + fn frozen_balance(asset: u32, who: &u64) -> Option { + FROZEN.with(|f| f.borrow().get(&(asset, who.clone())).cloned()) + } + + fn melted(asset: u32, who: &u64, amount_left_frozen: u64) { + HOOKS.with(|h| h.borrow_mut().push(Hook::Melted(asset, who.clone(), amount_left_frozen))); + } + + fn died(asset: u32, who: &u64) { + HOOKS.with(|h| h.borrow_mut().push(Hook::Died(asset, who.clone()))); + } +} + +pub(crate) fn set_frozen_balance(asset: u32, who: u64, amount: u64) { + FROZEN.with(|f| f.borrow_mut().insert((asset, who), amount)); +} +pub(crate) fn clear_frozen_balance(asset: u32, who: u64) { + FROZEN.with(|f| f.borrow_mut().remove(&(asset, who))); +} +pub(crate) fn hooks() -> Vec { + HOOKS.with(|h| h.borrow().clone()) +} + +pub(crate) fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} diff --git a/frame/assets-freezer/src/tests.rs b/frame/assets-freezer/src/tests.rs new file mode 100644 index 0000000000000..a1c379a7c5d32 --- /dev/null +++ b/frame/assets-freezer/src/tests.rs @@ -0,0 +1,547 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for Assets pallet. + +use super::*; +use crate::{Error, mock::*}; +use sp_runtime::TokenError; +use frame_support::{assert_ok, assert_noop, traits::Currency}; +use pallet_balances::Error as BalancesError; + +fn last_event() -> mock::Event { + frame_system::Pallet::::events().pop().expect("Event expected").event +} + +#[test] +fn basic_minting_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::mint(Origin::signed(1), 0, 2, 100)); + assert_eq!(Assets::balance(0, 2), 100); + }); +} + +#[test] +fn approval_lifecycle_works() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + Balances::make_free_balance_be(&1, 1); + assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); + assert_eq!(Balances::reserved_balance(&1), 1); + assert_ok!(Assets::transfer_approved(Origin::signed(2), 0, 1, 3, 40)); + assert_ok!(Assets::cancel_approval(Origin::signed(1), 0, 2)); + assert_eq!(Assets::balance(0, 1), 60); + assert_eq!(Assets::balance(0, 3), 40); + assert_eq!(Balances::reserved_balance(&1), 0); + }); +} + +#[test] +fn approval_deposits_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + let e = BalancesError::::InsufficientBalance; + assert_noop!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50), e); + + Balances::make_free_balance_be(&1, 1); + assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); + assert_eq!(Balances::reserved_balance(&1), 1); + + assert_ok!(Assets::transfer_approved(Origin::signed(2), 0, 1, 3, 50)); + assert_eq!(Balances::reserved_balance(&1), 0); + + assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); + assert_ok!(Assets::cancel_approval(Origin::signed(1), 0, 2)); + assert_eq!(Balances::reserved_balance(&1), 0); + }); +} + +#[test] +fn cannot_transfer_more_than_approved() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + Balances::make_free_balance_be(&1, 1); + assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); + let e = Error::::Unapproved; + assert_noop!(Assets::transfer_approved(Origin::signed(2), 0, 1, 3, 51), e); + }); +} + +#[test] +fn cannot_transfer_more_than_exists() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + Balances::make_free_balance_be(&1, 1); + assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 101)); + let e = Error::::BalanceLow; + assert_noop!(Assets::transfer_approved(Origin::signed(2), 0, 1, 3, 101), e); + }); +} + +#[test] +fn cancel_approval_works() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + Balances::make_free_balance_be(&1, 1); + assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); + assert_noop!(Assets::cancel_approval(Origin::signed(1), 1, 2), Error::::Unknown); + assert_noop!(Assets::cancel_approval(Origin::signed(2), 0, 2), Error::::Unknown); + assert_noop!(Assets::cancel_approval(Origin::signed(1), 0, 3), Error::::Unknown); + assert_ok!(Assets::cancel_approval(Origin::signed(1), 0, 2)); + assert_noop!(Assets::cancel_approval(Origin::signed(1), 0, 2), Error::::Unknown); + }); +} + +#[test] +fn force_cancel_approval_works() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + Balances::make_free_balance_be(&1, 1); + assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); + let e = Error::::NoPermission; + assert_noop!(Assets::force_cancel_approval(Origin::signed(2), 0, 1, 2), e); + assert_noop!(Assets::force_cancel_approval(Origin::signed(1), 1, 1, 2), Error::::Unknown); + assert_noop!(Assets::force_cancel_approval(Origin::signed(1), 0, 2, 2), Error::::Unknown); + assert_noop!(Assets::force_cancel_approval(Origin::signed(1), 0, 1, 3), Error::::Unknown); + assert_ok!(Assets::force_cancel_approval(Origin::signed(1), 0, 1, 2)); + assert_noop!(Assets::force_cancel_approval(Origin::signed(1), 0, 1, 2), Error::::Unknown); + }); +} + +#[test] +fn lifecycle_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::create(Origin::signed(1), 0, 1, 1)); + assert_eq!(Balances::reserved_balance(&1), 1); + assert!(Asset::::contains_key(0)); + + assert_ok!(Assets::set_metadata(Origin::signed(1), 0, vec![0], vec![0], 12)); + assert_eq!(Balances::reserved_balance(&1), 4); + assert!(Metadata::::contains_key(0)); + + Balances::make_free_balance_be(&10, 100); + assert_ok!(Assets::mint(Origin::signed(1), 0, 10, 100)); + Balances::make_free_balance_be(&20, 100); + assert_ok!(Assets::mint(Origin::signed(1), 0, 20, 100)); + assert_eq!(Account::::iter_prefix(0).count(), 2); + + let w = Asset::::get(0).unwrap().destroy_witness(); + assert_ok!(Assets::destroy(Origin::signed(1), 0, w)); + assert_eq!(Balances::reserved_balance(&1), 0); + + assert!(!Asset::::contains_key(0)); + assert!(!Metadata::::contains_key(0)); + assert_eq!(Account::::iter_prefix(0).count(), 0); + + assert_ok!(Assets::create(Origin::signed(1), 0, 1, 1)); + assert_eq!(Balances::reserved_balance(&1), 1); + assert!(Asset::::contains_key(0)); + + assert_ok!(Assets::set_metadata(Origin::signed(1), 0, vec![0], vec![0], 12)); + assert_eq!(Balances::reserved_balance(&1), 4); + assert!(Metadata::::contains_key(0)); + + assert_ok!(Assets::mint(Origin::signed(1), 0, 10, 100)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 20, 100)); + assert_eq!(Account::::iter_prefix(0).count(), 2); + + let w = Asset::::get(0).unwrap().destroy_witness(); + assert_ok!(Assets::destroy(Origin::root(), 0, w)); + assert_eq!(Balances::reserved_balance(&1), 0); + + assert!(!Asset::::contains_key(0)); + assert!(!Metadata::::contains_key(0)); + assert_eq!(Account::::iter_prefix(0).count(), 0); + }); +} + +#[test] +fn destroy_with_bad_witness_should_not_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + let w = Asset::::get(0).unwrap().destroy_witness(); + assert_ok!(Assets::mint(Origin::signed(1), 0, 10, 100)); + assert_noop!(Assets::destroy(Origin::signed(1), 0, w), Error::::BadWitness); + }); +} + +#[test] +fn non_providing_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, false, 1)); + + Balances::make_free_balance_be(&0, 100); + assert_ok!(Assets::mint(Origin::signed(1), 0, 0, 100)); + + // Cannot mint into account 2 since it doesn't (yet) exist... + assert_noop!(Assets::mint(Origin::signed(1), 0, 1, 100), TokenError::CannotCreate); + // ...or transfer... + assert_noop!(Assets::transfer(Origin::signed(0), 0, 1, 50), TokenError::CannotCreate); + // ...or force-transfer + assert_noop!(Assets::force_transfer(Origin::signed(1), 0, 0, 1, 50), TokenError::CannotCreate); + + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + assert_ok!(Assets::transfer(Origin::signed(0), 0, 1, 25)); + assert_ok!(Assets::force_transfer(Origin::signed(1), 0, 0, 2, 25)); + }); +} + +#[test] +fn min_balance_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 10)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_eq!(Asset::::get(0).unwrap().accounts, 1); + + // Cannot create a new account with a balance that is below minimum... + assert_noop!(Assets::mint(Origin::signed(1), 0, 2, 9), TokenError::BelowMinimum); + assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 9), TokenError::BelowMinimum); + assert_noop!(Assets::force_transfer(Origin::signed(1), 0, 1, 2, 9), TokenError::BelowMinimum); + + // When deducting from an account to below minimum, it should be reaped. + assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 91)); + assert!(Assets::balance(0, 1).is_zero()); + assert_eq!(Assets::balance(0, 2), 100); + assert_eq!(Asset::::get(0).unwrap().accounts, 1); + + assert_ok!(Assets::force_transfer(Origin::signed(1), 0, 2, 1, 91)); + assert!(Assets::balance(0, 2).is_zero()); + assert_eq!(Assets::balance(0, 1), 100); + assert_eq!(Asset::::get(0).unwrap().accounts, 1); + + assert_ok!(Assets::burn(Origin::signed(1), 0, 1, 91)); + assert!(Assets::balance(0, 1).is_zero()); + assert_eq!(Asset::::get(0).unwrap().accounts, 0); + }); +} + +#[test] +fn querying_total_supply_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 2), 50); + assert_ok!(Assets::transfer(Origin::signed(2), 0, 3, 31)); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 2), 19); + assert_eq!(Assets::balance(0, 3), 31); + assert_ok!(Assets::burn(Origin::signed(1), 0, 3, u64::max_value())); + assert_eq!(Assets::total_supply(0), 69); + }); +} + +#[test] +fn transferring_amount_below_available_balance_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 2), 50); + }); +} + +#[test] +fn transferring_enough_to_kill_source_when_keep_alive_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 10)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_noop!(Assets::transfer_keep_alive(Origin::signed(1), 0, 2, 91), Error::::BalanceLow); + assert_ok!(Assets::transfer_keep_alive(Origin::signed(1), 0, 2, 90)); + assert_eq!(Assets::balance(0, 1), 10); + assert_eq!(Assets::balance(0, 2), 90); + }); +} + +#[test] +fn transferring_frozen_user_should_not_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::freeze(Origin::signed(1), 0, 1)); + assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 50), Error::::Frozen); + assert_ok!(Assets::thaw(Origin::signed(1), 0, 1)); + assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); + }); +} + +#[test] +fn transferring_frozen_asset_should_not_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::freeze_asset(Origin::signed(1), 0)); + assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 50), Error::::Frozen); + assert_ok!(Assets::thaw_asset(Origin::signed(1), 0)); + assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); + }); +} + +#[test] +fn origin_guards_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_noop!(Assets::transfer_ownership(Origin::signed(2), 0, 2), Error::::NoPermission); + assert_noop!(Assets::set_team(Origin::signed(2), 0, 2, 2, 2), Error::::NoPermission); + assert_noop!(Assets::freeze(Origin::signed(2), 0, 1), Error::::NoPermission); + assert_noop!(Assets::thaw(Origin::signed(2), 0, 2), Error::::NoPermission); + assert_noop!(Assets::mint(Origin::signed(2), 0, 2, 100), Error::::NoPermission); + assert_noop!(Assets::burn(Origin::signed(2), 0, 1, 100), Error::::NoPermission); + assert_noop!(Assets::force_transfer(Origin::signed(2), 0, 1, 2, 100), Error::::NoPermission); + let w = Asset::::get(0).unwrap().destroy_witness(); + assert_noop!(Assets::destroy(Origin::signed(2), 0, w), Error::::NoPermission); + }); +} + +#[test] +fn transfer_owner_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + assert_ok!(Assets::create(Origin::signed(1), 0, 1, 1)); + + assert_eq!(Balances::reserved_balance(&1), 1); + + assert_ok!(Assets::transfer_ownership(Origin::signed(1), 0, 2)); + assert_eq!(Balances::reserved_balance(&2), 1); + assert_eq!(Balances::reserved_balance(&1), 0); + + assert_noop!(Assets::transfer_ownership(Origin::signed(1), 0, 1), Error::::NoPermission); + + // Set metadata now and make sure that deposit gets transferred back. + assert_ok!(Assets::set_metadata(Origin::signed(2), 0, vec![0u8; 10], vec![0u8; 10], 12)); + assert_ok!(Assets::transfer_ownership(Origin::signed(2), 0, 1)); + assert_eq!(Balances::reserved_balance(&1), 22); + assert_eq!(Balances::reserved_balance(&2), 0); + }); +} + +#[test] +fn set_team_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::set_team(Origin::signed(1), 0, 2, 3, 4)); + + assert_ok!(Assets::mint(Origin::signed(2), 0, 2, 100)); + assert_ok!(Assets::freeze(Origin::signed(4), 0, 2)); + assert_ok!(Assets::thaw(Origin::signed(3), 0, 2)); + assert_ok!(Assets::force_transfer(Origin::signed(3), 0, 2, 3, 100)); + assert_ok!(Assets::burn(Origin::signed(3), 0, 3, 100)); + }); +} + +#[test] +fn transferring_to_frozen_account_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 2, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_eq!(Assets::balance(0, 2), 100); + assert_ok!(Assets::freeze(Origin::signed(1), 0, 2)); + assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); + assert_eq!(Assets::balance(0, 2), 150); + }); +} + +#[test] +fn transferring_amount_more_than_available_balance_should_not_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 2), 50); + assert_ok!(Assets::burn(Origin::signed(1), 0, 1, u64::max_value())); + assert_eq!(Assets::balance(0, 1), 0); + assert_noop!(Assets::transfer(Origin::signed(1), 0, 1, 50), Error::::BalanceLow); + assert_noop!(Assets::transfer(Origin::signed(2), 0, 1, 51), Error::::BalanceLow); + }); +} + +#[test] +fn transferring_less_than_one_unit_is_fine() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 0)); + assert_eq!( + last_event(), + mock::Event::pallet_assets(crate::Event::Transferred(0, 1, 2, 0)), + ); + }); +} + +#[test] +fn transferring_more_units_than_total_supply_should_not_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 101), Error::::BalanceLow); + }); +} + +#[test] +fn burning_asset_balance_with_positive_balance_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::burn(Origin::signed(1), 0, 1, u64::max_value())); + assert_eq!(Assets::balance(0, 1), 0); + }); +} + +#[test] +fn burning_asset_balance_with_zero_balance_does_nothing() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 2), 0); + assert_ok!(Assets::burn(Origin::signed(1), 0, 2, u64::max_value())); + assert_eq!(Assets::balance(0, 2), 0); + assert_eq!(Assets::total_supply(0), 100); + }); +} + +#[test] +fn set_metadata_should_work() { + new_test_ext().execute_with(|| { + // Cannot add metadata to unknown asset + assert_noop!( + Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 10], 12), + Error::::Unknown, + ); + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + // Cannot add metadata to unowned asset + assert_noop!( + Assets::set_metadata(Origin::signed(2), 0, vec![0u8; 10], vec![0u8; 10], 12), + Error::::NoPermission, + ); + + // Cannot add oversized metadata + assert_noop!( + Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 100], vec![0u8; 10], 12), + Error::::BadMetadata, + ); + assert_noop!( + Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 100], 12), + Error::::BadMetadata, + ); + + // Successfully add metadata and take deposit + Balances::make_free_balance_be(&1, 30); + assert_ok!(Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 10], 12)); + assert_eq!(Balances::free_balance(&1), 9); + + // Update deposit + assert_ok!(Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 5], 12)); + assert_eq!(Balances::free_balance(&1), 14); + assert_ok!(Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 15], 12)); + assert_eq!(Balances::free_balance(&1), 4); + + // Cannot over-reserve + assert_noop!( + Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 20], vec![0u8; 20], 12), + BalancesError::::InsufficientBalance, + ); + + // Clear Metadata + assert!(Metadata::::contains_key(0)); + assert_noop!(Assets::clear_metadata(Origin::signed(2), 0), Error::::NoPermission); + assert_noop!(Assets::clear_metadata(Origin::signed(1), 1), Error::::Unknown); + assert_ok!(Assets::clear_metadata(Origin::signed(1), 0)); + assert!(!Metadata::::contains_key(0)); + }); +} + +// TODO: tests for force_set_metadata, force_clear_metadata, force_asset_status + +#[test] +fn freezer_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 10)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + + + // freeze 50 of it. + set_frozen_balance(0, 1, 50); + + assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 20)); + + assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 21), Error::::BalanceLow); + + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); + assert_noop!(Assets::transfer_approved(Origin::signed(2), 0, 1, 2, 21), Error::::BalanceLow); + + assert_ok!(Assets::force_transfer(Origin::signed(1), 0, 1, 2, 21)); + assert_eq!(hooks(), vec![Hook::Melted(0, 1, 49)]); + + clear_frozen_balance(0, 1); + assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); + assert_eq!(hooks(), vec![Hook::Melted(0, 1, 49), Hook::Died(0, 1)]); + }); +} + +#[test] +fn imbalances_should_work() { + use frame_support::traits::tokens::fungibles::Balanced; + + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + + let imb = Assets::issue(0, 100); + assert_eq!(Assets::total_supply(0), 100); + assert_eq!(imb.peek(), 100); + + let (imb1, imb2) = imb.split(30); + assert_eq!(imb1.peek(), 30); + assert_eq!(imb2.peek(), 70); + + drop(imb2); + assert_eq!(Assets::total_supply(0), 30); + + assert!(Assets::resolve(&1, imb1).is_ok()); + assert_eq!(Assets::balance(0, 1), 30); + assert_eq!(Assets::total_supply(0), 30); + }); +} diff --git a/frame/assets-freezer/src/weights.rs b/frame/assets-freezer/src/weights.rs new file mode 100644 index 0000000000000..c3c804a392dbe --- /dev/null +++ b/frame/assets-freezer/src/weights.rs @@ -0,0 +1,339 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_assets +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 +//! DATE: 2021-03-08, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// target/release/substrate +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_assets +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/assets/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + + +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_assets. +pub trait WeightInfo { + fn create() -> Weight; + fn force_create() -> Weight; + fn destroy(c: u32, s: u32, a: u32, ) -> Weight; + fn mint() -> Weight; + fn burn() -> Weight; + fn transfer() -> Weight; + fn transfer_keep_alive() -> Weight; + fn force_transfer() -> Weight; + fn freeze() -> Weight; + fn thaw() -> Weight; + fn freeze_asset() -> Weight; + fn thaw_asset() -> Weight; + fn transfer_ownership() -> Weight; + fn set_team() -> Weight; + fn set_metadata(n: u32, s: u32, ) -> Weight; + fn clear_metadata() -> Weight; + fn force_set_metadata(n: u32, s: u32, ) -> Weight; + fn force_clear_metadata() -> Weight; + fn force_asset_status() -> Weight; + fn approve_transfer() -> Weight; + fn transfer_approved() -> Weight; + fn cancel_approval() -> Weight; + fn force_cancel_approval() -> Weight; +} + +/// Weights for pallet_assets using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + fn create() -> Weight { + (48_305_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn force_create() -> Weight { + (23_827_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn destroy(c: u32, s: u32, a: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 38_000 + .saturating_add((24_232_000 as Weight).saturating_mul(c as Weight)) + // Standard Error: 38_000 + .saturating_add((30_467_000 as Weight).saturating_mul(s as Weight)) + // Standard Error: 383_000 + .saturating_add((2_343_000 as Weight).saturating_mul(a as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) + .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(s as Weight))) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(c as Weight))) + .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(s as Weight))) + .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(a as Weight))) + } + fn mint() -> Weight { + (46_433_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn burn() -> Weight { + (46_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn transfer() -> Weight { + (70_793_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + fn transfer_keep_alive() -> Weight { + (57_453_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + fn force_transfer() -> Weight { + (70_968_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + fn freeze() -> Weight { + (34_290_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn thaw() -> Weight { + (34_419_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn freeze_asset() -> Weight { + (24_373_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn thaw_asset() -> Weight { + (24_096_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn transfer_ownership() -> Weight { + (28_566_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn set_team() -> Weight { + (25_297_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn set_metadata(_n: u32, s: u32, ) -> Weight { + (53_367_000 as Weight) + // Standard Error: 0 + .saturating_add((8_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn clear_metadata() -> Weight { + (51_721_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn force_set_metadata(_n: u32, s: u32, ) -> Weight { + (27_117_000 as Weight) + // Standard Error: 0 + .saturating_add((5_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn force_clear_metadata() -> Weight { + (51_598_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn force_asset_status() -> Weight { + (23_366_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn approve_transfer() -> Weight { + (47_906_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn transfer_approved() -> Weight { + (90_338_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) + } + fn cancel_approval() -> Weight { + (48_591_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn force_cancel_approval() -> Weight { + (54_879_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn create() -> Weight { + (48_305_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn force_create() -> Weight { + (23_827_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn destroy(c: u32, s: u32, a: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 38_000 + .saturating_add((24_232_000 as Weight).saturating_mul(c as Weight)) + // Standard Error: 38_000 + .saturating_add((30_467_000 as Weight).saturating_mul(s as Weight)) + // Standard Error: 383_000 + .saturating_add((2_343_000 as Weight).saturating_mul(a as Weight)) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) + .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(s as Weight))) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(c as Weight))) + .saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(s as Weight))) + .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(a as Weight))) + } + fn mint() -> Weight { + (46_433_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + fn burn() -> Weight { + (46_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + fn transfer() -> Weight { + (70_793_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + fn transfer_keep_alive() -> Weight { + (57_453_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + fn force_transfer() -> Weight { + (70_968_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + fn freeze() -> Weight { + (34_290_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn thaw() -> Weight { + (34_419_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn freeze_asset() -> Weight { + (24_373_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn thaw_asset() -> Weight { + (24_096_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn transfer_ownership() -> Weight { + (28_566_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn set_team() -> Weight { + (25_297_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn set_metadata(_n: u32, s: u32, ) -> Weight { + (53_367_000 as Weight) + // Standard Error: 0 + .saturating_add((8_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn clear_metadata() -> Weight { + (51_721_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn force_set_metadata(_n: u32, s: u32, ) -> Weight { + (27_117_000 as Weight) + // Standard Error: 0 + .saturating_add((5_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn force_clear_metadata() -> Weight { + (51_598_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn force_asset_status() -> Weight { + (23_366_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn approve_transfer() -> Weight { + (47_906_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn transfer_approved() -> Weight { + (90_338_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } + fn cancel_approval() -> Weight { + (48_591_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn force_cancel_approval() -> Weight { + (54_879_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } +} From 6d4cccdf3606f91215bb562c0da84f05ac29ebc7 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 28 Mar 2021 23:57:09 +0200 Subject: [PATCH 03/22] Last bits --- frame/assets-freezer/src/lib.rs | 78 ++++++++++++++++--------- frame/support/src/traits/tokens/misc.rs | 15 +++++ 2 files changed, 64 insertions(+), 29 deletions(-) diff --git a/frame/assets-freezer/src/lib.rs b/frame/assets-freezer/src/lib.rs index 3ad9d29a652e6..d47f2738d3135 100644 --- a/frame/assets-freezer/src/lib.rs +++ b/frame/assets-freezer/src/lib.rs @@ -45,6 +45,7 @@ use frame_system::Config as SystemConfig; //pub use weights::WeightInfo; pub use pallet::*; +use frame_benchmarking::frame_support::traits::fungibles::Inspect; type BalanceOf = <::Assets as fungibles::Inspect<::AccountId>>::Balance; type AssetIdOf = <::Assets as fungibles::Inspect<::AccountId>>::AssetId; @@ -81,9 +82,7 @@ pub mod pallet { type Event: From> + IsType<::Event>; /// The fungibles trait impl whose assets this reserves. - type Assets: fungibles::Transfer - + fungibles::Inspect; - + fungibles::InspectWithoutFreezer; + type Assets: fungibles::Inspect; /// Place to store the fast-access freeze data for the given asset/account. type Store: StoredMap<(AssetIdOf, Self::AccountId), FreezeData>>; @@ -121,18 +120,11 @@ pub mod pallet { impl Pallet {} } -// The main implementation block for the module. -impl Pallet { -} - impl FrozenBalance, T::AccountId, BalanceOf> for Pallet { fn frozen_balance(id: AssetIdOf, who: &T::AccountId) -> Option> { let f = T::Store::get(&(id, who.clone())); if f.reserved.is_zero() { None } else { Some(f.reserved) } } - fn died(_: AssetIdOf, _: &T::AccountId) { - // Eventually need to remove lock named reserve/lock info. - } } impl fungibles::Inspect<::AccountId> for Pallet { @@ -164,8 +156,18 @@ impl fungibles::Inspect<::AccountId> for Pallet } } -impl fungibles::Transfer<::AccountId> for Pallet { - +impl fungibles::Transfer<::AccountId> for Pallet where + T::Assets: fungibles::Transfer +{ + fn transfer( + asset: Self::AssetId, + source: &T::AccountId, + dest: &T::AccountId, + amount: Self::Balance, + keep_alive: bool, + ) -> Result { + T::Assets::transfer(asset, source, dest, amount, keep_alive) + } } impl fungibles::InspectHold<::AccountId> for Pallet { @@ -178,7 +180,9 @@ impl fungibles::InspectHold<::AccountId> for Palle } } -impl fungibles::MutateHold<::AccountId> for Pallet { +impl fungibles::MutateHold<::AccountId> for Pallet where + T::Assets: fungibles::Transfer + fungibles::InspectWithoutFreezer +{ fn hold(asset: AssetIdOf, who: &T::AccountId, amount: BalanceOf) -> DispatchResult { use fungibles::InspectHold; if !Self::can_hold(asset, who, amount) { @@ -216,40 +220,56 @@ impl fungibles::MutateHold<::AccountId> for Pallet best_effort: bool, on_hold: bool, ) -> Result, DispatchError> { - use fungibles::{InspectHold, InspectWithoutFreezer}; + use fungibles::{InspectHold, Transfer, InspectWithoutFreezer}; // Can't create the account with just a chunk of held balance - there needs to already be // the minimum deposit. - let min_balance = Self::min_balance(asset); + let min_balance = > ::minimum_balance(asset); ensure!(!on_hold || Self::balance(asset, dest) < min_balance, TokenError::CannotCreate); - - T::Store::try_mutate_exists(&(asset, who.clone()), |maybe_extra| { + let actual = T::Store::try_mutate_exists( + &(asset, source.clone()), + |maybe_extra| -> Result, DispatchError> + { if let Some(ref mut extra) = maybe_extra { - // Figure out the most we can unreserve. + // Figure out the most we can unreserve and transfer. let old = extra.reserved; extra.reserved = extra.reserved.saturating_sub(amount); let actual = old - extra.reserved; ensure!(best_effort || actual == amount, TokenError::NoFunds); + // actual is how much we can unreserve. now we check that the balance actually + // exists in the account. let balance_left = >::balance(asset, source).saturating_sub(min_balance); ensure!(balance_left >= actual, TokenError::NoFunds); - Self::can_deposit(asset, dest, actual).into_result()?; + // the balance for the reserved amount actually exists. now we check that it's + // possible to actually transfer it out. the only reason this would fail is if the + // asset class or account is completely frozen. + Self::can_withdraw(asset, dest, actual).into_result_keep_alive()?; + // all good. we should now be able to unreserve and transfer without any error. Ok(actual) } else { Err(TokenError::NoFunds)? } - }); - - let e = Self::transfer(asset, source, dest, amount, best_effort); - debug_assert!(e.is_ok(), "Checked that the "); - - todo!(); -// T::Store:: + })?; + + // `best_effort` is `false` here since we've already checked that it should go through in + // the previous logic. if it fails here, then it must be due to a logic error. + let result = Self::transfer(asset, source, dest, actual, false); + if result.is_ok() { + if on_hold { + let r = T::Store::mutate( + &(asset, dest.clone()), + |extra| extra.reserved = extra.reserved.saturating_add(actual), + ); + debug_assert!(r.is_ok(), "account exists and funds transferred in; qed"); + r? + } + } else { + debug_assert!(false, "can_withdraw was successful; qed"); + } + result } } - -//impl fungibles::MutateReserve<::AccountId> for Pallet { -//} diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index 827edda5ea51f..df82262608217 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -66,6 +66,21 @@ impl WithdrawConsequence { Success => Ok(Zero::zero()), } } + + /// Convert the type into a `Result` with `TokenError` as the error. An error will be returned + /// if the account were to be destroyed. + pub fn into_result_keep_alive(self) -> Result<(), TokenError> { + use WithdrawConsequence::*; + match self { + NoFunds => Err(TokenError::NoFunds), + WouldDie | ReducedToZero(_) => Err(TokenError::WouldDie), + UnknownAsset => Err(TokenError::UnknownAsset), + Underflow => Err(TokenError::Underflow), + Overflow => Err(TokenError::Overflow), + Frozen => Err(TokenError::Frozen), + Success => Ok(()), + } + } } /// One of a number of consequences of withdrawing a fungible from an account. From 3c2e7bc124e5dc979fdf5037846a0fc76a1e49d6 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 29 Mar 2021 14:36:00 +0200 Subject: [PATCH 04/22] Some alterations to traits in preparation for Balanced Hold impl. --- frame/assets-freezer/src/lib.rs | 30 ++++-- frame/assets/src/benchmarking.rs | 2 +- frame/assets/src/functions.rs | 14 +-- frame/assets/src/impl_fungibles.rs | 60 ++--------- frame/assets/src/lib.rs | 43 ++------ frame/assets/src/tests.rs | 14 +-- frame/assets/src/types.rs | 43 +------- frame/assets/src/weights.rs | 6 +- frame/balances/src/lib.rs | 11 +- frame/support/src/traits/tokens.rs | 2 +- frame/support/src/traits/tokens/fungible.rs | 69 +++++++----- .../src/traits/tokens/fungible/balanced.rs | 101 +++++++----------- frame/support/src/traits/tokens/fungibles.rs | 49 +++++---- .../src/traits/tokens/fungibles/balanced.rs | 98 +++++++---------- frame/support/src/traits/tokens/misc.rs | 43 ++++---- 15 files changed, 236 insertions(+), 349 deletions(-) diff --git a/frame/assets-freezer/src/lib.rs b/frame/assets-freezer/src/lib.rs index d47f2738d3135..ba62b9a775580 100644 --- a/frame/assets-freezer/src/lib.rs +++ b/frame/assets-freezer/src/lib.rs @@ -39,13 +39,14 @@ use sp_runtime::{ }; use codec::{Encode, Decode, HasCompact}; use frame_support::{ensure, dispatch::{DispatchError, DispatchResult}}; -use frame_support::traits::{Currency, ReservableCurrency, BalanceStatus::Reserved, StoredMap}; -use frame_support::traits::tokens::{WithdrawConsequence, DepositConsequence, fungibles, FrozenBalance}; +use frame_support::traits::{ + Currency, ReservableCurrency, BalanceStatus::Reserved, StoredMap, tokens::{ + WithdrawConsequence, DepositConsequence, fungibles, FrozenBalance, WhenDust +}}; use frame_system::Config as SystemConfig; //pub use weights::WeightInfo; pub use pallet::*; -use frame_benchmarking::frame_support::traits::fungibles::Inspect; type BalanceOf = <::Assets as fungibles::Inspect<::AccountId>>::Balance; type AssetIdOf = <::Assets as fungibles::Inspect<::AccountId>>::AssetId; @@ -117,7 +118,9 @@ pub mod pallet { // Only admin calls. #[pallet::call] - impl Pallet {} + impl Pallet { + + } } impl FrozenBalance, T::AccountId, BalanceOf> for Pallet { @@ -164,9 +167,10 @@ impl fungibles::Transfer<::AccountId> for Pallet Result { - T::Assets::transfer(asset, source, dest, amount, keep_alive) + T::Assets::transfer(asset, source, dest, amount, best_effort, death) } } @@ -225,7 +229,8 @@ impl fungibles::MutateHold<::AccountId> for Pallet // Can't create the account with just a chunk of held balance - there needs to already be // the minimum deposit. let min_balance = > ::minimum_balance(asset); - ensure!(!on_hold || Self::balance(asset, dest) < min_balance, TokenError::CannotCreate); + let dest_balance = >::balance(asset, dest); + ensure!(!on_hold || dest_balance >= min_balance, TokenError::CannotCreate); let actual = T::Store::try_mutate_exists( &(asset, source.clone()), @@ -240,13 +245,15 @@ impl fungibles::MutateHold<::AccountId> for Pallet // actual is how much we can unreserve. now we check that the balance actually // exists in the account. - let balance_left = >::balance(asset, source).saturating_sub(min_balance); + let balance_left = >::balance(asset, source) + .saturating_sub(min_balance); ensure!(balance_left >= actual, TokenError::NoFunds); // the balance for the reserved amount actually exists. now we check that it's // possible to actually transfer it out. the only reason this would fail is if the // asset class or account is completely frozen. - Self::can_withdraw(asset, dest, actual).into_result_keep_alive()?; + >::can_withdraw(asset, dest, actual) + .into_result(true)?; // all good. we should now be able to unreserve and transfer without any error. Ok(actual) @@ -257,7 +264,10 @@ impl fungibles::MutateHold<::AccountId> for Pallet // `best_effort` is `false` here since we've already checked that it should go through in // the previous logic. if it fails here, then it must be due to a logic error. - let result = Self::transfer(asset, source, dest, actual, false); + // `death` is `KeepAlive` here since we're only transferring funds that were on hold, for + // which there must be an additional min_balance, it should be impossible for the transfer + // to cause the account to be deleted. + let result = Self::transfer(asset, source, dest, actual, true, WhenDust::KeepAlive); if result.is_ok() { if on_hold { let r = T::Store::mutate( diff --git a/frame/assets/src/benchmarking.rs b/frame/assets/src/benchmarking.rs index 227d45623d688..091e278106273 100644 --- a/frame/assets/src/benchmarking.rs +++ b/frame/assets/src/benchmarking.rs @@ -175,7 +175,7 @@ benchmarks! { assert_last_event::(Event::Issued(Default::default(), caller, amount).into()); } - burn { + slash { let amount = T::Balance::from(100u32); let (caller, caller_lookup) = create_default_minted_asset::(true, amount); }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, amount) diff --git a/frame/assets/src/functions.rs b/frame/assets/src/functions.rs index 54c7a779bea5d..94e972d582274 100644 --- a/frame/assets/src/functions.rs +++ b/frame/assets/src/functions.rs @@ -104,7 +104,7 @@ impl Pallet { id: T::AssetId, who: &T::AccountId, amount: T::Balance, - f: DecreaseFlags, + f: DebitFlags, ) -> WithdrawConsequence { use WithdrawConsequence::*; let details = match Asset::::get(id) { @@ -153,7 +153,7 @@ impl Pallet { pub(super) fn reducible_balance( id: T::AssetId, who: &T::AccountId, - f: DecreaseFlags, + f: DebitFlags, ) -> Result> { let details = match Asset::::get(id) { Some(details) => details, @@ -205,10 +205,10 @@ impl Pallet { ) -> Result { let actual = Self::reducible_balance(id, target, f.into())? .min(amount); - ensure!(f.best_effort || actual >= amount, Error::::BalanceLow); + ensure!(actual >= amount, Error::::BalanceLow); let conseq = Self::can_decrease(id, target, actual, f.into()); - let actual = match conseq.into_result() { + let actual = match conseq.into_result(false) { Ok(dust) => actual.saturating_add(dust), //< guaranteed by reducible_balance Err(e) => { debug_assert!(false, "passed from reducible_balance; qed"); @@ -398,7 +398,7 @@ impl Pallet { dest: &T::AccountId, amount: T::Balance, maybe_need_admin: Option, - f: TransferFlags, + death: WhenDust, ) -> Result { // Early exist if no-op. if amount.is_zero() { @@ -407,8 +407,8 @@ impl Pallet { } // Figure out the debit and credit, together with side-effects. - let debit = Self::prep_debit(id, &source, amount, f.into())?; - let (credit, maybe_burn) = Self::prep_credit(id, &dest, amount, debit, f.burn_dust)?; + let debit = Self::prep_debit(id, &source, amount, death.into())?; + let (credit, maybe_burn) = Self::prep_credit(id, &dest, amount, debit, death.dispose())?; let mut source_account = Account::::get(id, &source); diff --git a/frame/assets/src/impl_fungibles.rs b/frame/assets/src/impl_fungibles.rs index 1b06525a2cfdc..cddc6d14a1524 100644 --- a/frame/assets/src/impl_fungibles.rs +++ b/frame/assets/src/impl_fungibles.rs @@ -43,7 +43,7 @@ impl fungibles::Inspect<::AccountId> for Pallet who: &::AccountId, keep_alive: bool, ) -> Self::Balance { - let f = DecreaseFlags { keep_alive, ignore_freezer: false }; + let f = DebitFlags { keep_alive, ignore_freezer: false }; Pallet::::reducible_balance(asset, who, f).unwrap_or(Zero::zero()) } @@ -60,7 +60,7 @@ impl fungibles::Inspect<::AccountId> for Pallet who: &::AccountId, amount: Self::Balance, ) -> WithdrawConsequence { - let f = DecreaseFlags { keep_alive: false, ignore_freezer: false }; + let f = DebitFlags { keep_alive: false, ignore_freezer: false }; Pallet::::can_decrease(asset, who, amount, f) } } @@ -71,7 +71,7 @@ impl fungibles::InspectWithoutFreezer<::AccountId> who: &::AccountId, keep_alive: bool, ) -> Self::Balance { - let f = DecreaseFlags { keep_alive, ignore_freezer: true }; + let f = DebitFlags { keep_alive, ignore_freezer: true }; Pallet::::reducible_balance(asset, who, f).unwrap_or(Zero::zero()) } @@ -80,7 +80,7 @@ impl fungibles::InspectWithoutFreezer<::AccountId> who: &::AccountId, amount: Self::Balance, ) -> WithdrawConsequence { - let f = DecreaseFlags { keep_alive: false, ignore_freezer: true }; + let f = DebitFlags { keep_alive: false, ignore_freezer: true }; Pallet::::can_decrease(asset, who, amount, f) } } @@ -99,24 +99,7 @@ impl fungibles::Mutate<::AccountId> for Pallet who: &::AccountId, amount: Self::Balance, ) -> Result { - let f = DebitFlags { - keep_alive: false, - best_effort: false, - ignore_freezer: false, - }; - Self::do_burn(asset, who, amount, None, f) - } - - fn slash( - asset: Self::AssetId, - who: &::AccountId, - amount: Self::Balance, - ) -> Result { - let f = DebitFlags { - keep_alive: false, - best_effort: true, - ignore_freezer: false, - }; + let f = DebitFlags { keep_alive: false, ignore_freezer: false }; Self::do_burn(asset, who, amount, None, f) } } @@ -127,14 +110,9 @@ impl fungibles::Transfer for Pallet { source: &T::AccountId, dest: &T::AccountId, amount: T::Balance, - keep_alive: bool, + death: WhenDust, ) -> Result { - let f = TransferFlags { - keep_alive, - best_effort: false, - burn_dust: false - }; - Self::do_transfer(asset, source, dest, amount, None, f) + Self::do_transfer(asset, source, dest, amount, None, death) } } @@ -147,31 +125,15 @@ impl fungibles::Unbalanced for Pallet { asset.supply = amount }); } - fn decrease_balance(asset: T::AssetId, who: &T::AccountId, amount: Self::Balance) + fn decrease_balance(asset: T::AssetId, who: &T::AccountId, amount: Self::Balance, keep_alive: bool) -> Result { - let f = DebitFlags { keep_alive: false, best_effort: false, ignore_freezer: false }; - 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, ignore_freezer: false }; + let f = DebitFlags { keep_alive, ignore_freezer: false }; Self::decrease_balance(asset, who, amount, f, |_, _| Ok(())) - .unwrap_or(Zero::zero()) } fn increase_balance(asset: T::AssetId, who: &T::AccountId, amount: Self::Balance) - -> Result - { - 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 + -> DispatchResult { - match Self::increase_balance(asset, who, amount, |_| Ok(())) { - Ok(()) => amount, - Err(_) => Zero::zero(), - } + Self::increase_balance(asset, who, amount, |_| Ok(())) } } diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 1fee57d9591d8..9f43ebc6452a2 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -149,7 +149,7 @@ use codec::{Encode, Decode, HasCompact}; use frame_support::{ ensure, dispatch::{DispatchError, DispatchResult}, traits::{ Currency, ReservableCurrency, BalanceStatus::Reserved, StoredMap, tokens::{ - WithdrawConsequence, DepositConsequence, fungibles, FrozenBalance, + WithdrawConsequence, DepositConsequence, fungibles, FrozenBalance, WhenDust }, }, }; @@ -535,7 +535,7 @@ pub mod pallet { /// /// Bails with `BalanceZero` if the `who` is already dead. /// - /// - `id`: The identifier of the asset to have some amount burned. + /// - `id`: The identifier of the asset to have some amount slashed. /// - `who`: The account to be debited from. /// - `amount`: The maximum amount by which `who`'s balance should be reduced. /// @@ -544,8 +544,8 @@ pub mod pallet { /// /// Weight: `O(1)` /// Modes: Post-existence of `who`; Pre & post Zombie-status of `who`. - #[pallet::weight(T::WeightInfo::burn())] - pub(super) fn burn( + #[pallet::weight(T::WeightInfo::slash())] + pub(super) fn slash( origin: OriginFor, #[pallet::compact] id: T::AssetId, who: ::Source, @@ -554,7 +554,8 @@ pub mod pallet { let origin = ensure_signed(origin)?; let who = T::Lookup::lookup(who)?; - let f = DebitFlags { keep_alive: false, best_effort: true, ignore_freezer: false }; + let f = DebitFlags { keep_alive: false, ignore_freezer: false }; + let amount = amount.min(Self::reducible_balance(id, &who, f)?); let burned = Self::do_burn(id, &who, amount, Some(origin), f)?; Self::deposit_event(Event::Burned(id, who, burned)); Ok(()) @@ -583,17 +584,12 @@ pub mod pallet { origin: OriginFor, #[pallet::compact] id: T::AssetId, target: ::Source, - #[pallet::compact] amount: T::Balance + #[pallet::compact] amount: T::Balance, ) -> DispatchResult { let origin = ensure_signed(origin)?; let dest = T::Lookup::lookup(target)?; - let f = TransferFlags { - keep_alive: false, - best_effort: false, - burn_dust: false - }; - Self::do_transfer(id, &origin, &dest, amount, None, f).map(|_| ()) + Self::do_transfer(id, &origin, &dest, amount, None, WhenDust::Credit).map(|_| ()) } /// Move some assets from the sender account to another, keeping the sender account alive. @@ -623,13 +619,7 @@ pub mod pallet { ) -> DispatchResult { let source = ensure_signed(origin)?; let dest = T::Lookup::lookup(target)?; - - let f = TransferFlags { - keep_alive: true, - best_effort: false, - burn_dust: false - }; - Self::do_transfer(id, &source, &dest, amount, None, f).map(|_| ()) + Self::do_transfer(id, &source, &dest, amount, None, WhenDust::KeepAlive).map(|_| ()) } /// Move some assets from one account to another. @@ -662,13 +652,7 @@ pub mod pallet { let origin = ensure_signed(origin)?; let source = T::Lookup::lookup(source)?; let dest = T::Lookup::lookup(dest)?; - - let f = TransferFlags { - keep_alive: false, - best_effort: false, - burn_dust: false - }; - Self::do_transfer(id, &source, &dest, amount, Some(origin), f).map(|_| ()) + Self::do_transfer(id, &source, &dest, amount, Some(origin), WhenDust::Credit).map(|_| ()) } /// Disallow further unprivileged transfers from an account. @@ -1221,12 +1205,7 @@ pub mod pallet { let mut approved = maybe_approved.take().ok_or(Error::::Unapproved)?; let remaining = approved.amount.checked_sub(&amount).ok_or(Error::::Unapproved)?; - let f = TransferFlags { - keep_alive: false, - best_effort: false, - burn_dust: false - }; - Self::do_transfer(id, &key.owner, &destination, amount, None, f)?; + Self::do_transfer(id, &key.owner, &destination, amount, None, WhenDust::Credit)?; if remaining.is_zero() { T::Currency::unreserve(&key.owner, approved.deposit); diff --git a/frame/assets/src/tests.rs b/frame/assets/src/tests.rs index 0bbdb6ae1445d..c9bdbd1b35603 100644 --- a/frame/assets/src/tests.rs +++ b/frame/assets/src/tests.rs @@ -235,7 +235,7 @@ fn min_balance_should_work() { assert_eq!(Assets::balance(0, 1), 100); assert_eq!(Asset::::get(0).unwrap().accounts, 1); - assert_ok!(Assets::burn(Origin::signed(1), 0, 1, 91)); + assert_ok!(Assets::slash(Origin::signed(1), 0, 1, 91)); assert!(Assets::balance(0, 1).is_zero()); assert_eq!(Asset::::get(0).unwrap().accounts, 0); }); @@ -254,7 +254,7 @@ fn querying_total_supply_should_work() { assert_eq!(Assets::balance(0, 1), 50); assert_eq!(Assets::balance(0, 2), 19); assert_eq!(Assets::balance(0, 3), 31); - assert_ok!(Assets::burn(Origin::signed(1), 0, 3, u64::max_value())); + assert_ok!(Assets::slash(Origin::signed(1), 0, 3, u64::max_value())); assert_eq!(Assets::total_supply(0), 69); }); } @@ -320,7 +320,7 @@ fn origin_guards_should_work() { assert_noop!(Assets::freeze(Origin::signed(2), 0, 1), Error::::NoPermission); assert_noop!(Assets::thaw(Origin::signed(2), 0, 2), Error::::NoPermission); assert_noop!(Assets::mint(Origin::signed(2), 0, 2, 100), Error::::NoPermission); - assert_noop!(Assets::burn(Origin::signed(2), 0, 1, 100), Error::::NoPermission); + assert_noop!(Assets::slash(Origin::signed(2), 0, 1, 100), Error::::NoPermission); assert_noop!(Assets::force_transfer(Origin::signed(2), 0, 1, 2, 100), Error::::NoPermission); let w = Asset::::get(0).unwrap().destroy_witness(); assert_noop!(Assets::destroy(Origin::signed(2), 0, w), Error::::NoPermission); @@ -360,7 +360,7 @@ fn set_team_should_work() { assert_ok!(Assets::freeze(Origin::signed(4), 0, 2)); assert_ok!(Assets::thaw(Origin::signed(3), 0, 2)); assert_ok!(Assets::force_transfer(Origin::signed(3), 0, 2, 3, 100)); - assert_ok!(Assets::burn(Origin::signed(3), 0, 3, 100)); + assert_ok!(Assets::slash(Origin::signed(3), 0, 3, 100)); }); } @@ -387,7 +387,7 @@ fn transferring_amount_more_than_available_balance_should_not_work() { assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); assert_eq!(Assets::balance(0, 1), 50); assert_eq!(Assets::balance(0, 2), 50); - assert_ok!(Assets::burn(Origin::signed(1), 0, 1, u64::max_value())); + assert_ok!(Assets::slash(Origin::signed(1), 0, 1, u64::max_value())); assert_eq!(Assets::balance(0, 1), 0); assert_noop!(Assets::transfer(Origin::signed(1), 0, 1, 50), Error::::BalanceLow); assert_noop!(Assets::transfer(Origin::signed(2), 0, 1, 51), Error::::BalanceLow); @@ -424,7 +424,7 @@ fn burning_asset_balance_with_positive_balance_should_work() { assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::burn(Origin::signed(1), 0, 1, u64::max_value())); + assert_ok!(Assets::slash(Origin::signed(1), 0, 1, u64::max_value())); assert_eq!(Assets::balance(0, 1), 0); }); } @@ -435,7 +435,7 @@ fn burning_asset_balance_with_zero_balance_does_nothing() { assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 2), 0); - assert_ok!(Assets::burn(Origin::signed(1), 0, 2, u64::max_value())); + assert_ok!(Assets::slash(Origin::signed(1), 0, 2, u64::max_value())); assert_eq!(Assets::balance(0, 2), 0); assert_eq!(Assets::total_supply(0), 100); }); diff --git a/frame/assets/src/types.rs b/frame/assets/src/types.rs index 1b0e71893abf7..ea085cb805930 100644 --- a/frame/assets/src/types.rs +++ b/frame/assets/src/types.rs @@ -125,57 +125,20 @@ pub struct DestroyWitness { pub(super) approvals: u32, } -#[derive(Copy, Clone, PartialEq, Eq)] -pub(super) struct TransferFlags { - /// The debited account must stay alive at the end of the operation; an error is returned if - /// this cannot be achieved legally. - pub(super) keep_alive: bool, - /// Less than the amount specified needs be debited by the operation for it to be considered - /// successful. If `false`, then the amount debited will always be at least the amount - /// specified. - pub(super) best_effort: bool, - /// Any additional funds debited (due to minimum balance requirements) should be burned rather - /// than credited to the destination account. - pub(super) burn_dust: bool, -} - #[derive(Copy, Clone, PartialEq, Eq)] pub(super) struct DebitFlags { /// The debited account must stay alive at the end of the operation; an error is returned if /// this cannot be achieved legally. pub(super) keep_alive: bool, - /// Less than the amount specified needs be debited by the operation for it to be considered - /// successful. If `false`, then the amount debited will always be at least the amount - /// specified. - pub(super) best_effort: bool, /// Ignore the freezer. Don't set this to true unless you actually are the underlying freezer. pub(super) ignore_freezer: bool, } -#[derive(Copy, Clone, PartialEq, Eq)] -pub(super) struct DecreaseFlags { - /// The debited account must stay alive at the end of the operation; an error is returned if - /// this cannot be achieved legally. - pub(super) keep_alive: bool, - /// Ignore the freezer. Don't set this to true unless you actually are the underlying freezer. - pub(super) ignore_freezer: bool, -} - -impl From for DebitFlags { - fn from(f: TransferFlags) -> Self { +impl From for DebitFlags { + fn from(death: WhenDust) -> Self { Self { - keep_alive: f.keep_alive, - best_effort: f.best_effort, + keep_alive: death.keep_alive(), ignore_freezer: false, } } } - -impl From for DecreaseFlags { - fn from(f: DebitFlags) -> Self { - Self { - keep_alive: f.keep_alive, - ignore_freezer: f.ignore_freezer, - } - } -} diff --git a/frame/assets/src/weights.rs b/frame/assets/src/weights.rs index c3c804a392dbe..bc581e3924c80 100644 --- a/frame/assets/src/weights.rs +++ b/frame/assets/src/weights.rs @@ -48,7 +48,7 @@ pub trait WeightInfo { fn force_create() -> Weight; fn destroy(c: u32, s: u32, a: u32, ) -> Weight; fn mint() -> Weight; - fn burn() -> Weight; + fn slash() -> Weight; fn transfer() -> Weight; fn transfer_keep_alive() -> Weight; fn force_transfer() -> Weight; @@ -103,7 +103,7 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } - fn burn() -> Weight { + fn slash() -> Weight { (46_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -237,7 +237,7 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } - fn burn() -> Weight { + fn slash() -> Weight { (46_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index 89420e4fe99bd..4166a845858a7 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -164,8 +164,9 @@ use frame_support::{ Currency, OnUnbalanced, TryDrop, StoredMap, WithdrawReasons, LockIdentifier, LockableCurrency, ExistenceRequirement, Imbalance, SignedImbalance, ReservableCurrency, Get, ExistenceRequirement::KeepAlive, - ExistenceRequirement::AllowDeath, - tokens::{fungible, DepositConsequence, WithdrawConsequence, BalanceStatus as Status} + ExistenceRequirement::AllowDeath, tokens::{ + fungible, DepositConsequence, WithdrawConsequence, BalanceStatus as Status, WhenDust + } } }; #[cfg(feature = "std")] @@ -975,7 +976,7 @@ impl, I: 'static> fungible::Mutate for Pallet { fn burn_from(who: &T::AccountId, amount: Self::Balance) -> Result { if amount.is_zero() { return Ok(Self::Balance::zero()); } let actual = Self::try_mutate_account(who, |account, _is_new| -> Result { - let extra = Self::withdraw_consequence(who, amount, &account).into_result()?; + let extra = Self::withdraw_consequence(who, amount, &account).into_result(false)?; let actual = amount + extra; account.free -= actual; Ok(actual) @@ -990,9 +991,9 @@ impl, I: 'static> fungible::Transfer for Pallet source: &T::AccountId, dest: &T::AccountId, amount: T::Balance, - keep_alive: bool, + death: WhenDust, ) -> Result { - let er = if keep_alive { KeepAlive } else { AllowDeath }; + let er = if death.keep_alive() { KeepAlive } else { AllowDeath }; >::transfer(source, dest, amount, er) .map(|_| amount) } diff --git a/frame/support/src/traits/tokens.rs b/frame/support/src/traits/tokens.rs index 2007627d58c74..0e3bce12d0c18 100644 --- a/frame/support/src/traits/tokens.rs +++ b/frame/support/src/traits/tokens.rs @@ -24,6 +24,6 @@ pub mod imbalance; mod misc; pub use misc::{ WithdrawConsequence, DepositConsequence, ExistenceRequirement, BalanceStatus, WithdrawReasons, - FrozenBalance, + FrozenBalance, WhenDust, }; pub use imbalance::Imbalance; diff --git a/frame/support/src/traits/tokens/fungible.rs b/frame/support/src/traits/tokens/fungible.rs index 432a5da0dc3b9..2f5a9db361a03 100644 --- a/frame/support/src/traits/tokens/fungible.rs +++ b/frame/support/src/traits/tokens/fungible.rs @@ -21,7 +21,7 @@ use super::*; use sp_runtime::traits::Saturating; use crate::traits::misc::Get; use crate::dispatch::{DispatchResult, DispatchError}; -use super::misc::{DepositConsequence, WithdrawConsequence, Balance}; +use super::misc::{DepositConsequence, WithdrawConsequence, Balance, WhenDust}; mod balanced; mod imbalance; @@ -91,7 +91,7 @@ pub trait Mutate: Inspect { dest: &AccountId, amount: Self::Balance, ) -> Result { - let extra = Self::can_withdraw(&source, amount).into_result()?; + let extra = Self::can_withdraw(&source, amount).into_result(false)?; Self::can_deposit(&dest, amount.saturating_add(extra)).into_result()?; let actual = Self::burn_from(source, amount)?; debug_assert!(actual == amount.saturating_add(extra), "can_withdraw must agree with withdraw; qed"); @@ -110,13 +110,33 @@ pub trait Mutate: Inspect { /// Trait for providing a fungible asset which can only be transferred. pub trait Transfer: Inspect { - /// Transfer funds from one account into another. + /// Transfer `amount` of funds from `source` account into `dest`, possibly transferring a + /// little more depending on the value of `death`. + /// + /// If successful, will return the amount transferred, which will never be less than `amount`. + /// On error, nothing will be done and an `Err` returned. fn transfer( source: &AccountId, dest: &AccountId, amount: Self::Balance, - keep_alive: bool, + death: WhenDust, ) -> Result; + + /// Transfer `amount` of funds, or as much of them that are available for transfer, from + /// `source` account into `dest`, possibly transferring a little more depending on the value of + /// `death`. + /// + /// If successful, will return the amount transferred. On error, nothing will be done and an + /// `Err` returned. + fn transfer_best_effort( + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + death: WhenDust, + ) -> Result { + let possible = Self::reducible_balance(source, death.keep_alive()); + Self::transfer(source, dest, amount.min(possible), death) + } } /// Trait for inspecting a fungible asset which can be reserved. @@ -158,7 +178,7 @@ pub trait MutateHold: Inspect { dest: &AccountId, amount: Self::Balance, best_effort: bool, - on_held: bool, + on_hold: bool, ) -> Result; } @@ -170,23 +190,8 @@ pub trait BalancedHold: Balanced { /// /// 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, Self::Balance); -} - -impl< - AccountId, - T: Balanced + MutateHold, -> BalancedHold for T { - fn slash_held(who: &AccountId, amount: Self::Balance) - -> (CreditOf, Self::Balance) - { - let actual = match Self::release(who, amount, true) { - Ok(x) => x, - Err(_) => return (Imbalance::default(), amount), - }; - >::slash(who, actual) - } + fn slash_held(who: &AccountId, amount: Self::Balance, best_effort: bool) + -> Result, DispatchError>; } /// Convert a `fungibles` trait implementation into a `fungible` trait implementation by identifying @@ -256,10 +261,16 @@ impl< A: Get<>::AssetId>, AccountId, > Transfer for ItemOf { - fn transfer(source: &AccountId, dest: &AccountId, amount: Self::Balance, keep_alive: bool) + fn transfer(source: &AccountId, dest: &AccountId, amount: Self::Balance, death: WhenDust) + -> Result + { + >::transfer(A::get(), source, dest, amount, death) + } + + fn transfer_best_effort(source: &AccountId, dest: &AccountId, amount: Self::Balance, death: WhenDust) -> Result { - >::transfer(A::get(), source, dest, amount, keep_alive) + >::transfer_best_effort(A::get(), source, dest, amount, death) } } @@ -318,13 +329,13 @@ impl< fn set_total_issuance(amount: Self::Balance) -> () { >::set_total_issuance(A::get(), amount) } - fn decrease_balance(who: &AccountId, amount: Self::Balance) -> Result { - >::decrease_balance(A::get(), who, amount) + fn decrease_balance(who: &AccountId, amount: Self::Balance, keep_alive: bool) -> Result { + >::decrease_balance(A::get(), who, amount, keep_alive) } - fn decrease_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance { - >::decrease_balance_at_most(A::get(), who, amount) + fn decrease_balance_at_most(who: &AccountId, amount: Self::Balance, keep_alive: bool) -> Self::Balance { + >::decrease_balance_at_most(A::get(), who, amount, keep_alive) } - fn increase_balance(who: &AccountId, amount: Self::Balance) -> Result { + fn increase_balance(who: &AccountId, amount: Self::Balance) -> Result<(), DispatchError> { >::increase_balance(A::get(), who, amount) } fn increase_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance { diff --git a/frame/support/src/traits/tokens/fungible/balanced.rs b/frame/support/src/traits/tokens/fungible/balanced.rs index 19bdb4f245ee8..e490179cfa9c5 100644 --- a/frame/support/src/traits/tokens/fungible/balanced.rs +++ b/frame/support/src/traits/tokens/fungible/balanced.rs @@ -90,7 +90,7 @@ pub trait Balanced: Inspect { fn withdraw( who: &AccountId, value: Self::Balance, - //TODO: liveness: ExistenceRequirement, + keep_alive: bool, ) -> Result, DispatchError>; /// The balance of `who` is increased in order to counter `credit`. If the whole of `credit` @@ -119,10 +119,10 @@ pub trait Balanced: Inspect { fn settle( who: &AccountId, debt: DebtOf, - //TODO: liveness: ExistenceRequirement, + keep_alive: bool, ) -> Result, DebtOf> { let amount = debt.peek(); - let credit = match Self::withdraw(who, amount) { + let credit = match Self::withdraw(who, amount, keep_alive) { Err(_) => return Err(debt), Ok(d) => d, }; @@ -148,6 +148,9 @@ pub trait Balanced: Inspect { pub trait Unbalanced: Inspect { /// 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. + /// + /// This only needs to be implemented if `decrease_balance` and `increase_balance` are left + /// to their default implementations. fn set_balance(who: &AccountId, amount: Self::Balance) -> DispatchResult; /// Set the total issuance to `amount`. @@ -158,9 +161,10 @@ pub trait Unbalanced: Inspect { /// /// 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) + fn decrease_balance(who: &AccountId, amount: Self::Balance, keep_alive: bool) -> Result { + Self::can_withdraw(who, amount).into_result(keep_alive)?; let old_balance = Self::balance(who); let (mut new_balance, mut amount) = if old_balance < amount { Err(TokenError::NoFunds)? @@ -176,49 +180,13 @@ pub trait Unbalanced: Inspect { 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 (mut new_balance, mut amount) = if old_balance < amount { - (Zero::zero(), old_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 + -> Result<(), DispatchError> { let old_balance = Self::balance(who); let new_balance = old_balance.checked_add(&amount).ok_or(TokenError::Overflow)?; @@ -228,30 +196,34 @@ pub trait Unbalanced: Inspect { if old_balance != new_balance { Self::set_balance(who, new_balance)?; } - Ok(amount) + Ok(()) + } + + /// Reduce the balance of `who` by the most that is possible, up to `amount`. + /// + /// Minimum balance will be respected and the returned amount may be up to + /// `Self::minimum_balance() - 1` greater than `amount`. + /// + /// Return the amount by which the account was reduced. + /// + /// NOTE: This contains a default implementation that should be sufficient in most + /// circumstances. + fn decrease_balance_at_most(who: &AccountId, amount: Self::Balance, keep_alive: bool) -> Self::Balance { + let amount = amount.min(Self::reducible_balance(who, keep_alive)); + Self::decrease_balance(who, amount, keep_alive).unwrap_or(Zero::zero()) } /// 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 + /// Minimum balance will be respected and the returned amount 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() - } + /// Return the amount by which the account was increased. + /// + /// NOTE: This contains a default implementation that should be sufficient in most + /// circumstances. + fn increase_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance { + Self::increase_balance(who, amount).map_or(Zero::zero(), |_| amount) } } @@ -332,7 +304,7 @@ impl> Balanced for U { who: &AccountId, amount: Self::Balance, ) -> (Credit, Self::Balance) { - let slashed = U::decrease_balance_at_most(who, amount); + let slashed = U::decrease_balance_at_most(who, amount, false); // `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. @@ -346,15 +318,16 @@ impl> Balanced for U { who: &AccountId, amount: Self::Balance ) -> Result, DispatchError> { - let increase = U::increase_balance(who, amount)?; - Ok(debt(increase)) + U::increase_balance(who, amount)?; + Ok(debt(amount)) } fn withdraw( who: &AccountId, amount: Self::Balance, - //TODO: liveness: ExistenceRequirement, + keep_alive: bool, ) -> Result, DispatchError> { - let decrease = U::decrease_balance(who, amount)?; + Self::can_withdraw(who, amount).into_result(keep_alive)?; + let decrease = U::decrease_balance(who, amount, keep_alive)?; Ok(credit(decrease)) } } diff --git a/frame/support/src/traits/tokens/fungibles.rs b/frame/support/src/traits/tokens/fungibles.rs index b2db85f2af8e8..ee2df9be90d7c 100644 --- a/frame/support/src/traits/tokens/fungibles.rs +++ b/frame/support/src/traits/tokens/fungibles.rs @@ -19,7 +19,7 @@ use super::*; use crate::dispatch::{DispatchError, DispatchResult}; -use super::misc::{AssetId, Balance}; +use super::misc::{AssetId, Balance, WhenDust}; use sp_runtime::traits::Saturating; mod balanced; @@ -136,7 +136,7 @@ pub trait Mutate: Inspect { dest: &AccountId, amount: Self::Balance, ) -> Result { - let extra = Self::can_withdraw(asset, &source, amount).into_result()?; + let extra = Self::can_withdraw(asset, &source, amount).into_result(false)?; Self::can_deposit(asset, &dest, amount.saturating_add(extra)).into_result()?; let actual = Self::burn_from(asset, source, amount)?; debug_assert!(actual == amount.saturating_add(extra), "can_withdraw must agree with withdraw; qed"); @@ -155,15 +155,35 @@ pub trait Mutate: Inspect { /// Trait for providing a set of named fungible assets which can only be transferred. pub trait Transfer: Inspect { - /// Transfer funds from one account into another. + /// Transfer `amount` of `asset` from `source` account into `dest`, possibly transferring a + /// little more depending on the value of `death`. + /// + /// If successful, will return the amount transferred, which will never be less than `amount`. + /// On error, nothing will be done and an `Err` returned. fn transfer( asset: Self::AssetId, source: &AccountId, dest: &AccountId, amount: Self::Balance, -//TODO: best_effort: bool, - keep_alive: bool, + death: WhenDust, ) -> Result; + + /// Transfer `amount` of `asset` from `source` account into `dest`, or as much of it that are + /// available for transfer, possibly transferring a little more depending on the value of + /// `death`. + /// + /// If successful, will return the amount transferred. On error, nothing will be done and an + /// `Err` returned. + fn transfer_best_effort( + asset: Self::AssetId, + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + death: WhenDust, + ) -> Result { + let possible = Self::reducible_balance(asset, source, death.keep_alive()); + Self::transfer(asset, source, dest, amount.min(possible), death) + } } /// Trait for inspecting a set of named fungible assets which can be placed on hold. @@ -216,21 +236,6 @@ pub trait BalancedHold: Balanced { /// /// 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, Self::Balance); -} - -impl< - AccountId, - T: Balanced + MutateHold, -> BalancedHold for T { - fn slash_held(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) - -> (CreditOf, Self::Balance) - { - let actual = match Self::release(asset, who, amount, true) { - Ok(x) => x, - Err(_) => return (Imbalance::zero(asset), amount), - }; - >::slash(asset, who, actual) - } + fn slash_held(asset: Self::AssetId, who: &AccountId, amount: Self::Balance, best_effort: bool) + -> Result, DispatchError>; } diff --git a/frame/support/src/traits/tokens/fungibles/balanced.rs b/frame/support/src/traits/tokens/fungibles/balanced.rs index efb21300bcaa8..802bcf9775adb 100644 --- a/frame/support/src/traits/tokens/fungibles/balanced.rs +++ b/frame/support/src/traits/tokens/fungibles/balanced.rs @@ -96,7 +96,7 @@ pub trait Balanced: Inspect { asset: Self::AssetId, who: &AccountId, value: Self::Balance, - //TODO: liveness: ExistenceRequirement, + keep_alive: bool, ) -> Result, DispatchError>; /// The balance of `who` is increased in order to counter `credit`. If the whole of `credit` @@ -129,11 +129,11 @@ pub trait Balanced: Inspect { fn settle( who: &AccountId, debt: DebtOf, - //TODO: liveness: ExistenceRequirement, + keep_alive: bool, ) -> Result, DebtOf> { let amount = debt.peek(); let asset = debt.asset(); - let credit = match Self::withdraw(asset, who, amount) { + let credit = match Self::withdraw(asset, who, amount, keep_alive) { Err(_) => return Err(debt), Ok(d) => d, }; @@ -163,6 +163,9 @@ pub trait Balanced: Inspect { pub trait Unbalanced: Inspect { /// Set the `asset` 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. + /// + /// This only needs to be implemented if `decrease_balance` and `increase_balance` are left + /// to their default implementations. fn set_balance(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> DispatchResult; /// Set the total issuance of `asset` to `amount`. @@ -173,9 +176,10 @@ pub trait Unbalanced: Inspect { /// /// Minimum balance will be respected and the returned imbalance may be up to /// `Self::minimum_balance() - 1` greater than `amount`. - fn decrease_balance(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) + fn decrease_balance(asset: Self::AssetId, who: &AccountId, amount: Self::Balance, keep_alive: bool) -> Result { + Self::can_withdraw(asset, who, amount).into_result(keep_alive)?; let old_balance = Self::balance(asset, who); let (mut new_balance, mut amount) = if old_balance < amount { Err(TokenError::NoFunds)? @@ -191,49 +195,13 @@ pub trait Unbalanced: Inspect { Ok(amount) } - /// Reduce the `asset` 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(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) - -> Self::Balance - { - let old_balance = Self::balance(asset, who); - let (mut new_balance, mut amount) = if old_balance < amount { - (Zero::zero(), old_balance) - } else { - (old_balance - amount, amount) - }; - let minimum_balance = Self::minimum_balance(asset); - if new_balance < minimum_balance { - amount = amount.saturating_add(new_balance); - new_balance = Zero::zero(); - } - let mut r = Self::set_balance(asset, 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(asset, 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 `asset` 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(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) - -> Result + -> Result<(), DispatchError> { let old_balance = Self::balance(asset, who); let new_balance = old_balance.checked_add(&amount).ok_or(TokenError::Overflow)?; @@ -243,30 +211,38 @@ pub trait Unbalanced: Inspect { if old_balance != new_balance { Self::set_balance(asset, who, new_balance)?; } - Ok(amount) + Ok(()) + } + + /// Reduce the `asset` balance of `who` by the most that is possible, up to `amount`. + /// + /// Minimum balance will be respected and the returned amount may be up to + /// `Self::minimum_balance() - 1` greater than `amount`. + /// + /// Return the amount by which the account was reduced. + /// + /// NOTE: This contains a default implementation that should be sufficient in most + /// circumstances. + fn decrease_balance_at_most(asset: Self::AssetId, who: &AccountId, amount: Self::Balance, keep_alive: bool) + -> Self::Balance + { + let amount = amount.min(Self::reducible_balance(asset, who, keep_alive)); + Self::decrease_balance(asset, who, amount, keep_alive).unwrap_or(Zero::zero()) } /// Increase the `asset` 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 + /// Minimum balance will be respected and the returned amount will be zero in the case that /// `amount < Self::minimum_balance()`. /// - /// Return the imbalance by which the account was increased. + /// Return the amount by which the account was increased. + /// + /// NOTE: This contains a default implementation that should be sufficient in most + /// circumstances. fn increase_balance_at_most(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> Self::Balance { - let old_balance = Self::balance(asset, who); - let mut new_balance = old_balance.saturating_add(amount); - let mut amount = new_balance - old_balance; - if new_balance < Self::minimum_balance(asset) { - new_balance = Zero::zero(); - amount = Zero::zero(); - } - if old_balance == new_balance || Self::set_balance(asset, who, new_balance).is_ok() { - amount - } else { - Zero::zero() - } + Self::increase_balance(asset, who, amount).map_or(Zero::zero(), |_| amount) } } @@ -348,7 +324,7 @@ impl> Balanced for U { who: &AccountId, amount: Self::Balance, ) -> (Credit, Self::Balance) { - let slashed = U::decrease_balance_at_most(asset, who, amount); + let slashed = U::decrease_balance_at_most(asset, who, amount, false); // `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. @@ -363,16 +339,16 @@ impl> Balanced for U { who: &AccountId, amount: Self::Balance ) -> Result, DispatchError> { - let increase = U::increase_balance(asset, who, amount)?; - Ok(debt(asset, increase)) + U::increase_balance(asset, who, amount)?; + Ok(debt(asset, amount)) } fn withdraw( asset: Self::AssetId, who: &AccountId, amount: Self::Balance, - //TODO: liveness: ExistenceRequirement, + keep_alive: bool, ) -> Result, DispatchError> { - let decrease = U::decrease_balance(asset, who, amount)?; + let decrease = U::decrease_balance(asset, who, amount, keep_alive)?; Ok(credit(asset, decrease)) } } diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index df82262608217..d2c670056b97c 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -53,32 +53,17 @@ pub enum WithdrawConsequence { impl WithdrawConsequence { /// Convert the type into a `Result` with `TokenError` as the error or the additional `Balance` /// by which the account will be reduced. - pub fn into_result(self) -> Result { + pub fn into_result(self, keep_alive: bool) -> Result { use WithdrawConsequence::*; match self { - NoFunds => Err(TokenError::NoFunds), - WouldDie => Err(TokenError::WouldDie), - UnknownAsset => Err(TokenError::UnknownAsset), - Underflow => Err(TokenError::Underflow), - Overflow => Err(TokenError::Overflow), - Frozen => Err(TokenError::Frozen), - ReducedToZero(result) => Ok(result), Success => Ok(Zero::zero()), - } - } - - /// Convert the type into a `Result` with `TokenError` as the error. An error will be returned - /// if the account were to be destroyed. - pub fn into_result_keep_alive(self) -> Result<(), TokenError> { - use WithdrawConsequence::*; - match self { - NoFunds => Err(TokenError::NoFunds), + ReducedToZero(result) if !keep_alive => Ok(result), WouldDie | ReducedToZero(_) => Err(TokenError::WouldDie), UnknownAsset => Err(TokenError::UnknownAsset), Underflow => Err(TokenError::Underflow), Overflow => Err(TokenError::Overflow), Frozen => Err(TokenError::Frozen), - Success => Ok(()), + NoFunds => Err(TokenError::NoFunds), } } } @@ -139,6 +124,28 @@ pub enum BalanceStatus { Reserved, } +/// When happens if/when a debited account has been reduced below the dust threshold (aka "minimum +/// balance" or "existential deposit"). +#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)] +pub enum WhenDust { + /// Operation must not result in the account going out of existence. + KeepAlive, + /// Any dust resulting from the deletion of the debited account should be disposed of. + Dispose, + /// Any dust resulting from the deletion of the debited account should be added to the + /// resulting credit. + Credit, +} + +impl WhenDust { + /// Return `true` if the account balance should be disposed of should it fall below minimum. + pub fn dispose(self) -> bool { matches!(self, WhenDust::Dispose) } + + /// Return `true` if the account balance should neve be allowed to fall below minimum. + pub fn keep_alive(self) -> bool { matches!(self, WhenDust::KeepAlive) } +} + + /// Trait for allowing a minimum balance on the account to be specified, beyond the /// `minimum_balance` of the asset. This is additive - the `minimum_balance` of the asset must be /// met *and then* anything here in addition. From 109621e1ea3efeffe3c1bc9bf019134085593f0a Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 29 Mar 2021 17:05:13 +0200 Subject: [PATCH 05/22] Balanced Hold impl --- frame/assets-freezer/src/lib.rs | 156 +++++++++++++----- frame/support/src/traits/tokens/fungible.rs | 12 -- .../src/traits/tokens/fungible/balanced.rs | 40 ++++- frame/support/src/traits/tokens/fungibles.rs | 14 +- .../src/traits/tokens/fungibles/balanced.rs | 48 +++++- 5 files changed, 203 insertions(+), 67 deletions(-) diff --git a/frame/assets-freezer/src/lib.rs b/frame/assets-freezer/src/lib.rs index ba62b9a775580..989ecf175c052 100644 --- a/frame/assets-freezer/src/lib.rs +++ b/frame/assets-freezer/src/lib.rs @@ -47,6 +47,8 @@ use frame_system::Config as SystemConfig; //pub use weights::WeightInfo; pub use pallet::*; +use frame_benchmarking::frame_support::dispatch::result::Result::{Err, Ok}; +use frame_benchmarking::frame_support::traits::fungibles::{Unbalanced, Inspect}; type BalanceOf = <::Assets as fungibles::Inspect<::AccountId>>::Balance; type AssetIdOf = <::Assets as fungibles::Inspect<::AccountId>>::AssetId; @@ -105,12 +107,9 @@ pub mod pallet { Released(AssetIdOf, T::AccountId, BalanceOf), } - // No new errors? + // No new errors #[pallet::error] - pub enum Error { - /// The origin account is frozen. - Frozen, - } + pub enum Error {} // No hooks. #[pallet::hooks] @@ -118,9 +117,7 @@ pub mod pallet { // Only admin calls. #[pallet::call] - impl Pallet { - - } + impl Pallet {} } impl FrozenBalance, T::AccountId, BalanceOf> for Pallet { @@ -167,10 +164,64 @@ impl fungibles::Transfer<::AccountId> for Pallet Result { - T::Assets::transfer(asset, source, dest, amount, best_effort, death) + T::Assets::transfer(asset, source, dest, amount, death) + } + fn transfer_best_effort( + asset: Self::AssetId, + source: &T::AccountId, + dest: &T::AccountId, + amount: Self::Balance, + death: WhenDust, + ) -> Result { + T::Assets::transfer_best_effort(asset, source, dest, amount, death) + } +} + +impl fungibles::Unbalanced for Pallet where + T::Assets: fungibles::Unbalanced +{ + fn set_balance(asset: Self::AssetId, who: &T::AccountId, amount: Self::Balance) + -> DispatchResult + { + T::Assets::set_balance(asset, who, amount) + } + + fn set_total_issuance(asset: Self::AssetId, amount: Self::Balance) { + T::Assets::set_total_issuance(asset, amount) + } + + fn decrease_balance( + asset: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + keep_alive: bool, + ) -> Result { + T::Assets::decrease_balance(asset, who, amount, keep_alive) + } + + fn increase_balance(asset: Self::AssetId, who: &T::AccountId, amount: Self::Balance) + -> Result<(), DispatchError> + { + T::Assets::increase_balance(asset, who, amount) + } + + fn decrease_balance_at_most( + asset: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + keep_alive: bool, + ) -> Self::Balance { + T::Assets::decrease_balance_at_most(asset, who, amount, keep_alive) + } + + fn increase_balance_at_most( + asset: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + ) -> Self::Balance { + T::Assets::increase_balance_at_most(asset, who, amount) } } @@ -224,15 +275,66 @@ impl fungibles::MutateHold<::AccountId> for Pallet best_effort: bool, on_hold: bool, ) -> Result, DispatchError> { - use fungibles::{InspectHold, Transfer, InspectWithoutFreezer}; - // Can't create the account with just a chunk of held balance - there needs to already be // the minimum deposit. let min_balance = > ::minimum_balance(asset); let dest_balance = >::balance(asset, dest); ensure!(!on_hold || dest_balance >= min_balance, TokenError::CannotCreate); - let actual = T::Store::try_mutate_exists( + let actual = Self::decrease_on_hold(asset, source, amount, best_effort)?; + + // `death` is `KeepAlive` here since we're only transferring funds that were on hold, for + // which there must be an additional min_balance, it should be impossible for the transfer + // to cause the account to be deleted. + use fungibles::Transfer; + let result = Self::transfer(asset, source, dest, actual, WhenDust::KeepAlive); + if result.is_ok() { + if on_hold { + let r = T::Store::mutate( + &(asset, dest.clone()), + |extra| extra.reserved = extra.reserved.saturating_add(actual), + ); + debug_assert!(r.is_ok(), "account exists and funds transferred in; qed"); + r? + } + } else { + debug_assert!(false, "can_withdraw was successful; qed"); + } + result + } +} + +impl fungibles::UnbalancedHold<::AccountId> for Pallet where + T::Assets: fungibles::Unbalanced +{ + fn decrease_balance_on_hold( + asset: AssetIdOf, + source: &T::AccountId, + amount: BalanceOf, + best_effort: bool, + ) -> Result, DispatchError> { + let actual = Self::decrease_on_hold(asset, source, amount, best_effort)?; + // The previous call's success guarantees the next will succeed. + >::decrease_balance(asset, source, actual, true) + } +} + +impl Pallet { + /// Reduce the amount we have on hold of an account in such a way to ensure that the balance + /// should be decreasable by the amount reduced. + /// + /// NOTE: This won't alter the balance of the account. + fn decrease_on_hold( + asset: AssetIdOf, + source: &T::AccountId, + amount: BalanceOf, + best_effort: bool, + ) -> Result, DispatchError> { + use fungibles::{InspectHold, Transfer, InspectWithoutFreezer}; + + let min_balance = >::minimum_balance(asset); + + T::Store::try_mutate_exists( &(asset, source.clone()), |maybe_extra| -> Result, DispatchError> { @@ -250,9 +352,9 @@ impl fungibles::MutateHold<::AccountId> for Pallet ensure!(balance_left >= actual, TokenError::NoFunds); // the balance for the reserved amount actually exists. now we check that it's - // possible to actually transfer it out. the only reason this would fail is if the - // asset class or account is completely frozen. - >::can_withdraw(asset, dest, actual) + // possible to actually transfer it out. practically, the only reason this would + // fail is if the asset class or account is completely frozen. + >::can_withdraw(asset, source, actual) .into_result(true)?; // all good. we should now be able to unreserve and transfer without any error. @@ -260,26 +362,6 @@ impl fungibles::MutateHold<::AccountId> for Pallet } else { Err(TokenError::NoFunds)? } - })?; - - // `best_effort` is `false` here since we've already checked that it should go through in - // the previous logic. if it fails here, then it must be due to a logic error. - // `death` is `KeepAlive` here since we're only transferring funds that were on hold, for - // which there must be an additional min_balance, it should be impossible for the transfer - // to cause the account to be deleted. - let result = Self::transfer(asset, source, dest, actual, true, WhenDust::KeepAlive); - if result.is_ok() { - if on_hold { - let r = T::Store::mutate( - &(asset, dest.clone()), - |extra| extra.reserved = extra.reserved.saturating_add(actual), - ); - debug_assert!(r.is_ok(), "account exists and funds transferred in; qed"); - r? - } - } else { - debug_assert!(false, "can_withdraw was successful; qed"); - } - result + }) } } diff --git a/frame/support/src/traits/tokens/fungible.rs b/frame/support/src/traits/tokens/fungible.rs index 2f5a9db361a03..c34c0a75b390b 100644 --- a/frame/support/src/traits/tokens/fungible.rs +++ b/frame/support/src/traits/tokens/fungible.rs @@ -182,18 +182,6 @@ pub trait MutateHold: Inspect { ) -> Result; } -/// Trait for slashing a fungible asset which can be reserved. -pub trait BalancedHold: Balanced { - /// 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, best_effort: bool) - -> Result, DispatchError>; -} - /// Convert a `fungibles` trait implementation into a `fungible` trait implementation by identifying /// a single item. pub struct ItemOf< diff --git a/frame/support/src/traits/tokens/fungible/balanced.rs b/frame/support/src/traits/tokens/fungible/balanced.rs index e490179cfa9c5..7fa31184cd499 100644 --- a/frame/support/src/traits/tokens/fungible/balanced.rs +++ b/frame/support/src/traits/tokens/fungible/balanced.rs @@ -137,6 +137,16 @@ pub trait Balanced: Inspect { } } +/// Trait for mutating one of several types of fungible assets which can be held. +pub trait BalancedHold: Unbalanced { + /// Release and slash some as much funds on hold in an account up to `amount`. + /// + /// The resulting imbalance is the first item of the tuple returned; the second is the + /// remainder, if any, from `amount`. + fn slash_held(who: &AccountId, amount: Self::Balance) + -> (Credit, Self::Balance); +} + /// A fungible token class where the balance can be set arbitrarily. /// /// **WARNING** @@ -145,7 +155,7 @@ pub trait Balanced: Inspect { /// 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: Inspect { +pub trait Unbalanced: Inspect + Sized { /// 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. /// @@ -227,6 +237,23 @@ pub trait Unbalanced: Inspect { } } +/// A fungible token class capable of placing funds on hold where the balance can be changed +/// arbitrarily. +pub trait UnbalancedHold: Unbalanced { + /// Reduce the `asset` balance of `who` by `amount` from the funds on hold. + /// + /// If successful, then the amount decreased is returned. + /// + /// If `best_effort` is false then the amount reduced may be below the `amount` given. + /// + /// If it cannot be validly reduced, return `Err` and do nothing. + fn decrease_balance_on_hold( + who: &AccountId, + amount: Self::Balance, + best_effort: bool, + ) -> Result; +} + /// 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(PhantomData<(AccountId, U)>); @@ -331,3 +358,14 @@ impl> Balanced for U { Ok(credit(decrease)) } } + +impl> BalancedHold for U { + fn slash_held( + who: &AccountId, + amount: Self::Balance, + ) -> (Credit, Self::Balance) { + let slashed = U::decrease_balance_on_hold(who, amount, true) + .unwrap_or(Zero::zero()); + (credit(slashed), amount.saturating_sub(slashed)) + } +} diff --git a/frame/support/src/traits/tokens/fungibles.rs b/frame/support/src/traits/tokens/fungibles.rs index ee2df9be90d7c..bb64afb66a55c 100644 --- a/frame/support/src/traits/tokens/fungibles.rs +++ b/frame/support/src/traits/tokens/fungibles.rs @@ -23,7 +23,7 @@ use super::misc::{AssetId, Balance, WhenDust}; use sp_runtime::traits::Saturating; mod balanced; -pub use balanced::{Balanced, Unbalanced}; +pub use balanced::{Balanced, Unbalanced, BalancedHold, UnbalancedHold}; mod imbalance; pub use imbalance::{Imbalance, HandleImbalanceDrop, DebtOf, CreditOf}; @@ -227,15 +227,3 @@ pub trait MutateHold: Inspect { on_hold: bool, ) -> Result; } - -/// Trait for mutating one of several types of fungible assets which can be held. -pub trait BalancedHold: Balanced { - /// 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, best_effort: bool) - -> Result, DispatchError>; -} diff --git a/frame/support/src/traits/tokens/fungibles/balanced.rs b/frame/support/src/traits/tokens/fungibles/balanced.rs index 802bcf9775adb..b4875d08a3194 100644 --- a/frame/support/src/traits/tokens/fungibles/balanced.rs +++ b/frame/support/src/traits/tokens/fungibles/balanced.rs @@ -56,7 +56,7 @@ pub trait Balanced: Inspect { /// This is just the same as burning and issuing the same amount and has no effect on the /// total issuance. fn pair(asset: Self::AssetId, amount: Self::Balance) - -> (DebtOf, CreditOf) + -> (DebtOf, CreditOf) { (Self::rescind(asset, amount), Self::issue(asset, amount)) } @@ -152,6 +152,16 @@ pub trait Balanced: Inspect { } } +/// Trait for mutating one of several types of fungible assets which can be held. +pub trait BalancedHold: Unbalanced { + /// Release and slash some as much funds on hold in an account up to `amount`. + /// + /// The resulting imbalance is the first item of the tuple returned; the second is the + /// remainder, if any, from `amount`. + fn slash_held(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) + -> (Credit, Self::Balance); +} + /// A fungible token class where the balance can be set arbitrarily. /// /// **WARNING** @@ -160,7 +170,7 @@ pub trait Balanced: Inspect { /// 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: Inspect { +pub trait Unbalanced: Inspect + Sized { /// Set the `asset` 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. /// @@ -246,9 +256,27 @@ pub trait Unbalanced: Inspect { } } +/// A fungible token class capable of placing funds on hold where the balance can be changed +/// arbitrarily. +pub trait UnbalancedHold: Unbalanced { + /// Reduce the `asset` balance of `who` by `amount` from the funds on hold. + /// + /// If successful, then the amount decreased is returned. + /// + /// If `best_effort` is false then the amount reduced may be below the `amount` given. + /// + /// If it cannot be validly reduced, return `Err` and do nothing. + fn decrease_balance_on_hold( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + best_effort: bool, + ) -> Result; +} + /// 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(PhantomData<(AccountId, U)>); +pub struct IncreaseIssuance(PhantomData<(AccountId, U)>); impl> HandleImbalanceDrop for IncreaseIssuance { @@ -259,7 +287,7 @@ impl> HandleImbalanceDrop(PhantomData<(AccountId, U)>); +pub struct DecreaseIssuance(PhantomData<(AccountId, U)>); impl> HandleImbalanceDrop for DecreaseIssuance { @@ -352,3 +380,15 @@ impl> Balanced for U { Ok(credit(asset, decrease)) } } + +impl> BalancedHold for U { + fn slash_held( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> (Credit, Self::Balance) { + let slashed = U::decrease_balance_on_hold(asset, who, amount, true) + .unwrap_or(Zero::zero()); + (credit(asset, slashed), amount.saturating_sub(slashed)) + } +} From d9a5250ea30339597b963c61a357ac0e9fe41ec1 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 29 Mar 2021 17:25:25 +0200 Subject: [PATCH 06/22] Warnings. --- frame/assets-freezer/src/lib.rs | 46 ++++++++++++--------------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/frame/assets-freezer/src/lib.rs b/frame/assets-freezer/src/lib.rs index 989ecf175c052..d63a017916081 100644 --- a/frame/assets-freezer/src/lib.rs +++ b/frame/assets-freezer/src/lib.rs @@ -21,44 +21,30 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -/* -pub mod weights; -#[cfg(feature = "runtime-benchmarks")] -mod benchmarking; -#[cfg(test)] + +/*#[cfg(test)] pub mod mock; #[cfg(test)] -mod tests; -*/ +mod tests;*/ + use sp_std::prelude::*; -use sp_runtime::{ - RuntimeDebug, TokenError, traits::{ - AtLeast32BitUnsigned, Zero, StaticLookup, Saturating, CheckedSub, CheckedAdd, - StoredMapError, - } -}; -use codec::{Encode, Decode, HasCompact}; +use sp_runtime::{TokenError, traits::{Zero, Saturating}}; use frame_support::{ensure, dispatch::{DispatchError, DispatchResult}}; use frame_support::traits::{ - Currency, ReservableCurrency, BalanceStatus::Reserved, StoredMap, tokens::{ + StoredMap, tokens::{ WithdrawConsequence, DepositConsequence, fungibles, FrozenBalance, WhenDust }}; use frame_system::Config as SystemConfig; //pub use weights::WeightInfo; pub use pallet::*; -use frame_benchmarking::frame_support::dispatch::result::Result::{Err, Ok}; -use frame_benchmarking::frame_support::traits::fungibles::{Unbalanced, Inspect}; type BalanceOf = <::Assets as fungibles::Inspect<::AccountId>>::Balance; type AssetIdOf = <::Assets as fungibles::Inspect<::AccountId>>::AssetId; #[frame_support::pallet] pub mod pallet { - use frame_support::{ - dispatch::DispatchResult, - pallet_prelude::*, - }; + use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; use super::*; @@ -89,9 +75,6 @@ pub mod pallet { /// Place to store the fast-access freeze data for the given asset/account. type Store: StoredMap<(AssetIdOf, Self::AccountId), FreezeData>>; - -// /// Weight information for extrinsics in this pallet. -// type WeightInfo: WeightInfo; } // @@ -115,7 +98,7 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet {} - // Only admin calls. + // No calls. #[pallet::call] impl Pallet {} } @@ -247,6 +230,8 @@ impl fungibles::MutateHold<::AccountId> for Pallet &(asset, who.clone()), |extra| extra.reserved = extra.reserved.saturating_add(amount), )?; + + Self::deposit_event(Event::Held(asset, who.clone(), amount)); Ok(()) } @@ -260,6 +245,9 @@ impl fungibles::MutateHold<::AccountId> for Pallet extra.reserved = extra.reserved.saturating_sub(amount); let actual = old - extra.reserved; ensure!(best_effort || actual == amount, TokenError::NoFunds); + + Self::deposit_event(Event::Released(asset, who.clone(), actual)); + Ok(actual) } else { Err(TokenError::NoFunds)? @@ -330,9 +318,9 @@ impl Pallet { amount: BalanceOf, best_effort: bool, ) -> Result, DispatchError> { - use fungibles::{InspectHold, Transfer, InspectWithoutFreezer}; + use fungibles::Inspect; - let min_balance = >::minimum_balance(asset); + let min_balance = Self::minimum_balance(asset); T::Store::try_mutate_exists( &(asset, source.clone()), @@ -347,14 +335,14 @@ impl Pallet { // actual is how much we can unreserve. now we check that the balance actually // exists in the account. - let balance_left = >::balance(asset, source) + let balance_left = Self::balance(asset, source) .saturating_sub(min_balance); ensure!(balance_left >= actual, TokenError::NoFunds); // the balance for the reserved amount actually exists. now we check that it's // possible to actually transfer it out. practically, the only reason this would // fail is if the asset class or account is completely frozen. - >::can_withdraw(asset, source, actual) + Self::can_withdraw(asset, source, actual) .into_result(true)?; // all good. we should now be able to unreserve and transfer without any error. From 02a697d31657fbea6ce668ba63652d29b3ebb9b6 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 29 Mar 2021 19:13:31 +0200 Subject: [PATCH 07/22] fungible traits for Hold. --- frame/assets-freezer/src/lib.rs | 96 ++++++++++--------- frame/support/src/traits/tokens/fungible.rs | 91 ++++++++++++++++-- .../src/traits/tokens/fungible/balanced.rs | 26 +++-- frame/support/src/traits/tokens/fungibles.rs | 43 +++++++-- .../src/traits/tokens/fungibles/balanced.rs | 24 +++-- 5 files changed, 206 insertions(+), 74 deletions(-) diff --git a/frame/assets-freezer/src/lib.rs b/frame/assets-freezer/src/lib.rs index d63a017916081..ee743b572a980 100644 --- a/frame/assets-freezer/src/lib.rs +++ b/frame/assets-freezer/src/lib.rs @@ -28,15 +28,16 @@ pub mod mock; mod tests;*/ use sp_std::prelude::*; -use sp_runtime::{TokenError, traits::{Zero, Saturating}}; +use sp_runtime::{TokenError, traits::{Zero, Saturating, CheckedAdd, CheckedSub}}; use frame_support::{ensure, dispatch::{DispatchError, DispatchResult}}; use frame_support::traits::{ StoredMap, tokens::{ - WithdrawConsequence, DepositConsequence, fungibles, FrozenBalance, WhenDust -}}; + WithdrawConsequence, DepositConsequence, fungibles, fungibles::InspectHold, FrozenBalance, + WhenDust + } +}; use frame_system::Config as SystemConfig; -//pub use weights::WeightInfo; pub use pallet::*; type BalanceOf = <::Assets as fungibles::Inspect<::AccountId>>::Balance; @@ -71,7 +72,7 @@ pub mod pallet { type Event: From> + IsType<::Event>; /// The fungibles trait impl whose assets this reserves. - type Assets: fungibles::Inspect; + type Assets: fungibles::InspectWithoutFreezer; /// Place to store the fast-access freeze data for the given asset/account. type Store: StoredMap<(AssetIdOf, Self::AccountId), FreezeData>>; @@ -216,13 +217,21 @@ impl fungibles::InspectHold<::AccountId> for Palle // If we can withdraw without destroying the account, then we're good. >::can_withdraw(asset, who, amount) == WithdrawConsequence::Success } + fn reducible_balance_on_hold(asset: AssetIdOf, who: &T::AccountId) -> BalanceOf { + // Figure out the most we can transfer from the balance on hold. This is basically the same + // as the balance on hold, but also ensures that the account actually has enough funds to + // be reduced and that no freezes have been placed on the asset/account. + let amount = T::Store::get(&(asset, who.clone())).reserved; + use fungibles::InspectWithoutFreezer; + let backing = T::Assets::reducible_balance(asset, who, true); + amount.min(backing) + } } impl fungibles::MutateHold<::AccountId> for Pallet where T::Assets: fungibles::Transfer + fungibles::InspectWithoutFreezer { fn hold(asset: AssetIdOf, who: &T::AccountId, amount: BalanceOf) -> DispatchResult { - use fungibles::InspectHold; if !Self::can_hold(asset, who, amount) { Err(TokenError::NoFunds)? } @@ -260,7 +269,6 @@ impl fungibles::MutateHold<::AccountId> for Pallet source: &T::AccountId, dest: &T::AccountId, amount: BalanceOf, - best_effort: bool, on_hold: bool, ) -> Result, DispatchError> { // Can't create the account with just a chunk of held balance - there needs to already be @@ -268,20 +276,18 @@ impl fungibles::MutateHold<::AccountId> for Pallet let min_balance = > ::minimum_balance(asset); let dest_balance = >::balance(asset, dest); ensure!(!on_hold || dest_balance >= min_balance, TokenError::CannotCreate); + Self::balance_on_hold(asset, dest).checked_add(&amount).ok_or(TokenError::Overflow)?; - let actual = Self::decrease_on_hold(asset, source, amount, best_effort)?; + Self::decrease_on_hold_ensuring_backed(asset, source, amount)?; // `death` is `KeepAlive` here since we're only transferring funds that were on hold, for // which there must be an additional min_balance, it should be impossible for the transfer // to cause the account to be deleted. use fungibles::Transfer; - let result = Self::transfer(asset, source, dest, actual, WhenDust::KeepAlive); + let result = Self::transfer(asset, source, dest, amount, WhenDust::KeepAlive); if result.is_ok() { if on_hold { - let r = T::Store::mutate( - &(asset, dest.clone()), - |extra| extra.reserved = extra.reserved.saturating_add(actual), - ); + let r = Self::increase_on_hold(asset, dest, amount); debug_assert!(r.is_ok(), "account exists and funds transferred in; qed"); r? } @@ -299,11 +305,10 @@ impl fungibles::UnbalancedHold<::AccountId> for Pa asset: AssetIdOf, source: &T::AccountId, amount: BalanceOf, - best_effort: bool, ) -> Result, DispatchError> { - let actual = Self::decrease_on_hold(asset, source, amount, best_effort)?; + Self::decrease_on_hold_ensuring_backed(asset, source, amount)?; // The previous call's success guarantees the next will succeed. - >::decrease_balance(asset, source, actual, true) + >::decrease_balance(asset, source, amount, true) } } @@ -316,40 +321,41 @@ impl Pallet { asset: AssetIdOf, source: &T::AccountId, amount: BalanceOf, - best_effort: bool, - ) -> Result, DispatchError> { - use fungibles::Inspect; - - let min_balance = Self::minimum_balance(asset); - - T::Store::try_mutate_exists( - &(asset, source.clone()), - |maybe_extra| -> Result, DispatchError> - { + ) -> Result<(), DispatchError> { + T::Store::try_mutate_exists(&(asset, source.clone()), |maybe_extra| { if let Some(ref mut extra) = maybe_extra { // Figure out the most we can unreserve and transfer. - let old = extra.reserved; - extra.reserved = extra.reserved.saturating_sub(amount); - let actual = old - extra.reserved; - ensure!(best_effort || actual == amount, TokenError::NoFunds); - - // actual is how much we can unreserve. now we check that the balance actually - // exists in the account. - let balance_left = Self::balance(asset, source) - .saturating_sub(min_balance); - ensure!(balance_left >= actual, TokenError::NoFunds); - - // the balance for the reserved amount actually exists. now we check that it's - // possible to actually transfer it out. practically, the only reason this would - // fail is if the asset class or account is completely frozen. - Self::can_withdraw(asset, source, actual) - .into_result(true)?; - - // all good. we should now be able to unreserve and transfer without any error. - Ok(actual) + extra.reserved = extra.reserved.checked_sub(&amount).ok_or(TokenError::NoFunds)?; + Ok(()) } else { Err(TokenError::NoFunds)? } }) } + + fn increase_on_hold( + asset: AssetIdOf, + source: &T::AccountId, + amount: BalanceOf, + ) -> Result<(), DispatchError> { + T::Store::mutate( + &(asset, source.clone()), + |extra| extra.reserved = extra.reserved.saturating_add(amount) + )?; + Ok(()) + } + + /// Same as decrease_on_hold, except that we guarantee that `amount` balance will be definitely + /// be reducible immediately afterwards. + fn decrease_on_hold_ensuring_backed( + asset: AssetIdOf, + source: &T::AccountId, + amount: BalanceOf, + ) -> Result<(), DispatchError> { + // Just make sure that we can actually draw the amount of asset out of source once it + // becomes unfrozen first. + >::can_withdraw(asset, source, amount) + .into_result(true)?; + Self::decrease_on_hold(asset, source, amount) + } } diff --git a/frame/support/src/traits/tokens/fungible.rs b/frame/support/src/traits/tokens/fungible.rs index c34c0a75b390b..0eaf7d4e9e9d1 100644 --- a/frame/support/src/traits/tokens/fungible.rs +++ b/frame/support/src/traits/tokens/fungible.rs @@ -25,7 +25,7 @@ use super::misc::{DepositConsequence, WithdrawConsequence, Balance, WhenDust}; mod balanced; mod imbalance; -pub use balanced::{Balanced, Unbalanced}; +pub use balanced::{Balanced, Unbalanced, BalancedHold, UnbalancedHold}; pub use imbalance::{Imbalance, HandleImbalanceDrop, DebtOf, CreditOf}; /// Trait for providing balance-inspection access to a fungible asset. @@ -146,10 +146,25 @@ pub trait InspectHold: Inspect { /// Check to see if some `amount` of funds of `who` may be placed on hold. fn can_hold(who: &AccountId, amount: Self::Balance) -> bool; + + /// Return the amount of funds which can be reduced of account `who` from the part of their + /// account balance on hold. + /// + /// Generally, this should be the same as `balance_on_hold`, but if the account is frozen or + /// has somehow had its balance reduced below that which is on hold, then it may be less. + /// + /// Assuming your type implements `InspectWithoutFreezer`, then this can generally be + /// implemented very simply with: + /// + /// ```nocompile + /// >::reducible_balance(who, true) + /// .min(Self::balance_on_hold(who)) + /// ``` + fn reducible_balance_on_hold(who: &AccountId) -> Self::Balance; } /// Trait for mutating a fungible asset which can be reserved. -pub trait MutateHold: Inspect { +pub trait MutateHold: InspectHold { /// Hold some funds in an account. fn hold(who: &AccountId, amount: Self::Balance) -> DispatchResult; @@ -162,24 +177,39 @@ pub trait MutateHold: Inspect { fn release(who: &AccountId, amount: Self::Balance, best_effort: bool) -> Result; - /// Transfer held funds into a destination account. + /// Transfer exactly `amount` of funds from `source` account into `dest`. /// /// 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_hold: bool, ) -> Result; + + /// Transfer as much as possible of funds on hold in `source` account, up to `amount`, into + /// `dest` 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. + /// + /// The actual amount transferred is returned, or `Err` in the case of error and nothing is + /// changed. + fn transfer_best_effort_held( + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + on_hold: bool, + ) -> Result { + let possible = Self::reducible_balance_on_hold(source); + Self::transfer_held(source, dest, amount.min(possible), on_hold) + } } /// Convert a `fungibles` trait implementation into a `fungible` trait implementation by identifying @@ -273,6 +303,9 @@ impl< fn can_hold(who: &AccountId, amount: Self::Balance) -> bool { >::can_hold(A::get(), who, amount) } + fn reducible_balance_on_hold(who: &AccountId) -> Self::Balance { + >::reducible_balance_on_hold(A::get(), who) + } } impl< @@ -292,7 +325,6 @@ impl< source: &AccountId, dest: &AccountId, amount: Self::Balance, - best_effort: bool, on_hold: bool, ) -> Result { >::transfer_held( @@ -300,10 +332,51 @@ impl< source, dest, amount, - best_effort, on_hold, ) } + fn transfer_best_effort_held( + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + on_hold: bool, + ) -> Result { + >::transfer_best_effort_held( + A::get(), + source, + dest, + amount, + on_hold, + ) + } +} + +impl< + F: fungibles::UnbalancedHold, + A: Get<>::AssetId>, + AccountId, +> UnbalancedHold for ItemOf { + fn decrease_balance_on_hold( + who: &AccountId, + amount: Self::Balance, + ) -> Result { + >::decrease_balance_on_hold( + A::get(), + who, + amount, + ) + } + + fn decrease_balance_on_hold_at_most( + who: &AccountId, + amount: Self::Balance, + ) -> Result { + >::decrease_balance_on_hold_at_most( + A::get(), + who, + amount, + ) + } } impl< diff --git a/frame/support/src/traits/tokens/fungible/balanced.rs b/frame/support/src/traits/tokens/fungible/balanced.rs index 7fa31184cd499..d8f68a99e2148 100644 --- a/frame/support/src/traits/tokens/fungible/balanced.rs +++ b/frame/support/src/traits/tokens/fungible/balanced.rs @@ -239,19 +239,29 @@ pub trait Unbalanced: Inspect + Sized { /// A fungible token class capable of placing funds on hold where the balance can be changed /// arbitrarily. -pub trait UnbalancedHold: Unbalanced { - /// Reduce the `asset` balance of `who` by `amount` from the funds on hold. +pub trait UnbalancedHold: Unbalanced + InspectHold { + /// Reduce the balance of `who` by `amount` from the funds on hold. /// - /// If successful, then the amount decreased is returned. + /// If successful, then exactly `amount` is returned otherwise an `Err` is returned and + /// nothing is changed. + fn decrease_balance_on_hold( + who: &AccountId, + amount: Self::Balance, + ) -> Result; + + /// Reduce the balance of `who` by as much as possible up to at most `amount` from the + /// funds on hold. /// - /// If `best_effort` is false then the amount reduced may be below the `amount` given. + /// If successful, then the amount decreased is returned. /// /// If it cannot be validly reduced, return `Err` and do nothing. - fn decrease_balance_on_hold( + fn decrease_balance_on_hold_at_most( who: &AccountId, amount: Self::Balance, - best_effort: bool, - ) -> Result; + ) -> Result { + let amount = amount.min(Self::reducible_balance_on_hold(who)); + Self::decrease_balance_on_hold(who, amount) + } } /// Simple handler for an imbalance drop which increases the total issuance of the system by the @@ -364,7 +374,7 @@ impl> BalancedHold for U { who: &AccountId, amount: Self::Balance, ) -> (Credit, Self::Balance) { - let slashed = U::decrease_balance_on_hold(who, amount, true) + let slashed = U::decrease_balance_on_hold_at_most(who, amount,) .unwrap_or(Zero::zero()); (credit(slashed), amount.saturating_sub(slashed)) } diff --git a/frame/support/src/traits/tokens/fungibles.rs b/frame/support/src/traits/tokens/fungibles.rs index bb64afb66a55c..9c76e5c09219a 100644 --- a/frame/support/src/traits/tokens/fungibles.rs +++ b/frame/support/src/traits/tokens/fungibles.rs @@ -193,10 +193,25 @@ pub trait InspectHold: Inspect { /// 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; + + /// Return the amount of `asset` which can be reduced of account `who` from the part of their + /// account balance on hold. + /// + /// Generally, this should be the same as `balance_on_hold`, but if the account is frozen or + /// has somehow had its balance reduced below that which is on hold, then it may be less. + /// + /// If your type implements `InspectWithoutFreezer`, then this can generally be + /// implemented very simply with: + /// + /// ```nocompile + /// >::reducible_balance(asset, who, true) + /// .min(Self::balance_on_hold(asset, who)) + /// ``` + fn reducible_balance_on_hold(asset: Self::AssetId, who: &AccountId) -> Self::Balance; } /// Trait for mutating a set of named fungible assets which can be placed on hold. -pub trait MutateHold: Inspect { +pub trait MutateHold: InspectHold { /// Hold some funds in an account. fn hold(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> DispatchResult; @@ -207,15 +222,12 @@ pub trait MutateHold: Inspect { fn release(asset: Self::AssetId, who: &AccountId, amount: Self::Balance, best_effort: bool) -> Result; - /// Transfer held funds into a destination account. + /// Transfer exactly `amount` of `asset` from `source` account into `dest`. /// /// 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( @@ -223,7 +235,26 @@ pub trait MutateHold: Inspect { source: &AccountId, dest: &AccountId, amount: Self::Balance, - best_effort: bool, on_hold: bool, ) -> Result; + + /// Transfer as much as possible of `asset` on hold in `source` account, up to `amount`, into + /// `dest` 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. + /// + /// The actual amount transferred is returned, or `Err` in the case of error and nothing is + /// changed. + fn transfer_best_effort_held( + asset: Self::AssetId, + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + on_hold: bool, + ) -> Result { + let possible = Self::reducible_balance_on_hold(asset, source); + Self::transfer_held(asset, source, dest, amount.min(possible), on_hold) + } } diff --git a/frame/support/src/traits/tokens/fungibles/balanced.rs b/frame/support/src/traits/tokens/fungibles/balanced.rs index b4875d08a3194..a710784552521 100644 --- a/frame/support/src/traits/tokens/fungibles/balanced.rs +++ b/frame/support/src/traits/tokens/fungibles/balanced.rs @@ -258,20 +258,32 @@ pub trait Unbalanced: Inspect + Sized { /// A fungible token class capable of placing funds on hold where the balance can be changed /// arbitrarily. -pub trait UnbalancedHold: Unbalanced { +pub trait UnbalancedHold: Unbalanced + InspectHold { /// Reduce the `asset` balance of `who` by `amount` from the funds on hold. /// - /// If successful, then the amount decreased is returned. - /// - /// If `best_effort` is false then the amount reduced may be below the `amount` given. + /// If successful, then the amount decreased is returned. This will be exactly `amount`. /// /// If it cannot be validly reduced, return `Err` and do nothing. fn decrease_balance_on_hold( asset: Self::AssetId, who: &AccountId, amount: Self::Balance, - best_effort: bool, ) -> Result; + + /// Reduce the `asset` balance of `who` by as much as possible up to at most `amount` from the + /// funds on hold. + /// + /// If successful, then the amount decreased is returned. + /// + /// If it cannot be validly reduced, return `Err` and do nothing. + fn decrease_balance_on_hold_at_most( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> Result { + let amount = amount.min(Self::reducible_balance_on_hold(asset, who)); + Self::decrease_balance_on_hold(asset, who, amount) + } } /// Simple handler for an imbalance drop which increases the total issuance of the system by the @@ -387,7 +399,7 @@ impl> BalancedHold for U { who: &AccountId, amount: Self::Balance, ) -> (Credit, Self::Balance) { - let slashed = U::decrease_balance_on_hold(asset, who, amount, true) + let slashed = U::decrease_balance_on_hold_at_most(asset, who, amount) .unwrap_or(Zero::zero()); (credit(asset, slashed), amount.saturating_sub(slashed)) } From 14a4f3443ed1622367e284458dfb7fdd5a0a7112 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 30 Mar 2021 11:54:59 +0200 Subject: [PATCH 08/22] Fixes --- Cargo.lock | 16 ++++++++++++++++ frame/balances/src/lib.rs | 6 ++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1a18f3a8120b..1c3afe8e4183c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4635,6 +4635,22 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-assets-freezer" +version = "3.0.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-assets", + "parity-scale-codec 2.0.1", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-atomic-swap" version = "3.0.0" diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index 4166a845858a7..3244f02dd52dd 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -1026,6 +1026,9 @@ impl, I: 'static> fungible::InspectHold for Pallet= required_free } + fn reducible_balance_on_hold(who: &T::AccountId) -> Self::Balance { + Self::balance_on_hold(who) + } } impl, I: 'static> fungible::MutateHold for Pallet { fn hold(who: &T::AccountId, amount: Self::Balance) -> DispatchResult { @@ -1056,11 +1059,10 @@ impl, I: 'static> fungible::MutateHold for Pallet Result { let status = if on_hold { Status::Reserved } else { Status::Free }; - Self::do_transfer_reserved(source, dest, amount, best_effort, status) + Self::do_transfer_reserved(source, dest, amount, false, status) } } From 3bc8b231b6d6acf025406f4fb34fd940a42b7354 Mon Sep 17 00:00:00 2001 From: Ricardo Rius Date: Thu, 22 Apr 2021 21:07:12 +0200 Subject: [PATCH 09/22] Fix build --- Cargo.lock | 2 +- frame/assets/src/functions.rs | 6 +++--- frame/assets/src/impl_fungibles.rs | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8852e83456ce1..b674317f062a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4623,7 +4623,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-assets", - "parity-scale-codec 2.0.1", + "parity-scale-codec", "serde", "sp-core", "sp-io", diff --git a/frame/assets/src/functions.rs b/frame/assets/src/functions.rs index 3178db520e00e..ceabc25b2e9d3 100644 --- a/frame/assets/src/functions.rs +++ b/frame/assets/src/functions.rs @@ -161,8 +161,8 @@ impl, I: 'static> Pallet { id: T::AssetId, who: &T::AccountId, f: DebitFlags, - ) -> Result> { - let details = match Asset::::get(id) { + ) -> Result> { + let details = match Asset::::get(id) { Some(details) => details, None => return Err(Error::::Unknown), }; @@ -214,7 +214,7 @@ impl, I: 'static> Pallet { ) -> Result { let actual = Self::reducible_balance(id, target, f.into())? .min(amount); - ensure!(actual >= amount, Error::::BalanceLow); + ensure!(actual >= amount, Error::::BalanceLow); let conseq = Self::can_decrease(id, target, actual, f.into()); let actual = match conseq.into_result(false) { diff --git a/frame/assets/src/impl_fungibles.rs b/frame/assets/src/impl_fungibles.rs index 8aecec904e080..294a23ae4c2a1 100644 --- a/frame/assets/src/impl_fungibles.rs +++ b/frame/assets/src/impl_fungibles.rs @@ -45,7 +45,7 @@ impl, I: 'static> fungibles::Inspect<::AccountId keep_alive: bool, ) -> Self::Balance { let f = DebitFlags { keep_alive, ignore_freezer: false }; - Pallet::::reducible_balance(asset, who, f).unwrap_or(Zero::zero()) + Pallet::::reducible_balance(asset, who, f).unwrap_or(Zero::zero()) } fn can_deposit( @@ -62,18 +62,18 @@ impl, I: 'static> fungibles::Inspect<::AccountId amount: Self::Balance, ) -> WithdrawConsequence { let f = DebitFlags { keep_alive: false, ignore_freezer: false }; - Pallet::::can_decrease(asset, who, amount, f) + Pallet::::can_decrease(asset, who, amount, f) } } -impl fungibles::InspectWithoutFreezer<::AccountId> for Pallet { +impl, I: 'static> fungibles::InspectWithoutFreezer<::AccountId> for Pallet { fn reducible_balance( asset: Self::AssetId, who: &::AccountId, keep_alive: bool, ) -> Self::Balance { let f = DebitFlags { keep_alive, ignore_freezer: true }; - Pallet::::reducible_balance(asset, who, f).unwrap_or(Zero::zero()) + Pallet::::reducible_balance(asset, who, f).unwrap_or(Zero::zero()) } fn can_withdraw( @@ -82,11 +82,11 @@ impl fungibles::InspectWithoutFreezer<::AccountId> amount: Self::Balance, ) -> WithdrawConsequence { let f = DebitFlags { keep_alive: false, ignore_freezer: true }; - Pallet::::can_decrease(asset, who, amount, f) + Pallet::::can_decrease(asset, who, amount, f) } } -impl fungibles::Mutate<::AccountId> for Pallet { +impl, I: 'static> fungibles::Mutate<::AccountId> for Pallet { fn mint_into( asset: Self::AssetId, who: &::AccountId, From e2acff92c3e83b2a3089289958abf84f68204877 Mon Sep 17 00:00:00 2001 From: Ricardo Rius Date: Fri, 23 Apr 2021 10:08:28 +0200 Subject: [PATCH 10/22] Freezer mock still not working --- Cargo.lock | 1 + frame/assets-freezer/Cargo.toml | 1 + frame/assets-freezer/src/lib.rs | 4 +- frame/assets-freezer/src/mock.rs | 58 ++-- frame/assets-freezer/src/tests.rs | 522 +----------------------------- 5 files changed, 30 insertions(+), 556 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b674317f062a4..968d60dc35615 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4623,6 +4623,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-assets", + "pallet-balances", "parity-scale-codec", "serde", "sp-core", diff --git a/frame/assets-freezer/Cargo.toml b/frame/assets-freezer/Cargo.toml index d24e51d743033..f404331596f3e 100644 --- a/frame/assets-freezer/Cargo.toml +++ b/frame/assets-freezer/Cargo.toml @@ -28,6 +28,7 @@ frame-benchmarking = { version = "3.1.0", default-features = false, path = "../b sp-core = { version = "3.0.0", path = "../../primitives/core" } sp-std = { version = "3.0.0", path = "../../primitives/std" } sp-io = { version = "3.0.0", path = "../../primitives/io" } +pallet-balances = { version = "3.0.0", path = "../balances" } pallet-assets = { version = "3.0.0", default-features = false, path = "../assets" } [features] diff --git a/frame/assets-freezer/src/lib.rs b/frame/assets-freezer/src/lib.rs index ee743b572a980..a75b6c68c3aee 100644 --- a/frame/assets-freezer/src/lib.rs +++ b/frame/assets-freezer/src/lib.rs @@ -22,10 +22,10 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -/*#[cfg(test)] +#[cfg(test)] pub mod mock; #[cfg(test)] -mod tests;*/ +mod tests; use sp_std::prelude::*; use sp_runtime::{TokenError, traits::{Zero, Saturating, CheckedAdd, CheckedSub}}; diff --git a/frame/assets-freezer/src/mock.rs b/frame/assets-freezer/src/mock.rs index 96567537b9ff6..811731f7c5adf 100644 --- a/frame/assets-freezer/src/mock.rs +++ b/frame/assets-freezer/src/mock.rs @@ -18,11 +18,12 @@ //! Test environment for Assets pallet. use super::*; -use crate as pallet_assets; +use crate as pallet_assets_freezer; use sp_core::H256; use sp_runtime::{traits::{BlakeTwo256, IdentityLookup}, testing::Header}; use frame_support::{parameter_types, construct_runtime}; +use frame_support::traits::StorageMapShim; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -34,8 +35,9 @@ construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Assets: pallet_assets::{Pallet, Call, Storage, Event}, + AssetsFreezer: pallet_assets_freezer::{Pallet, Call, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, } ); @@ -65,6 +67,7 @@ impl frame_system::Config for Test { type OnKilledAccount = (); type SystemWeightInfo = (); type SS58Prefix = (); + type OnSetCode = (); } parameter_types! { @@ -89,7 +92,7 @@ parameter_types! { pub const MetadataDepositPerByte: u64 = 1; } -impl Config for Test { +impl pallet_assets::Config for Test { type Event = Event; type Balance = u64; type AssetId = u32; @@ -100,47 +103,24 @@ impl Config for Test { type MetadataDepositPerByte = MetadataDepositPerByte; type ApprovalDeposit = ApprovalDeposit; type StringLimit = StringLimit; - type Freezer = TestFreezer; + type Freezer = AssetsFreezer; type WeightInfo = (); + type Extra = (); } -use std::cell::RefCell; -use std::collections::HashMap; - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub(crate) enum Hook { - Melted(u32, u64, u64), - Died(u32, u64), -} -thread_local! { - static FROZEN: RefCell> = RefCell::new(Default::default()); - static HOOKS: RefCell> = RefCell::new(Default::default()); -} - -pub struct TestFreezer; -impl FrozenBalance for TestFreezer { - fn frozen_balance(asset: u32, who: &u64) -> Option { - FROZEN.with(|f| f.borrow().get(&(asset, who.clone())).cloned()) - } - - fn melted(asset: u32, who: &u64, amount_left_frozen: u64) { - HOOKS.with(|h| h.borrow_mut().push(Hook::Melted(asset, who.clone(), amount_left_frozen))); - } - - fn died(asset: u32, who: &u64) { - HOOKS.with(|h| h.borrow_mut().push(Hook::Died(asset, who.clone()))); - } +impl Config for Test { + type Event = Event; + type Assets = Assets; + type Store = StorageMapShim< + _, + frame_system::Provider, + (super::AssetIdOf,u64) + super::FreezeData, + >; } -pub(crate) fn set_frozen_balance(asset: u32, who: u64, amount: u64) { - FROZEN.with(|f| f.borrow_mut().insert((asset, who), amount)); -} -pub(crate) fn clear_frozen_balance(asset: u32, who: u64) { - FROZEN.with(|f| f.borrow_mut().remove(&(asset, who))); -} -pub(crate) fn hooks() -> Vec { - HOOKS.with(|h| h.borrow().clone()) -} +use std::cell::RefCell; +use std::collections::HashMap; pub(crate) fn new_test_ext() -> sp_io::TestExternalities { let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); diff --git a/frame/assets-freezer/src/tests.rs b/frame/assets-freezer/src/tests.rs index a1c379a7c5d32..d7376d10f6e64 100644 --- a/frame/assets-freezer/src/tests.rs +++ b/frame/assets-freezer/src/tests.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Tests for Assets pallet. +//! Tests for Assets Freezer pallet. use super::*; use crate::{Error, mock::*}; @@ -30,518 +30,10 @@ fn last_event() -> mock::Event { #[test] fn basic_minting_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::mint(Origin::signed(1), 0, 2, 100)); - assert_eq!(Assets::balance(0, 2), 100); + // assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + // assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + // assert_eq!(Assets::balance(0, 1), 100); + // assert_ok!(AssetsFreezer::set_total_issuance(Origin::signed(1), 0, 2, 100)); + // assert_eq!(AssetsFreezer::balance(0, 2), 100); }); -} - -#[test] -fn approval_lifecycle_works() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 1); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); - assert_eq!(Balances::reserved_balance(&1), 1); - assert_ok!(Assets::transfer_approved(Origin::signed(2), 0, 1, 3, 40)); - assert_ok!(Assets::cancel_approval(Origin::signed(1), 0, 2)); - assert_eq!(Assets::balance(0, 1), 60); - assert_eq!(Assets::balance(0, 3), 40); - assert_eq!(Balances::reserved_balance(&1), 0); - }); -} - -#[test] -fn approval_deposits_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - let e = BalancesError::::InsufficientBalance; - assert_noop!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50), e); - - Balances::make_free_balance_be(&1, 1); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); - assert_eq!(Balances::reserved_balance(&1), 1); - - assert_ok!(Assets::transfer_approved(Origin::signed(2), 0, 1, 3, 50)); - assert_eq!(Balances::reserved_balance(&1), 0); - - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); - assert_ok!(Assets::cancel_approval(Origin::signed(1), 0, 2)); - assert_eq!(Balances::reserved_balance(&1), 0); - }); -} - -#[test] -fn cannot_transfer_more_than_approved() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 1); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); - let e = Error::::Unapproved; - assert_noop!(Assets::transfer_approved(Origin::signed(2), 0, 1, 3, 51), e); - }); -} - -#[test] -fn cannot_transfer_more_than_exists() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 1); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 101)); - let e = Error::::BalanceLow; - assert_noop!(Assets::transfer_approved(Origin::signed(2), 0, 1, 3, 101), e); - }); -} - -#[test] -fn cancel_approval_works() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 1); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); - assert_noop!(Assets::cancel_approval(Origin::signed(1), 1, 2), Error::::Unknown); - assert_noop!(Assets::cancel_approval(Origin::signed(2), 0, 2), Error::::Unknown); - assert_noop!(Assets::cancel_approval(Origin::signed(1), 0, 3), Error::::Unknown); - assert_ok!(Assets::cancel_approval(Origin::signed(1), 0, 2)); - assert_noop!(Assets::cancel_approval(Origin::signed(1), 0, 2), Error::::Unknown); - }); -} - -#[test] -fn force_cancel_approval_works() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 1); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); - let e = Error::::NoPermission; - assert_noop!(Assets::force_cancel_approval(Origin::signed(2), 0, 1, 2), e); - assert_noop!(Assets::force_cancel_approval(Origin::signed(1), 1, 1, 2), Error::::Unknown); - assert_noop!(Assets::force_cancel_approval(Origin::signed(1), 0, 2, 2), Error::::Unknown); - assert_noop!(Assets::force_cancel_approval(Origin::signed(1), 0, 1, 3), Error::::Unknown); - assert_ok!(Assets::force_cancel_approval(Origin::signed(1), 0, 1, 2)); - assert_noop!(Assets::force_cancel_approval(Origin::signed(1), 0, 1, 2), Error::::Unknown); - }); -} - -#[test] -fn lifecycle_should_work() { - new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::create(Origin::signed(1), 0, 1, 1)); - assert_eq!(Balances::reserved_balance(&1), 1); - assert!(Asset::::contains_key(0)); - - assert_ok!(Assets::set_metadata(Origin::signed(1), 0, vec![0], vec![0], 12)); - assert_eq!(Balances::reserved_balance(&1), 4); - assert!(Metadata::::contains_key(0)); - - Balances::make_free_balance_be(&10, 100); - assert_ok!(Assets::mint(Origin::signed(1), 0, 10, 100)); - Balances::make_free_balance_be(&20, 100); - assert_ok!(Assets::mint(Origin::signed(1), 0, 20, 100)); - assert_eq!(Account::::iter_prefix(0).count(), 2); - - let w = Asset::::get(0).unwrap().destroy_witness(); - assert_ok!(Assets::destroy(Origin::signed(1), 0, w)); - assert_eq!(Balances::reserved_balance(&1), 0); - - assert!(!Asset::::contains_key(0)); - assert!(!Metadata::::contains_key(0)); - assert_eq!(Account::::iter_prefix(0).count(), 0); - - assert_ok!(Assets::create(Origin::signed(1), 0, 1, 1)); - assert_eq!(Balances::reserved_balance(&1), 1); - assert!(Asset::::contains_key(0)); - - assert_ok!(Assets::set_metadata(Origin::signed(1), 0, vec![0], vec![0], 12)); - assert_eq!(Balances::reserved_balance(&1), 4); - assert!(Metadata::::contains_key(0)); - - assert_ok!(Assets::mint(Origin::signed(1), 0, 10, 100)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 20, 100)); - assert_eq!(Account::::iter_prefix(0).count(), 2); - - let w = Asset::::get(0).unwrap().destroy_witness(); - assert_ok!(Assets::destroy(Origin::root(), 0, w)); - assert_eq!(Balances::reserved_balance(&1), 0); - - assert!(!Asset::::contains_key(0)); - assert!(!Metadata::::contains_key(0)); - assert_eq!(Account::::iter_prefix(0).count(), 0); - }); -} - -#[test] -fn destroy_with_bad_witness_should_not_work() { - new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - let w = Asset::::get(0).unwrap().destroy_witness(); - assert_ok!(Assets::mint(Origin::signed(1), 0, 10, 100)); - assert_noop!(Assets::destroy(Origin::signed(1), 0, w), Error::::BadWitness); - }); -} - -#[test] -fn non_providing_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, false, 1)); - - Balances::make_free_balance_be(&0, 100); - assert_ok!(Assets::mint(Origin::signed(1), 0, 0, 100)); - - // Cannot mint into account 2 since it doesn't (yet) exist... - assert_noop!(Assets::mint(Origin::signed(1), 0, 1, 100), TokenError::CannotCreate); - // ...or transfer... - assert_noop!(Assets::transfer(Origin::signed(0), 0, 1, 50), TokenError::CannotCreate); - // ...or force-transfer - assert_noop!(Assets::force_transfer(Origin::signed(1), 0, 0, 1, 50), TokenError::CannotCreate); - - Balances::make_free_balance_be(&1, 100); - Balances::make_free_balance_be(&2, 100); - assert_ok!(Assets::transfer(Origin::signed(0), 0, 1, 25)); - assert_ok!(Assets::force_transfer(Origin::signed(1), 0, 0, 2, 25)); - }); -} - -#[test] -fn min_balance_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 10)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - assert_eq!(Asset::::get(0).unwrap().accounts, 1); - - // Cannot create a new account with a balance that is below minimum... - assert_noop!(Assets::mint(Origin::signed(1), 0, 2, 9), TokenError::BelowMinimum); - assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 9), TokenError::BelowMinimum); - assert_noop!(Assets::force_transfer(Origin::signed(1), 0, 1, 2, 9), TokenError::BelowMinimum); - - // When deducting from an account to below minimum, it should be reaped. - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 91)); - assert!(Assets::balance(0, 1).is_zero()); - assert_eq!(Assets::balance(0, 2), 100); - assert_eq!(Asset::::get(0).unwrap().accounts, 1); - - assert_ok!(Assets::force_transfer(Origin::signed(1), 0, 2, 1, 91)); - assert!(Assets::balance(0, 2).is_zero()); - assert_eq!(Assets::balance(0, 1), 100); - assert_eq!(Asset::::get(0).unwrap().accounts, 1); - - assert_ok!(Assets::burn(Origin::signed(1), 0, 1, 91)); - assert!(Assets::balance(0, 1).is_zero()); - assert_eq!(Asset::::get(0).unwrap().accounts, 0); - }); -} - -#[test] -fn querying_total_supply_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); - assert_eq!(Assets::balance(0, 1), 50); - assert_eq!(Assets::balance(0, 2), 50); - assert_ok!(Assets::transfer(Origin::signed(2), 0, 3, 31)); - assert_eq!(Assets::balance(0, 1), 50); - assert_eq!(Assets::balance(0, 2), 19); - assert_eq!(Assets::balance(0, 3), 31); - assert_ok!(Assets::burn(Origin::signed(1), 0, 3, u64::max_value())); - assert_eq!(Assets::total_supply(0), 69); - }); -} - -#[test] -fn transferring_amount_below_available_balance_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); - assert_eq!(Assets::balance(0, 1), 50); - assert_eq!(Assets::balance(0, 2), 50); - }); -} - -#[test] -fn transferring_enough_to_kill_source_when_keep_alive_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 10)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_noop!(Assets::transfer_keep_alive(Origin::signed(1), 0, 2, 91), Error::::BalanceLow); - assert_ok!(Assets::transfer_keep_alive(Origin::signed(1), 0, 2, 90)); - assert_eq!(Assets::balance(0, 1), 10); - assert_eq!(Assets::balance(0, 2), 90); - }); -} - -#[test] -fn transferring_frozen_user_should_not_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::freeze(Origin::signed(1), 0, 1)); - assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 50), Error::::Frozen); - assert_ok!(Assets::thaw(Origin::signed(1), 0, 1)); - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); - }); -} - -#[test] -fn transferring_frozen_asset_should_not_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::freeze_asset(Origin::signed(1), 0)); - assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 50), Error::::Frozen); - assert_ok!(Assets::thaw_asset(Origin::signed(1), 0)); - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); - }); -} - -#[test] -fn origin_guards_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - assert_noop!(Assets::transfer_ownership(Origin::signed(2), 0, 2), Error::::NoPermission); - assert_noop!(Assets::set_team(Origin::signed(2), 0, 2, 2, 2), Error::::NoPermission); - assert_noop!(Assets::freeze(Origin::signed(2), 0, 1), Error::::NoPermission); - assert_noop!(Assets::thaw(Origin::signed(2), 0, 2), Error::::NoPermission); - assert_noop!(Assets::mint(Origin::signed(2), 0, 2, 100), Error::::NoPermission); - assert_noop!(Assets::burn(Origin::signed(2), 0, 1, 100), Error::::NoPermission); - assert_noop!(Assets::force_transfer(Origin::signed(2), 0, 1, 2, 100), Error::::NoPermission); - let w = Asset::::get(0).unwrap().destroy_witness(); - assert_noop!(Assets::destroy(Origin::signed(2), 0, w), Error::::NoPermission); - }); -} - -#[test] -fn transfer_owner_should_work() { - new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); - Balances::make_free_balance_be(&2, 100); - assert_ok!(Assets::create(Origin::signed(1), 0, 1, 1)); - - assert_eq!(Balances::reserved_balance(&1), 1); - - assert_ok!(Assets::transfer_ownership(Origin::signed(1), 0, 2)); - assert_eq!(Balances::reserved_balance(&2), 1); - assert_eq!(Balances::reserved_balance(&1), 0); - - assert_noop!(Assets::transfer_ownership(Origin::signed(1), 0, 1), Error::::NoPermission); - - // Set metadata now and make sure that deposit gets transferred back. - assert_ok!(Assets::set_metadata(Origin::signed(2), 0, vec![0u8; 10], vec![0u8; 10], 12)); - assert_ok!(Assets::transfer_ownership(Origin::signed(2), 0, 1)); - assert_eq!(Balances::reserved_balance(&1), 22); - assert_eq!(Balances::reserved_balance(&2), 0); - }); -} - -#[test] -fn set_team_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::set_team(Origin::signed(1), 0, 2, 3, 4)); - - assert_ok!(Assets::mint(Origin::signed(2), 0, 2, 100)); - assert_ok!(Assets::freeze(Origin::signed(4), 0, 2)); - assert_ok!(Assets::thaw(Origin::signed(3), 0, 2)); - assert_ok!(Assets::force_transfer(Origin::signed(3), 0, 2, 3, 100)); - assert_ok!(Assets::burn(Origin::signed(3), 0, 3, 100)); - }); -} - -#[test] -fn transferring_to_frozen_account_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 2, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_eq!(Assets::balance(0, 2), 100); - assert_ok!(Assets::freeze(Origin::signed(1), 0, 2)); - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); - assert_eq!(Assets::balance(0, 2), 150); - }); -} - -#[test] -fn transferring_amount_more_than_available_balance_should_not_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); - assert_eq!(Assets::balance(0, 1), 50); - assert_eq!(Assets::balance(0, 2), 50); - assert_ok!(Assets::burn(Origin::signed(1), 0, 1, u64::max_value())); - assert_eq!(Assets::balance(0, 1), 0); - assert_noop!(Assets::transfer(Origin::signed(1), 0, 1, 50), Error::::BalanceLow); - assert_noop!(Assets::transfer(Origin::signed(2), 0, 1, 51), Error::::BalanceLow); - }); -} - -#[test] -fn transferring_less_than_one_unit_is_fine() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 0)); - assert_eq!( - last_event(), - mock::Event::pallet_assets(crate::Event::Transferred(0, 1, 2, 0)), - ); - }); -} - -#[test] -fn transferring_more_units_than_total_supply_should_not_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 101), Error::::BalanceLow); - }); -} - -#[test] -fn burning_asset_balance_with_positive_balance_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::burn(Origin::signed(1), 0, 1, u64::max_value())); - assert_eq!(Assets::balance(0, 1), 0); - }); -} - -#[test] -fn burning_asset_balance_with_zero_balance_does_nothing() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 2), 0); - assert_ok!(Assets::burn(Origin::signed(1), 0, 2, u64::max_value())); - assert_eq!(Assets::balance(0, 2), 0); - assert_eq!(Assets::total_supply(0), 100); - }); -} - -#[test] -fn set_metadata_should_work() { - new_test_ext().execute_with(|| { - // Cannot add metadata to unknown asset - assert_noop!( - Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 10], 12), - Error::::Unknown, - ); - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - // Cannot add metadata to unowned asset - assert_noop!( - Assets::set_metadata(Origin::signed(2), 0, vec![0u8; 10], vec![0u8; 10], 12), - Error::::NoPermission, - ); - - // Cannot add oversized metadata - assert_noop!( - Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 100], vec![0u8; 10], 12), - Error::::BadMetadata, - ); - assert_noop!( - Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 100], 12), - Error::::BadMetadata, - ); - - // Successfully add metadata and take deposit - Balances::make_free_balance_be(&1, 30); - assert_ok!(Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 10], 12)); - assert_eq!(Balances::free_balance(&1), 9); - - // Update deposit - assert_ok!(Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 5], 12)); - assert_eq!(Balances::free_balance(&1), 14); - assert_ok!(Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 15], 12)); - assert_eq!(Balances::free_balance(&1), 4); - - // Cannot over-reserve - assert_noop!( - Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 20], vec![0u8; 20], 12), - BalancesError::::InsufficientBalance, - ); - - // Clear Metadata - assert!(Metadata::::contains_key(0)); - assert_noop!(Assets::clear_metadata(Origin::signed(2), 0), Error::::NoPermission); - assert_noop!(Assets::clear_metadata(Origin::signed(1), 1), Error::::Unknown); - assert_ok!(Assets::clear_metadata(Origin::signed(1), 0)); - assert!(!Metadata::::contains_key(0)); - }); -} - -// TODO: tests for force_set_metadata, force_clear_metadata, force_asset_status - -#[test] -fn freezer_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 10)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - - - // freeze 50 of it. - set_frozen_balance(0, 1, 50); - - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 20)); - - assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 21), Error::::BalanceLow); - - Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); - assert_noop!(Assets::transfer_approved(Origin::signed(2), 0, 1, 2, 21), Error::::BalanceLow); - - assert_ok!(Assets::force_transfer(Origin::signed(1), 0, 1, 2, 21)); - assert_eq!(hooks(), vec![Hook::Melted(0, 1, 49)]); - - clear_frozen_balance(0, 1); - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); - assert_eq!(hooks(), vec![Hook::Melted(0, 1, 49), Hook::Died(0, 1)]); - }); -} - -#[test] -fn imbalances_should_work() { - use frame_support::traits::tokens::fungibles::Balanced; - - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - - let imb = Assets::issue(0, 100); - assert_eq!(Assets::total_supply(0), 100); - assert_eq!(imb.peek(), 100); - - let (imb1, imb2) = imb.split(30); - assert_eq!(imb1.peek(), 30); - assert_eq!(imb2.peek(), 70); - - drop(imb2); - assert_eq!(Assets::total_supply(0), 30); - - assert!(Assets::resolve(&1, imb1).is_ok()); - assert_eq!(Assets::balance(0, 1), 30); - assert_eq!(Assets::total_supply(0), 30); - }); -} +} \ No newline at end of file From 92ea8eafe64bdff374780d8f789933c6d2a6a3e9 Mon Sep 17 00:00:00 2001 From: Ricardo Rius Date: Mon, 26 Apr 2021 10:24:43 +0200 Subject: [PATCH 11/22] Fixes --- frame/assets-freezer/src/mock.rs | 10 ++-------- frame/assets-freezer/src/tests.rs | 10 ---------- frame/assets/src/functions.rs | 4 ++-- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/frame/assets-freezer/src/mock.rs b/frame/assets-freezer/src/mock.rs index 811731f7c5adf..998adcfd5836f 100644 --- a/frame/assets-freezer/src/mock.rs +++ b/frame/assets-freezer/src/mock.rs @@ -24,7 +24,6 @@ use sp_core::H256; use sp_runtime::{traits::{BlakeTwo256, IdentityLookup}, testing::Header}; use frame_support::{parameter_types, construct_runtime}; use frame_support::traits::StorageMapShim; - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -105,18 +104,13 @@ impl pallet_assets::Config for Test { type StringLimit = StringLimit; type Freezer = AssetsFreezer; type WeightInfo = (); - type Extra = (); + type Extra = super::FreezeData; } impl Config for Test { type Event = Event; type Assets = Assets; - type Store = StorageMapShim< - _, - frame_system::Provider, - (super::AssetIdOf,u64) - super::FreezeData, - >; + type Store = pallet_assets::Pallet; } use std::cell::RefCell; diff --git a/frame/assets-freezer/src/tests.rs b/frame/assets-freezer/src/tests.rs index d7376d10f6e64..8570ebc3be372 100644 --- a/frame/assets-freezer/src/tests.rs +++ b/frame/assets-freezer/src/tests.rs @@ -27,13 +27,3 @@ fn last_event() -> mock::Event { frame_system::Pallet::::events().pop().expect("Event expected").event } -#[test] -fn basic_minting_should_work() { - new_test_ext().execute_with(|| { - // assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - // assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - // assert_eq!(Assets::balance(0, 1), 100); - // assert_ok!(AssetsFreezer::set_total_issuance(Origin::signed(1), 0, 2, 100)); - // assert_eq!(AssetsFreezer::balance(0, 2), 100); - }); -} \ No newline at end of file diff --git a/frame/assets/src/functions.rs b/frame/assets/src/functions.rs index c67faf700a0b3..e3ba12fa1c0a0 100644 --- a/frame/assets/src/functions.rs +++ b/frame/assets/src/functions.rs @@ -160,8 +160,8 @@ impl, I: 'static> Pallet { pub(super) fn reducible_balance( id: T::AssetId, who: &T::AccountId, - keep_alive: bool, - ) -> Result> { + f: DebitFlags, + ) -> Result> { let details = Asset::::get(id).ok_or_else(|| Error::::Unknown)?; ensure!(!details.is_frozen, Error::::Frozen); From 34741055aa629741562c05385c68c930340681cc Mon Sep 17 00:00:00 2001 From: Ricardo Rius Date: Tue, 4 May 2021 12:06:09 +0200 Subject: [PATCH 12/22] Add some tests --- frame/assets-freezer/src/mock.rs | 6 +-- frame/assets-freezer/src/tests.rs | 80 ++++++++++++++++++++++++++++++- frame/assets/src/lib.rs | 14 +++--- 3 files changed, 87 insertions(+), 13 deletions(-) diff --git a/frame/assets-freezer/src/mock.rs b/frame/assets-freezer/src/mock.rs index 998adcfd5836f..83e8b4a67433e 100644 --- a/frame/assets-freezer/src/mock.rs +++ b/frame/assets-freezer/src/mock.rs @@ -23,7 +23,6 @@ use crate as pallet_assets_freezer; use sp_core::H256; use sp_runtime::{traits::{BlakeTwo256, IdentityLookup}, testing::Header}; use frame_support::{parameter_types, construct_runtime}; -use frame_support::traits::StorageMapShim; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -110,12 +109,9 @@ impl pallet_assets::Config for Test { impl Config for Test { type Event = Event; type Assets = Assets; - type Store = pallet_assets::Pallet; + type Store = Assets; } -use std::cell::RefCell; -use std::collections::HashMap; - pub(crate) fn new_test_ext() -> sp_io::TestExternalities { let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); diff --git a/frame/assets-freezer/src/tests.rs b/frame/assets-freezer/src/tests.rs index 8570ebc3be372..b651d1a95b35b 100644 --- a/frame/assets-freezer/src/tests.rs +++ b/frame/assets-freezer/src/tests.rs @@ -20,10 +20,88 @@ use super::*; use crate::{Error, mock::*}; use sp_runtime::TokenError; -use frame_support::{assert_ok, assert_noop, traits::Currency}; +use frame_support::{ + assert_ok, + assert_noop, + traits::{ + Currency, + fungibles::{ + Inspect, + MutateHold, + UnbalancedHold, + Transfer, + }, + }, +}; use pallet_balances::Error as BalancesError; +use pallet_assets::Error as AssetsError; fn last_event() -> mock::Event { frame_system::Pallet::::events().pop().expect("Event expected").event } +#[test] +fn basic_minting_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_eq!(AssetsFreezer::balance(0, &1), 100); + assert_eq!(AssetsFreezer::total_issuance(0), 100); + }); +} + +#[test] +fn hold_and_release_balance_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 200)); + assert_eq!(AssetsFreezer::can_hold(0, &1, 100), true); + assert_ok!(AssetsFreezer::hold(0, &1, 100)); + assert_eq!(AssetsFreezer::balance(0, &1), 200); + assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 100); + assert_ok!(AssetsFreezer::release(0, &1, 50, true)); + assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 50); + assert_ok!(AssetsFreezer::release(0, &1, 50, true)); + assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 0); + assert_eq!(AssetsFreezer::balance(0, &1), 200); + }); +} + +#[test] +fn decrease_and_destroy_held_asset_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 200)); + assert_eq!(AssetsFreezer::can_hold(0, &1, 100), true); + assert_ok!(AssetsFreezer::hold(0, &1, 100)); + assert_eq!(AssetsFreezer::balance(0, &1), 200); + assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 100); + assert_ok!(AssetsFreezer::decrease_balance_on_hold(0, &1, 50)); + assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 50); + assert_ok!(AssetsFreezer::release(0, &1, 50, true)); + assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 0); + assert_eq!(AssetsFreezer::balance(0, &1), 150); + }); +} + +#[test] +fn transfer_held_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 200)); + assert_ok!(AssetsFreezer::hold(0, &1, 100)); + assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 100); + assert_noop!(AssetsFreezer::transfer(0, &1, &2, 150, WhenDust::Dispose), AssetsError::::BalanceLow); + // Can't create the account with just a chunk of held balance - there needs to already be + // the minimum deposit. + assert_noop!(AssetsFreezer::transfer_held(0, &1, &2, 100, true), TokenError::CannotCreate); + assert_ok!(Assets::mint(Origin::signed(1), 0, 2, 1)); + assert_noop!(AssetsFreezer::transfer_held(0, &1, &2, 150, true), TokenError::NoFunds); + assert_eq!(AssetsFreezer::transfer_held(0, &1, &2, 100, true), Ok(100)); + assert_noop!(AssetsFreezer::transfer_held(0, &1, &2, 50, true), TokenError::NoFunds); + assert_eq!(AssetsFreezer::balance(0, &1), 100); + assert_eq!(AssetsFreezer::balance(0, &2), 101); + assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 0); + assert_eq!(AssetsFreezer::balance_on_hold(0, &2), 100); + }); +} \ No newline at end of file diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 461ce33db7af9..962d746e0232e 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -425,7 +425,7 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::force_create())] - pub(super) fn force_create( + pub fn force_create( origin: OriginFor, #[pallet::compact] id: T::AssetId, owner: ::Source, @@ -529,7 +529,7 @@ pub mod pallet { /// Weight: `O(1)` /// Modes: Pre-existing balance of `beneficiary`; Account pre-existence of `beneficiary`. #[pallet::weight(T::WeightInfo::mint())] - pub(super) fn mint( + pub fn mint( origin: OriginFor, #[pallet::compact] id: T::AssetId, beneficiary: ::Source, @@ -679,7 +679,7 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::freeze())] - pub(super) fn freeze( + pub fn freeze( origin: OriginFor, #[pallet::compact] id: T::AssetId, who: ::Source @@ -711,7 +711,7 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::thaw())] - pub(super) fn thaw( + pub fn thaw( origin: OriginFor, #[pallet::compact] id: T::AssetId, @@ -1099,7 +1099,7 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::approve_transfer())] - pub(super) fn approve_transfer( + pub fn approve_transfer( origin: OriginFor, #[pallet::compact] id: T::AssetId, delegate: ::Source, @@ -1139,7 +1139,7 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::cancel_approval())] - pub(super) fn cancel_approval( + pub fn cancel_approval( origin: OriginFor, #[pallet::compact] id: T::AssetId, delegate: ::Source, @@ -1213,7 +1213,7 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::transfer_approved())] - pub(super) fn transfer_approved( + pub fn transfer_approved( origin: OriginFor, #[pallet::compact] id: T::AssetId, owner: ::Source, From db5a62c376a5d79e118d029ed325928a5a85377a Mon Sep 17 00:00:00 2001 From: Ricardo Rius Date: Tue, 4 May 2021 15:20:37 +0200 Subject: [PATCH 13/22] Add more tests --- frame/assets-freezer/src/benchmarking.rs | 429 ----------------------- frame/assets-freezer/src/tests.rs | 115 ++++-- frame/assets-freezer/src/weights.rs | 339 ------------------ frame/assets/src/lib.rs | 10 +- 4 files changed, 100 insertions(+), 793 deletions(-) delete mode 100644 frame/assets-freezer/src/benchmarking.rs delete mode 100644 frame/assets-freezer/src/weights.rs diff --git a/frame/assets-freezer/src/benchmarking.rs b/frame/assets-freezer/src/benchmarking.rs deleted file mode 100644 index 227d45623d688..0000000000000 --- a/frame/assets-freezer/src/benchmarking.rs +++ /dev/null @@ -1,429 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2020-2021 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. - -//! Assets pallet benchmarking. - -#![cfg(feature = "runtime-benchmarks")] - -use sp_std::prelude::*; -use super::*; -use sp_runtime::traits::Bounded; -use frame_system::RawOrigin as SystemOrigin; -use frame_benchmarking::{ - benchmarks, account, whitelisted_caller, whitelist_account, impl_benchmark_test_suite -}; -use frame_support::traits::Get; -use frame_support::{traits::EnsureOrigin, dispatch::UnfilteredDispatchable}; - -use crate::Pallet as Assets; - -const SEED: u32 = 0; - -fn create_default_asset(is_sufficient: bool) - -> (T::AccountId, ::Source) -{ - let caller: T::AccountId = whitelisted_caller(); - let caller_lookup = T::Lookup::unlookup(caller.clone()); - let root = SystemOrigin::Root.into(); - assert!(Assets::::force_create( - root, - Default::default(), - caller_lookup.clone(), - is_sufficient, - 1u32.into(), - ).is_ok()); - (caller, caller_lookup) -} - -fn create_default_minted_asset(is_sufficient: bool, amount: T::Balance) - -> (T::AccountId, ::Source) -{ - let (caller, caller_lookup) = create_default_asset::(is_sufficient); - if !is_sufficient { - T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance()); - } - assert!(Assets::::mint( - SystemOrigin::Signed(caller.clone()).into(), - Default::default(), - caller_lookup.clone(), - amount, - ).is_ok()); - (caller, caller_lookup) -} - -fn swap_is_sufficient(s: &mut bool) { - Asset::::mutate(&T::AssetId::default(), |maybe_a| - if let Some(ref mut a) = maybe_a { sp_std::mem::swap(s, &mut a.is_sufficient) } - ); -} - -fn add_consumers(minter: T::AccountId, n: u32) { - let origin = SystemOrigin::Signed(minter); - let mut s = false; - swap_is_sufficient::(&mut s); - for i in 0..n { - let target = account("consumer", i, SEED); - T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); - let target_lookup = T::Lookup::unlookup(target); - assert!(Assets::::mint(origin.clone().into(), Default::default(), target_lookup, 100u32.into()).is_ok()); - } - swap_is_sufficient::(&mut s); -} - -fn add_sufficients(minter: T::AccountId, n: u32) { - let origin = SystemOrigin::Signed(minter); - let mut s = true; - swap_is_sufficient::(&mut s); - for i in 0..n { - let target = account("sufficient", i, SEED); - let target_lookup = T::Lookup::unlookup(target); - assert!(Assets::::mint(origin.clone().into(), Default::default(), target_lookup, 100u32.into()).is_ok()); - } - swap_is_sufficient::(&mut s); -} - -fn add_approvals(minter: T::AccountId, n: u32) { - T::Currency::deposit_creating(&minter, T::ApprovalDeposit::get() * n.into()); - let minter_lookup = T::Lookup::unlookup(minter.clone()); - let origin = SystemOrigin::Signed(minter); - Assets::::mint( - origin.clone().into(), - Default::default(), - minter_lookup, - (100 * (n + 1)).into(), - ).unwrap(); - for i in 0..n { - let target = account("approval", i, SEED); - T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); - let target_lookup = T::Lookup::unlookup(target); - Assets::::approve_transfer( - origin.clone().into(), - Default::default(), - target_lookup, - 100u32.into(), - ).unwrap(); - } -} - -fn assert_last_event(generic_event: ::Event) { - let events = frame_system::Pallet::::events(); - let system_event: ::Event = generic_event.into(); - // compare to the last event record - let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; - assert_eq!(event, &system_event); -} - -fn assert_event(generic_event: ::Event) { - let system_event: ::Event = generic_event.into(); - let events = frame_system::Pallet::::events(); - assert!(events.iter().any(|event_record| { - matches!(&event_record, frame_system::EventRecord { event, .. } if &system_event == event) - })); -} - -benchmarks! { - create { - let caller: T::AccountId = whitelisted_caller(); - let caller_lookup = T::Lookup::unlookup(caller.clone()); - T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, 1u32.into()) - verify { - assert_last_event::(Event::Created(Default::default(), caller.clone(), caller).into()); - } - - force_create { - let caller: T::AccountId = whitelisted_caller(); - let caller_lookup = T::Lookup::unlookup(caller.clone()); - }: _(SystemOrigin::Root, Default::default(), caller_lookup, true, 1u32.into()) - verify { - assert_last_event::(Event::ForceCreated(Default::default(), caller).into()); - } - - destroy { - let c in 0 .. 5_000; - let s in 0 .. 5_000; - let a in 0 .. 5_00; - let (caller, _) = create_default_asset::(true); - add_consumers::(caller.clone(), c); - add_sufficients::(caller.clone(), s); - add_approvals::(caller.clone(), a); - let witness = Asset::::get(T::AssetId::default()).unwrap().destroy_witness(); - }: _(SystemOrigin::Signed(caller), Default::default(), witness) - verify { - assert_last_event::(Event::Destroyed(Default::default()).into()); - } - - mint { - let (caller, caller_lookup) = create_default_asset::(true); - let amount = T::Balance::from(100u32); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, amount) - verify { - assert_last_event::(Event::Issued(Default::default(), caller, amount).into()); - } - - burn { - let amount = T::Balance::from(100u32); - let (caller, caller_lookup) = create_default_minted_asset::(true, amount); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, amount) - verify { - assert_last_event::(Event::Burned(Default::default(), caller, amount).into()); - } - - transfer { - let amount = T::Balance::from(100u32); - let (caller, caller_lookup) = create_default_minted_asset::(true, amount); - let target: T::AccountId = account("target", 0, SEED); - let target_lookup = T::Lookup::unlookup(target.clone()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), target_lookup, amount) - verify { - assert_last_event::(Event::Transferred(Default::default(), caller, target, amount).into()); - } - - transfer_keep_alive { - let mint_amount = T::Balance::from(200u32); - let amount = T::Balance::from(100u32); - let (caller, caller_lookup) = create_default_minted_asset::(true, mint_amount); - let target: T::AccountId = account("target", 0, SEED); - let target_lookup = T::Lookup::unlookup(target.clone()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), target_lookup, amount) - verify { - assert!(frame_system::Pallet::::account_exists(&caller)); - assert_last_event::(Event::Transferred(Default::default(), caller, target, amount).into()); - } - - force_transfer { - let amount = T::Balance::from(100u32); - let (caller, caller_lookup) = create_default_minted_asset::(true, amount); - let target: T::AccountId = account("target", 0, SEED); - let target_lookup = T::Lookup::unlookup(target.clone()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, target_lookup, amount) - verify { - assert_last_event::( - Event::Transferred(Default::default(), caller, target, amount).into() - ); - } - - freeze { - let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup) - verify { - assert_last_event::(Event::Frozen(Default::default(), caller).into()); - } - - thaw { - let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); - Assets::::freeze( - SystemOrigin::Signed(caller.clone()).into(), - Default::default(), - caller_lookup.clone(), - )?; - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup) - verify { - assert_last_event::(Event::Thawed(Default::default(), caller).into()); - } - - freeze_asset { - let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default()) - verify { - assert_last_event::(Event::AssetFrozen(Default::default()).into()); - } - - thaw_asset { - let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); - Assets::::freeze_asset( - SystemOrigin::Signed(caller.clone()).into(), - Default::default(), - )?; - }: _(SystemOrigin::Signed(caller.clone()), Default::default()) - verify { - assert_last_event::(Event::AssetThawed(Default::default()).into()); - } - - transfer_ownership { - let (caller, _) = create_default_asset::(true); - let target: T::AccountId = account("target", 0, SEED); - let target_lookup = T::Lookup::unlookup(target.clone()); - }: _(SystemOrigin::Signed(caller), Default::default(), target_lookup) - verify { - assert_last_event::(Event::OwnerChanged(Default::default(), target).into()); - } - - set_team { - let (caller, _) = create_default_asset::(true); - let target0 = T::Lookup::unlookup(account("target", 0, SEED)); - let target1 = T::Lookup::unlookup(account("target", 1, SEED)); - let target2 = T::Lookup::unlookup(account("target", 2, SEED)); - }: _(SystemOrigin::Signed(caller), Default::default(), target0.clone(), target1.clone(), target2.clone()) - verify { - assert_last_event::(Event::TeamChanged( - Default::default(), - account("target", 0, SEED), - account("target", 1, SEED), - account("target", 2, SEED), - ).into()); - } - - set_metadata { - let n in 0 .. T::StringLimit::get(); - let s in 0 .. T::StringLimit::get(); - - let name = vec![0u8; n as usize]; - let symbol = vec![0u8; s as usize]; - let decimals = 12; - - let (caller, _) = create_default_asset::(true); - T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - }: _(SystemOrigin::Signed(caller), Default::default(), name.clone(), symbol.clone(), decimals) - verify { - let id = Default::default(); - assert_last_event::(Event::MetadataSet(id, name, symbol, decimals, false).into()); - } - - clear_metadata { - let (caller, _) = create_default_asset::(true); - T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - let dummy = vec![0u8; T::StringLimit::get() as usize]; - let origin = SystemOrigin::Signed(caller.clone()).into(); - Assets::::set_metadata(origin, Default::default(), dummy.clone(), dummy, 12)?; - }: _(SystemOrigin::Signed(caller), Default::default()) - verify { - assert_last_event::(Event::MetadataCleared(Default::default()).into()); - } - - force_set_metadata { - let n in 0 .. T::StringLimit::get(); - let s in 0 .. T::StringLimit::get(); - - let name = vec![0u8; n as usize]; - let symbol = vec![0u8; s as usize]; - let decimals = 12; - - create_default_asset::(true); - - let origin = T::ForceOrigin::successful_origin(); - let call = Call::::force_set_metadata( - Default::default(), - name.clone(), - symbol.clone(), - decimals, - false, - ); - }: { call.dispatch_bypass_filter(origin)? } - verify { - let id = Default::default(); - assert_last_event::(Event::MetadataSet(id, name, symbol, decimals, false).into()); - } - - force_clear_metadata { - let (caller, _) = create_default_asset::(true); - T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - let dummy = vec![0u8; T::StringLimit::get() as usize]; - let origin = SystemOrigin::Signed(caller.clone()).into(); - Assets::::set_metadata(origin, Default::default(), dummy.clone(), dummy, 12)?; - - let origin = T::ForceOrigin::successful_origin(); - let call = Call::::force_clear_metadata(Default::default()); - }: { call.dispatch_bypass_filter(origin)? } - verify { - assert_last_event::(Event::MetadataCleared(Default::default()).into()); - } - - force_asset_status { - let (caller, caller_lookup) = create_default_asset::(true); - - let origin = T::ForceOrigin::successful_origin(); - let call = Call::::force_asset_status( - Default::default(), - caller_lookup.clone(), - caller_lookup.clone(), - caller_lookup.clone(), - caller_lookup.clone(), - 100u32.into(), - true, - false, - ); - }: { call.dispatch_bypass_filter(origin)? } - verify { - assert_last_event::(Event::AssetStatusChanged(Default::default()).into()); - } - - approve_transfer { - let (caller, _) = create_default_minted_asset::(true, 100u32.into()); - T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - - let id = Default::default(); - let delegate: T::AccountId = account("delegate", 0, SEED); - let delegate_lookup = T::Lookup::unlookup(delegate.clone()); - let amount = 100u32.into(); - }: _(SystemOrigin::Signed(caller.clone()), id, delegate_lookup, amount) - verify { - assert_last_event::(Event::ApprovedTransfer(id, caller, delegate, amount).into()); - } - - transfer_approved { - let (owner, owner_lookup) = create_default_minted_asset::(true, 100u32.into()); - T::Currency::make_free_balance_be(&owner, DepositBalanceOf::::max_value()); - - let id = Default::default(); - let delegate: T::AccountId = account("delegate", 0, SEED); - whitelist_account!(delegate); - let delegate_lookup = T::Lookup::unlookup(delegate.clone()); - let amount = 100u32.into(); - let origin = SystemOrigin::Signed(owner.clone()).into(); - Assets::::approve_transfer(origin, id, delegate_lookup.clone(), amount)?; - - let dest: T::AccountId = account("dest", 0, SEED); - let dest_lookup = T::Lookup::unlookup(dest.clone()); - }: _(SystemOrigin::Signed(delegate.clone()), id, owner_lookup, dest_lookup, amount) - verify { - assert!(T::Currency::reserved_balance(&owner).is_zero()); - assert_event::(Event::Transferred(id, owner, dest, amount).into()); - } - - cancel_approval { - let (caller, _) = create_default_minted_asset::(true, 100u32.into()); - T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - - let id = Default::default(); - let delegate: T::AccountId = account("delegate", 0, SEED); - let delegate_lookup = T::Lookup::unlookup(delegate.clone()); - let amount = 100u32.into(); - let origin = SystemOrigin::Signed(caller.clone()).into(); - Assets::::approve_transfer(origin, id, delegate_lookup.clone(), amount)?; - }: _(SystemOrigin::Signed(caller.clone()), id, delegate_lookup) - verify { - assert_last_event::(Event::ApprovalCancelled(id, caller, delegate).into()); - } - - force_cancel_approval { - let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); - T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - - let id = Default::default(); - let delegate: T::AccountId = account("delegate", 0, SEED); - let delegate_lookup = T::Lookup::unlookup(delegate.clone()); - let amount = 100u32.into(); - let origin = SystemOrigin::Signed(caller.clone()).into(); - Assets::::approve_transfer(origin, id, delegate_lookup.clone(), amount)?; - }: _(SystemOrigin::Signed(caller.clone()), id, caller_lookup, delegate_lookup) - verify { - assert_last_event::(Event::ApprovalCancelled(id, caller, delegate).into()); - } -} - -impl_benchmark_test_suite!(Assets, crate::mock::new_test_ext(), crate::mock::Test); diff --git a/frame/assets-freezer/src/tests.rs b/frame/assets-freezer/src/tests.rs index b651d1a95b35b..324a35699e466 100644 --- a/frame/assets-freezer/src/tests.rs +++ b/frame/assets-freezer/src/tests.rs @@ -18,13 +18,12 @@ //! Tests for Assets Freezer pallet. use super::*; -use crate::{Error, mock::*}; +use crate::mock::*; use sp_runtime::TokenError; use frame_support::{ assert_ok, assert_noop, traits::{ - Currency, fungibles::{ Inspect, MutateHold, @@ -33,7 +32,6 @@ use frame_support::{ }, }, }; -use pallet_balances::Error as BalancesError; use pallet_assets::Error as AssetsError; fn last_event() -> mock::Event { @@ -51,24 +49,23 @@ fn basic_minting_should_work() { } #[test] -fn hold_and_release_balance_should_work() { +fn hold_asset_balance_should_work() { new_test_ext().execute_with(|| { assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 200)); assert_eq!(AssetsFreezer::can_hold(0, &1, 100), true); assert_ok!(AssetsFreezer::hold(0, &1, 100)); - assert_eq!(AssetsFreezer::balance(0, &1), 200); + assert_eq!( + last_event(), + mock::Event::pallet_assets_freezer(crate::Event::Held(0, 1, 100)), + ); assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 100); - assert_ok!(AssetsFreezer::release(0, &1, 50, true)); - assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 50); - assert_ok!(AssetsFreezer::release(0, &1, 50, true)); - assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 0); assert_eq!(AssetsFreezer::balance(0, &1), 200); }); } #[test] -fn decrease_and_destroy_held_asset_should_work() { +fn decrease_and_remove_asset_on_hold_should_work() { new_test_ext().execute_with(|| { assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 200)); @@ -78,30 +75,108 @@ fn decrease_and_destroy_held_asset_should_work() { assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 100); assert_ok!(AssetsFreezer::decrease_balance_on_hold(0, &1, 50)); assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 50); - assert_ok!(AssetsFreezer::release(0, &1, 50, true)); - assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 0); assert_eq!(AssetsFreezer::balance(0, &1), 150); }); } #[test] -fn transfer_held_should_work() { +fn decrease_asset_on_hold_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 200)); + assert_eq!(AssetsFreezer::can_hold(0, &1, 100), true); + assert_ok!(AssetsFreezer::hold(0, &1, 100)); + assert_eq!(AssetsFreezer::balance(0, &1), 200); + assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 100); + assert_ok!(AssetsFreezer::decrease_on_hold(0, &1, 50)); + assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 50); + assert_eq!(AssetsFreezer::balance(0, &1), 200); + }); +} + +#[test] +fn decrease_reducible_asset_on_hold_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 200)); + assert_ok!(AssetsFreezer::hold(0, &1, 100)); + assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 100); + assert_eq!(AssetsFreezer::reducible_balance_on_hold(0, &1), 100); + assert_noop!(AssetsFreezer::decrease_on_hold(0, &1, 150), TokenError::NoFunds); + assert_ok!(AssetsFreezer::decrease_on_hold(0, &1, 50)); + assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 50); + assert_eq!(AssetsFreezer::balance(0, &1), 200); + }); +} + +#[test] +fn increase_asset_on_hold_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 200)); + assert_eq!(AssetsFreezer::can_hold(0, &1, 100), true); + assert_ok!(AssetsFreezer::hold(0, &1, 100)); + assert_eq!(AssetsFreezer::balance(0, &1), 200); + assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 100); + assert_ok!(AssetsFreezer::increase_on_hold(0, &1, 50)); + assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 150); + assert_eq!(AssetsFreezer::balance(0, &1), 200); + }); +} + +#[test] +fn release_asset_on_hold_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 200)); + assert_eq!(AssetsFreezer::can_hold(0, &1, 100), true); + assert_ok!(AssetsFreezer::hold(0, &1, 100)); + assert_eq!(AssetsFreezer::balance(0, &1), 200); + assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 100); + assert_ok!(AssetsFreezer::release(0, &1, 30, true)); + assert_eq!( + last_event(), + mock::Event::pallet_assets_freezer(crate::Event::Released(0, 1, 30)), + ); + assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 70); + assert_ok!(AssetsFreezer::release(0, &1, 70, true)); + assert_eq!( + last_event(), + mock::Event::pallet_assets_freezer(crate::Event::Released(0, 1, 70)), + ); + assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 0); + assert_eq!(AssetsFreezer::balance(0, &1), 200); + }); +} + +#[test] +fn transfer_asset_on_hold_should_work() { new_test_ext().execute_with(|| { assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 200)); assert_ok!(AssetsFreezer::hold(0, &1, 100)); assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 100); - assert_noop!(AssetsFreezer::transfer(0, &1, &2, 150, WhenDust::Dispose), AssetsError::::BalanceLow); - // Can't create the account with just a chunk of held balance - there needs to already be - // the minimum deposit. - assert_noop!(AssetsFreezer::transfer_held(0, &1, &2, 100, true), TokenError::CannotCreate); assert_ok!(Assets::mint(Origin::signed(1), 0, 2, 1)); - assert_noop!(AssetsFreezer::transfer_held(0, &1, &2, 150, true), TokenError::NoFunds); assert_eq!(AssetsFreezer::transfer_held(0, &1, &2, 100, true), Ok(100)); - assert_noop!(AssetsFreezer::transfer_held(0, &1, &2, 50, true), TokenError::NoFunds); assert_eq!(AssetsFreezer::balance(0, &1), 100); assert_eq!(AssetsFreezer::balance(0, &2), 101); assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 0); assert_eq!(AssetsFreezer::balance_on_hold(0, &2), 100); }); -} \ No newline at end of file +} + +#[test] +fn transfer_low_asset_on_hold_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 200)); + assert_ok!(AssetsFreezer::hold(0, &1, 100)); + assert_eq!(AssetsFreezer::balance_on_hold(0, &1), 100); + assert_noop!(AssetsFreezer::transfer(0, &1, &2, 150, WhenDust::Dispose), AssetsError::::BalanceLow); + // Can't create the account with just a chunk of held balance - there needs to already be + // the minimum deposit. + assert_noop!(AssetsFreezer::transfer_held(0, &1, &2, 150, true), TokenError::CannotCreate); + assert_ok!(Assets::mint(Origin::signed(1), 0, 2, 1)); + assert_noop!(AssetsFreezer::transfer_held(0, &1, &2, 150, true), TokenError::NoFunds); + }); +} diff --git a/frame/assets-freezer/src/weights.rs b/frame/assets-freezer/src/weights.rs deleted file mode 100644 index c3c804a392dbe..0000000000000 --- a/frame/assets-freezer/src/weights.rs +++ /dev/null @@ -1,339 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Autogenerated weights for pallet_assets -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 -//! DATE: 2021-03-08, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 - -// Executed Command: -// target/release/substrate -// benchmark -// --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=pallet_assets -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./frame/assets/src/weights.rs -// --template=./.maintain/frame-weight-template.hbs - - -#![allow(unused_parens)] -#![allow(unused_imports)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; - -/// Weight functions needed for pallet_assets. -pub trait WeightInfo { - fn create() -> Weight; - fn force_create() -> Weight; - fn destroy(c: u32, s: u32, a: u32, ) -> Weight; - fn mint() -> Weight; - fn burn() -> Weight; - fn transfer() -> Weight; - fn transfer_keep_alive() -> Weight; - fn force_transfer() -> Weight; - fn freeze() -> Weight; - fn thaw() -> Weight; - fn freeze_asset() -> Weight; - fn thaw_asset() -> Weight; - fn transfer_ownership() -> Weight; - fn set_team() -> Weight; - fn set_metadata(n: u32, s: u32, ) -> Weight; - fn clear_metadata() -> Weight; - fn force_set_metadata(n: u32, s: u32, ) -> Weight; - fn force_clear_metadata() -> Weight; - fn force_asset_status() -> Weight; - fn approve_transfer() -> Weight; - fn transfer_approved() -> Weight; - fn cancel_approval() -> Weight; - fn force_cancel_approval() -> Weight; -} - -/// Weights for pallet_assets using the Substrate node and recommended hardware. -pub struct SubstrateWeight(PhantomData); -impl WeightInfo for SubstrateWeight { - fn create() -> Weight { - (48_305_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn force_create() -> Weight { - (23_827_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn destroy(c: u32, s: u32, a: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 38_000 - .saturating_add((24_232_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 38_000 - .saturating_add((30_467_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 383_000 - .saturating_add((2_343_000 as Weight).saturating_mul(a as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) - .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(s as Weight))) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(c as Weight))) - .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(s as Weight))) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(a as Weight))) - } - fn mint() -> Weight { - (46_433_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - fn burn() -> Weight { - (46_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - fn transfer() -> Weight { - (70_793_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) - } - fn transfer_keep_alive() -> Weight { - (57_453_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) - } - fn force_transfer() -> Weight { - (70_968_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) - } - fn freeze() -> Weight { - (34_290_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn thaw() -> Weight { - (34_419_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn freeze_asset() -> Weight { - (24_373_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn thaw_asset() -> Weight { - (24_096_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn transfer_ownership() -> Weight { - (28_566_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn set_team() -> Weight { - (25_297_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn set_metadata(_n: u32, s: u32, ) -> Weight { - (53_367_000 as Weight) - // Standard Error: 0 - .saturating_add((8_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn clear_metadata() -> Weight { - (51_721_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn force_set_metadata(_n: u32, s: u32, ) -> Weight { - (27_117_000 as Weight) - // Standard Error: 0 - .saturating_add((5_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn force_clear_metadata() -> Weight { - (51_598_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn force_asset_status() -> Weight { - (23_366_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn approve_transfer() -> Weight { - (47_906_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn transfer_approved() -> Weight { - (90_338_000 as Weight) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) - } - fn cancel_approval() -> Weight { - (48_591_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn force_cancel_approval() -> Weight { - (54_879_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } -} - -// For backwards compatibility and tests -impl WeightInfo for () { - fn create() -> Weight { - (48_305_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn force_create() -> Weight { - (23_827_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn destroy(c: u32, s: u32, a: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 38_000 - .saturating_add((24_232_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 38_000 - .saturating_add((30_467_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 383_000 - .saturating_add((2_343_000 as Weight).saturating_mul(a as Weight)) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) - .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(s as Weight))) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(c as Weight))) - .saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(s as Weight))) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(a as Weight))) - } - fn mint() -> Weight { - (46_433_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - fn burn() -> Weight { - (46_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - fn transfer() -> Weight { - (70_793_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) - } - fn transfer_keep_alive() -> Weight { - (57_453_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) - } - fn force_transfer() -> Weight { - (70_968_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) - } - fn freeze() -> Weight { - (34_290_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn thaw() -> Weight { - (34_419_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn freeze_asset() -> Weight { - (24_373_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn thaw_asset() -> Weight { - (24_096_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn transfer_ownership() -> Weight { - (28_566_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn set_team() -> Weight { - (25_297_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn set_metadata(_n: u32, s: u32, ) -> Weight { - (53_367_000 as Weight) - // Standard Error: 0 - .saturating_add((8_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn clear_metadata() -> Weight { - (51_721_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn force_set_metadata(_n: u32, s: u32, ) -> Weight { - (27_117_000 as Weight) - // Standard Error: 0 - .saturating_add((5_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn force_clear_metadata() -> Weight { - (51_598_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn force_asset_status() -> Weight { - (23_366_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn approve_transfer() -> Weight { - (47_906_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn transfer_approved() -> Weight { - (90_338_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) - } - fn cancel_approval() -> Weight { - (48_591_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn force_cancel_approval() -> Weight { - (54_879_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } -} diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 962d746e0232e..5a32b5b4388fb 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -679,7 +679,7 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::freeze())] - pub fn freeze( + pub(super) fn freeze( origin: OriginFor, #[pallet::compact] id: T::AssetId, who: ::Source @@ -711,7 +711,7 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::thaw())] - pub fn thaw( + pub(super) fn thaw( origin: OriginFor, #[pallet::compact] id: T::AssetId, @@ -1099,7 +1099,7 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::approve_transfer())] - pub fn approve_transfer( + pub(super) fn approve_transfer( origin: OriginFor, #[pallet::compact] id: T::AssetId, delegate: ::Source, @@ -1139,7 +1139,7 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::cancel_approval())] - pub fn cancel_approval( + pub(super) fn cancel_approval( origin: OriginFor, #[pallet::compact] id: T::AssetId, delegate: ::Source, @@ -1213,7 +1213,7 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::transfer_approved())] - pub fn transfer_approved( + pub(super) fn transfer_approved( origin: OriginFor, #[pallet::compact] id: T::AssetId, owner: ::Source, From fbe71561fed65208d221b41ebddb3a1ca836da01 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Sun, 16 May 2021 13:37:17 -0400 Subject: [PATCH 14/22] fix --- frame/support/src/traits/tokens/misc.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index 1e8444577480c..40febbd3f8f3a 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -53,17 +53,17 @@ pub enum WithdrawConsequence { impl WithdrawConsequence { /// Convert the type into a `Result` with `DispatchError` as the error or the additional `Balance` /// by which the account will be reduced. - pub fn into_result(self, keep_alive: bool) -> Result { + pub fn into_result(self, keep_alive: bool) -> Result { use WithdrawConsequence::*; match self { Success => Ok(Zero::zero()), ReducedToZero(result) if !keep_alive => Ok(result), - WouldDie | ReducedToZero(_) => Err(TokenError::WouldDie), - UnknownAsset => Err(TokenError::UnknownAsset), + WouldDie | ReducedToZero(_) => Err(TokenError::WouldDie.into()), + UnknownAsset => Err(TokenError::UnknownAsset.into()), Underflow => Err(ArithmeticError::Underflow.into()), Overflow => Err(ArithmeticError::Overflow.into()), - Frozen => Err(TokenError::Frozen), - NoFunds => Err(TokenError::NoFunds), + Frozen => Err(TokenError::Frozen.into()), + NoFunds => Err(TokenError::NoFunds.into()), } } } From dbcfa7df89a25df9574d3daab383f13e847e8d3c Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Sun, 16 May 2021 13:38:33 -0400 Subject: [PATCH 15/22] more fix --- frame/assets-freezer/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/assets-freezer/src/lib.rs b/frame/assets-freezer/src/lib.rs index a75b6c68c3aee..14dbc2b39d5ca 100644 --- a/frame/assets-freezer/src/lib.rs +++ b/frame/assets-freezer/src/lib.rs @@ -28,7 +28,7 @@ pub mod mock; mod tests; use sp_std::prelude::*; -use sp_runtime::{TokenError, traits::{Zero, Saturating, CheckedAdd, CheckedSub}}; +use sp_runtime::{TokenError, ArithmeticError, traits::{Zero, Saturating, CheckedAdd, CheckedSub}}; use frame_support::{ensure, dispatch::{DispatchError, DispatchResult}}; use frame_support::traits::{ StoredMap, tokens::{ @@ -276,7 +276,7 @@ impl fungibles::MutateHold<::AccountId> for Pallet let min_balance = > ::minimum_balance(asset); let dest_balance = >::balance(asset, dest); ensure!(!on_hold || dest_balance >= min_balance, TokenError::CannotCreate); - Self::balance_on_hold(asset, dest).checked_add(&amount).ok_or(TokenError::Overflow)?; + Self::balance_on_hold(asset, dest).checked_add(&amount).ok_or(ArithmeticError::Overflow)?; Self::decrease_on_hold_ensuring_backed(asset, source, amount)?; From 4f99fb0f65e9228c54788395549cf34902dc5938 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Wed, 19 May 2021 15:43:06 +0200 Subject: [PATCH 16/22] Update frame/assets-freezer/Cargo.toml Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/assets-freezer/Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/frame/assets-freezer/Cargo.toml b/frame/assets-freezer/Cargo.toml index f404331596f3e..0d60553e64570 100644 --- a/frame/assets-freezer/Cargo.toml +++ b/frame/assets-freezer/Cargo.toml @@ -16,11 +16,8 @@ targets = ["x86_64-unknown-linux-gnu"] serde = { version = "1.0.101", optional = true } codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } sp-std = { version = "3.0.0", default-features = false, path = "../../primitives/std" } -# Needed for various traits. In our case, `OnFinalize`. sp-runtime = { version = "3.0.0", default-features = false, path = "../../primitives/runtime" } -# Needed for type-safe access to storage DB. frame-support = { version = "3.0.0", default-features = false, path = "../support" } -# `system` module provides us with all sorts of useful stuff and macros depend on it being around. frame-system = { version = "3.0.0", default-features = false, path = "../system" } frame-benchmarking = { version = "3.1.0", default-features = false, path = "../benchmarking", optional = true } From 82564cda3047348c2712f5ff9b27d60fba7320b3 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Thu, 27 May 2021 16:55:23 +0100 Subject: [PATCH 17/22] Update frame/assets/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/assets/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 09e2e7f5be844..eac949dc5a1ad 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -567,8 +567,7 @@ pub mod pallet { let f = DebitFlags { keep_alive: false, ignore_freezer: false }; let amount = amount.min(Self::reducible_balance(id, &who, f)?); - let burned = Self::do_burn(id, &who, amount, Some(origin), f)?; - Self::deposit_event(Event::Burned(id, who, burned)); + let _ = Self::do_burn(id, &who, amount, Some(origin), f)?; Ok(()) } From 21cbcc6a718233c0b198ae308115184e197b50d6 Mon Sep 17 00:00:00 2001 From: joepetrowski Date: Thu, 27 May 2021 20:42:50 +0200 Subject: [PATCH 18/22] remove duplicate mint event --- frame/assets/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 81d43b8edf6d9..3fe3f4ed4cd14 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -535,7 +535,6 @@ pub mod pallet { let origin = ensure_signed(origin)?; let beneficiary = T::Lookup::lookup(beneficiary)?; Self::do_mint(id, &beneficiary, amount, Some(origin))?; - Self::deposit_event(Event::Issued(id, beneficiary, amount)); Ok(()) } From db1779694f8838bb015c75c6504bcd134a07af74 Mon Sep 17 00:00:00 2001 From: joepetrowski Date: Fri, 28 May 2021 08:04:18 +0200 Subject: [PATCH 19/22] fix build --- frame/assets/src/lib.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 3fe3f4ed4cd14..02168b7eb1a8b 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -1223,12 +1223,7 @@ pub mod pallet { .checked_sub(&amount) .ok_or(Error::::Unapproved)?; - let f = TransferFlags { - keep_alive: false, - best_effort: false, - burn_dust: false - }; - Self::do_transfer(id, &owner, &destination, amount, None, f)?; + Self::do_transfer(id, &owner, &destination, amount, None, WhenDust::Credit)?; if remaining.is_zero() { T::Currency::unreserve(&owner, approved.deposit); From 66132c575e67fe4b98cffb4498ce882f97044d18 Mon Sep 17 00:00:00 2001 From: joepetrowski Date: Fri, 28 May 2021 08:09:22 +0200 Subject: [PATCH 20/22] bump spec --- bin/node/runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 92f3d43901a97..708dec9569006 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -114,7 +114,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 265, + spec_version: 266, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 2, From cc4a9104a1a4c794a016edf84bd0d2a9b1d8abfe Mon Sep 17 00:00:00 2001 From: joepetrowski Date: Fri, 28 May 2021 10:37:01 +0200 Subject: [PATCH 21/22] revert bump spec --- bin/node/runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 708dec9569006..92f3d43901a97 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -114,7 +114,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 266, + spec_version: 265, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 2, From ac14c0e3bb1110cc040a61010324a826b18c5dc3 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Fri, 28 May 2021 15:26:20 +0200 Subject: [PATCH 22/22] Update frame/assets-freezer/src/lib.rs --- frame/assets-freezer/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/assets-freezer/src/lib.rs b/frame/assets-freezer/src/lib.rs index 14dbc2b39d5ca..cc6c385de4446 100644 --- a/frame/assets-freezer/src/lib.rs +++ b/frame/assets-freezer/src/lib.rs @@ -273,7 +273,7 @@ impl fungibles::MutateHold<::AccountId> for Pallet ) -> Result, DispatchError> { // Can't create the account with just a chunk of held balance - there needs to already be // the minimum deposit. - let min_balance = > ::minimum_balance(asset); + let min_balance = >::minimum_balance(asset); let dest_balance = >::balance(asset, dest); ensure!(!on_hold || dest_balance >= min_balance, TokenError::CannotCreate); Self::balance_on_hold(asset, dest).checked_add(&amount).ok_or(ArithmeticError::Overflow)?;