diff --git a/runtime/kusama/src/xcm_config.rs b/runtime/kusama/src/xcm_config.rs index c7320a85a354..1806a06066dd 100644 --- a/runtime/kusama/src/xcm_config.rs +++ b/runtime/kusama/src/xcm_config.rs @@ -422,6 +422,8 @@ impl pallet_xcm::Config for Runtime { type TrustedLockers = (); type SovereignAccountOf = SovereignAccountOf; type MaxLockers = ConstU32<8>; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); type WeightInfo = crate::weights::pallet_xcm::WeightInfo; #[cfg(feature = "runtime-benchmarks")] type ReachableDest = ReachableDest; diff --git a/runtime/polkadot/src/xcm_config.rs b/runtime/polkadot/src/xcm_config.rs index e17b04a24edc..d154959781f9 100644 --- a/runtime/polkadot/src/xcm_config.rs +++ b/runtime/polkadot/src/xcm_config.rs @@ -417,6 +417,8 @@ impl pallet_xcm::Config for Runtime { type TrustedLockers = (); type SovereignAccountOf = SovereignAccountOf; type MaxLockers = ConstU32<8>; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); type WeightInfo = crate::weights::pallet_xcm::WeightInfo; #[cfg(feature = "runtime-benchmarks")] type ReachableDest = ReachableDest; diff --git a/runtime/rococo/src/xcm_config.rs b/runtime/rococo/src/xcm_config.rs index de3bdfde4c0b..4bf3cb3181ea 100644 --- a/runtime/rococo/src/xcm_config.rs +++ b/runtime/rococo/src/xcm_config.rs @@ -384,6 +384,8 @@ impl pallet_xcm::Config for Runtime { type TrustedLockers = (); type SovereignAccountOf = LocationConverter; type MaxLockers = ConstU32<8>; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); type WeightInfo = crate::weights::pallet_xcm::WeightInfo; #[cfg(feature = "runtime-benchmarks")] type ReachableDest = ReachableDest; diff --git a/runtime/test-runtime/src/xcm_config.rs b/runtime/test-runtime/src/xcm_config.rs index 12ff38b43f9f..8d7c57224ea6 100644 --- a/runtime/test-runtime/src/xcm_config.rs +++ b/runtime/test-runtime/src/xcm_config.rs @@ -144,6 +144,8 @@ impl pallet_xcm::Config for crate::Runtime { type TrustedLockers = (); type SovereignAccountOf = (); type MaxLockers = frame_support::traits::ConstU32<8>; + type MaxRemoteLockConsumers = frame_support::traits::ConstU32<0>; + type RemoteLockConsumerIdentifier = (); type WeightInfo = pallet_xcm::TestWeightInfo; #[cfg(feature = "runtime-benchmarks")] type ReachableDest = ReachableDest; diff --git a/runtime/westend/src/xcm_config.rs b/runtime/westend/src/xcm_config.rs index 148405c7e4e7..7e37d227a677 100644 --- a/runtime/westend/src/xcm_config.rs +++ b/runtime/westend/src/xcm_config.rs @@ -300,6 +300,8 @@ impl pallet_xcm::Config for Runtime { type TrustedLockers = (); type SovereignAccountOf = LocationConverter; type MaxLockers = ConstU32<8>; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); type WeightInfo = crate::weights::pallet_xcm::WeightInfo; #[cfg(feature = "runtime-benchmarks")] type ReachableDest = ReachableDest; diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 5d98aa027425..03c3fa0ba2f0 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -250,6 +250,12 @@ pub mod pallet { /// The maximum number of local XCM locks that a single account may have. type MaxLockers: Get; + /// The maximum number of consumers a single remote lock may have. + type MaxRemoteLockConsumers: Get; + + /// The ID type for local consumers of remote locks. + type RemoteLockConsumerIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy; + /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; @@ -445,7 +451,7 @@ pub mod pallet { FeesNotMet, /// A remote lock with the corresponding data could not be found. LockNotFound, - /// The unlock operation cannot succeed because there are still users of the lock. + /// The unlock operation cannot succeed because there are still consumers of the lock. InUse, } @@ -588,11 +594,26 @@ pub mod pallet { StorageValue<_, VersionMigrationStage, OptionQuery>; #[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo, MaxEncodedLen)] - pub struct RemoteLockedFungibleRecord { + #[scale_info(skip_type_params(MaxConsumers))] + pub struct RemoteLockedFungibleRecord> { + /// Total amount of the asset held by the remote lock. pub amount: u128, + /// The owner of the locked asset. pub owner: VersionedMultiLocation, + /// The location which holds the original lock. pub locker: VersionedMultiLocation, - pub users: u32, + /// Local consumers of the remote lock with a consumer identifier and the amount + /// of fungible asset every consumer holds. + /// Every consumer can hold up to total amount of the remote lock. + pub consumers: BoundedVec<(ConsumerIdentifier, u128), MaxConsumers>, + } + + impl> RemoteLockedFungibleRecord { + /// Amount of the remote lock in use by consumers. + /// Returns `None` if the remote lock has no consumers. + pub fn amount_held(&self) -> Option { + self.consumers.iter().max_by(|x, y| x.1.cmp(&y.1)).map(|max| max.1) + } } /// Fungible assets which we know are locked on a remote chain. @@ -604,7 +625,7 @@ pub mod pallet { NMapKey, NMapKey, ), - RemoteLockedFungibleRecord, + RemoteLockedFungibleRecord, OptionQuery, >; @@ -1693,11 +1714,12 @@ impl xcm_executor::traits::Enact for ReduceTicket { use xcm_executor::traits::LockError::UnexpectedState; let mut record = RemoteLockedFungibles::::get(&self.key).ok_or(UnexpectedState)?; ensure!(self.locker == record.locker && self.owner == record.owner, UnexpectedState); - ensure!(record.users == 0, UnexpectedState); - record.amount = record.amount.checked_sub(self.amount).ok_or(UnexpectedState)?; - if record.amount == 0 { + let new_amount = record.amount.checked_sub(self.amount).ok_or(UnexpectedState)?; + ensure!(record.amount_held().map_or(true, |h| new_amount >= h), UnexpectedState); + if new_amount == 0 { RemoteLockedFungibles::::remove(&self.key); } else { + record.amount = new_amount; RemoteLockedFungibles::::insert(&self.key, &record); } Ok(()) @@ -1757,11 +1779,12 @@ impl xcm_executor::traits::AssetLock for Pallet { let owner = owner.into(); let id: VersionedAssetId = asset.id.into(); let key = (XCM_VERSION, account, id); - let mut record = RemoteLockedFungibleRecord { amount, owner, locker, users: 0 }; + let mut record = + RemoteLockedFungibleRecord { amount, owner, locker, consumers: BoundedVec::default() }; if let Some(old) = RemoteLockedFungibles::::get(&key) { // Make sure that the new record wouldn't clobber any old data. ensure!(old.locker == record.locker && old.owner == record.owner, WouldClobber); - record.users = old.users; + record.consumers = old.consumers; record.amount = record.amount.max(old.amount); } RemoteLockedFungibles::::insert(&key, record); @@ -1788,8 +1811,11 @@ impl xcm_executor::traits::AssetLock for Pallet { let record = RemoteLockedFungibles::::get(&key).ok_or(NotLocked)?; // Make sure that the record contains what we expect and there's enough to unlock. ensure!(locker == record.locker && owner == record.owner, WouldClobber); - ensure!(record.users == 0, InUse); ensure!(record.amount >= amount, NotEnoughLocked); + ensure!( + record.amount_held().map_or(true, |h| record.amount.saturating_sub(amount) >= h), + InUse + ); Ok(ReduceTicket { key, amount, locker, owner }) } } diff --git a/xcm/pallet-xcm/src/mock.rs b/xcm/pallet-xcm/src/mock.rs index 0faef76f9eac..30ad6457986b 100644 --- a/xcm/pallet-xcm/src/mock.rs +++ b/xcm/pallet-xcm/src/mock.rs @@ -342,6 +342,8 @@ impl pallet_xcm::Config for Test { type Currency = Balances; type CurrencyMatcher = IsConcrete; type MaxLockers = frame_support::traits::ConstU32<8>; + type MaxRemoteLockConsumers = frame_support::traits::ConstU32<0>; + type RemoteLockConsumerIdentifier = (); type WeightInfo = TestWeightInfo; #[cfg(feature = "runtime-benchmarks")] type ReachableDest = ReachableDest; diff --git a/xcm/xcm-builder/tests/mock/mod.rs b/xcm/xcm-builder/tests/mock/mod.rs index 4bd1d78b5fbb..8f22b79e11bc 100644 --- a/xcm/xcm-builder/tests/mock/mod.rs +++ b/xcm/xcm-builder/tests/mock/mod.rs @@ -233,6 +233,8 @@ impl pallet_xcm::Config for Runtime { type Currency = Balances; type CurrencyMatcher = IsConcrete; type MaxLockers = frame_support::traits::ConstU32<8>; + type MaxRemoteLockConsumers = frame_support::traits::ConstU32<0>; + type RemoteLockConsumerIdentifier = (); type WeightInfo = pallet_xcm::TestWeightInfo; #[cfg(feature = "runtime-benchmarks")] type ReachableDest = ReachableDest; diff --git a/xcm/xcm-simulator/example/src/parachain.rs b/xcm/xcm-simulator/example/src/parachain.rs index 4d1f7f13c2a9..1985c721765e 100644 --- a/xcm/xcm-simulator/example/src/parachain.rs +++ b/xcm/xcm-simulator/example/src/parachain.rs @@ -423,6 +423,8 @@ impl pallet_xcm::Config for Runtime { type TrustedLockers = (); type SovereignAccountOf = LocationToAccountId; type MaxLockers = ConstU32<8>; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); type WeightInfo = pallet_xcm::TestWeightInfo; #[cfg(feature = "runtime-benchmarks")] type ReachableDest = ReachableDest; diff --git a/xcm/xcm-simulator/example/src/relay_chain.rs b/xcm/xcm-simulator/example/src/relay_chain.rs index e2caeb2bfc72..a6585b74c106 100644 --- a/xcm/xcm-simulator/example/src/relay_chain.rs +++ b/xcm/xcm-simulator/example/src/relay_chain.rs @@ -220,6 +220,8 @@ impl pallet_xcm::Config for Runtime { type TrustedLockers = (); type SovereignAccountOf = LocationToAccountId; type MaxLockers = ConstU32<8>; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); type WeightInfo = pallet_xcm::TestWeightInfo; #[cfg(feature = "runtime-benchmarks")] type ReachableDest = ReachableDest; diff --git a/xcm/xcm-simulator/fuzzer/src/parachain.rs b/xcm/xcm-simulator/fuzzer/src/parachain.rs index 3a0dfd735956..aace3b379c6a 100644 --- a/xcm/xcm-simulator/fuzzer/src/parachain.rs +++ b/xcm/xcm-simulator/fuzzer/src/parachain.rs @@ -338,6 +338,8 @@ impl pallet_xcm::Config for Runtime { type TrustedLockers = (); type SovereignAccountOf = LocationToAccountId; type MaxLockers = frame_support::traits::ConstU32<8>; + type MaxRemoteLockConsumers = frame_support::traits::ConstU32<0>; + type RemoteLockConsumerIdentifier = (); type WeightInfo = pallet_xcm::TestWeightInfo; #[cfg(feature = "runtime-benchmarks")] type ReachableDest = ReachableDest; diff --git a/xcm/xcm-simulator/fuzzer/src/relay_chain.rs b/xcm/xcm-simulator/fuzzer/src/relay_chain.rs index 8f0a8a281ec5..cb001d91f9b5 100644 --- a/xcm/xcm-simulator/fuzzer/src/relay_chain.rs +++ b/xcm/xcm-simulator/fuzzer/src/relay_chain.rs @@ -184,6 +184,8 @@ impl pallet_xcm::Config for Runtime { type TrustedLockers = (); type SovereignAccountOf = SovereignAccountOf; type MaxLockers = ConstU32<8>; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); type WeightInfo = pallet_xcm::TestWeightInfo; #[cfg(feature = "runtime-benchmarks")] type ReachableDest = ReachableDest;