From 640ce7c345d41de004658dc33e99fd3c73563c69 Mon Sep 17 00:00:00 2001 From: Nicolas Fernandez Date: Thu, 19 Oct 2023 15:33:07 +0200 Subject: [PATCH] Add hold traits to pallet assets --- Cargo.lock | 58 ++--- .../assets/asset-hub-kusama/src/lib.rs | 6 + .../assets/asset-hub-polkadot/src/lib.rs | 4 + .../assets/asset-hub-rococo/src/lib.rs | 6 + .../assets/asset-hub-westend/src/lib.rs | 8 +- .../runtimes/testing/penpal/src/lib.rs | 2 + .../testing/rococo-parachain/src/lib.rs | 2 + .../xcm/xcm-builder/src/tests/pay/mock.rs | 2 + substrate/bin/node/runtime/src/lib.rs | 4 + substrate/frame/asset-conversion/src/mock.rs | 4 + substrate/frame/assets/src/functions.rs | 48 ++--- substrate/frame/assets/src/impl_fungibles.rs | 100 +++++++++ substrate/frame/assets/src/lib.rs | 24 ++- substrate/frame/assets/src/mock.rs | 25 ++- substrate/frame/assets/src/tests.rs | 203 +++++++++++++++++- substrate/frame/assets/src/types.rs | 13 +- .../frame/nft-fractionalization/src/mock.rs | 4 + .../asset-conversion-tx-payment/src/mock.rs | 4 + .../asset-tx-payment/src/mock.rs | 2 + 19 files changed, 450 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b2d655e6f41c..a2d2dcff7384 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,9 +121,9 @@ dependencies = [ [[package]] name = "aes-gcm" -version = "0.10.3" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" dependencies = [ "aead 0.5.2", "aes 0.8.3", @@ -655,7 +655,7 @@ dependencies = [ "num-traits", "rusticata-macros", "thiserror", - "time 0.3.27", + "time", ] [[package]] @@ -671,7 +671,7 @@ dependencies = [ "num-traits", "rusticata-macros", "thiserror", - "time 0.3.27", + "time", ] [[package]] @@ -1493,8 +1493,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" dependencies = [ "bitcoin_hashes", - "rand 0.7.3", - "rand_core 0.5.1", + "rand 0.8.5", + "rand_core 0.6.4", "serde", "unicode-normalization", ] @@ -2652,15 +2652,14 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.27" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f56b4c72906975ca04becb8a30e102dfecddd0c06181e3e95ddc444be28881f8" +checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", - "time 0.1.45", "wasm-bindgen", "windows-targets 0.48.5", ] @@ -13836,9 +13835,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c" dependencies = [ "unicode-ident", ] @@ -14061,9 +14060,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.9.5" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c956be1b23f4261676aed05a0046e204e8a6836e50203902683a718af0797989" +checksum = "f31999cfc7927c4e212e60fd50934ab40e8e8bfd2d493d6095d2d306bc0764d9" dependencies = [ "bytes", "rand 0.8.5", @@ -14218,7 +14217,7 @@ checksum = "6413f3de1edee53342e6138e75b56d32e7bc6e332b3bd62d497b1929d4cfbcdd" dependencies = [ "pem", "ring 0.16.20", - "time 0.3.27", + "time", "x509-parser 0.13.2", "yasna", ] @@ -14231,7 +14230,7 @@ checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" dependencies = [ "pem", "ring 0.16.20", - "time 0.3.27", + "time", "yasna", ] @@ -18030,7 +18029,7 @@ dependencies = [ name = "sp-statement-store" version = "4.0.0-dev" dependencies = [ - "aes-gcm 0.10.3", + "aes-gcm 0.10.2", "curve25519-dalek 4.0.0", "ed25519-dalek", "hkdf", @@ -19431,17 +19430,6 @@ dependencies = [ "tikv-jemalloc-sys", ] -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - [[package]] name = "time" version = "0.3.27" @@ -20274,12 +20262,6 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -20782,7 +20764,7 @@ dependencies = [ "sha2 0.10.7", "stun", "thiserror", - "time 0.3.27", + "time", "tokio", "turn", "url", @@ -20819,7 +20801,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a00f4242f2db33307347bd5be53263c52a0331c96c14292118c9a6bb48d267" dependencies = [ "aes 0.6.0", - "aes-gcm 0.10.3", + "aes-gcm 0.10.2", "async-trait", "bincode", "block-modes", @@ -21429,7 +21411,7 @@ dependencies = [ "ring 0.16.20", "rusticata-macros", "thiserror", - "time 0.3.27", + "time", ] [[package]] @@ -21447,7 +21429,7 @@ dependencies = [ "oid-registry 0.6.1", "rusticata-macros", "thiserror", - "time 0.3.27", + "time", ] [[package]] @@ -21619,7 +21601,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" dependencies = [ - "time 0.3.27", + "time", ] [[package]] diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/lib.rs index af0116d7014a..6634e2583617 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/lib.rs @@ -286,6 +286,8 @@ impl pallet_assets::Config for Runtime { type CallbackHandle = (); type AssetAccountDeposit = AssetAccountDeposit; type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } @@ -324,6 +326,8 @@ impl pallet_assets::Config for Runtime { type Extra = (); type WeightInfo = weights::pallet_assets_pool::WeightInfo; type CallbackHandle = (); + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } @@ -398,6 +402,8 @@ impl pallet_assets::Config for Runtime { type CallbackHandle = (); type AssetAccountDeposit = ForeignAssetsAssetAccountDeposit; type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = xcm_config::XcmBenchmarkHelper; } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs index 1b7ef10f4857..ba922270765f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs @@ -294,6 +294,8 @@ impl pallet_assets::Config for Runtime { type CallbackHandle = (); type AssetAccountDeposit = AssetAccountDeposit; type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } @@ -336,6 +338,8 @@ impl pallet_assets::Config for Runtime { type CallbackHandle = (); type AssetAccountDeposit = ForeignAssetsAssetAccountDeposit; type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = xcm_config::XcmBenchmarkHelper; } 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 b274f45877b3..b6cf78bfa4ca 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -283,6 +283,8 @@ impl pallet_assets::Config for Runtime { type CallbackHandle = (); type AssetAccountDeposit = AssetAccountDeposit; type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } @@ -321,6 +323,8 @@ impl pallet_assets::Config for Runtime { type Extra = (); type WeightInfo = weights::pallet_assets_pool::WeightInfo; type CallbackHandle = (); + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } @@ -395,6 +399,8 @@ impl pallet_assets::Config for Runtime { type CallbackHandle = (); type AssetAccountDeposit = ForeignAssetsAssetAccountDeposit; type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = xcm_config::XcmBenchmarkHelper; } 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 d52edfe479ce..6af999c56b80 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -266,6 +266,8 @@ impl pallet_assets::Config for Runtime { type CallbackHandle = (); type AssetAccountDeposit = AssetAccountDeposit; type RemoveItemsLimit = ConstU32<1000>; + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } @@ -303,6 +305,8 @@ impl pallet_assets::Config for Runtime { type Extra = (); type WeightInfo = weights::pallet_assets_pool::WeightInfo; type CallbackHandle = (); + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } @@ -376,6 +380,8 @@ impl pallet_assets::Config for Runtime { type CallbackHandle = (); type AssetAccountDeposit = ForeignAssetsAssetAccountDeposit; type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = xcm_config::XcmBenchmarkHelper; } @@ -1692,7 +1698,7 @@ pub mod migrations { let pool_asset_id = pool_info.lp_token.clone(); if old_pool_id.0.as_ref() != &invalid_native_asset { // skip, if ok - continue + continue; } // fix new account diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 1ddad31920a5..ef7a7dad2565 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -453,6 +453,8 @@ impl pallet_assets::Config for Runtime { type CallbackHandle = (); type AssetAccountDeposit = AssetAccountDeposit; type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs index 6df00d43e8d3..2e14b0b506f2 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs @@ -576,6 +576,8 @@ impl pallet_assets::Config for Runtime { type CallbackHandle = (); type AssetAccountDeposit = AssetAccountDeposit; type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } diff --git a/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs b/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs index 78b9284c689f..8ad4164e1a1a 100644 --- a/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs +++ b/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs @@ -110,6 +110,8 @@ impl pallet_assets::Config for Test { type RemoveItemsLimit = RemoveItemsLimit; type AssetIdParameter = AssetIdForAssets; type CallbackHandle = (); + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index d7beb29becf4..56a4aedc325c 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1613,6 +1613,8 @@ impl pallet_assets::Config for Runtime { type CallbackHandle = (); type WeightInfo = pallet_assets::weights::SubstrateWeight; type RemoveItemsLimit = ConstU32<1000>; + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } @@ -1640,6 +1642,8 @@ impl pallet_assets::Config for Runtime { type WeightInfo = pallet_assets::weights::SubstrateWeight; type RemoveItemsLimit = ConstU32<1000>; type CallbackHandle = (); + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } diff --git a/substrate/frame/asset-conversion/src/mock.rs b/substrate/frame/asset-conversion/src/mock.rs index 4eee701f193e..d98fefeb1a1e 100644 --- a/substrate/frame/asset-conversion/src/mock.rs +++ b/substrate/frame/asset-conversion/src/mock.rs @@ -110,6 +110,8 @@ impl pallet_assets::Config for Test { type Extra = (); type WeightInfo = (); type CallbackHandle = (); + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; pallet_assets::runtime_benchmarks_enabled! { type BenchmarkHelper = (); } @@ -135,6 +137,8 @@ impl pallet_assets::Config for Test { type Extra = (); type WeightInfo = (); type CallbackHandle = (); + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; pallet_assets::runtime_benchmarks_enabled! { type BenchmarkHelper = (); } diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index c2c1b6839060..17a0b5d3a0ca 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -86,7 +86,7 @@ impl, I: 'static> Pallet { // allow accidental usage of all consumer references which could cause grief. if !frame_system::Pallet::::can_inc_consumer(who) { frame_system::Pallet::::dec_consumers(who); - return Err(Error::::UnavailableConsumer.into()) + return Err(Error::::UnavailableConsumer.into()); } ExistenceReason::Consumer }; @@ -133,24 +133,24 @@ impl, I: 'static> Pallet { None => return DepositConsequence::UnknownAsset, }; if increase_supply && details.supply.checked_add(&amount).is_none() { - return DepositConsequence::Overflow + return DepositConsequence::Overflow; } if let Some(account) = Account::::get(id, who) { if account.status.is_blocked() { - return DepositConsequence::Blocked + return DepositConsequence::Blocked; } if account.balance.checked_add(&amount).is_none() { - return DepositConsequence::Overflow + return DepositConsequence::Overflow; } } else { if amount < details.min_balance { - return DepositConsequence::BelowMinimum + return DepositConsequence::BelowMinimum; } if !details.is_sufficient && !frame_system::Pallet::::can_accrue_consumers(who, 2) { - return DepositConsequence::CannotCreate + return DepositConsequence::CannotCreate; } if details.is_sufficient && details.sufficients.checked_add(1).is_none() { - return DepositConsequence::Overflow + return DepositConsequence::Overflow; } } @@ -170,20 +170,20 @@ impl, I: 'static> Pallet { None => return UnknownAsset, }; if details.supply.checked_sub(&amount).is_none() { - return Underflow + return Underflow; } if details.status == AssetStatus::Frozen { - return Frozen + return Frozen; } if amount.is_zero() { - return Success + return Success; } let account = match Account::::get(&id, who) { Some(a) => a, None => return BalanceLow, }; if account.status.is_frozen() { - return Frozen + return Frozen; } if let Some(rest) = account.balance.checked_sub(&amount) { if let Some(frozen) = T::Freezer::frozen_balance(id.clone(), who) { @@ -267,7 +267,7 @@ impl, I: 'static> Pallet { Ok(dust) => actual.saturating_add(dust), //< guaranteed by reducible_balance Err(e) => { debug_assert!(false, "passed from reducible_balance; qed"); - return Err(e) + return Err(e); }, }; @@ -360,7 +360,7 @@ impl, I: 'static> Pallet { 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(()) + return Ok(()); } Asset::::insert(&id, details); // Executing a hook here is safe, since it is not in a `mutate`. @@ -391,12 +391,12 @@ impl, I: 'static> Pallet { 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(()) + return Ok(()); } Asset::::insert(&id, details); // Executing a hook here is safe, since it is not in a `mutate`. T::Freezer::died(id, &who); - return Ok(()) + return Ok(()); } /// Increases the asset `id` balance of `beneficiary` by `amount`. @@ -441,7 +441,7 @@ impl, I: 'static> Pallet { ) -> DispatchResult, ) -> DispatchResult { if amount.is_zero() { - return Ok(()) + return Ok(()); } Self::can_increase(id.clone(), beneficiary, amount, true).into_result()?; @@ -528,7 +528,7 @@ impl, I: 'static> Pallet { ) -> DispatchResult, ) -> Result { if amount.is_zero() { - return Ok(amount) + return Ok(amount); } let details = Asset::::get(&id).ok_or(Error::::Unknown)?; @@ -551,7 +551,7 @@ impl, I: 'static> Pallet { debug_assert!(account.balance.is_zero(), "checked in prep; qed"); target_died = Some(Self::dead_account(target, details, &account.reason, false)); if let Some(Remove) = target_died { - return Ok(()) + return Ok(()); } }; *maybe_account = Some(account); @@ -604,7 +604,7 @@ impl, I: 'static> Pallet { ) -> Result<(T::Balance, Option), DispatchError> { // Early exit if no-op. if amount.is_zero() { - return Ok((amount, None)) + return Ok((amount, None)); } let details = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); @@ -627,7 +627,7 @@ impl, I: 'static> Pallet { // Skip if source == dest if source == dest { - return Ok(()) + return Ok(()); } // Burn any dust if needed. @@ -672,7 +672,7 @@ impl, I: 'static> Pallet { Some(Self::dead_account(source, details, &source_account.reason, false)); if let Some(Remove) = source_died { Account::::remove(&id, &source); - return Ok(()) + return Ok(()); } } Account::::insert(&id, &source, &source_account); @@ -753,7 +753,7 @@ impl, I: 'static> Pallet { id: T::AssetId, max_items: u32, ) -> Result { - let mut dead_accounts: Vec = vec![]; + let mut dead_accounts: Vec = Vec::new(); let mut remaining_accounts = 0; let _ = Asset::::try_mutate_exists(&id, |maybe_details| -> Result<(), DispatchError> { @@ -776,7 +776,7 @@ impl, I: 'static> Pallet { defensive!("destroy did not result in dead account?!"); } if i + 1 >= (max_items as usize) { - break + break; } } remaining_accounts = details.accounts; @@ -817,7 +817,7 @@ impl, I: 'static> Pallet { removed_approvals = removed_approvals.saturating_add(1); details.approvals = details.approvals.saturating_sub(1); if removed_approvals >= max_items { - break + break; } } Self::deposit_event(Event::ApprovalsDestroyed { diff --git a/substrate/frame/assets/src/impl_fungibles.rs b/substrate/frame/assets/src/impl_fungibles.rs index 123abeba8283..905a033a7397 100644 --- a/substrate/frame/assets/src/impl_fungibles.rs +++ b/substrate/frame/assets/src/impl_fungibles.rs @@ -308,3 +308,103 @@ impl, I: 'static> fungibles::InspectEnumerable for Pa Asset::::iter_keys() } } + +impl, I: 'static> fungibles::MutateHold for Pallet {} + +impl, I: 'static> fungibles::InspectHold for Pallet { + type Reason = T::RuntimeHoldReason; + + fn total_balance_on_hold(asset: T::AssetId, who: &T::AccountId) -> T::Balance { + Holds::::get(who, asset) + .iter() + .map(|x| x.amount) + .fold(T::Balance::zero(), |acc, x| acc.saturating_add(x)) + } + + fn reducible_total_balance_on_hold( + asset: T::AssetId, + who: &T::AccountId, + _force: Fortitude, + ) -> Self::Balance { + let total_hold = Self::total_balance_on_hold(asset.clone(), who); + let free = Account::::get(asset.clone(), who) + .map(|account| account.balance) + .unwrap_or(Self::Balance::zero()); + // take alternative of unwrap + let ed = Asset::::get(asset).map(|x| x.min_balance).unwrap(); + + if free.saturating_sub(total_hold) < ed { + return total_hold.saturating_sub(ed); + } + total_hold + } + fn balance_on_hold(asset: T::AssetId, reason: &Self::Reason, who: &T::AccountId) -> T::Balance { + Holds::::get(who, asset) + .iter() + .find(|x| &x.id == reason) + .map_or_else(Zero::zero, |x| x.amount) + } + fn hold_available(asset: T::AssetId, reason: &Self::Reason, who: &T::AccountId) -> bool { + let asset_details = match Asset::::get(&asset) { + Some(details) => details, + None => return false, + }; + + let holds = Holds::::get(who, &asset); + + if !holds.is_full() && asset_details.is_sufficient { + return true; + } + + // Return true only if there are providers for the given 'who' + // and either holds is not full or there's a hold with the given reason. + frame_system::Pallet::::providers(who) > 0 && + (!holds.is_full() || holds.iter().any(|x| &x.id == reason)) + } +} + +impl, I: 'static> fungibles::UnbalancedHold for Pallet { + fn set_balance_on_hold( + asset: T::AssetId, + reason: &Self::Reason, + who: &T::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + let mut holds = Holds::::get(who, asset.clone()); + + if let Some(item) = holds.iter_mut().find(|x| &x.id == reason) { + let delta = item.amount.max(amount) - item.amount.min(amount); + let increase = amount > item.amount; + + if increase { + item.amount = item.amount.checked_add(&delta).ok_or(ArithmeticError::Overflow)? + } else { + item.amount = item.amount.checked_sub(&delta).ok_or(ArithmeticError::Underflow)? + }; + + holds.retain(|x| !x.amount.is_zero()); + } else { + if !amount.is_zero() { + holds + .try_push(IdAmount { id: *reason, amount }) + .map_err(|_| Error::::TooManyHolds)?; + } + } + + let account: Option> = Account::::get(&asset, &who); + + if let None = account { + let mut details = Asset::::get(&asset).ok_or(Error::::Unknown)?; + let new_account = AssetAccountOf:: { + balance: Zero::zero(), + status: AccountStatus::Liquid, + reason: Self::new_account(who, &mut details, None)?, + extra: T::Extra::default(), + }; + Account::::insert(&asset, &who, new_account); + } + + Holds::::insert(who, asset, holds); + Ok(()) + } +} diff --git a/substrate/frame/assets/src/lib.rs b/substrate/frame/assets/src/lib.rs index 79e4fe300187..006542005fcd 100644 --- a/substrate/frame/assets/src/lib.rs +++ b/substrate/frame/assets/src/lib.rs @@ -276,6 +276,8 @@ pub mod pallet { Success = Self::AccountId, >; + type RuntimeHoldReason: Parameter + Member + MaxEncodedLen + Copy; + /// The origin which may forcibly create or destroy an asset or otherwise alter privileged /// attributes. type ForceOrigin: EnsureOrigin; @@ -306,6 +308,10 @@ pub mod pallet { #[pallet::constant] type StringLimit: Get; + /// The maximum number of holds that can exist on an account at any time. + #[pallet::constant] + type MaxHolds: Get; + /// A hook to allow a per-asset, per-account minimum balance to be enforced. This must be /// respected in all permissionless operations. type Freezer: FrozenBalance; @@ -368,6 +374,18 @@ pub mod pallet { ValueQuery, >; + #[pallet::storage] + /// Holds on account balances. + pub type Holds, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Blake2_128Concat, + T::AssetId, + BoundedVec, T::MaxHolds>, + ValueQuery, + >; + #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig, I: 'static = ()> { @@ -571,6 +589,10 @@ pub mod pallet { NotFrozen, /// Callback action resulted in error CallbackFailed, + /// Number of holds exceed `MaxHolds` + TooManyHolds, + /// Error to update holds + HoldsNotUpdated, } #[pallet::call(weight(>::WeightInfo))] @@ -1069,7 +1091,7 @@ pub mod pallet { ensure!(details.status == AssetStatus::Live, Error::::LiveAsset); ensure!(origin == details.owner, Error::::NoPermission); if details.owner == owner { - return Ok(()) + return Ok(()); } let metadata_deposit = Metadata::::get(&id).deposit; diff --git a/substrate/frame/assets/src/mock.rs b/substrate/frame/assets/src/mock.rs index 2c2203bcdada..f4814ffb80a9 100644 --- a/substrate/frame/assets/src/mock.rs +++ b/substrate/frame/assets/src/mock.rs @@ -20,7 +20,7 @@ use super::*; use crate as pallet_assets; -use codec::Encode; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ construct_runtime, parameter_types, traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, @@ -29,7 +29,7 @@ use sp_core::H256; use sp_io::storage; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, + BuildStorage, RuntimeDebug, }; type Block = frame_system::mocking::MockBlock; @@ -132,6 +132,25 @@ impl AssetsCallbackHandle { } } +#[derive( + Encode, + Decode, + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + MaxEncodedLen, + TypeInfo, + RuntimeDebug, +)] +pub enum TestId { + Foo, + Bar, + Baz, +} + impl Config for Test { type RuntimeEvent = RuntimeEvent; type Balance = u64; @@ -151,6 +170,8 @@ impl Config for Test { type CallbackHandle = AssetsCallbackHandle; type Extra = (); type RemoveItemsLimit = ConstU32<5>; + type MaxHolds = ConstU32<10>; + type RuntimeHoldReason = TestId; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } diff --git a/substrate/frame/assets/src/tests.rs b/substrate/frame/assets/src/tests.rs index f1b116a0f4a0..25bff89b8a36 100644 --- a/substrate/frame/assets/src/tests.rs +++ b/substrate/frame/assets/src/tests.rs @@ -22,7 +22,16 @@ use crate::{mock::*, Error}; use frame_support::{ assert_noop, assert_ok, dispatch::GetDispatchInfo, - traits::{fungibles::InspectEnumerable, tokens::Preservation::Protect, Currency}, + traits::{ + fungibles::{InspectEnumerable, InspectHold, MutateHold}, + tokens::{ + Fortitude::{Force, Polite}, + Precision::{BestEffort, Exact}, + Preservation::Protect, + Restriction::Free, + }, + Currency, + }, }; use pallet_balances::Error as BalancesError; use sp_io::storage; @@ -1775,3 +1784,195 @@ fn asset_destroy_refund_existence_deposit() { assert_eq!(Balances::reserved_balance(&admin), 0); }); } + +#[test] +fn unbalanced_trait_set_balance_works() { + new_test_ext().execute_with(|| { + let asset = 0; + assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset, 1, false, 1)); + let admin = 1; + let dest = 2; // account with own deposit + Balances::make_free_balance_be(&admin, 100); + Balances::make_free_balance_be(&dest, 100); + + assert_eq!(>::balance(asset, &dest), 0); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), asset, dest, 100)); + assert_eq!(>::balance(asset, &dest), 100); + + assert_ok!(>::hold(asset, &TestId::Foo, &dest, 60)); + assert_eq!(>::balance(asset, &dest), 40); + assert_eq!(>::total_balance_on_hold(asset, &dest), 60); + assert_eq!( + >::balance_on_hold(asset, &TestId::Foo, &dest), + 60 + ); + + assert_eq!( + >::balance_on_hold(asset, &TestId::Foo, &dest), + 60 + ); + + assert_ok!(>::release( + asset, + &TestId::Foo, + &dest, + 30, + Exact + )); + + assert_eq!( + >::balance_on_hold(asset, &TestId::Foo, &dest), + 30 + ); + assert_eq!(>::total_balance_on_hold(asset, &dest), 30); + + assert_ok!(>::release( + asset, + &TestId::Foo, + &dest, + 30, + Exact + )); + + assert_eq!( + >::balance_on_hold(asset, &TestId::Foo, &dest), + 0 + ); + assert_eq!(>::total_balance_on_hold(asset, &dest), 0); + let holds = Holds::::get(&dest, asset); + assert_eq!(holds.len(), 0); + }); +} + +#[test] +fn transfer_and_hold_works() { + new_test_ext().execute_with(|| { + let asset = 0; + let admin = 1; + let source = 2; // account with own deposit + let dest = 3; // account with own deposit + assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset, admin, true, 1)); + + Balances::make_free_balance_be(&admin, 100); + Balances::make_free_balance_be(&source, 100); + + assert_eq!(>::balance(asset, &source), 0); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), asset, source, 100)); + + assert_eq!(>::balance(asset, &source), 100); + + assert_ok!(>::transfer_and_hold( + asset, + &TestId::Foo, + &source, + &dest, + 60, + Exact, + Protect, + Polite + )); + + assert_eq!(>::balance(asset, &source), 40); + assert_eq!( + >::balance_on_hold(asset, &TestId::Foo, &dest), + 60 + ); + assert_eq!(>::total_balance_on_hold(asset, &dest), 60); + + assert_ok!(>::release( + asset, + &TestId::Foo, + &dest, + 20, + Exact + )); + assert_eq!( + >::balance_on_hold(asset, &TestId::Foo, &dest), + 40 + ); + assert_eq!(>::balance(asset, &dest), 20); + }); +} + +#[test] +fn transfer_and_hold_does_not_work_freeze() { + new_test_ext().execute_with(|| { + let asset = 0; + let admin = 1; + let source = 2; // account with own deposit + let dest = 3; // account with own deposit + assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset, admin, true, 1)); + + Balances::make_free_balance_be(&admin, 100); + Balances::make_free_balance_be(&source, 100); + + assert_eq!(>::balance(asset, &source), 0); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), asset, source, 100)); + + assert_eq!(>::balance(asset, &source), 100); + + assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), asset, source)); + + assert_noop!( + >::transfer_and_hold( + asset, + &TestId::Foo, + &source, + &dest, + 60, + Exact, + Protect, + Polite + ), + Error::::Frozen + ); + }); +} + +#[test] +fn freezing_and_holds_should_overlap() { + new_test_ext().execute_with(|| { + set_frozen_balance(999, 1, 99); + assert_ok!(Assets::hold(999, &TestId::Foo, &1, 90)); + assert_eq!(Assets::balance(999, &1), 10); + assert_eq!(System::consumers(&1), 1); + assert_eq!(Assets::total_balance_on_hold(999, &1), 90); + }) +} + +#[test] +fn frozen_hold_balance_cannot_be_moved_without_force() { + new_test_ext().execute_with(|| { + set_frozen_balance(999, 1, 99); + assert_ok!(Assets::hold(999, &TestId::Foo, &1, 90)); + assert_eq!(Assets::reducible_total_balance_on_hold(999, &1, Force), 90); + assert_eq!(Assets::reducible_total_balance_on_hold(999, &1, Polite), 0); + assert_noop!( + Assets::transfer_on_hold(999, &TestId::Foo, &1, &2, 1, Exact, Free, Polite), + TokenError::Frozen + ); + assert_ok!(Assets::transfer_on_hold(999, &TestId::Foo, &1, &2, 1, Exact, Free, Force)); + }) +} + +#[test] +fn frozen_hold_balance_best_effort_transfer_works() { + new_test_ext().execute_with(|| { + set_frozen_balance(999, 1, 50); + assert_ok!(Assets::hold(999, &TestId::Foo, &1, 90)); + assert_eq!(Assets::reducible_total_balance_on_hold(999, &1, Force), 90); + assert_eq!(Assets::reducible_total_balance_on_hold(999, &1, Polite), 50); + assert_ok!(Assets::transfer_on_hold( + 999, + &TestId::Foo, + &1, + &2, + 1, + BestEffort, + Free, + Polite + )); + assert_eq!(Assets::balance(999, &1), 50); + assert_eq!(Assets::balance(999, &2), 50); + }) +} diff --git a/substrate/frame/assets/src/types.rs b/substrate/frame/assets/src/types.rs index 67f9bf07f5e7..f798079565b6 100644 --- a/substrate/frame/assets/src/types.rs +++ b/substrate/frame/assets/src/types.rs @@ -87,6 +87,15 @@ pub struct Approval { pub(super) deposit: DepositBalance, } +/// An identifier and balance. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub struct IdAmount { + /// An identifier for this item. + pub id: Id, + /// Some amount for this item. + pub amount: Balance, +} + #[test] fn ensure_bool_decodes_to_consumer_or_sufficient() { assert_eq!(false.encode(), ExistenceReason::<(), ()>::Consumer.encode()); @@ -120,7 +129,7 @@ where { pub(crate) fn take_deposit(&mut self) -> Option { if !matches!(self, ExistenceReason::DepositHeld(_)) { - return None + return None; } if let ExistenceReason::DepositHeld(deposit) = sp_std::mem::replace(self, ExistenceReason::DepositRefunded) @@ -133,7 +142,7 @@ where pub(crate) fn take_deposit_from(&mut self) -> Option<(AccountId, Balance)> { if !matches!(self, ExistenceReason::DepositFrom(..)) { - return None + return None; } if let ExistenceReason::DepositFrom(depositor, deposit) = sp_std::mem::replace(self, ExistenceReason::DepositRefunded) diff --git a/substrate/frame/nft-fractionalization/src/mock.rs b/substrate/frame/nft-fractionalization/src/mock.rs index 987c65a8954f..a7ed7f208278 100644 --- a/substrate/frame/nft-fractionalization/src/mock.rs +++ b/substrate/frame/nft-fractionalization/src/mock.rs @@ -111,9 +111,13 @@ impl pallet_assets::Config for Test { type Extra = (); type CallbackHandle = (); type WeightInfo = (); + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; pallet_assets::runtime_benchmarks_enabled! { type BenchmarkHelper = (); } + type RuntimeHoldReason = RuntimeHoldReason; + type MaxHolds = ConstU32<1>; } parameter_types! { 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 76c78fb42223..b5de2e52c210 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 @@ -192,6 +192,8 @@ impl pallet_assets::Config for Runtime { type CallbackHandle = (); type WeightInfo = (); type RemoveItemsLimit = ConstU32<1000>; + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; pallet_assets::runtime_benchmarks_enabled! { type BenchmarkHelper = (); } @@ -216,6 +218,8 @@ impl pallet_assets::Config for Runtime { type Extra = (); type WeightInfo = (); type CallbackHandle = (); + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; pallet_assets::runtime_benchmarks_enabled! { type BenchmarkHelper = (); } 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 5fa8a4ab27dd..1f2dd86f3553 100644 --- a/substrate/frame/transaction-payment/asset-tx-payment/src/mock.rs +++ b/substrate/frame/transaction-payment/asset-tx-payment/src/mock.rs @@ -165,6 +165,8 @@ impl pallet_assets::Config for Runtime { type CallbackHandle = (); type WeightInfo = (); type RemoveItemsLimit = ConstU32<1000>; + type MaxHolds = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; pallet_assets::runtime_benchmarks_enabled! { type BenchmarkHelper = (); }