From a0f5015b70e6ceb78ab52c16979327b511426d7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Tue, 21 May 2024 00:42:22 -0500 Subject: [PATCH 01/59] change(pallet-assets): define HeldBalance --- substrate/frame/assets/src/lib.rs | 5 +++++ substrate/frame/assets/src/mock.rs | 1 + substrate/frame/assets/src/types.rs | 31 +++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/substrate/frame/assets/src/lib.rs b/substrate/frame/assets/src/lib.rs index 6891f04dfb51..923bd9e63078 100644 --- a/substrate/frame/assets/src/lib.rs +++ b/substrate/frame/assets/src/lib.rs @@ -357,6 +357,11 @@ pub mod pallet { #[pallet::no_default] type Freezer: FrozenBalance; + /// A hook to allow a per-asset, per-account minimum balance to be enforced. This must be + /// respected in all permissionless operations. + #[pallet::no_default] + type Holder: HeldBalance; + /// Additional data to be stored with an account's asset balance. type Extra: Member + Parameter + Default + MaxEncodedLen; diff --git a/substrate/frame/assets/src/mock.rs b/substrate/frame/assets/src/mock.rs index f6173a451fff..a2ebdeba9ef3 100644 --- a/substrate/frame/assets/src/mock.rs +++ b/substrate/frame/assets/src/mock.rs @@ -114,6 +114,7 @@ impl Config for Test { type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = frame_system::EnsureRoot; type Freezer = TestFreezer; + type Holder = (); type CallbackHandle = AssetsCallbackHandle; } diff --git a/substrate/frame/assets/src/types.rs b/substrate/frame/assets/src/types.rs index 11edc7d3fcb5..e994385b400a 100644 --- a/substrate/frame/assets/src/types.rs +++ b/substrate/frame/assets/src/types.rs @@ -232,6 +232,37 @@ impl FrozenBalance for fn died(_: AssetId, _: &AccountId) {} } +/// Trait for specifying a balance that cannot be reduced or forcely reduced. +/// This balance is then added to the minimum balance that must be met in conjunction +/// with the frozen balance (if any), and the `minimum_balance` of the asset. +pub trait HeldBalance { + /// Return the held balance. + /// + /// Generally, the balance of every account must be at least the sum of this (if `Some`) and + /// the asset's `minimum_balance` (the latter since there may be complications to destroying an + /// asset's account completely). + /// + /// Under normal circumstances, the account balance should not go below the sum of this (if + /// `Some`) and the asset's minimum balance. + /// + /// In special cases (privileged intervention) the account balance may also go below the sum. + /// + /// If `None` is returned, then nothing special is enforced. + fn held_balance(asset: AssetId, who: &AccountId) -> Option; + + /// Called after an account has been removed. + /// + /// NOTE: It is possible that the asset does no longer exist when this hook is called. + fn died(asset: AssetId, who: &AccountId); +} + +impl HeldBalance for () { + fn held_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 From 6d2aa57bd9746a5ab3dc3319238241e8c3774664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Mon, 10 Jun 2024 11:29:16 -0500 Subject: [PATCH 02/59] change(pallet-assets): implement `Holder` to calculate reducible balance based on whether there's held assets or not --- Cargo.lock | 4 ++++ substrate/frame/assets/Cargo.toml | 1 + substrate/frame/assets/src/functions.rs | 28 +++++++++++++++---------- substrate/frame/assets/src/tests.rs | 1 + substrate/frame/assets/src/types.rs | 17 ++++++--------- 5 files changed, 29 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27cb7af04d63..888f39c3175e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5212,8 +5212,11 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" dependencies = [ + "atty", + "humantime", "log", "regex", + "termcolor", ] [[package]] @@ -9573,6 +9576,7 @@ dependencies = [ name = "pallet-assets" version = "29.0.0" dependencies = [ + "env_logger 0.8.4", "frame-benchmarking", "frame-support", "frame-system", diff --git a/substrate/frame/assets/Cargo.toml b/substrate/frame/assets/Cargo.toml index 3b95750c14c8..1a8246f72d4f 100644 --- a/substrate/frame/assets/Cargo.toml +++ b/substrate/frame/assets/Cargo.toml @@ -30,6 +30,7 @@ frame-benchmarking = { path = "../benchmarking", default-features = false, optio sp-core = { path = "../../primitives/core", default-features = false } [dev-dependencies] +env_logger = "*" sp-std = { path = "../../primitives/std" } sp-io = { path = "../../primitives/io" } pallet-balances = { path = "../balances" } diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index 8791aaa736b3..5ec85de3b960 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -221,20 +221,26 @@ impl, I: 'static> Pallet { let account = Account::::get(&id, who).ok_or(Error::::NoAccount)?; ensure!(!account.status.is_frozen(), Error::::Frozen); - let amount = if let Some(frozen) = T::Freezer::frozen_balance(id, who) { - // Frozen balance: account CANNOT be deleted - let required = - frozen.checked_add(&details.min_balance).ok_or(ArithmeticError::Overflow)?; - account.balance.saturating_sub(required) + let frozen = T::Freezer::frozen_balance(id.clone(), who).unwrap_or_default(); + let amount = if let Some(held) = T::Holder::held_balance(id, who) { + let untouchable = frozen + .saturating_sub(held) + .checked_add(&details.min_balance) + .ok_or(ArithmeticError::Overflow)?; + account.balance.saturating_sub(untouchable) } else { - if keep_alive { - // We want to keep the account around. - account.balance.saturating_sub(details.min_balance) + let required = if frozen > Zero::zero() { + frozen.checked_add(&details.min_balance).ok_or(ArithmeticError::Overflow)? } else { - // Don't care if the account dies - account.balance - } + if keep_alive { + details.min_balance + } else { + Zero::zero() + } + }; + account.balance.saturating_sub(required) }; + Ok(amount.min(details.supply)) } diff --git a/substrate/frame/assets/src/tests.rs b/substrate/frame/assets/src/tests.rs index c7021bcad531..3b5b56aa3fc2 100644 --- a/substrate/frame/assets/src/tests.rs +++ b/substrate/frame/assets/src/tests.rs @@ -1290,6 +1290,7 @@ fn finish_destroy_asset_destroys_asset() { #[test] fn freezer_should_work() { + let _ = env_logger::try_init(); new_test_ext().execute_with(|| { assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); diff --git a/substrate/frame/assets/src/types.rs b/substrate/frame/assets/src/types.rs index e994385b400a..d1f8718c0b5f 100644 --- a/substrate/frame/assets/src/types.rs +++ b/substrate/frame/assets/src/types.rs @@ -232,20 +232,15 @@ impl FrozenBalance for fn died(_: AssetId, _: &AccountId) {} } -/// Trait for specifying a balance that cannot be reduced or forcely reduced. -/// This balance is then added to the minimum balance that must be met in conjunction -/// with the frozen balance (if any), and the `minimum_balance` of the asset. +/// Trait for specifying a balance that is distinct from the free balance. +/// This balance is then summed up with the balance of the account, and the +/// `minimum_balance` (and frozen balance, if any) of the asset to calculate +/// the reducible balance. pub trait HeldBalance { /// Return the held balance. /// - /// Generally, the balance of every account must be at least the sum of this (if `Some`) and - /// the asset's `minimum_balance` (the latter since there may be complications to destroying an - /// asset's account completely). - /// - /// Under normal circumstances, the account balance should not go below the sum of this (if - /// `Some`) and the asset's minimum balance. - /// - /// In special cases (privileged intervention) the account balance may also go below the sum. + /// If `Some`, it means some balance is suspended, and it can be infallibly + /// slashed. /// /// If `None` is returned, then nothing special is enforced. fn held_balance(asset: AssetId, who: &AccountId) -> Option; From 029e887382193818eca705b7895ab8a5f0fcecf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Mon, 17 Jun 2024 10:24:29 -0500 Subject: [PATCH 03/59] change: add Holder type to missing pallet_assets Config implementations --- cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs | 3 +++ .../parachains/runtimes/assets/asset-hub-westend/src/lib.rs | 3 +++ cumulus/parachains/runtimes/testing/penpal/src/lib.rs | 2 ++ .../parachains/runtimes/testing/rococo-parachain/src/lib.rs | 1 + polkadot/xcm/pallet-xcm/src/mock.rs | 1 + polkadot/xcm/xcm-builder/src/tests/pay/mock.rs | 1 + substrate/bin/node/runtime/src/lib.rs | 2 ++ substrate/frame/asset-conversion/src/mock.rs | 2 ++ substrate/frame/contracts/mock-network/src/parachain.rs | 1 + substrate/frame/nft-fractionalization/src/mock.rs | 1 + .../asset-conversion-tx-payment/src/mock.rs | 2 ++ .../frame/transaction-payment/asset-tx-payment/src/mock.rs | 1 + 12 files changed, 20 insertions(+) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index 23d8f9b667dd..ed4e8b521f87 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -265,6 +265,7 @@ impl pallet_assets::Config for Runtime { type MetadataDepositPerByte = MetadataDepositPerByte; type ApprovalDeposit = ApprovalDeposit; type StringLimit = AssetsStringLimit; + type Holder = (); type Freezer = (); type Extra = (); type WeightInfo = weights::pallet_assets_local::WeightInfo; @@ -303,6 +304,7 @@ impl pallet_assets::Config for Runtime { type MetadataDepositPerByte = ConstU128<0>; type ApprovalDeposit = ApprovalDeposit; type StringLimit = ConstU32<50>; + type Holder = (); type Freezer = (); type Extra = (); type WeightInfo = weights::pallet_assets_pool::WeightInfo; @@ -398,6 +400,7 @@ impl pallet_assets::Config for Runtime { type MetadataDepositPerByte = ForeignAssetsMetadataDepositPerByte; type ApprovalDeposit = ForeignAssetsApprovalDeposit; type StringLimit = ForeignAssetsAssetsStringLimit; + type Holder = (); type Freezer = (); type Extra = (); type WeightInfo = weights::pallet_assets_foreign::WeightInfo; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index cb2f11637187..5475c0c23d76 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -248,6 +248,7 @@ impl pallet_assets::Config for Runtime { type MetadataDepositPerByte = MetadataDepositPerByte; type ApprovalDeposit = ApprovalDeposit; type StringLimit = AssetsStringLimit; + type Holder = (); type Freezer = (); type Extra = (); type WeightInfo = weights::pallet_assets_local::WeightInfo; @@ -285,6 +286,7 @@ impl pallet_assets::Config for Runtime { type MetadataDepositPerByte = ConstU128<0>; type ApprovalDeposit = ConstU128<0>; type StringLimit = ConstU32<50>; + type Holder = (); type Freezer = (); type Extra = (); type WeightInfo = weights::pallet_assets_pool::WeightInfo; @@ -377,6 +379,7 @@ impl pallet_assets::Config for Runtime { type MetadataDepositPerByte = ForeignAssetsMetadataDepositPerByte; type ApprovalDeposit = ForeignAssetsApprovalDeposit; type StringLimit = ForeignAssetsAssetsStringLimit; + type Holder = (); type Freezer = (); type Extra = (); type WeightInfo = weights::pallet_assets_foreign::WeightInfo; diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 89885d77378b..6c4d72cb6b56 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -445,6 +445,7 @@ impl pallet_assets::Config for Runtime { type MetadataDepositPerByte = MetadataDepositPerByte; type ApprovalDeposit = ApprovalDeposit; type StringLimit = AssetsStringLimit; + type Holder = (); type Freezer = (); type Extra = (); type WeightInfo = pallet_assets::weights::SubstrateWeight; @@ -480,6 +481,7 @@ impl pallet_assets::Config for Runtime { type MetadataDepositPerByte = ForeignAssetsMetadataDepositPerByte; type ApprovalDeposit = ForeignAssetsApprovalDeposit; type StringLimit = ForeignAssetsAssetsStringLimit; + type Holder = (); type Freezer = (); type Extra = (); type WeightInfo = pallet_assets::weights::SubstrateWeight; diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs index df335368be1c..3d3468f53d91 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs @@ -580,6 +580,7 @@ impl pallet_assets::Config for Runtime { type MetadataDepositPerByte = MetadataDepositPerByte; type ApprovalDeposit = ApprovalDeposit; type StringLimit = AssetsStringLimit; + type Holder = (); type Freezer = (); type Extra = (); type WeightInfo = pallet_assets::weights::SubstrateWeight; diff --git a/polkadot/xcm/pallet-xcm/src/mock.rs b/polkadot/xcm/pallet-xcm/src/mock.rs index 2cc228476ba8..7948d2b346c9 100644 --- a/polkadot/xcm/pallet-xcm/src/mock.rs +++ b/polkadot/xcm/pallet-xcm/src/mock.rs @@ -314,6 +314,7 @@ impl pallet_assets::Config for Test { type MetadataDepositPerByte = ConstU128<1>; type ApprovalDeposit = ConstU128<1>; type StringLimit = ConstU32<50>; + type Holder = (); type Freezer = (); type WeightInfo = (); type CallbackHandle = (); diff --git a/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs b/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs index 019113a12b2f..8ac3d8288920 100644 --- a/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs +++ b/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs @@ -103,6 +103,7 @@ impl pallet_assets::Config for Test { type AssetAccountDeposit = AssetAccountDeposit; type ApprovalDeposit = ApprovalDeposit; type StringLimit = AssetsStringLimit; + type Holder = (); type Freezer = (); type Extra = (); type WeightInfo = (); diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index faf7cf7967b7..09d5bc8bb6d5 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1658,6 +1658,7 @@ impl pallet_assets::Config for Runtime { type MetadataDepositPerByte = MetadataDepositPerByte; type ApprovalDeposit = ApprovalDeposit; type StringLimit = StringLimit; + type Holder = (); type Freezer = (); type Extra = (); type CallbackHandle = (); @@ -1685,6 +1686,7 @@ impl pallet_assets::Config for Runtime { type MetadataDepositPerByte = MetadataDepositPerByte; type ApprovalDeposit = ApprovalDeposit; type StringLimit = StringLimit; + type Holder = (); type Freezer = (); type Extra = (); type WeightInfo = pallet_assets::weights::SubstrateWeight; diff --git a/substrate/frame/asset-conversion/src/mock.rs b/substrate/frame/asset-conversion/src/mock.rs index 4591b87c1867..2a50d17f286a 100644 --- a/substrate/frame/asset-conversion/src/mock.rs +++ b/substrate/frame/asset-conversion/src/mock.rs @@ -92,6 +92,7 @@ impl pallet_assets::Config for Test { type MetadataDepositPerByte = ConstU128<1>; type ApprovalDeposit = ConstU128<1>; type StringLimit = ConstU32<50>; + type Holder = (); type Freezer = (); type Extra = (); type WeightInfo = (); @@ -117,6 +118,7 @@ impl pallet_assets::Config for Test { type MetadataDepositPerByte = ConstU128<0>; type ApprovalDeposit = ConstU128<0>; type StringLimit = ConstU32<50>; + type Holder = (); type Freezer = (); type Extra = (); type WeightInfo = (); diff --git a/substrate/frame/contracts/mock-network/src/parachain.rs b/substrate/frame/contracts/mock-network/src/parachain.rs index d4ad47581d16..b680ba1a9f0f 100644 --- a/substrate/frame/contracts/mock-network/src/parachain.rs +++ b/substrate/frame/contracts/mock-network/src/parachain.rs @@ -125,6 +125,7 @@ impl pallet_assets::Config for Runtime { type AssetAccountDeposit = AssetAccountDeposit; type ApprovalDeposit = ApprovalDeposit; type StringLimit = AssetsStringLimit; + type Holder = (); type Freezer = (); type Extra = (); type WeightInfo = (); diff --git a/substrate/frame/nft-fractionalization/src/mock.rs b/substrate/frame/nft-fractionalization/src/mock.rs index 82a608816260..14ac58122459 100644 --- a/substrate/frame/nft-fractionalization/src/mock.rs +++ b/substrate/frame/nft-fractionalization/src/mock.rs @@ -88,6 +88,7 @@ impl pallet_assets::Config for Test { type MetadataDepositPerByte = ConstU64<1>; type ApprovalDeposit = ConstU64<1>; type StringLimit = ConstU32<50>; + type Holder = (); type Freezer = (); type Extra = (); type CallbackHandle = (); diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs index d61558cf5363..7ee4600f0ea6 100644 --- a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs +++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs @@ -199,6 +199,7 @@ impl pallet_assets::Config for Runtime { type MetadataDepositPerByte = ConstU64<0>; type ApprovalDeposit = ConstU64<0>; type StringLimit = ConstU32<20>; + type Holder = (); type Freezer = (); type Extra = (); type CallbackHandle = (); @@ -224,6 +225,7 @@ impl pallet_assets::Config for Runtime { type MetadataDepositPerByte = ConstU64<0>; type ApprovalDeposit = ConstU64<0>; type StringLimit = ConstU32<50>; + type Holder = (); type Freezer = (); type Extra = (); type WeightInfo = (); diff --git a/substrate/frame/transaction-payment/asset-tx-payment/src/mock.rs b/substrate/frame/transaction-payment/asset-tx-payment/src/mock.rs index b04d4ffd9e0b..c925eaa9d4ee 100644 --- a/substrate/frame/transaction-payment/asset-tx-payment/src/mock.rs +++ b/substrate/frame/transaction-payment/asset-tx-payment/src/mock.rs @@ -162,6 +162,7 @@ impl pallet_assets::Config for Runtime { type MetadataDepositPerByte = ConstU64<0>; type ApprovalDeposit = ConstU64<0>; type StringLimit = ConstU32<20>; + type Holder = (); type Freezer = (); type Extra = (); type CallbackHandle = (); From bf376a5931f0c22a6ca4352a50aad8df369e8c62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Mon, 17 Jun 2024 10:25:07 -0500 Subject: [PATCH 04/59] change(pallet-assets): call died hook for Holder whenever necessary --- substrate/frame/assets/src/functions.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index 5ec85de3b960..4c780f737371 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -370,7 +370,8 @@ impl, I: 'static> Pallet { } Asset::::insert(&id, details); // Executing a hook here is safe, since it is not in a `mutate`. - T::Freezer::died(id, &who); + T::Freezer::died(id.clone(), &who); + T::Holder::died(id, &who); Ok(()) } @@ -401,7 +402,8 @@ impl, I: 'static> Pallet { } Asset::::insert(&id, details); // Executing a hook here is safe, since it is not in a `mutate`. - T::Freezer::died(id, &who); + T::Freezer::died(id.clone(), &who); + T::Holder::died(id, &who); return Ok(()) } @@ -569,7 +571,8 @@ impl, I: 'static> Pallet { // Execute hook outside of `mutate`. if let Some(Remove) = target_died { - T::Freezer::died(id, target); + T::Freezer::died(id.clone(), target); + T::Holder::died(id, target); } Ok(actual) } @@ -593,7 +596,8 @@ impl, I: 'static> Pallet { let (balance, died) = Self::transfer_and_die(id.clone(), source, dest, amount, maybe_need_admin, f)?; if let Some(Remove) = died { - T::Freezer::died(id, source); + T::Freezer::died(id.clone(), source); + T::Holder::died(id, source); } Ok(balance) } @@ -791,6 +795,7 @@ impl, I: 'static> Pallet { for who in &dead_accounts { T::Freezer::died(id.clone(), &who); + T::Holder::died(id.clone(), &who); } Self::deposit_event(Event::AccountsDestroyed { @@ -951,7 +956,8 @@ impl, I: 'static> Pallet { // Execute hook outside of `mutate`. if let Some(Remove) = owner_died { - T::Freezer::died(id, owner); + T::Freezer::died(id.clone(), owner); + T::Holder::died(id, owner); } Ok(()) } From 89ea43a436c15f463c130573294f2bb4707c7029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Mon, 17 Jun 2024 11:10:52 -0500 Subject: [PATCH 05/59] fix(pallet-asset-conversion-ops): missing Holder in mock config --- polkadot/xcm/xcm-fee-payment-runtime-api/tests/mock.rs | 1 + substrate/frame/asset-conversion/ops/src/mock.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/polkadot/xcm/xcm-fee-payment-runtime-api/tests/mock.rs b/polkadot/xcm/xcm-fee-payment-runtime-api/tests/mock.rs index aa6c1422b608..912d2c1fea4b 100644 --- a/polkadot/xcm/xcm-fee-payment-runtime-api/tests/mock.rs +++ b/polkadot/xcm/xcm-fee-payment-runtime-api/tests/mock.rs @@ -87,6 +87,7 @@ impl pallet_assets::Config for TestRuntime { type Currency = Balances; type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = frame_system::EnsureRoot; + type Holder = (); type Freezer = (); type AssetDeposit = ConstU128<1>; type AssetAccountDeposit = ConstU128<10>; diff --git a/substrate/frame/asset-conversion/ops/src/mock.rs b/substrate/frame/asset-conversion/ops/src/mock.rs index 91c18b2e7949..31c3bb41fd12 100644 --- a/substrate/frame/asset-conversion/ops/src/mock.rs +++ b/substrate/frame/asset-conversion/ops/src/mock.rs @@ -68,6 +68,7 @@ impl pallet_assets::Config for Test { type Currency = Balances; type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = frame_system::EnsureRoot; + type Holder = (); type Freezer = (); } @@ -77,6 +78,7 @@ impl pallet_assets::Config for Test { type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = frame_system::EnsureRoot; + type Holder = (); type Freezer = (); } From 2ab35354d86904c035b21a2229452841b79b0457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 10 Jul 2024 14:34:15 +0200 Subject: [PATCH 06/59] change(pallet-assets): adjust reducible/can_decrease to be in line with the tokens' _balance components_ model. On the most recent documentation about tokens (both _fungible_ and _fungibles_ (sets)), the model for calculating the different balance components is explained. The prior implementation of `pallet-assets` featured a weird definition of how to handle them that was not in line with the expected (see ) definition of tokens. This commit changes this implementation for methods `reducible_balance` and `can_decrease` to introduce the calculation of `spendable` balance, and the consequences derived of decreasing a balance that may include both `frozen` and `held` balances. --- substrate/frame/assets/src/functions.rs | 70 +++++++++++++------------ substrate/frame/assets/src/tests.rs | 8 +-- 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index 075af2712622..4e8131de62f5 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -186,22 +186,31 @@ impl, I: 'static> Pallet { return Frozen } if let Some(rest) = account.balance.checked_sub(&amount) { - if let Some(frozen) = T::Freezer::frozen_balance(id.clone(), who) { - match frozen.checked_add(&details.min_balance) { - Some(required) if rest < required => return Frozen, - None => return Overflow, - _ => {}, - } - } + match ( + T::Holder::held_balance(id.clone(), who), + T::Freezer::frozen_balance(id.clone(), who), + ) { + (None, None) => + if rest < details.min_balance { + if keep_alive { + WouldDie + } else { + ReducedToZero(rest) + } + } else { + Success + }, + (maybe_held, maybe_frozen) => { + let frozen = maybe_frozen.unwrap_or_default(); + let held = maybe_held.unwrap_or_default(); - if rest < details.min_balance { - if keep_alive { - WouldDie - } else { - ReducedToZero(rest) - } - } else { - Success + let required = frozen.saturating_sub(held).max(details.min_balance); + if rest < required { + return Frozen + } + + Success + }, } } else { BalanceLow @@ -221,25 +230,20 @@ impl, I: 'static> Pallet { let account = Account::::get(&id, who).ok_or(Error::::NoAccount)?; ensure!(!account.status.is_frozen(), Error::::Frozen); - let frozen = T::Freezer::frozen_balance(id.clone(), who).unwrap_or_default(); - let amount = if let Some(held) = T::Holder::held_balance(id, who) { - let untouchable = frozen - .saturating_sub(held) - .checked_add(&details.min_balance) - .ok_or(ArithmeticError::Overflow)?; - account.balance.saturating_sub(untouchable) - } else { - let required = if frozen > Zero::zero() { - frozen.checked_add(&details.min_balance).ok_or(ArithmeticError::Overflow)? - } else { - if keep_alive { - details.min_balance - } else { - Zero::zero() - } - }; - account.balance.saturating_sub(required) + let untouchable = match ( + T::Holder::held_balance(id.clone(), who), + T::Freezer::frozen_balance(id.clone(), who), + keep_alive, + ) { + (None, None, true) => details.min_balance, + (None, None, false) => Zero::zero(), + (maybe_held, maybe_frozen, _) => { + let held = maybe_held.unwrap_or_default(); + let frozen = maybe_frozen.unwrap_or_default(); + frozen.saturating_sub(held).max(details.min_balance) + }, }; + let amount = account.balance.saturating_sub(untouchable); Ok(amount.min(details.supply)) } diff --git a/substrate/frame/assets/src/tests.rs b/substrate/frame/assets/src/tests.rs index 3b5b56aa3fc2..00283dd90286 100644 --- a/substrate/frame/assets/src/tests.rs +++ b/substrate/frame/assets/src/tests.rs @@ -1299,9 +1299,9 @@ fn freezer_should_work() { // freeze 50 of it. set_frozen_balance(0, 1, 50); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 20)); - // cannot transfer another 21 away as this would take the non-frozen balance (30) to below - // the minimum balance (10). + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 30)); + // cannot transfer another 21 away as this would take the spendable balance (30) to below + // zero. assert_noop!( Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 21), Error::::BalanceLow @@ -1324,7 +1324,7 @@ fn freezer_should_work() { // and if we clear it, we can remove the account completely. clear_frozen_balance(0, 1); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 49)); assert_eq!(hooks(), vec![Hook::Died(0, 1)]); }); } From c76b654c5243e1ea8233f2dcb26c84f5980cc0f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Thu, 11 Jul 2024 10:56:38 +0200 Subject: [PATCH 07/59] change(pallet-assets): introduce tests to check for holds and freezes working together --- substrate/frame/assets/src/mock.rs | 35 ++++++++++++++++++++-- substrate/frame/assets/src/tests.rs | 45 ++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/substrate/frame/assets/src/mock.rs b/substrate/frame/assets/src/mock.rs index 9fe7bb99c1f6..e9a0306726d6 100644 --- a/substrate/frame/assets/src/mock.rs +++ b/substrate/frame/assets/src/mock.rs @@ -22,7 +22,7 @@ use crate as pallet_assets; use codec::Encode; use frame_support::{ - construct_runtime, derive_impl, parameter_types, + assert_ok, construct_runtime, derive_impl, parameter_types, traits::{AsEnsureOriginWithArg, ConstU32}, }; use sp_io::storage; @@ -103,7 +103,7 @@ impl Config for Test { type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = frame_system::EnsureRoot; type Freezer = TestFreezer; - type Holder = (); + type Holder = TestHolder; type CallbackHandle = (AssetsCallbackHandle, AutoIncAssetId); } @@ -115,9 +115,40 @@ pub enum Hook { } parameter_types! { static Frozen: HashMap<(u32, u64), u64> = Default::default(); + static Held: HashMap<(u32, u64), u64> = Default::default(); static Hooks: Vec = Default::default(); } +pub struct TestHolder; +impl HeldBalance for TestHolder { + fn held_balance(asset: u32, who: &u64) -> Option { + Held::get().get(&(asset, *who)).cloned() + } + + fn died(_asset: u32, _who: &u64) { + // TODO: Connect with existing hooks list + } +} + +pub(crate) fn set_held_balance(asset: u32, who: u64, amount: u64) { + Held::mutate(|v| { + let held_amount = v.get(&(asset, who)).unwrap_or(&0); + + if &amount > held_amount { + // Hold more funds + let amount = amount - held_amount; + let f = DebitFlags { keep_alive: true, best_effort: false }; + assert_ok!(Assets::decrease_balance(asset, &who, amount, f, |_, _| Ok(()))); + } else { + // Release held funds + let amount = held_amount - amount; + assert_ok!(Assets::increase_balance(asset, &who, amount, |_| Ok(()))); + } + + // Asset amount still "exists", we just store it here + v.insert((asset, who), amount); + }); +} pub struct TestFreezer; impl FrozenBalance for TestFreezer { fn frozen_balance(asset: u32, who: &u64) -> Option { diff --git a/substrate/frame/assets/src/tests.rs b/substrate/frame/assets/src/tests.rs index 70dd619ac8bf..e54ce926f86d 100644 --- a/substrate/frame/assets/src/tests.rs +++ b/substrate/frame/assets/src/tests.rs @@ -1294,7 +1294,6 @@ fn finish_destroy_asset_destroys_asset() { #[test] fn freezer_should_work() { - let _ = env_logger::try_init(); new_test_ext().execute_with(|| { assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); @@ -1333,6 +1332,50 @@ fn freezer_should_work() { }); } +#[test] +fn freezing_and_holds_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + + // Hold 50 of it + set_held_balance(0, 1, 50); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(TestHolder::held_balance(0, &1), Some(50)); + + // Can freeze up to held + min_balance without affecting reducible + set_frozen_balance(0, 1, 59); + assert_eq!(Assets::reducible_balance(0, &1, true), Ok(40)); + set_frozen_balance(0, 1, 61); + assert_eq!(Assets::reducible_balance(0, &1, true), Ok(39)); + + // Increasing hold is not necessarily restricted by the frozen balance + set_held_balance(0, 1, 62); + assert_eq!(Assets::reducible_balance(0, &1, true), Ok(28)); + + // Transfers are bound to the spendable amount + assert_noop!( + Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 29), + Error::::BalanceLow + ); + // Approved transfers fail as well + Balances::make_free_balance_be(&1, 2); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 29)); + assert_noop!( + Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 2, 29), + Error::::BalanceLow + ); + // Also forced transfers fail + assert_noop!( + Assets::force_transfer(RuntimeOrigin::signed(1), 0, 1, 2, 29), + Error::::BalanceLow + ); + // ...but transferring up to spendable works + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 28)); + }); +} + #[test] fn imbalances_should_work() { use frame_support::traits::fungibles::Balanced; From a4bb10cfe8f79cced347deededf27cf08e883e27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Sun, 28 Jul 2024 23:12:27 -0400 Subject: [PATCH 08/59] change(pallet-assets-freezer): adjust mock and tests to changes in `pallet-assets` --- substrate/frame/assets-freezer/src/mock.rs | 1 + substrate/frame/assets-freezer/src/tests.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/substrate/frame/assets-freezer/src/mock.rs b/substrate/frame/assets-freezer/src/mock.rs index 5e04dfe8e2b9..ec1ba9964167 100644 --- a/substrate/frame/assets-freezer/src/mock.rs +++ b/substrate/frame/assets-freezer/src/mock.rs @@ -105,6 +105,7 @@ impl pallet_assets::Config for Test { type RemoveItemsLimit = ConstU32<10>; type CallbackHandle = (); type Currency = Balances; + type Holder = (); type Freezer = AssetsFreezer; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); diff --git a/substrate/frame/assets-freezer/src/tests.rs b/substrate/frame/assets-freezer/src/tests.rs index 4f2dea79c705..cee14a90d16a 100644 --- a/substrate/frame/assets-freezer/src/tests.rs +++ b/substrate/frame/assets-freezer/src/tests.rs @@ -168,7 +168,7 @@ mod impl_mutate_freeze { Preservation::Preserve, Fortitude::Polite, ), - 89 + 90 ); System::assert_last_event( Event::::Frozen { asset_id: ASSET_ID, who: WHO, amount: 10 }.into(), @@ -186,7 +186,7 @@ mod impl_mutate_freeze { Preservation::Preserve, Fortitude::Polite, ), - 91 + 92 ); System::assert_last_event( Event::::Thawed { asset_id: ASSET_ID, who: WHO, amount: 2 }.into(), @@ -219,7 +219,7 @@ mod impl_mutate_freeze { Preservation::Preserve, Fortitude::Polite, ), - 89 + 90 ); assert_ok!(AssetsFreezer::extend_freeze( ASSET_ID, @@ -237,7 +237,7 @@ mod impl_mutate_freeze { Preservation::Preserve, Fortitude::Polite, ), - 88 + 89 ); }); } @@ -261,7 +261,7 @@ mod impl_mutate_freeze { Preservation::Preserve, Fortitude::Polite, ), - 89 + 90 ); assert_ok!(AssetsFreezer::thaw(ASSET_ID, &DummyFreezeReason::Governance, &WHO)); System::assert_has_event( @@ -295,10 +295,10 @@ mod with_pallet_assets { 20 )); assert_noop!( - Assets::transfer(RuntimeOrigin::signed(WHO), Compact(ASSET_ID), 2, 80), + Assets::transfer(RuntimeOrigin::signed(WHO), Compact(ASSET_ID), 2, 81), pallet_assets::Error::::BalanceLow, ); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(WHO), Compact(ASSET_ID), 2, 79)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(WHO), Compact(ASSET_ID), 2, 80)); }); } } From 32fa5c1c2d40453a16824f41a413a55300cbd221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 31 Jul 2024 22:27:16 -0400 Subject: [PATCH 09/59] feat(pallet-assets-holder): pallet implementation --- Cargo.lock | 23 +- Cargo.toml | 1 + substrate/frame/assets-holder/Cargo.toml | 63 +++ substrate/frame/assets-holder/src/impls.rs | 253 +++++++++++ substrate/frame/assets-holder/src/lib.rs | 168 ++++++++ substrate/frame/assets-holder/src/mock.rs | 156 +++++++ substrate/frame/assets-holder/src/tests.rs | 480 +++++++++++++++++++++ 7 files changed, 1140 insertions(+), 4 deletions(-) create mode 100644 substrate/frame/assets-holder/Cargo.toml create mode 100644 substrate/frame/assets-holder/src/impls.rs create mode 100644 substrate/frame/assets-holder/src/lib.rs create mode 100644 substrate/frame/assets-holder/src/mock.rs create mode 100644 substrate/frame/assets-holder/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index f38149b1806e..0512f96be22b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5230,11 +5230,8 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" dependencies = [ - "atty", - "humantime", "log", "regex", - "termcolor", ] [[package]] @@ -9778,7 +9775,7 @@ dependencies = [ name = "pallet-assets" version = "29.1.0" dependencies = [ - "env_logger 0.8.4", + "env_logger 0.11.3", "frame-benchmarking", "frame-support", "frame-system", @@ -9810,6 +9807,24 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "pallet-assets-holder" +version = "0.1.0" +dependencies = [ + "env_logger 0.11.3", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-assets", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", +] + [[package]] name = "pallet-atomic-swap" version = "28.0.0" diff --git a/Cargo.toml b/Cargo.toml index 5c2677fffeb2..a1caf537deb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -306,6 +306,7 @@ members = [ "substrate/frame/asset-conversion/ops", "substrate/frame/asset-rate", "substrate/frame/assets", + "substrate/frame/assets-holder", "substrate/frame/assets-freezer", "substrate/frame/atomic-swap", "substrate/frame/aura", diff --git a/substrate/frame/assets-holder/Cargo.toml b/substrate/frame/assets-holder/Cargo.toml new file mode 100644 index 000000000000..a2501725d3c9 --- /dev/null +++ b/substrate/frame/assets-holder/Cargo.toml @@ -0,0 +1,63 @@ +[package] +authors.workspace = true +description = "Provides holding features to `pallet-assets`" +edition.workspace = true +homepage = "https://substrate.io" +license = "MIT-0" +name = "pallet-assets-holder" +repository.workspace = true +version = "0.1.0" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +log = {workspace = true} + +codec = {workspace = true} +frame-benchmarking = {optional = true, workspace = true} +frame-support = {workspace = true} +frame-system = {workspace = true} +pallet-assets = {workspace = true} +scale-info = {features = ["derive"], workspace = true} +sp-runtime = {workspace = true} + +[dev-dependencies] +env_logger = {workspace = true} +pallet-balances = {workspace = true} +sp-core = {workspace = true} +sp-io = {workspace = true} + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-assets/std", + "pallet-balances/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-assets/try-runtime", + "pallet-balances/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/assets-holder/src/impls.rs b/substrate/frame/assets-holder/src/impls.rs new file mode 100644 index 000000000000..6266b41c22aa --- /dev/null +++ b/substrate/frame/assets-holder/src/impls.rs @@ -0,0 +1,253 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; + +use frame_support::traits::{ + fungibles::{Dust, Inspect, InspectHold, MutateHold, Unbalanced, UnbalancedHold}, + tokens::{ + DepositConsequence, Fortitude, Precision, Preservation, Provenance, WithdrawConsequence, + }, +}; +use pallet_assets::HeldBalance; +use sp_runtime::{ + traits::{CheckedAdd, CheckedSub, Zero}, + ArithmeticError, +}; + +// Implements [`HeldBalance`] from [`pallet-assets`], so it can understand whether there's a held +// amount for an asset account, and is able to signal to this pallet when to clear the state of an +// account. +impl, I: 'static> HeldBalance for Pallet { + fn held_balance(asset: T::AssetId, who: &T::AccountId) -> Option { + HeldBalances::::get(asset, who) + } + + fn died(asset: T::AssetId, who: &T::AccountId) { + HeldBalances::::remove(asset.clone(), who); + Holds::::remove(asset, who); + } +} + +// Implement [`fungibles::Inspect`](frame_support::traits::fungibles::Inspect) as it is bound by +// [`fungibles::InspectHold`](frame_support::traits::fungibles::InspectHold) and +// [`fungibles::MutateHold`](frame_support::traits::fungibles::MutateHold). To do so, we'll +// re-export all of `pallet-assets` implementation of the same trait. +impl, I: 'static> Inspect for Pallet { + type AssetId = T::AssetId; + type Balance = T::Balance; + + fn total_issuance(asset: Self::AssetId) -> Self::Balance { + pallet_assets::Pallet::::total_issuance(asset) + } + + fn minimum_balance(asset: Self::AssetId) -> Self::Balance { + pallet_assets::Pallet::::minimum_balance(asset) + } + + fn total_balance(asset: Self::AssetId, who: &T::AccountId) -> Self::Balance { + pallet_assets::Pallet::::total_balance(asset, who) + } + + fn balance(asset: Self::AssetId, who: &T::AccountId) -> Self::Balance { + pallet_assets::Pallet::::balance(asset, who) + } + + fn reducible_balance( + asset: Self::AssetId, + who: &T::AccountId, + preservation: Preservation, + force: Fortitude, + ) -> Self::Balance { + pallet_assets::Pallet::::reducible_balance(asset, who, preservation, force) + } + + fn can_deposit( + asset: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + provenance: Provenance, + ) -> DepositConsequence { + pallet_assets::Pallet::::can_deposit(asset, who, amount, provenance) + } + + fn can_withdraw( + asset: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + ) -> WithdrawConsequence { + pallet_assets::Pallet::::can_withdraw(asset, who, amount) + } + + fn asset_exists(asset: Self::AssetId) -> bool { + pallet_assets::Pallet::::asset_exists(asset) + } +} + +impl, I: 'static> InspectHold for Pallet { + type Reason = T::RuntimeHoldReason; + + fn total_balance_on_hold(asset: Self::AssetId, who: &T::AccountId) -> Self::Balance { + HeldBalances::::get(asset, who).unwrap_or_else(Zero::zero) + } + + fn balance_on_hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &T::AccountId, + ) -> Self::Balance { + Holds::::get(asset, who) + .iter() + .find(|x| &x.id == reason) + .map(|x| x.amount) + .unwrap_or_else(Zero::zero) + } +} + +impl, I: 'static> Unbalanced for Pallet { + fn handle_dust(dust: Dust) { + let Dust(id, balance) = dust; + pallet_assets::Pallet::::handle_dust(Dust(id, balance)); + } + + fn write_balance( + asset: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + ) -> Result, DispatchError> { + pallet_assets::Pallet::::write_balance(asset, who, amount) + } + + fn set_total_issuance(asset: Self::AssetId, amount: Self::Balance) { + pallet_assets::Pallet::::set_total_issuance(asset, amount) + } + + fn decrease_balance( + asset: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + precision: Precision, + preservation: Preservation, + force: Fortitude, + ) -> Result { + pallet_assets::Pallet::::decrease_balance( + asset, + who, + amount, + precision, + preservation, + force, + ) + } + + fn increase_balance( + asset: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + pallet_assets::Pallet::::increase_balance(asset, who, amount, precision) + } +} + +impl, I: 'static> UnbalancedHold for Pallet { + fn set_balance_on_hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &T::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + let mut holds = Holds::::get(asset.clone(), who); + let mut increase = true; + let mut delta = amount; + + if let Some(item) = holds.iter_mut().find(|x| &x.id == reason) { + delta = item.amount.max(amount) - item.amount.min(amount); + increase = amount > item.amount; + item.amount = amount; + holds.retain(|x| !x.amount.is_zero()); + } else { + if !amount.is_zero() { + holds + .try_push(IdAmount { id: *reason, amount }) + .map_err(|_| Error::::TooManyHolds)?; + } + } + + let held_amount = HeldBalances::::get(asset.clone(), who).unwrap_or_else(Zero::zero); + + let held_amount = if increase { + held_amount.checked_add(&delta).ok_or(ArithmeticError::Overflow)? + } else { + held_amount.checked_sub(&delta).ok_or(ArithmeticError::Underflow)? + }; + + if held_amount.is_zero() { + HeldBalances::::remove(asset.clone(), who); + } else { + HeldBalances::::insert(asset.clone(), who, held_amount); + } + + Holds::::insert(asset, who, holds); + + Ok(()) + } +} + +impl, I: 'static> MutateHold for Pallet { + fn done_hold( + asset_id: Self::AssetId, + reason: &Self::Reason, + who: &T::AccountId, + amount: Self::Balance, + ) { + Self::deposit_event(Event::::Held { + asset_id, + who: who.clone(), + reason: *reason, + amount, + }); + } + + fn done_release( + asset_id: Self::AssetId, + reason: &Self::Reason, + who: &T::AccountId, + amount: Self::Balance, + ) { + Self::deposit_event(Event::::Released { + asset_id, + who: who.clone(), + reason: *reason, + amount, + }); + } + + fn done_burn_held( + asset_id: Self::AssetId, + reason: &Self::Reason, + who: &T::AccountId, + amount: Self::Balance, + ) { + Self::deposit_event(Event::::Slashed { + asset_id, + who: who.clone(), + reason: *reason, + amount, + }); + } +} diff --git a/substrate/frame/assets-holder/src/lib.rs b/substrate/frame/assets-holder/src/lib.rs new file mode 100644 index 000000000000..b50e6035f596 --- /dev/null +++ b/substrate/frame/assets-holder/src/lib.rs @@ -0,0 +1,168 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Assets Holder Pallet +//! +//! A pallet capable of holding fungibles from `pallet-assets`. This is an extension of +//! `pallet-assets`, wrapping [`fungibles::Inspect`](`frame_support::traits::fungibles::Inspect`). +//! It implements both +//! [`fungibles::hold::Inspect`](frame_support::traits::fungibles::hold::Inspect), +//! [`fungibles::hold::Mutate`](frame_support::traits::fungibles::hold::Mutate), and especially +//! [`fungibles::hold::Unbalanced`](frame_support::traits::fungibles::hold::Unbalanced). The +//! complexity of the operations is `O(1)`. +//! +//! ## Pallet API +//! +//! See the [`pallet`] module for more information about the interfaces this pallet exposes, +//! including its configuration trait, dispatchables, storage items, events and errors. +//! +//! ## Overview +//! +//! This pallet provides the following functionality: +//! +//! - Pallet hooks allowing [`pallet-assets`] to know the held balance for an account on a given +//! asset (see [`pallet_assets::HeldBalance`]). +//! - An implementation of +//! [`fungibles::hold::Inspect`](frame_support::traits::fungibles::hold::Inspect), +//! [`fungibles::hold::Mutate`](frame_support::traits::fungibles::hold::Mutate) and +//! [`fungibles::hold::Unbalanced`](frame_support::traits::fungibles::hold::Unbalanced), allowing +//! other pallets to manage holds for the `pallet-assets` assets. + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{ + pallet_prelude::*, + traits::{tokens::IdAmount, VariantCount, VariantCountOf}, + BoundedVec, +}; +use frame_system::pallet_prelude::BlockNumberFor; + +pub use pallet::*; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +mod impls; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config + pallet_assets::Config { + /// The overarching freeze reason. + #[pallet::no_default_bounds] + type RuntimeHoldReason: Parameter + Member + MaxEncodedLen + Copy + VariantCount; + + /// The overarching event type. + #[pallet::no_default_bounds] + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + } + + #[pallet::error] + pub enum Error { + /// Number of holds on an account would exceed the count of `RuntimeHoldReason`. + TooManyHolds, + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + // `who`s held balance was increased by `amount`. + Held { + who: T::AccountId, + asset_id: T::AssetId, + reason: T::RuntimeHoldReason, + amount: T::Balance, + }, + // `who`s held balance was decreased by `amount`. + Released { + who: T::AccountId, + asset_id: T::AssetId, + reason: T::RuntimeHoldReason, + amount: T::Balance, + }, + // `who`s held balance was burned by `amount`. + Slashed { + who: T::AccountId, + asset_id: T::AssetId, + reason: T::RuntimeHoldReason, + amount: T::Balance, + }, + } + + /// A map that stores holds applied on an account for a given AssetId. + #[pallet::storage] + pub(super) type Holds, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::AssetId, + Blake2_128Concat, + T::AccountId, + BoundedVec< + IdAmount, + VariantCountOf, + >, + ValueQuery, + >; + + /// A map that stores the current total held balance for every account on a given AssetId. + #[pallet::storage] + pub(super) type HeldBalances, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::AssetId, + Blake2_128Concat, + T::AccountId, + T::Balance, + >; + + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + #[cfg(feature = "try-runtime")] + fn try_state(_: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { + Self::do_try_state() + } + } +} + +impl, I: 'static> Pallet { + #[cfg(any(test, feature = "try-runtime"))] + fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { + use sp_runtime::traits::{Saturating, Zero}; + + for (asset, who, _) in HeldBalances::::iter() { + let held_amount: T::Balance = Holds::::get(asset.clone(), who.clone()) + .iter() + .map(|l| l.amount) + .fold(Zero::zero(), |prev, amount| prev.saturating_add(amount)); + + frame_support::ensure!( + HeldBalances::::get(asset, who).unwrap_or_else(Zero::zero) == held_amount, + "The `HeldAmount` is not equal to the sum of `Holds` for (`asset`, `who`)" + ); + } + + Ok(()) + } +} diff --git a/substrate/frame/assets-holder/src/mock.rs b/substrate/frame/assets-holder/src/mock.rs new file mode 100644 index 000000000000..be9df18518b4 --- /dev/null +++ b/substrate/frame/assets-holder/src/mock.rs @@ -0,0 +1,156 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests mock for `pallet-assets-freezer`. + +use crate as pallet_assets_holder; +pub use crate::*; +use codec::{Compact, Decode, Encode, MaxEncodedLen}; +use frame_support::{ + derive_impl, + traits::{AsEnsureOriginWithArg, ConstU64}, +}; +use scale_info::TypeInfo; +use sp_core::{ConstU32, H256}; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; + +pub type AccountId = u64; +pub type Balance = u64; +pub type AssetId = u32; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Assets: pallet_assets, + AssetsHolder: pallet_assets_holder, + Balances: pallet_balances, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; + type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type RuntimeFreezeReason = (); +} + +impl pallet_assets::Config for Test { + type AssetId = AssetId; + type AssetIdParameter = Compact; + type AssetDeposit = ConstU64<1>; + type Balance = Balance; + type AssetAccountDeposit = ConstU64<1>; + type MetadataDepositBase = (); + type MetadataDepositPerByte = (); + type ApprovalDeposit = (); + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type StringLimit = ConstU32<32>; + type Extra = (); + type RemoveItemsLimit = ConstU32<10>; + type CallbackHandle = (); + type Currency = Balances; + type Holder = AssetsHolder; + type Freezer = (); + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +#[derive( + Decode, Encode, MaxEncodedLen, PartialEq, Eq, Ord, PartialOrd, TypeInfo, Debug, Clone, Copy, +)] +pub enum DummyHoldReason { + Governance, + Staking, + Other, +} + +impl VariantCount for DummyHoldReason { + // Intentionally set below the actual count of variants, to allow testing for `can_freeze` + const VARIANT_COUNT: u32 = 2; +} + +impl Config for Test { + type RuntimeHoldReason = DummyHoldReason; + type RuntimeEvent = RuntimeEvent; +} + +pub fn new_test_ext(execute: impl FnOnce()) -> sp_io::TestExternalities { + let t = RuntimeGenesisConfig { + assets: pallet_assets::GenesisConfig { + assets: vec![(1, 0, true, 1)], + metadata: vec![], + accounts: vec![(1, 1, 100)], + next_asset_id: None, + }, + system: Default::default(), + balances: Default::default(), + } + .build_storage() + .unwrap(); + let mut ext: sp_io::TestExternalities = t.into(); + ext.execute_with(|| { + System::set_block_number(1); + execute(); + frame_support::assert_ok!(AssetsHolder::do_try_state()); + }); + + ext +} diff --git a/substrate/frame/assets-holder/src/tests.rs b/substrate/frame/assets-holder/src/tests.rs new file mode 100644 index 000000000000..1f8addd4650a --- /dev/null +++ b/substrate/frame/assets-holder/src/tests.rs @@ -0,0 +1,480 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for pallet-assets-holder. + +use crate::mock::*; + +use frame_support::{ + assert_noop, assert_ok, + traits::tokens::fungibles::{Inspect, InspectHold, MutateHold, UnbalancedHold}, +}; +use pallet_assets::HeldBalance; + +const WHO: AccountId = 1; +const ASSET_ID: AssetId = 1; + +fn test_hold(id: DummyHoldReason, amount: Balance) { + assert_ok!(AssetsHolder::set_balance_on_hold(ASSET_ID, &id, &WHO, amount)); +} + +fn test_release(id: DummyHoldReason) { + assert_ok!(AssetsHolder::set_balance_on_hold(ASSET_ID, &id, &WHO, 0)); +} + +mod impl_held_balance { + use super::*; + + #[test] + fn held_balance_works() { + let _ = env_logger::init(); + new_test_ext(|| { + assert_eq!(AssetsHolder::held_balance(ASSET_ID, &WHO), None); + test_hold(DummyHoldReason::Governance, 1); + assert_eq!(AssetsHolder::held_balance(ASSET_ID, &WHO), Some(1u64)); + test_hold(DummyHoldReason::Staking, 3); + assert_eq!(AssetsHolder::held_balance(ASSET_ID, &WHO), Some(4u64)); + test_hold(DummyHoldReason::Governance, 2); + assert_eq!(AssetsHolder::held_balance(ASSET_ID, &WHO), Some(5u64)); + // also test releasing works to reduce a balance, and finally releasing everything + // resets to None + test_release(DummyHoldReason::Governance); + assert_eq!(AssetsHolder::held_balance(ASSET_ID, &WHO), Some(3u64)); + test_release(DummyHoldReason::Staking); + assert_eq!(AssetsHolder::held_balance(ASSET_ID, &WHO), None); + }); + } + + #[test] + fn died_works() { + new_test_ext(|| { + test_hold(DummyHoldReason::Governance, 1); + AssetsHolder::died(ASSET_ID, &WHO); + assert!(HeldBalances::::get(ASSET_ID, WHO).is_none()); + assert!(Holds::::get(ASSET_ID, WHO).is_empty()); + }); + } +} + +mod impl_hold_inspect { + use super::*; + + #[test] + fn total_balance_on_hold_works() { + new_test_ext(|| { + assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 0u64); + test_hold(DummyHoldReason::Governance, 1); + assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 1u64); + test_hold(DummyHoldReason::Staking, 3); + assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 4u64); + test_hold(DummyHoldReason::Governance, 2); + assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 5u64); + // also test release to reduce a balance, and finally releasing everything resets to + // 0 + test_release(DummyHoldReason::Governance); + assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 3u64); + test_release(DummyHoldReason::Staking); + assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 0u64); + }); + } + + #[test] + fn balance_on_hold_works() { + new_test_ext(|| { + assert_eq!( + AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Governance, &WHO), + 0u64 + ); + test_hold(DummyHoldReason::Governance, 1); + assert_eq!( + AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Governance, &WHO), + 1u64 + ); + test_hold(DummyHoldReason::Staking, 3); + assert_eq!( + AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Staking, &WHO), + 3u64 + ); + test_hold(DummyHoldReason::Staking, 2); + assert_eq!( + AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Staking, &WHO), + 2u64 + ); + // also test release to reduce a balance, and finally releasing everything resets to + // 0 + test_release(DummyHoldReason::Governance); + assert_eq!( + AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Governance, &WHO), + 0u64 + ); + test_release(DummyHoldReason::Staking); + assert_eq!( + AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Staking, &WHO), + 0u64 + ); + }); + } +} + +mod impl_hold_unbalanced { + use super::*; + + // Note: Tests for `handle_dust`, `write_balance`, `set_total_issuance`, `decrease_balance` + // and `increase_balance` are intentionally left out without testing, since: + // 1. It is expected these methods are tested within `pallet-assets`, and + // 2. There are no valid cases that can be directly asserted using those methods in + // the scope of this pallet. + + #[test] + fn set_balance_on_hold_works() { + new_test_ext(|| { + assert_eq!(Holds::::get(ASSET_ID, WHO).to_vec(), vec![]); + assert_eq!(HeldBalances::::get(ASSET_ID, WHO), None); + // Adding held balance works + assert_ok!(AssetsHolder::set_balance_on_hold( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO, + 1 + )); + assert_eq!( + Holds::::get(ASSET_ID, WHO).to_vec(), + vec![IdAmount { id: DummyHoldReason::Governance, amount: 1 }] + ); + assert_eq!(HeldBalances::::get(ASSET_ID, WHO), Some(1)); + // Increasing hold works + assert_ok!(AssetsHolder::set_balance_on_hold( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO, + 3 + )); + assert_eq!( + Holds::::get(ASSET_ID, WHO).to_vec(), + vec![IdAmount { id: DummyHoldReason::Governance, amount: 3 }] + ); + assert_eq!(HeldBalances::::get(ASSET_ID, WHO), Some(3)); + // Adding new held balance works + assert_ok!(AssetsHolder::set_balance_on_hold( + ASSET_ID, + &DummyHoldReason::Staking, + &WHO, + 2 + )); + assert_eq!( + Holds::::get(ASSET_ID, WHO).to_vec(), + vec![ + IdAmount { id: DummyHoldReason::Governance, amount: 3 }, + IdAmount { id: DummyHoldReason::Staking, amount: 2 } + ] + ); + assert_eq!(HeldBalances::::get(ASSET_ID, WHO), Some(5)); + // Adding more than max holds fails + assert_noop!( + AssetsHolder::set_balance_on_hold(ASSET_ID, &DummyHoldReason::Other, &WHO, 1), + Error::::TooManyHolds + ); + // Decreasing held balance works + assert_ok!(AssetsHolder::set_balance_on_hold( + ASSET_ID, + &DummyHoldReason::Staking, + &WHO, + 1 + )); + assert_eq!( + Holds::::get(ASSET_ID, WHO).to_vec(), + vec![ + IdAmount { id: DummyHoldReason::Governance, amount: 3 }, + IdAmount { id: DummyHoldReason::Staking, amount: 1 } + ] + ); + assert_eq!(HeldBalances::::get(ASSET_ID, WHO), Some(4)); + // Decreasing until removal of held balance works + assert_ok!(AssetsHolder::set_balance_on_hold( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO, + 0 + )); + assert_eq!( + Holds::::get(ASSET_ID, WHO).to_vec(), + vec![IdAmount { id: DummyHoldReason::Staking, amount: 1 }] + ); + assert_eq!(HeldBalances::::get(ASSET_ID, WHO), Some(1)); + // Clearing ol all holds works + assert_ok!(AssetsHolder::set_balance_on_hold( + ASSET_ID, + &DummyHoldReason::Staking, + &WHO, + 0 + )); + assert_eq!(Holds::::get(ASSET_ID, WHO).to_vec(), vec![]); + assert_eq!(HeldBalances::::get(ASSET_ID, WHO), None); + }); + } +} + +mod impl_hold_mutate { + use super::*; + use frame_support::traits::tokens::{Fortitude, Precision, Preservation}; + use sp_runtime::TokenError; + + #[test] + fn hold_works() { + super::new_test_ext(|| { + // Holding some `amount` would decrease the asset account balance and change the + // reducible balance, while total issuance is preserved. + assert_ok!(AssetsHolder::hold(ASSET_ID, &DummyHoldReason::Governance, &WHO, 10)); + assert_eq!(Assets::balance(ASSET_ID, &WHO), 90); + // Reducible balance is tested once to ensure token balance model is compliant. + assert_eq!( + Assets::reducible_balance( + ASSET_ID, + &WHO, + Preservation::Expendable, + Fortitude::Force + ), + 89 + ); + assert_eq!( + AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Governance, &WHO), + 10 + ); + assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 10); + assert_eq!(Assets::total_issuance(ASSET_ID), 100); + + // Increasing the amount held for the same reason has the same effect as described above + // in `set_balance_on_hold_works`, while total issuance is preserved. + // Consideration: holding for an amount `x` will increase the already held amount by + // `x`. + assert_ok!(AssetsHolder::hold(ASSET_ID, &DummyHoldReason::Governance, &WHO, 20)); + assert_eq!(Assets::balance(ASSET_ID, &WHO), 70); + assert_eq!( + AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Governance, &WHO), + 30 + ); + assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 30); + assert_eq!(Assets::total_issuance(ASSET_ID), 100); + + // Holding some amount for a different reason has the same effect as described above in + // `set_balance_on_hold_works`, while total issuance is preserved. + assert_ok!(AssetsHolder::hold(ASSET_ID, &DummyHoldReason::Staking, &WHO, 20)); + assert_eq!(Assets::balance(ASSET_ID, &WHO), 50); + assert_eq!( + AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Staking, &WHO), + 20 + ); + assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 50); + assert_eq!(Assets::total_issuance(ASSET_ID), 100); + }); + } + + fn new_test_ext() -> sp_io::TestExternalities { + super::new_test_ext(|| { + assert_ok!(AssetsHolder::hold(ASSET_ID, &DummyHoldReason::Governance, &WHO, 30)); + assert_ok!(AssetsHolder::hold(ASSET_ID, &DummyHoldReason::Staking, &WHO, 20)); + }) + } + + #[test] + fn release_works() { + // Releasing up to some amount will increase the balance by the released + // amount, while preserving total issuance. + new_test_ext().execute_with(|| { + assert_ok!(AssetsHolder::release( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO, + 20, + Precision::Exact, + )); + assert_eq!( + AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Governance, &WHO), + 10 + ); + assert_eq!(Assets::balance(ASSET_ID, WHO), 70); + }); + + // Releasing over the max held amount with `BestEffort` will increase the + // balance by the previously held amount, while preserving total issuance. + new_test_ext().execute_with(|| { + assert_ok!(AssetsHolder::release( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO, + 31, + Precision::BestEffort, + )); + assert_eq!( + AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Governance, &WHO), + 0 + ); + assert_eq!(Assets::balance(ASSET_ID, WHO), 80); + }); + + // Releasing over the max held amount with `Exact` will fail. + new_test_ext().execute_with(|| { + assert_noop!( + AssetsHolder::release( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO, + 31, + Precision::Exact, + ), + TokenError::FundsUnavailable + ); + }); + } + + #[test] + fn burn_held_works() { + // Burning works, reducing total issuance. + new_test_ext().execute_with(|| { + assert_ok!(AssetsHolder::burn_held( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO, + 1, + Precision::BestEffort, + Fortitude::Polite + )); + assert_eq!(Assets::total_issuance(ASSET_ID), 99); + }); + + // Burning by an amount up to the held balance with `Exact` works, reducing held balance up + // to the given amount. + new_test_ext().execute_with(|| { + assert_ok!(AssetsHolder::burn_held( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO, + 10, + Precision::Exact, + Fortitude::Polite + )); + assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 40); + assert_eq!(Assets::balance(ASSET_ID, WHO), 50); + }); + + // Burning by an amount over the held balance with `BestEffort` works, reducing held balance + // up to the given amount. + new_test_ext().execute_with(|| { + assert_ok!(AssetsHolder::burn_held( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO, + 31, + Precision::BestEffort, + Fortitude::Polite + )); + assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 20); + assert_eq!(Assets::balance(ASSET_ID, WHO), 50); + }); + + // Burning by an amount over the held balance with `Exact` fails. + new_test_ext().execute_with(|| { + assert_noop!( + AssetsHolder::burn_held( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO, + 31, + Precision::Exact, + Fortitude::Polite + ), + TokenError::FundsUnavailable + ); + }); + } + + #[test] + fn burn_all_held_works() { + new_test_ext().execute_with(|| { + // Burning all held works as burning giving the held balance + // as amount with `BestEffort` + assert_ok!(AssetsHolder::burn_all_held( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO, + Precision::BestEffort, + Fortitude::Polite, + )); + assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 20); + assert_eq!(Assets::balance(ASSET_ID, WHO), 50); + }); + } + + #[test] + fn done_held_works() { + new_test_ext().execute_with(|| { + System::assert_has_event( + Event::::Held { + who: WHO, + asset_id: ASSET_ID, + reason: DummyHoldReason::Governance, + amount: 30, + } + .into(), + ); + }); + } + + #[test] + fn done_release_works() { + new_test_ext().execute_with(|| { + assert_ok!(AssetsHolder::release( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO, + 31, + Precision::BestEffort + )); + System::assert_has_event( + Event::::Released { + who: WHO, + asset_id: ASSET_ID, + reason: DummyHoldReason::Governance, + amount: 30, + } + .into(), + ); + }); + } + + #[test] + fn done_burn_held_works() { + new_test_ext().execute_with(|| { + assert_ok!(AssetsHolder::burn_all_held( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO, + Precision::BestEffort, + Fortitude::Polite, + )); + System::assert_has_event( + Event::::Slashed { + who: WHO, + asset_id: ASSET_ID, + reason: DummyHoldReason::Governance, + amount: 30, + } + .into(), + ); + }); + } +} From b93337ec1858421e6dec26ff71270bab9514f7c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 31 Jul 2024 22:44:49 -0400 Subject: [PATCH 10/59] fix: fmt / cargo fmt --- Cargo.lock | 3 +- Cargo.toml | 3 +- .../assets/asset-hub-rococo/src/lib.rs | 3 +- .../assets/asset-hub-westend/src/lib.rs | 3 +- substrate/frame/assets-holder/Cargo.toml | 80 +++++++++---------- substrate/frame/assets-holder/src/tests.rs | 1 - substrate/frame/assets/Cargo.toml | 25 +++--- 7 files changed, 56 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 06d8a661c26c..c7a72f4ba82c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9846,7 +9846,6 @@ dependencies = [ name = "pallet-assets" version = "29.1.0" dependencies = [ - "env_logger 0.11.3", "frame-benchmarking", "frame-support", "frame-system", @@ -9882,7 +9881,6 @@ dependencies = [ name = "pallet-assets-holder" version = "0.1.0" dependencies = [ - "env_logger 0.11.3", "frame-benchmarking", "frame-support", "frame-system", @@ -14260,6 +14258,7 @@ dependencies = [ "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", + "pallet-assets-holder", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", diff --git a/Cargo.toml b/Cargo.toml index fbead04d7385..c566967c2ece 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -311,8 +311,8 @@ members = [ "substrate/frame/asset-conversion/ops", "substrate/frame/asset-rate", "substrate/frame/assets", - "substrate/frame/assets-holder", "substrate/frame/assets-freezer", + "substrate/frame/assets-holder", "substrate/frame/atomic-swap", "substrate/frame/aura", "substrate/frame/authority-discovery", @@ -874,6 +874,7 @@ pallet-asset-rate = { path = "substrate/frame/asset-rate", default-features = fa pallet-asset-tx-payment = { path = "substrate/frame/transaction-payment/asset-tx-payment", default-features = false } pallet-assets = { path = "substrate/frame/assets", default-features = false } pallet-assets-freezer = { path = "substrate/frame/assets-freezer", default-features = false } +pallet-assets-holder = { path = "substrate/frame/assets-holder", default-features = false } pallet-atomic-swap = { default-features = false, path = "substrate/frame/atomic-swap" } pallet-aura = { path = "substrate/frame/aura", default-features = false } pallet-authority-discovery = { path = "substrate/frame/authority-discovery", default-features = false } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index c0427da22589..c9fee4626fac 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -545,8 +545,7 @@ impl InstanceFilter for ProxyType { RuntimeCall::Utility { .. } | RuntimeCall::Multisig { .. } | RuntimeCall::NftFractionalization { .. } | - RuntimeCall::Nfts { .. } | - RuntimeCall::Uniques { .. } + RuntimeCall::Nfts { .. } | RuntimeCall::Uniques { .. } ) }, ProxyType::AssetOwner => matches!( diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 6cdc038b1df9..f3d3b29a23a8 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -539,8 +539,7 @@ impl InstanceFilter for ProxyType { RuntimeCall::Utility { .. } | RuntimeCall::Multisig { .. } | RuntimeCall::NftFractionalization { .. } | - RuntimeCall::Nfts { .. } | - RuntimeCall::Uniques { .. } + RuntimeCall::Nfts { .. } | RuntimeCall::Uniques { .. } ) }, ProxyType::AssetOwner => matches!( diff --git a/substrate/frame/assets-holder/Cargo.toml b/substrate/frame/assets-holder/Cargo.toml index a2501725d3c9..7e3aed6e17e9 100644 --- a/substrate/frame/assets-holder/Cargo.toml +++ b/substrate/frame/assets-holder/Cargo.toml @@ -1,12 +1,12 @@ [package] +name = "pallet-assets-holder" +version = "0.1.0" authors.workspace = true -description = "Provides holding features to `pallet-assets`" edition.workspace = true -homepage = "https://substrate.io" license = "MIT-0" -name = "pallet-assets-holder" +homepage.workspace = true repository.workspace = true -version = "0.1.0" +description = "Provides holding features to `pallet-assets`" [lints] workspace = true @@ -15,49 +15,47 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = {workspace = true} - -codec = {workspace = true} -frame-benchmarking = {optional = true, workspace = true} -frame-support = {workspace = true} -frame-system = {workspace = true} -pallet-assets = {workspace = true} -scale-info = {features = ["derive"], workspace = true} -sp-runtime = {workspace = true} +codec = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +frame-benchmarking = { optional = true, workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-assets = { workspace = true } +sp-runtime = { workspace = true } [dev-dependencies] -env_logger = {workspace = true} -pallet-balances = {workspace = true} -sp-core = {workspace = true} -sp-io = {workspace = true} +sp-io = { workspace = true } +sp-core = { workspace = true } +pallet-balances = { workspace = true } [features] default = ["std"] -runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "pallet-assets/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", -] std = [ - "codec/std", - "frame-benchmarking?/std", - "frame-support/std", - "frame-system/std", - "log/std", - "pallet-assets/std", - "pallet-balances/std", - "scale-info/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-assets/std", + "pallet-balances/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "pallet-assets/try-runtime", - "pallet-balances/try-runtime", - "sp-runtime/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-assets/try-runtime", + "pallet-balances/try-runtime", + "sp-runtime/try-runtime", ] diff --git a/substrate/frame/assets-holder/src/tests.rs b/substrate/frame/assets-holder/src/tests.rs index 1f8addd4650a..c4836647bb29 100644 --- a/substrate/frame/assets-holder/src/tests.rs +++ b/substrate/frame/assets-holder/src/tests.rs @@ -41,7 +41,6 @@ mod impl_held_balance { #[test] fn held_balance_works() { - let _ = env_logger::init(); new_test_ext(|| { assert_eq!(AssetsHolder::held_balance(ASSET_ID, &WHO), None); test_hold(DummyHoldReason::Governance, 1); diff --git a/substrate/frame/assets/Cargo.toml b/substrate/frame/assets/Cargo.toml index b93c224927b0..fcb151298f09 100644 --- a/substrate/frame/assets/Cargo.toml +++ b/substrate/frame/assets/Cargo.toml @@ -1,13 +1,13 @@ [package] +name = "pallet-assets" +version = "29.1.0" authors.workspace = true edition.workspace = true +license = "Apache-2.0" homepage.workspace = true repository.workspace = true description = "FRAME asset management pallet" -license = "Apache-2.0" -name = "pallet-assets" readme = "README.md" -version = "29.1.0" [lints] workspace = true @@ -30,7 +30,6 @@ frame-benchmarking = { optional = true, workspace = true } sp-core = { workspace = true } [dev-dependencies] -env_logger = { workspace = true } sp-std = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } @@ -50,15 +49,15 @@ std = [ "sp-runtime/std", ] runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "pallet-balances/try-runtime", - "sp-runtime/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "sp-runtime/try-runtime", ] From 4e0ced776b78551a720a61f5a1e5553c009fc3a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 31 Jul 2024 22:44:53 -0400 Subject: [PATCH 11/59] chore: umbrella --- umbrella/Cargo.toml | 9 +++++++++ umbrella/src/lib.rs | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 8d85e26d8fe7..0bde9e518dbf 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -69,6 +69,7 @@ std = [ "pallet-asset-rate?/std", "pallet-asset-tx-payment?/std", "pallet-assets-freezer?/std", + "pallet-assets-holder?/std", "pallet-assets?/std", "pallet-atomic-swap?/std", "pallet-aura?/std", @@ -265,6 +266,7 @@ runtime-benchmarks = [ "pallet-asset-rate?/runtime-benchmarks", "pallet-asset-tx-payment?/runtime-benchmarks", "pallet-assets-freezer?/runtime-benchmarks", + "pallet-assets-holder?/runtime-benchmarks", "pallet-assets?/runtime-benchmarks", "pallet-babe?/runtime-benchmarks", "pallet-bags-list?/runtime-benchmarks", @@ -389,6 +391,7 @@ try-runtime = [ "pallet-asset-rate?/try-runtime", "pallet-asset-tx-payment?/try-runtime", "pallet-assets-freezer?/try-runtime", + "pallet-assets-holder?/try-runtime", "pallet-assets?/try-runtime", "pallet-atomic-swap?/try-runtime", "pallet-aura?/try-runtime", @@ -603,6 +606,7 @@ runtime = [ "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", + "pallet-assets-holder", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", @@ -1103,6 +1107,11 @@ path = "../substrate/frame/assets-freezer" default-features = false optional = true +[dependencies.pallet-assets-holder] +path = "../substrate/frame/assets-holder" +default-features = false +optional = true + [dependencies.pallet-atomic-swap] path = "../substrate/frame/atomic-swap" default-features = false diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index 58a5691961d9..473c6114a5ac 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -364,6 +364,10 @@ pub use pallet_assets; #[cfg(feature = "pallet-assets-freezer")] pub use pallet_assets_freezer; +/// Provides holding features to `pallet-assets`. +#[cfg(feature = "pallet-assets-holder")] +pub use pallet_assets_holder; + /// FRAME atomic swap pallet. #[cfg(feature = "pallet-atomic-swap")] pub use pallet_atomic_swap; From c5b103a2601923a42c34d76a07e9b78e4772efd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 31 Jul 2024 23:10:30 -0400 Subject: [PATCH 12/59] chore: pr doc --- prdoc/pr_4530.prdoc | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 prdoc/pr_4530.prdoc diff --git a/prdoc/pr_4530.prdoc b/prdoc/pr_4530.prdoc new file mode 100644 index 000000000000..64f59d69ad4b --- /dev/null +++ b/prdoc/pr_4530.prdoc @@ -0,0 +1,37 @@ +title: "Implement `pallet-assets-holder`" + +doc: + - audience: Runtime Dev + description: | + This change creates the `pallet-assets-holder` pallet, as well as changes `pallet-assets` + to support querying held balances via a new trait: `HeldBalance`. + + It also adjusts the balance model implementation for fungible + sets (see [sdk docs](https://paritytech.github.io/polkadot-sdk/master/frame_support/traits/tokens/fungible/index.html#visualising-balance-components-together-)). + + The `pallet-assets-holder` implements `hold` traits for `pallet-assets`, by extending this + pallet and implementing the `HeldBalance` trait so the held balance can be queried by + `pallet-assets` to calculate the reducible (a.k.a. spendable) balance. + + These changes imply adding a configuration type in `pallet-assets` for `Holder` + + ## Default implementation of `Holder` + + Use `()` as the default value, when no holding capabilities are wanted in the runtime + implementation. + + ## Enable `pallet-assets-holder` + + Define an instance of `pallet-assets-holder` (we'll call it `AssetsHolder`) and use + `AssetsHolder` as the value for `Holder`, when intend to use holding capabilities are + wanted in the runtime implementation. + +crates: + - name: pallet-assets + bump: major + - name: pallet-assets-holder + bump: major + - name: asset-hub-rococo-runtime + bump: minor + - name: asset-hub-westend-runtime + bump: minor From f36b5402ab7a9a7ea6eeeb2eaafdc7f1360364fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 7 Aug 2024 21:00:06 -0500 Subject: [PATCH 13/59] fix(staging-xcm-builder): missing references to `Holder` Note: these mocks were introduced after the latest merge from `master`. --- .../xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs index 4d9809e84f88..91aa180fe1bd 100644 --- a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs +++ b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs @@ -82,6 +82,7 @@ impl pallet_assets::Config for Runtime { type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = frame_system::EnsureRoot; type Freezer = (); + type Holder = (); type CallbackHandle = (); } @@ -97,6 +98,7 @@ impl pallet_assets::Config for Runtime { type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = frame_system::EnsureRoot; type Freezer = (); + type Holder = (); type CallbackHandle = (); } From 5829558085d967a3ce629278e1b1819cebb1238e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 7 Aug 2024 21:00:24 -0500 Subject: [PATCH 14/59] change(pallet-assets): set `Holder` and `Freezer` to have default values, as per @gui1117's suggestion --- substrate/frame/assets/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/substrate/frame/assets/src/lib.rs b/substrate/frame/assets/src/lib.rs index a77f0362ff76..aed4ed460bd4 100644 --- a/substrate/frame/assets/src/lib.rs +++ b/substrate/frame/assets/src/lib.rs @@ -295,6 +295,8 @@ pub mod pallet { type MetadataDepositPerByte = ConstU64<1>; type ApprovalDeposit = ConstU64<1>; type StringLimit = ConstU32<50>; + type Freezer = (); + type Holder = (); type Extra = (); type CallbackHandle = (); type WeightInfo = (); @@ -390,12 +392,10 @@ pub mod pallet { /// A hook to allow a per-asset, per-account minimum balance to be enforced. This must be /// respected in all permissionless operations. - #[pallet::no_default] type Freezer: FrozenBalance; - /// A hook to allow a per-asset, per-account minimum balance to be enforced. This must be - /// respected in all permissionless operations. - #[pallet::no_default] + /// A hook to inspect a per-asset, per-account balance that is held. This goes in + /// accordance with balance model. type Holder: HeldBalance; /// Additional data to be stored with an account's asset balance. From 17ce1d92ba8c793e78d3dda65a880ff28657d39b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 7 Aug 2024 21:00:39 -0500 Subject: [PATCH 15/59] change(pallet-assets-holder): apply @gui1117's suggestion of tightly coupling `pallet-assets-holder` with `pallet-assets` --- substrate/frame/assets-holder/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/substrate/frame/assets-holder/src/lib.rs b/substrate/frame/assets-holder/src/lib.rs index b50e6035f596..c09b18b3a923 100644 --- a/substrate/frame/assets-holder/src/lib.rs +++ b/substrate/frame/assets-holder/src/lib.rs @@ -65,7 +65,9 @@ pub mod pallet { use super::*; #[pallet::config(with_default)] - pub trait Config: frame_system::Config + pallet_assets::Config { + pub trait Config: + frame_system::Config + pallet_assets::Config> + { /// The overarching freeze reason. #[pallet::no_default_bounds] type RuntimeHoldReason: Parameter + Member + MaxEncodedLen + Copy + VariantCount; From f2a9e96297f61580f22f28be4c990a99f51f0776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 7 Aug 2024 22:34:03 -0500 Subject: [PATCH 16/59] change(pallet-assets): account for `held_balance` when calculating `total_balance`. Note: `transfer_all_works_3` changes, due to the change in the balance model --- substrate/frame/assets/src/impl_fungibles.rs | 3 ++- substrate/frame/assets/src/tests.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/substrate/frame/assets/src/impl_fungibles.rs b/substrate/frame/assets/src/impl_fungibles.rs index 578fa08c4e63..6ac4ef25e5c5 100644 --- a/substrate/frame/assets/src/impl_fungibles.rs +++ b/substrate/frame/assets/src/impl_fungibles.rs @@ -47,7 +47,8 @@ impl, I: 'static> fungibles::Inspect<::AccountId } fn total_balance(asset: Self::AssetId, who: &::AccountId) -> Self::Balance { - Pallet::::balance(asset, who) + Pallet::::balance(asset.clone(), who) + .saturating_add(T::Holder::held_balance(asset, who).unwrap_or_default()) } fn reducible_balance( diff --git a/substrate/frame/assets/src/tests.rs b/substrate/frame/assets/src/tests.rs index c71cb261f19c..502f41f33bb8 100644 --- a/substrate/frame/assets/src/tests.rs +++ b/substrate/frame/assets/src/tests.rs @@ -837,8 +837,8 @@ fn transfer_all_works_3() { assert_ok!(Assets::mint(RuntimeOrigin::signed(0), 0, 2, 100)); // transfer all and allow death w/ frozen assert_ok!(Assets::transfer_all(Some(1).into(), 0, 2, false)); - assert_eq!(Assets::balance(0, &1), 110); - assert_eq!(Assets::balance(0, &2), 200); + assert_eq!(Assets::balance(0, &1), 100); + assert_eq!(Assets::balance(0, &2), 210); }); } From fc41634ea649013c772cbd96eee2123bc6d90f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 7 Aug 2024 22:47:06 -0500 Subject: [PATCH 17/59] change(pallet-assets-holder): test preservation of `total_balance`. --- substrate/frame/assets-holder/src/tests.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/substrate/frame/assets-holder/src/tests.rs b/substrate/frame/assets-holder/src/tests.rs index c4836647bb29..12e7c4c92bf3 100644 --- a/substrate/frame/assets-holder/src/tests.rs +++ b/substrate/frame/assets-holder/src/tests.rs @@ -254,6 +254,9 @@ mod impl_hold_mutate { 10 ); assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 10); + // Holding preserves `total_balance` + assert_eq!(Assets::total_balance(ASSET_ID, &WHO), 100); + // Holding preserves `total_issuance` assert_eq!(Assets::total_issuance(ASSET_ID), 100); // Increasing the amount held for the same reason has the same effect as described above @@ -342,7 +345,7 @@ mod impl_hold_mutate { #[test] fn burn_held_works() { - // Burning works, reducing total issuance. + // Burning works, reducing total issuance and `total_balance`. new_test_ext().execute_with(|| { assert_ok!(AssetsHolder::burn_held( ASSET_ID, @@ -352,6 +355,7 @@ mod impl_hold_mutate { Precision::BestEffort, Fortitude::Polite )); + assert_eq!(Assets::total_balance(ASSET_ID, &WHO), 99); assert_eq!(Assets::total_issuance(ASSET_ID), 99); }); From 9e9edfbf7a07129eaf80b9a97ed7543d08d52333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Thu, 8 Aug 2024 08:28:25 -0500 Subject: [PATCH 18/59] change(pallet-assets-holder): resolve `VARIANT_COUNT` to match number of enum variants in mock --- substrate/frame/assets-holder/src/mock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/assets-holder/src/mock.rs b/substrate/frame/assets-holder/src/mock.rs index be9df18518b4..2b4978be6681 100644 --- a/substrate/frame/assets-holder/src/mock.rs +++ b/substrate/frame/assets-holder/src/mock.rs @@ -124,7 +124,7 @@ pub enum DummyHoldReason { impl VariantCount for DummyHoldReason { // Intentionally set below the actual count of variants, to allow testing for `can_freeze` - const VARIANT_COUNT: u32 = 2; + const VARIANT_COUNT: u32 = 3; } impl Config for Test { From e7155eb5279b26606a0f95166252bffabed43b50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Fri, 9 Aug 2024 14:27:44 -0500 Subject: [PATCH 19/59] change(pallet-assets-holder): remove assertion in test that fails after changing variant count for `DummyHoldReason`. --- substrate/frame/assets-holder/src/tests.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/substrate/frame/assets-holder/src/tests.rs b/substrate/frame/assets-holder/src/tests.rs index 12e7c4c92bf3..a10cc89ed83c 100644 --- a/substrate/frame/assets-holder/src/tests.rs +++ b/substrate/frame/assets-holder/src/tests.rs @@ -182,11 +182,15 @@ mod impl_hold_unbalanced { ] ); assert_eq!(HeldBalances::::get(ASSET_ID, WHO), Some(5)); - // Adding more than max holds fails - assert_noop!( - AssetsHolder::set_balance_on_hold(ASSET_ID, &DummyHoldReason::Other, &WHO, 1), - Error::::TooManyHolds - ); + + // Note: Assertion skipped to meet @gavofyork's suggestion of matching the number of + // variant count with the number of enum's variants. + // // Adding more than max holds fails + // assert_noop!( + // AssetsHolder::set_balance_on_hold(ASSET_ID, &DummyHoldReason::Other, &WHO, 1), + // Error::::TooManyHolds + // ); + // Decreasing held balance works assert_ok!(AssetsHolder::set_balance_on_hold( ASSET_ID, From 4268b76dbcf19e89aeb942874526fcfd8b29cd52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Tue, 27 Aug 2024 19:58:55 -0500 Subject: [PATCH 20/59] change(pallet-assets-holder): implement changes suggested by @gui1117 --- substrate/frame/assets-holder/src/impls.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/substrate/frame/assets-holder/src/impls.rs b/substrate/frame/assets-holder/src/impls.rs index 6266b41c22aa..55258503151c 100644 --- a/substrate/frame/assets-holder/src/impls.rs +++ b/substrate/frame/assets-holder/src/impls.rs @@ -172,15 +172,20 @@ impl, I: 'static> UnbalancedHold for Pallet { amount: Self::Balance, ) -> DispatchResult { let mut holds = Holds::::get(asset.clone(), who); - let mut increase = true; - let mut delta = amount; + let increase; + let delta; - if let Some(item) = holds.iter_mut().find(|x| &x.id == reason) { + if let Some(pos) = holds.iter().position(|x| &x.id == reason) { + let item = &mut holds[pos]; delta = item.amount.max(amount) - item.amount.min(amount); increase = amount > item.amount; item.amount = amount; - holds.retain(|x| !x.amount.is_zero()); + if item.amount.is_zero() { + holds.swap_remove(pos); + } } else { + increase = true; + delta = amount; if !amount.is_zero() { holds .try_push(IdAmount { id: *reason, amount }) @@ -202,7 +207,11 @@ impl, I: 'static> UnbalancedHold for Pallet { HeldBalances::::insert(asset.clone(), who, held_amount); } - Holds::::insert(asset, who, holds); + if !holds.is_empty() { + Holds::::insert(asset, who, holds); + } else { + Holds::::remove(asset, who); + } Ok(()) } From 818e59048049ce51c6c3dca860653e533582769a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Tue, 27 Aug 2024 21:34:56 -0500 Subject: [PATCH 21/59] change(pallet-assets-holder): apply change from HeldBalance to BalanceOnHold, suggested by @gavofyork and @gui1117 --- substrate/frame/assets-holder/src/impls.rs | 8 +- substrate/frame/assets-holder/src/tests.rs | 102 +++++++++++++++---- substrate/frame/assets/src/functions.rs | 4 +- substrate/frame/assets/src/impl_fungibles.rs | 2 +- substrate/frame/assets/src/lib.rs | 2 +- substrate/frame/assets/src/mock.rs | 4 +- substrate/frame/assets/src/tests.rs | 2 +- substrate/frame/assets/src/types.rs | 8 +- 8 files changed, 98 insertions(+), 34 deletions(-) diff --git a/substrate/frame/assets-holder/src/impls.rs b/substrate/frame/assets-holder/src/impls.rs index 55258503151c..a5b371cc9c10 100644 --- a/substrate/frame/assets-holder/src/impls.rs +++ b/substrate/frame/assets-holder/src/impls.rs @@ -23,7 +23,7 @@ use frame_support::traits::{ DepositConsequence, Fortitude, Precision, Preservation, Provenance, WithdrawConsequence, }, }; -use pallet_assets::HeldBalance; +use pallet_assets::BalanceOnHold; use sp_runtime::{ traits::{CheckedAdd, CheckedSub, Zero}, ArithmeticError, @@ -32,8 +32,10 @@ use sp_runtime::{ // Implements [`HeldBalance`] from [`pallet-assets`], so it can understand whether there's a held // amount for an asset account, and is able to signal to this pallet when to clear the state of an // account. -impl, I: 'static> HeldBalance for Pallet { - fn held_balance(asset: T::AssetId, who: &T::AccountId) -> Option { +impl, I: 'static> BalanceOnHold + for Pallet +{ + fn balance_on_hold(asset: T::AssetId, who: &T::AccountId) -> Option { HeldBalances::::get(asset, who) } diff --git a/substrate/frame/assets-holder/src/tests.rs b/substrate/frame/assets-holder/src/tests.rs index a10cc89ed83c..ddd51b97059e 100644 --- a/substrate/frame/assets-holder/src/tests.rs +++ b/substrate/frame/assets-holder/src/tests.rs @@ -23,7 +23,7 @@ use frame_support::{ assert_noop, assert_ok, traits::tokens::fungibles::{Inspect, InspectHold, MutateHold, UnbalancedHold}, }; -use pallet_assets::HeldBalance; +use pallet_assets::BalanceOnHold; const WHO: AccountId = 1; const ASSET_ID: AssetId = 1; @@ -36,25 +36,43 @@ fn test_release(id: DummyHoldReason) { assert_ok!(AssetsHolder::set_balance_on_hold(ASSET_ID, &id, &WHO, 0)); } -mod impl_held_balance { +mod impl_balance_on_hold { use super::*; #[test] - fn held_balance_works() { + fn balance_on_hold_works() { new_test_ext(|| { - assert_eq!(AssetsHolder::held_balance(ASSET_ID, &WHO), None); + assert_eq!( + >::balance_on_hold(ASSET_ID, &WHO), + None + ); test_hold(DummyHoldReason::Governance, 1); - assert_eq!(AssetsHolder::held_balance(ASSET_ID, &WHO), Some(1u64)); + assert_eq!( + >::balance_on_hold(ASSET_ID, &WHO), + Some(1u64) + ); test_hold(DummyHoldReason::Staking, 3); - assert_eq!(AssetsHolder::held_balance(ASSET_ID, &WHO), Some(4u64)); + assert_eq!( + >::balance_on_hold(ASSET_ID, &WHO), + Some(4u64) + ); test_hold(DummyHoldReason::Governance, 2); - assert_eq!(AssetsHolder::held_balance(ASSET_ID, &WHO), Some(5u64)); + assert_eq!( + >::balance_on_hold(ASSET_ID, &WHO), + Some(5u64) + ); // also test releasing works to reduce a balance, and finally releasing everything // resets to None test_release(DummyHoldReason::Governance); - assert_eq!(AssetsHolder::held_balance(ASSET_ID, &WHO), Some(3u64)); + assert_eq!( + >::balance_on_hold(ASSET_ID, &WHO), + Some(3u64) + ); test_release(DummyHoldReason::Staking); - assert_eq!(AssetsHolder::held_balance(ASSET_ID, &WHO), None); + assert_eq!( + >::balance_on_hold(ASSET_ID, &WHO), + None + ); }); } @@ -95,34 +113,58 @@ mod impl_hold_inspect { fn balance_on_hold_works() { new_test_ext(|| { assert_eq!( - AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Governance, &WHO), + >::balance_on_hold( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO + ), 0u64 ); test_hold(DummyHoldReason::Governance, 1); assert_eq!( - AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Governance, &WHO), + >::balance_on_hold( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO + ), 1u64 ); test_hold(DummyHoldReason::Staking, 3); assert_eq!( - AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Staking, &WHO), + >::balance_on_hold( + ASSET_ID, + &DummyHoldReason::Staking, + &WHO + ), 3u64 ); test_hold(DummyHoldReason::Staking, 2); assert_eq!( - AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Staking, &WHO), + >::balance_on_hold( + ASSET_ID, + &DummyHoldReason::Staking, + &WHO + ), 2u64 ); // also test release to reduce a balance, and finally releasing everything resets to // 0 test_release(DummyHoldReason::Governance); assert_eq!( - AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Governance, &WHO), + >::balance_on_hold( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO + ), 0u64 ); test_release(DummyHoldReason::Staking); assert_eq!( - AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Staking, &WHO), + >::balance_on_hold( + ASSET_ID, + &DummyHoldReason::Staking, + &WHO + ), 0u64 ); }); @@ -254,7 +296,11 @@ mod impl_hold_mutate { 89 ); assert_eq!( - AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Governance, &WHO), + >::balance_on_hold( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO + ), 10 ); assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 10); @@ -270,7 +316,11 @@ mod impl_hold_mutate { assert_ok!(AssetsHolder::hold(ASSET_ID, &DummyHoldReason::Governance, &WHO, 20)); assert_eq!(Assets::balance(ASSET_ID, &WHO), 70); assert_eq!( - AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Governance, &WHO), + >::balance_on_hold( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO + ), 30 ); assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 30); @@ -281,7 +331,11 @@ mod impl_hold_mutate { assert_ok!(AssetsHolder::hold(ASSET_ID, &DummyHoldReason::Staking, &WHO, 20)); assert_eq!(Assets::balance(ASSET_ID, &WHO), 50); assert_eq!( - AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Staking, &WHO), + >::balance_on_hold( + ASSET_ID, + &DummyHoldReason::Staking, + &WHO + ), 20 ); assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 50); @@ -309,7 +363,11 @@ mod impl_hold_mutate { Precision::Exact, )); assert_eq!( - AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Governance, &WHO), + >::balance_on_hold( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO + ), 10 ); assert_eq!(Assets::balance(ASSET_ID, WHO), 70); @@ -326,7 +384,11 @@ mod impl_hold_mutate { Precision::BestEffort, )); assert_eq!( - AssetsHolder::balance_on_hold(ASSET_ID, &DummyHoldReason::Governance, &WHO), + >::balance_on_hold( + ASSET_ID, + &DummyHoldReason::Governance, + &WHO + ), 0 ); assert_eq!(Assets::balance(ASSET_ID, WHO), 80); diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index e0f7fd6bb079..ab82db86c36c 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -194,7 +194,7 @@ impl, I: 'static> Pallet { } if let Some(rest) = account.balance.checked_sub(&amount) { match ( - T::Holder::held_balance(id.clone(), who), + T::Holder::balance_on_hold(id.clone(), who), T::Freezer::frozen_balance(id.clone(), who), ) { (None, None) => @@ -238,7 +238,7 @@ impl, I: 'static> Pallet { ensure!(!account.status.is_frozen(), Error::::Frozen); let untouchable = match ( - T::Holder::held_balance(id.clone(), who), + T::Holder::balance_on_hold(id.clone(), who), T::Freezer::frozen_balance(id.clone(), who), keep_alive, ) { diff --git a/substrate/frame/assets/src/impl_fungibles.rs b/substrate/frame/assets/src/impl_fungibles.rs index 6ac4ef25e5c5..6ab7e941ea1a 100644 --- a/substrate/frame/assets/src/impl_fungibles.rs +++ b/substrate/frame/assets/src/impl_fungibles.rs @@ -48,7 +48,7 @@ impl, I: 'static> fungibles::Inspect<::AccountId fn total_balance(asset: Self::AssetId, who: &::AccountId) -> Self::Balance { Pallet::::balance(asset.clone(), who) - .saturating_add(T::Holder::held_balance(asset, who).unwrap_or_default()) + .saturating_add(T::Holder::balance_on_hold(asset, who).unwrap_or_default()) } fn reducible_balance( diff --git a/substrate/frame/assets/src/lib.rs b/substrate/frame/assets/src/lib.rs index aed4ed460bd4..297ba1f55bc1 100644 --- a/substrate/frame/assets/src/lib.rs +++ b/substrate/frame/assets/src/lib.rs @@ -396,7 +396,7 @@ pub mod pallet { /// A hook to inspect a per-asset, per-account balance that is held. This goes in /// accordance with balance model. - type Holder: HeldBalance; + type Holder: BalanceOnHold; /// Additional data to be stored with an account's asset balance. type Extra: Member + Parameter + Default + MaxEncodedLen; diff --git a/substrate/frame/assets/src/mock.rs b/substrate/frame/assets/src/mock.rs index e9a0306726d6..4a46b8edab01 100644 --- a/substrate/frame/assets/src/mock.rs +++ b/substrate/frame/assets/src/mock.rs @@ -120,8 +120,8 @@ parameter_types! { } pub struct TestHolder; -impl HeldBalance for TestHolder { - fn held_balance(asset: u32, who: &u64) -> Option { +impl BalanceOnHold for TestHolder { + fn balance_on_hold(asset: u32, who: &u64) -> Option { Held::get().get(&(asset, *who)).cloned() } diff --git a/substrate/frame/assets/src/tests.rs b/substrate/frame/assets/src/tests.rs index 502f41f33bb8..8444fb34d62b 100644 --- a/substrate/frame/assets/src/tests.rs +++ b/substrate/frame/assets/src/tests.rs @@ -1385,7 +1385,7 @@ fn freezing_and_holds_work() { // Hold 50 of it set_held_balance(0, 1, 50); assert_eq!(Assets::balance(0, 1), 50); - assert_eq!(TestHolder::held_balance(0, &1), Some(50)); + assert_eq!(TestHolder::balance_on_hold(0, &1), Some(50)); // Can freeze up to held + min_balance without affecting reducible set_frozen_balance(0, 1, 59); diff --git a/substrate/frame/assets/src/types.rs b/substrate/frame/assets/src/types.rs index d1f8718c0b5f..d560da5319a8 100644 --- a/substrate/frame/assets/src/types.rs +++ b/substrate/frame/assets/src/types.rs @@ -236,14 +236,14 @@ impl FrozenBalance for /// This balance is then summed up with the balance of the account, and the /// `minimum_balance` (and frozen balance, if any) of the asset to calculate /// the reducible balance. -pub trait HeldBalance { +pub trait BalanceOnHold { /// Return the held balance. /// /// If `Some`, it means some balance is suspended, and it can be infallibly /// slashed. /// /// If `None` is returned, then nothing special is enforced. - fn held_balance(asset: AssetId, who: &AccountId) -> Option; + fn balance_on_hold(asset: AssetId, who: &AccountId) -> Option; /// Called after an account has been removed. /// @@ -251,8 +251,8 @@ pub trait HeldBalance { fn died(asset: AssetId, who: &AccountId); } -impl HeldBalance for () { - fn held_balance(_: AssetId, _: &AccountId) -> Option { +impl BalanceOnHold for () { + fn balance_on_hold(_: AssetId, _: &AccountId) -> Option { None } fn died(_: AssetId, _: &AccountId) {} From 06db70187ebea344a1db2f8e263f94be68d0b993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Tue, 27 Aug 2024 21:51:36 -0500 Subject: [PATCH 22/59] minor(pallet-assets-holder): small adjustments - `impls`: Minor codebase improvements - `lib`: Rename `HeldBalances` to `BalancesOnHold`. --- substrate/frame/assets-holder/src/impls.rs | 29 +++++++++++----------- substrate/frame/assets-holder/src/lib.rs | 6 ++--- substrate/frame/assets-holder/src/tests.rs | 16 ++++++------ 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/substrate/frame/assets-holder/src/impls.rs b/substrate/frame/assets-holder/src/impls.rs index a5b371cc9c10..a53592e6683e 100644 --- a/substrate/frame/assets-holder/src/impls.rs +++ b/substrate/frame/assets-holder/src/impls.rs @@ -36,11 +36,11 @@ impl, I: 'static> BalanceOnHold { fn balance_on_hold(asset: T::AssetId, who: &T::AccountId) -> Option { - HeldBalances::::get(asset, who) + BalancesOnHold::::get(asset, who) } fn died(asset: T::AssetId, who: &T::AccountId) { - HeldBalances::::remove(asset.clone(), who); + BalancesOnHold::::remove(asset.clone(), who); Holds::::remove(asset, who); } } @@ -104,7 +104,7 @@ impl, I: 'static> InspectHold for Pallet { type Reason = T::RuntimeHoldReason; fn total_balance_on_hold(asset: Self::AssetId, who: &T::AccountId) -> Self::Balance { - HeldBalances::::get(asset, who).unwrap_or_else(Zero::zero) + BalancesOnHold::::get(asset, who).unwrap_or_else(Zero::zero) } fn balance_on_hold( @@ -174,28 +174,29 @@ impl, I: 'static> UnbalancedHold for Pallet { amount: Self::Balance, ) -> DispatchResult { let mut holds = Holds::::get(asset.clone(), who); - let increase; - let delta; - if let Some(pos) = holds.iter().position(|x| &x.id == reason) { + let (increase, delta) = if let Some(pos) = holds.iter().position(|x| &x.id == reason) { let item = &mut holds[pos]; - delta = item.amount.max(amount) - item.amount.min(amount); - increase = amount > item.amount; + let (increase, delta) = + (amount > item.amount, item.amount.max(amount) - item.amount.min(amount)); + item.amount = amount; if item.amount.is_zero() { holds.swap_remove(pos); } + + (increase, delta) } else { - increase = true; - delta = amount; if !amount.is_zero() { holds .try_push(IdAmount { id: *reason, amount }) .map_err(|_| Error::::TooManyHolds)?; } - } + (true, amount) + }; - let held_amount = HeldBalances::::get(asset.clone(), who).unwrap_or_else(Zero::zero); + let held_amount = + BalancesOnHold::::get(asset.clone(), who).unwrap_or_else(Zero::zero); let held_amount = if increase { held_amount.checked_add(&delta).ok_or(ArithmeticError::Overflow)? @@ -204,9 +205,9 @@ impl, I: 'static> UnbalancedHold for Pallet { }; if held_amount.is_zero() { - HeldBalances::::remove(asset.clone(), who); + BalancesOnHold::::remove(asset.clone(), who); } else { - HeldBalances::::insert(asset.clone(), who, held_amount); + BalancesOnHold::::insert(asset.clone(), who, held_amount); } if !holds.is_empty() { diff --git a/substrate/frame/assets-holder/src/lib.rs b/substrate/frame/assets-holder/src/lib.rs index c09b18b3a923..c4c6ddcb9897 100644 --- a/substrate/frame/assets-holder/src/lib.rs +++ b/substrate/frame/assets-holder/src/lib.rs @@ -130,7 +130,7 @@ pub mod pallet { /// A map that stores the current total held balance for every account on a given AssetId. #[pallet::storage] - pub(super) type HeldBalances, I: 'static = ()> = StorageDoubleMap< + pub(super) type BalancesOnHold, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::AssetId, @@ -153,14 +153,14 @@ impl, I: 'static> Pallet { fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { use sp_runtime::traits::{Saturating, Zero}; - for (asset, who, _) in HeldBalances::::iter() { + for (asset, who, _) in BalancesOnHold::::iter() { let held_amount: T::Balance = Holds::::get(asset.clone(), who.clone()) .iter() .map(|l| l.amount) .fold(Zero::zero(), |prev, amount| prev.saturating_add(amount)); frame_support::ensure!( - HeldBalances::::get(asset, who).unwrap_or_else(Zero::zero) == held_amount, + BalancesOnHold::::get(asset, who).unwrap_or_else(Zero::zero) == held_amount, "The `HeldAmount` is not equal to the sum of `Holds` for (`asset`, `who`)" ); } diff --git a/substrate/frame/assets-holder/src/tests.rs b/substrate/frame/assets-holder/src/tests.rs index ddd51b97059e..87d3b5f18d6c 100644 --- a/substrate/frame/assets-holder/src/tests.rs +++ b/substrate/frame/assets-holder/src/tests.rs @@ -81,7 +81,7 @@ mod impl_balance_on_hold { new_test_ext(|| { test_hold(DummyHoldReason::Governance, 1); AssetsHolder::died(ASSET_ID, &WHO); - assert!(HeldBalances::::get(ASSET_ID, WHO).is_none()); + assert!(BalancesOnHold::::get(ASSET_ID, WHO).is_none()); assert!(Holds::::get(ASSET_ID, WHO).is_empty()); }); } @@ -184,7 +184,7 @@ mod impl_hold_unbalanced { fn set_balance_on_hold_works() { new_test_ext(|| { assert_eq!(Holds::::get(ASSET_ID, WHO).to_vec(), vec![]); - assert_eq!(HeldBalances::::get(ASSET_ID, WHO), None); + assert_eq!(BalancesOnHold::::get(ASSET_ID, WHO), None); // Adding held balance works assert_ok!(AssetsHolder::set_balance_on_hold( ASSET_ID, @@ -196,7 +196,7 @@ mod impl_hold_unbalanced { Holds::::get(ASSET_ID, WHO).to_vec(), vec![IdAmount { id: DummyHoldReason::Governance, amount: 1 }] ); - assert_eq!(HeldBalances::::get(ASSET_ID, WHO), Some(1)); + assert_eq!(BalancesOnHold::::get(ASSET_ID, WHO), Some(1)); // Increasing hold works assert_ok!(AssetsHolder::set_balance_on_hold( ASSET_ID, @@ -208,7 +208,7 @@ mod impl_hold_unbalanced { Holds::::get(ASSET_ID, WHO).to_vec(), vec![IdAmount { id: DummyHoldReason::Governance, amount: 3 }] ); - assert_eq!(HeldBalances::::get(ASSET_ID, WHO), Some(3)); + assert_eq!(BalancesOnHold::::get(ASSET_ID, WHO), Some(3)); // Adding new held balance works assert_ok!(AssetsHolder::set_balance_on_hold( ASSET_ID, @@ -223,7 +223,7 @@ mod impl_hold_unbalanced { IdAmount { id: DummyHoldReason::Staking, amount: 2 } ] ); - assert_eq!(HeldBalances::::get(ASSET_ID, WHO), Some(5)); + assert_eq!(BalancesOnHold::::get(ASSET_ID, WHO), Some(5)); // Note: Assertion skipped to meet @gavofyork's suggestion of matching the number of // variant count with the number of enum's variants. @@ -247,7 +247,7 @@ mod impl_hold_unbalanced { IdAmount { id: DummyHoldReason::Staking, amount: 1 } ] ); - assert_eq!(HeldBalances::::get(ASSET_ID, WHO), Some(4)); + assert_eq!(BalancesOnHold::::get(ASSET_ID, WHO), Some(4)); // Decreasing until removal of held balance works assert_ok!(AssetsHolder::set_balance_on_hold( ASSET_ID, @@ -259,7 +259,7 @@ mod impl_hold_unbalanced { Holds::::get(ASSET_ID, WHO).to_vec(), vec![IdAmount { id: DummyHoldReason::Staking, amount: 1 }] ); - assert_eq!(HeldBalances::::get(ASSET_ID, WHO), Some(1)); + assert_eq!(BalancesOnHold::::get(ASSET_ID, WHO), Some(1)); // Clearing ol all holds works assert_ok!(AssetsHolder::set_balance_on_hold( ASSET_ID, @@ -268,7 +268,7 @@ mod impl_hold_unbalanced { 0 )); assert_eq!(Holds::::get(ASSET_ID, WHO).to_vec(), vec![]); - assert_eq!(HeldBalances::::get(ASSET_ID, WHO), None); + assert_eq!(BalancesOnHold::::get(ASSET_ID, WHO), None); }); } } From 7dff1117024b75938f4237867a5cde4d98930b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Tue, 27 Aug 2024 22:14:36 -0500 Subject: [PATCH 23/59] fix(pallet-revive-mock-network): include Holder on pallet_assets configuration --- substrate/frame/revive/mock-network/src/parachain.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/substrate/frame/revive/mock-network/src/parachain.rs b/substrate/frame/revive/mock-network/src/parachain.rs index 3def48cca96b..112f51f4e9d9 100644 --- a/substrate/frame/revive/mock-network/src/parachain.rs +++ b/substrate/frame/revive/mock-network/src/parachain.rs @@ -119,6 +119,7 @@ impl pallet_assets::Config for Runtime { type AssetAccountDeposit = AssetAccountDeposit; type ApprovalDeposit = ApprovalDeposit; type StringLimit = AssetsStringLimit; + type Holder = (); type Freezer = (); type Extra = (); type WeightInfo = (); From 5d04950ba9026de1d918645cefc4de0dc5fc1d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Tue, 27 Aug 2024 22:21:36 -0500 Subject: [PATCH 24/59] change(prdoc): update prdoc with all impacted crates --- prdoc/pr_4530.prdoc | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/prdoc/pr_4530.prdoc b/prdoc/pr_4530.prdoc index 64f59d69ad4b..c073ad5685ac 100644 --- a/prdoc/pr_4530.prdoc +++ b/prdoc/pr_4530.prdoc @@ -27,11 +27,37 @@ doc: wanted in the runtime implementation. crates: + - name: asset-hub-rococo-runtime + bump: minor + - name: asset-hub-westend-runtime + bump: minor + - name: pallet-asset-tx-payment + bump: patch + - name: pallet-asset-conversion-ops + bump: patch + - name: pallet-asset-conversion-tx-payment + bump: patch - name: pallet-assets bump: major - name: pallet-assets-holder bump: major - - name: asset-hub-rococo-runtime - bump: minor - - name: asset-hub-westend-runtime + - name: pallet-assets-freezer + bump: patch + - name: pallet-contracts-mock-network + bump: patch + - name: pallet-nft-fractionalization + bump: patch + - name: pallet-revive-mock-network + bump: patch + - name: pallet-xcm + bump: patch + - name: penpal-runtime + bump: patch + - name: rococo-parachain-runtime + bump: patch + - name: polkadot-sdk bump: minor + - name: staging-xcm-builder + bump: patch + - name: xcm-runtime-apis + bump: patch From 44e8f622e7841d4c3e8702bad304482ca3ebc736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 28 Aug 2024 08:27:19 -0500 Subject: [PATCH 25/59] change(pallet-assets-holder): add additional assertions to `try_state` (suggestion by @gui1117) --- substrate/frame/assets-holder/src/lib.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/substrate/frame/assets-holder/src/lib.rs b/substrate/frame/assets-holder/src/lib.rs index c4c6ddcb9897..399c8ee29996 100644 --- a/substrate/frame/assets-holder/src/lib.rs +++ b/substrate/frame/assets-holder/src/lib.rs @@ -153,15 +153,18 @@ impl, I: 'static> Pallet { fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { use sp_runtime::traits::{Saturating, Zero}; - for (asset, who, _) in BalancesOnHold::::iter() { - let held_amount: T::Balance = Holds::::get(asset.clone(), who.clone()) - .iter() - .map(|l| l.amount) - .fold(Zero::zero(), |prev, amount| prev.saturating_add(amount)); + for (asset, who, balance_on_hold) in BalancesOnHold::::iter() { + ensure!(balance_on_hold != Zero::zero(), "zero on hold must not be in state"); + + let mut amount_from_holds: T::Balance = Zero::zero(); + for l in Holds::::get(asset.clone(), who.clone()).iter() { + ensure!(l.amount != Zero::zero(), "zero amount is invalid"); + amount_from_holds = amount_from_holds.saturating_add(l.amount); + } frame_support::ensure!( - BalancesOnHold::::get(asset, who).unwrap_or_else(Zero::zero) == held_amount, - "The `HeldAmount` is not equal to the sum of `Holds` for (`asset`, `who`)" + balance_on_hold == amount_from_holds, + "The `BalancesOnHold` amount is not equal to the sum of `Holds` for (`asset`, `who`)" ); } From bb1a98d7b073fe416bc8b08ed78899cf06ab1c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 28 Aug 2024 08:28:03 -0500 Subject: [PATCH 26/59] minor(pallet-assets-holder): rename _held_ references in comments with _on hold_ --- prdoc/pr_4530.prdoc | 4 +-- substrate/frame/assets-holder/src/impls.rs | 18 ++++++------ substrate/frame/assets-holder/src/lib.rs | 12 ++++---- substrate/frame/assets-holder/src/tests.rs | 33 +++++++++++----------- substrate/frame/assets/src/mock.rs | 10 +++---- 5 files changed, 38 insertions(+), 39 deletions(-) diff --git a/prdoc/pr_4530.prdoc b/prdoc/pr_4530.prdoc index c073ad5685ac..894b668c57de 100644 --- a/prdoc/pr_4530.prdoc +++ b/prdoc/pr_4530.prdoc @@ -4,13 +4,13 @@ doc: - audience: Runtime Dev description: | This change creates the `pallet-assets-holder` pallet, as well as changes `pallet-assets` - to support querying held balances via a new trait: `HeldBalance`. + to support querying held balances via a new trait: `BalanceOnHold`. It also adjusts the balance model implementation for fungible sets (see [sdk docs](https://paritytech.github.io/polkadot-sdk/master/frame_support/traits/tokens/fungible/index.html#visualising-balance-components-together-)). The `pallet-assets-holder` implements `hold` traits for `pallet-assets`, by extending this - pallet and implementing the `HeldBalance` trait so the held balance can be queried by + pallet and implementing the `BalanceOnHold` trait so the held balance can be queried by `pallet-assets` to calculate the reducible (a.k.a. spendable) balance. These changes imply adding a configuration type in `pallet-assets` for `Holder` diff --git a/substrate/frame/assets-holder/src/impls.rs b/substrate/frame/assets-holder/src/impls.rs index a53592e6683e..be7586075c1a 100644 --- a/substrate/frame/assets-holder/src/impls.rs +++ b/substrate/frame/assets-holder/src/impls.rs @@ -29,9 +29,9 @@ use sp_runtime::{ ArithmeticError, }; -// Implements [`HeldBalance`] from [`pallet-assets`], so it can understand whether there's a held -// amount for an asset account, and is able to signal to this pallet when to clear the state of an -// account. +// Implements [`BalanceOnHold`] from [`pallet-assets`], so it can understand whether there's some +// balance on hold for an asset account, and is able to signal to this pallet when to clear the +// state of an account. impl, I: 'static> BalanceOnHold for Pallet { @@ -195,19 +195,19 @@ impl, I: 'static> UnbalancedHold for Pallet { (true, amount) }; - let held_amount = + let amount_on_hold = BalancesOnHold::::get(asset.clone(), who).unwrap_or_else(Zero::zero); - let held_amount = if increase { - held_amount.checked_add(&delta).ok_or(ArithmeticError::Overflow)? + let amount_on_hold = if increase { + amount_on_hold.checked_add(&delta).ok_or(ArithmeticError::Overflow)? } else { - held_amount.checked_sub(&delta).ok_or(ArithmeticError::Underflow)? + amount_on_hold.checked_sub(&delta).ok_or(ArithmeticError::Underflow)? }; - if held_amount.is_zero() { + if amount_on_hold.is_zero() { BalancesOnHold::::remove(asset.clone(), who); } else { - BalancesOnHold::::insert(asset.clone(), who, held_amount); + BalancesOnHold::::insert(asset.clone(), who, amount_on_hold); } if !holds.is_empty() { diff --git a/substrate/frame/assets-holder/src/lib.rs b/substrate/frame/assets-holder/src/lib.rs index 399c8ee29996..8ea8639235ed 100644 --- a/substrate/frame/assets-holder/src/lib.rs +++ b/substrate/frame/assets-holder/src/lib.rs @@ -34,8 +34,8 @@ //! //! This pallet provides the following functionality: //! -//! - Pallet hooks allowing [`pallet-assets`] to know the held balance for an account on a given -//! asset (see [`pallet_assets::HeldBalance`]). +//! - Pallet hooks allowing [`pallet-assets`] to know the balance on hold for an account on a given +//! asset (see [`pallet_assets::BalanceOnHold`](pallet_assets::BalanceOnHold)). //! - An implementation of //! [`fungibles::hold::Inspect`](frame_support::traits::fungibles::hold::Inspect), //! [`fungibles::hold::Mutate`](frame_support::traits::fungibles::hold::Mutate) and @@ -90,21 +90,21 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { - // `who`s held balance was increased by `amount`. + // `who`s balance on hold was increased by `amount`. Held { who: T::AccountId, asset_id: T::AssetId, reason: T::RuntimeHoldReason, amount: T::Balance, }, - // `who`s held balance was decreased by `amount`. + // `who`s balance on hold was decreased by `amount`. Released { who: T::AccountId, asset_id: T::AssetId, reason: T::RuntimeHoldReason, amount: T::Balance, }, - // `who`s held balance was burned by `amount`. + // `who`s balance on hold was burned by `amount`. Slashed { who: T::AccountId, asset_id: T::AssetId, @@ -128,7 +128,7 @@ pub mod pallet { ValueQuery, >; - /// A map that stores the current total held balance for every account on a given AssetId. + /// A map that stores the current total balance on hold for every account on a given AssetId. #[pallet::storage] pub(super) type BalancesOnHold, I: 'static = ()> = StorageDoubleMap< _, diff --git a/substrate/frame/assets-holder/src/tests.rs b/substrate/frame/assets-holder/src/tests.rs index 87d3b5f18d6c..eb9f89feab66 100644 --- a/substrate/frame/assets-holder/src/tests.rs +++ b/substrate/frame/assets-holder/src/tests.rs @@ -185,7 +185,7 @@ mod impl_hold_unbalanced { new_test_ext(|| { assert_eq!(Holds::::get(ASSET_ID, WHO).to_vec(), vec![]); assert_eq!(BalancesOnHold::::get(ASSET_ID, WHO), None); - // Adding held balance works + // Adding balance on hold works assert_ok!(AssetsHolder::set_balance_on_hold( ASSET_ID, &DummyHoldReason::Governance, @@ -209,7 +209,7 @@ mod impl_hold_unbalanced { vec![IdAmount { id: DummyHoldReason::Governance, amount: 3 }] ); assert_eq!(BalancesOnHold::::get(ASSET_ID, WHO), Some(3)); - // Adding new held balance works + // Adding new balance on hold works assert_ok!(AssetsHolder::set_balance_on_hold( ASSET_ID, &DummyHoldReason::Staking, @@ -233,7 +233,7 @@ mod impl_hold_unbalanced { // Error::::TooManyHolds // ); - // Decreasing held balance works + // Decreasing balance on hold works assert_ok!(AssetsHolder::set_balance_on_hold( ASSET_ID, &DummyHoldReason::Staking, @@ -248,7 +248,7 @@ mod impl_hold_unbalanced { ] ); assert_eq!(BalancesOnHold::::get(ASSET_ID, WHO), Some(4)); - // Decreasing until removal of held balance works + // Decreasing until removal of balance on hold works assert_ok!(AssetsHolder::set_balance_on_hold( ASSET_ID, &DummyHoldReason::Governance, @@ -309,9 +309,9 @@ mod impl_hold_mutate { // Holding preserves `total_issuance` assert_eq!(Assets::total_issuance(ASSET_ID), 100); - // Increasing the amount held for the same reason has the same effect as described above - // in `set_balance_on_hold_works`, while total issuance is preserved. - // Consideration: holding for an amount `x` will increase the already held amount by + // Increasing the amount on hold for the same reason has the same effect as described + // above in `set_balance_on_hold_works`, while total issuance is preserved. + // Consideration: holding for an amount `x` will increase the already amount on hold by // `x`. assert_ok!(AssetsHolder::hold(ASSET_ID, &DummyHoldReason::Governance, &WHO, 20)); assert_eq!(Assets::balance(ASSET_ID, &WHO), 70); @@ -373,8 +373,8 @@ mod impl_hold_mutate { assert_eq!(Assets::balance(ASSET_ID, WHO), 70); }); - // Releasing over the max held amount with `BestEffort` will increase the - // balance by the previously held amount, while preserving total issuance. + // Releasing over the max amount on hold with `BestEffort` will increase the + // balance by the previously amount on hold, while preserving total issuance. new_test_ext().execute_with(|| { assert_ok!(AssetsHolder::release( ASSET_ID, @@ -394,7 +394,7 @@ mod impl_hold_mutate { assert_eq!(Assets::balance(ASSET_ID, WHO), 80); }); - // Releasing over the max held amount with `Exact` will fail. + // Releasing over the max amount on hold with `Exact` will fail. new_test_ext().execute_with(|| { assert_noop!( AssetsHolder::release( @@ -425,8 +425,8 @@ mod impl_hold_mutate { assert_eq!(Assets::total_issuance(ASSET_ID), 99); }); - // Burning by an amount up to the held balance with `Exact` works, reducing held balance up - // to the given amount. + // Burning by an amount up to the balance on hold with `Exact` works, reducing balance on + // hold up to the given amount. new_test_ext().execute_with(|| { assert_ok!(AssetsHolder::burn_held( ASSET_ID, @@ -440,8 +440,8 @@ mod impl_hold_mutate { assert_eq!(Assets::balance(ASSET_ID, WHO), 50); }); - // Burning by an amount over the held balance with `BestEffort` works, reducing held balance - // up to the given amount. + // Burning by an amount over the balance on hold with `BestEffort` works, reducing balance + // on hold up to the given amount. new_test_ext().execute_with(|| { assert_ok!(AssetsHolder::burn_held( ASSET_ID, @@ -455,7 +455,7 @@ mod impl_hold_mutate { assert_eq!(Assets::balance(ASSET_ID, WHO), 50); }); - // Burning by an amount over the held balance with `Exact` fails. + // Burning by an amount over the balance on hold with `Exact` fails. new_test_ext().execute_with(|| { assert_noop!( AssetsHolder::burn_held( @@ -474,8 +474,7 @@ mod impl_hold_mutate { #[test] fn burn_all_held_works() { new_test_ext().execute_with(|| { - // Burning all held works as burning giving the held balance - // as amount with `BestEffort` + // Burning all balance on hold works as burning passing it as amount with `BestEffort` assert_ok!(AssetsHolder::burn_all_held( ASSET_ID, &DummyHoldReason::Governance, diff --git a/substrate/frame/assets/src/mock.rs b/substrate/frame/assets/src/mock.rs index 4a46b8edab01..0f81eea47f57 100644 --- a/substrate/frame/assets/src/mock.rs +++ b/substrate/frame/assets/src/mock.rs @@ -132,16 +132,16 @@ impl BalanceOnHold for TestHolder { pub(crate) fn set_held_balance(asset: u32, who: u64, amount: u64) { Held::mutate(|v| { - let held_amount = v.get(&(asset, who)).unwrap_or(&0); + let amount_on_hold = v.get(&(asset, who)).unwrap_or(&0); - if &amount > held_amount { + if &amount > amount_on_hold { // Hold more funds - let amount = amount - held_amount; + let amount = amount - amount_on_hold; let f = DebitFlags { keep_alive: true, best_effort: false }; assert_ok!(Assets::decrease_balance(asset, &who, amount, f, |_, _| Ok(()))); } else { - // Release held funds - let amount = held_amount - amount; + // Release funds on hold + let amount = amount_on_hold - amount; assert_ok!(Assets::increase_balance(asset, &who, amount, |_| Ok(()))); } From 3688f89db57c86c7ba1bbca24109f063fa41e06b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 4 Sep 2024 16:48:16 -0500 Subject: [PATCH 27/59] change(pallet-assets-holder): address changes requested by @muharem --- substrate/frame/assets-holder/src/impls.rs | 13 ++++++++++--- substrate/frame/assets-holder/src/lib.rs | 5 +++-- substrate/frame/assets-holder/src/tests.rs | 2 +- substrate/frame/assets/src/functions.rs | 6 ++++-- substrate/frame/assets/src/types.rs | 16 +++++++++------- 5 files changed, 27 insertions(+), 15 deletions(-) diff --git a/substrate/frame/assets-holder/src/impls.rs b/substrate/frame/assets-holder/src/impls.rs index be7586075c1a..69951898ba42 100644 --- a/substrate/frame/assets-holder/src/impls.rs +++ b/substrate/frame/assets-holder/src/impls.rs @@ -40,8 +40,15 @@ impl, I: 'static> BalanceOnHold::remove(asset.clone(), who); - Holds::::remove(asset, who); + let _ = Holds::::try_mutate(asset.clone(), who, |holds| { + for l in holds.iter() { + Self::burn_all_held(asset.clone(), &l.id, who, Precision::Exact, Fortitude::Force)?; + } + + Holds::::remove(asset.clone(), who); + BalancesOnHold::::remove(asset.clone(), who); + Ok::<(), DispatchError>(()) + }); } } @@ -255,7 +262,7 @@ impl, I: 'static> MutateHold for Pallet { who: &T::AccountId, amount: Self::Balance, ) { - Self::deposit_event(Event::::Slashed { + Self::deposit_event(Event::::Burned { asset_id, who: who.clone(), reason: *reason, diff --git a/substrate/frame/assets-holder/src/lib.rs b/substrate/frame/assets-holder/src/lib.rs index 8ea8639235ed..99efe45a2277 100644 --- a/substrate/frame/assets-holder/src/lib.rs +++ b/substrate/frame/assets-holder/src/lib.rs @@ -105,7 +105,7 @@ pub mod pallet { amount: T::Balance, }, // `who`s balance on hold was burned by `amount`. - Slashed { + Burned { who: T::AccountId, asset_id: T::AssetId, reason: T::RuntimeHoldReason, @@ -159,7 +159,8 @@ impl, I: 'static> Pallet { let mut amount_from_holds: T::Balance = Zero::zero(); for l in Holds::::get(asset.clone(), who.clone()).iter() { ensure!(l.amount != Zero::zero(), "zero amount is invalid"); - amount_from_holds = amount_from_holds.saturating_add(l.amount); + amount_from_holds = + amount_from_holds.checked_add(l.amount).ok_or(ArithmeticError::Overflow)?; } frame_support::ensure!( diff --git a/substrate/frame/assets-holder/src/tests.rs b/substrate/frame/assets-holder/src/tests.rs index eb9f89feab66..a6abddbacb88 100644 --- a/substrate/frame/assets-holder/src/tests.rs +++ b/substrate/frame/assets-holder/src/tests.rs @@ -535,7 +535,7 @@ mod impl_hold_mutate { Fortitude::Polite, )); System::assert_has_event( - Event::::Slashed { + Event::::Burned { who: WHO, asset_id: ASSET_ID, reason: DummyHoldReason::Governance, diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index ab82db86c36c..75ff0013ce9d 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -211,8 +211,10 @@ impl, I: 'static> Pallet { let frozen = maybe_frozen.unwrap_or_default(); let held = maybe_held.unwrap_or_default(); - let required = frozen.saturating_sub(held).max(details.min_balance); - if rest < required { + // The `untouchable` balance of the asset account of `who`. This is described + // here: https://paritytech.github.io/polkadot-sdk/master/frame_support/traits/tokens/fungible/index.html#visualising-balance-components-together- + let untouchable = frozen.saturating_sub(held).max(details.min_balance); + if rest < untouchable { return Frozen } diff --git a/substrate/frame/assets/src/types.rs b/substrate/frame/assets/src/types.rs index d560da5319a8..01fc4577cf6e 100644 --- a/substrate/frame/assets/src/types.rs +++ b/substrate/frame/assets/src/types.rs @@ -232,17 +232,19 @@ impl FrozenBalance for fn died(_: AssetId, _: &AccountId) {} } -/// Trait for specifying a balance that is distinct from the free balance. -/// This balance is then summed up with the balance of the account, and the -/// `minimum_balance` (and frozen balance, if any) of the asset to calculate -/// the reducible balance. +/// This trait indicates a balance that is _on hold_ for an asset account. +/// +/// A balance _on hold_ is a balance that, while is assigned to an account, +/// is outside the direct control of it. Instead, is being _held_ by the +/// system logic (i.e. Pallets) and can be eventually burned or released. pub trait BalanceOnHold { /// Return the held balance. /// - /// If `Some`, it means some balance is suspended, and it can be infallibly - /// slashed. + /// If `Some`, it means some balance is _on hold_, and it can be + /// infallibly burned. /// - /// If `None` is returned, then nothing special is enforced. + /// If `None` is returned, then no balance is _on hold_ for `who`'s asset + /// account. fn balance_on_hold(asset: AssetId, who: &AccountId) -> Option; /// Called after an account has been removed. From 900dbaad79e1b10fba6067c0e6b73731f1571f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 4 Sep 2024 16:50:14 -0500 Subject: [PATCH 28/59] change(pallet-assets-holder): impls -> impl_fungibles --- .../frame/assets-holder/src/{impls.rs => impl_fungibles.rs} | 0 substrate/frame/assets-holder/src/lib.rs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename substrate/frame/assets-holder/src/{impls.rs => impl_fungibles.rs} (100%) diff --git a/substrate/frame/assets-holder/src/impls.rs b/substrate/frame/assets-holder/src/impl_fungibles.rs similarity index 100% rename from substrate/frame/assets-holder/src/impls.rs rename to substrate/frame/assets-holder/src/impl_fungibles.rs diff --git a/substrate/frame/assets-holder/src/lib.rs b/substrate/frame/assets-holder/src/lib.rs index 99efe45a2277..0756b25c4729 100644 --- a/substrate/frame/assets-holder/src/lib.rs +++ b/substrate/frame/assets-holder/src/lib.rs @@ -58,7 +58,7 @@ mod mock; #[cfg(test)] mod tests; -mod impls; +mod impl_fungibles; #[frame_support::pallet] pub mod pallet { From b689e2e97d9a72a99226666f433ff741c1988949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 4 Sep 2024 19:44:46 -0500 Subject: [PATCH 29/59] fix(pallet-assets-holder): missing `CheckedAdd` import --- substrate/frame/assets-holder/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/substrate/frame/assets-holder/src/lib.rs b/substrate/frame/assets-holder/src/lib.rs index 0756b25c4729..13fcf17a9c79 100644 --- a/substrate/frame/assets-holder/src/lib.rs +++ b/substrate/frame/assets-holder/src/lib.rs @@ -50,6 +50,7 @@ use frame_support::{ BoundedVec, }; use frame_system::pallet_prelude::BlockNumberFor; +use sp_runtime::traits::CheckedAdd; pub use pallet::*; From 2f14b320d6b570cba4a789ce96242ddce05194d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Thu, 5 Sep 2024 09:52:10 -0500 Subject: [PATCH 30/59] change(pallet-assets-holder): make `died` burn balance on hold (if any). - now it returns a `DispatchResult`. - apply changes on usages and traits impls. - change tests to assert calling the method returns `Ok`. --- substrate/frame/assets-holder/src/impl_fungibles.rs | 10 +++++----- substrate/frame/assets-holder/src/lib.rs | 6 ++++-- substrate/frame/assets-holder/src/tests.rs | 2 +- substrate/frame/assets/src/functions.rs | 12 ++++++------ substrate/frame/assets/src/mock.rs | 3 ++- substrate/frame/assets/src/types.rs | 6 ++++-- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/substrate/frame/assets-holder/src/impl_fungibles.rs b/substrate/frame/assets-holder/src/impl_fungibles.rs index 69951898ba42..71fbfa12438e 100644 --- a/substrate/frame/assets-holder/src/impl_fungibles.rs +++ b/substrate/frame/assets-holder/src/impl_fungibles.rs @@ -39,16 +39,16 @@ impl, I: 'static> BalanceOnHold::get(asset, who) } - fn died(asset: T::AssetId, who: &T::AccountId) { - let _ = Holds::::try_mutate(asset.clone(), who, |holds| { + fn died(asset: T::AssetId, who: &T::AccountId) -> DispatchResult { + Holds::::try_mutate(asset.clone(), who, |holds| { for l in holds.iter() { Self::burn_all_held(asset.clone(), &l.id, who, Precision::Exact, Fortitude::Force)?; } - Holds::::remove(asset.clone(), who); + *holds = BoundedVec::new(); BalancesOnHold::::remove(asset.clone(), who); - Ok::<(), DispatchError>(()) - }); + Ok(()) + }) } } diff --git a/substrate/frame/assets-holder/src/lib.rs b/substrate/frame/assets-holder/src/lib.rs index 13fcf17a9c79..b5ccb8f50a35 100644 --- a/substrate/frame/assets-holder/src/lib.rs +++ b/substrate/frame/assets-holder/src/lib.rs @@ -50,7 +50,9 @@ use frame_support::{ BoundedVec, }; use frame_system::pallet_prelude::BlockNumberFor; -use sp_runtime::traits::CheckedAdd; + +#[cfg(any(test, feature = "try-runtime"))] +use sp_runtime::{traits::CheckedAdd, ArithmeticError}; pub use pallet::*; @@ -161,7 +163,7 @@ impl, I: 'static> Pallet { for l in Holds::::get(asset.clone(), who.clone()).iter() { ensure!(l.amount != Zero::zero(), "zero amount is invalid"); amount_from_holds = - amount_from_holds.checked_add(l.amount).ok_or(ArithmeticError::Overflow)?; + amount_from_holds.checked_add(&l.amount).ok_or(ArithmeticError::Overflow)?; } frame_support::ensure!( diff --git a/substrate/frame/assets-holder/src/tests.rs b/substrate/frame/assets-holder/src/tests.rs index a6abddbacb88..8676105a49dc 100644 --- a/substrate/frame/assets-holder/src/tests.rs +++ b/substrate/frame/assets-holder/src/tests.rs @@ -80,7 +80,7 @@ mod impl_balance_on_hold { fn died_works() { new_test_ext(|| { test_hold(DummyHoldReason::Governance, 1); - AssetsHolder::died(ASSET_ID, &WHO); + assert_ok!(AssetsHolder::died(ASSET_ID, &WHO)); assert!(BalancesOnHold::::get(ASSET_ID, WHO).is_none()); assert!(Holds::::get(ASSET_ID, WHO).is_empty()); }); diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index 75ff0013ce9d..54d544481cba 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -384,7 +384,7 @@ impl, I: 'static> Pallet { Asset::::insert(&id, details); // Executing a hook here is safe, since it is not in a `mutate`. T::Freezer::died(id.clone(), &who); - T::Holder::died(id, &who); + T::Holder::died(id, &who)?; Ok(()) } @@ -421,7 +421,7 @@ impl, I: 'static> Pallet { Asset::::insert(&id, details); // Executing a hook here is safe, since it is not in a `mutate`. T::Freezer::died(id.clone(), &who); - T::Holder::died(id, &who); + T::Holder::died(id, &who)?; return Ok(()) } @@ -590,7 +590,7 @@ impl, I: 'static> Pallet { // Execute hook outside of `mutate`. if let Some(Remove) = target_died { T::Freezer::died(id.clone(), target); - T::Holder::died(id, target); + T::Holder::died(id, target)?; } Ok(actual) } @@ -615,7 +615,7 @@ impl, I: 'static> Pallet { Self::transfer_and_die(id.clone(), source, dest, amount, maybe_need_admin, f)?; if let Some(Remove) = died { T::Freezer::died(id.clone(), source); - T::Holder::died(id, source); + T::Holder::died(id, source)?; } Ok(balance) } @@ -816,7 +816,7 @@ impl, I: 'static> Pallet { for who in &dead_accounts { T::Freezer::died(id.clone(), &who); - T::Holder::died(id.clone(), &who); + T::Holder::died(id.clone(), &who)?; } Self::deposit_event(Event::AccountsDestroyed { @@ -978,7 +978,7 @@ impl, I: 'static> Pallet { // Execute hook outside of `mutate`. if let Some(Remove) = owner_died { T::Freezer::died(id.clone(), owner); - T::Holder::died(id, owner); + T::Holder::died(id, owner)?; } Ok(()) } diff --git a/substrate/frame/assets/src/mock.rs b/substrate/frame/assets/src/mock.rs index 0f81eea47f57..6f9db628f92f 100644 --- a/substrate/frame/assets/src/mock.rs +++ b/substrate/frame/assets/src/mock.rs @@ -125,8 +125,9 @@ impl BalanceOnHold for TestHolder { Held::get().get(&(asset, *who)).cloned() } - fn died(_asset: u32, _who: &u64) { + fn died(_asset: u32, _who: &u64) -> DispatchResult { // TODO: Connect with existing hooks list + Ok(()) } } diff --git a/substrate/frame/assets/src/types.rs b/substrate/frame/assets/src/types.rs index 01fc4577cf6e..50e3fab91b6f 100644 --- a/substrate/frame/assets/src/types.rs +++ b/substrate/frame/assets/src/types.rs @@ -250,14 +250,16 @@ pub trait BalanceOnHold { /// Called after an account has been removed. /// /// NOTE: It is possible that the asset does no longer exist when this hook is called. - fn died(asset: AssetId, who: &AccountId); + fn died(asset: AssetId, who: &AccountId) -> DispatchResult; } impl BalanceOnHold for () { fn balance_on_hold(_: AssetId, _: &AccountId) -> Option { None } - fn died(_: AssetId, _: &AccountId) {} + fn died(_: AssetId, _: &AccountId) -> DispatchResult { + Ok(()) + } } #[derive(Copy, Clone, PartialEq, Eq)] From 5d7d87473c0b5c8cb3050fd855472cd444d29cc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Thu, 5 Sep 2024 18:29:09 -0500 Subject: [PATCH 31/59] change(pallet-assets-holder): fast exit for `set_balance_on_hold`. --- .../frame/assets-holder/src/impl_fungibles.rs | 59 +++++++++++-------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/substrate/frame/assets-holder/src/impl_fungibles.rs b/substrate/frame/assets-holder/src/impl_fungibles.rs index 71fbfa12438e..0b6663647a49 100644 --- a/substrate/frame/assets-holder/src/impl_fungibles.rs +++ b/substrate/frame/assets-holder/src/impl_fungibles.rs @@ -181,46 +181,57 @@ impl, I: 'static> UnbalancedHold for Pallet { amount: Self::Balance, ) -> DispatchResult { let mut holds = Holds::::get(asset.clone(), who); + let amount_on_hold = + BalancesOnHold::::get(asset.clone(), who).unwrap_or_else(Zero::zero); - let (increase, delta) = if let Some(pos) = holds.iter().position(|x| &x.id == reason) { - let item = &mut holds[pos]; - let (increase, delta) = - (amount > item.amount, item.amount.max(amount) - item.amount.min(amount)); + let amount_on_hold = if amount.is_zero() { + if let Some(pos) = holds.iter().position(|x| &x.id == reason) { + let item = &mut holds[pos]; + let amount = item.amount; - item.amount = amount; - if item.amount.is_zero() { holds.swap_remove(pos); + amount_on_hold.checked_sub(&amount).ok_or(ArithmeticError::Underflow)? + } else { + amount_on_hold } - - (increase, delta) } else { - if !amount.is_zero() { + let (increase, delta) = if let Some(pos) = holds.iter().position(|x| &x.id == reason) { + let item = &mut holds[pos]; + let (increase, delta) = + (amount > item.amount, item.amount.max(amount) - item.amount.min(amount)); + + item.amount = amount; + if item.amount.is_zero() { + holds.swap_remove(pos); + } + + (increase, delta) + } else { holds .try_push(IdAmount { id: *reason, amount }) .map_err(|_| Error::::TooManyHolds)?; - } - (true, amount) - }; + (true, amount) + }; - let amount_on_hold = - BalancesOnHold::::get(asset.clone(), who).unwrap_or_else(Zero::zero); + let amount_on_hold = if increase { + amount_on_hold.checked_add(&delta).ok_or(ArithmeticError::Overflow)? + } else { + amount_on_hold.checked_sub(&delta).ok_or(ArithmeticError::Underflow)? + }; - let amount_on_hold = if increase { - amount_on_hold.checked_add(&delta).ok_or(ArithmeticError::Overflow)? - } else { - amount_on_hold.checked_sub(&delta).ok_or(ArithmeticError::Underflow)? + amount_on_hold }; - if amount_on_hold.is_zero() { - BalancesOnHold::::remove(asset.clone(), who); + if !holds.is_empty() { + Holds::::insert(asset.clone(), who, holds); } else { - BalancesOnHold::::insert(asset.clone(), who, amount_on_hold); + Holds::::remove(asset.clone(), who); } - if !holds.is_empty() { - Holds::::insert(asset, who, holds); + if amount_on_hold.is_zero() { + BalancesOnHold::::remove(asset.clone(), who); } else { - Holds::::remove(asset, who); + BalancesOnHold::::insert(asset.clone(), who, amount_on_hold); } Ok(()) From 427a29bc5c5178299a66ef0fce3c071da7731585 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Fri, 6 Sep 2024 07:21:42 -0500 Subject: [PATCH 32/59] fix(penpal-runtime): missing `Holder` in `PoolAssets` config --- cumulus/parachains/runtimes/testing/penpal/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 69466d6bd250..aa0c6d3b2be4 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -540,6 +540,7 @@ impl pallet_assets::Config for Runtime { type MetadataDepositPerByte = ConstU128<0>; type ApprovalDeposit = ConstU128<0>; type StringLimit = ConstU32<50>; + type Holder = (); type Freezer = (); type Extra = (); type WeightInfo = pallet_assets::weights::SubstrateWeight; From 9ccc35ce546791028dab830e9ad98b5bb5bc70c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Fri, 6 Sep 2024 08:50:03 -0500 Subject: [PATCH 33/59] fix(pallet-assets-holder): lint --- substrate/frame/assets-holder/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/substrate/frame/assets-holder/src/lib.rs b/substrate/frame/assets-holder/src/lib.rs index b5ccb8f50a35..cda267d639eb 100644 --- a/substrate/frame/assets-holder/src/lib.rs +++ b/substrate/frame/assets-holder/src/lib.rs @@ -51,9 +51,6 @@ use frame_support::{ }; use frame_system::pallet_prelude::BlockNumberFor; -#[cfg(any(test, feature = "try-runtime"))] -use sp_runtime::{traits::CheckedAdd, ArithmeticError}; - pub use pallet::*; #[cfg(test)] @@ -154,7 +151,10 @@ pub mod pallet { impl, I: 'static> Pallet { #[cfg(any(test, feature = "try-runtime"))] fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { - use sp_runtime::traits::{Saturating, Zero}; + use sp_runtime::{ + traits::{CheckedAdd, Zero}, + ArithmeticError, + }; for (asset, who, balance_on_hold) in BalancesOnHold::::iter() { ensure!(balance_on_hold != Zero::zero(), "zero on hold must not be in state"); From c65a4c2857bcee6b90f96a4cacc3f11c7313fa3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Fri, 6 Sep 2024 09:41:11 -0500 Subject: [PATCH 34/59] change(pallet-assets-holder): revert `died` to make it an infallible function --- .../frame/assets-holder/src/impl_fungibles.rs | 24 ++++++++++++------- substrate/frame/assets-holder/src/tests.rs | 12 +++++++++- substrate/frame/assets/src/functions.rs | 12 +++++----- substrate/frame/assets/src/mock.rs | 3 +-- substrate/frame/assets/src/types.rs | 11 +++++---- 5 files changed, 39 insertions(+), 23 deletions(-) diff --git a/substrate/frame/assets-holder/src/impl_fungibles.rs b/substrate/frame/assets-holder/src/impl_fungibles.rs index 0b6663647a49..ef277f8b8d06 100644 --- a/substrate/frame/assets-holder/src/impl_fungibles.rs +++ b/substrate/frame/assets-holder/src/impl_fungibles.rs @@ -39,16 +39,22 @@ impl, I: 'static> BalanceOnHold::get(asset, who) } - fn died(asset: T::AssetId, who: &T::AccountId) -> DispatchResult { - Holds::::try_mutate(asset.clone(), who, |holds| { - for l in holds.iter() { - Self::burn_all_held(asset.clone(), &l.id, who, Precision::Exact, Fortitude::Force)?; - } + fn died(asset: T::AssetId, who: &T::AccountId) { + if pallet_assets::Pallet::::can_withdraw(asset.clone(), who, Zero::zero()) == + WithdrawConsequence::Success + { + defensive_assert!( + Holds::::get(asset.clone(), who).is_empty(), + "The list of Holds should be empty before allowing an account to die" + ); + defensive_assert!( + BalancesOnHold::::get(asset.clone(), who).is_none(), + "The should not be a balance on hold before allowing to die" + ); + } - *holds = BoundedVec::new(); - BalancesOnHold::::remove(asset.clone(), who); - Ok(()) - }) + Holds::::remove(asset.clone(), who); + BalancesOnHold::::remove(asset, who); } } diff --git a/substrate/frame/assets-holder/src/tests.rs b/substrate/frame/assets-holder/src/tests.rs index 8676105a49dc..433ed664a144 100644 --- a/substrate/frame/assets-holder/src/tests.rs +++ b/substrate/frame/assets-holder/src/tests.rs @@ -76,11 +76,21 @@ mod impl_balance_on_hold { }); } + #[test] + #[should_panic = "The list of Holds should be empty before allowing an account to die"] + fn died_fails_if_holds_exist() { + new_test_ext(|| { + test_hold(DummyHoldReason::Governance, 1); + AssetsHolder::died(ASSET_ID, &WHO); + }); + } + #[test] fn died_works() { new_test_ext(|| { test_hold(DummyHoldReason::Governance, 1); - assert_ok!(AssetsHolder::died(ASSET_ID, &WHO)); + test_release(DummyHoldReason::Governance); + AssetsHolder::died(ASSET_ID, &WHO); assert!(BalancesOnHold::::get(ASSET_ID, WHO).is_none()); assert!(Holds::::get(ASSET_ID, WHO).is_empty()); }); diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index 54d544481cba..75ff0013ce9d 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -384,7 +384,7 @@ impl, I: 'static> Pallet { Asset::::insert(&id, details); // Executing a hook here is safe, since it is not in a `mutate`. T::Freezer::died(id.clone(), &who); - T::Holder::died(id, &who)?; + T::Holder::died(id, &who); Ok(()) } @@ -421,7 +421,7 @@ impl, I: 'static> Pallet { Asset::::insert(&id, details); // Executing a hook here is safe, since it is not in a `mutate`. T::Freezer::died(id.clone(), &who); - T::Holder::died(id, &who)?; + T::Holder::died(id, &who); return Ok(()) } @@ -590,7 +590,7 @@ impl, I: 'static> Pallet { // Execute hook outside of `mutate`. if let Some(Remove) = target_died { T::Freezer::died(id.clone(), target); - T::Holder::died(id, target)?; + T::Holder::died(id, target); } Ok(actual) } @@ -615,7 +615,7 @@ impl, I: 'static> Pallet { Self::transfer_and_die(id.clone(), source, dest, amount, maybe_need_admin, f)?; if let Some(Remove) = died { T::Freezer::died(id.clone(), source); - T::Holder::died(id, source)?; + T::Holder::died(id, source); } Ok(balance) } @@ -816,7 +816,7 @@ impl, I: 'static> Pallet { for who in &dead_accounts { T::Freezer::died(id.clone(), &who); - T::Holder::died(id.clone(), &who)?; + T::Holder::died(id.clone(), &who); } Self::deposit_event(Event::AccountsDestroyed { @@ -978,7 +978,7 @@ impl, I: 'static> Pallet { // Execute hook outside of `mutate`. if let Some(Remove) = owner_died { T::Freezer::died(id.clone(), owner); - T::Holder::died(id, owner)?; + T::Holder::died(id, owner); } Ok(()) } diff --git a/substrate/frame/assets/src/mock.rs b/substrate/frame/assets/src/mock.rs index 6f9db628f92f..0f81eea47f57 100644 --- a/substrate/frame/assets/src/mock.rs +++ b/substrate/frame/assets/src/mock.rs @@ -125,9 +125,8 @@ impl BalanceOnHold for TestHolder { Held::get().get(&(asset, *who)).cloned() } - fn died(_asset: u32, _who: &u64) -> DispatchResult { + fn died(_asset: u32, _who: &u64) { // TODO: Connect with existing hooks list - Ok(()) } } diff --git a/substrate/frame/assets/src/types.rs b/substrate/frame/assets/src/types.rs index 50e3fab91b6f..162e88f5dc0d 100644 --- a/substrate/frame/assets/src/types.rs +++ b/substrate/frame/assets/src/types.rs @@ -249,17 +249,18 @@ pub trait BalanceOnHold { /// Called after an account has been removed. /// - /// NOTE: It is possible that the asset does no longer exist when this hook is called. - fn died(asset: AssetId, who: &AccountId) -> DispatchResult; + /// It is expected that this method is called only when there is not balance + /// on hold. Otherwise, an account should not be removed. + /// + /// NOTE: It is possible that the asset no longer exists when this hook is called. + fn died(asset: AssetId, who: &AccountId); } impl BalanceOnHold for () { fn balance_on_hold(_: AssetId, _: &AccountId) -> Option { None } - fn died(_: AssetId, _: &AccountId) -> DispatchResult { - Ok(()) - } + fn died(_: AssetId, _: &AccountId) {} } #[derive(Copy, Clone, PartialEq, Eq)] From f1c0983b768ad671ade34ca5726b93c1947415e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Mon, 9 Sep 2024 11:05:03 -0500 Subject: [PATCH 35/59] change(pallet-assets): remark the change of parameters in the `freezer_should_work` test --- substrate/frame/assets/src/tests.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/substrate/frame/assets/src/tests.rs b/substrate/frame/assets/src/tests.rs index 8444fb34d62b..ff0f1951be97 100644 --- a/substrate/frame/assets/src/tests.rs +++ b/substrate/frame/assets/src/tests.rs @@ -1345,6 +1345,12 @@ fn freezer_should_work() { // freeze 50 of it. set_frozen_balance(0, 1, 50); + // Note: The amount to be transferred in this step changed deliberately from 20 to 30 + // (https://github.com/paritytech/polkadot-sdk/pull/4530/commits/2ab35354d86904c035b21a2229452841b79b0457) + // to reflect the change in how `reducible_balance` is calculated: from untouchable = ed + + // frozen, to untouchalbe = max(ed, frozen) + // + // This is done in this line so most of the remaining test is preserved without changes assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 30)); // cannot transfer another 21 away as this would take the spendable balance (30) to below // zero. From 92769f1a825770364cf24cfa9a910903b7053897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 11 Sep 2024 08:46:17 -0500 Subject: [PATCH 36/59] change(pallet-assets): implement contains_holds / contains_freezes --- substrate/frame/assets-freezer/src/impls.rs | 4 +++ .../frame/assets-holder/src/impl_fungibles.rs | 25 ++++++++-------- substrate/frame/assets/src/functions.rs | 4 +++ substrate/frame/assets/src/lib.rs | 4 +++ substrate/frame/assets/src/mock.rs | 23 ++++++++++++--- substrate/frame/assets/src/tests.rs | 29 +++++++++++++++++-- substrate/frame/assets/src/types.rs | 16 +++++++--- 7 files changed, 83 insertions(+), 22 deletions(-) diff --git a/substrate/frame/assets-freezer/src/impls.rs b/substrate/frame/assets-freezer/src/impls.rs index cd383f1c3cd1..a73ab46eaf04 100644 --- a/substrate/frame/assets-freezer/src/impls.rs +++ b/substrate/frame/assets-freezer/src/impls.rs @@ -38,6 +38,10 @@ impl, I: 'static> FrozenBalance::remove(asset.clone(), who); Freezes::::remove(asset, who); } + + fn contains_freezes(asset: T::AssetId) -> bool { + Freezes::::contains_prefix(asset) + } } // Implement [`fungibles::Inspect`](frame_support::traits::fungibles::Inspect) as it is bound by diff --git a/substrate/frame/assets-holder/src/impl_fungibles.rs b/substrate/frame/assets-holder/src/impl_fungibles.rs index ef277f8b8d06..b286cbb2eb49 100644 --- a/substrate/frame/assets-holder/src/impl_fungibles.rs +++ b/substrate/frame/assets-holder/src/impl_fungibles.rs @@ -28,6 +28,7 @@ use sp_runtime::{ traits::{CheckedAdd, CheckedSub, Zero}, ArithmeticError, }; +use storage::StorageDoubleMap; // Implements [`BalanceOnHold`] from [`pallet-assets`], so it can understand whether there's some // balance on hold for an asset account, and is able to signal to this pallet when to clear the @@ -40,22 +41,22 @@ impl, I: 'static> BalanceOnHold::can_withdraw(asset.clone(), who, Zero::zero()) == - WithdrawConsequence::Success - { - defensive_assert!( - Holds::::get(asset.clone(), who).is_empty(), - "The list of Holds should be empty before allowing an account to die" - ); - defensive_assert!( - BalancesOnHold::::get(asset.clone(), who).is_none(), - "The should not be a balance on hold before allowing to die" - ); - } + defensive_assert!( + Holds::::get(asset.clone(), who).is_empty(), + "The list of Holds should be empty before allowing an account to die" + ); + defensive_assert!( + BalancesOnHold::::get(asset.clone(), who).is_none(), + "The should not be a balance on hold before allowing to die" + ); Holds::::remove(asset.clone(), who); BalancesOnHold::::remove(asset, who); } + + fn contains_holds(asset: T::AssetId) -> bool { + Holds::::contains_prefix(asset) + } } // Implement [`fungibles::Inspect`](frame_support::traits::fungibles::Inspect) as it is bound by diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index 75ff0013ce9d..b5c80358da08 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -769,6 +769,10 @@ impl, I: 'static> Pallet { if let Some(check_owner) = maybe_check_owner { ensure!(details.owner == check_owner, Error::::NoPermission); } + + ensure!(!T::Holder::contains_holds(id.clone()), Error::::ContainsHolds); + ensure!(!T::Freezer::contains_freezes(id.clone()), Error::::ContainsFreezes); + details.status = AssetStatus::Destroying; Self::deposit_event(Event::DestructionStarted { asset_id: id }); diff --git a/substrate/frame/assets/src/lib.rs b/substrate/frame/assets/src/lib.rs index 8d2f9a63b69b..7dbb55569026 100644 --- a/substrate/frame/assets/src/lib.rs +++ b/substrate/frame/assets/src/lib.rs @@ -693,6 +693,10 @@ pub mod pallet { CallbackFailed, /// The asset ID must be equal to the [`NextAssetId`]. BadAssetId, + /// The asset cannot be destroyed because some accounts for this asset contain holds. + ContainsHolds, + /// The asset cannot be destroyed because some accounts for this asset contain freezes. + ContainsFreezes, } #[pallet::call(weight(>::WeightInfo))] diff --git a/substrate/frame/assets/src/mock.rs b/substrate/frame/assets/src/mock.rs index 0f81eea47f57..388da8a3894f 100644 --- a/substrate/frame/assets/src/mock.rs +++ b/substrate/frame/assets/src/mock.rs @@ -115,23 +115,27 @@ pub enum Hook { } parameter_types! { static Frozen: HashMap<(u32, u64), u64> = Default::default(); - static Held: HashMap<(u32, u64), u64> = Default::default(); + static OnHold: HashMap<(u32, u64), u64> = Default::default(); static Hooks: Vec = Default::default(); } pub struct TestHolder; impl BalanceOnHold for TestHolder { fn balance_on_hold(asset: u32, who: &u64) -> Option { - Held::get().get(&(asset, *who)).cloned() + OnHold::get().get(&(asset, *who)).cloned() } fn died(_asset: u32, _who: &u64) { // TODO: Connect with existing hooks list } + + fn contains_holds(asset: AssetId) -> bool { + !OnHold::get().iter().find(|((k, _), _)| &asset == k).is_none() + } } -pub(crate) fn set_held_balance(asset: u32, who: u64, amount: u64) { - Held::mutate(|v| { +pub(crate) fn set_balance_on_hold(asset: u32, who: u64, amount: u64) { + OnHold::mutate(|v| { let amount_on_hold = v.get(&(asset, who)).unwrap_or(&0); if &amount > amount_on_hold { @@ -149,6 +153,12 @@ pub(crate) fn set_held_balance(asset: u32, who: u64, amount: u64) { v.insert((asset, who), amount); }); } + +pub(crate) fn clear_balance_on_hold(asset: u32, who: u64) { + OnHold::mutate(|v| { + v.remove(&(asset, who)); + }); +} pub struct TestFreezer; impl FrozenBalance for TestFreezer { fn frozen_balance(asset: u32, who: &u64) -> Option { @@ -161,6 +171,11 @@ impl FrozenBalance for TestFreezer { // Sanity check: dead accounts have no balance. assert!(Assets::balance(asset, *who).is_zero()); } + + /// Return a value that indicates if there are registered freezes for a given asset. + fn contains_freezes(asset: AssetId) -> bool { + !Frozen::get().iter().find(|((k, _), _)| &asset == k).is_none() + } } pub(crate) fn set_frozen_balance(asset: u32, who: u64, amount: u64) { diff --git a/substrate/frame/assets/src/tests.rs b/substrate/frame/assets/src/tests.rs index ff0f1951be97..def1d52346c2 100644 --- a/substrate/frame/assets/src/tests.rs +++ b/substrate/frame/assets/src/tests.rs @@ -1389,7 +1389,7 @@ fn freezing_and_holds_work() { assert_eq!(Assets::balance(0, 1), 100); // Hold 50 of it - set_held_balance(0, 1, 50); + set_balance_on_hold(0, 1, 50); assert_eq!(Assets::balance(0, 1), 50); assert_eq!(TestHolder::balance_on_hold(0, &1), Some(50)); @@ -1400,7 +1400,7 @@ fn freezing_and_holds_work() { assert_eq!(Assets::reducible_balance(0, &1, true), Ok(39)); // Increasing hold is not necessarily restricted by the frozen balance - set_held_balance(0, 1, 62); + set_balance_on_hold(0, 1, 62); assert_eq!(Assets::reducible_balance(0, &1, true), Ok(28)); // Transfers are bound to the spendable amount @@ -1785,6 +1785,31 @@ fn root_asset_create_should_work() { }); } +#[test] +fn asset_start_destroy_fails_if_there_are_holds_or_freezes() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + + set_frozen_balance(0, 1, 50); + assert_noop!( + Assets::start_destroy(RuntimeOrigin::signed(1), 0), + Error::::ContainsFreezes + ); + + set_balance_on_hold(0, 1, 50); + assert_noop!( + Assets::start_destroy(RuntimeOrigin::signed(1), 0), + Error::::ContainsHolds + ); + + clear_frozen_balance(0, 1); + clear_balance_on_hold(0, 1); + + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + }); +} + #[test] fn asset_create_and_destroy_is_reverted_if_callback_fails() { new_test_ext().execute_with(|| { diff --git a/substrate/frame/assets/src/types.rs b/substrate/frame/assets/src/types.rs index 162e88f5dc0d..3b4b82ad7273 100644 --- a/substrate/frame/assets/src/types.rs +++ b/substrate/frame/assets/src/types.rs @@ -220,9 +220,10 @@ pub trait FrozenBalance { fn frozen_balance(asset: AssetId, who: &AccountId) -> Option; /// Called after an account has been removed. - /// - /// NOTE: It is possible that the asset does no longer exist when this hook is called. fn died(asset: AssetId, who: &AccountId); + + /// Return a value that indicates if there are registered freezes for a given asset. + fn contains_freezes(asset: AssetId) -> bool; } impl FrozenBalance for () { @@ -230,6 +231,9 @@ impl FrozenBalance for None } fn died(_: AssetId, _: &AccountId) {} + fn contains_freezes(_: AssetId) -> bool { + false + } } /// This trait indicates a balance that is _on hold_ for an asset account. @@ -251,9 +255,10 @@ pub trait BalanceOnHold { /// /// It is expected that this method is called only when there is not balance /// on hold. Otherwise, an account should not be removed. - /// - /// NOTE: It is possible that the asset no longer exists when this hook is called. fn died(asset: AssetId, who: &AccountId); + + /// Return a value that indicates if there are registered holds for a given asset. + fn contains_holds(asset: AssetId) -> bool; } impl BalanceOnHold for () { @@ -261,6 +266,9 @@ impl BalanceOnHold for None } fn died(_: AssetId, _: &AccountId) {} + fn contains_holds(_: AssetId) -> bool { + false + } } #[derive(Copy, Clone, PartialEq, Eq)] From 0e848ac34ec0162032a797ba58f28839f692119c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 11 Sep 2024 18:36:42 -0500 Subject: [PATCH 37/59] fix(pallet-assets-freezer): missing import --- substrate/frame/assets-freezer/src/impls.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/substrate/frame/assets-freezer/src/impls.rs b/substrate/frame/assets-freezer/src/impls.rs index a73ab46eaf04..11fb4d77ad87 100644 --- a/substrate/frame/assets-freezer/src/impls.rs +++ b/substrate/frame/assets-freezer/src/impls.rs @@ -23,6 +23,7 @@ use frame_support::traits::{ }; use pallet_assets::FrozenBalance; use sp_runtime::traits::Zero; +use storage::StorageDoubleMap; // Implements [`FrozenBalance`] from [`pallet-assets`], so it can understand how much of an // account balance is frozen, and is able to signal to this pallet when to clear the state of an From 3bd72414a3fece4b03de70ded6e7eca476a7481d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Thu, 12 Sep 2024 22:39:18 -0500 Subject: [PATCH 38/59] change(pallet-assets): disallow `refund`/`refund_other` for an account's deposit if there are holds or freezes in place / update documentation for `start_destroy` --- substrate/frame/assets/src/functions.rs | 19 +++++++++++++++++++ substrate/frame/assets/src/lib.rs | 3 +++ 2 files changed, 22 insertions(+) diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index b5c80358da08..b38cdf8e6874 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -363,6 +363,16 @@ impl, I: 'static> Pallet { pub(super) fn do_refund(id: T::AssetId, who: T::AccountId, allow_burn: bool) -> DispatchResult { use AssetStatus::*; use ExistenceReason::*; + + ensure!( + T::Holder::balance_on_hold(id.clone(), who).is_none(), + Error::::ContainsHolds + ); + ensure!( + T::Freezer::frozen_balance(id.clone(), who).is_none(), + Error::::ContainsFreezes + ); + let mut account = Account::::get(&id, &who).ok_or(Error::::NoDeposit)?; ensure!(matches!(account.reason, Consumer | DepositHeld(..)), Error::::NoDeposit); let mut details = Asset::::get(&id).ok_or(Error::::Unknown)?; @@ -397,6 +407,15 @@ impl, I: 'static> Pallet { who: &T::AccountId, maybe_check_caller: Option, ) -> DispatchResult { + ensure!( + T::Holder::balance_on_hold(id.clone(), who).is_none(), + Error::::ContainsHolds + ); + ensure!( + T::Freezer::frozen_balance(id.clone(), who).is_none(), + Error::::ContainsFreezes + ); + let mut account = Account::::get(&id, &who).ok_or(Error::::NoDeposit)?; let (depositor, deposit) = account.reason.take_deposit_from().ok_or(Error::::NoDeposit)?; diff --git a/substrate/frame/assets/src/lib.rs b/substrate/frame/assets/src/lib.rs index 7dbb55569026..f7990e29c45b 100644 --- a/substrate/frame/assets/src/lib.rs +++ b/substrate/frame/assets/src/lib.rs @@ -810,6 +810,9 @@ pub mod pallet { /// /// - `id`: The identifier of the asset to be destroyed. This must identify an existing /// asset. + /// + /// It will fail with either [`Error::ContainsHolds`] or [`Error::ContainsFreezes`] if + /// an account has holds or freezes in place. #[pallet::call_index(2)] pub fn start_destroy(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { let maybe_check_owner = match T::ForceOrigin::try_origin(origin) { From 3233d4819c213a0e027d11506e8f26f79a93cc88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Thu, 12 Sep 2024 22:47:04 -0500 Subject: [PATCH 39/59] fix(umbrella): revert unintended misformatting of Cargo.toml --- umbrella/Cargo.toml | 1512 ++++++++++++++++++++++++++++--------------- 1 file changed, 980 insertions(+), 532 deletions(-) diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index f5764ac44efe..1d5cbbca875e 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -1,169 +1,617 @@ [package] -description = "Polkadot SDK umbrella crate." -license = "Apache-2.0" name = "polkadot-sdk" version = "0.1.0" +description = "Polkadot SDK umbrella crate." +license = "Apache-2.0" [features] default = ["std"] +std = [ + "asset-test-utils?/std", + "assets-common?/std", + "binary-merkle-tree?/std", + "bp-header-chain?/std", + "bp-messages?/std", + "bp-parachains?/std", + "bp-polkadot-core?/std", + "bp-polkadot?/std", + "bp-relayers?/std", + "bp-runtime?/std", + "bp-test-utils?/std", + "bp-xcm-bridge-hub-router?/std", + "bp-xcm-bridge-hub?/std", + "bridge-hub-common?/std", + "bridge-hub-test-utils?/std", + "bridge-runtime-common?/std", + "cumulus-pallet-aura-ext?/std", + "cumulus-pallet-dmp-queue?/std", + "cumulus-pallet-parachain-system-proc-macro?/std", + "cumulus-pallet-parachain-system?/std", + "cumulus-pallet-session-benchmarking?/std", + "cumulus-pallet-solo-to-para?/std", + "cumulus-pallet-xcm?/std", + "cumulus-pallet-xcmp-queue?/std", + "cumulus-ping?/std", + "cumulus-primitives-aura?/std", + "cumulus-primitives-core?/std", + "cumulus-primitives-parachain-inherent?/std", + "cumulus-primitives-proof-size-hostfunction?/std", + "cumulus-primitives-storage-weight-reclaim?/std", + "cumulus-primitives-timestamp?/std", + "cumulus-primitives-utility?/std", + "cumulus-test-relay-sproof-builder?/std", + "frame-benchmarking-pallet-pov?/std", + "frame-benchmarking?/std", + "frame-election-provider-support?/std", + "frame-executive?/std", + "frame-metadata-hash-extension?/std", + "frame-support-procedural?/std", + "frame-support?/std", + "frame-system-benchmarking?/std", + "frame-system-rpc-runtime-api?/std", + "frame-system?/std", + "frame-try-runtime?/std", + "pallet-alliance?/std", + "pallet-asset-conversion-ops?/std", + "pallet-asset-conversion-tx-payment?/std", + "pallet-asset-conversion?/std", + "pallet-asset-rate?/std", + "pallet-asset-tx-payment?/std", + "pallet-assets-freezer?/std", + "pallet-assets-holder?/std", + "pallet-assets?/std", + "pallet-atomic-swap?/std", + "pallet-aura?/std", + "pallet-authority-discovery?/std", + "pallet-authorship?/std", + "pallet-babe?/std", + "pallet-bags-list?/std", + "pallet-balances?/std", + "pallet-beefy-mmr?/std", + "pallet-beefy?/std", + "pallet-bounties?/std", + "pallet-bridge-grandpa?/std", + "pallet-bridge-messages?/std", + "pallet-bridge-parachains?/std", + "pallet-bridge-relayers?/std", + "pallet-broker?/std", + "pallet-child-bounties?/std", + "pallet-collator-selection?/std", + "pallet-collective-content?/std", + "pallet-collective?/std", + "pallet-contracts-mock-network?/std", + "pallet-contracts?/std", + "pallet-conviction-voting?/std", + "pallet-core-fellowship?/std", + "pallet-delegated-staking?/std", + "pallet-democracy?/std", + "pallet-dev-mode?/std", + "pallet-election-provider-multi-phase?/std", + "pallet-election-provider-support-benchmarking?/std", + "pallet-elections-phragmen?/std", + "pallet-fast-unstake?/std", + "pallet-glutton?/std", + "pallet-grandpa?/std", + "pallet-identity?/std", + "pallet-im-online?/std", + "pallet-indices?/std", + "pallet-insecure-randomness-collective-flip?/std", + "pallet-lottery?/std", + "pallet-membership?/std", + "pallet-message-queue?/std", + "pallet-migrations?/std", + "pallet-mixnet?/std", + "pallet-mmr?/std", + "pallet-multisig?/std", + "pallet-nft-fractionalization?/std", + "pallet-nfts-runtime-api?/std", + "pallet-nfts?/std", + "pallet-nis?/std", + "pallet-node-authorization?/std", + "pallet-nomination-pools-benchmarking?/std", + "pallet-nomination-pools-runtime-api?/std", + "pallet-nomination-pools?/std", + "pallet-offences-benchmarking?/std", + "pallet-offences?/std", + "pallet-paged-list?/std", + "pallet-parameters?/std", + "pallet-preimage?/std", + "pallet-proxy?/std", + "pallet-ranked-collective?/std", + "pallet-recovery?/std", + "pallet-referenda?/std", + "pallet-remark?/std", + "pallet-revive-fixtures?/std", + "pallet-revive-mock-network?/std", + "pallet-revive?/std", + "pallet-root-offences?/std", + "pallet-root-testing?/std", + "pallet-safe-mode?/std", + "pallet-salary?/std", + "pallet-scheduler?/std", + "pallet-scored-pool?/std", + "pallet-session-benchmarking?/std", + "pallet-session?/std", + "pallet-skip-feeless-payment?/std", + "pallet-society?/std", + "pallet-staking-reward-fn?/std", + "pallet-staking-runtime-api?/std", + "pallet-staking?/std", + "pallet-state-trie-migration?/std", + "pallet-statement?/std", + "pallet-sudo?/std", + "pallet-timestamp?/std", + "pallet-tips?/std", + "pallet-transaction-payment-rpc-runtime-api?/std", + "pallet-transaction-payment?/std", + "pallet-transaction-storage?/std", + "pallet-treasury?/std", + "pallet-tx-pause?/std", + "pallet-uniques?/std", + "pallet-utility?/std", + "pallet-vesting?/std", + "pallet-whitelist?/std", + "pallet-xcm-benchmarks?/std", + "pallet-xcm-bridge-hub-router?/std", + "pallet-xcm-bridge-hub?/std", + "pallet-xcm?/std", + "parachains-common?/std", + "parachains-runtimes-test-utils?/std", + "polkadot-core-primitives?/std", + "polkadot-parachain-primitives?/std", + "polkadot-primitives?/std", + "polkadot-runtime-common?/std", + "polkadot-runtime-metrics?/std", + "polkadot-runtime-parachains?/std", + "polkadot-sdk-frame?/std", + "sc-executor?/std", + "slot-range-helper?/std", + "snowbridge-beacon-primitives?/std", + "snowbridge-core?/std", + "snowbridge-ethereum?/std", + "snowbridge-outbound-queue-merkle-tree?/std", + "snowbridge-outbound-queue-runtime-api?/std", + "snowbridge-pallet-ethereum-client-fixtures?/std", + "snowbridge-pallet-ethereum-client?/std", + "snowbridge-pallet-inbound-queue-fixtures?/std", + "snowbridge-pallet-inbound-queue?/std", + "snowbridge-pallet-outbound-queue?/std", + "snowbridge-pallet-system?/std", + "snowbridge-router-primitives?/std", + "snowbridge-runtime-common?/std", + "snowbridge-runtime-test-common?/std", + "snowbridge-system-runtime-api?/std", + "sp-api-proc-macro?/std", + "sp-api?/std", + "sp-application-crypto?/std", + "sp-arithmetic?/std", + "sp-authority-discovery?/std", + "sp-block-builder?/std", + "sp-consensus-aura?/std", + "sp-consensus-babe?/std", + "sp-consensus-beefy?/std", + "sp-consensus-grandpa?/std", + "sp-consensus-pow?/std", + "sp-consensus-slots?/std", + "sp-core-hashing?/std", + "sp-core?/std", + "sp-crypto-ec-utils?/std", + "sp-crypto-hashing?/std", + "sp-debug-derive?/std", + "sp-externalities?/std", + "sp-genesis-builder?/std", + "sp-inherents?/std", + "sp-io?/std", + "sp-keyring?/std", + "sp-keystore?/std", + "sp-metadata-ir?/std", + "sp-mixnet?/std", + "sp-mmr-primitives?/std", + "sp-npos-elections?/std", + "sp-offchain?/std", + "sp-runtime-interface?/std", + "sp-runtime?/std", + "sp-session?/std", + "sp-staking?/std", + "sp-state-machine?/std", + "sp-statement-store?/std", + "sp-std?/std", + "sp-storage?/std", + "sp-timestamp?/std", + "sp-tracing?/std", + "sp-transaction-pool?/std", + "sp-transaction-storage-proof?/std", + "sp-trie?/std", + "sp-version?/std", + "sp-wasm-interface?/std", + "sp-weights?/std", + "staging-parachain-info?/std", + "staging-xcm-builder?/std", + "staging-xcm-executor?/std", + "staging-xcm?/std", + "substrate-bip39?/std", + "testnet-parachains-constants?/std", + "xcm-runtime-apis?/std", +] +runtime-benchmarks = [ + "assets-common?/runtime-benchmarks", + "bridge-hub-common?/runtime-benchmarks", + "bridge-runtime-common?/runtime-benchmarks", + "cumulus-pallet-dmp-queue?/runtime-benchmarks", + "cumulus-pallet-parachain-system?/runtime-benchmarks", + "cumulus-pallet-session-benchmarking?/runtime-benchmarks", + "cumulus-pallet-xcmp-queue?/runtime-benchmarks", + "cumulus-primitives-core?/runtime-benchmarks", + "cumulus-primitives-utility?/runtime-benchmarks", + "frame-benchmarking-cli?/runtime-benchmarks", + "frame-benchmarking-pallet-pov?/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "frame-election-provider-support?/runtime-benchmarks", + "frame-support?/runtime-benchmarks", + "frame-system-benchmarking?/runtime-benchmarks", + "frame-system?/runtime-benchmarks", + "pallet-alliance?/runtime-benchmarks", + "pallet-asset-conversion-ops?/runtime-benchmarks", + "pallet-asset-conversion?/runtime-benchmarks", + "pallet-asset-rate?/runtime-benchmarks", + "pallet-asset-tx-payment?/runtime-benchmarks", + "pallet-assets-freezer?/runtime-benchmarks", + "pallet-assets-holder?/runtime-benchmarks", + "pallet-assets?/runtime-benchmarks", + "pallet-babe?/runtime-benchmarks", + "pallet-bags-list?/runtime-benchmarks", + "pallet-balances?/runtime-benchmarks", + "pallet-beefy-mmr?/runtime-benchmarks", + "pallet-bounties?/runtime-benchmarks", + "pallet-bridge-grandpa?/runtime-benchmarks", + "pallet-bridge-messages?/runtime-benchmarks", + "pallet-bridge-parachains?/runtime-benchmarks", + "pallet-bridge-relayers?/runtime-benchmarks", + "pallet-broker?/runtime-benchmarks", + "pallet-child-bounties?/runtime-benchmarks", + "pallet-collator-selection?/runtime-benchmarks", + "pallet-collective-content?/runtime-benchmarks", + "pallet-collective?/runtime-benchmarks", + "pallet-contracts-mock-network?/runtime-benchmarks", + "pallet-contracts?/runtime-benchmarks", + "pallet-conviction-voting?/runtime-benchmarks", + "pallet-core-fellowship?/runtime-benchmarks", + "pallet-delegated-staking?/runtime-benchmarks", + "pallet-democracy?/runtime-benchmarks", + "pallet-election-provider-multi-phase?/runtime-benchmarks", + "pallet-election-provider-support-benchmarking?/runtime-benchmarks", + "pallet-elections-phragmen?/runtime-benchmarks", + "pallet-fast-unstake?/runtime-benchmarks", + "pallet-glutton?/runtime-benchmarks", + "pallet-grandpa?/runtime-benchmarks", + "pallet-identity?/runtime-benchmarks", + "pallet-im-online?/runtime-benchmarks", + "pallet-indices?/runtime-benchmarks", + "pallet-lottery?/runtime-benchmarks", + "pallet-membership?/runtime-benchmarks", + "pallet-message-queue?/runtime-benchmarks", + "pallet-migrations?/runtime-benchmarks", + "pallet-mixnet?/runtime-benchmarks", + "pallet-mmr?/runtime-benchmarks", + "pallet-multisig?/runtime-benchmarks", + "pallet-nft-fractionalization?/runtime-benchmarks", + "pallet-nfts?/runtime-benchmarks", + "pallet-nis?/runtime-benchmarks", + "pallet-nomination-pools-benchmarking?/runtime-benchmarks", + "pallet-nomination-pools?/runtime-benchmarks", + "pallet-offences-benchmarking?/runtime-benchmarks", + "pallet-offences?/runtime-benchmarks", + "pallet-paged-list?/runtime-benchmarks", + "pallet-parameters?/runtime-benchmarks", + "pallet-preimage?/runtime-benchmarks", + "pallet-proxy?/runtime-benchmarks", + "pallet-ranked-collective?/runtime-benchmarks", + "pallet-recovery?/runtime-benchmarks", + "pallet-referenda?/runtime-benchmarks", + "pallet-remark?/runtime-benchmarks", + "pallet-revive-mock-network?/runtime-benchmarks", + "pallet-revive?/runtime-benchmarks", + "pallet-root-offences?/runtime-benchmarks", + "pallet-safe-mode?/runtime-benchmarks", + "pallet-salary?/runtime-benchmarks", + "pallet-scheduler?/runtime-benchmarks", + "pallet-session-benchmarking?/runtime-benchmarks", + "pallet-skip-feeless-payment?/runtime-benchmarks", + "pallet-society?/runtime-benchmarks", + "pallet-staking?/runtime-benchmarks", + "pallet-state-trie-migration?/runtime-benchmarks", + "pallet-sudo?/runtime-benchmarks", + "pallet-timestamp?/runtime-benchmarks", + "pallet-tips?/runtime-benchmarks", + "pallet-transaction-storage?/runtime-benchmarks", + "pallet-treasury?/runtime-benchmarks", + "pallet-tx-pause?/runtime-benchmarks", + "pallet-uniques?/runtime-benchmarks", + "pallet-utility?/runtime-benchmarks", + "pallet-vesting?/runtime-benchmarks", + "pallet-whitelist?/runtime-benchmarks", + "pallet-xcm-benchmarks?/runtime-benchmarks", + "pallet-xcm-bridge-hub-router?/runtime-benchmarks", + "pallet-xcm-bridge-hub?/runtime-benchmarks", + "pallet-xcm?/runtime-benchmarks", + "parachains-common?/runtime-benchmarks", + "polkadot-cli?/runtime-benchmarks", + "polkadot-node-metrics?/runtime-benchmarks", + "polkadot-parachain-lib?/runtime-benchmarks", + "polkadot-parachain-primitives?/runtime-benchmarks", + "polkadot-primitives?/runtime-benchmarks", + "polkadot-runtime-common?/runtime-benchmarks", + "polkadot-runtime-parachains?/runtime-benchmarks", + "polkadot-sdk-frame?/runtime-benchmarks", + "polkadot-service?/runtime-benchmarks", + "sc-client-db?/runtime-benchmarks", + "sc-service?/runtime-benchmarks", + "snowbridge-core?/runtime-benchmarks", + "snowbridge-pallet-ethereum-client-fixtures?/runtime-benchmarks", + "snowbridge-pallet-ethereum-client?/runtime-benchmarks", + "snowbridge-pallet-inbound-queue-fixtures?/runtime-benchmarks", + "snowbridge-pallet-inbound-queue?/runtime-benchmarks", + "snowbridge-pallet-outbound-queue?/runtime-benchmarks", + "snowbridge-pallet-system?/runtime-benchmarks", + "snowbridge-router-primitives?/runtime-benchmarks", + "snowbridge-runtime-common?/runtime-benchmarks", + "snowbridge-runtime-test-common?/runtime-benchmarks", + "sp-runtime?/runtime-benchmarks", + "sp-staking?/runtime-benchmarks", + "staging-node-inspect?/runtime-benchmarks", + "staging-xcm-builder?/runtime-benchmarks", + "staging-xcm-executor?/runtime-benchmarks", + "xcm-runtime-apis?/runtime-benchmarks", +] +try-runtime = [ + "cumulus-pallet-aura-ext?/try-runtime", + "cumulus-pallet-dmp-queue?/try-runtime", + "cumulus-pallet-parachain-system?/try-runtime", + "cumulus-pallet-solo-to-para?/try-runtime", + "cumulus-pallet-xcm?/try-runtime", + "cumulus-pallet-xcmp-queue?/try-runtime", + "cumulus-ping?/try-runtime", + "frame-benchmarking-pallet-pov?/try-runtime", + "frame-election-provider-support?/try-runtime", + "frame-executive?/try-runtime", + "frame-support?/try-runtime", + "frame-system?/try-runtime", + "frame-try-runtime/try-runtime", + "pallet-alliance?/try-runtime", + "pallet-asset-conversion-ops?/try-runtime", + "pallet-asset-conversion-tx-payment?/try-runtime", + "pallet-asset-conversion?/try-runtime", + "pallet-asset-rate?/try-runtime", + "pallet-asset-tx-payment?/try-runtime", + "pallet-assets-freezer?/try-runtime", + "pallet-assets-holder?/try-runtime", + "pallet-assets?/try-runtime", + "pallet-atomic-swap?/try-runtime", + "pallet-aura?/try-runtime", + "pallet-authority-discovery?/try-runtime", + "pallet-authorship?/try-runtime", + "pallet-babe?/try-runtime", + "pallet-bags-list?/try-runtime", + "pallet-balances?/try-runtime", + "pallet-beefy-mmr?/try-runtime", + "pallet-beefy?/try-runtime", + "pallet-bounties?/try-runtime", + "pallet-bridge-grandpa?/try-runtime", + "pallet-bridge-messages?/try-runtime", + "pallet-bridge-parachains?/try-runtime", + "pallet-bridge-relayers?/try-runtime", + "pallet-broker?/try-runtime", + "pallet-child-bounties?/try-runtime", + "pallet-collator-selection?/try-runtime", + "pallet-collective-content?/try-runtime", + "pallet-collective?/try-runtime", + "pallet-contracts?/try-runtime", + "pallet-conviction-voting?/try-runtime", + "pallet-core-fellowship?/try-runtime", + "pallet-delegated-staking?/try-runtime", + "pallet-democracy?/try-runtime", + "pallet-dev-mode?/try-runtime", + "pallet-election-provider-multi-phase?/try-runtime", + "pallet-elections-phragmen?/try-runtime", + "pallet-fast-unstake?/try-runtime", + "pallet-glutton?/try-runtime", + "pallet-grandpa?/try-runtime", + "pallet-identity?/try-runtime", + "pallet-im-online?/try-runtime", + "pallet-indices?/try-runtime", + "pallet-insecure-randomness-collective-flip?/try-runtime", + "pallet-lottery?/try-runtime", + "pallet-membership?/try-runtime", + "pallet-message-queue?/try-runtime", + "pallet-migrations?/try-runtime", + "pallet-mixnet?/try-runtime", + "pallet-mmr?/try-runtime", + "pallet-multisig?/try-runtime", + "pallet-nft-fractionalization?/try-runtime", + "pallet-nfts?/try-runtime", + "pallet-nis?/try-runtime", + "pallet-node-authorization?/try-runtime", + "pallet-nomination-pools?/try-runtime", + "pallet-offences?/try-runtime", + "pallet-paged-list?/try-runtime", + "pallet-parameters?/try-runtime", + "pallet-preimage?/try-runtime", + "pallet-proxy?/try-runtime", + "pallet-ranked-collective?/try-runtime", + "pallet-recovery?/try-runtime", + "pallet-referenda?/try-runtime", + "pallet-remark?/try-runtime", + "pallet-revive?/try-runtime", + "pallet-root-offences?/try-runtime", + "pallet-root-testing?/try-runtime", + "pallet-safe-mode?/try-runtime", + "pallet-salary?/try-runtime", + "pallet-scheduler?/try-runtime", + "pallet-scored-pool?/try-runtime", + "pallet-session?/try-runtime", + "pallet-skip-feeless-payment?/try-runtime", + "pallet-society?/try-runtime", + "pallet-staking?/try-runtime", + "pallet-state-trie-migration?/try-runtime", + "pallet-statement?/try-runtime", + "pallet-sudo?/try-runtime", + "pallet-timestamp?/try-runtime", + "pallet-tips?/try-runtime", + "pallet-transaction-payment?/try-runtime", + "pallet-transaction-storage?/try-runtime", + "pallet-treasury?/try-runtime", + "pallet-tx-pause?/try-runtime", + "pallet-uniques?/try-runtime", + "pallet-utility?/try-runtime", + "pallet-vesting?/try-runtime", + "pallet-whitelist?/try-runtime", + "pallet-xcm-bridge-hub-router?/try-runtime", + "pallet-xcm-bridge-hub?/try-runtime", + "pallet-xcm?/try-runtime", + "polkadot-cli?/try-runtime", + "polkadot-parachain-lib?/try-runtime", + "polkadot-runtime-common?/try-runtime", + "polkadot-runtime-parachains?/try-runtime", + "polkadot-sdk-frame?/try-runtime", + "polkadot-service?/try-runtime", + "snowbridge-pallet-ethereum-client?/try-runtime", + "snowbridge-pallet-inbound-queue?/try-runtime", + "snowbridge-pallet-outbound-queue?/try-runtime", + "snowbridge-pallet-system?/try-runtime", + "sp-runtime?/try-runtime", + "staging-parachain-info?/try-runtime", +] +serde = [ + "bp-polkadot-core?/serde", + "frame-benchmarking?/serde", + "pallet-asset-tx-payment?/serde", + "pallet-beefy-mmr?/serde", + "pallet-beefy?/serde", + "pallet-contracts?/serde", + "pallet-conviction-voting?/serde", + "pallet-democracy?/serde", + "pallet-message-queue?/serde", + "pallet-offences?/serde", + "pallet-parameters?/serde", + "pallet-referenda?/serde", + "pallet-remark?/serde", + "pallet-revive?/serde", + "pallet-state-trie-migration?/serde", + "pallet-tips?/serde", + "pallet-transaction-payment?/serde", + "pallet-transaction-storage?/serde", + "pallet-treasury?/serde", + "pallet-xcm?/serde", + "snowbridge-beacon-primitives?/serde", + "snowbridge-core?/serde", + "snowbridge-ethereum?/serde", + "snowbridge-pallet-ethereum-client?/serde", + "snowbridge-pallet-inbound-queue?/serde", + "sp-application-crypto?/serde", + "sp-arithmetic?/serde", + "sp-authority-discovery?/serde", + "sp-consensus-aura?/serde", + "sp-consensus-babe?/serde", + "sp-consensus-beefy?/serde", + "sp-consensus-grandpa?/serde", + "sp-consensus-slots?/serde", + "sp-core?/serde", + "sp-mmr-primitives?/serde", + "sp-npos-elections?/serde", + "sp-runtime?/serde", + "sp-staking?/serde", + "sp-statement-store?/serde", + "sp-storage?/serde", + "sp-version?/serde", + "sp-weights?/serde", +] experimental = [ - "frame-support-procedural?/experimental", - "frame-support?/experimental", - "frame-system?/experimental", - "polkadot-sdk-frame?/experimental", + "frame-support-procedural?/experimental", + "frame-support?/experimental", + "frame-system?/experimental", + "polkadot-sdk-frame?/experimental", ] -node = [ - "asset-test-utils", - "bridge-hub-test-utils", - "cumulus-client-cli", - "cumulus-client-collator", - "cumulus-client-consensus-aura", - "cumulus-client-consensus-common", - "cumulus-client-consensus-proposer", - "cumulus-client-consensus-relay-chain", - "cumulus-client-network", - "cumulus-client-parachain-inherent", - "cumulus-client-pov-recovery", - "cumulus-client-service", - "cumulus-relay-chain-inprocess-interface", - "cumulus-relay-chain-interface", - "cumulus-relay-chain-minimal-node", - "cumulus-relay-chain-rpc-interface", - "cumulus-test-relay-sproof-builder", - "emulated-integration-tests-common", - "fork-tree", - "frame-benchmarking-cli", - "frame-remote-externalities", - "frame-support-procedural-tools", - "generate-bags", - "mmr-gadget", - "mmr-rpc", - "pallet-contracts-mock-network", - "pallet-revive-mock-network", - "pallet-transaction-payment-rpc", - "parachains-runtimes-test-utils", - "polkadot-approval-distribution", - "polkadot-availability-bitfield-distribution", - "polkadot-availability-distribution", - "polkadot-availability-recovery", - "polkadot-cli", - "polkadot-collator-protocol", - "polkadot-dispute-distribution", - "polkadot-erasure-coding", - "polkadot-gossip-support", - "polkadot-network-bridge", - "polkadot-node-collation-generation", - "polkadot-node-core-approval-voting", - "polkadot-node-core-av-store", - "polkadot-node-core-backing", - "polkadot-node-core-bitfield-signing", - "polkadot-node-core-candidate-validation", - "polkadot-node-core-chain-api", - "polkadot-node-core-chain-selection", - "polkadot-node-core-dispute-coordinator", - "polkadot-node-core-parachains-inherent", - "polkadot-node-core-prospective-parachains", - "polkadot-node-core-provisioner", - "polkadot-node-core-pvf", - "polkadot-node-core-pvf-checker", - "polkadot-node-core-pvf-common", - "polkadot-node-core-pvf-execute-worker", - "polkadot-node-core-pvf-prepare-worker", - "polkadot-node-core-runtime-api", - "polkadot-node-jaeger", - "polkadot-node-metrics", - "polkadot-node-network-protocol", - "polkadot-node-primitives", - "polkadot-node-subsystem", - "polkadot-node-subsystem-types", - "polkadot-node-subsystem-util", - "polkadot-overseer", - "polkadot-parachain-lib", - "polkadot-rpc", - "polkadot-service", - "polkadot-statement-distribution", - "polkadot-statement-table", - "sc-allocator", - "sc-authority-discovery", - "sc-basic-authorship", - "sc-block-builder", - "sc-chain-spec", - "sc-cli", - "sc-client-api", - "sc-client-db", - "sc-consensus", - "sc-consensus-aura", - "sc-consensus-babe", - "sc-consensus-babe-rpc", - "sc-consensus-beefy", - "sc-consensus-beefy-rpc", - "sc-consensus-epochs", - "sc-consensus-grandpa", - "sc-consensus-grandpa-rpc", - "sc-consensus-manual-seal", - "sc-consensus-pow", - "sc-consensus-slots", - "sc-executor", - "sc-executor-common", - "sc-executor-polkavm", - "sc-executor-wasmtime", - "sc-informant", - "sc-keystore", - "sc-mixnet", - "sc-network", - "sc-network-common", - "sc-network-gossip", - "sc-network-light", - "sc-network-statement", - "sc-network-sync", - "sc-network-transactions", - "sc-network-types", - "sc-offchain", - "sc-proposer-metrics", - "sc-rpc", - "sc-rpc-api", - "sc-rpc-server", - "sc-rpc-spec-v2", - "sc-service", - "sc-state-db", - "sc-statement-store", - "sc-storage-monitor", - "sc-sync-state-rpc", - "sc-sysinfo", - "sc-telemetry", - "sc-tracing", - "sc-transaction-pool", - "sc-transaction-pool-api", - "sc-utils", - "snowbridge-runtime-test-common", - "sp-blockchain", - "sp-consensus", - "sp-core-hashing", - "sp-core-hashing-proc-macro", - "sp-database", - "sp-maybe-compressed-blob", - "sp-panic-handler", - "sp-rpc", - "staging-chain-spec-builder", - "staging-node-inspect", - "staging-tracking-allocator", - "std", - "subkey", - "substrate-build-script-utils", - "substrate-frame-rpc-support", - "substrate-frame-rpc-system", - "substrate-prometheus-endpoint", - "substrate-rpc-client", - "substrate-state-trie-migration-rpc", - "substrate-wasm-builder", - "tracing-gum", - "xcm-emulator", - "xcm-simulator", +with-tracing = [ + "frame-executive?/with-tracing", + "frame-executive?/with-tracing", + "sp-io?/with-tracing", + "sp-io?/with-tracing", + "sp-tracing?/with-tracing", + "sp-tracing?/with-tracing", +] +runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-assets-holder", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-fixtures", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] +runtime = [ + "frame-benchmarking", + "frame-benchmarking-pallet-pov", + "frame-election-provider-solution-type", + "frame-election-provider-support", + "frame-executive", + "frame-metadata-hash-extension", + "frame-support", + "frame-support-procedural", + "frame-support-procedural-tools-derive", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "polkadot-sdk-frame", + "polkadot-sdk-frame?/runtime", + "sp-api", + "sp-api-proc-macro", + "sp-application-crypto", + "sp-arithmetic", + "sp-authority-discovery", + "sp-block-builder", + "sp-consensus-aura", + "sp-consensus-babe", + "sp-consensus-beefy", + "sp-consensus-grandpa", + "sp-consensus-pow", + "sp-consensus-slots", + "sp-core", + "sp-crypto-ec-utils", + "sp-crypto-hashing", + "sp-crypto-hashing-proc-macro", + "sp-debug-derive", + "sp-externalities", + "sp-genesis-builder", + "sp-inherents", + "sp-io", + "sp-keyring", + "sp-keystore", + "sp-metadata-ir", + "sp-mixnet", + "sp-mmr-primitives", + "sp-npos-elections", + "sp-offchain", + "sp-runtime", + "sp-runtime-interface", + "sp-runtime-interface-proc-macro", + "sp-session", + "sp-staking", + "sp-state-machine", + "sp-statement-store", + "sp-std", + "sp-storage", + "sp-timestamp", + "sp-tracing", + "sp-transaction-pool", + "sp-transaction-storage-proof", + "sp-trie", + "sp-version", + "sp-version-proc-macro", + "sp-wasm-interface", + "sp-weights", +] +node = ["asset-test-utils", "bridge-hub-test-utils", "cumulus-client-cli", "cumulus-client-collator", "cumulus-client-consensus-aura", "cumulus-client-consensus-common", "cumulus-client-consensus-proposer", "cumulus-client-consensus-relay-chain", "cumulus-client-network", "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", "cumulus-relay-chain-rpc-interface", "cumulus-test-relay-sproof-builder", "emulated-integration-tests-common", "fork-tree", "frame-benchmarking-cli", "frame-remote-externalities", "frame-support-procedural-tools", "generate-bags", "mmr-gadget", "mmr-rpc", "pallet-contracts-mock-network", "pallet-revive-mock-network", "pallet-transaction-payment-rpc", "parachains-runtimes-test-utils", "polkadot-approval-distribution", "polkadot-availability-bitfield-distribution", "polkadot-availability-distribution", "polkadot-availability-recovery", "polkadot-cli", "polkadot-collator-protocol", "polkadot-dispute-distribution", "polkadot-erasure-coding", "polkadot-gossip-support", "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", "polkadot-node-core-candidate-validation", "polkadot-node-core-chain-api", "polkadot-node-core-chain-selection", "polkadot-node-core-dispute-coordinator", "polkadot-node-core-parachains-inherent", "polkadot-node-core-prospective-parachains", "polkadot-node-core-provisioner", "polkadot-node-core-pvf", "polkadot-node-core-pvf-checker", "polkadot-node-core-pvf-common", "polkadot-node-core-pvf-execute-worker", "polkadot-node-core-pvf-prepare-worker", "polkadot-node-core-runtime-api", "polkadot-node-jaeger", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-overseer", "polkadot-parachain-lib", "polkadot-rpc", "polkadot-service", "polkadot-statement-distribution", "polkadot-statement-table", "sc-allocator", "sc-authority-discovery", "sc-basic-authorship", "sc-block-builder", "sc-chain-spec", "sc-cli", "sc-client-api", "sc-client-db", "sc-consensus", "sc-consensus-aura", "sc-consensus-babe", "sc-consensus-babe-rpc", "sc-consensus-beefy", "sc-consensus-beefy-rpc", "sc-consensus-epochs", "sc-consensus-grandpa", "sc-consensus-grandpa-rpc", "sc-consensus-manual-seal", "sc-consensus-pow", "sc-consensus-slots", "sc-executor", "sc-executor-common", "sc-executor-polkavm", "sc-executor-wasmtime", "sc-informant", "sc-keystore", "sc-mixnet", "sc-network", "sc-network-common", "sc-network-gossip", "sc-network-light", "sc-network-statement", "sc-network-sync", "sc-network-transactions", "sc-network-types", "sc-offchain", "sc-proposer-metrics", "sc-rpc", "sc-rpc-api", "sc-rpc-server", "sc-rpc-spec-v2", "sc-service", "sc-state-db", "sc-statement-store", "sc-storage-monitor", "sc-sync-state-rpc", "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "snowbridge-runtime-test-common", "sp-blockchain", "sp-consensus", "sp-core-hashing", "sp-core-hashing-proc-macro", "sp-database", "sp-maybe-compressed-blob", "sp-panic-handler", "sp-rpc", "staging-chain-spec-builder", "staging-node-inspect", "staging-tracking-allocator", "std", "subkey", "substrate-build-script-utils", "substrate-frame-rpc-support", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-rpc-client", "substrate-state-trie-migration-rpc", "substrate-wasm-builder", "tracing-gum", "xcm-emulator", "xcm-simulator"] +tuples-96 = [ + "frame-support-procedural?/tuples-96", + "frame-support?/tuples-96", ] riscv = [ - "pallet-revive-fixtures?/riscv", - "pallet-revive-mock-network?/riscv", - "pallet-revive?/riscv", + "pallet-revive-fixtures?/riscv", + "pallet-revive-mock-network?/riscv", + "pallet-revive?/riscv", ] [package.edition] @@ -173,1880 +621,1880 @@ workspace = true workspace = true [dependencies.assets-common] +path = "../cumulus/parachains/runtimes/assets/common" default-features = false optional = true -path = "../cumulus/parachains/runtimes/assets/common" [dependencies.binary-merkle-tree] +path = "../substrate/utils/binary-merkle-tree" default-features = false optional = true -path = "../substrate/utils/binary-merkle-tree" [dependencies.bp-header-chain] +path = "../bridges/primitives/header-chain" default-features = false optional = true -path = "../bridges/primitives/header-chain" [dependencies.bp-messages] +path = "../bridges/primitives/messages" default-features = false optional = true -path = "../bridges/primitives/messages" [dependencies.bp-parachains] +path = "../bridges/primitives/parachains" default-features = false optional = true -path = "../bridges/primitives/parachains" [dependencies.bp-polkadot] +path = "../bridges/chains/chain-polkadot" default-features = false optional = true -path = "../bridges/chains/chain-polkadot" [dependencies.bp-polkadot-core] +path = "../bridges/primitives/polkadot-core" default-features = false optional = true -path = "../bridges/primitives/polkadot-core" [dependencies.bp-relayers] +path = "../bridges/primitives/relayers" default-features = false optional = true -path = "../bridges/primitives/relayers" [dependencies.bp-runtime] +path = "../bridges/primitives/runtime" default-features = false optional = true -path = "../bridges/primitives/runtime" [dependencies.bp-test-utils] +path = "../bridges/primitives/test-utils" default-features = false optional = true -path = "../bridges/primitives/test-utils" [dependencies.bp-xcm-bridge-hub] +path = "../bridges/primitives/xcm-bridge-hub" default-features = false optional = true -path = "../bridges/primitives/xcm-bridge-hub" [dependencies.bp-xcm-bridge-hub-router] +path = "../bridges/primitives/xcm-bridge-hub-router" default-features = false optional = true -path = "../bridges/primitives/xcm-bridge-hub-router" [dependencies.bridge-hub-common] +path = "../cumulus/parachains/runtimes/bridge-hubs/common" default-features = false optional = true -path = "../cumulus/parachains/runtimes/bridge-hubs/common" [dependencies.bridge-runtime-common] +path = "../bridges/bin/runtime-common" default-features = false optional = true -path = "../bridges/bin/runtime-common" [dependencies.cumulus-pallet-aura-ext] +path = "../cumulus/pallets/aura-ext" default-features = false optional = true -path = "../cumulus/pallets/aura-ext" [dependencies.cumulus-pallet-dmp-queue] +path = "../cumulus/pallets/dmp-queue" default-features = false optional = true -path = "../cumulus/pallets/dmp-queue" [dependencies.cumulus-pallet-parachain-system] +path = "../cumulus/pallets/parachain-system" default-features = false optional = true -path = "../cumulus/pallets/parachain-system" [dependencies.cumulus-pallet-parachain-system-proc-macro] +path = "../cumulus/pallets/parachain-system/proc-macro" default-features = false optional = true -path = "../cumulus/pallets/parachain-system/proc-macro" [dependencies.cumulus-pallet-session-benchmarking] +path = "../cumulus/pallets/session-benchmarking" default-features = false optional = true -path = "../cumulus/pallets/session-benchmarking" [dependencies.cumulus-pallet-solo-to-para] +path = "../cumulus/pallets/solo-to-para" default-features = false optional = true -path = "../cumulus/pallets/solo-to-para" [dependencies.cumulus-pallet-xcm] +path = "../cumulus/pallets/xcm" default-features = false optional = true -path = "../cumulus/pallets/xcm" [dependencies.cumulus-pallet-xcmp-queue] +path = "../cumulus/pallets/xcmp-queue" default-features = false optional = true -path = "../cumulus/pallets/xcmp-queue" [dependencies.cumulus-ping] +path = "../cumulus/parachains/pallets/ping" default-features = false optional = true -path = "../cumulus/parachains/pallets/ping" [dependencies.cumulus-primitives-aura] +path = "../cumulus/primitives/aura" default-features = false optional = true -path = "../cumulus/primitives/aura" [dependencies.cumulus-primitives-core] +path = "../cumulus/primitives/core" default-features = false optional = true -path = "../cumulus/primitives/core" [dependencies.cumulus-primitives-parachain-inherent] +path = "../cumulus/primitives/parachain-inherent" default-features = false optional = true -path = "../cumulus/primitives/parachain-inherent" [dependencies.cumulus-primitives-proof-size-hostfunction] +path = "../cumulus/primitives/proof-size-hostfunction" default-features = false optional = true -path = "../cumulus/primitives/proof-size-hostfunction" [dependencies.cumulus-primitives-storage-weight-reclaim] +path = "../cumulus/primitives/storage-weight-reclaim" default-features = false optional = true -path = "../cumulus/primitives/storage-weight-reclaim" [dependencies.cumulus-primitives-timestamp] +path = "../cumulus/primitives/timestamp" default-features = false optional = true -path = "../cumulus/primitives/timestamp" [dependencies.cumulus-primitives-utility] +path = "../cumulus/primitives/utility" default-features = false optional = true -path = "../cumulus/primitives/utility" [dependencies.frame-benchmarking] +path = "../substrate/frame/benchmarking" default-features = false optional = true -path = "../substrate/frame/benchmarking" [dependencies.frame-benchmarking-pallet-pov] +path = "../substrate/frame/benchmarking/pov" default-features = false optional = true -path = "../substrate/frame/benchmarking/pov" [dependencies.frame-election-provider-solution-type] +path = "../substrate/frame/election-provider-support/solution-type" default-features = false optional = true -path = "../substrate/frame/election-provider-support/solution-type" [dependencies.frame-election-provider-support] +path = "../substrate/frame/election-provider-support" default-features = false optional = true -path = "../substrate/frame/election-provider-support" [dependencies.frame-executive] +path = "../substrate/frame/executive" default-features = false optional = true -path = "../substrate/frame/executive" [dependencies.frame-metadata-hash-extension] +path = "../substrate/frame/metadata-hash-extension" default-features = false optional = true -path = "../substrate/frame/metadata-hash-extension" [dependencies.frame-support] +path = "../substrate/frame/support" default-features = false optional = true -path = "../substrate/frame/support" [dependencies.frame-support-procedural] +path = "../substrate/frame/support/procedural" default-features = false optional = true -path = "../substrate/frame/support/procedural" [dependencies.frame-support-procedural-tools-derive] +path = "../substrate/frame/support/procedural/tools/derive" default-features = false optional = true -path = "../substrate/frame/support/procedural/tools/derive" [dependencies.frame-system] +path = "../substrate/frame/system" default-features = false optional = true -path = "../substrate/frame/system" [dependencies.frame-system-benchmarking] +path = "../substrate/frame/system/benchmarking" default-features = false optional = true -path = "../substrate/frame/system/benchmarking" [dependencies.frame-system-rpc-runtime-api] +path = "../substrate/frame/system/rpc/runtime-api" default-features = false optional = true -path = "../substrate/frame/system/rpc/runtime-api" [dependencies.frame-try-runtime] +path = "../substrate/frame/try-runtime" default-features = false optional = true -path = "../substrate/frame/try-runtime" [dependencies.pallet-alliance] +path = "../substrate/frame/alliance" default-features = false optional = true -path = "../substrate/frame/alliance" [dependencies.pallet-asset-conversion] +path = "../substrate/frame/asset-conversion" default-features = false optional = true -path = "../substrate/frame/asset-conversion" [dependencies.pallet-asset-conversion-ops] +path = "../substrate/frame/asset-conversion/ops" default-features = false optional = true -path = "../substrate/frame/asset-conversion/ops" [dependencies.pallet-asset-conversion-tx-payment] +path = "../substrate/frame/transaction-payment/asset-conversion-tx-payment" default-features = false optional = true -path = "../substrate/frame/transaction-payment/asset-conversion-tx-payment" [dependencies.pallet-asset-rate] +path = "../substrate/frame/asset-rate" default-features = false optional = true -path = "../substrate/frame/asset-rate" [dependencies.pallet-asset-tx-payment] +path = "../substrate/frame/transaction-payment/asset-tx-payment" default-features = false optional = true -path = "../substrate/frame/transaction-payment/asset-tx-payment" [dependencies.pallet-assets] +path = "../substrate/frame/assets" default-features = false optional = true -path = "../substrate/frame/assets" [dependencies.pallet-assets-freezer] +path = "../substrate/frame/assets-freezer" default-features = false optional = true -path = "../substrate/frame/assets-freezer" [dependencies.pallet-assets-holder] +path = "../substrate/frame/assets-holder" default-features = false optional = true -path = "../substrate/frame/assets-holder" [dependencies.pallet-atomic-swap] +path = "../substrate/frame/atomic-swap" default-features = false optional = true -path = "../substrate/frame/atomic-swap" [dependencies.pallet-aura] +path = "../substrate/frame/aura" default-features = false optional = true -path = "../substrate/frame/aura" [dependencies.pallet-authority-discovery] +path = "../substrate/frame/authority-discovery" default-features = false optional = true -path = "../substrate/frame/authority-discovery" [dependencies.pallet-authorship] +path = "../substrate/frame/authorship" default-features = false optional = true -path = "../substrate/frame/authorship" [dependencies.pallet-babe] +path = "../substrate/frame/babe" default-features = false optional = true -path = "../substrate/frame/babe" [dependencies.pallet-bags-list] +path = "../substrate/frame/bags-list" default-features = false optional = true -path = "../substrate/frame/bags-list" [dependencies.pallet-balances] +path = "../substrate/frame/balances" default-features = false optional = true -path = "../substrate/frame/balances" [dependencies.pallet-beefy] +path = "../substrate/frame/beefy" default-features = false optional = true -path = "../substrate/frame/beefy" [dependencies.pallet-beefy-mmr] +path = "../substrate/frame/beefy-mmr" default-features = false optional = true -path = "../substrate/frame/beefy-mmr" [dependencies.pallet-bounties] +path = "../substrate/frame/bounties" default-features = false optional = true -path = "../substrate/frame/bounties" [dependencies.pallet-bridge-grandpa] +path = "../bridges/modules/grandpa" default-features = false optional = true -path = "../bridges/modules/grandpa" [dependencies.pallet-bridge-messages] +path = "../bridges/modules/messages" default-features = false optional = true -path = "../bridges/modules/messages" [dependencies.pallet-bridge-parachains] +path = "../bridges/modules/parachains" default-features = false optional = true -path = "../bridges/modules/parachains" [dependencies.pallet-bridge-relayers] +path = "../bridges/modules/relayers" default-features = false optional = true -path = "../bridges/modules/relayers" [dependencies.pallet-broker] +path = "../substrate/frame/broker" default-features = false optional = true -path = "../substrate/frame/broker" [dependencies.pallet-child-bounties] +path = "../substrate/frame/child-bounties" default-features = false optional = true -path = "../substrate/frame/child-bounties" [dependencies.pallet-collator-selection] +path = "../cumulus/pallets/collator-selection" default-features = false optional = true -path = "../cumulus/pallets/collator-selection" [dependencies.pallet-collective] +path = "../substrate/frame/collective" default-features = false optional = true -path = "../substrate/frame/collective" [dependencies.pallet-collective-content] +path = "../cumulus/parachains/pallets/collective-content" default-features = false optional = true -path = "../cumulus/parachains/pallets/collective-content" [dependencies.pallet-contracts] +path = "../substrate/frame/contracts" default-features = false optional = true -path = "../substrate/frame/contracts" [dependencies.pallet-contracts-proc-macro] +path = "../substrate/frame/contracts/proc-macro" default-features = false optional = true -path = "../substrate/frame/contracts/proc-macro" [dependencies.pallet-contracts-uapi] +path = "../substrate/frame/contracts/uapi" default-features = false optional = true -path = "../substrate/frame/contracts/uapi" [dependencies.pallet-conviction-voting] +path = "../substrate/frame/conviction-voting" default-features = false optional = true -path = "../substrate/frame/conviction-voting" [dependencies.pallet-core-fellowship] +path = "../substrate/frame/core-fellowship" default-features = false optional = true -path = "../substrate/frame/core-fellowship" [dependencies.pallet-delegated-staking] +path = "../substrate/frame/delegated-staking" default-features = false optional = true -path = "../substrate/frame/delegated-staking" [dependencies.pallet-democracy] +path = "../substrate/frame/democracy" default-features = false optional = true -path = "../substrate/frame/democracy" [dependencies.pallet-dev-mode] +path = "../substrate/frame/examples/dev-mode" default-features = false optional = true -path = "../substrate/frame/examples/dev-mode" [dependencies.pallet-election-provider-multi-phase] +path = "../substrate/frame/election-provider-multi-phase" default-features = false optional = true -path = "../substrate/frame/election-provider-multi-phase" [dependencies.pallet-election-provider-support-benchmarking] +path = "../substrate/frame/election-provider-support/benchmarking" default-features = false optional = true -path = "../substrate/frame/election-provider-support/benchmarking" [dependencies.pallet-elections-phragmen] +path = "../substrate/frame/elections-phragmen" default-features = false optional = true -path = "../substrate/frame/elections-phragmen" [dependencies.pallet-fast-unstake] +path = "../substrate/frame/fast-unstake" default-features = false optional = true -path = "../substrate/frame/fast-unstake" [dependencies.pallet-glutton] +path = "../substrate/frame/glutton" default-features = false optional = true -path = "../substrate/frame/glutton" [dependencies.pallet-grandpa] +path = "../substrate/frame/grandpa" default-features = false optional = true -path = "../substrate/frame/grandpa" [dependencies.pallet-identity] +path = "../substrate/frame/identity" default-features = false optional = true -path = "../substrate/frame/identity" [dependencies.pallet-im-online] +path = "../substrate/frame/im-online" default-features = false optional = true -path = "../substrate/frame/im-online" [dependencies.pallet-indices] +path = "../substrate/frame/indices" default-features = false optional = true -path = "../substrate/frame/indices" [dependencies.pallet-insecure-randomness-collective-flip] +path = "../substrate/frame/insecure-randomness-collective-flip" default-features = false optional = true -path = "../substrate/frame/insecure-randomness-collective-flip" [dependencies.pallet-lottery] +path = "../substrate/frame/lottery" default-features = false optional = true -path = "../substrate/frame/lottery" [dependencies.pallet-membership] +path = "../substrate/frame/membership" default-features = false optional = true -path = "../substrate/frame/membership" [dependencies.pallet-message-queue] +path = "../substrate/frame/message-queue" default-features = false optional = true -path = "../substrate/frame/message-queue" [dependencies.pallet-migrations] +path = "../substrate/frame/migrations" default-features = false optional = true -path = "../substrate/frame/migrations" [dependencies.pallet-mixnet] +path = "../substrate/frame/mixnet" default-features = false optional = true -path = "../substrate/frame/mixnet" [dependencies.pallet-mmr] +path = "../substrate/frame/merkle-mountain-range" default-features = false optional = true -path = "../substrate/frame/merkle-mountain-range" [dependencies.pallet-multisig] +path = "../substrate/frame/multisig" default-features = false optional = true -path = "../substrate/frame/multisig" [dependencies.pallet-nft-fractionalization] +path = "../substrate/frame/nft-fractionalization" default-features = false optional = true -path = "../substrate/frame/nft-fractionalization" [dependencies.pallet-nfts] +path = "../substrate/frame/nfts" default-features = false optional = true -path = "../substrate/frame/nfts" [dependencies.pallet-nfts-runtime-api] +path = "../substrate/frame/nfts/runtime-api" default-features = false optional = true -path = "../substrate/frame/nfts/runtime-api" [dependencies.pallet-nis] +path = "../substrate/frame/nis" default-features = false optional = true -path = "../substrate/frame/nis" [dependencies.pallet-node-authorization] +path = "../substrate/frame/node-authorization" default-features = false optional = true -path = "../substrate/frame/node-authorization" [dependencies.pallet-nomination-pools] +path = "../substrate/frame/nomination-pools" default-features = false optional = true -path = "../substrate/frame/nomination-pools" [dependencies.pallet-nomination-pools-benchmarking] +path = "../substrate/frame/nomination-pools/benchmarking" default-features = false optional = true -path = "../substrate/frame/nomination-pools/benchmarking" [dependencies.pallet-nomination-pools-runtime-api] +path = "../substrate/frame/nomination-pools/runtime-api" default-features = false optional = true -path = "../substrate/frame/nomination-pools/runtime-api" [dependencies.pallet-offences] +path = "../substrate/frame/offences" default-features = false optional = true -path = "../substrate/frame/offences" [dependencies.pallet-offences-benchmarking] +path = "../substrate/frame/offences/benchmarking" default-features = false optional = true -path = "../substrate/frame/offences/benchmarking" [dependencies.pallet-paged-list] +path = "../substrate/frame/paged-list" default-features = false optional = true -path = "../substrate/frame/paged-list" [dependencies.pallet-parameters] +path = "../substrate/frame/parameters" default-features = false optional = true -path = "../substrate/frame/parameters" [dependencies.pallet-preimage] +path = "../substrate/frame/preimage" default-features = false optional = true -path = "../substrate/frame/preimage" [dependencies.pallet-proxy] +path = "../substrate/frame/proxy" default-features = false optional = true -path = "../substrate/frame/proxy" [dependencies.pallet-ranked-collective] +path = "../substrate/frame/ranked-collective" default-features = false optional = true -path = "../substrate/frame/ranked-collective" [dependencies.pallet-recovery] +path = "../substrate/frame/recovery" default-features = false optional = true -path = "../substrate/frame/recovery" [dependencies.pallet-referenda] +path = "../substrate/frame/referenda" default-features = false optional = true -path = "../substrate/frame/referenda" [dependencies.pallet-remark] +path = "../substrate/frame/remark" default-features = false optional = true -path = "../substrate/frame/remark" [dependencies.pallet-revive] +path = "../substrate/frame/revive" default-features = false optional = true -path = "../substrate/frame/revive" [dependencies.pallet-revive-fixtures] +path = "../substrate/frame/revive/fixtures" default-features = false optional = true -path = "../substrate/frame/revive/fixtures" [dependencies.pallet-revive-proc-macro] +path = "../substrate/frame/revive/proc-macro" default-features = false optional = true -path = "../substrate/frame/revive/proc-macro" [dependencies.pallet-revive-uapi] +path = "../substrate/frame/revive/uapi" default-features = false optional = true -path = "../substrate/frame/revive/uapi" [dependencies.pallet-root-offences] +path = "../substrate/frame/root-offences" default-features = false optional = true -path = "../substrate/frame/root-offences" [dependencies.pallet-root-testing] +path = "../substrate/frame/root-testing" default-features = false optional = true -path = "../substrate/frame/root-testing" [dependencies.pallet-safe-mode] +path = "../substrate/frame/safe-mode" default-features = false optional = true -path = "../substrate/frame/safe-mode" [dependencies.pallet-salary] +path = "../substrate/frame/salary" default-features = false optional = true -path = "../substrate/frame/salary" [dependencies.pallet-scheduler] +path = "../substrate/frame/scheduler" default-features = false optional = true -path = "../substrate/frame/scheduler" [dependencies.pallet-scored-pool] +path = "../substrate/frame/scored-pool" default-features = false optional = true -path = "../substrate/frame/scored-pool" [dependencies.pallet-session] +path = "../substrate/frame/session" default-features = false optional = true -path = "../substrate/frame/session" [dependencies.pallet-session-benchmarking] +path = "../substrate/frame/session/benchmarking" default-features = false optional = true -path = "../substrate/frame/session/benchmarking" [dependencies.pallet-skip-feeless-payment] +path = "../substrate/frame/transaction-payment/skip-feeless-payment" default-features = false optional = true -path = "../substrate/frame/transaction-payment/skip-feeless-payment" [dependencies.pallet-society] +path = "../substrate/frame/society" default-features = false optional = true -path = "../substrate/frame/society" [dependencies.pallet-staking] +path = "../substrate/frame/staking" default-features = false optional = true -path = "../substrate/frame/staking" [dependencies.pallet-staking-reward-curve] +path = "../substrate/frame/staking/reward-curve" default-features = false optional = true -path = "../substrate/frame/staking/reward-curve" [dependencies.pallet-staking-reward-fn] +path = "../substrate/frame/staking/reward-fn" default-features = false optional = true -path = "../substrate/frame/staking/reward-fn" [dependencies.pallet-staking-runtime-api] +path = "../substrate/frame/staking/runtime-api" default-features = false optional = true -path = "../substrate/frame/staking/runtime-api" [dependencies.pallet-state-trie-migration] +path = "../substrate/frame/state-trie-migration" default-features = false optional = true -path = "../substrate/frame/state-trie-migration" [dependencies.pallet-statement] +path = "../substrate/frame/statement" default-features = false optional = true -path = "../substrate/frame/statement" [dependencies.pallet-sudo] +path = "../substrate/frame/sudo" default-features = false optional = true -path = "../substrate/frame/sudo" [dependencies.pallet-timestamp] +path = "../substrate/frame/timestamp" default-features = false optional = true -path = "../substrate/frame/timestamp" [dependencies.pallet-tips] +path = "../substrate/frame/tips" default-features = false optional = true -path = "../substrate/frame/tips" [dependencies.pallet-transaction-payment] +path = "../substrate/frame/transaction-payment" default-features = false optional = true -path = "../substrate/frame/transaction-payment" [dependencies.pallet-transaction-payment-rpc-runtime-api] +path = "../substrate/frame/transaction-payment/rpc/runtime-api" default-features = false optional = true -path = "../substrate/frame/transaction-payment/rpc/runtime-api" [dependencies.pallet-transaction-storage] +path = "../substrate/frame/transaction-storage" default-features = false optional = true -path = "../substrate/frame/transaction-storage" [dependencies.pallet-treasury] +path = "../substrate/frame/treasury" default-features = false optional = true -path = "../substrate/frame/treasury" [dependencies.pallet-tx-pause] +path = "../substrate/frame/tx-pause" default-features = false optional = true -path = "../substrate/frame/tx-pause" [dependencies.pallet-uniques] +path = "../substrate/frame/uniques" default-features = false optional = true -path = "../substrate/frame/uniques" [dependencies.pallet-utility] +path = "../substrate/frame/utility" default-features = false optional = true -path = "../substrate/frame/utility" [dependencies.pallet-vesting] +path = "../substrate/frame/vesting" default-features = false optional = true -path = "../substrate/frame/vesting" [dependencies.pallet-whitelist] +path = "../substrate/frame/whitelist" default-features = false optional = true -path = "../substrate/frame/whitelist" [dependencies.pallet-xcm] +path = "../polkadot/xcm/pallet-xcm" default-features = false optional = true -path = "../polkadot/xcm/pallet-xcm" [dependencies.pallet-xcm-benchmarks] +path = "../polkadot/xcm/pallet-xcm-benchmarks" default-features = false optional = true -path = "../polkadot/xcm/pallet-xcm-benchmarks" [dependencies.pallet-xcm-bridge-hub] +path = "../bridges/modules/xcm-bridge-hub" default-features = false optional = true -path = "../bridges/modules/xcm-bridge-hub" [dependencies.pallet-xcm-bridge-hub-router] +path = "../bridges/modules/xcm-bridge-hub-router" default-features = false optional = true -path = "../bridges/modules/xcm-bridge-hub-router" [dependencies.parachains-common] +path = "../cumulus/parachains/common" default-features = false optional = true -path = "../cumulus/parachains/common" [dependencies.polkadot-core-primitives] +path = "../polkadot/core-primitives" default-features = false optional = true -path = "../polkadot/core-primitives" [dependencies.polkadot-parachain-primitives] +path = "../polkadot/parachain" default-features = false optional = true -path = "../polkadot/parachain" [dependencies.polkadot-primitives] +path = "../polkadot/primitives" default-features = false optional = true -path = "../polkadot/primitives" [dependencies.polkadot-runtime-common] +path = "../polkadot/runtime/common" default-features = false optional = true -path = "../polkadot/runtime/common" [dependencies.polkadot-runtime-metrics] +path = "../polkadot/runtime/metrics" default-features = false optional = true -path = "../polkadot/runtime/metrics" [dependencies.polkadot-runtime-parachains] +path = "../polkadot/runtime/parachains" default-features = false optional = true -path = "../polkadot/runtime/parachains" [dependencies.polkadot-sdk-frame] +path = "../substrate/frame" default-features = false optional = true -path = "../substrate/frame" [dependencies.sc-chain-spec-derive] +path = "../substrate/client/chain-spec/derive" default-features = false optional = true -path = "../substrate/client/chain-spec/derive" [dependencies.sc-tracing-proc-macro] +path = "../substrate/client/tracing/proc-macro" default-features = false optional = true -path = "../substrate/client/tracing/proc-macro" [dependencies.slot-range-helper] +path = "../polkadot/runtime/common/slot_range_helper" default-features = false optional = true -path = "../polkadot/runtime/common/slot_range_helper" [dependencies.snowbridge-beacon-primitives] +path = "../bridges/snowbridge/primitives/beacon" default-features = false optional = true -path = "../bridges/snowbridge/primitives/beacon" [dependencies.snowbridge-core] +path = "../bridges/snowbridge/primitives/core" default-features = false optional = true -path = "../bridges/snowbridge/primitives/core" [dependencies.snowbridge-ethereum] +path = "../bridges/snowbridge/primitives/ethereum" default-features = false optional = true -path = "../bridges/snowbridge/primitives/ethereum" [dependencies.snowbridge-outbound-queue-merkle-tree] +path = "../bridges/snowbridge/pallets/outbound-queue/merkle-tree" default-features = false optional = true -path = "../bridges/snowbridge/pallets/outbound-queue/merkle-tree" [dependencies.snowbridge-outbound-queue-runtime-api] +path = "../bridges/snowbridge/pallets/outbound-queue/runtime-api" default-features = false optional = true -path = "../bridges/snowbridge/pallets/outbound-queue/runtime-api" [dependencies.snowbridge-pallet-ethereum-client] +path = "../bridges/snowbridge/pallets/ethereum-client" default-features = false optional = true -path = "../bridges/snowbridge/pallets/ethereum-client" [dependencies.snowbridge-pallet-ethereum-client-fixtures] +path = "../bridges/snowbridge/pallets/ethereum-client/fixtures" default-features = false optional = true -path = "../bridges/snowbridge/pallets/ethereum-client/fixtures" [dependencies.snowbridge-pallet-inbound-queue] +path = "../bridges/snowbridge/pallets/inbound-queue" default-features = false optional = true -path = "../bridges/snowbridge/pallets/inbound-queue" [dependencies.snowbridge-pallet-inbound-queue-fixtures] +path = "../bridges/snowbridge/pallets/inbound-queue/fixtures" default-features = false optional = true -path = "../bridges/snowbridge/pallets/inbound-queue/fixtures" [dependencies.snowbridge-pallet-outbound-queue] +path = "../bridges/snowbridge/pallets/outbound-queue" default-features = false optional = true -path = "../bridges/snowbridge/pallets/outbound-queue" [dependencies.snowbridge-pallet-system] +path = "../bridges/snowbridge/pallets/system" default-features = false optional = true -path = "../bridges/snowbridge/pallets/system" [dependencies.snowbridge-router-primitives] +path = "../bridges/snowbridge/primitives/router" default-features = false optional = true -path = "../bridges/snowbridge/primitives/router" [dependencies.snowbridge-runtime-common] +path = "../bridges/snowbridge/runtime/runtime-common" default-features = false optional = true -path = "../bridges/snowbridge/runtime/runtime-common" [dependencies.snowbridge-system-runtime-api] +path = "../bridges/snowbridge/pallets/system/runtime-api" default-features = false optional = true -path = "../bridges/snowbridge/pallets/system/runtime-api" [dependencies.sp-api] +path = "../substrate/primitives/api" default-features = false optional = true -path = "../substrate/primitives/api" [dependencies.sp-api-proc-macro] +path = "../substrate/primitives/api/proc-macro" default-features = false optional = true -path = "../substrate/primitives/api/proc-macro" [dependencies.sp-application-crypto] +path = "../substrate/primitives/application-crypto" default-features = false optional = true -path = "../substrate/primitives/application-crypto" [dependencies.sp-arithmetic] +path = "../substrate/primitives/arithmetic" default-features = false optional = true -path = "../substrate/primitives/arithmetic" [dependencies.sp-authority-discovery] +path = "../substrate/primitives/authority-discovery" default-features = false optional = true -path = "../substrate/primitives/authority-discovery" [dependencies.sp-block-builder] +path = "../substrate/primitives/block-builder" default-features = false optional = true -path = "../substrate/primitives/block-builder" [dependencies.sp-consensus-aura] +path = "../substrate/primitives/consensus/aura" default-features = false optional = true -path = "../substrate/primitives/consensus/aura" [dependencies.sp-consensus-babe] +path = "../substrate/primitives/consensus/babe" default-features = false optional = true -path = "../substrate/primitives/consensus/babe" [dependencies.sp-consensus-beefy] +path = "../substrate/primitives/consensus/beefy" default-features = false optional = true -path = "../substrate/primitives/consensus/beefy" [dependencies.sp-consensus-grandpa] +path = "../substrate/primitives/consensus/grandpa" default-features = false optional = true -path = "../substrate/primitives/consensus/grandpa" [dependencies.sp-consensus-pow] +path = "../substrate/primitives/consensus/pow" default-features = false optional = true -path = "../substrate/primitives/consensus/pow" [dependencies.sp-consensus-slots] +path = "../substrate/primitives/consensus/slots" default-features = false optional = true -path = "../substrate/primitives/consensus/slots" [dependencies.sp-core] +path = "../substrate/primitives/core" default-features = false optional = true -path = "../substrate/primitives/core" [dependencies.sp-crypto-ec-utils] +path = "../substrate/primitives/crypto/ec-utils" default-features = false optional = true -path = "../substrate/primitives/crypto/ec-utils" [dependencies.sp-crypto-hashing] +path = "../substrate/primitives/crypto/hashing" default-features = false optional = true -path = "../substrate/primitives/crypto/hashing" [dependencies.sp-crypto-hashing-proc-macro] +path = "../substrate/primitives/crypto/hashing/proc-macro" default-features = false optional = true -path = "../substrate/primitives/crypto/hashing/proc-macro" [dependencies.sp-debug-derive] +path = "../substrate/primitives/debug-derive" default-features = false optional = true -path = "../substrate/primitives/debug-derive" [dependencies.sp-externalities] +path = "../substrate/primitives/externalities" default-features = false optional = true -path = "../substrate/primitives/externalities" [dependencies.sp-genesis-builder] +path = "../substrate/primitives/genesis-builder" default-features = false optional = true -path = "../substrate/primitives/genesis-builder" [dependencies.sp-inherents] +path = "../substrate/primitives/inherents" default-features = false optional = true -path = "../substrate/primitives/inherents" [dependencies.sp-io] +path = "../substrate/primitives/io" default-features = false optional = true -path = "../substrate/primitives/io" [dependencies.sp-keyring] +path = "../substrate/primitives/keyring" default-features = false optional = true -path = "../substrate/primitives/keyring" [dependencies.sp-keystore] +path = "../substrate/primitives/keystore" default-features = false optional = true -path = "../substrate/primitives/keystore" [dependencies.sp-metadata-ir] +path = "../substrate/primitives/metadata-ir" default-features = false optional = true -path = "../substrate/primitives/metadata-ir" [dependencies.sp-mixnet] +path = "../substrate/primitives/mixnet" default-features = false optional = true -path = "../substrate/primitives/mixnet" [dependencies.sp-mmr-primitives] +path = "../substrate/primitives/merkle-mountain-range" default-features = false optional = true -path = "../substrate/primitives/merkle-mountain-range" [dependencies.sp-npos-elections] +path = "../substrate/primitives/npos-elections" default-features = false optional = true -path = "../substrate/primitives/npos-elections" [dependencies.sp-offchain] +path = "../substrate/primitives/offchain" default-features = false optional = true -path = "../substrate/primitives/offchain" [dependencies.sp-runtime] +path = "../substrate/primitives/runtime" default-features = false optional = true -path = "../substrate/primitives/runtime" [dependencies.sp-runtime-interface] +path = "../substrate/primitives/runtime-interface" default-features = false optional = true -path = "../substrate/primitives/runtime-interface" [dependencies.sp-runtime-interface-proc-macro] +path = "../substrate/primitives/runtime-interface/proc-macro" default-features = false optional = true -path = "../substrate/primitives/runtime-interface/proc-macro" [dependencies.sp-session] +path = "../substrate/primitives/session" default-features = false optional = true -path = "../substrate/primitives/session" [dependencies.sp-staking] +path = "../substrate/primitives/staking" default-features = false optional = true -path = "../substrate/primitives/staking" [dependencies.sp-state-machine] +path = "../substrate/primitives/state-machine" default-features = false optional = true -path = "../substrate/primitives/state-machine" [dependencies.sp-statement-store] +path = "../substrate/primitives/statement-store" default-features = false optional = true -path = "../substrate/primitives/statement-store" [dependencies.sp-std] +path = "../substrate/primitives/std" default-features = false optional = true -path = "../substrate/primitives/std" [dependencies.sp-storage] +path = "../substrate/primitives/storage" default-features = false optional = true -path = "../substrate/primitives/storage" [dependencies.sp-timestamp] +path = "../substrate/primitives/timestamp" default-features = false optional = true -path = "../substrate/primitives/timestamp" [dependencies.sp-tracing] +path = "../substrate/primitives/tracing" default-features = false optional = true -path = "../substrate/primitives/tracing" [dependencies.sp-transaction-pool] +path = "../substrate/primitives/transaction-pool" default-features = false optional = true -path = "../substrate/primitives/transaction-pool" [dependencies.sp-transaction-storage-proof] +path = "../substrate/primitives/transaction-storage-proof" default-features = false optional = true -path = "../substrate/primitives/transaction-storage-proof" [dependencies.sp-trie] +path = "../substrate/primitives/trie" default-features = false optional = true -path = "../substrate/primitives/trie" [dependencies.sp-version] +path = "../substrate/primitives/version" default-features = false optional = true -path = "../substrate/primitives/version" [dependencies.sp-version-proc-macro] +path = "../substrate/primitives/version/proc-macro" default-features = false optional = true -path = "../substrate/primitives/version/proc-macro" [dependencies.sp-wasm-interface] +path = "../substrate/primitives/wasm-interface" default-features = false optional = true -path = "../substrate/primitives/wasm-interface" [dependencies.sp-weights] +path = "../substrate/primitives/weights" default-features = false optional = true -path = "../substrate/primitives/weights" [dependencies.staging-parachain-info] +path = "../cumulus/parachains/pallets/parachain-info" default-features = false optional = true -path = "../cumulus/parachains/pallets/parachain-info" [dependencies.staging-xcm] +path = "../polkadot/xcm" default-features = false optional = true -path = "../polkadot/xcm" [dependencies.staging-xcm-builder] +path = "../polkadot/xcm/xcm-builder" default-features = false optional = true -path = "../polkadot/xcm/xcm-builder" [dependencies.staging-xcm-executor] +path = "../polkadot/xcm/xcm-executor" default-features = false optional = true -path = "../polkadot/xcm/xcm-executor" [dependencies.substrate-bip39] +path = "../substrate/utils/substrate-bip39" default-features = false optional = true -path = "../substrate/utils/substrate-bip39" [dependencies.testnet-parachains-constants] +path = "../cumulus/parachains/runtimes/constants" default-features = false optional = true -path = "../cumulus/parachains/runtimes/constants" [dependencies.tracing-gum-proc-macro] +path = "../polkadot/node/gum/proc-macro" default-features = false optional = true -path = "../polkadot/node/gum/proc-macro" [dependencies.xcm-procedural] +path = "../polkadot/xcm/procedural" default-features = false optional = true -path = "../polkadot/xcm/procedural" [dependencies.xcm-runtime-apis] +path = "../polkadot/xcm/xcm-runtime-apis" default-features = false optional = true -path = "../polkadot/xcm/xcm-runtime-apis" [dependencies.asset-test-utils] +path = "../cumulus/parachains/runtimes/assets/test-utils" default-features = false optional = true -path = "../cumulus/parachains/runtimes/assets/test-utils" [dependencies.bridge-hub-test-utils] +path = "../cumulus/parachains/runtimes/bridge-hubs/test-utils" default-features = false optional = true -path = "../cumulus/parachains/runtimes/bridge-hubs/test-utils" [dependencies.cumulus-client-cli] +path = "../cumulus/client/cli" default-features = false optional = true -path = "../cumulus/client/cli" [dependencies.cumulus-client-collator] +path = "../cumulus/client/collator" default-features = false optional = true -path = "../cumulus/client/collator" [dependencies.cumulus-client-consensus-aura] +path = "../cumulus/client/consensus/aura" default-features = false optional = true -path = "../cumulus/client/consensus/aura" [dependencies.cumulus-client-consensus-common] +path = "../cumulus/client/consensus/common" default-features = false optional = true -path = "../cumulus/client/consensus/common" [dependencies.cumulus-client-consensus-proposer] +path = "../cumulus/client/consensus/proposer" default-features = false optional = true -path = "../cumulus/client/consensus/proposer" [dependencies.cumulus-client-consensus-relay-chain] +path = "../cumulus/client/consensus/relay-chain" default-features = false optional = true -path = "../cumulus/client/consensus/relay-chain" [dependencies.cumulus-client-network] +path = "../cumulus/client/network" default-features = false optional = true -path = "../cumulus/client/network" [dependencies.cumulus-client-parachain-inherent] +path = "../cumulus/client/parachain-inherent" default-features = false optional = true -path = "../cumulus/client/parachain-inherent" [dependencies.cumulus-client-pov-recovery] +path = "../cumulus/client/pov-recovery" default-features = false optional = true -path = "../cumulus/client/pov-recovery" [dependencies.cumulus-client-service] +path = "../cumulus/client/service" default-features = false optional = true -path = "../cumulus/client/service" [dependencies.cumulus-relay-chain-inprocess-interface] +path = "../cumulus/client/relay-chain-inprocess-interface" default-features = false optional = true -path = "../cumulus/client/relay-chain-inprocess-interface" [dependencies.cumulus-relay-chain-interface] +path = "../cumulus/client/relay-chain-interface" default-features = false optional = true -path = "../cumulus/client/relay-chain-interface" [dependencies.cumulus-relay-chain-minimal-node] +path = "../cumulus/client/relay-chain-minimal-node" default-features = false optional = true -path = "../cumulus/client/relay-chain-minimal-node" [dependencies.cumulus-relay-chain-rpc-interface] +path = "../cumulus/client/relay-chain-rpc-interface" default-features = false optional = true -path = "../cumulus/client/relay-chain-rpc-interface" [dependencies.cumulus-test-relay-sproof-builder] +path = "../cumulus/test/relay-sproof-builder" default-features = false optional = true -path = "../cumulus/test/relay-sproof-builder" [dependencies.emulated-integration-tests-common] +path = "../cumulus/parachains/integration-tests/emulated/common" default-features = false optional = true -path = "../cumulus/parachains/integration-tests/emulated/common" [dependencies.fork-tree] +path = "../substrate/utils/fork-tree" default-features = false optional = true -path = "../substrate/utils/fork-tree" [dependencies.frame-benchmarking-cli] +path = "../substrate/utils/frame/benchmarking-cli" default-features = false optional = true -path = "../substrate/utils/frame/benchmarking-cli" [dependencies.frame-remote-externalities] +path = "../substrate/utils/frame/remote-externalities" default-features = false optional = true -path = "../substrate/utils/frame/remote-externalities" [dependencies.frame-support-procedural-tools] +path = "../substrate/frame/support/procedural/tools" default-features = false optional = true -path = "../substrate/frame/support/procedural/tools" [dependencies.generate-bags] +path = "../substrate/utils/frame/generate-bags" default-features = false optional = true -path = "../substrate/utils/frame/generate-bags" [dependencies.mmr-gadget] +path = "../substrate/client/merkle-mountain-range" default-features = false optional = true -path = "../substrate/client/merkle-mountain-range" [dependencies.mmr-rpc] +path = "../substrate/client/merkle-mountain-range/rpc" default-features = false optional = true -path = "../substrate/client/merkle-mountain-range/rpc" [dependencies.pallet-contracts-mock-network] +path = "../substrate/frame/contracts/mock-network" default-features = false optional = true -path = "../substrate/frame/contracts/mock-network" [dependencies.pallet-revive-mock-network] +path = "../substrate/frame/revive/mock-network" default-features = false optional = true -path = "../substrate/frame/revive/mock-network" [dependencies.pallet-transaction-payment-rpc] +path = "../substrate/frame/transaction-payment/rpc" default-features = false optional = true -path = "../substrate/frame/transaction-payment/rpc" [dependencies.parachains-runtimes-test-utils] +path = "../cumulus/parachains/runtimes/test-utils" default-features = false optional = true -path = "../cumulus/parachains/runtimes/test-utils" [dependencies.polkadot-approval-distribution] +path = "../polkadot/node/network/approval-distribution" default-features = false optional = true -path = "../polkadot/node/network/approval-distribution" [dependencies.polkadot-availability-bitfield-distribution] +path = "../polkadot/node/network/bitfield-distribution" default-features = false optional = true -path = "../polkadot/node/network/bitfield-distribution" [dependencies.polkadot-availability-distribution] +path = "../polkadot/node/network/availability-distribution" default-features = false optional = true -path = "../polkadot/node/network/availability-distribution" [dependencies.polkadot-availability-recovery] +path = "../polkadot/node/network/availability-recovery" default-features = false optional = true -path = "../polkadot/node/network/availability-recovery" [dependencies.polkadot-cli] +path = "../polkadot/cli" default-features = false optional = true -path = "../polkadot/cli" [dependencies.polkadot-collator-protocol] +path = "../polkadot/node/network/collator-protocol" default-features = false optional = true -path = "../polkadot/node/network/collator-protocol" [dependencies.polkadot-dispute-distribution] +path = "../polkadot/node/network/dispute-distribution" default-features = false optional = true -path = "../polkadot/node/network/dispute-distribution" [dependencies.polkadot-erasure-coding] +path = "../polkadot/erasure-coding" default-features = false optional = true -path = "../polkadot/erasure-coding" [dependencies.polkadot-gossip-support] +path = "../polkadot/node/network/gossip-support" default-features = false optional = true -path = "../polkadot/node/network/gossip-support" [dependencies.polkadot-network-bridge] +path = "../polkadot/node/network/bridge" default-features = false optional = true -path = "../polkadot/node/network/bridge" [dependencies.polkadot-node-collation-generation] +path = "../polkadot/node/collation-generation" default-features = false optional = true -path = "../polkadot/node/collation-generation" [dependencies.polkadot-node-core-approval-voting] +path = "../polkadot/node/core/approval-voting" default-features = false optional = true -path = "../polkadot/node/core/approval-voting" [dependencies.polkadot-node-core-av-store] +path = "../polkadot/node/core/av-store" default-features = false optional = true -path = "../polkadot/node/core/av-store" [dependencies.polkadot-node-core-backing] +path = "../polkadot/node/core/backing" default-features = false optional = true -path = "../polkadot/node/core/backing" [dependencies.polkadot-node-core-bitfield-signing] +path = "../polkadot/node/core/bitfield-signing" default-features = false optional = true -path = "../polkadot/node/core/bitfield-signing" [dependencies.polkadot-node-core-candidate-validation] +path = "../polkadot/node/core/candidate-validation" default-features = false optional = true -path = "../polkadot/node/core/candidate-validation" [dependencies.polkadot-node-core-chain-api] +path = "../polkadot/node/core/chain-api" default-features = false optional = true -path = "../polkadot/node/core/chain-api" [dependencies.polkadot-node-core-chain-selection] +path = "../polkadot/node/core/chain-selection" default-features = false optional = true -path = "../polkadot/node/core/chain-selection" [dependencies.polkadot-node-core-dispute-coordinator] +path = "../polkadot/node/core/dispute-coordinator" default-features = false optional = true -path = "../polkadot/node/core/dispute-coordinator" [dependencies.polkadot-node-core-parachains-inherent] +path = "../polkadot/node/core/parachains-inherent" default-features = false optional = true -path = "../polkadot/node/core/parachains-inherent" [dependencies.polkadot-node-core-prospective-parachains] +path = "../polkadot/node/core/prospective-parachains" default-features = false optional = true -path = "../polkadot/node/core/prospective-parachains" [dependencies.polkadot-node-core-provisioner] +path = "../polkadot/node/core/provisioner" default-features = false optional = true -path = "../polkadot/node/core/provisioner" [dependencies.polkadot-node-core-pvf] +path = "../polkadot/node/core/pvf" default-features = false optional = true -path = "../polkadot/node/core/pvf" [dependencies.polkadot-node-core-pvf-checker] +path = "../polkadot/node/core/pvf-checker" default-features = false optional = true -path = "../polkadot/node/core/pvf-checker" [dependencies.polkadot-node-core-pvf-common] +path = "../polkadot/node/core/pvf/common" default-features = false optional = true -path = "../polkadot/node/core/pvf/common" [dependencies.polkadot-node-core-pvf-execute-worker] +path = "../polkadot/node/core/pvf/execute-worker" default-features = false optional = true -path = "../polkadot/node/core/pvf/execute-worker" [dependencies.polkadot-node-core-pvf-prepare-worker] +path = "../polkadot/node/core/pvf/prepare-worker" default-features = false optional = true -path = "../polkadot/node/core/pvf/prepare-worker" [dependencies.polkadot-node-core-runtime-api] +path = "../polkadot/node/core/runtime-api" default-features = false optional = true -path = "../polkadot/node/core/runtime-api" [dependencies.polkadot-node-jaeger] +path = "../polkadot/node/jaeger" default-features = false optional = true -path = "../polkadot/node/jaeger" [dependencies.polkadot-node-metrics] +path = "../polkadot/node/metrics" default-features = false optional = true -path = "../polkadot/node/metrics" [dependencies.polkadot-node-network-protocol] +path = "../polkadot/node/network/protocol" default-features = false optional = true -path = "../polkadot/node/network/protocol" [dependencies.polkadot-node-primitives] +path = "../polkadot/node/primitives" default-features = false optional = true -path = "../polkadot/node/primitives" [dependencies.polkadot-node-subsystem] +path = "../polkadot/node/subsystem" default-features = false optional = true -path = "../polkadot/node/subsystem" [dependencies.polkadot-node-subsystem-types] +path = "../polkadot/node/subsystem-types" default-features = false optional = true -path = "../polkadot/node/subsystem-types" [dependencies.polkadot-node-subsystem-util] +path = "../polkadot/node/subsystem-util" default-features = false optional = true -path = "../polkadot/node/subsystem-util" [dependencies.polkadot-overseer] +path = "../polkadot/node/overseer" default-features = false optional = true -path = "../polkadot/node/overseer" [dependencies.polkadot-parachain-lib] +path = "../cumulus/polkadot-parachain/polkadot-parachain-lib" default-features = false optional = true -path = "../cumulus/polkadot-parachain/polkadot-parachain-lib" [dependencies.polkadot-rpc] +path = "../polkadot/rpc" default-features = false optional = true -path = "../polkadot/rpc" [dependencies.polkadot-service] +path = "../polkadot/node/service" default-features = false optional = true -path = "../polkadot/node/service" [dependencies.polkadot-statement-distribution] +path = "../polkadot/node/network/statement-distribution" default-features = false optional = true -path = "../polkadot/node/network/statement-distribution" [dependencies.polkadot-statement-table] +path = "../polkadot/statement-table" default-features = false optional = true -path = "../polkadot/statement-table" [dependencies.sc-allocator] +path = "../substrate/client/allocator" default-features = false optional = true -path = "../substrate/client/allocator" [dependencies.sc-authority-discovery] +path = "../substrate/client/authority-discovery" default-features = false optional = true -path = "../substrate/client/authority-discovery" [dependencies.sc-basic-authorship] +path = "../substrate/client/basic-authorship" default-features = false optional = true -path = "../substrate/client/basic-authorship" [dependencies.sc-block-builder] +path = "../substrate/client/block-builder" default-features = false optional = true -path = "../substrate/client/block-builder" [dependencies.sc-chain-spec] +path = "../substrate/client/chain-spec" default-features = false optional = true -path = "../substrate/client/chain-spec" [dependencies.sc-cli] +path = "../substrate/client/cli" default-features = false optional = true -path = "../substrate/client/cli" [dependencies.sc-client-api] +path = "../substrate/client/api" default-features = false optional = true -path = "../substrate/client/api" [dependencies.sc-client-db] +path = "../substrate/client/db" default-features = false optional = true -path = "../substrate/client/db" [dependencies.sc-consensus] +path = "../substrate/client/consensus/common" default-features = false optional = true -path = "../substrate/client/consensus/common" [dependencies.sc-consensus-aura] +path = "../substrate/client/consensus/aura" default-features = false optional = true -path = "../substrate/client/consensus/aura" [dependencies.sc-consensus-babe] +path = "../substrate/client/consensus/babe" default-features = false optional = true -path = "../substrate/client/consensus/babe" [dependencies.sc-consensus-babe-rpc] +path = "../substrate/client/consensus/babe/rpc" default-features = false optional = true -path = "../substrate/client/consensus/babe/rpc" [dependencies.sc-consensus-beefy] +path = "../substrate/client/consensus/beefy" default-features = false optional = true -path = "../substrate/client/consensus/beefy" [dependencies.sc-consensus-beefy-rpc] +path = "../substrate/client/consensus/beefy/rpc" default-features = false optional = true -path = "../substrate/client/consensus/beefy/rpc" [dependencies.sc-consensus-epochs] +path = "../substrate/client/consensus/epochs" default-features = false optional = true -path = "../substrate/client/consensus/epochs" [dependencies.sc-consensus-grandpa] +path = "../substrate/client/consensus/grandpa" default-features = false optional = true -path = "../substrate/client/consensus/grandpa" [dependencies.sc-consensus-grandpa-rpc] +path = "../substrate/client/consensus/grandpa/rpc" default-features = false optional = true -path = "../substrate/client/consensus/grandpa/rpc" [dependencies.sc-consensus-manual-seal] +path = "../substrate/client/consensus/manual-seal" default-features = false optional = true -path = "../substrate/client/consensus/manual-seal" [dependencies.sc-consensus-pow] +path = "../substrate/client/consensus/pow" default-features = false optional = true -path = "../substrate/client/consensus/pow" [dependencies.sc-consensus-slots] +path = "../substrate/client/consensus/slots" default-features = false optional = true -path = "../substrate/client/consensus/slots" [dependencies.sc-executor] +path = "../substrate/client/executor" default-features = false optional = true -path = "../substrate/client/executor" [dependencies.sc-executor-common] +path = "../substrate/client/executor/common" default-features = false optional = true -path = "../substrate/client/executor/common" [dependencies.sc-executor-polkavm] +path = "../substrate/client/executor/polkavm" default-features = false optional = true -path = "../substrate/client/executor/polkavm" [dependencies.sc-executor-wasmtime] +path = "../substrate/client/executor/wasmtime" default-features = false optional = true -path = "../substrate/client/executor/wasmtime" [dependencies.sc-informant] +path = "../substrate/client/informant" default-features = false optional = true -path = "../substrate/client/informant" [dependencies.sc-keystore] +path = "../substrate/client/keystore" default-features = false optional = true -path = "../substrate/client/keystore" [dependencies.sc-mixnet] +path = "../substrate/client/mixnet" default-features = false optional = true -path = "../substrate/client/mixnet" [dependencies.sc-network] +path = "../substrate/client/network" default-features = false optional = true -path = "../substrate/client/network" [dependencies.sc-network-common] +path = "../substrate/client/network/common" default-features = false optional = true -path = "../substrate/client/network/common" [dependencies.sc-network-gossip] +path = "../substrate/client/network-gossip" default-features = false optional = true -path = "../substrate/client/network-gossip" [dependencies.sc-network-light] +path = "../substrate/client/network/light" default-features = false optional = true -path = "../substrate/client/network/light" [dependencies.sc-network-statement] +path = "../substrate/client/network/statement" default-features = false optional = true -path = "../substrate/client/network/statement" [dependencies.sc-network-sync] +path = "../substrate/client/network/sync" default-features = false optional = true -path = "../substrate/client/network/sync" [dependencies.sc-network-transactions] +path = "../substrate/client/network/transactions" default-features = false optional = true -path = "../substrate/client/network/transactions" [dependencies.sc-network-types] +path = "../substrate/client/network/types" default-features = false optional = true -path = "../substrate/client/network/types" [dependencies.sc-offchain] +path = "../substrate/client/offchain" default-features = false optional = true -path = "../substrate/client/offchain" [dependencies.sc-proposer-metrics] +path = "../substrate/client/proposer-metrics" default-features = false optional = true -path = "../substrate/client/proposer-metrics" [dependencies.sc-rpc] +path = "../substrate/client/rpc" default-features = false optional = true -path = "../substrate/client/rpc" [dependencies.sc-rpc-api] +path = "../substrate/client/rpc-api" default-features = false optional = true -path = "../substrate/client/rpc-api" [dependencies.sc-rpc-server] +path = "../substrate/client/rpc-servers" default-features = false optional = true -path = "../substrate/client/rpc-servers" [dependencies.sc-rpc-spec-v2] +path = "../substrate/client/rpc-spec-v2" default-features = false optional = true -path = "../substrate/client/rpc-spec-v2" [dependencies.sc-service] +path = "../substrate/client/service" default-features = false optional = true -path = "../substrate/client/service" [dependencies.sc-state-db] +path = "../substrate/client/state-db" default-features = false optional = true -path = "../substrate/client/state-db" [dependencies.sc-statement-store] +path = "../substrate/client/statement-store" default-features = false optional = true -path = "../substrate/client/statement-store" [dependencies.sc-storage-monitor] +path = "../substrate/client/storage-monitor" default-features = false optional = true -path = "../substrate/client/storage-monitor" [dependencies.sc-sync-state-rpc] +path = "../substrate/client/sync-state-rpc" default-features = false optional = true -path = "../substrate/client/sync-state-rpc" [dependencies.sc-sysinfo] +path = "../substrate/client/sysinfo" default-features = false optional = true -path = "../substrate/client/sysinfo" [dependencies.sc-telemetry] +path = "../substrate/client/telemetry" default-features = false optional = true -path = "../substrate/client/telemetry" [dependencies.sc-tracing] +path = "../substrate/client/tracing" default-features = false optional = true -path = "../substrate/client/tracing" [dependencies.sc-transaction-pool] +path = "../substrate/client/transaction-pool" default-features = false optional = true -path = "../substrate/client/transaction-pool" [dependencies.sc-transaction-pool-api] +path = "../substrate/client/transaction-pool/api" default-features = false optional = true -path = "../substrate/client/transaction-pool/api" [dependencies.sc-utils] +path = "../substrate/client/utils" default-features = false optional = true -path = "../substrate/client/utils" [dependencies.snowbridge-runtime-test-common] +path = "../bridges/snowbridge/runtime/test-common" default-features = false optional = true -path = "../bridges/snowbridge/runtime/test-common" [dependencies.sp-blockchain] +path = "../substrate/primitives/blockchain" default-features = false optional = true -path = "../substrate/primitives/blockchain" [dependencies.sp-consensus] +path = "../substrate/primitives/consensus/common" default-features = false optional = true -path = "../substrate/primitives/consensus/common" [dependencies.sp-core-hashing] +path = "../substrate/deprecated/hashing" default-features = false optional = true -path = "../substrate/deprecated/hashing" [dependencies.sp-core-hashing-proc-macro] +path = "../substrate/deprecated/hashing/proc-macro" default-features = false optional = true -path = "../substrate/deprecated/hashing/proc-macro" [dependencies.sp-database] +path = "../substrate/primitives/database" default-features = false optional = true -path = "../substrate/primitives/database" [dependencies.sp-maybe-compressed-blob] +path = "../substrate/primitives/maybe-compressed-blob" default-features = false optional = true -path = "../substrate/primitives/maybe-compressed-blob" [dependencies.sp-panic-handler] +path = "../substrate/primitives/panic-handler" default-features = false optional = true -path = "../substrate/primitives/panic-handler" [dependencies.sp-rpc] +path = "../substrate/primitives/rpc" default-features = false optional = true -path = "../substrate/primitives/rpc" [dependencies.staging-chain-spec-builder] +path = "../substrate/bin/utils/chain-spec-builder" default-features = false optional = true -path = "../substrate/bin/utils/chain-spec-builder" [dependencies.staging-node-inspect] +path = "../substrate/bin/node/inspect" default-features = false optional = true -path = "../substrate/bin/node/inspect" [dependencies.staging-tracking-allocator] +path = "../polkadot/node/tracking-allocator" default-features = false optional = true -path = "../polkadot/node/tracking-allocator" [dependencies.subkey] +path = "../substrate/bin/utils/subkey" default-features = false optional = true -path = "../substrate/bin/utils/subkey" [dependencies.substrate-build-script-utils] +path = "../substrate/utils/build-script-utils" default-features = false optional = true -path = "../substrate/utils/build-script-utils" [dependencies.substrate-frame-rpc-support] +path = "../substrate/utils/frame/rpc/support" default-features = false optional = true -path = "../substrate/utils/frame/rpc/support" [dependencies.substrate-frame-rpc-system] +path = "../substrate/utils/frame/rpc/system" default-features = false optional = true -path = "../substrate/utils/frame/rpc/system" [dependencies.substrate-prometheus-endpoint] +path = "../substrate/utils/prometheus" default-features = false optional = true -path = "../substrate/utils/prometheus" [dependencies.substrate-rpc-client] +path = "../substrate/utils/frame/rpc/client" default-features = false optional = true -path = "../substrate/utils/frame/rpc/client" [dependencies.substrate-state-trie-migration-rpc] +path = "../substrate/utils/frame/rpc/state-trie-migration-rpc" default-features = false optional = true -path = "../substrate/utils/frame/rpc/state-trie-migration-rpc" [dependencies.substrate-wasm-builder] +path = "../substrate/utils/wasm-builder" default-features = false optional = true -path = "../substrate/utils/wasm-builder" [dependencies.tracing-gum] +path = "../polkadot/node/gum" default-features = false optional = true -path = "../polkadot/node/gum" [dependencies.xcm-emulator] +path = "../cumulus/xcm/xcm-emulator" default-features = false optional = true -path = "../cumulus/xcm/xcm-emulator" [dependencies.xcm-simulator] +path = "../polkadot/xcm/xcm-simulator" default-features = false optional = true -path = "../polkadot/xcm/xcm-simulator" [package.metadata.docs.rs] features = ["node", "runtime-full"] -targets = ["x86_64-unknown-linux-gnu"] +targets = ["x86_64-unknown-linux-gnu"] \ No newline at end of file From a80e6e32c1610857cf0e98d854ef8432cc86869a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Thu, 12 Sep 2024 22:49:43 -0500 Subject: [PATCH 40/59] fix(Cargo): revert unintended regression in Cargo lockfile --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2d22a56d08c8..ebbe7af20e84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16889,9 +16889,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.11.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608a5726529f2f0ef81b8fde9873c4bb829d6b5b5ca6be4d97345ddf0749c825" +checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", @@ -16913,9 +16913,9 @@ dependencies = [ [[package]] name = "ruint-macro" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rustc-demangle" From 668c148dce325048cde81409e541e621bede58da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Thu, 12 Sep 2024 23:04:27 -0500 Subject: [PATCH 41/59] fix(pallet-assets): missing documentation changes --- substrate/frame/assets/src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/substrate/frame/assets/src/lib.rs b/substrate/frame/assets/src/lib.rs index f7990e29c45b..9b3b43a8fcda 100644 --- a/substrate/frame/assets/src/lib.rs +++ b/substrate/frame/assets/src/lib.rs @@ -812,7 +812,7 @@ pub mod pallet { /// asset. /// /// It will fail with either [`Error::ContainsHolds`] or [`Error::ContainsFreezes`] if - /// an account has holds or freezes in place. + /// an account contains holds or freezes in place. #[pallet::call_index(2)] pub fn start_destroy(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { let maybe_check_owner = match T::ForceOrigin::try_origin(origin) { @@ -1627,6 +1627,9 @@ pub mod pallet { /// refunded. /// - `allow_burn`: If `true` then assets may be destroyed in order to complete the refund. /// + /// It will fail with either [`Error::ContainsHolds`] or [`Error::ContainsFreezes`] if + /// the asset account contains holds or freezes in place. + /// /// Emits `Refunded` event when successful. #[pallet::call_index(27)] #[pallet::weight(T::WeightInfo::refund())] @@ -1717,6 +1720,9 @@ pub mod pallet { /// - `id`: The identifier of the asset for the account holding a deposit. /// - `who`: The account to refund. /// + /// It will fail with either [`Error::ContainsHolds`] or [`Error::ContainsFreezes`] if + /// the asset account contains holds or freezes in place. + /// /// Emits `Refunded` event when successful. #[pallet::call_index(30)] #[pallet::weight(T::WeightInfo::refund_other())] From 4f2772537040ef7fd2eb7602d99fa9dfc8774ce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Fri, 13 Sep 2024 08:44:41 -0500 Subject: [PATCH 42/59] fix(pallet-assets): missing borrowing --- substrate/frame/assets/src/functions.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index b38cdf8e6874..ebebb4105edb 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -365,11 +365,11 @@ impl, I: 'static> Pallet { use ExistenceReason::*; ensure!( - T::Holder::balance_on_hold(id.clone(), who).is_none(), + T::Holder::balance_on_hold(id.clone(), &who).is_none(), Error::::ContainsHolds ); ensure!( - T::Freezer::frozen_balance(id.clone(), who).is_none(), + T::Freezer::frozen_balance(id.clone(), &who).is_none(), Error::::ContainsFreezes ); From dec2a4c9c38879680c21d54f8aaf6a49b0d5122a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Mon, 16 Sep 2024 09:10:41 -0500 Subject: [PATCH 43/59] change: resolve issues raised by ci checks --- substrate/frame/assets-holder/src/lib.rs | 2 +- substrate/frame/assets/src/mock.rs | 4 ++-- umbrella/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/substrate/frame/assets-holder/src/lib.rs b/substrate/frame/assets-holder/src/lib.rs index cda267d639eb..26890e47c33e 100644 --- a/substrate/frame/assets-holder/src/lib.rs +++ b/substrate/frame/assets-holder/src/lib.rs @@ -35,7 +35,7 @@ //! This pallet provides the following functionality: //! //! - Pallet hooks allowing [`pallet-assets`] to know the balance on hold for an account on a given -//! asset (see [`pallet_assets::BalanceOnHold`](pallet_assets::BalanceOnHold)). +//! asset (see [`pallet_assets::BalanceOnHold`]). //! - An implementation of //! [`fungibles::hold::Inspect`](frame_support::traits::fungibles::hold::Inspect), //! [`fungibles::hold::Mutate`](frame_support::traits::fungibles::hold::Mutate) and diff --git a/substrate/frame/assets/src/mock.rs b/substrate/frame/assets/src/mock.rs index 388da8a3894f..a65a50c5f079 100644 --- a/substrate/frame/assets/src/mock.rs +++ b/substrate/frame/assets/src/mock.rs @@ -130,7 +130,7 @@ impl BalanceOnHold for TestHolder { } fn contains_holds(asset: AssetId) -> bool { - !OnHold::get().iter().find(|((k, _), _)| &asset == k).is_none() + OnHold::get().iter().any(|((k, _), _)| &asset == k) } } @@ -174,7 +174,7 @@ impl FrozenBalance for TestFreezer { /// Return a value that indicates if there are registered freezes for a given asset. fn contains_freezes(asset: AssetId) -> bool { - !Frozen::get().iter().find(|((k, _), _)| &asset == k).is_none() + Frozen::get().iter().any(|((k, _), _)| &asset == k) } } diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 1d5cbbca875e..28ee76936b4a 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -2497,4 +2497,4 @@ optional = true [package.metadata.docs.rs] features = ["node", "runtime-full"] -targets = ["x86_64-unknown-linux-gnu"] \ No newline at end of file +targets = ["x86_64-unknown-linux-gnu"] From 25d9bb050df9347a71ad02e89b08f00ed4ba5400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Mon, 23 Sep 2024 11:19:54 -0500 Subject: [PATCH 44/59] change(pallet-assets): make `dead_account` return either `DeadConsequence` or fail if account cannot die. --- substrate/frame/assets/src/functions.rs | 45 +++++++++++-------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index ebebb4105edb..3bf704d3124f 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -96,12 +96,20 @@ impl, I: 'static> Pallet { } pub(super) fn dead_account( + id: T::AssetId, who: &T::AccountId, d: &mut AssetDetails>, reason: &ExistenceReasonOf, force: bool, - ) -> DeadConsequence { + ) -> Result { use ExistenceReason::*; + + ensure!( + T::Holder::balance_on_hold(id.clone(), &who).is_none(), + Error::::ContainsHolds + ); + ensure!(T::Freezer::frozen_balance(id, &who).is_none(), Error::::ContainsFreezes); + match *reason { Consumer => frame_system::Pallet::::dec_consumers(who), Sufficient => { @@ -109,11 +117,11 @@ impl, I: 'static> Pallet { frame_system::Pallet::::dec_sufficients(who); }, DepositRefunded => {}, - DepositHeld(_) | DepositFrom(..) if !force => return Keep, + DepositHeld(_) | DepositFrom(..) if !force => return Ok(Keep), DepositHeld(_) | DepositFrom(..) => {}, } d.accounts = d.accounts.saturating_sub(1); - Remove + Ok(Remove) } /// Returns `true` when the balance of `account` can be increased by `amount`. @@ -364,15 +372,6 @@ impl, I: 'static> Pallet { use AssetStatus::*; use ExistenceReason::*; - ensure!( - T::Holder::balance_on_hold(id.clone(), &who).is_none(), - Error::::ContainsHolds - ); - ensure!( - T::Freezer::frozen_balance(id.clone(), &who).is_none(), - Error::::ContainsFreezes - ); - let mut account = Account::::get(&id, &who).ok_or(Error::::NoDeposit)?; ensure!(matches!(account.reason, Consumer | DepositHeld(..)), Error::::NoDeposit); let mut details = Asset::::get(&id).ok_or(Error::::Unknown)?; @@ -383,7 +382,8 @@ impl, I: 'static> Pallet { T::Currency::unreserve(&who, deposit); } - if let Remove = Self::dead_account(&who, &mut details, &account.reason, false) { + if let Remove = Self::dead_account(id.clone(), &who, &mut details, &account.reason, false)? + { Account::::remove(&id, &who); } else { debug_assert!(false, "refund did not result in dead account?!"); @@ -407,15 +407,6 @@ impl, I: 'static> Pallet { who: &T::AccountId, maybe_check_caller: Option, ) -> DispatchResult { - ensure!( - T::Holder::balance_on_hold(id.clone(), who).is_none(), - Error::::ContainsHolds - ); - ensure!( - T::Freezer::frozen_balance(id.clone(), who).is_none(), - Error::::ContainsFreezes - ); - let mut account = Account::::get(&id, &who).ok_or(Error::::NoDeposit)?; let (depositor, deposit) = account.reason.take_deposit_from().ok_or(Error::::NoDeposit)?; @@ -429,7 +420,8 @@ impl, I: 'static> Pallet { T::Currency::unreserve(&depositor, deposit); - if let Remove = Self::dead_account(&who, &mut details, &account.reason, false) { + if let Remove = Self::dead_account(id.clone(), &who, &mut details, &account.reason, false)? + { Account::::remove(&id, &who); } else { debug_assert!(false, "refund did not result in dead account?!"); @@ -594,7 +586,8 @@ impl, I: 'static> Pallet { account.balance = account.balance.saturating_sub(actual); if account.balance < details.min_balance { debug_assert!(account.balance.is_zero(), "checked in prep; qed"); - target_died = Some(Self::dead_account(target, details, &account.reason, false)); + target_died = + Some(Self::dead_account(id, target, details, &account.reason, false)?); if let Some(Remove) = target_died { return Ok(()) } @@ -716,7 +709,7 @@ impl, I: 'static> Pallet { if source_account.balance < details.min_balance { debug_assert!(source_account.balance.is_zero(), "checked in prep; qed"); source_died = - Some(Self::dead_account(source, details, &source_account.reason, false)); + Some(Self::dead_account(id, source, details, &source_account.reason, false)?); if let Some(Remove) = source_died { Account::::remove(&id, &source); return Ok(()) @@ -821,7 +814,7 @@ impl, I: 'static> Pallet { } else if let Some(deposit) = v.reason.take_deposit() { T::Currency::unreserve(&who, deposit); } - if let Remove = Self::dead_account(&who, &mut details, &v.reason, false) { + if let Remove = Self::dead_account(id, &who, &mut details, &v.reason, false)? { Account::::remove(&id, &who); dead_accounts.push(who); } else { From c81ad85d040a3ce31c4700264871dad2c34af487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Mon, 23 Sep 2024 11:33:47 -0500 Subject: [PATCH 45/59] change(Cargo): update lockfile --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 30fb076f5d50..50e37eab9fcc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10779,9 +10779,9 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] From bd72de1fa2c0c2c60b9d9f27b0a994129c741aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Mon, 23 Sep 2024 11:39:59 -0500 Subject: [PATCH 46/59] fix(pallet-pass): missing clone for asset id --- substrate/frame/assets/src/functions.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index 3bf704d3124f..743b4cf0b47b 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -814,7 +814,9 @@ impl, I: 'static> Pallet { } else if let Some(deposit) = v.reason.take_deposit() { T::Currency::unreserve(&who, deposit); } - if let Remove = Self::dead_account(id, &who, &mut details, &v.reason, false)? { + if let Remove = + Self::dead_account(id.clone(), &who, &mut details, &v.reason, false)? + { Account::::remove(&id, &who); dead_accounts.push(who); } else { From 4bab6f1a23188226c8be505ce047af1e9c61ed3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Mon, 23 Sep 2024 13:13:55 -0500 Subject: [PATCH 47/59] make ci happy --- substrate/frame/assets/src/functions.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index 743b4cf0b47b..d66658e01113 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -586,8 +586,13 @@ impl, I: 'static> Pallet { account.balance = account.balance.saturating_sub(actual); if account.balance < details.min_balance { debug_assert!(account.balance.is_zero(), "checked in prep; qed"); - target_died = - Some(Self::dead_account(id, target, details, &account.reason, false)?); + target_died = Some(Self::dead_account( + id.clone(), + target, + details, + &account.reason, + false, + )?); if let Some(Remove) = target_died { return Ok(()) } @@ -708,8 +713,13 @@ impl, I: 'static> Pallet { // Remove source account if it's now dead. if source_account.balance < details.min_balance { debug_assert!(source_account.balance.is_zero(), "checked in prep; qed"); - source_died = - Some(Self::dead_account(id, source, details, &source_account.reason, false)?); + source_died = Some(Self::dead_account( + id.clone(), + source, + details, + &source_account.reason, + false, + )?); if let Some(Remove) = source_died { Account::::remove(&id, &source); return Ok(()) From 8d46fa396a6632de0894bde26e0aa1e597233d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Mon, 7 Oct 2024 02:34:19 -0500 Subject: [PATCH 48/59] change(pallet-assets): tests to assert calling on `dead_account` failures --- substrate/frame/assets/src/lib.rs | 4 +- substrate/frame/assets/src/tests.rs | 112 ++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/substrate/frame/assets/src/lib.rs b/substrate/frame/assets/src/lib.rs index 9b3b43a8fcda..e74a68963ab3 100644 --- a/substrate/frame/assets/src/lib.rs +++ b/substrate/frame/assets/src/lib.rs @@ -693,10 +693,10 @@ pub mod pallet { CallbackFailed, /// The asset ID must be equal to the [`NextAssetId`]. BadAssetId, - /// The asset cannot be destroyed because some accounts for this asset contain holds. - ContainsHolds, /// The asset cannot be destroyed because some accounts for this asset contain freezes. ContainsFreezes, + /// The asset cannot be destroyed because some accounts for this asset contain holds. + ContainsHolds, } #[pallet::call(weight(>::WeightInfo))] diff --git a/substrate/frame/assets/src/tests.rs b/substrate/frame/assets/src/tests.rs index def1d52346c2..5ab6fd399c6c 100644 --- a/substrate/frame/assets/src/tests.rs +++ b/substrate/frame/assets/src/tests.rs @@ -1302,6 +1302,118 @@ fn set_metadata_should_work() { }); } +/// Calling on `dead_account` should be either unreachable, or fail if either a freeze or some +/// balance on hold exists. +/// +/// ### Case 1: Sufficient asset +/// +/// This asserts for `dead_account` on `decrease_balance`, `transfer_and_die` and +/// `do_destry_accounts`. +#[test] +fn calling_dead_account_fails_if_freezes_or_balances_on_hold_exist_1() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 50)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + + set_frozen_balance(0, 1, 50); + // Cannot transfer out less than max(freezes, ed). This happens in + // `prep_debit` under `transfer_and_die`. Would not reach `dead_account`. + assert_noop!( + Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 100), + Error::::BalanceLow + ); + assert_noop!( + Assets::transfer_keep_alive(RuntimeOrigin::signed(1), 0, 2, 100), + Error::::BalanceLow + ); + assert_noop!( + Assets::force_transfer(RuntimeOrigin::signed(1), 0, 1, 2, 100), + Error::::BalanceLow + ); + // Cannot start destroying the asset, because some accounts contain freezes + assert_noop!( + Assets::start_destroy(RuntimeOrigin::signed(1), 0), + Error::::ContainsFreezes + ); + clear_frozen_balance(0, 1); + + set_balance_on_hold(0, 1, 50); + // Cannot transfer out less than max(freezes, ed). This happens in + // `prep_debit` under `transfer_and_die`. Would not reach `dead_account`. + assert_noop!( + Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 100), + Error::::BalanceLow + ); + assert_noop!( + Assets::transfer_keep_alive(RuntimeOrigin::signed(1), 0, 2, 100), + Error::::BalanceLow + ); + assert_noop!( + Assets::force_transfer(RuntimeOrigin::signed(1), 0, 1, 2, 100), + Error::::BalanceLow + ); + // Cannot start destroying the asset, because some accounts contain freezes + assert_noop!( + Assets::start_destroy(RuntimeOrigin::signed(1), 0), + Error::::ContainsHolds + ); + }) +} + +/// Calling on `dead_account` should be either unreachable, or fail if either a freeze or some +/// balance on hold exists. +/// +/// ### Case 2: Inufficient asset +/// +/// This asserts for `dead_account` on `do_refund` and `do_refund_other`. +#[test] +fn calling_dead_account_fails_if_freezes_or_balances_on_hold_exist_2() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + + set_frozen_balance(0, 1, 50); + assert_noop!( + Assets::refund(RuntimeOrigin::signed(1), 0, true), + Error::::ContainsFreezes + ); + clear_frozen_balance(0, 1); + + set_balance_on_hold(0, 1, 50); + assert_noop!( + Assets::refund(RuntimeOrigin::signed(1), 0, true), + Error::::ContainsHolds + ); + clear_balance_on_hold(0, 1); + assert_ok!(Assets::refund(RuntimeOrigin::signed(1), 0, true)); + }); + + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 2)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); + + set_frozen_balance(0, 2, 100); + assert_noop!( + Assets::refund_other(RuntimeOrigin::signed(1), 0, 2), + Error::::WouldBurn + ); + clear_frozen_balance(0, 2); + + // Note: It's not possible to set balance on hold for the maximum balance, + // as it `WouldBurn` because of how setting the balance works on mock. + set_balance_on_hold(0, 2, 99); + assert_noop!( + Assets::refund_other(RuntimeOrigin::signed(1), 0, 2), + Error::::WouldBurn + ); + clear_balance_on_hold(0, 2); + }) +} + /// Destroying an asset calls the `FrozenBalance::died` hooks of all accounts. #[test] fn destroy_accounts_calls_died_hooks() { From 7d84beef55cbfc682e6d7e6b961cc35bfdfd355c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Mon, 7 Oct 2024 12:35:35 -0500 Subject: [PATCH 49/59] change(pallet-assets-holder): make CI happy --- substrate/frame/assets-holder/src/mock.rs | 104 +++++++--------------- 1 file changed, 32 insertions(+), 72 deletions(-) diff --git a/substrate/frame/assets-holder/src/mock.rs b/substrate/frame/assets-holder/src/mock.rs index 2b4978be6681..8d9ea1f51a3d 100644 --- a/substrate/frame/assets-holder/src/mock.rs +++ b/substrate/frame/assets-holder/src/mock.rs @@ -19,98 +19,58 @@ use crate as pallet_assets_holder; pub use crate::*; -use codec::{Compact, Decode, Encode, MaxEncodedLen}; -use frame_support::{ - derive_impl, - traits::{AsEnsureOriginWithArg, ConstU64}, -}; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{derive_impl, traits::AsEnsureOriginWithArg}; use scale_info::TypeInfo; -use sp_core::{ConstU32, H256}; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::BuildStorage; -pub type AccountId = u64; -pub type Balance = u64; -pub type AssetId = u32; +pub type AccountId = ::AccountId; +pub type Balance = ::Balance; +pub type AssetId = ::AssetId; type Block = frame_system::mocking::MockBlock; -frame_support::construct_runtime!( - pub enum Test - { - System: frame_system, - Assets: pallet_assets, - AssetsHolder: pallet_assets_holder, - Balances: pallet_balances, - } -); +#[frame_support::runtime] +mod runtime { + #[runtime::runtime] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeTask, + RuntimeHoldReason, + RuntimeFreezeReason + )] + pub struct Test; + + #[runtime::pallet_index(0)] + pub type System = frame_system; + #[runtime::pallet_index(10)] + pub type Balances = pallet_balances; + #[runtime::pallet_index(20)] + pub type Assets = pallet_assets; + #[runtime::pallet_index(21)] + pub type AssetsHolder = pallet_assets_holder; +} #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)] impl pallet_balances::Config for Test { - type MaxLocks = (); - type MaxReserves = (); - type ReserveIdentifier = [u8; 8]; - type Balance = Balance; - type DustRemoval = (); - type RuntimeEvent = RuntimeEvent; - type ExistentialDeposit = ConstU64<1>; type AccountStore = System; - type WeightInfo = (); - type FreezeIdentifier = (); - type MaxFreezes = (); - type RuntimeHoldReason = (); - type RuntimeFreezeReason = (); } +#[derive_impl(pallet_assets::config_preludes::TestDefaultConfig as pallet_assets::DefaultConfig)] impl pallet_assets::Config for Test { - type AssetId = AssetId; - type AssetIdParameter = Compact; - type AssetDeposit = ConstU64<1>; - type Balance = Balance; - type AssetAccountDeposit = ConstU64<1>; - type MetadataDepositBase = (); - type MetadataDepositPerByte = (); - type ApprovalDeposit = (); + // type AssetAccountDeposit = ConstU64<1>; type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = frame_system::EnsureRoot; - type StringLimit = ConstU32<32>; - type Extra = (); - type RemoveItemsLimit = ConstU32<10>; - type CallbackHandle = (); type Currency = Balances; type Holder = AssetsHolder; - type Freezer = (); - type RuntimeEvent = RuntimeEvent; - type WeightInfo = (); - #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = (); } #[derive( From 608b6880570ce176827cc785c879d34f244c5f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Sun, 3 Nov 2024 18:25:36 -0500 Subject: [PATCH 50/59] change(prdoc): apply suggestions and clarify changes in balance model --- prdoc/pr_4530.prdoc | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/prdoc/pr_4530.prdoc b/prdoc/pr_4530.prdoc index 894b668c57de..f5e02e93dc36 100644 --- a/prdoc/pr_4530.prdoc +++ b/prdoc/pr_4530.prdoc @@ -5,9 +5,22 @@ doc: description: | This change creates the `pallet-assets-holder` pallet, as well as changes `pallet-assets` to support querying held balances via a new trait: `BalanceOnHold`. + + ## Changes in Balance Model - It also adjusts the balance model implementation for fungible - sets (see [sdk docs](https://paritytech.github.io/polkadot-sdk/master/frame_support/traits/tokens/fungible/index.html#visualising-balance-components-together-)). + The change also adjusts the balance model implementation for fungible sets. This aligns the + calculation of the _spendable_ balance (that can be reduced either via withdrawals, like + paying for fees, or transfer to other accounts) to behave like it works with native tokens. + + As a consequence, when this change is introduced, adding freezes (a.k.a. locks) or balances + on hold (a.k.a. reserves) to an asset account will constraint the amount of balance for such + account that can be withdrawn or transferred, and will affect the ability for these accounts + to be destroyed. + + See [sdk docs](https://paritytech.github.io/polkadot-sdk/master/frame_support/traits/tokens/fungible/index.html#visualising-balance-components-together-) + to understand how to calculate the spendable balance of an asset account on the client side. + + ## Implementation of `InspectHold` and `MutateHold` The `pallet-assets-holder` implements `hold` traits for `pallet-assets`, by extending this pallet and implementing the `BalanceOnHold` trait so the held balance can be queried by @@ -23,7 +36,7 @@ doc: ## Enable `pallet-assets-holder` Define an instance of `pallet-assets-holder` (we'll call it `AssetsHolder`) and use - `AssetsHolder` as the value for `Holder`, when intend to use holding capabilities are + `AssetsHolder` as the type for `Holder`, when intend to use holding capabilities are wanted in the runtime implementation. crates: From 189d8d452f8ab6938d330b82b08e0025c14b4f1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Sun, 3 Nov 2024 18:49:26 -0500 Subject: [PATCH 51/59] change(pallet-assets-freezer): handle `assert_debug`s when calling `died` --- substrate/frame/assets-freezer/src/impls.rs | 9 +++++++++ substrate/frame/assets-freezer/src/tests.rs | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/substrate/frame/assets-freezer/src/impls.rs b/substrate/frame/assets-freezer/src/impls.rs index 11fb4d77ad87..b1a203297cf4 100644 --- a/substrate/frame/assets-freezer/src/impls.rs +++ b/substrate/frame/assets-freezer/src/impls.rs @@ -36,6 +36,15 @@ impl, I: 'static> FrozenBalance::get(asset.clone(), who).is_empty(), + "The list of Freezes should be empty before allowing an account to die" + ); + defensive_assert!( + FrozenBalances::::get(asset.clone(), who).is_none(), + "There should not be a frozen balance before allowing to die" + ); + FrozenBalances::::remove(asset.clone(), who); Freezes::::remove(asset, who); } diff --git a/substrate/frame/assets-freezer/src/tests.rs b/substrate/frame/assets-freezer/src/tests.rs index cee14a90d16a..ea514458eb34 100644 --- a/substrate/frame/assets-freezer/src/tests.rs +++ b/substrate/frame/assets-freezer/src/tests.rs @@ -75,10 +75,21 @@ mod impl_frozen_balance { }); } + #[test] + #[should_panic = "The list of Freezes should be empty before allowing an account to die"] + fn died_fails_if_freezes_exist() { + new_test_ext(|| { + test_set_freeze(DummyFreezeReason::Governance, 1); + AssetsFreezer::died(ASSET_ID, &WHO); + }); + } + + #[test] fn died_works() { new_test_ext(|| { test_set_freeze(DummyFreezeReason::Governance, 1); + test_thaw(DummyFreezeReason::Governance); AssetsFreezer::died(ASSET_ID, &WHO); assert!(FrozenBalances::::get(ASSET_ID, WHO).is_none()); assert!(Freezes::::get(ASSET_ID, WHO).is_empty()); From 6130c609962ce8b433368bd9904296e02c2adbcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Sun, 3 Nov 2024 19:10:01 -0500 Subject: [PATCH 52/59] fix(pallet-assets): on `can_decrease`, return `WouldDie` if there's balance on hold, but frozen value is zero, as the account would need to exist --- substrate/frame/assets/src/functions.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index d66658e01113..4ffbbcf0bc83 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -223,10 +223,14 @@ impl, I: 'static> Pallet { // here: https://paritytech.github.io/polkadot-sdk/master/frame_support/traits/tokens/fungible/index.html#visualising-balance-components-together- let untouchable = frozen.saturating_sub(held).max(details.min_balance); if rest < untouchable { - return Frozen + if !frozen.is_zero() { + Frozen + } else { + WouldDie + } + } else { + Success } - - Success }, } } else { From f7d0a416f6417665d905025585e1a24d66e4ac07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Sun, 3 Nov 2024 19:23:18 -0500 Subject: [PATCH 53/59] change(pallet-assets): assert `died` hooks for both `Freezer` AND `Holder`. --- substrate/frame/assets/src/mock.rs | 4 +- substrate/frame/assets/src/tests.rs | 2655 ++++++++++++++------------- 2 files changed, 1345 insertions(+), 1314 deletions(-) diff --git a/substrate/frame/assets/src/mock.rs b/substrate/frame/assets/src/mock.rs index a65a50c5f079..9803f929a566 100644 --- a/substrate/frame/assets/src/mock.rs +++ b/substrate/frame/assets/src/mock.rs @@ -125,8 +125,8 @@ impl BalanceOnHold for TestHolder { OnHold::get().get(&(asset, *who)).cloned() } - fn died(_asset: u32, _who: &u64) { - // TODO: Connect with existing hooks list + fn died(asset: u32, who: &u64) { + Hooks::mutate(|v| v.push(Hook::Died(asset, *who))) } fn contains_holds(asset: AssetId) -> bool { diff --git a/substrate/frame/assets/src/tests.rs b/substrate/frame/assets/src/tests.rs index 23daaa5857ef..b1a62389ca43 100644 --- a/substrate/frame/assets/src/tests.rs +++ b/substrate/frame/assets/src/tests.rs @@ -20,13 +20,13 @@ use super::*; use crate::{mock::*, Error}; use frame_support::{ - assert_noop, assert_ok, - dispatch::GetDispatchInfo, - traits::{ - fungibles::InspectEnumerable, - tokens::{Preservation::Protect, Provenance}, - Currency, - }, + assert_noop, assert_ok, + dispatch::GetDispatchInfo, + traits::{ + fungibles::InspectEnumerable, + tokens::{Preservation::Protect, Provenance}, + Currency, + }, }; use pallet_balances::Error as BalancesError; use sp_io::storage; @@ -35,1271 +35,1291 @@ use sp_runtime::{traits::ConvertInto, TokenError}; mod sets; fn asset_ids() -> Vec { - let mut s: Vec<_> = Assets::asset_ids().collect(); - s.sort(); - s + let mut s: Vec<_> = Assets::asset_ids().collect(); + s.sort(); + s } /// returns tuple of asset's account and sufficient counts fn asset_account_counts(asset_id: u32) -> (u32, u32) { - let asset = Asset::::get(asset_id).unwrap(); - (asset.accounts, asset.sufficients) + let asset = Asset::::get(asset_id).unwrap(); + (asset.accounts, asset.sufficients) } #[test] fn transfer_should_never_burn() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - Balances::make_free_balance_be(&1, 100); - Balances::make_free_balance_be(&2, 100); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); - while System::inc_consumers(&2).is_ok() {} - let _ = System::dec_consumers(&2); - let _ = System::dec_consumers(&2); - // Exactly one consumer ref remaining. - assert_eq!(System::consumers(&2), 1); + while System::inc_consumers(&2).is_ok() {} + let _ = System::dec_consumers(&2); + let _ = System::dec_consumers(&2); + // Exactly one consumer ref remaining. + assert_eq!(System::consumers(&2), 1); - let _ = >::transfer(0, &1, &2, 50, Protect); - System::assert_has_event(RuntimeEvent::Assets(crate::Event::Transferred { - asset_id: 0, - from: 1, - to: 2, - amount: 50, - })); - assert_eq!(Assets::balance(0, 1), 50); - assert_eq!(Assets::balance(0, 1) + Assets::balance(0, 2), 100); - }); + let _ = >::transfer(0, &1, &2, 50, Protect); + System::assert_has_event(RuntimeEvent::Assets(crate::Event::Transferred { + asset_id: 0, + from: 1, + to: 2, + amount: 50, + })); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 1) + Assets::balance(0, 2), 100); + }); } #[test] fn basic_minting_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 1, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - System::assert_last_event(RuntimeEvent::Assets(crate::Event::Issued { - asset_id: 0, - owner: 1, - amount: 100, - })); - assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); - System::assert_last_event(RuntimeEvent::Assets(crate::Event::Issued { - asset_id: 0, - owner: 2, - amount: 100, - })); - assert_eq!(Assets::balance(0, 2), 100); - assert_eq!(asset_ids(), vec![0, 1, 999]); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 1, 1, 100)); - System::assert_last_event(RuntimeEvent::Assets(crate::Event::Issued { - asset_id: 1, - owner: 1, - amount: 100, - })); - assert_eq!(Assets::account_balances(1), vec![(0, 100), (999, 100), (1, 100)]); - }); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 1, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + System::assert_last_event(RuntimeEvent::Assets(crate::Event::Issued { + asset_id: 0, + owner: 1, + amount: 100, + })); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); + System::assert_last_event(RuntimeEvent::Assets(crate::Event::Issued { + asset_id: 0, + owner: 2, + amount: 100, + })); + assert_eq!(Assets::balance(0, 2), 100); + assert_eq!(asset_ids(), vec![0, 1, 999]); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 1, 1, 100)); + System::assert_last_event(RuntimeEvent::Assets(crate::Event::Issued { + asset_id: 1, + owner: 1, + amount: 100, + })); + assert_eq!(Assets::account_balances(1), vec![(0, 100), (999, 100), (1, 100)]); + }); } #[test] fn minting_too_many_insufficient_assets_fails() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 1, 1, false, 1)); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 2, 1, false, 1)); - Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 1, 1, 100)); - assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100), TokenError::CannotCreate); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 1, 1, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 2, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 1, 1, 100)); + assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100), TokenError::CannotCreate); - Balances::make_free_balance_be(&2, 1); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 100)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100)); - assert_eq!(asset_ids(), vec![0, 1, 2, 999]); - }); + Balances::make_free_balance_be(&2, 1); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100)); + assert_eq!(asset_ids(), vec![0, 1, 2, 999]); + }); } #[test] fn minting_insufficient_asset_with_deposit_should_work_when_consumers_exhausted() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 1, 1, false, 1)); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 2, 1, false, 1)); - Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 1, 1, 100)); - assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100), TokenError::CannotCreate); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 1, 1, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 2, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 1, 1, 100)); + assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100), TokenError::CannotCreate); - assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 2)); - assert_eq!(Balances::reserved_balance(&1), 10); + assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 2)); + assert_eq!(Balances::reserved_balance(&1), 10); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100)); - }); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100)); + }); } #[test] fn minting_insufficient_assets_with_deposit_without_consumer_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100), TokenError::CannotCreate); - Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Balances::reserved_balance(&1), 10); - assert_eq!(System::consumers(&1), 1); - }); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100), TokenError::CannotCreate); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Balances::reserved_balance(&1), 10); + assert_eq!(System::consumers(&1), 1); + }); } #[test] fn refunding_asset_deposit_with_burn_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_ok!(Assets::refund(RuntimeOrigin::signed(1), 0, true)); - assert_eq!(Balances::reserved_balance(&1), 0); - assert_eq!(Assets::balance(1, 0), 0); - }); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_ok!(Assets::refund(RuntimeOrigin::signed(1), 0, true)); + assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(Assets::balance(1, 0), 0); + }); } #[test] fn refunding_asset_deposit_with_burn_disallowed_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_noop!(Assets::refund(RuntimeOrigin::signed(1), 0, false), Error::::WouldBurn); - }); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_noop!(Assets::refund(RuntimeOrigin::signed(1), 0, false), Error::::WouldBurn); + }); } #[test] fn refunding_asset_deposit_without_burn_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100), TokenError::CannotCreate); - Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&2, 100); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 100)); - assert_eq!(Assets::balance(0, 2), 100); - assert_eq!(Assets::balance(0, 1), 0); - assert_eq!(Balances::reserved_balance(&1), 10); - assert_eq!(asset_account_counts(0), (2, 0)); - assert_ok!(Assets::refund(RuntimeOrigin::signed(1), 0, false)); - assert_eq!(Balances::reserved_balance(&1), 0); - assert_eq!(Assets::balance(1, 0), 0); - assert_eq!(asset_account_counts(0), (1, 0)); - }); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100), TokenError::CannotCreate); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + Balances::make_free_balance_be(&2, 100); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 100)); + assert_eq!(Assets::balance(0, 2), 100); + assert_eq!(Assets::balance(0, 1), 0); + assert_eq!(Balances::reserved_balance(&1), 10); + assert_eq!(asset_account_counts(0), (2, 0)); + assert_ok!(Assets::refund(RuntimeOrigin::signed(1), 0, false)); + assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(Assets::balance(1, 0), 0); + assert_eq!(asset_account_counts(0), (1, 0)); + }); } /// Refunding reaps an account and calls the `FrozenBalance::died` hook. #[test] fn refunding_calls_died_hook() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_ok!(Assets::refund(RuntimeOrigin::signed(1), 0, true)); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_ok!(Assets::refund(RuntimeOrigin::signed(1), 0, true)); - assert_eq!(Asset::::get(0).unwrap().accounts, 0); - assert_eq!(hooks(), vec![Hook::Died(0, 1)]); - assert_eq!(asset_ids(), vec![0, 999]); - }); + assert_eq!(Asset::::get(0).unwrap().accounts, 0); + assert_eq!(hooks(), vec![ + Hook::Died(0, 1), + // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. + Hook::Died(0, 1) + ]); + assert_eq!(asset_ids(), vec![0, 999]); + }); } #[test] fn refunding_with_sufficient_existence_reason_should_fail() { - new_test_ext().execute_with(|| { - // create sufficient asset - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - // create an asset account with sufficient existence reason - // by transferring some sufficient assets - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - assert_eq!(Assets::balance(0, 1), 50); - assert_eq!(Assets::balance(0, 2), 50); - assert_eq!(asset_account_counts(0), (2, 2)); - // fails to refund - assert_noop!(Assets::refund(RuntimeOrigin::signed(2), 0, true), Error::::NoDeposit); - }); + new_test_ext().execute_with(|| { + // create sufficient asset + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + // create an asset account with sufficient existence reason + // by transferring some sufficient assets + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 2), 50); + assert_eq!(asset_account_counts(0), (2, 2)); + // fails to refund + assert_noop!(Assets::refund(RuntimeOrigin::signed(2), 0, true), Error::::NoDeposit); + }); } #[test] fn refunding_with_deposit_from_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - Balances::make_free_balance_be(&1, 100); - // create asset account `2` with deposit from `1` - assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 2)); - assert_eq!(Balances::reserved_balance(&1), 10); - // fails to refund - assert_noop!(Assets::refund(RuntimeOrigin::signed(2), 0, true), Error::::NoDeposit); - assert!(Account::::contains_key(0, &2)); - }); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + // create asset account `2` with deposit from `1` + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 2)); + assert_eq!(Balances::reserved_balance(&1), 10); + // fails to refund + assert_noop!(Assets::refund(RuntimeOrigin::signed(2), 0, true), Error::::NoDeposit); + assert!(Account::::contains_key(0, &2)); + }); } #[test] fn refunding_frozen_with_consumer_ref_works() { - new_test_ext().execute_with(|| { - // 1 will be an admin - // 2 will be a frozen account - Balances::make_free_balance_be(&1, 100); - Balances::make_free_balance_be(&2, 100); - // create non-sufficient asset - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(System::consumers(&2), 0); - // create asset account `2` with a consumer reference by transferring - // non-sufficient funds into - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - assert_eq!(System::consumers(&2), 1); - assert_eq!(Assets::balance(0, 1), 50); - assert_eq!(Assets::balance(0, 2), 50); - assert_eq!(asset_account_counts(0), (2, 0)); - // freeze asset account `2` and asset `0` - assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2)); - assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); - // refund works - assert_ok!(Assets::refund(RuntimeOrigin::signed(2), 0, true)); - assert!(!Account::::contains_key(0, &2)); - assert_eq!(System::consumers(&2), 0); - assert_eq!(asset_account_counts(0), (1, 0)); - }); + new_test_ext().execute_with(|| { + // 1 will be an admin + // 2 will be a frozen account + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + // create non-sufficient asset + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(System::consumers(&2), 0); + // create asset account `2` with a consumer reference by transferring + // non-sufficient funds into + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_eq!(System::consumers(&2), 1); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 2), 50); + assert_eq!(asset_account_counts(0), (2, 0)); + // freeze asset account `2` and asset `0` + assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2)); + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + // refund works + assert_ok!(Assets::refund(RuntimeOrigin::signed(2), 0, true)); + assert!(!Account::::contains_key(0, &2)); + assert_eq!(System::consumers(&2), 0); + assert_eq!(asset_account_counts(0), (1, 0)); + }); } #[test] fn refunding_frozen_with_deposit_works() { - new_test_ext().execute_with(|| { - // 1 will be an asset admin - // 2 will be a frozen account - Balances::make_free_balance_be(&1, 100); - Balances::make_free_balance_be(&2, 100); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(System::consumers(&2), 0); - assert_ok!(Assets::touch(RuntimeOrigin::signed(2), 0)); - // reserve deposit holds one consumer ref - assert_eq!(System::consumers(&2), 1); - assert_eq!(Balances::reserved_balance(&2), 10); - assert!(Account::::contains_key(0, &2)); - // transfer some assets to `2` - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - assert_eq!(System::consumers(&2), 1); - assert_eq!(Assets::balance(0, 1), 50); - assert_eq!(Assets::balance(0, 2), 50); - assert_eq!(asset_account_counts(0), (2, 0)); - // ensure refundable even if asset account and asset is frozen - assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2)); - assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); - // success - assert_ok!(Assets::refund(RuntimeOrigin::signed(2), 0, true)); - assert!(!Account::::contains_key(0, &2)); - assert_eq!(Balances::reserved_balance(&2), 0); - assert_eq!(System::consumers(&2), 0); - assert_eq!(asset_account_counts(0), (1, 0)); - }); + new_test_ext().execute_with(|| { + // 1 will be an asset admin + // 2 will be a frozen account + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(System::consumers(&2), 0); + assert_ok!(Assets::touch(RuntimeOrigin::signed(2), 0)); + // reserve deposit holds one consumer ref + assert_eq!(System::consumers(&2), 1); + assert_eq!(Balances::reserved_balance(&2), 10); + assert!(Account::::contains_key(0, &2)); + // transfer some assets to `2` + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_eq!(System::consumers(&2), 1); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 2), 50); + assert_eq!(asset_account_counts(0), (2, 0)); + // ensure refundable even if asset account and asset is frozen + assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2)); + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + // success + assert_ok!(Assets::refund(RuntimeOrigin::signed(2), 0, true)); + assert!(!Account::::contains_key(0, &2)); + assert_eq!(Balances::reserved_balance(&2), 0); + assert_eq!(System::consumers(&2), 0); + assert_eq!(asset_account_counts(0), (1, 0)); + }); } #[test] fn approval_lifecycle_works() { - new_test_ext().execute_with(|| { - // can't approve non-existent token - assert_noop!( + new_test_ext().execute_with(|| { + // can't approve non-existent token + assert_noop!( Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50), Error::::Unknown ); - // so we create it :) - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 2); - assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - assert_eq!(Asset::::get(0).unwrap().approvals, 1); - assert_eq!(Balances::reserved_balance(&1), 1); - assert_ok!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 40)); - assert_eq!(Asset::::get(0).unwrap().approvals, 1); - assert_ok!(Assets::cancel_approval(RuntimeOrigin::signed(1), 0, 2)); - assert_eq!(Asset::::get(0).unwrap().approvals, 0); - assert_eq!(Assets::balance(0, 1), 60); - assert_eq!(Assets::balance(0, 3), 40); - assert_eq!(Balances::reserved_balance(&1), 0); - assert_eq!(asset_ids(), vec![0, 999]); - }); + // so we create it :) + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + Balances::make_free_balance_be(&1, 2); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_eq!(Asset::::get(0).unwrap().approvals, 1); + assert_eq!(Balances::reserved_balance(&1), 1); + assert_ok!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 40)); + assert_eq!(Asset::::get(0).unwrap().approvals, 1); + assert_ok!(Assets::cancel_approval(RuntimeOrigin::signed(1), 0, 2)); + assert_eq!(Asset::::get(0).unwrap().approvals, 0); + assert_eq!(Assets::balance(0, 1), 60); + assert_eq!(Assets::balance(0, 3), 40); + assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(asset_ids(), vec![0, 999]); + }); } #[test] fn transfer_approved_all_funds() { - new_test_ext().execute_with(|| { - // can't approve non-existent token - assert_noop!( + new_test_ext().execute_with(|| { + // can't approve non-existent token + assert_noop!( Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50), Error::::Unknown ); - // so we create it :) - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 2); - assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - assert_eq!(Asset::::get(0).unwrap().approvals, 1); - assert_eq!(Balances::reserved_balance(&1), 1); + // so we create it :) + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + Balances::make_free_balance_be(&1, 2); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_eq!(Asset::::get(0).unwrap().approvals, 1); + assert_eq!(Balances::reserved_balance(&1), 1); - // transfer the full amount, which should trigger auto-cleanup - assert_ok!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 50)); - assert_eq!(Asset::::get(0).unwrap().approvals, 0); - assert_eq!(Assets::balance(0, 1), 50); - assert_eq!(Assets::balance(0, 3), 50); - assert_eq!(Balances::reserved_balance(&1), 0); - }); + // transfer the full amount, which should trigger auto-cleanup + assert_ok!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 50)); + assert_eq!(Asset::::get(0).unwrap().approvals, 0); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 3), 50); + assert_eq!(Balances::reserved_balance(&1), 0); + }); } #[test] fn approval_deposits_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - let e = BalancesError::::InsufficientBalance; - assert_noop!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50), e); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + let e = BalancesError::::InsufficientBalance; + assert_noop!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50), e); - Balances::make_free_balance_be(&1, 2); - assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - assert_eq!(Balances::reserved_balance(&1), 1); + Balances::make_free_balance_be(&1, 2); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_eq!(Balances::reserved_balance(&1), 1); - assert_ok!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 50)); - assert_eq!(Balances::reserved_balance(&1), 0); + assert_ok!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 50)); + assert_eq!(Balances::reserved_balance(&1), 0); - assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - assert_ok!(Assets::cancel_approval(RuntimeOrigin::signed(1), 0, 2)); - assert_eq!(Balances::reserved_balance(&1), 0); - }); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_ok!(Assets::cancel_approval(RuntimeOrigin::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(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 2); - assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - let e = Error::::Unapproved; - assert_noop!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 51), e); - }); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + Balances::make_free_balance_be(&1, 2); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + let e = Error::::Unapproved; + assert_noop!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 51), e); + }); } #[test] fn cannot_transfer_more_than_exists() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 2); - assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 101)); - let e = Error::::BalanceLow; - assert_noop!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 101), e); - }); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + Balances::make_free_balance_be(&1, 2); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 101)); + let e = Error::::BalanceLow; + assert_noop!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 101), e); + }); } #[test] fn cancel_approval_works() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 2); - assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - assert_eq!(Asset::::get(0).unwrap().approvals, 1); - assert_noop!( + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + Balances::make_free_balance_be(&1, 2); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_eq!(Asset::::get(0).unwrap().approvals, 1); + assert_noop!( Assets::cancel_approval(RuntimeOrigin::signed(1), 1, 2), Error::::Unknown ); - assert_noop!( + assert_noop!( Assets::cancel_approval(RuntimeOrigin::signed(2), 0, 2), Error::::Unknown ); - assert_noop!( + assert_noop!( Assets::cancel_approval(RuntimeOrigin::signed(1), 0, 3), Error::::Unknown ); - assert_eq!(Asset::::get(0).unwrap().approvals, 1); - assert_ok!(Assets::cancel_approval(RuntimeOrigin::signed(1), 0, 2)); - assert_eq!(Asset::::get(0).unwrap().approvals, 0); - assert_noop!( + assert_eq!(Asset::::get(0).unwrap().approvals, 1); + assert_ok!(Assets::cancel_approval(RuntimeOrigin::signed(1), 0, 2)); + assert_eq!(Asset::::get(0).unwrap().approvals, 0); + assert_noop!( Assets::cancel_approval(RuntimeOrigin::signed(1), 0, 2), Error::::Unknown ); - }); + }); } #[test] fn force_cancel_approval_works() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 2); - assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - assert_eq!(Asset::::get(0).unwrap().approvals, 1); - let e = Error::::NoPermission; - assert_noop!(Assets::force_cancel_approval(RuntimeOrigin::signed(2), 0, 1, 2), e); - assert_noop!( + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + Balances::make_free_balance_be(&1, 2); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_eq!(Asset::::get(0).unwrap().approvals, 1); + let e = Error::::NoPermission; + assert_noop!(Assets::force_cancel_approval(RuntimeOrigin::signed(2), 0, 1, 2), e); + assert_noop!( Assets::force_cancel_approval(RuntimeOrigin::signed(1), 1, 1, 2), Error::::Unknown ); - assert_noop!( + assert_noop!( Assets::force_cancel_approval(RuntimeOrigin::signed(1), 0, 2, 2), Error::::Unknown ); - assert_noop!( + assert_noop!( Assets::force_cancel_approval(RuntimeOrigin::signed(1), 0, 1, 3), Error::::Unknown ); - assert_eq!(Asset::::get(0).unwrap().approvals, 1); - assert_ok!(Assets::force_cancel_approval(RuntimeOrigin::signed(1), 0, 1, 2)); - assert_eq!(Asset::::get(0).unwrap().approvals, 0); - assert_noop!( + assert_eq!(Asset::::get(0).unwrap().approvals, 1); + assert_ok!(Assets::force_cancel_approval(RuntimeOrigin::signed(1), 0, 1, 2)); + assert_eq!(Asset::::get(0).unwrap().approvals, 0); + assert_noop!( Assets::force_cancel_approval(RuntimeOrigin::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(RuntimeOrigin::signed(1), 0, 1, 1)); - assert_eq!(Balances::reserved_balance(&1), 1); - assert!(Asset::::contains_key(0)); + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); + assert_eq!(Balances::reserved_balance(&1), 1); + assert!(Asset::::contains_key(0)); - assert_ok!(Assets::set_metadata(RuntimeOrigin::signed(1), 0, vec![0], vec![0], 12)); - assert_eq!(Balances::reserved_balance(&1), 4); - assert!(Metadata::::contains_key(0)); + assert_ok!(Assets::set_metadata(RuntimeOrigin::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(RuntimeOrigin::signed(1), 0, 10, 100)); - Balances::make_free_balance_be(&20, 100); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 20, 100)); - assert_eq!(Account::::iter_prefix(0).count(), 2); + Balances::make_free_balance_be(&10, 100); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 10, 100)); + Balances::make_free_balance_be(&20, 100); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 20, 100)); + assert_eq!(Account::::iter_prefix(0).count(), 2); - assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); - assert_eq!(Balances::reserved_balance(&1), 0); + 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!(!Asset::::contains_key(0)); + assert!(!Metadata::::contains_key(0)); + assert_eq!(Account::::iter_prefix(0).count(), 0); - assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); - assert_eq!(Balances::reserved_balance(&1), 1); - assert!(Asset::::contains_key(0)); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); + assert_eq!(Balances::reserved_balance(&1), 1); + assert!(Asset::::contains_key(0)); - assert_ok!(Assets::set_metadata(RuntimeOrigin::signed(1), 0, vec![0], vec![0], 12)); - assert_eq!(Balances::reserved_balance(&1), 4); - assert!(Metadata::::contains_key(0)); + assert_ok!(Assets::set_metadata(RuntimeOrigin::signed(1), 0, vec![0], vec![0], 12)); + assert_eq!(Balances::reserved_balance(&1), 4); + assert!(Metadata::::contains_key(0)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 10, 100)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 20, 100)); - assert_eq!(Account::::iter_prefix(0).count(), 2); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 10, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 20, 100)); + assert_eq!(Account::::iter_prefix(0).count(), 2); - assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); - assert_eq!(Balances::reserved_balance(&1), 0); + 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!(!Asset::::contains_key(0)); + assert!(!Metadata::::contains_key(0)); + assert_eq!(Account::::iter_prefix(0).count(), 0); + }); } #[test] fn destroy_should_refund_approvals() { - new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 10, 100)); - assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 3, 50)); - assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 4, 50)); - assert_eq!(Balances::reserved_balance(&1), 3); - assert_eq!(asset_ids(), vec![0, 999]); + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 10, 100)); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 3, 50)); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 4, 50)); + assert_eq!(Balances::reserved_balance(&1), 3); + assert_eq!(asset_ids(), vec![0, 999]); - assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); - assert_eq!(Balances::reserved_balance(&1), 0); - assert_eq!(asset_ids(), vec![999]); + assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(asset_ids(), vec![999]); - // all approvals are removed - assert!(Approvals::::iter().count().is_zero()) - }); + // all approvals are removed + assert!(Approvals::::iter().count().is_zero()) + }); } #[test] fn partial_destroy_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 10)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 10)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 3, 10)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 4, 10)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 5, 10)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 6, 10)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 7, 10)); - assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); - - assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); - // Asset is in use, as all the accounts have not yet been destroyed. - // We need to call destroy_accounts or destroy_approvals again until asset is completely - // cleaned up. - assert_noop!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0), Error::::InUse); - - System::assert_has_event(RuntimeEvent::Assets(crate::Event::AccountsDestroyed { - asset_id: 0, - accounts_destroyed: 5, - accounts_remaining: 2, - })); - System::assert_has_event(RuntimeEvent::Assets(crate::Event::ApprovalsDestroyed { - asset_id: 0, - approvals_destroyed: 0, - approvals_remaining: 0, - })); - // Partially destroyed Asset should continue to exist - assert!(Asset::::contains_key(0)); - - // Second call to destroy on PartiallyDestroyed asset - assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); - System::assert_has_event(RuntimeEvent::Assets(crate::Event::AccountsDestroyed { - asset_id: 0, - accounts_destroyed: 2, - accounts_remaining: 0, - })); - assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); - - System::assert_has_event(RuntimeEvent::Assets(crate::Event::Destroyed { asset_id: 0 })); - - // Destroyed Asset should not exist - assert!(!Asset::::contains_key(0)); - }) + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 3, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 4, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 5, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 6, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 7, 10)); + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + // Asset is in use, as all the accounts have not yet been destroyed. + // We need to call destroy_accounts or destroy_approvals again until asset is completely + // cleaned up. + assert_noop!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0), Error::::InUse); + + System::assert_has_event(RuntimeEvent::Assets(crate::Event::AccountsDestroyed { + asset_id: 0, + accounts_destroyed: 5, + accounts_remaining: 2, + })); + System::assert_has_event(RuntimeEvent::Assets(crate::Event::ApprovalsDestroyed { + asset_id: 0, + approvals_destroyed: 0, + approvals_remaining: 0, + })); + // Partially destroyed Asset should continue to exist + assert!(Asset::::contains_key(0)); + + // Second call to destroy on PartiallyDestroyed asset + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); + System::assert_has_event(RuntimeEvent::Assets(crate::Event::AccountsDestroyed { + asset_id: 0, + accounts_destroyed: 2, + accounts_remaining: 0, + })); + assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); + + System::assert_has_event(RuntimeEvent::Assets(crate::Event::Destroyed { asset_id: 0 })); + + // Destroyed Asset should not exist + assert!(!Asset::::contains_key(0)); + }) } #[test] fn non_providing_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - Balances::make_free_balance_be(&0, 100); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 0, 100)); + Balances::make_free_balance_be(&0, 100); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 0, 100)); - // Cannot mint into account 2 since it doesn't (yet) exist... - assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100), TokenError::CannotCreate); - // ...or transfer... - assert_noop!( + // Cannot mint into account 2 since it doesn't (yet) exist... + assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100), TokenError::CannotCreate); + // ...or transfer... + assert_noop!( Assets::transfer(RuntimeOrigin::signed(0), 0, 1, 50), TokenError::CannotCreate ); - // ...or force-transfer - assert_noop!( + // ...or force-transfer + assert_noop!( Assets::force_transfer(RuntimeOrigin::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(RuntimeOrigin::signed(0), 0, 1, 25)); - assert_ok!(Assets::force_transfer(RuntimeOrigin::signed(1), 0, 0, 2, 25)); - assert_eq!(asset_ids(), vec![0, 999]); - }); + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(0), 0, 1, 25)); + assert_ok!(Assets::force_transfer(RuntimeOrigin::signed(1), 0, 0, 2, 25)); + assert_eq!(asset_ids(), vec![0, 999]); + }); } #[test] fn min_balance_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10)); - assert_ok!(Assets::mint(RuntimeOrigin::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(RuntimeOrigin::signed(1), 0, 2, 9), TokenError::BelowMinimum); - assert_noop!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 9), TokenError::BelowMinimum); - assert_noop!( + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::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(RuntimeOrigin::signed(1), 0, 2, 9), TokenError::BelowMinimum); + assert_noop!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 9), TokenError::BelowMinimum); + assert_noop!( Assets::force_transfer(RuntimeOrigin::signed(1), 0, 1, 2, 9), TokenError::BelowMinimum ); - // When deducting from an account to below minimum, it should be reaped. - // Death by `transfer`. - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 91)); - assert!(Assets::maybe_balance(0, 1).is_none()); - assert_eq!(Assets::balance(0, 2), 100); - assert_eq!(Asset::::get(0).unwrap().accounts, 1); - assert_eq!(take_hooks(), vec![Hook::Died(0, 1)]); - - // Death by `force_transfer`. - assert_ok!(Assets::force_transfer(RuntimeOrigin::signed(1), 0, 2, 1, 91)); - assert!(Assets::maybe_balance(0, 2).is_none()); - assert_eq!(Assets::balance(0, 1), 100); - assert_eq!(Asset::::get(0).unwrap().accounts, 1); - assert_eq!(take_hooks(), vec![Hook::Died(0, 2)]); - - // Death by `burn`. - assert_ok!(Assets::burn(RuntimeOrigin::signed(1), 0, 1, 91)); - assert!(Assets::maybe_balance(0, 1).is_none()); - assert_eq!(Asset::::get(0).unwrap().accounts, 0); - assert_eq!(take_hooks(), vec![Hook::Died(0, 1)]); - - // Death by `transfer_approved`. - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 2); - assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 100)); - assert_ok!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 91)); - assert_eq!(take_hooks(), vec![Hook::Died(0, 1)]); - }); + // When deducting from an account to below minimum, it should be reaped. + // Death by `transfer`. + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 91)); + assert!(Assets::maybe_balance(0, 1).is_none()); + assert_eq!(Assets::balance(0, 2), 100); + assert_eq!(Asset::::get(0).unwrap().accounts, 1); + assert_eq!(take_hooks(), vec![ + Hook::Died(0, 1), + // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. + Hook::Died(0, 1) + ]); + + // Death by `force_transfer`. + assert_ok!(Assets::force_transfer(RuntimeOrigin::signed(1), 0, 2, 1, 91)); + assert!(Assets::maybe_balance(0, 2).is_none()); + assert_eq!(Assets::balance(0, 1), 100); + assert_eq!(Asset::::get(0).unwrap().accounts, 1); + assert_eq!(take_hooks(), vec![ + Hook::Died(0, 2), + // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. + Hook::Died(0, 2) + ]); + + // Death by `burn`. + assert_ok!(Assets::burn(RuntimeOrigin::signed(1), 0, 1, 91)); + assert!(Assets::maybe_balance(0, 1).is_none()); + assert_eq!(Asset::::get(0).unwrap().accounts, 0); + assert_eq!(take_hooks(), vec![ + Hook::Died(0, 1), + // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. + Hook::Died(0, 1) + ]); + + // Death by `transfer_approved`. + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + Balances::make_free_balance_be(&1, 2); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 100)); + assert_ok!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 91)); + assert_eq!(take_hooks(), vec![ + Hook::Died(0, 1), + // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. + Hook::Died(0, 1) + ]); + }); } #[test] fn querying_total_supply_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - assert_eq!(Assets::balance(0, 1), 50); - assert_eq!(Assets::balance(0, 2), 50); - assert_ok!(Assets::transfer(RuntimeOrigin::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(RuntimeOrigin::signed(1), 0, 3, u64::MAX)); - assert_eq!(Assets::total_supply(0), 69); - }); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 2), 50); + assert_ok!(Assets::transfer(RuntimeOrigin::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(RuntimeOrigin::signed(1), 0, 3, u64::MAX)); + 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(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - assert_eq!(Assets::balance(0, 1), 50); - assert_eq!(Assets::balance(0, 2), 50); - }); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::transfer(RuntimeOrigin::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(RuntimeOrigin::root(), 0, 1, true, 10)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_noop!( + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_noop!( Assets::transfer_keep_alive(RuntimeOrigin::signed(1), 0, 2, 91), Error::::BalanceLow ); - assert_ok!(Assets::transfer_keep_alive(RuntimeOrigin::signed(1), 0, 2, 90)); - assert_eq!(Assets::balance(0, 1), 10); - assert_eq!(Assets::balance(0, 2), 90); - assert!(hooks().is_empty()); - assert_eq!(asset_ids(), vec![0, 999]); - }); + assert_ok!(Assets::transfer_keep_alive(RuntimeOrigin::signed(1), 0, 2, 90)); + assert_eq!(Assets::balance(0, 1), 10); + assert_eq!(Assets::balance(0, 2), 90); + assert!(hooks().is_empty()); + assert_eq!(asset_ids(), vec![0, 999]); + }); } #[test] fn transferring_frozen_user_should_not_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 1)); - assert_noop!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50), Error::::Frozen); - assert_ok!(Assets::thaw(RuntimeOrigin::signed(1), 0, 1)); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - }); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 1)); + assert_noop!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50), Error::::Frozen); + assert_ok!(Assets::thaw(RuntimeOrigin::signed(1), 0, 1)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + }); } #[test] fn transferring_frozen_asset_should_not_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); - assert_noop!( + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + assert_noop!( Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50), Error::::AssetNotLive ); - assert_ok!(Assets::thaw_asset(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - }); + assert_ok!(Assets::thaw_asset(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + }); } #[test] fn approve_transfer_frozen_asset_should_not_work() { - new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); - assert_noop!( + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + assert_noop!( Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50), Error::::AssetNotLive ); - assert_ok!(Assets::thaw_asset(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - }); + assert_ok!(Assets::thaw_asset(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + }); } #[test] fn transferring_from_blocked_account_should_not_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::block(RuntimeOrigin::signed(1), 0, 1)); - // behaves as frozen when transferring from blocked - assert_noop!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50), Error::::Frozen); - assert_ok!(Assets::thaw(RuntimeOrigin::signed(1), 0, 1)); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50)); - }); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::block(RuntimeOrigin::signed(1), 0, 1)); + // behaves as frozen when transferring from blocked + assert_noop!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50), Error::::Frozen); + assert_ok!(Assets::thaw(RuntimeOrigin::signed(1), 0, 1)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50)); + }); } #[test] fn transferring_to_blocked_account_should_not_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_eq!(Assets::balance(0, 2), 100); - assert_ok!(Assets::block(RuntimeOrigin::signed(1), 0, 1)); - assert_noop!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50), TokenError::Blocked); - assert_ok!(Assets::thaw(RuntimeOrigin::signed(1), 0, 1)); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50)); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - }); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_eq!(Assets::balance(0, 2), 100); + assert_ok!(Assets::block(RuntimeOrigin::signed(1), 0, 1)); + assert_noop!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50), TokenError::Blocked); + assert_ok!(Assets::thaw(RuntimeOrigin::signed(1), 0, 1)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + }); } #[test] fn transfer_all_works_1() { - new_test_ext().execute_with(|| { - // setup - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 0, true, 100)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(0), 0, 1, 200)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(0), 0, 2, 100)); - // transfer all and allow death - assert_ok!(Assets::transfer_all(Some(1).into(), 0, 2, false)); - assert_eq!(Assets::balance(0, &1), 0); - assert_eq!(Assets::balance(0, &2), 300); - }); + new_test_ext().execute_with(|| { + // setup + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 0, true, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(0), 0, 1, 200)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(0), 0, 2, 100)); + // transfer all and allow death + assert_ok!(Assets::transfer_all(Some(1).into(), 0, 2, false)); + assert_eq!(Assets::balance(0, &1), 0); + assert_eq!(Assets::balance(0, &2), 300); + }); } #[test] fn transfer_all_works_2() { - new_test_ext().execute_with(|| { - // setup - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 0, true, 100)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(0), 0, 1, 200)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(0), 0, 2, 100)); - // transfer all and allow death - assert_ok!(Assets::transfer_all(Some(1).into(), 0, 2, true)); - assert_eq!(Assets::balance(0, &1), 100); - assert_eq!(Assets::balance(0, &2), 200); - }); + new_test_ext().execute_with(|| { + // setup + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 0, true, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(0), 0, 1, 200)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(0), 0, 2, 100)); + // transfer all and allow death + assert_ok!(Assets::transfer_all(Some(1).into(), 0, 2, true)); + assert_eq!(Assets::balance(0, &1), 100); + assert_eq!(Assets::balance(0, &2), 200); + }); } #[test] fn transfer_all_works_3() { - new_test_ext().execute_with(|| { - // setup - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 0, true, 100)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(0), 0, 1, 210)); - set_frozen_balance(0, 1, 10); - assert_ok!(Assets::mint(RuntimeOrigin::signed(0), 0, 2, 100)); - // transfer all and allow death w/ frozen - assert_ok!(Assets::transfer_all(Some(1).into(), 0, 2, false)); - assert_eq!(Assets::balance(0, &1), 100); - assert_eq!(Assets::balance(0, &2), 210); - }); + new_test_ext().execute_with(|| { + // setup + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 0, true, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(0), 0, 1, 210)); + set_frozen_balance(0, 1, 10); + assert_ok!(Assets::mint(RuntimeOrigin::signed(0), 0, 2, 100)); + // transfer all and allow death w/ frozen + assert_ok!(Assets::transfer_all(Some(1).into(), 0, 2, false)); + assert_eq!(Assets::balance(0, &1), 100); + assert_eq!(Assets::balance(0, &2), 210); + }); } #[test] fn origin_guards_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_noop!( + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_noop!( Assets::transfer_ownership(RuntimeOrigin::signed(2), 0, 2), Error::::NoPermission ); - assert_noop!( + assert_noop!( Assets::set_team(RuntimeOrigin::signed(2), 0, 2, 2, 2), Error::::NoPermission ); - assert_noop!(Assets::freeze(RuntimeOrigin::signed(2), 0, 1), Error::::NoPermission); - assert_noop!(Assets::thaw(RuntimeOrigin::signed(2), 0, 2), Error::::NoPermission); - assert_noop!( + assert_noop!(Assets::freeze(RuntimeOrigin::signed(2), 0, 1), Error::::NoPermission); + assert_noop!(Assets::thaw(RuntimeOrigin::signed(2), 0, 2), Error::::NoPermission); + assert_noop!( Assets::mint(RuntimeOrigin::signed(2), 0, 2, 100), Error::::NoPermission ); - assert_noop!( + assert_noop!( Assets::burn(RuntimeOrigin::signed(2), 0, 1, 100), Error::::NoPermission ); - assert_noop!( + assert_noop!( Assets::force_transfer(RuntimeOrigin::signed(2), 0, 1, 2, 100), Error::::NoPermission ); - assert_noop!( + assert_noop!( Assets::start_destroy(RuntimeOrigin::signed(2), 0), 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(RuntimeOrigin::signed(1), 0, 1, 1)); - assert_eq!(asset_ids(), vec![0, 999]); + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); + assert_eq!(asset_ids(), vec![0, 999]); - assert_eq!(Balances::reserved_balance(&1), 1); + assert_eq!(Balances::reserved_balance(&1), 1); - assert_ok!(Assets::transfer_ownership(RuntimeOrigin::signed(1), 0, 2)); - assert_eq!(Balances::reserved_balance(&2), 1); - assert_eq!(Balances::reserved_balance(&1), 0); + assert_ok!(Assets::transfer_ownership(RuntimeOrigin::signed(1), 0, 2)); + assert_eq!(Balances::reserved_balance(&2), 1); + assert_eq!(Balances::reserved_balance(&1), 0); - assert_noop!( + assert_noop!( Assets::transfer_ownership(RuntimeOrigin::signed(1), 0, 1), Error::::NoPermission ); - // Set metadata now and make sure that deposit gets transferred back. - assert_ok!(Assets::set_metadata( + // Set metadata now and make sure that deposit gets transferred back. + assert_ok!(Assets::set_metadata( RuntimeOrigin::signed(2), 0, vec![0u8; 10], vec![0u8; 10], 12 )); - assert_ok!(Assets::transfer_ownership(RuntimeOrigin::signed(2), 0, 1)); - assert_eq!(Balances::reserved_balance(&1), 22); - assert_eq!(Balances::reserved_balance(&2), 0); - }); + assert_ok!(Assets::transfer_ownership(RuntimeOrigin::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(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(2), 0, 2, 100)); - assert_ok!(Assets::freeze(RuntimeOrigin::signed(4), 0, 2)); - assert_ok!(Assets::thaw(RuntimeOrigin::signed(3), 0, 2)); - assert_ok!(Assets::force_transfer(RuntimeOrigin::signed(3), 0, 2, 3, 100)); - assert_ok!(Assets::burn(RuntimeOrigin::signed(3), 0, 3, 100)); - }); + assert_ok!(Assets::mint(RuntimeOrigin::signed(2), 0, 2, 100)); + assert_ok!(Assets::freeze(RuntimeOrigin::signed(4), 0, 2)); + assert_ok!(Assets::thaw(RuntimeOrigin::signed(3), 0, 2)); + assert_ok!(Assets::force_transfer(RuntimeOrigin::signed(3), 0, 2, 3, 100)); + assert_ok!(Assets::burn(RuntimeOrigin::signed(3), 0, 3, 100)); + }); } #[test] fn transferring_from_frozen_account_should_not_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_eq!(Assets::balance(0, 2), 100); - assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2)); - // can transfer to `2` - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - // cannot transfer from `2` - assert_noop!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 25), Error::::Frozen); - assert_eq!(Assets::balance(0, 1), 50); - assert_eq!(Assets::balance(0, 2), 150); - }); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_eq!(Assets::balance(0, 2), 100); + assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2)); + // can transfer to `2` + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + // cannot transfer from `2` + assert_noop!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 25), Error::::Frozen); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 2), 150); + }); } #[test] fn touching_and_freezing_account_with_zero_asset_balance_should_work() { - new_test_ext().execute_with(|| { - // need some deposit for the touch - Balances::make_free_balance_be(&2, 100); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_eq!(Assets::balance(0, 2), 0); - // cannot freeze an account that doesn't have an `Assets` entry - assert_noop!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2), Error::::NoAccount); - assert_ok!(Assets::touch(RuntimeOrigin::signed(2), 0)); - // now it can be frozen - assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2)); - // can transfer to `2` even though its frozen - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - // cannot transfer from `2` - assert_noop!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 25), Error::::Frozen); - assert_eq!(Assets::balance(0, 1), 50); - assert_eq!(Assets::balance(0, 2), 50); - }); + new_test_ext().execute_with(|| { + // need some deposit for the touch + Balances::make_free_balance_be(&2, 100); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_eq!(Assets::balance(0, 2), 0); + // cannot freeze an account that doesn't have an `Assets` entry + assert_noop!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2), Error::::NoAccount); + assert_ok!(Assets::touch(RuntimeOrigin::signed(2), 0)); + // now it can be frozen + assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2)); + // can transfer to `2` even though its frozen + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + // cannot transfer from `2` + assert_noop!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 25), Error::::Frozen); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 2), 50); + }); } #[test] fn touch_other_works() { - new_test_ext().execute_with(|| { - // 1 will be admin - // 2 will be freezer - // 4 will be an account attempting to execute `touch_other` - Balances::make_free_balance_be(&1, 100); - Balances::make_free_balance_be(&2, 100); - Balances::make_free_balance_be(&4, 100); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - assert_ok!(Assets::set_team(RuntimeOrigin::signed(1), 0, 1, 1, 2)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - // account `3` does not exist - assert!(!Account::::contains_key(0, &3)); - // creation of asset account `3` by account `4` fails - assert_noop!( + new_test_ext().execute_with(|| { + // 1 will be admin + // 2 will be freezer + // 4 will be an account attempting to execute `touch_other` + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + Balances::make_free_balance_be(&4, 100); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_ok!(Assets::set_team(RuntimeOrigin::signed(1), 0, 1, 1, 2)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + // account `3` does not exist + assert!(!Account::::contains_key(0, &3)); + // creation of asset account `3` by account `4` fails + assert_noop!( Assets::touch_other(RuntimeOrigin::signed(4), 0, 3), Error::::NoPermission ); - // creation of asset account `3` by admin `1` works - assert!(!Account::::contains_key(0, &3)); - assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 3)); - assert!(Account::::contains_key(0, &3)); - // creation of asset account `4` by freezer `2` works - assert!(!Account::::contains_key(0, &4)); - assert_ok!(Assets::touch_other(RuntimeOrigin::signed(2), 0, 4)); - assert!(Account::::contains_key(0, &4)); - }); + // creation of asset account `3` by admin `1` works + assert!(!Account::::contains_key(0, &3)); + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 3)); + assert!(Account::::contains_key(0, &3)); + // creation of asset account `4` by freezer `2` works + assert!(!Account::::contains_key(0, &4)); + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(2), 0, 4)); + assert!(Account::::contains_key(0, &4)); + }); } #[test] fn touch_other_and_freeze_works() { - new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - // account `2` does not exist - assert!(!Account::::contains_key(0, &2)); - // create account `2` with touch_other - assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 2)); - assert!(Account::::contains_key(0, &2)); - // now it can be frozen - assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2)); - // can transfer to `2` even though its frozen - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - // cannot transfer from `2` - assert_noop!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 25), Error::::Frozen); - assert_eq!(Assets::balance(0, 1), 50); - assert_eq!(Assets::balance(0, 2), 50); - }); + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + // account `2` does not exist + assert!(!Account::::contains_key(0, &2)); + // create account `2` with touch_other + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 2)); + assert!(Account::::contains_key(0, &2)); + // now it can be frozen + assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2)); + // can transfer to `2` even though its frozen + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + // cannot transfer from `2` + assert_noop!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 25), Error::::Frozen); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 2), 50); + }); } #[test] fn account_with_deposit_not_destroyed() { - new_test_ext().execute_with(|| { - // 1 will be the asset admin - // 2 will exist without balance but with deposit - Balances::make_free_balance_be(&1, 100); - Balances::make_free_balance_be(&2, 100); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_eq!(Assets::balance(0, 2), 0); - // case 1; account `2` not destroyed with a holder's deposit - assert_ok!(Assets::touch(RuntimeOrigin::signed(2), 0)); - assert_eq!(Balances::reserved_balance(&2), 10); - assert!(Account::::contains_key(0, &2)); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50)); - assert_eq!(Assets::balance(0, 2), 0); - assert!(Account::::contains_key(0, &2)); - - // destroy account `2` - assert_ok!(Assets::refund(RuntimeOrigin::signed(2), 0, false)); - assert!(!Account::::contains_key(0, &2)); - - // case 2; account `2` not destroyed with a deposit from `1` - assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 2)); - assert_eq!(Balances::reserved_balance(&1), 10); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50)); - assert!(Account::::contains_key(0, &2)); - }); + new_test_ext().execute_with(|| { + // 1 will be the asset admin + // 2 will exist without balance but with deposit + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_eq!(Assets::balance(0, 2), 0); + // case 1; account `2` not destroyed with a holder's deposit + assert_ok!(Assets::touch(RuntimeOrigin::signed(2), 0)); + assert_eq!(Balances::reserved_balance(&2), 10); + assert!(Account::::contains_key(0, &2)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50)); + assert_eq!(Assets::balance(0, 2), 0); + assert!(Account::::contains_key(0, &2)); + + // destroy account `2` + assert_ok!(Assets::refund(RuntimeOrigin::signed(2), 0, false)); + assert!(!Account::::contains_key(0, &2)); + + // case 2; account `2` not destroyed with a deposit from `1` + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 2)); + assert_eq!(Balances::reserved_balance(&1), 10); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50)); + assert!(Account::::contains_key(0, &2)); + }); } #[test] fn refund_other_should_fails() { - new_test_ext().execute_with(|| { - // 1 will be the asset admin - // 2 will be the asset freezer - // 3 will be created with deposit of 2 - Balances::make_free_balance_be(&1, 100); - Balances::make_free_balance_be(&2, 100); - Balances::make_free_balance_be(&3, 0); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::set_team(RuntimeOrigin::signed(1), 0, 1, 1, 2)); - assert!(!Account::::contains_key(0, &3)); - - // create asset account `3` with a deposit from freezer `2` - assert_ok!(Assets::touch_other(RuntimeOrigin::signed(2), 0, 3)); - assert_eq!(Balances::reserved_balance(&2), 10); - - // fail case; non-existing asset account `10` - assert_noop!( + new_test_ext().execute_with(|| { + // 1 will be the asset admin + // 2 will be the asset freezer + // 3 will be created with deposit of 2 + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + Balances::make_free_balance_be(&3, 0); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::set_team(RuntimeOrigin::signed(1), 0, 1, 1, 2)); + assert!(!Account::::contains_key(0, &3)); + + // create asset account `3` with a deposit from freezer `2` + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(2), 0, 3)); + assert_eq!(Balances::reserved_balance(&2), 10); + + // fail case; non-existing asset account `10` + assert_noop!( Assets::refund_other(RuntimeOrigin::signed(2), 0, 10), Error::::NoDeposit ); - // fail case; non-existing asset `3` - assert_noop!( + // fail case; non-existing asset `3` + assert_noop!( Assets::refund_other(RuntimeOrigin::signed(2), 1, 3), Error::::NoDeposit ); - // fail case; no `DepositFrom` for asset account `1` - assert_noop!( + // fail case; no `DepositFrom` for asset account `1` + assert_noop!( Assets::refund_other(RuntimeOrigin::signed(2), 0, 1), Error::::NoDeposit ); - // fail case; asset `0` is frozen - assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(2), 0)); - assert_noop!( + // fail case; asset `0` is frozen + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(2), 0)); + assert_noop!( Assets::refund_other(RuntimeOrigin::signed(2), 0, 3), Error::::AssetNotLive ); - assert_ok!(Assets::thaw_asset(RuntimeOrigin::signed(1), 0)); - // fail case; asset `1` is being destroyed - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 10, 1, true, 1)); - assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 10, 3)); - assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 10)); - assert_noop!( + assert_ok!(Assets::thaw_asset(RuntimeOrigin::signed(1), 0)); + // fail case; asset `1` is being destroyed + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 10, 1, true, 1)); + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 10, 3)); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 10)); + assert_noop!( Assets::refund_other(RuntimeOrigin::signed(2), 10, 3), Error::::AssetNotLive ); - assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 10)); - assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 10)); - // fail case; account is frozen - assert_ok!(Assets::freeze(RuntimeOrigin::signed(2), 0, 3)); - assert_noop!(Assets::refund_other(RuntimeOrigin::signed(2), 0, 3), Error::::Frozen); - assert_ok!(Assets::thaw(RuntimeOrigin::signed(1), 0, 3)); - // fail case; not a freezer or an admin - assert_noop!( + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 10)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 10)); + // fail case; account is frozen + assert_ok!(Assets::freeze(RuntimeOrigin::signed(2), 0, 3)); + assert_noop!(Assets::refund_other(RuntimeOrigin::signed(2), 0, 3), Error::::Frozen); + assert_ok!(Assets::thaw(RuntimeOrigin::signed(1), 0, 3)); + // fail case; not a freezer or an admin + assert_noop!( Assets::refund_other(RuntimeOrigin::signed(4), 0, 3), Error::::NoPermission ); - // fail case; would burn - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 3, 100)); - assert_noop!( + // fail case; would burn + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 3, 100)); + assert_noop!( Assets::refund_other(RuntimeOrigin::signed(1), 0, 3), Error::::WouldBurn ); - assert_ok!(Assets::burn(RuntimeOrigin::signed(1), 0, 3, 100)); - }) + assert_ok!(Assets::burn(RuntimeOrigin::signed(1), 0, 3, 100)); + }) } #[test] fn refund_other_works() { - new_test_ext().execute_with(|| { - // 1 will be the asset admin - // 2 will be the asset freezer - // 3 will be created with deposit of 2 - Balances::make_free_balance_be(&1, 100); - Balances::make_free_balance_be(&2, 100); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::set_team(RuntimeOrigin::signed(1), 0, 1, 1, 2)); - assert!(!Account::::contains_key(0, &3)); - assert_eq!(asset_account_counts(0), (0, 0)); - - // success case; freezer is depositor - assert_ok!(Assets::touch_other(RuntimeOrigin::signed(2), 0, 3)); - assert_eq!(Balances::reserved_balance(&2), 10); - assert_eq!(asset_account_counts(0), (1, 0)); - assert_ok!(Assets::refund_other(RuntimeOrigin::signed(2), 0, 3)); - assert_eq!(Balances::reserved_balance(&2), 0); - assert!(!Account::::contains_key(0, &3)); - assert_eq!(asset_account_counts(0), (0, 0)); - - // success case; admin is depositor - assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 3)); - assert_eq!(Balances::reserved_balance(&1), 10); - assert_eq!(asset_account_counts(0), (1, 0)); - assert_ok!(Assets::refund_other(RuntimeOrigin::signed(1), 0, 3)); - assert_eq!(Balances::reserved_balance(&1), 0); - assert!(!Account::::contains_key(0, &3)); - assert_eq!(asset_account_counts(0), (0, 0)); - }) + new_test_ext().execute_with(|| { + // 1 will be the asset admin + // 2 will be the asset freezer + // 3 will be created with deposit of 2 + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::set_team(RuntimeOrigin::signed(1), 0, 1, 1, 2)); + assert!(!Account::::contains_key(0, &3)); + assert_eq!(asset_account_counts(0), (0, 0)); + + // success case; freezer is depositor + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(2), 0, 3)); + assert_eq!(Balances::reserved_balance(&2), 10); + assert_eq!(asset_account_counts(0), (1, 0)); + assert_ok!(Assets::refund_other(RuntimeOrigin::signed(2), 0, 3)); + assert_eq!(Balances::reserved_balance(&2), 0); + assert!(!Account::::contains_key(0, &3)); + assert_eq!(asset_account_counts(0), (0, 0)); + + // success case; admin is depositor + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 3)); + assert_eq!(Balances::reserved_balance(&1), 10); + assert_eq!(asset_account_counts(0), (1, 0)); + assert_ok!(Assets::refund_other(RuntimeOrigin::signed(1), 0, 3)); + assert_eq!(Balances::reserved_balance(&1), 0); + assert!(!Account::::contains_key(0, &3)); + assert_eq!(asset_account_counts(0), (0, 0)); + }) } #[test] fn transferring_amount_more_than_available_balance_should_not_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - assert_eq!(Assets::balance(0, 1), 50); - assert_eq!(Assets::balance(0, 2), 50); - assert_ok!(Assets::burn(RuntimeOrigin::signed(1), 0, 1, u64::MAX)); - assert_eq!(Assets::balance(0, 1), 0); - assert_noop!( + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 2), 50); + assert_ok!(Assets::burn(RuntimeOrigin::signed(1), 0, 1, u64::MAX)); + assert_eq!(Assets::balance(0, 1), 0); + assert_noop!( Assets::transfer(RuntimeOrigin::signed(1), 0, 1, 50), Error::::NoAccount ); - assert_noop!( + assert_noop!( Assets::transfer(RuntimeOrigin::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(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 0)); - // `ForceCreated` and `Issued` but no `Transferred` event. - assert_eq!(System::events().len(), 2); - }); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 0)); + // `ForceCreated` and `Issued` but no `Transferred` event. + assert_eq!(System::events().len(), 2); + }); } #[test] fn transferring_more_units_than_total_supply_should_not_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_noop!( + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_noop!( Assets::transfer(RuntimeOrigin::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(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::burn(RuntimeOrigin::signed(1), 0, 1, u64::MAX)); - System::assert_last_event(RuntimeEvent::Assets(crate::Event::Burned { - asset_id: 0, - owner: 1, - balance: 100, - })); - assert_eq!(Assets::balance(0, 1), 0); - }); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::burn(RuntimeOrigin::signed(1), 0, 1, u64::MAX)); + System::assert_last_event(RuntimeEvent::Assets(crate::Event::Burned { + asset_id: 0, + owner: 1, + balance: 100, + })); + 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(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 2), 0); - assert_noop!( + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 2), 0); + assert_noop!( Assets::burn(RuntimeOrigin::signed(1), 0, 2, u64::MAX), Error::::NoAccount ); - assert_eq!(Assets::balance(0, 2), 0); - assert_eq!(Assets::total_supply(0), 100); - }); + 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!( + new_test_ext().execute_with(|| { + // Cannot add metadata to unknown asset + assert_noop!( Assets::set_metadata(RuntimeOrigin::signed(1), 0, vec![0u8; 10], vec![0u8; 10], 12), Error::::Unknown, ); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - // Cannot add metadata to unowned asset - assert_noop!( + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + // Cannot add metadata to unowned asset + assert_noop!( Assets::set_metadata(RuntimeOrigin::signed(2), 0, vec![0u8; 10], vec![0u8; 10], 12), Error::::NoPermission, ); - // Cannot add oversized metadata - assert_noop!( + // Cannot add oversized metadata + assert_noop!( Assets::set_metadata(RuntimeOrigin::signed(1), 0, vec![0u8; 100], vec![0u8; 10], 12), Error::::BadMetadata, ); - assert_noop!( + assert_noop!( Assets::set_metadata(RuntimeOrigin::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( + // Successfully add metadata and take deposit + Balances::make_free_balance_be(&1, 30); + assert_ok!(Assets::set_metadata( RuntimeOrigin::signed(1), 0, vec![0u8; 10], vec![0u8; 10], 12 )); - assert_eq!(Balances::free_balance(&1), 9); + assert_eq!(Balances::free_balance(&1), 9); - // Update deposit - assert_ok!(Assets::set_metadata( + // Update deposit + assert_ok!(Assets::set_metadata( RuntimeOrigin::signed(1), 0, vec![0u8; 10], vec![0u8; 5], 12 )); - assert_eq!(Balances::free_balance(&1), 14); - assert_ok!(Assets::set_metadata( + assert_eq!(Balances::free_balance(&1), 14); + assert_ok!(Assets::set_metadata( RuntimeOrigin::signed(1), 0, vec![0u8; 10], vec![0u8; 15], 12 )); - assert_eq!(Balances::free_balance(&1), 4); + assert_eq!(Balances::free_balance(&1), 4); - // Cannot over-reserve - assert_noop!( + // Cannot over-reserve + assert_noop!( Assets::set_metadata(RuntimeOrigin::signed(1), 0, vec![0u8; 20], vec![0u8; 20], 12), BalancesError::::InsufficientBalance, ); - // Clear Metadata - assert!(Metadata::::contains_key(0)); - assert_noop!( + // Clear Metadata + assert!(Metadata::::contains_key(0)); + assert_noop!( Assets::clear_metadata(RuntimeOrigin::signed(2), 0), Error::::NoPermission ); - assert_noop!(Assets::clear_metadata(RuntimeOrigin::signed(1), 1), Error::::Unknown); - assert_ok!(Assets::clear_metadata(RuntimeOrigin::signed(1), 0)); - assert!(!Metadata::::contains_key(0)); - }); + assert_noop!(Assets::clear_metadata(RuntimeOrigin::signed(1), 1), Error::::Unknown); + assert_ok!(Assets::clear_metadata(RuntimeOrigin::signed(1), 0)); + assert!(!Metadata::::contains_key(0)); + }); } /// Calling on `dead_account` should be either unreachable, or fail if either a freeze or some @@ -1311,53 +1331,53 @@ fn set_metadata_should_work() { /// `do_destry_accounts`. #[test] fn calling_dead_account_fails_if_freezes_or_balances_on_hold_exist_1() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 50)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - - set_frozen_balance(0, 1, 50); - // Cannot transfer out less than max(freezes, ed). This happens in - // `prep_debit` under `transfer_and_die`. Would not reach `dead_account`. - assert_noop!( + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 50)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + + set_frozen_balance(0, 1, 50); + // Cannot transfer out less than max(freezes, ed). This happens in + // `prep_debit` under `transfer_and_die`. Would not reach `dead_account`. + assert_noop!( Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 100), Error::::BalanceLow ); - assert_noop!( + assert_noop!( Assets::transfer_keep_alive(RuntimeOrigin::signed(1), 0, 2, 100), Error::::BalanceLow ); - assert_noop!( + assert_noop!( Assets::force_transfer(RuntimeOrigin::signed(1), 0, 1, 2, 100), Error::::BalanceLow ); - // Cannot start destroying the asset, because some accounts contain freezes - assert_noop!( + // Cannot start destroying the asset, because some accounts contain freezes + assert_noop!( Assets::start_destroy(RuntimeOrigin::signed(1), 0), Error::::ContainsFreezes ); - clear_frozen_balance(0, 1); + clear_frozen_balance(0, 1); - set_balance_on_hold(0, 1, 50); - // Cannot transfer out less than max(freezes, ed). This happens in - // `prep_debit` under `transfer_and_die`. Would not reach `dead_account`. - assert_noop!( + set_balance_on_hold(0, 1, 50); + // Cannot transfer out less than max(freezes, ed). This happens in + // `prep_debit` under `transfer_and_die`. Would not reach `dead_account`. + assert_noop!( Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 100), Error::::BalanceLow ); - assert_noop!( + assert_noop!( Assets::transfer_keep_alive(RuntimeOrigin::signed(1), 0, 2, 100), Error::::BalanceLow ); - assert_noop!( + assert_noop!( Assets::force_transfer(RuntimeOrigin::signed(1), 0, 1, 2, 100), Error::::BalanceLow ); - // Cannot start destroying the asset, because some accounts contain freezes - assert_noop!( + // Cannot start destroying the asset, because some accounts contain freezes + assert_noop!( Assets::start_destroy(RuntimeOrigin::signed(1), 0), Error::::ContainsHolds ); - }) + }) } /// Calling on `dead_account` should be either unreachable, or fail if either a freeze or some @@ -1368,205 +1388,216 @@ fn calling_dead_account_fails_if_freezes_or_balances_on_hold_exist_1() { /// This asserts for `dead_account` on `do_refund` and `do_refund_other`. #[test] fn calling_dead_account_fails_if_freezes_or_balances_on_hold_exist_2() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - - set_frozen_balance(0, 1, 50); - assert_noop!( + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + + set_frozen_balance(0, 1, 50); + assert_noop!( Assets::refund(RuntimeOrigin::signed(1), 0, true), Error::::ContainsFreezes ); - clear_frozen_balance(0, 1); + clear_frozen_balance(0, 1); - set_balance_on_hold(0, 1, 50); - assert_noop!( + set_balance_on_hold(0, 1, 50); + assert_noop!( Assets::refund(RuntimeOrigin::signed(1), 0, true), Error::::ContainsHolds ); - clear_balance_on_hold(0, 1); - assert_ok!(Assets::refund(RuntimeOrigin::signed(1), 0, true)); - }); + clear_balance_on_hold(0, 1); + assert_ok!(Assets::refund(RuntimeOrigin::signed(1), 0, true)); + }); - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 2)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 2)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); - set_frozen_balance(0, 2, 100); - assert_noop!( + set_frozen_balance(0, 2, 100); + assert_noop!( Assets::refund_other(RuntimeOrigin::signed(1), 0, 2), Error::::WouldBurn ); - clear_frozen_balance(0, 2); + clear_frozen_balance(0, 2); - // Note: It's not possible to set balance on hold for the maximum balance, - // as it `WouldBurn` because of how setting the balance works on mock. - set_balance_on_hold(0, 2, 99); - assert_noop!( + // Note: It's not possible to set balance on hold for the maximum balance, + // as it `WouldBurn` because of how setting the balance works on mock. + set_balance_on_hold(0, 2, 99); + assert_noop!( Assets::refund_other(RuntimeOrigin::signed(1), 0, 2), Error::::WouldBurn ); - clear_balance_on_hold(0, 2); - }) + clear_balance_on_hold(0, 2); + }) } /// Destroying an asset calls the `FrozenBalance::died` hooks of all accounts. #[test] fn destroy_accounts_calls_died_hooks() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 50)); - // Create account 1 and 2. - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); - // Destroy the accounts. - assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); - - // Accounts 1 and 2 died. - assert_eq!(hooks(), vec![Hook::Died(0, 1), Hook::Died(0, 2)]); - }) + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 50)); + // Create account 1 and 2. + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); + // Destroy the accounts. + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); + + // Accounts 1 and 2 died. + assert_eq!(hooks(), vec![ + Hook::Died(0, 1), + // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. + Hook::Died(0, 1), + Hook::Died(0, 2), + // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. + Hook::Died(0, 2) + ]); + }) } /// Destroying an asset calls the `FrozenBalance::died` hooks of all accounts. #[test] fn finish_destroy_asset_destroys_asset() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 50)); - // Destroy the accounts. - assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 50)); + // Destroy the accounts. + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); - // Asset is gone - assert!(Asset::::get(0).is_none()); - }) + // Asset is gone + assert!(Asset::::get(0).is_none()); + }) } #[test] fn freezer_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - - // freeze 50 of it. - set_frozen_balance(0, 1, 50); - - // Note: The amount to be transferred in this step changed deliberately from 20 to 30 - // (https://github.com/paritytech/polkadot-sdk/pull/4530/commits/2ab35354d86904c035b21a2229452841b79b0457) - // to reflect the change in how `reducible_balance` is calculated: from untouchable = ed + - // frozen, to untouchalbe = max(ed, frozen) - // - // This is done in this line so most of the remaining test is preserved without changes - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 30)); - // cannot transfer another 21 away as this would take the spendable balance (30) to below - // zero. - assert_noop!( + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + + // freeze 50 of it. + set_frozen_balance(0, 1, 50); + + // Note: The amount to be transferred in this step changed deliberately from 20 to 30 + // (https://github.com/paritytech/polkadot-sdk/pull/4530/commits/2ab35354d86904c035b21a2229452841b79b0457) + // to reflect the change in how `reducible_balance` is calculated: from untouchable = ed + + // frozen, to untouchalbe = max(ed, frozen) + // + // This is done in this line so most of the remaining test is preserved without changes + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 30)); + // cannot transfer another 21 away as this would take the spendable balance (30) to below + // zero. + assert_noop!( Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 21), Error::::BalanceLow ); - // create an approved transfer... - Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); - let e = Error::::BalanceLow; - // ...but that wont work either: - assert_noop!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 2, 21), e); - // a force transfer won't work also. - let e = Error::::BalanceLow; - assert_noop!(Assets::force_transfer(RuntimeOrigin::signed(1), 0, 1, 2, 21), e); + // create an approved transfer... + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + let e = Error::::BalanceLow; + // ...but that wont work either: + assert_noop!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 2, 21), e); + // a force transfer won't work also. + let e = Error::::BalanceLow; + assert_noop!(Assets::force_transfer(RuntimeOrigin::signed(1), 0, 1, 2, 21), e); - // reduce it to only 49 frozen... - set_frozen_balance(0, 1, 49); - // ...and it's all good: - assert_ok!(Assets::force_transfer(RuntimeOrigin::signed(1), 0, 1, 2, 21)); + // reduce it to only 49 frozen... + set_frozen_balance(0, 1, 49); + // ...and it's all good: + assert_ok!(Assets::force_transfer(RuntimeOrigin::signed(1), 0, 1, 2, 21)); - // and if we clear it, we can remove the account completely. - clear_frozen_balance(0, 1); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 49)); - assert_eq!(hooks(), vec![Hook::Died(0, 1)]); - }); + // and if we clear it, we can remove the account completely. + clear_frozen_balance(0, 1); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 49)); + assert_eq!(hooks(), vec![ + Hook::Died(0, 1), + // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. + Hook::Died(0, 1) + ]); + }); } #[test] fn freezing_and_holds_work() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); - - // Hold 50 of it - set_balance_on_hold(0, 1, 50); - assert_eq!(Assets::balance(0, 1), 50); - assert_eq!(TestHolder::balance_on_hold(0, &1), Some(50)); - - // Can freeze up to held + min_balance without affecting reducible - set_frozen_balance(0, 1, 59); - assert_eq!(Assets::reducible_balance(0, &1, true), Ok(40)); - set_frozen_balance(0, 1, 61); - assert_eq!(Assets::reducible_balance(0, &1, true), Ok(39)); - - // Increasing hold is not necessarily restricted by the frozen balance - set_balance_on_hold(0, 1, 62); - assert_eq!(Assets::reducible_balance(0, &1, true), Ok(28)); - - // Transfers are bound to the spendable amount - assert_noop!( + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + + // Hold 50 of it + set_balance_on_hold(0, 1, 50); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(TestHolder::balance_on_hold(0, &1), Some(50)); + + // Can freeze up to held + min_balance without affecting reducible + set_frozen_balance(0, 1, 59); + assert_eq!(Assets::reducible_balance(0, &1, true), Ok(40)); + set_frozen_balance(0, 1, 61); + assert_eq!(Assets::reducible_balance(0, &1, true), Ok(39)); + + // Increasing hold is not necessarily restricted by the frozen balance + set_balance_on_hold(0, 1, 62); + assert_eq!(Assets::reducible_balance(0, &1, true), Ok(28)); + + // Transfers are bound to the spendable amount + assert_noop!( Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 29), Error::::BalanceLow ); - // Approved transfers fail as well - Balances::make_free_balance_be(&1, 2); - assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 29)); - assert_noop!( + // Approved transfers fail as well + Balances::make_free_balance_be(&1, 2); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 29)); + assert_noop!( Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 2, 29), Error::::BalanceLow ); - // Also forced transfers fail - assert_noop!( + // Also forced transfers fail + assert_noop!( Assets::force_transfer(RuntimeOrigin::signed(1), 0, 1, 2, 29), Error::::BalanceLow ); - // ...but transferring up to spendable works - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 28)); - }); + // ...but transferring up to spendable works + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 28)); + }); } #[test] fn imbalances_should_work() { - use frame_support::traits::fungibles::Balanced; + use frame_support::traits::fungibles::Balanced; - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - let imb = Assets::issue(0, 100); - assert_eq!(Assets::total_supply(0), 100); - assert_eq!(imb.peek(), 100); + 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); + 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); + 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); - }); + assert!(Assets::resolve(&1, imb1).is_ok()); + assert_eq!(Assets::balance(0, 1), 30); + assert_eq!(Assets::total_supply(0), 30); + }); } #[test] fn force_metadata_should_work() { - new_test_ext().execute_with(|| { - // force set metadata works - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::force_set_metadata( + new_test_ext().execute_with(|| { + // force set metadata works + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_set_metadata( RuntimeOrigin::root(), 0, vec![0u8; 10], @@ -1574,11 +1605,11 @@ fn force_metadata_should_work() { 8, false )); - assert!(Metadata::::contains_key(0)); + assert!(Metadata::::contains_key(0)); - // overwrites existing metadata - let asset_original_metadata = Metadata::::get(0); - assert_ok!(Assets::force_set_metadata( + // overwrites existing metadata + let asset_original_metadata = Metadata::::get(0); + assert_ok!(Assets::force_set_metadata( RuntimeOrigin::root(), 0, vec![1u8; 10], @@ -1586,10 +1617,10 @@ fn force_metadata_should_work() { 8, false )); - assert_ne!(Metadata::::get(0), asset_original_metadata); + assert_ne!(Metadata::::get(0), asset_original_metadata); - // attempt to set metadata for non-existent asset class - assert_noop!( + // attempt to set metadata for non-existent asset class + assert_noop!( Assets::force_set_metadata( RuntimeOrigin::root(), 1, @@ -1601,9 +1632,9 @@ fn force_metadata_should_work() { Error::::Unknown ); - // string length limit check - let limit = 50usize; - assert_noop!( + // string length limit check + let limit = 50usize; + assert_noop!( Assets::force_set_metadata( RuntimeOrigin::root(), 0, @@ -1614,7 +1645,7 @@ fn force_metadata_should_work() { ), Error::::BadMetadata ); - assert_noop!( + assert_noop!( Assets::force_set_metadata( RuntimeOrigin::root(), 0, @@ -1626,30 +1657,30 @@ fn force_metadata_should_work() { Error::::BadMetadata ); - // force clear metadata works - assert!(Metadata::::contains_key(0)); - assert_ok!(Assets::force_clear_metadata(RuntimeOrigin::root(), 0)); - assert!(!Metadata::::contains_key(0)); + // force clear metadata works + assert!(Metadata::::contains_key(0)); + assert_ok!(Assets::force_clear_metadata(RuntimeOrigin::root(), 0)); + assert!(!Metadata::::contains_key(0)); - // Error handles clearing non-existent asset class - assert_noop!( + // Error handles clearing non-existent asset class + assert_noop!( Assets::force_clear_metadata(RuntimeOrigin::root(), 1), Error::::Unknown ); - }); + }); } #[test] fn force_asset_status_should_work() { - new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 10); - Balances::make_free_balance_be(&2, 10); - assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 30)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 50)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 150)); - - // force asset status to change min_balance > balance - assert_ok!(Assets::force_asset_status( + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 10); + Balances::make_free_balance_be(&2, 10); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 30)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 50)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 150)); + + // force asset status to change min_balance > balance + assert_ok!(Assets::force_asset_status( RuntimeOrigin::root(), 0, 1, @@ -1660,30 +1691,30 @@ fn force_asset_status_should_work() { true, false )); - assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 1), 50); - // account can receive assets for balance < min_balance - assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 1)); - assert_eq!(Assets::balance(0, 1), 51); + // account can receive assets for balance < min_balance + assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 1)); + assert_eq!(Assets::balance(0, 1), 51); - // account on outbound transfer will cleanup for balance < min_balance - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 1)); - assert_eq!(Assets::balance(0, 1), 0); + // account on outbound transfer will cleanup for balance < min_balance + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 1)); + assert_eq!(Assets::balance(0, 1), 0); - // won't create new account with balance below min_balance - assert_noop!( + // won't create new account with balance below min_balance + assert_noop!( Assets::transfer(RuntimeOrigin::signed(2), 0, 3, 50), TokenError::BelowMinimum ); - // force asset status will not execute for non-existent class - assert_noop!( + // force asset status will not execute for non-existent class + assert_noop!( Assets::force_asset_status(RuntimeOrigin::root(), 1, 1, 1, 1, 1, 90, true, false), Error::::Unknown ); - // account drains to completion when funds dip below min_balance - assert_ok!(Assets::force_asset_status( + // account drains to completion when funds dip below min_balance + assert_ok!(Assets::force_asset_status( RuntimeOrigin::root(), 0, 1, @@ -1694,29 +1725,29 @@ fn force_asset_status_should_work() { true, false )); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 110)); - assert_eq!(Assets::balance(0, 1), 200); - assert_eq!(Assets::balance(0, 2), 0); - assert_eq!(Assets::total_supply(0), 200); - }); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 110)); + assert_eq!(Assets::balance(0, 1), 200); + assert_eq!(Assets::balance(0, 2), 0); + assert_eq!(Assets::total_supply(0), 200); + }); } #[test] fn set_min_balance_should_work() { - new_test_ext().execute_with(|| { - let id = 42; - Balances::make_free_balance_be(&1, 10); - assert_ok!(Assets::create(RuntimeOrigin::signed(1), id, 1, 30)); - - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), id, 1, 100)); - // Won't execute because there is an asset holder. - assert_noop!( + new_test_ext().execute_with(|| { + let id = 42; + Balances::make_free_balance_be(&1, 10); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), id, 1, 30)); + + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), id, 1, 100)); + // Won't execute because there is an asset holder. + assert_noop!( Assets::set_min_balance(RuntimeOrigin::signed(1), id, 50), Error::::NoPermission ); - // Force asset status to make this a sufficient asset. - assert_ok!(Assets::force_asset_status( + // Force asset status to make this a sufficient asset. + assert_ok!(Assets::force_asset_status( RuntimeOrigin::root(), id, 1, @@ -1728,15 +1759,15 @@ fn set_min_balance_should_work() { false )); - // Won't execute because there is an account holding the asset and the asset is marked as - // sufficient. - assert_noop!( + // Won't execute because there is an account holding the asset and the asset is marked as + // sufficient. + assert_noop!( Assets::set_min_balance(RuntimeOrigin::signed(1), id, 10), Error::::NoPermission ); - // Make the asset not sufficient. - assert_ok!(Assets::force_asset_status( + // Make the asset not sufficient. + assert_ok!(Assets::force_asset_status( RuntimeOrigin::root(), id, 1, @@ -1748,63 +1779,63 @@ fn set_min_balance_should_work() { false )); - // Will execute because the new value of min_balance is less than the - // old value. 10 < 30 - assert_ok!(Assets::set_min_balance(RuntimeOrigin::signed(1), id, 10)); - assert_eq!(Asset::::get(id).unwrap().min_balance, 10); + // Will execute because the new value of min_balance is less than the + // old value. 10 < 30 + assert_ok!(Assets::set_min_balance(RuntimeOrigin::signed(1), id, 10)); + assert_eq!(Asset::::get(id).unwrap().min_balance, 10); - assert_ok!(Assets::burn(RuntimeOrigin::signed(1), id, 1, 100)); + assert_ok!(Assets::burn(RuntimeOrigin::signed(1), id, 1, 100)); - assert_ok!(Assets::set_min_balance(RuntimeOrigin::signed(1), id, 50)); - assert_eq!(Asset::::get(id).unwrap().min_balance, 50); - }); + assert_ok!(Assets::set_min_balance(RuntimeOrigin::signed(1), id, 50)); + assert_eq!(Asset::::get(id).unwrap().min_balance, 50); + }); } #[test] fn balance_conversion_should_work() { - new_test_ext().execute_with(|| { - use frame_support::traits::tokens::ConversionToAssetBalance; - - let id = 42; - assert_ok!(Assets::force_create(RuntimeOrigin::root(), id, 1, true, 10)); - let not_sufficient = 23; - assert_ok!(Assets::force_create(RuntimeOrigin::root(), not_sufficient, 1, false, 10)); - assert_eq!(asset_ids(), vec![23, 42, 999]); - assert_eq!( - BalanceToAssetBalance::::to_asset_balance(100, 1234), - Err(ConversionError::AssetMissing) - ); - assert_eq!( - BalanceToAssetBalance::::to_asset_balance( - 100, - not_sufficient - ), - Err(ConversionError::AssetNotSufficient) - ); - // 10 / 1 == 10 -> the conversion should 10x the value - assert_eq!( - BalanceToAssetBalance::::to_asset_balance(100, id), - Ok(100 * 10) - ); - }); + new_test_ext().execute_with(|| { + use frame_support::traits::tokens::ConversionToAssetBalance; + + let id = 42; + assert_ok!(Assets::force_create(RuntimeOrigin::root(), id, 1, true, 10)); + let not_sufficient = 23; + assert_ok!(Assets::force_create(RuntimeOrigin::root(), not_sufficient, 1, false, 10)); + assert_eq!(asset_ids(), vec![23, 42, 999]); + assert_eq!( + BalanceToAssetBalance::::to_asset_balance(100, 1234), + Err(ConversionError::AssetMissing) + ); + assert_eq!( + BalanceToAssetBalance::::to_asset_balance( + 100, + not_sufficient + ), + Err(ConversionError::AssetNotSufficient) + ); + // 10 / 1 == 10 -> the conversion should 10x the value + assert_eq!( + BalanceToAssetBalance::::to_asset_balance(100, id), + Ok(100 * 10) + ); + }); } #[test] fn assets_from_genesis_should_exist() { - new_test_ext().execute_with(|| { - assert_eq!(asset_ids(), vec![999]); - assert!(Metadata::::contains_key(999)); - assert_eq!(Assets::balance(999, 1), 100); - assert_eq!(Assets::total_supply(999), 100); - }); + new_test_ext().execute_with(|| { + assert_eq!(asset_ids(), vec![999]); + assert!(Metadata::::contains_key(999)); + assert_eq!(Assets::balance(999, 1), 100); + assert_eq!(Assets::total_supply(999), 100); + }); } #[test] fn querying_name_symbol_and_decimals_should_work() { - new_test_ext().execute_with(|| { - use frame_support::traits::fungibles::metadata::Inspect; - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::force_set_metadata( + new_test_ext().execute_with(|| { + use frame_support::traits::fungibles::metadata::Inspect; + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_set_metadata( RuntimeOrigin::root(), 0, vec![0u8; 10], @@ -1812,43 +1843,43 @@ fn querying_name_symbol_and_decimals_should_work() { 12, false )); - assert_eq!(Assets::name(0), vec![0u8; 10]); - assert_eq!(Assets::symbol(0), vec![1u8; 10]); - assert_eq!(Assets::decimals(0), 12); - }); + assert_eq!(Assets::name(0), vec![0u8; 10]); + assert_eq!(Assets::symbol(0), vec![1u8; 10]); + assert_eq!(Assets::decimals(0), 12); + }); } #[test] fn querying_allowance_should_work() { - new_test_ext().execute_with(|| { - use frame_support::traits::fungibles::approvals::{Inspect, Mutate}; - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 2); - assert_ok!(Assets::approve(0, &1, &2, 50)); - assert_eq!(Assets::allowance(0, &1, &2), 50); - // Transfer asset 0, from owner 1 and delegate 2 to destination 3 - assert_ok!(Assets::transfer_from(0, &1, &2, &3, 50)); - assert_eq!(Assets::allowance(0, &1, &2), 0); - }); + new_test_ext().execute_with(|| { + use frame_support::traits::fungibles::approvals::{Inspect, Mutate}; + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + Balances::make_free_balance_be(&1, 2); + assert_ok!(Assets::approve(0, &1, &2, 50)); + assert_eq!(Assets::allowance(0, &1, &2), 50); + // Transfer asset 0, from owner 1 and delegate 2 to destination 3 + assert_ok!(Assets::transfer_from(0, &1, &2, &3, 50)); + assert_eq!(Assets::allowance(0, &1, &2), 0); + }); } #[test] fn transfer_large_asset() { - new_test_ext().execute_with(|| { - let amount = u64::pow(2, 63) + 2; - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, amount)); - assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, amount - 1)); - }) + new_test_ext().execute_with(|| { + let amount = u64::pow(2, 63) + 2; + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, amount)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, amount - 1)); + }) } #[test] fn querying_roles_should_work() { - new_test_ext().execute_with(|| { - use frame_support::traits::fungibles::roles::Inspect; - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::set_team( + new_test_ext().execute_with(|| { + use frame_support::traits::fungibles::roles::Inspect; + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::set_team( RuntimeOrigin::signed(1), 0, // Issuer @@ -1858,253 +1889,253 @@ fn querying_roles_should_work() { // Freezer 4, )); - assert_eq!(Assets::owner(0), Some(1)); - assert_eq!(Assets::issuer(0), Some(2)); - assert_eq!(Assets::admin(0), Some(3)); - assert_eq!(Assets::freezer(0), Some(4)); - }); + assert_eq!(Assets::owner(0), Some(1)); + assert_eq!(Assets::issuer(0), Some(2)); + assert_eq!(Assets::admin(0), Some(3)); + assert_eq!(Assets::freezer(0), Some(4)); + }); } #[test] fn normal_asset_create_and_destroy_callbacks_should_work() { - new_test_ext().execute_with(|| { - assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_none()); - assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none()); + new_test_ext().execute_with(|| { + assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_none()); + assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none()); - Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); - assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_some()); - assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none()); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); + assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_some()); + assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none()); - assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); - // Callback still hasn't been invoked - assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none()); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + // Callback still hasn't been invoked + assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none()); - assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); - assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_some()); - }); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); + assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_some()); + }); } #[test] fn root_asset_create_should_work() { - new_test_ext().execute_with(|| { - assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_none()); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_some()); - assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none()); - }); + new_test_ext().execute_with(|| { + assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_none()); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_some()); + assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none()); + }); } #[test] fn asset_start_destroy_fails_if_there_are_holds_or_freezes() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - set_frozen_balance(0, 1, 50); - assert_noop!( + set_frozen_balance(0, 1, 50); + assert_noop!( Assets::start_destroy(RuntimeOrigin::signed(1), 0), Error::::ContainsFreezes ); - set_balance_on_hold(0, 1, 50); - assert_noop!( + set_balance_on_hold(0, 1, 50); + assert_noop!( Assets::start_destroy(RuntimeOrigin::signed(1), 0), Error::::ContainsHolds ); - clear_frozen_balance(0, 1); - clear_balance_on_hold(0, 1); + clear_frozen_balance(0, 1); + clear_balance_on_hold(0, 1); - assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); - }); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + }); } #[test] fn asset_create_and_destroy_is_reverted_if_callback_fails() { - new_test_ext().execute_with(|| { - // Asset creation fails due to callback failure - AssetsCallbackHandle::set_return_error(); - Balances::make_free_balance_be(&1, 100); - assert_noop!( + new_test_ext().execute_with(|| { + // Asset creation fails due to callback failure + AssetsCallbackHandle::set_return_error(); + Balances::make_free_balance_be(&1, 100); + assert_noop!( Assets::create(RuntimeOrigin::signed(1), 0, 1, 1), Error::::CallbackFailed ); - // Callback succeeds, so asset creation succeeds - AssetsCallbackHandle::set_return_ok(); - assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); + // Callback succeeds, so asset creation succeeds + AssetsCallbackHandle::set_return_ok(); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); - // Asset destroy should fail due to callback failure - AssetsCallbackHandle::set_return_error(); - assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); - assert_noop!( + // Asset destroy should fail due to callback failure + AssetsCallbackHandle::set_return_error(); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + assert_noop!( Assets::finish_destroy(RuntimeOrigin::signed(1), 0), Error::::CallbackFailed ); - }); + }); } #[test] fn multiple_transfer_alls_work_ok() { - new_test_ext().execute_with(|| { - // Only run PoC when the system pallet is enabled, since the underlying bug is in the - // system pallet it won't work with BalancesAccountStore - // Start with a balance of 100 - Balances::force_set_balance(RuntimeOrigin::root(), 1, 100).unwrap(); - // Emulate a sufficient, in reality this could be reached by transferring a sufficient - // asset to the account - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - // Spend the same balance multiple times - assert_ok!(Balances::transfer_all(RuntimeOrigin::signed(1), 1337, false)); - assert_ok!(Balances::transfer_all(RuntimeOrigin::signed(1), 1337, false)); + new_test_ext().execute_with(|| { + // Only run PoC when the system pallet is enabled, since the underlying bug is in the + // system pallet it won't work with BalancesAccountStore + // Start with a balance of 100 + Balances::force_set_balance(RuntimeOrigin::root(), 1, 100).unwrap(); + // Emulate a sufficient, in reality this could be reached by transferring a sufficient + // asset to the account + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + // Spend the same balance multiple times + assert_ok!(Balances::transfer_all(RuntimeOrigin::signed(1), 1337, false)); + assert_ok!(Balances::transfer_all(RuntimeOrigin::signed(1), 1337, false)); - assert_eq!(Balances::free_balance(&1), 0); - assert_eq!(Balances::free_balance(&1337), 100); - }); + assert_eq!(Balances::free_balance(&1), 0); + assert_eq!(Balances::free_balance(&1337), 100); + }); } #[test] fn weights_sane() { - let info = crate::Call::::create { id: 10, admin: 4, min_balance: 3 }.get_dispatch_info(); - assert_eq!(<() as crate::WeightInfo>::create(), info.call_weight); + let info = crate::Call::::create { id: 10, admin: 4, min_balance: 3 }.get_dispatch_info(); + assert_eq!(<() as crate::WeightInfo>::create(), info.call_weight); - let info = crate::Call::::finish_destroy { id: 10 }.get_dispatch_info(); - assert_eq!(<() as crate::WeightInfo>::finish_destroy(), info.call_weight); + let info = crate::Call::::finish_destroy { id: 10 }.get_dispatch_info(); + assert_eq!(<() as crate::WeightInfo>::finish_destroy(), info.call_weight); } #[test] fn asset_destroy_refund_existence_deposit() { - new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - Balances::make_free_balance_be(&1, 100); - let admin = 1; - let admin_origin = RuntimeOrigin::signed(admin); + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + let admin = 1; + let admin_origin = RuntimeOrigin::signed(admin); - let account2 = 2; // account with own deposit - let account3 = 3; // account with admin's deposit - Balances::make_free_balance_be(&account2, 100); + let account2 = 2; // account with own deposit + let account3 = 3; // account with admin's deposit + Balances::make_free_balance_be(&account2, 100); - assert_eq!(Balances::reserved_balance(&account2), 0); - assert_eq!(Balances::reserved_balance(&account3), 0); - assert_eq!(Balances::reserved_balance(&admin), 0); + assert_eq!(Balances::reserved_balance(&account2), 0); + assert_eq!(Balances::reserved_balance(&account3), 0); + assert_eq!(Balances::reserved_balance(&admin), 0); - assert_ok!(Assets::touch(RuntimeOrigin::signed(account2), 0)); - assert_ok!(Assets::touch_other(admin_origin.clone(), 0, account3)); + assert_ok!(Assets::touch(RuntimeOrigin::signed(account2), 0)); + assert_ok!(Assets::touch_other(admin_origin.clone(), 0, account3)); - assert_eq!(Balances::reserved_balance(&account2), 10); - assert_eq!(Balances::reserved_balance(&account3), 0); - assert_eq!(Balances::reserved_balance(&admin), 10); + assert_eq!(Balances::reserved_balance(&account2), 10); + assert_eq!(Balances::reserved_balance(&account3), 0); + assert_eq!(Balances::reserved_balance(&admin), 10); - assert_ok!(Assets::start_destroy(admin_origin.clone(), 0)); - assert_ok!(Assets::destroy_accounts(admin_origin.clone(), 0)); - assert_ok!(Assets::destroy_approvals(admin_origin.clone(), 0)); - assert_ok!(Assets::finish_destroy(admin_origin.clone(), 0)); + assert_ok!(Assets::start_destroy(admin_origin.clone(), 0)); + assert_ok!(Assets::destroy_accounts(admin_origin.clone(), 0)); + assert_ok!(Assets::destroy_approvals(admin_origin.clone(), 0)); + assert_ok!(Assets::finish_destroy(admin_origin.clone(), 0)); - assert_eq!(Balances::reserved_balance(&account2), 0); - assert_eq!(Balances::reserved_balance(&account3), 0); - assert_eq!(Balances::reserved_balance(&admin), 0); - }); + assert_eq!(Balances::reserved_balance(&account2), 0); + assert_eq!(Balances::reserved_balance(&account3), 0); + assert_eq!(Balances::reserved_balance(&admin), 0); + }); } #[test] fn increasing_or_decreasing_destroying_asset_should_not_work() { - new_test_ext().execute_with(|| { - use frame_support::traits::fungibles::Inspect; + new_test_ext().execute_with(|| { + use frame_support::traits::fungibles::Inspect; - let admin = 1; - let admin_origin = RuntimeOrigin::signed(admin); + let admin = 1; + let admin_origin = RuntimeOrigin::signed(admin); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, admin, true, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, admin, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); - assert_eq!(Assets::can_deposit(0, &1, 10, Provenance::Extant), DepositConsequence::Success); - assert_eq!(Assets::can_withdraw(0, &1, 10), WithdrawConsequence::<_>::Success); - assert_eq!(Assets::can_increase(0, &1, 10, false), DepositConsequence::Success); - assert_eq!(Assets::can_decrease(0, &1, 10, false), WithdrawConsequence::<_>::Success); + assert_eq!(Assets::can_deposit(0, &1, 10, Provenance::Extant), DepositConsequence::Success); + assert_eq!(Assets::can_withdraw(0, &1, 10), WithdrawConsequence::<_>::Success); + assert_eq!(Assets::can_increase(0, &1, 10, false), DepositConsequence::Success); + assert_eq!(Assets::can_decrease(0, &1, 10, false), WithdrawConsequence::<_>::Success); - assert_ok!(Assets::start_destroy(admin_origin, 0)); + assert_ok!(Assets::start_destroy(admin_origin, 0)); - assert_eq!( - Assets::can_deposit(0, &1, 10, Provenance::Extant), - DepositConsequence::UnknownAsset - ); - assert_eq!(Assets::can_withdraw(0, &1, 10), WithdrawConsequence::<_>::UnknownAsset); - assert_eq!(Assets::can_increase(0, &1, 10, false), DepositConsequence::UnknownAsset); - assert_eq!(Assets::can_decrease(0, &1, 10, false), WithdrawConsequence::<_>::UnknownAsset); - }); + assert_eq!( + Assets::can_deposit(0, &1, 10, Provenance::Extant), + DepositConsequence::UnknownAsset + ); + assert_eq!(Assets::can_withdraw(0, &1, 10), WithdrawConsequence::<_>::UnknownAsset); + assert_eq!(Assets::can_increase(0, &1, 10, false), DepositConsequence::UnknownAsset); + assert_eq!(Assets::can_decrease(0, &1, 10, false), WithdrawConsequence::<_>::UnknownAsset); + }); } #[test] fn asset_id_cannot_be_reused() { - new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); - // Asset id can be reused till auto increment is not enabled. - assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + // Asset id can be reused till auto increment is not enabled. + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); - assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); - assert!(!Asset::::contains_key(0)); + assert!(!Asset::::contains_key(0)); - // Asset id `0` is reused. - assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); - assert!(Asset::::contains_key(0)); + // Asset id `0` is reused. + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); + assert!(Asset::::contains_key(0)); - assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); - assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); - assert!(!Asset::::contains_key(0)); + assert!(!Asset::::contains_key(0)); - // Enable auto increment. Next asset id must be 5. - pallet::NextAssetId::::put(5); + // Enable auto increment. Next asset id must be 5. + pallet::NextAssetId::::put(5); - assert_noop!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1), Error::::BadAssetId); - assert_noop!(Assets::create(RuntimeOrigin::signed(1), 1, 1, 1), Error::::BadAssetId); - assert_noop!( + assert_noop!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1), Error::::BadAssetId); + assert_noop!(Assets::create(RuntimeOrigin::signed(1), 1, 1, 1), Error::::BadAssetId); + assert_noop!( Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1), Error::::BadAssetId ); - assert_noop!( + assert_noop!( Assets::force_create(RuntimeOrigin::root(), 1, 1, true, 1), Error::::BadAssetId ); - // Asset with id `5` is created. - assert_ok!(Assets::create(RuntimeOrigin::signed(1), 5, 1, 1)); - assert!(Asset::::contains_key(5)); + // Asset with id `5` is created. + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 5, 1, 1)); + assert!(Asset::::contains_key(5)); - // Destroy asset with id `6`. - assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 5)); - assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 5)); + // Destroy asset with id `6`. + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 5)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 5)); - assert!(!Asset::::contains_key(0)); + assert!(!Asset::::contains_key(0)); - // Asset id `5` cannot be reused. - assert_noop!(Assets::create(RuntimeOrigin::signed(1), 5, 1, 1), Error::::BadAssetId); + // Asset id `5` cannot be reused. + assert_noop!(Assets::create(RuntimeOrigin::signed(1), 5, 1, 1), Error::::BadAssetId); - assert_ok!(Assets::create(RuntimeOrigin::signed(1), 6, 1, 1)); - assert!(Asset::::contains_key(6)); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 6, 1, 1)); + assert!(Asset::::contains_key(6)); - // Destroy asset with id `6`. - assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 6)); - assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 6)); + // Destroy asset with id `6`. + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 6)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 6)); - assert!(!Asset::::contains_key(6)); + assert!(!Asset::::contains_key(6)); - // Asset id `6` cannot be reused with force. - assert_noop!( + // Asset id `6` cannot be reused with force. + assert_noop!( Assets::force_create(RuntimeOrigin::root(), 6, 1, false, 1), Error::::BadAssetId ); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 7, 1, false, 1)); - assert!(Asset::::contains_key(7)); - }); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 7, 1, false, 1)); + assert!(Asset::::contains_key(7)); + }); } From 6bebe7e403774ba3565cb54ecee3d7d10ad788d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Sun, 3 Nov 2024 19:31:45 -0500 Subject: [PATCH 54/59] change(pallet-assets): document the `balance` field of `AssetAccount` to reflect the changes in the balance model, so a first reader can understand the differences between _free_ balance, _reducible_ balance and balance _on hold_. --- substrate/frame/assets/src/types.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/substrate/frame/assets/src/types.rs b/substrate/frame/assets/src/types.rs index 3b4b82ad7273..8ea016bb0200 100644 --- a/substrate/frame/assets/src/types.rs +++ b/substrate/frame/assets/src/types.rs @@ -174,7 +174,13 @@ impl AccountStatus { #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] pub struct AssetAccount { - /// The balance. + /// The `free` balance of an asset account, which can be potentially used for an account, if + /// certain constraints are met. + /// + /// This balance is not the same as the _reducible_/_spendable_ balance of the asset account + /// (calculated on [`Pallet::reducible_balance`], and depends on freezes, defined on + /// [`Config::Freezer`], if any), and it's not the same as the _balance on hold_ (which depends + /// on [`Config::Holder`], if any). pub(super) balance: Balance, /// The status of the account. pub(super) status: AccountStatus, From 3e797b1dd93721725b7590b7c8d64acd150cc490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Tue, 5 Nov 2024 16:05:55 -0500 Subject: [PATCH 55/59] change(pallet-assets): assert deposit --- substrate/frame/assets/src/tests.rs | 65 +++++++++++++++++------------ 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/substrate/frame/assets/src/tests.rs b/substrate/frame/assets/src/tests.rs index b1a62389ca43..6db1ae42890f 100644 --- a/substrate/frame/assets/src/tests.rs +++ b/substrate/frame/assets/src/tests.rs @@ -210,10 +210,10 @@ fn refunding_calls_died_hook() { assert_eq!(Asset::::get(0).unwrap().accounts, 0); assert_eq!(hooks(), vec![ - Hook::Died(0, 1), - // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. - Hook::Died(0, 1) - ]); + Hook::Died(0, 1), + // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. + Hook::Died(0, 1) + ]); assert_eq!(asset_ids(), vec![0, 999]); }); } @@ -655,10 +655,10 @@ fn min_balance_should_work() { assert_eq!(Assets::balance(0, 2), 100); assert_eq!(Asset::::get(0).unwrap().accounts, 1); assert_eq!(take_hooks(), vec![ - Hook::Died(0, 1), - // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. - Hook::Died(0, 1) - ]); + Hook::Died(0, 1), + // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. + Hook::Died(0, 1) + ]); // Death by `force_transfer`. assert_ok!(Assets::force_transfer(RuntimeOrigin::signed(1), 0, 2, 1, 91)); @@ -666,20 +666,20 @@ fn min_balance_should_work() { assert_eq!(Assets::balance(0, 1), 100); assert_eq!(Asset::::get(0).unwrap().accounts, 1); assert_eq!(take_hooks(), vec![ - Hook::Died(0, 2), - // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. - Hook::Died(0, 2) - ]); + Hook::Died(0, 2), + // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. + Hook::Died(0, 2) + ]); // Death by `burn`. assert_ok!(Assets::burn(RuntimeOrigin::signed(1), 0, 1, 91)); assert!(Assets::maybe_balance(0, 1).is_none()); assert_eq!(Asset::::get(0).unwrap().accounts, 0); assert_eq!(take_hooks(), vec![ - Hook::Died(0, 1), - // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. - Hook::Died(0, 1) - ]); + Hook::Died(0, 1), + // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. + Hook::Died(0, 1) + ]); // Death by `transfer_approved`. assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); @@ -687,10 +687,10 @@ fn min_balance_should_work() { assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 100)); assert_ok!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 91)); assert_eq!(take_hooks(), vec![ - Hook::Died(0, 1), - // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. - Hook::Died(0, 1) - ]); + Hook::Died(0, 1), + // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. + Hook::Died(0, 1) + ]); }); } @@ -1395,10 +1395,23 @@ fn calling_dead_account_fails_if_freezes_or_balances_on_hold_exist_2() { assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); set_frozen_balance(0, 1, 50); + + let mut account = Account::::get(&0, &1) + .expect("account has already been touched; qed"); + let touch_deposit = account.reason.take_deposit().expect("account was created by touching it; qed"); + assert_noop!( Assets::refund(RuntimeOrigin::signed(1), 0, true), Error::::ContainsFreezes ); + + // Assert touch deposit is not tainted. + let deposit_after_noop = Account::::get(&0, &1) + .and_then(|mut account| { + account.reason.take_deposit() + }); + assert_eq!(deposit_after_noop, Some(touch_deposit)); + clear_frozen_balance(0, 1); set_balance_on_hold(0, 1, 50); @@ -1450,10 +1463,10 @@ fn destroy_accounts_calls_died_hooks() { // Accounts 1 and 2 died. assert_eq!(hooks(), vec![ Hook::Died(0, 1), - // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. + // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. Hook::Died(0, 1), Hook::Died(0, 2), - // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. + // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. Hook::Died(0, 2) ]); }) @@ -1517,10 +1530,10 @@ fn freezer_should_work() { clear_frozen_balance(0, 1); assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 49)); assert_eq!(hooks(), vec![ - Hook::Died(0, 1), - // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. - Hook::Died(0, 1) - ]); + Hook::Died(0, 1), + // Note: Hooks get called twice because the hook is called from `Holder` AND `Freezer`. + Hook::Died(0, 1) + ]); }); } From 595b838e57d87e4e25ce8b51216341f8e750f0ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Tue, 5 Nov 2024 16:10:56 -0500 Subject: [PATCH 56/59] fix(pallet-assets): typo (thanks to @gui1117 for the suggestion) --- substrate/frame/assets/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/assets/src/types.rs b/substrate/frame/assets/src/types.rs index 8ea016bb0200..3f1b6aeaf072 100644 --- a/substrate/frame/assets/src/types.rs +++ b/substrate/frame/assets/src/types.rs @@ -259,7 +259,7 @@ pub trait BalanceOnHold { /// Called after an account has been removed. /// - /// It is expected that this method is called only when there is not balance + /// It is expected that this method is called only when there is no balance /// on hold. Otherwise, an account should not be removed. fn died(asset: AssetId, who: &AccountId); From 17ba78cd474afb752e3e4702728b799584a368c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 6 Nov 2024 10:19:37 -0500 Subject: [PATCH 57/59] change(pallet-assets-holder): comment events as docstring (thanks @ggwpez for the suggestion) --- substrate/frame/assets-holder/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/substrate/frame/assets-holder/src/lib.rs b/substrate/frame/assets-holder/src/lib.rs index 26890e47c33e..ac63a252daaf 100644 --- a/substrate/frame/assets-holder/src/lib.rs +++ b/substrate/frame/assets-holder/src/lib.rs @@ -90,21 +90,21 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { - // `who`s balance on hold was increased by `amount`. + /// `who`s balance on hold was increased by `amount`. Held { who: T::AccountId, asset_id: T::AssetId, reason: T::RuntimeHoldReason, amount: T::Balance, }, - // `who`s balance on hold was decreased by `amount`. + /// `who`s balance on hold was decreased by `amount`. Released { who: T::AccountId, asset_id: T::AssetId, reason: T::RuntimeHoldReason, amount: T::Balance, }, - // `who`s balance on hold was burned by `amount`. + /// `who`s balance on hold was burned by `amount`. Burned { who: T::AccountId, asset_id: T::AssetId, From d2e2d18a96ffc0af8ad041dce1a0d9a22a1cd334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 6 Nov 2024 10:30:29 -0500 Subject: [PATCH 58/59] fix(pallet-assets): add additional storage layout on calls to `T::Currency::unreserve` that are followed by a potential failure of `dead_account`. This would prevent cases where `refund` is called externally by another pallet, and not as a part of a direct extrinsic call within the pallet. --- substrate/frame/assets/src/functions.rs | 114 +++++++++++++----------- 1 file changed, 61 insertions(+), 53 deletions(-) diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index 4ffbbcf0bc83..77b55c6b12ae 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -28,6 +28,7 @@ pub(super) enum DeadConsequence { } use DeadConsequence::*; +use frame_support::storage::with_storage_layer; // The main implementation block for the module. impl, I: 'static> Pallet { @@ -382,24 +383,27 @@ impl, I: 'static> Pallet { ensure!(matches!(details.status, Live | Frozen), Error::::IncorrectStatus); ensure!(account.balance.is_zero() || allow_burn, Error::::WouldBurn); - if let Some(deposit) = account.reason.take_deposit() { - T::Currency::unreserve(&who, deposit); - } + with_storage_layer(|| { + if let Some(deposit) = account.reason.take_deposit() { + T::Currency::unreserve(&who, deposit); + } - if let Remove = Self::dead_account(id.clone(), &who, &mut details, &account.reason, false)? - { - Account::::remove(&id, &who); - } else { - debug_assert!(false, "refund did not result in dead account?!"); - // deposit may have been refunded, need to update `Account` - Account::::insert(id, &who, account); - return Ok(()) - } - Asset::::insert(&id, details); - // Executing a hook here is safe, since it is not in a `mutate`. - T::Freezer::died(id.clone(), &who); - T::Holder::died(id, &who); - Ok(()) + if let Remove = Self::dead_account(id.clone(), &who, &mut details, &account.reason, false)? + { + Account::::remove(&id, &who); + } else { + debug_assert!(false, "refund did not result in dead account?!"); + // deposit may have been refunded, need to update `Account` + Account::::insert(id, &who, account); + return Ok(()) + } + + Asset::::insert(&id, details); + // Executing a hook here is safe, since it is not in a `mutate`. + T::Freezer::died(id.clone(), &who); + T::Holder::died(id, &who); + Ok(()) + }) } /// Refunds the `DepositFrom` of an account only if its balance is zero. @@ -422,22 +426,24 @@ impl, I: 'static> Pallet { } ensure!(account.balance.is_zero(), Error::::WouldBurn); - T::Currency::unreserve(&depositor, deposit); + with_storage_layer(|| { + T::Currency::unreserve(&depositor, deposit); - if let Remove = Self::dead_account(id.clone(), &who, &mut details, &account.reason, false)? - { - Account::::remove(&id, &who); - } else { - debug_assert!(false, "refund did not result in dead account?!"); - // deposit may have been refunded, need to update `Account` - Account::::insert(&id, &who, account); + if let Remove = Self::dead_account(id.clone(), &who, &mut details, &account.reason, false)? + { + Account::::remove(&id, &who); + } else { + debug_assert!(false, "refund did not result in dead account?!"); + // deposit may have been refunded, need to update `Account` + Account::::insert(&id, &who, account); + return Ok(()) + } + Asset::::insert(&id, details); + // Executing a hook here is safe, since it is not in a `mutate`. + T::Freezer::died(id.clone(), &who); + T::Holder::died(id, &who); return Ok(()) - } - Asset::::insert(&id, details); - // Executing a hook here is safe, since it is not in a `mutate`. - T::Freezer::died(id.clone(), &who); - T::Holder::died(id, &who); - return Ok(()) + }) } /// Increases the asset `id` balance of `beneficiary` by `amount`. @@ -821,29 +827,31 @@ impl, I: 'static> Pallet { let mut details = maybe_details.as_mut().ok_or(Error::::Unknown)?; // Should only destroy accounts while the asset is in a destroying state ensure!(details.status == AssetStatus::Destroying, Error::::IncorrectStatus); - for (i, (who, mut v)) in Account::::iter_prefix(&id).enumerate() { - // unreserve the existence deposit if any - if let Some((depositor, deposit)) = v.reason.take_deposit_from() { - T::Currency::unreserve(&depositor, deposit); - } else if let Some(deposit) = v.reason.take_deposit() { - T::Currency::unreserve(&who, deposit); - } - if let Remove = - Self::dead_account(id.clone(), &who, &mut details, &v.reason, false)? - { - Account::::remove(&id, &who); - dead_accounts.push(who); - } else { - // deposit may have been released, need to update `Account` - Account::::insert(&id, &who, v); - defensive!("destroy did not result in dead account?!"); - } - if i + 1 >= (max_items as usize) { - break + with_storage_layer(|| { + for (i, (who, mut v)) in Account::::iter_prefix(&id).enumerate() { + // unreserve the existence deposit if any + if let Some((depositor, deposit)) = v.reason.take_deposit_from() { + T::Currency::unreserve(&depositor, deposit); + } else if let Some(deposit) = v.reason.take_deposit() { + T::Currency::unreserve(&who, deposit); + } + if let Remove = + Self::dead_account(id.clone(), &who, &mut details, &v.reason, false)? + { + Account::::remove(&id, &who); + dead_accounts.push(who); + } else { + // deposit may have been released, need to update `Account` + Account::::insert(&id, &who, v); + defensive!("destroy did not result in dead account?!"); + } + if i + 1 >= (max_items as usize) { + break + } } - } - remaining_accounts = details.accounts; - Ok(()) + remaining_accounts = details.accounts; + Ok(()) + }) })?; for who in &dead_accounts { From c10123a17320e8bd084ce74f287502fa178f3d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 6 Nov 2024 11:11:02 -0500 Subject: [PATCH 59/59] fix(prdoc): extend explanation of changes in balance model to better illustrate the changes. --- prdoc/pr_4530.prdoc | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/prdoc/pr_4530.prdoc b/prdoc/pr_4530.prdoc index f5e02e93dc36..6984f861a76b 100644 --- a/prdoc/pr_4530.prdoc +++ b/prdoc/pr_4530.prdoc @@ -17,6 +17,37 @@ doc: account that can be withdrawn or transferred, and will affect the ability for these accounts to be destroyed. + ### Example + + Before the changes in the balance model, an asset account balance could look like something like this: + + ``` + |____________balance____________| + |__frozen__| + |__ed__| + |___untouchable___|__spendable__| + ``` + + In the previous model, you could spend funds up to `ed + frozen` where `ed` is the minimum balance for an asset + class, and `frozen` is the frozen amount (if any `freezes` are in place). + + Now, the model looks like this: + + ``` + |__total__________________________________| + |__on_hold__|_____________free____________| + |__________frozen___________| + |__on_hold__|__ed__| + |__untouchable__|__spendable__| + ``` + + There's now a balance `on_hold` and a `free` balance. The balance `on_hold` is managed by a `Holder` (typically + `pallet-assets-holder`) and `free` is the balance that remains in `pallet-assets`. The `frozen` amount can be + subsumed into the balance `on_hold`, and now you can spend funds up to `max(frozen, ed)`, so if for an account, + `frozen` is less or equal than `on_hold + ed`, you'd be able to spend your `free` balance up to `ed`. If for + the account, `frozen` is more than `on_hold + ed`, the remaining amount after subtracting `frozen` to + `on_hold + ed` is the amount you cannot spend from your `free` balance. + See [sdk docs](https://paritytech.github.io/polkadot-sdk/master/frame_support/traits/tokens/fungible/index.html#visualising-balance-components-together-) to understand how to calculate the spendable balance of an asset account on the client side.