diff --git a/Cargo.lock b/Cargo.lock index c254302517f00..4a81c2221e1a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1007,6 +1007,7 @@ dependencies = [ "sp-runtime", "sp-std", "staging-xcm", + "staging-xcm-builder", "staging-xcm-executor", "substrate-wasm-builder", ] diff --git a/cumulus/parachains/common/src/xcm_config.rs b/cumulus/parachains/common/src/xcm_config.rs index 146671441453d..aa1183f5d921e 100644 --- a/cumulus/parachains/common/src/xcm_config.rs +++ b/cumulus/parachains/common/src/xcm_config.rs @@ -18,10 +18,12 @@ use core::marker::PhantomData; use frame_support::{ traits::{fungibles::Inspect, tokens::ConversionToAssetBalance, ContainsPair}, weights::Weight, + DefaultNoBound, }; use log; use sp_runtime::traits::Get; use xcm::latest::prelude::*; +use xcm_builder::ExporterFor; /// A `ChargeFeeInFungibles` implementation that converts the output of /// a given WeightToFee implementation an amount charged in @@ -78,3 +80,141 @@ impl> ContainsPair matches!(asset.id, Concrete(ref id) if id == origin && origin == &Location::get()) } } + +/// Trait for matching `Location`. +pub trait MatchesLocation { + fn matches(&self, location: &Location) -> bool; +} + +/// Simple `MultiLocation` filter utility. +#[derive(Debug, DefaultNoBound)] +pub struct LocationFilter { + /// Requested location equals to `Location`. + pub equals_any: sp_std::vec::Vec, + /// Requested location starts with `Location`. + pub starts_with_any: sp_std::vec::Vec, +} + +impl LocationFilter { + pub fn add_equals(mut self, filter: Location) -> Self { + self.equals_any.push(filter); + self + } + pub fn add_starts_with(mut self, filter: Location) -> Self { + self.starts_with_any.push(filter); + self + } +} + +/// `MatchesLocation` implementation which works with `MultiLocation`. +impl MatchesLocation for LocationFilter { + fn matches(&self, location: &MultiLocation) -> bool { + for filter in &self.equals_any { + if location.eq(filter) { + return true + } + } + for filter in &self.starts_with_any { + if location.starts_with(filter) { + return true + } + } + false + } +} + +/// `MatchesLocation` implementation which works with `InteriorMultiLocation`. +impl MatchesLocation for LocationFilter { + fn matches(&self, location: &InteriorMultiLocation) -> bool { + for filter in &self.equals_any { + if location.eq(filter) { + return true + } + } + for filter in &self.starts_with_any { + if location.starts_with(filter) { + return true + } + } + false + } +} + +/// `FilteredNetworkExportTable` is adapter for `ExporterFor` implementation +/// which tries to find (`bridge_location`, `fees`) for requested `network` and `remote_location`. +/// +/// Inspired by `xcm_builder::NetworkExportTable`: +/// the main difference is that `NetworkExportTable` does not check `remote_location`. +pub struct FilteredNetworkExportTable(sp_std::marker::PhantomData); +impl< + T: Get< + sp_std::vec::Vec<( + NetworkId, + LocationFilter, + MultiLocation, + Option, + )>, + >, + > ExporterFor for FilteredNetworkExportTable +{ + fn exporter_for( + network: &NetworkId, + remote_location: &InteriorMultiLocation, + _: &Xcm<()>, + ) -> Option<(MultiLocation, Option)> { + T::get() + .into_iter() + .find(|(ref j, location_filter, ..)| { + j == network && location_filter.matches(remote_location) + }) + .map(|(_, _, bridge_location, p)| (bridge_location, p)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn filtered_network_export_table_works() { + frame_support::parameter_types! { + pub BridgeLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(1234))); + + pub BridgeTable: sp_std::vec::Vec<(NetworkId, LocationFilter, MultiLocation, Option)> = sp_std::vec![ + ( + Kusama, + LocationFilter::default() + .add_equals(X1(Parachain(2000))) + .add_equals(X1(Parachain(3000))) + .add_equals(X1(Parachain(4000))), + BridgeLocation::get(), + None + ) + ]; + } + + let test_data = vec![ + (Polkadot, X1(Parachain(1000)), None), + (Polkadot, X1(Parachain(2000)), None), + (Polkadot, X1(Parachain(3000)), None), + (Polkadot, X1(Parachain(4000)), None), + (Polkadot, X1(Parachain(5000)), None), + (Kusama, X1(Parachain(1000)), None), + (Kusama, X1(Parachain(2000)), Some((BridgeLocation::get(), None))), + (Kusama, X1(Parachain(3000)), Some((BridgeLocation::get(), None))), + (Kusama, X1(Parachain(4000)), Some((BridgeLocation::get(), None))), + (Kusama, X1(Parachain(5000)), None), + ]; + + for (network, remote_location, expected_result) in test_data { + assert_eq!( + FilteredNetworkExportTable::::exporter_for( + &network, + &remote_location, + &Xcm::default() + ), + expected_result, + ) + } + } +} 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 828d1b4750a3e..f8ebdefe6870d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/lib.rs @@ -1279,7 +1279,10 @@ impl_runtime_apis! { } fn universal_alias() -> Result<(MultiLocation, Junction), BenchmarkError> { - Err(BenchmarkError::Skip) + match xcm_config::bridging::BridgingBenchmarksHelper::prepare_universal_alias() { + Some(alias) => Ok(alias), + None => Err(BenchmarkError::Skip) + } } fn transact_origin_and_runtime_call() -> Result<(MultiLocation, RuntimeCall), BenchmarkError> { diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/xcm/mod.rs index 9aff4902d15ba..4bc3209a4e0fb 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/xcm/mod.rs @@ -220,7 +220,7 @@ impl XcmWeightInfo for AssetHubKusamaXcmWeight { XcmGeneric::::clear_transact_status() } fn universal_origin(_: &Junction) -> Weight { - Weight::MAX + XcmGeneric::::universal_origin() } fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { Weight::MAX diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 625549ecfccfa..6ab849e50a65f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -312,6 +312,16 @@ impl WeightInfo { // Minimum execution time: 2_886_000 picoseconds. Weight::from_parts(3_015_000, 0) } + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + pub fn universal_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1489` + // Minimum execution time: 5_088_000 picoseconds. + Weight::from_parts(5_253_000, 1489) + .saturating_add(T::DbWeight::get().reads(1)) + } pub fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/xcm_config.rs index 0c197598f8895..99e46a1b4fe6f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/xcm_config.rs @@ -18,7 +18,7 @@ use super::{ ParachainSystem, PolkadotXcm, PoolAssets, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, TrustBackedAssetsInstance, WeightToFee, XcmpQueue, }; -use crate::ForeignAssets; +use crate::{ForeignAssets, ForeignAssetsInstance}; use assets_common::{ local_and_foreign_assets::MatchesLocalAndForeignAssetsMultiLocation, matching::{ @@ -27,7 +27,7 @@ use assets_common::{ }; use frame_support::{ match_types, parameter_types, - traits::{ConstU32, Contains, Everything, Nothing, PalletInfoAccess}, + traits::{ConstU32, Contains, Everything, EverythingBut, Nothing, PalletInfoAccess}, }; use frame_system::EnsureRoot; use pallet_xcm::XcmPassthrough; @@ -39,11 +39,11 @@ use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, - EnsureXcmOrigin, FungiblesAdapter, HashedDescription, IsConcrete, LocalMint, NativeAsset, - NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + EnsureXcmOrigin, FungiblesAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, + IsConcrete, LocalMint, NativeAsset, NoChecking, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; @@ -80,6 +80,9 @@ pub type LocationToAccountId = ( AccountId32Aliases, // Foreign locations alias into accounts according to a hash of their standard description. HashedDescription>, + // Different global consensus parachain sovereign account. + // (Used for over-bridge transfers and reserve processing) + GlobalConsensusParachainConvertsFor, ); /// Means for transacting the native currency on this chain. @@ -463,6 +466,7 @@ pub type Barrier = TrailingSetTopicAsId< >, >; +/// Multiplier used for dedicated `TakeFirstAssetTrader` with `Assets` instance. pub type AssetFeeAsExistentialDepositMultiplierFeeCharger = AssetFeeAsExistentialDepositMultiplier< Runtime, WeightToFee, @@ -470,16 +474,25 @@ pub type AssetFeeAsExistentialDepositMultiplierFeeCharger = AssetFeeAsExistentia TrustBackedAssetsInstance, >; +/// Multiplier used for dedicated `TakeFirstAssetTrader` with `ForeignAssets` instance. +pub type ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger = + AssetFeeAsExistentialDepositMultiplier< + Runtime, + WeightToFee, + pallet_assets::BalanceToAssetBalance, + ForeignAssetsInstance, + >; + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; type AssetTransactor = AssetTransactors; type OriginConverter = XcmOriginToTransactDispatchOrigin; - // Asset Hub Kusama does not recognize a reserve location for any asset. This does not prevent - // Asset Hub acting _as_ a reserve location for KSM and assets created under `pallet-assets`. - // For KSM, users must use teleport where allowed (e.g. with the Relay Chain). - type IsReserve = (); + // Asset Hub trusts only particular configured bridge locations as reserve locations. + // Asset Hub may _act_ as a reserve location for KSM and assets created under `pallet-assets`. + // Users must use teleport where allowed (e.g. KSM with the Relay Chain). + type IsReserve = bridging::IsTrustedBridgedReserveLocationForConcreteAsset; // We allow: // - teleportation of KSM // - teleportation of sibling parachain's assets (as ForeignCreators) @@ -496,6 +509,8 @@ impl xcm_executor::Config for XcmConfig { >; type Trader = ( UsingComponents>, + // This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated + // `pallet_assets` instance - `Assets`. cumulus_primitives_utility::TakeFirstAssetTrader< AccountId, AssetFeeAsExistentialDepositMultiplierFeeCharger, @@ -507,6 +522,19 @@ impl xcm_executor::Config for XcmConfig { XcmAssetFeesReceiver, >, >, + // This trader allows to pay with `is_sufficient=true` "Foreign" assets from dedicated + // `pallet_assets` instance - `ForeignAssets`. + cumulus_primitives_utility::TakeFirstAssetTrader< + AccountId, + ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, + ForeignAssetsConvertedConcreteId, + ForeignAssets, + cumulus_primitives_utility::XcmFeesTo32ByteAccount< + ForeignFungiblesTransactor, + AccountId, + XcmAssetFeesReceiver, + >, + >, ); type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; @@ -518,7 +546,7 @@ impl xcm_executor::Config for XcmConfig { type AssetExchanger = (); type FeeManager = (); type MessageExporter = (); - type UniversalAliases = Nothing; + type UniversalAliases = bridging::UniversalAliases; type CallDispatcher = WithOriginFilter; type SafeCallFilter = SafeCallFilter; type Aliasers = Nothing; @@ -528,14 +556,17 @@ impl xcm_executor::Config for XcmConfig { /// Forms the basis for local origins sending/executing XCMs. pub type LocalOriginToLocation = SignedToAccountId32; -/// The means for routing XCM messages which are not for local execution into the right message -/// queues. -pub type XcmRouter = WithUniqueTopic<( +/// For routing XCM messages which do not cross local consensus boundary. +type LocalXcmRouter = ( // Two routers - use UMP to communicate with the relay chain: cumulus_primitives_utility::ParentAsUmp, // ..and XCMP to communicate with the sibling chains. XcmpQueue, -)>; +); + +/// The means for routing XCM messages which are not for local execution into the right message +/// queues. +pub type XcmRouter = WithUniqueTopic<(LocalXcmRouter, bridging::BridgingXcmRouter)>; #[cfg(feature = "runtime-benchmarks")] parameter_types! { @@ -554,7 +585,10 @@ impl pallet_xcm::Config for Runtime { type XcmExecuteFilter = Nothing; type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; - type XcmReserveTransferFilter = Everything; + // Allow reserve based transfer to everywhere except for bridging, here we strictly check what + // assets are allowed. + type XcmReserveTransferFilter = + EverythingBut; type Weigher = WeightInfoBounds< crate::weights::xcm::AssetHubKusamaXcmWeight, RuntimeCall, @@ -624,3 +658,118 @@ where sp_std::boxed::Box::new(Self::asset_id(asset_id)) } } + +/// All configuration related to bridging +pub mod bridging { + use super::*; + use assets_common::{matching, matching::*}; + use parachains_common::xcm_config::LocationFilter; + use sp_std::collections::btree_set::BTreeSet; + use xcm_builder::UnpaidRemoteExporter; + + parameter_types! { + pub BridgeHubKusamaParaId: u32 = 1002; + pub BridgeHubKusama: MultiLocation = MultiLocation::new(1, X1(Parachain(BridgeHubKusamaParaId::get()))); + pub BridgeHubKusamaWithBridgeHubPolkadotInstance: MultiLocation = MultiLocation::new(1, X2(Parachain(BridgeHubKusamaParaId::get()), PalletInstance(53))); + pub const PolkadotNetwork: NetworkId = NetworkId::Polkadot; + pub AssetHubPolkadot: MultiLocation = MultiLocation::new(2, X2(GlobalConsensus(PolkadotNetwork::get()), Parachain(1000))); + pub DotLocation: MultiLocation = MultiLocation::new(2, X1(GlobalConsensus(PolkadotNetwork::get()))); + + /// Set up exporters configuration. + pub BridgeTable: sp_std::vec::Vec<(NetworkId, LocationFilter, MultiLocation, Option)> = sp_std::vec![ + ( + PolkadotNetwork::get(), + LocationFilter::default() + // allow to bridge to AssetHubPolkadot + .add_equals(AssetHubPolkadot::get().interior.split_global().expect("invalid configuration for AssetHubPolkadot").1), + // and nothing else + BridgeHubKusama::get(), + None + ) + ]; + + /// Set up trusted bridged reserve locations. + /// Locations from which the runtime accepts reserved assets. + pub TrustedBridgedReserveLocations: sp_std::vec::Vec = sp_std::vec![ + // trust assets from AssetHubPolkadot + ( + AssetHubPolkadot::get(), + AssetFilter::ByMultiLocation( + LocationFilter::default() + // allow receive DOT + .add_equals(DotLocation::get()) + // and nothing else + ) + ) + ]; + + /// Allowed reserve transfer assets per destination. + /// Means that runtime allows to transfer reserve assets to these locations. + pub AllowedReserveTransferAssetsLocations: sp_std::vec::Vec = sp_std::vec![ + // allow to transfer assets to AssetHubPolkadot + ( + AssetHubPolkadot::get(), + AssetFilter::ByMultiLocation( + LocationFilter::default() + // allow send only KSM + .add_equals(KsmLocation::get()) + // and nothing else + ) + ) + ]; + + /// Universal aliases + pub UniversalAliases: BTreeSet<(MultiLocation, Junction)> = BTreeSet::from_iter( + sp_std::vec![ + (BridgeHubKusamaWithBridgeHubPolkadotInstance::get(), GlobalConsensus(PolkadotNetwork::get())) + ] + ); + } + + impl Contains<(MultiLocation, Junction)> for UniversalAliases { + fn contains(alias: &(MultiLocation, Junction)) -> bool { + UniversalAliases::get().contains(alias) + } + } + + pub type FilteredNetworkExportTable = + parachains_common::xcm_config::FilteredNetworkExportTable; + + /// Bridge router, which wraps and sends xcm to BridgeHub to be delivered to the different + /// GlobalConsensus + pub type BridgingXcmRouter = + UnpaidRemoteExporter; + + /// Reserve locations filter for `xcm_executor::Config::IsReserve`. + pub type IsTrustedBridgedReserveLocationForConcreteAsset = + matching::IsTrustedBridgedReserveLocationForConcreteAsset< + UniversalLocation, + TrustedBridgedReserveLocations, + >; + + /// Filter out those assets which are not allowed for bridged reserve based transfer. + /// (asset, location) filter for `pallet_xcm::Config::XcmReserveTransferFilter`. + pub type IsNotAllowedExplicitlyForReserveTransfer = ExcludeOnlyForRemoteDestination< + UniversalLocation, + FilteredNetworkExportTable, + IsNotAllowedConcreteAssetBy, + >; + + /// Benchmarks helper for bridging configuration. + #[cfg(feature = "runtime-benchmarks")] + pub struct BridgingBenchmarksHelper; + + #[cfg(feature = "runtime-benchmarks")] + impl BridgingBenchmarksHelper { + pub fn prepare_universal_alias() -> Option<(MultiLocation, Junction)> { + let alias = UniversalAliases::get().into_iter().find_map(|(location, junction)| { + match BridgeHubKusamaWithBridgeHubPolkadotInstance::get().eq(&location) { + true => Some((location, junction)), + false => None, + } + }); + assert!(alias.is_some(), "we expect here BridgeHubKusama to Polkadot mapping at least"); + Some(alias.unwrap()) + } + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/tests/tests.rs index 7d49b56e461a0..106a09d4e55c5 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-kusama/tests/tests.rs @@ -21,17 +21,20 @@ use asset_hub_kusama_runtime::xcm_config::{ AssetFeeAsExistentialDepositMultiplierFeeCharger, KsmLocation, TrustBackedAssetsPalletLocation, }; pub use asset_hub_kusama_runtime::{ - xcm_config::{CheckingAccount, ForeignCreatorsSovereignAccountOf, XcmConfig}, + xcm_config::{ + self, bridging, CheckingAccount, ForeignCreatorsSovereignAccountOf, LocationToAccountId, + XcmConfig, + }, AllPalletsWithoutSystem, AssetDeposit, Assets, Balances, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, Runtime, - RuntimeCall, RuntimeEvent, SessionKeys, System, TrustBackedAssetsInstance, + RuntimeCall, RuntimeEvent, SessionKeys, System, TrustBackedAssetsInstance, XcmpQueue, }; -use asset_test_utils::{CollatorSessionKeys, ExtBuilder}; +use asset_test_utils::{CollatorSessionKey, CollatorSessionKeys, ExtBuilder}; use codec::{Decode, Encode}; use cumulus_primitives_utility::ChargeWeightInFungibles; use frame_support::{ assert_noop, assert_ok, - traits::fungibles::InspectEnumerable, + traits::{fungibles::InspectEnumerable, Contains}, weights::{Weight, WeightToFee as WeightToFeeT}, }; use parachains_common::{ @@ -49,14 +52,18 @@ type AssetIdForTrustBackedAssetsConvert = type RuntimeHelper = asset_test_utils::RuntimeHelper; -fn collator_session_keys() -> CollatorSessionKeys { - CollatorSessionKeys::new( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, +fn collator_session_key(account: [u8; 32]) -> CollatorSessionKey { + CollatorSessionKey::new( + AccountId::from(account), + AccountId::from(account), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(account)) }, ) } +fn collator_session_keys() -> CollatorSessionKeys { + CollatorSessionKeys::default().add(collator_session_key(ALICE)) +} + #[test] fn test_asset_xcm_trader() { ExtBuilder::::default() @@ -632,3 +639,148 @@ asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_p assert_eq!(ForeignAssets::asset_ids().collect::>().len(), 1); }) ); + +fn bridging_to_asset_hub_polkadot() -> asset_test_utils::test_cases_over_bridge::TestBridgingConfig +{ + asset_test_utils::test_cases_over_bridge::TestBridgingConfig { + bridged_network: bridging::PolkadotNetwork::get(), + local_bridge_hub_para_id: bridging::BridgeHubKusamaParaId::get(), + local_bridge_hub_location: bridging::BridgeHubKusama::get(), + bridged_target_location: bridging::AssetHubPolkadot::get(), + } +} + +#[test] +fn limited_reserve_transfer_assets_for_native_asset_over_bridge_works() { + asset_test_utils::test_cases_over_bridge::limited_reserve_transfer_assets_for_native_asset_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ParachainSystem, + XcmpQueue, + LocationToAccountId, + >( + collator_session_keys(), + ExistentialDeposit::get(), + AccountId::from(ALICE), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), + _ => None, + } + }), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), + _ => None, + } + }), + bridging_to_asset_hub_polkadot, + WeightLimit::Unlimited, + ) +} + +#[test] +fn receive_reserve_asset_deposited_dot_from_asset_hub_polkadot_works() { + const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32]; + asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + LocationToAccountId, + ForeignAssetsInstance, + >( + collator_session_keys().add(collator_session_key(BLOCK_AUTHOR_ACCOUNT)), + ExistentialDeposit::get(), + AccountId::from([73; 32]), + AccountId::from(BLOCK_AUTHOR_ACCOUNT), + // receiving DOTs + (MultiLocation { parents: 2, interior: X1(GlobalConsensus(Polkadot)) }, 1000000000000, 1_000_000_000), + bridging_to_asset_hub_polkadot, + (X1(PalletInstance(53)), GlobalConsensus(Polkadot), X1(Parachain(1000))) + ) +} + +/// Tests expected configuration of isolated `pallet_xcm::Config::XcmReserveTransferFilter`. +#[test] +fn xcm_reserve_transfer_filter_works() { + // prepare assets + let only_native_assets = || vec![MultiAsset::from((KsmLocation::get(), 1000))]; + let only_trust_backed_assets = || { + vec![MultiAsset::from(( + AssetIdForTrustBackedAssetsConvert::convert_back(&12345).unwrap(), + 2000, + ))] + }; + let only_sibling_foreign_assets = + || vec![MultiAsset::from((MultiLocation::new(1, X1(Parachain(12345))), 3000))]; + let only_different_global_consensus_foreign_assets = || { + vec![MultiAsset::from(( + MultiLocation::new(2, X2(GlobalConsensus(Polkadot), Parachain(12345))), + 4000, + ))] + }; + + // prepare destinations + let relaychain = MultiLocation::parent(); + let sibling_parachain = MultiLocation::new(1, X1(Parachain(54321))); + let different_global_consensus_parachain_other_than_asset_hub_polkadot = + MultiLocation::new(2, X2(GlobalConsensus(Polkadot), Parachain(12345))); + let bridged_asset_hub = bridging::AssetHubPolkadot::get(); + + // prepare expected test data sets: (destination, assets, expected_result) + let test_data = vec![ + (relaychain, only_native_assets(), true), + (relaychain, only_trust_backed_assets(), true), + (relaychain, only_sibling_foreign_assets(), true), + (relaychain, only_different_global_consensus_foreign_assets(), true), + (sibling_parachain, only_native_assets(), true), + (sibling_parachain, only_trust_backed_assets(), true), + (sibling_parachain, only_sibling_foreign_assets(), true), + (sibling_parachain, only_different_global_consensus_foreign_assets(), true), + ( + different_global_consensus_parachain_other_than_asset_hub_polkadot, + only_native_assets(), + false, + ), + ( + different_global_consensus_parachain_other_than_asset_hub_polkadot, + only_trust_backed_assets(), + false, + ), + ( + different_global_consensus_parachain_other_than_asset_hub_polkadot, + only_sibling_foreign_assets(), + false, + ), + ( + different_global_consensus_parachain_other_than_asset_hub_polkadot, + only_different_global_consensus_foreign_assets(), + false, + ), + (bridged_asset_hub, only_native_assets(), true), + (bridged_asset_hub, only_trust_backed_assets(), false), + (bridged_asset_hub, only_sibling_foreign_assets(), false), + (bridged_asset_hub, only_different_global_consensus_foreign_assets(), false), + ]; + + // lets test filter with test data + ExtBuilder::::default() + .with_collators(collator_session_keys().collators()) + .with_session_keys(collator_session_keys().session_keys()) + .build() + .execute_with(|| { + type XcmReserveTransferFilter = + ::XcmReserveTransferFilter; + for (dest, assets, expected_result) in test_data { + assert_eq!( + expected_result, + XcmReserveTransferFilter::contains(&(dest, assets.clone())), + "expected_result: {} for dest: {:?} and assets: {:?}", + expected_result, + dest, + assets + ); + } + }) +} 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 0051af21f9a32..2e63d630dd60d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs @@ -569,7 +569,6 @@ parameter_types! { } impl cumulus_pallet_xcmp_queue::Config for Runtime { - type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; type RuntimeEvent = RuntimeEvent; type XcmExecutor = XcmExecutor; type ChannelInfo = ParachainSystem; @@ -581,6 +580,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { >; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; type PriceForSiblingDelivery = (); + type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; } impl cumulus_pallet_dmp_queue::Config for Runtime { @@ -1158,7 +1158,10 @@ impl_runtime_apis! { } fn universal_alias() -> Result<(MultiLocation, Junction), BenchmarkError> { - Err(BenchmarkError::Skip) + match xcm_config::bridging::BridgingBenchmarksHelper::prepare_universal_alias() { + Some(alias) => Ok(alias), + None => Err(BenchmarkError::Skip) + } } fn transact_origin_and_runtime_call() -> Result<(MultiLocation, RuntimeCall), BenchmarkError> { diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/xcm/mod.rs index 55fed809e2b75..6a525df07cf1a 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/xcm/mod.rs @@ -220,7 +220,7 @@ impl XcmWeightInfo for AssetHubPolkadotXcmWeight { XcmGeneric::::clear_transact_status() } fn universal_origin(_: &Junction) -> Weight { - Weight::MAX + XcmGeneric::::universal_origin() } fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { Weight::MAX diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 061992691a605..961857553da32 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -312,6 +312,16 @@ impl WeightInfo { // Minimum execution time: 2_845_000 picoseconds. Weight::from_parts(2_970_000, 0) } + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + pub fn universal_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1489` + // Minimum execution time: 4_942_000 picoseconds. + Weight::from_parts(5_195_000, 1489) + .saturating_add(T::DbWeight::get().reads(1)) + } pub fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/xcm_config.rs index d59507e4bd07d..4b11eb6682c53 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/xcm_config.rs @@ -18,12 +18,13 @@ use super::{ ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, TrustBackedAssetsInstance, WeightToFee, XcmpQueue, }; +use crate::ForeignAssetsInstance; use assets_common::matching::{ FromSiblingParachain, IsForeignConcreteAsset, StartsWith, StartsWithExplicitGlobalConsensus, }; use frame_support::{ match_types, parameter_types, - traits::{ConstU32, Contains, Everything, Nothing, PalletInfoAccess}, + traits::{ConstU32, Contains, Everything, EverythingBut, Nothing, PalletInfoAccess}, }; use frame_system::EnsureRoot; use pallet_xcm::XcmPassthrough; @@ -35,11 +36,11 @@ use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, DenyReserveTransferToRelayChain, DenyThenTry, DescribeFamily, DescribePalletTerminal, - EnsureXcmOrigin, FungiblesAdapter, HashedDescription, IsConcrete, LocalMint, NativeAsset, - NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + EnsureXcmOrigin, FungiblesAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, + IsConcrete, LocalMint, NativeAsset, NoChecking, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; @@ -70,6 +71,9 @@ pub type LocationToAccountId = ( // Foreign chain account alias into local accounts according to a hash of their standard // description. HashedDescription>, + // Different global consensus parachain sovereign account. + // (Used for over-bridge transfers and reserve processing) + GlobalConsensusParachainConvertsFor, ); /// Means for transacting the native currency on this chain. @@ -383,6 +387,7 @@ pub type Barrier = TrailingSetTopicAsId< >, >; +/// Multiplier used for dedicated `TakeFirstAssetTrader` with `Assets` instance. pub type AssetFeeAsExistentialDepositMultiplierFeeCharger = AssetFeeAsExistentialDepositMultiplier< Runtime, WeightToFee, @@ -390,16 +395,25 @@ pub type AssetFeeAsExistentialDepositMultiplierFeeCharger = AssetFeeAsExistentia TrustBackedAssetsInstance, >; +/// Multiplier used for dedicated `TakeFirstAssetTrader` with `ForeignAssets` instance. +pub type ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger = + AssetFeeAsExistentialDepositMultiplier< + Runtime, + WeightToFee, + pallet_assets::BalanceToAssetBalance, + ForeignAssetsInstance, + >; + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; type AssetTransactor = AssetTransactors; type OriginConverter = XcmOriginToTransactDispatchOrigin; - // Asset Hub Polkadot does not recognize a reserve location for any asset. This does not prevent - // Asset Hub acting _as_ a reserve location for DOT and assets created under `pallet-assets`. - // For DOT, users must use teleport where allowed (e.g. with the Relay Chain). - type IsReserve = (); + // Asset Hub trusts only particular configured bridge locations as reserve locations. + // Asset Hub may _act_ as a reserve location for DOT and assets created under `pallet-assets`. + // Users must use teleport where allowed (e.g. DOT with the Relay Chain). + type IsReserve = bridging::IsTrustedBridgedReserveLocationForConcreteAsset; // We allow: // - teleportation of DOT // - teleportation of sibling parachain's assets (as ForeignCreators) @@ -416,6 +430,8 @@ impl xcm_executor::Config for XcmConfig { >; type Trader = ( UsingComponents>, + // This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated + // `pallet_assets` instance - `Assets`. cumulus_primitives_utility::TakeFirstAssetTrader< AccountId, AssetFeeAsExistentialDepositMultiplierFeeCharger, @@ -427,6 +443,19 @@ impl xcm_executor::Config for XcmConfig { XcmAssetFeesReceiver, >, >, + // This trader allows to pay with `is_sufficient=true` "Foreign" assets from dedicated + // `pallet_assets` instance - `ForeignAssets`. + cumulus_primitives_utility::TakeFirstAssetTrader< + AccountId, + ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, + ForeignAssetsConvertedConcreteId, + ForeignAssets, + cumulus_primitives_utility::XcmFeesTo32ByteAccount< + ForeignFungiblesTransactor, + AccountId, + XcmAssetFeesReceiver, + >, + >, ); type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; @@ -438,7 +467,7 @@ impl xcm_executor::Config for XcmConfig { type AssetExchanger = (); type FeeManager = (); type MessageExporter = (); - type UniversalAliases = Nothing; + type UniversalAliases = bridging::UniversalAliases; type CallDispatcher = WithOriginFilter; type SafeCallFilter = SafeCallFilter; type Aliasers = Nothing; @@ -448,14 +477,17 @@ impl xcm_executor::Config for XcmConfig { /// Forms the basis for local origins sending/executing XCMs. pub type LocalOriginToLocation = SignedToAccountId32; -/// The means for routing XCM messages which are not for local execution into the right message -/// queues. -pub type XcmRouter = WithUniqueTopic<( +/// For routing XCM messages which do not cross local consensus boundary. +type LocalXcmRouter = ( // Two routers - use UMP to communicate with the relay chain: cumulus_primitives_utility::ParentAsUmp, // ..and XCMP to communicate with the sibling chains. XcmpQueue, -)>; +); + +/// The means for routing XCM messages which are not for local execution into the right message +/// queues. +pub type XcmRouter = WithUniqueTopic<(LocalXcmRouter, bridging::BridgingXcmRouter)>; #[cfg(feature = "runtime-benchmarks")] parameter_types! { @@ -474,7 +506,10 @@ impl pallet_xcm::Config for Runtime { type XcmExecuteFilter = Nothing; type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; - type XcmReserveTransferFilter = Everything; + // Allow reserve based transfer to everywhere except for bridging, here we strictly check what + // assets are allowed. + type XcmReserveTransferFilter = + EverythingBut; type Weigher = WeightInfoBounds< crate::weights::xcm::AssetHubPolkadotXcmWeight, RuntimeCall, @@ -532,3 +567,118 @@ fn foreign_pallet_has_correct_local_account() { let address = Ss58Codec::to_ss58check_with_version(&account, polkadot); assert_eq!(address, "13w7NdvSR1Af8xsQTArDtZmVvjE8XhWNdL4yed3iFHrUNCnS"); } + +/// All configuration related to bridging +pub mod bridging { + use super::*; + use assets_common::{matching, matching::*}; + use parachains_common::xcm_config::LocationFilter; + use sp_std::collections::btree_set::BTreeSet; + use xcm_builder::UnpaidRemoteExporter; + + parameter_types! { + pub BridgeHubPolkadotParaId: u32 = 1002; + pub BridgeHubPolkadot: MultiLocation = MultiLocation::new(1, X1(Parachain(BridgeHubPolkadotParaId::get()))); + pub BridgeHubPolkadotWithBridgeHubKusamaInstance: MultiLocation = MultiLocation::new(1, X2(Parachain(BridgeHubPolkadotParaId::get()), PalletInstance(53))); + pub const KusamaNetwork: NetworkId = NetworkId::Kusama; + pub AssetHubKusama: MultiLocation = MultiLocation::new(2, X2(GlobalConsensus(KusamaNetwork::get()), Parachain(1000))); + pub KsmLocation: MultiLocation = MultiLocation::new(2, X1(GlobalConsensus(KusamaNetwork::get()))); + + /// Set up exporters configuration. + pub BridgeTable: sp_std::vec::Vec<(NetworkId, LocationFilter, MultiLocation, Option)> = sp_std::vec![ + ( + KusamaNetwork::get(), + LocationFilter::default() + // allow to bridge to AssetHubKusama + .add_equals(AssetHubKusama::get().interior.split_global().expect("invalid configuration for AssetHubKusama").1), + // and nothing else + BridgeHubPolkadot::get(), + None + ) + ]; + + /// Set up trusted bridged reserve locations. + /// Locations from which the runtime accepts reserved assets. + pub TrustedBridgedReserveLocations: sp_std::vec::Vec = sp_std::vec![ + // trust assets from AssetHubKusama + ( + AssetHubKusama::get(), + AssetFilter::ByMultiLocation( + LocationFilter::default() + // allow receive KSM + .add_equals(KsmLocation::get()) + // and nothing else + ) + ) + ]; + + /// Allowed reserve transfer assets per destination. + /// Means that runtime allows to transfer reserve assets to these locations. + pub AllowedReserveTransferAssetsLocations: sp_std::vec::Vec = sp_std::vec![ + // allow to transfer assets to AssetHubKusama + ( + AssetHubKusama::get(), + AssetFilter::ByMultiLocation( + LocationFilter::default() + // allow send only DOT + .add_equals(DotLocation::get()) + // and nothing else + ) + ) + ]; + + /// Universal aliases + pub UniversalAliases: BTreeSet<(MultiLocation, Junction)> = BTreeSet::from_iter( + sp_std::vec![ + (BridgeHubPolkadotWithBridgeHubKusamaInstance::get(), GlobalConsensus(KusamaNetwork::get())) + ] + ); + } + + impl Contains<(MultiLocation, Junction)> for UniversalAliases { + fn contains(alias: &(MultiLocation, Junction)) -> bool { + UniversalAliases::get().contains(alias) + } + } + + pub type FilteredNetworkExportTable = + parachains_common::xcm_config::FilteredNetworkExportTable; + + /// Bridge router, which wraps and sends xcm to BridgeHub to be delivered to the different + /// GlobalConsensus + pub type BridgingXcmRouter = + UnpaidRemoteExporter; + + /// Reserve locations filter for `xcm_executor::Config::IsReserve`. + pub type IsTrustedBridgedReserveLocationForConcreteAsset = + matching::IsTrustedBridgedReserveLocationForConcreteAsset< + UniversalLocation, + TrustedBridgedReserveLocations, + >; + + /// Filter out those assets which are not allowed for bridged reserve based transfer. + /// (asset, location) filter for `pallet_xcm::Config::XcmReserveTransferFilter`. + pub type IsNotAllowedExplicitlyForReserveTransfer = ExcludeOnlyForRemoteDestination< + UniversalLocation, + FilteredNetworkExportTable, + IsNotAllowedConcreteAssetBy, + >; + + /// Benchmarks helper for bridging configuration. + #[cfg(feature = "runtime-benchmarks")] + pub struct BridgingBenchmarksHelper; + + #[cfg(feature = "runtime-benchmarks")] + impl BridgingBenchmarksHelper { + pub fn prepare_universal_alias() -> Option<(MultiLocation, Junction)> { + let alias = UniversalAliases::get().into_iter().find_map(|(location, junction)| { + match BridgeHubPolkadotWithBridgeHubKusamaInstance::get().eq(&location) { + true => Some((location, junction)), + false => None, + } + }); + assert!(alias.is_some(), "we expect here BridgeHubPolkadot to Kusama mapping at least"); + Some(alias.unwrap()) + } + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/tests/tests.rs index 7200ebc16a287..f76f11bac1768 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/tests/tests.rs @@ -18,20 +18,21 @@ //! Tests for the Statemint (Polkadot Assets Hub) chain. use asset_hub_polkadot_runtime::xcm_config::{ - AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount, DotLocation, - ForeignCreatorsSovereignAccountOf, TrustBackedAssetsPalletLocation, XcmConfig, + bridging, AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount, DotLocation, + ForeignCreatorsSovereignAccountOf, LocationToAccountId, TrustBackedAssetsPalletLocation, + XcmConfig, }; pub use asset_hub_polkadot_runtime::{ AllPalletsWithoutSystem, AssetDeposit, Assets, Balances, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, Runtime, - RuntimeCall, RuntimeEvent, SessionKeys, System, TrustBackedAssetsInstance, + RuntimeCall, RuntimeEvent, SessionKeys, System, TrustBackedAssetsInstance, XcmpQueue, }; -use asset_test_utils::{CollatorSessionKeys, ExtBuilder}; +use asset_test_utils::{CollatorSessionKey, CollatorSessionKeys, ExtBuilder}; use codec::{Decode, Encode}; use cumulus_primitives_utility::ChargeWeightInFungibles; use frame_support::{ assert_noop, assert_ok, - traits::fungibles::InspectEnumerable, + traits::{fungibles::InspectEnumerable, Contains}, weights::{Weight, WeightToFee as WeightToFeeT}, }; use parachains_common::{ @@ -50,14 +51,18 @@ type AssetIdForTrustBackedAssetsConvert = type RuntimeHelper = asset_test_utils::RuntimeHelper; -fn collator_session_keys() -> CollatorSessionKeys { - CollatorSessionKeys::new( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, +fn collator_session_key(account: [u8; 32]) -> CollatorSessionKey { + CollatorSessionKey::new( + AccountId::from(account), + AccountId::from(account), + SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(account)) }, ) } +fn collator_session_keys() -> CollatorSessionKeys { + CollatorSessionKeys::default().add(collator_session_key(ALICE)) +} + #[test] fn test_asset_xcm_trader() { ExtBuilder::::default() @@ -657,3 +662,147 @@ asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_p assert_eq!(ForeignAssets::asset_ids().collect::>().len(), 1); }) ); + +fn bridging_to_asset_hub_kusama() -> asset_test_utils::test_cases_over_bridge::TestBridgingConfig { + asset_test_utils::test_cases_over_bridge::TestBridgingConfig { + bridged_network: bridging::KusamaNetwork::get(), + local_bridge_hub_para_id: bridging::BridgeHubPolkadotParaId::get(), + local_bridge_hub_location: bridging::BridgeHubPolkadot::get(), + bridged_target_location: bridging::AssetHubKusama::get(), + } +} + +#[test] +fn limited_reserve_transfer_assets_for_native_asset_over_bridge_works() { + asset_test_utils::test_cases_over_bridge::limited_reserve_transfer_assets_for_native_asset_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ParachainSystem, + XcmpQueue, + LocationToAccountId, + >( + collator_session_keys(), + ExistentialDeposit::get(), + AccountId::from(ALICE), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), + _ => None, + } + }), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), + _ => None, + } + }), + bridging_to_asset_hub_kusama, + WeightLimit::Unlimited, + ) +} + +#[test] +fn receive_reserve_asset_deposited_ksm_from_asset_hub_kusama_works() { + const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32]; + asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + LocationToAccountId, + ForeignAssetsInstance, + >( + collator_session_keys().add(collator_session_key(BLOCK_AUTHOR_ACCOUNT)), + ExistentialDeposit::get(), + AccountId::from([73; 32]), + AccountId::from(BLOCK_AUTHOR_ACCOUNT), + // receiving DOTs + (MultiLocation { parents: 2, interior: X1(GlobalConsensus(Kusama)) }, 1000000000000, 10_000_000_000), + bridging_to_asset_hub_kusama, + (X1(PalletInstance(53)), GlobalConsensus(Kusama), X1(Parachain(1000))) + ) +} + +/// Tests expected configuration of isolated `pallet_xcm::Config::XcmReserveTransferFilter`. +#[test] +fn xcm_reserve_transfer_filter_works() { + // prepare assets + let only_native_assets = || vec![MultiAsset::from((DotLocation::get(), 1000))]; + let only_trust_backed_assets = || { + vec![MultiAsset::from(( + AssetIdForTrustBackedAssetsConvert::convert_back(&12345).unwrap(), + 2000, + ))] + }; + let only_sibling_foreign_assets = + || vec![MultiAsset::from((MultiLocation::new(1, X1(Parachain(12345))), 3000))]; + let only_different_global_consensus_foreign_assets = || { + vec![MultiAsset::from(( + MultiLocation::new(2, X2(GlobalConsensus(Kusama), Parachain(12345))), + 4000, + ))] + }; + + // prepare destinations + let relaychain = MultiLocation::parent(); + let sibling_parachain = MultiLocation::new(1, X1(Parachain(54321))); + let different_global_consensus_parachain_other_than_asset_hub_kusama = + MultiLocation::new(2, X2(GlobalConsensus(Kusama), Parachain(12345))); + let bridged_asset_hub = bridging::AssetHubKusama::get(); + + // prepare expected test data sets: (destination, assets, expected_result) + let test_data = vec![ + (relaychain, only_native_assets(), true), + (relaychain, only_trust_backed_assets(), true), + (relaychain, only_sibling_foreign_assets(), true), + (relaychain, only_different_global_consensus_foreign_assets(), true), + (sibling_parachain, only_native_assets(), true), + (sibling_parachain, only_trust_backed_assets(), true), + (sibling_parachain, only_sibling_foreign_assets(), true), + (sibling_parachain, only_different_global_consensus_foreign_assets(), true), + ( + different_global_consensus_parachain_other_than_asset_hub_kusama, + only_native_assets(), + false, + ), + ( + different_global_consensus_parachain_other_than_asset_hub_kusama, + only_trust_backed_assets(), + false, + ), + ( + different_global_consensus_parachain_other_than_asset_hub_kusama, + only_sibling_foreign_assets(), + false, + ), + ( + different_global_consensus_parachain_other_than_asset_hub_kusama, + only_different_global_consensus_foreign_assets(), + false, + ), + (bridged_asset_hub, only_native_assets(), true), + (bridged_asset_hub, only_trust_backed_assets(), false), + (bridged_asset_hub, only_sibling_foreign_assets(), false), + (bridged_asset_hub, only_different_global_consensus_foreign_assets(), false), + ]; + + // lets test filter with test data + ExtBuilder::::default() + .with_collators(collator_session_keys().collators()) + .with_session_keys(collator_session_keys().session_keys()) + .build() + .execute_with(|| { + type XcmReserveTransferFilter = + ::XcmReserveTransferFilter; + for (dest, assets, expected_result) in test_data { + assert_eq!( + expected_result, + XcmReserveTransferFilter::contains(&(dest, assets.clone())), + "expected_result: {} for dest: {:?} and assets: {:?}", + expected_result, + dest, + assets + ); + } + }) +} diff --git a/cumulus/parachains/runtimes/assets/common/src/matching.rs b/cumulus/parachains/runtimes/assets/common/src/matching.rs index 08d391170014c..0402e7a560ed2 100644 --- a/cumulus/parachains/runtimes/assets/common/src/matching.rs +++ b/cumulus/parachains/runtimes/assets/common/src/matching.rs @@ -18,10 +18,12 @@ use frame_support::{ pallet_prelude::Get, traits::{Contains, ContainsPair}, }; +use parachains_common::xcm_config::{LocationFilter, MatchesLocation}; use xcm::{ latest::prelude::{MultiAsset, MultiLocation}, prelude::*, }; +use xcm_builder::{ensure_is_remote, ExporterFor}; pub struct StartsWith(sp_std::marker::PhantomData); impl> Contains for StartsWith { @@ -82,3 +84,163 @@ impl> ContainsPair } } } + +/// Adapter verifies if it is allowed to receive `MultiAsset` from `MultiLocation`. +/// +/// Note: `MultiLocation` has to be from different global consensus. +pub struct IsTrustedBridgedReserveLocationForConcreteAsset( + sp_std::marker::PhantomData<(UniversalLocation, Reserves)>, +); +impl< + UniversalLocation: Get, + Reserves: Get>, + > ContainsPair + for IsTrustedBridgedReserveLocationForConcreteAsset +{ + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { + let universal_source = UniversalLocation::get(); + log::trace!( + target: "xcm::contains", + "IsTrustedBridgedReserveLocationForConcreteAsset asset: {:?}, origin: {:?}, universal_source: {:?}", + asset, origin, universal_source + ); + + // check remote origin + let _ = match ensure_is_remote(universal_source, *origin) { + Ok(devolved) => devolved, + Err(_) => { + log::trace!( + target: "xcm::contains", + "IsTrustedBridgedReserveLocationForConcreteAsset origin: {:?} is not remote to the universal_source: {:?}", + origin, universal_source + ); + return false + }, + }; + + // check asset location + let asset_location = match &asset.id { + Concrete(location) => location, + _ => return false, + }; + + // check asset according to the configured reserve locations + for (reserve_location, asset_filter) in Reserves::get() { + if origin.eq(&reserve_location) { + if asset_filter.matches(asset_location) { + return true + } + } + } + + false + } +} + +/// Filter assets that are explicitly not allowed for destination. +/// +/// Returns true if asset is not `Concrete` or is explicitly not allowed by `LocationAssetFilters`. +/// Returns false if `dest` does not match any location in `LocationAssetFilters`. +pub struct IsNotAllowedConcreteAssetBy( + sp_std::marker::PhantomData, +); +impl>> + Contains<(MultiLocation, sp_std::vec::Vec)> + for IsNotAllowedConcreteAssetBy +{ + fn contains((dest, assets): &(MultiLocation, sp_std::vec::Vec)) -> bool { + for (allowed_dest, asset_filter) in LocationAssetFilters::get().iter() { + // we care only about explicitly configured destinations + if !allowed_dest.eq(dest) { + continue + } + + // check all assets + for asset in assets { + let asset_location = match &asset.id { + Concrete(location) => location, + _ => return true, + }; + + // filter have to match for all assets + if !asset_filter.matches(asset_location) { + // if asset does not match filter, we found explicitly not allowed asset + return true + } + } + } + + // by default, everything is allowed + false + } +} + +/// Adapter for `Contains<(MultiLocation, sp_std::vec::Vec)>` which checks +/// if `Exporters` contains exporter for **remote** `MultiLocation` and iff so, then checks +/// `Filter`, anyway return false. +/// +/// Note: Assumes that `Exporters` do not depend on `XCM program` and works for `Xcm::default()`. +pub struct ExcludeOnlyForRemoteDestination( + sp_std::marker::PhantomData<(UniversalLocation, Exporters, Exclude)>, +); +impl Contains<(MultiLocation, sp_std::vec::Vec)> + for ExcludeOnlyForRemoteDestination +where + UniversalLocation: Get, + Exporters: ExporterFor, + Exclude: Contains<(MultiLocation, sp_std::vec::Vec)>, +{ + fn contains(dest_and_assets: &(MultiLocation, sp_std::vec::Vec)) -> bool { + let universal_source = UniversalLocation::get(); + log::trace!( + target: "xcm::contains", + "CheckOnlyForRemoteDestination dest: {:?}, assets: {:?}, universal_source: {:?}", + dest_and_assets.0, dest_and_assets.1, universal_source + ); + + // check if it is remote destination + match ensure_is_remote(universal_source, dest_and_assets.0) { + Ok((remote_network, remote_destination)) => { + if Exporters::exporter_for(&remote_network, &remote_destination, &Xcm::default()) + .is_some() + { + Exclude::contains(dest_and_assets) + } else { + log::trace!( + target: "xcm::contains", + "CheckOnlyForRemoteDestination no exporter for dest: {:?}", + dest_and_assets.0 + ); + // no exporter means that we exclude by default + true + } + }, + Err(_) => { + log::trace!( + target: "xcm::contains", + "CheckOnlyForRemoteDestination dest: {:?} is not remote to the universal_source: {:?}", + dest_and_assets.0, universal_source + ); + // + false + }, + } + } +} + +/// Location as `MultiLocation` with `AssetFilter`. +pub type FilteredLocation = (MultiLocation, AssetFilter); + +/// Simple asset location filter. +#[derive(Debug)] +pub enum AssetFilter { + ByMultiLocation(LocationFilter), +} + +impl MatchesLocation for AssetFilter { + fn matches(&self, asset_location: &MultiLocation) -> bool { + match self { + AssetFilter::ByMultiLocation(by_location) => by_location.matches(asset_location), + } + } +} diff --git a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml index 7fee710f000c0..6913144191029 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml @@ -36,6 +36,7 @@ parachains-runtimes-test-utils = { path = "../../test-utils", default-features = # Polkadot xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false} +xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false} xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false} pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", default-features = false} polkadot-parachain-primitives = { path = "../../../../../polkadot/parachain", default-features = false} @@ -72,6 +73,7 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "xcm-builder/std", "xcm-executor/std", "xcm/std", ] diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/lib.rs b/cumulus/parachains/runtimes/assets/test-utils/src/lib.rs index 7177726e07046..3c62faf3d5ecc 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/lib.rs @@ -17,4 +17,5 @@ //! Module contains predefined test-case scenarios for `Runtime` with various assets. pub mod test_cases; +pub mod test_cases_over_bridge; pub use parachains_runtimes_test_utils::*; diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs index 7a8d571403c6b..6503d7e6a7365 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs @@ -78,6 +78,7 @@ pub fn teleports_for_native_asset_works< Into<<::RuntimeOrigin as OriginTrait>::AccountId>, <::Lookup as StaticLookup>::Source: From<::AccountId>, + ::AccountId: From, XcmConfig: xcm_executor::Config, CheckingAccount: Get>, HrmpChannelOpener: frame_support::inherent::ProvideInherent< @@ -96,7 +97,7 @@ pub fn teleports_for_native_asset_works< let included_head = RuntimeHelper::::run_to_block( 2, - AccountId::from(alice), + AccountId::from(alice).into(), ); // check Balances before assert_eq!(>::free_balance(&target_account), 0.into()); @@ -346,6 +347,7 @@ pub fn teleports_for_foreign_assets_works< Into<<::RuntimeOrigin as OriginTrait>::AccountId>, <::Lookup as StaticLookup>::Source: From<::AccountId>, + ::AccountId: From, ForeignAssetsPalletInstance: 'static, { // foreign parachain with the same consenus currency as asset @@ -391,7 +393,7 @@ pub fn teleports_for_foreign_assets_works< let included_head = RuntimeHelper::::run_to_block( 2, - AccountId::from(alice), + AccountId::from(alice).into(), ); // checks target_account before assert_eq!( @@ -1005,6 +1007,7 @@ macro_rules! include_asset_transactor_transfer_with_pallet_assets_instance_works } ); +/// Test-case makes sure that `Runtime` can create and manage `ForeignAssets` pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_works< Runtime, XcmConfig, diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs new file mode 100644 index 0000000000000..a815367587ab0 --- /dev/null +++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs @@ -0,0 +1,504 @@ +// Copyright (C) 2023 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. + +//! Module contains predefined test-case scenarios for `Runtime` with various assets transferred +//! over a bridge. + +use codec::Encode; +use cumulus_primitives_core::XcmpMessageSource; +use frame_support::{ + assert_ok, + traits::{Currency, OnFinalize, OnInitialize, OriginTrait, ProcessMessageError}, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use parachains_common::{AccountId, Balance}; +use parachains_runtimes_test_utils::{ + mock_open_hrmp_channel, AccountIdOf, BalanceOf, CollatorSessionKeys, ExtBuilder, RuntimeHelper, + ValidatorIdOf, XcmReceivedFrom, +}; +use sp_runtime::traits::StaticLookup; +use xcm::{latest::prelude::*, VersionedMultiAssets}; +use xcm_builder::{CreateMatcher, MatchXcm}; +use xcm_executor::{traits::ConvertLocation, XcmExecutor}; + +pub struct TestBridgingConfig { + pub bridged_network: NetworkId, + pub local_bridge_hub_para_id: u32, + pub local_bridge_hub_location: MultiLocation, + pub bridged_target_location: MultiLocation, +} + +/// Test-case makes sure that `Runtime` can initiate **reserve transfer assets** over bridge. +pub fn limited_reserve_transfer_assets_for_native_asset_works< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + HrmpChannelOpener, + HrmpChannelSource, + LocationToAccountId, +>( + collator_session_keys: CollatorSessionKeys, + existential_deposit: BalanceOf, + alice_account: AccountIdOf, + unwrap_pallet_xcm_event: Box) -> Option>>, + unwrap_xcmp_queue_event: Box< + dyn Fn(Vec) -> Option>, + >, + ensure_configuration: fn() -> TestBridgingConfig, + weight_limit: WeightLimit, +) where + Runtime: frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config + + cumulus_pallet_xcmp_queue::Config, + AllPalletsWithoutSystem: + OnInitialize> + OnFinalize>, + AccountIdOf: Into<[u8; 32]>, + ValidatorIdOf: From>, + BalanceOf: From, + ::Balance: From + Into, + XcmConfig: xcm_executor::Config, + LocationToAccountId: ConvertLocation>, + ::AccountId: + Into<<::RuntimeOrigin as OriginTrait>::AccountId>, + <::Lookup as StaticLookup>::Source: + From<::AccountId>, + ::AccountId: From, + HrmpChannelOpener: frame_support::inherent::ProvideInherent< + Call = cumulus_pallet_parachain_system::Call, + >, + HrmpChannelSource: XcmpMessageSource, +{ + let runtime_para_id = 1000; + ExtBuilder::::default() + .with_collators(collator_session_keys.collators()) + .with_session_keys(collator_session_keys.session_keys()) + .with_tracing() + .with_safe_xcm_version(3) + .with_para_id(runtime_para_id.into()) + .build() + .execute_with(|| { + let mut alice = [0u8; 32]; + alice[0] = 1; + let included_head = RuntimeHelper::::run_to_block( + 2, + AccountId::from(alice).into(), + ); + + // prepare bridge config + let TestBridgingConfig { + bridged_network, + local_bridge_hub_para_id, + bridged_target_location: target_location_from_different_consensus, + .. + } = ensure_configuration(); + + let reserve_account = + LocationToAccountId::convert_location(&target_location_from_different_consensus) + .expect("Sovereign account for reserves"); + let balance_to_transfer = 1_000_000_000_000_u128; + let native_asset = MultiLocation::parent(); + + // open HRMP to bridge hub + mock_open_hrmp_channel::( + runtime_para_id.into(), + local_bridge_hub_para_id.into(), + included_head, + &alice, + ); + + // drip ED to account + let alice_account_init_balance = existential_deposit + balance_to_transfer.into(); + let _ = >::deposit_creating( + &alice_account, + alice_account_init_balance, + ); + // SA of target location needs to have at least ED, otherwise making reserve fails + let _ = >::deposit_creating( + &reserve_account, + existential_deposit, + ); + + // we just check here, that user retains enough balance after withdrawal + // and also we check if `balance_to_transfer` is more than `existential_deposit`, + assert!( + (>::free_balance(&alice_account) - + balance_to_transfer.into()) >= + existential_deposit + ); + // SA has just ED + assert_eq!( + >::free_balance(&reserve_account), + existential_deposit + ); + + // local native asset (pallet_balances) + let asset_to_transfer = MultiAsset { + fun: Fungible(balance_to_transfer.into()), + id: Concrete(native_asset), + }; + + // destination is (some) account relative to the destination different consensus + let target_destination_account = MultiLocation { + parents: 0, + interior: X1(AccountId32 { + network: Some(bridged_network), + id: sp_runtime::AccountId32::new([3; 32]).into(), + }), + }; + + // do pallet_xcm call reserve transfer + assert_ok!(>::limited_reserve_transfer_assets( + RuntimeHelper::::origin_of(alice_account.clone()), + Box::new(target_location_from_different_consensus.into_versioned()), + Box::new(target_destination_account.into_versioned()), + Box::new(VersionedMultiAssets::from(MultiAssets::from(asset_to_transfer))), + 0, + weight_limit, + )); + + // check alice account decreased by balance_to_transfer + assert_eq!( + >::free_balance(&alice_account), + alice_account_init_balance - balance_to_transfer.into() + ); + + // check reserve account + // check reserve account increased by balance_to_transfer + assert_eq!( + >::free_balance(&reserve_account), + existential_deposit + balance_to_transfer.into() + ); + + // check events + // check pallet_xcm attempted + RuntimeHelper::::assert_pallet_xcm_event_outcome( + &unwrap_pallet_xcm_event, + |outcome| { + assert_ok!(outcome.ensure_complete()); + }, + ); + + // check that xcm was sent + let xcm_sent_message_hash = >::events() + .into_iter() + .filter_map(|e| unwrap_xcmp_queue_event(e.event.encode())) + .find_map(|e| match e { + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { message_hash } => + Some(message_hash), + _ => None, + }); + + // read xcm + let xcm_sent = RuntimeHelper::::take_xcm( + local_bridge_hub_para_id.into(), + ) + .unwrap(); + + assert_eq!( + xcm_sent_message_hash, + Some(xcm_sent.using_encoded(sp_io::hashing::blake2_256)) + ); + let mut xcm_sent: Xcm<()> = xcm_sent.try_into().expect("versioned xcm"); + + // check sent XCM ExportMessage to bridge-hub + xcm_sent + .0 + .matcher() + .match_next_inst(|instr| match instr { + // first instruction is UNpai (because we have explicit unpaid execution on + // bridge-hub now) + UnpaidExecution { weight_limit, check_origin } + if weight_limit == &Unlimited && check_origin.is_none() => + Ok(()), + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("contains UnpaidExecution") + .match_next_inst(|instr| match instr { + // second instruction is ExportMessage + ExportMessage { network, destination, xcm: inner_xcm } => { + assert_eq!(network, &bridged_network); + let (_, target_location_junctions_without_global_consensus) = + target_location_from_different_consensus + .interior + .split_global() + .expect("split works"); + assert_eq!( + destination, + &target_location_junctions_without_global_consensus + ); + assert_matches_pallet_xcm_reserve_transfer_assets_instructions(inner_xcm); + Ok(()) + }, + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("contains ExportMessage"); + }) +} + +pub fn receive_reserve_asset_deposited_from_different_consensus_works< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + LocationToAccountId, + ForeignAssetsPalletInstance, +>( + collator_session_keys: CollatorSessionKeys, + existential_deposit: BalanceOf, + target_account: AccountIdOf, + block_author_account: AccountIdOf, + ( + foreign_asset_id_multilocation, + transfered_foreign_asset_id_amount, + foreign_asset_id_minimum_balance, + ): (MultiLocation, u128, u128), + ensure_configuration: fn() -> TestBridgingConfig, + (bridge_instance, universal_origin, descend_origin): (Junctions, Junction, Junctions), /* bridge adds origin manipulation on the way */ +) where + Runtime: frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config + + cumulus_pallet_xcmp_queue::Config + + pallet_assets::Config, + AllPalletsWithoutSystem: + OnInitialize> + OnFinalize>, + AccountIdOf: Into<[u8; 32]>, + ValidatorIdOf: From>, + BalanceOf: From, + XcmConfig: xcm_executor::Config, + LocationToAccountId: ConvertLocation>, + >::AssetId: + From + Into, + >::AssetIdParameter: + From + Into, + >::Balance: + From + Into + From, + ::AccountId: + Into<<::RuntimeOrigin as OriginTrait>::AccountId>, + <::Lookup as StaticLookup>::Source: + From<::AccountId>, + ForeignAssetsPalletInstance: 'static, +{ + ExtBuilder::::default() + .with_collators(collator_session_keys.collators()) + .with_session_keys(collator_session_keys.session_keys()) + .with_tracing() + .build() + .execute_with(|| { + // Set account as block author, who will receive fees + RuntimeHelper::::run_to_block( + 2, + block_author_account.clone(), + ); + + // prepare bridge config + let TestBridgingConfig { local_bridge_hub_location, .. } = ensure_configuration(); + + // drip 'ED' user target account + let _ = >::deposit_creating( + &target_account, + existential_deposit, + ); + + // sovereign account as foreign asset owner (can be whoever for this scenario, doesnt + // matter) + let sovereign_account_as_owner_of_foreign_asset = + LocationToAccountId::convert_location(&MultiLocation::parent()).unwrap(); + + // staking pot account for collecting local native fees from `BuyExecution` + let staking_pot = >::account_id(); + let _ = >::deposit_creating( + &staking_pot, + existential_deposit, + ); + + // create foreign asset for wrapped/derivated representation + assert_ok!( + >::force_create( + RuntimeHelper::::root_origin(), + foreign_asset_id_multilocation.into(), + sovereign_account_as_owner_of_foreign_asset.clone().into(), + true, // is_sufficient=true + foreign_asset_id_minimum_balance.into() + ) + ); + + // Balances before + assert_eq!( + >::free_balance(&target_account), + existential_deposit.clone() + ); + assert_eq!( + >::free_balance(&block_author_account), + 0.into() + ); + assert_eq!( + >::free_balance(&staking_pot), + existential_deposit.clone() + ); + + // ForeignAssets balances before + assert_eq!( + >::balance( + foreign_asset_id_multilocation.into(), + &target_account + ), + 0.into() + ); + assert_eq!( + >::balance( + foreign_asset_id_multilocation.into(), + &block_author_account + ), + 0.into() + ); + assert_eq!( + >::balance( + foreign_asset_id_multilocation.into(), + &staking_pot + ), + 0.into() + ); + + // Call received XCM execution + let xcm = Xcm(vec![ + DescendOrigin(bridge_instance), + UniversalOrigin(universal_origin), + DescendOrigin(descend_origin), + ReserveAssetDeposited(MultiAssets::from(vec![MultiAsset { + id: Concrete(foreign_asset_id_multilocation), + fun: Fungible(transfered_foreign_asset_id_amount), + }])), + ClearOrigin, + BuyExecution { + fees: MultiAsset { + id: Concrete(foreign_asset_id_multilocation), + fun: Fungible(transfered_foreign_asset_id_amount), + }, + weight_limit: Unlimited, + }, + DepositAsset { + assets: Wild(AllCounted(1)), + beneficiary: MultiLocation { + parents: 0, + interior: X1(AccountId32 { + network: None, + id: target_account.clone().into(), + }), + }, + }, + SetTopic([ + 220, 188, 144, 32, 213, 83, 111, 175, 44, 210, 111, 19, 90, 165, 191, 112, 140, + 247, 192, 124, 42, 17, 153, 141, 114, 34, 189, 20, 83, 69, 237, 173, + ]), + ]); + assert_matches_pallet_xcm_reserve_transfer_assets_instructions(&mut xcm.clone()); + + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + + // execute xcm as XcmpQueue would do + let outcome = XcmExecutor::::execute_xcm( + local_bridge_hub_location, + xcm, + hash, + RuntimeHelper::::xcm_max_weight( + XcmReceivedFrom::Sibling, + ), + ); + assert_eq!(outcome.ensure_complete(), Ok(())); + + // author actual balance after (received fees from Trader for ForeignAssets) + let author_received_fees = + >::balance( + foreign_asset_id_multilocation.into(), + &block_author_account, + ); + + // Balances after (untouched) + assert_eq!( + >::free_balance(&target_account), + existential_deposit.clone() + ); + assert_eq!( + >::free_balance(&block_author_account), + 0.into() + ); + assert_eq!( + >::free_balance(&staking_pot), + existential_deposit.clone() + ); + + // ForeignAssets balances after + assert_eq!( + >::balance( + foreign_asset_id_multilocation.into(), + &target_account + ), + (transfered_foreign_asset_id_amount - author_received_fees.into()).into() + ); + assert_eq!( + >::balance( + foreign_asset_id_multilocation.into(), + &block_author_account + ), + author_received_fees + ); + assert_eq!( + >::balance( + foreign_asset_id_multilocation.into(), + &staking_pot + ), + 0.into() + ); + }) +} + +fn assert_matches_pallet_xcm_reserve_transfer_assets_instructions( + xcm: &mut Xcm, +) { + let _ = xcm + .0 + .matcher() + .skip_inst_while(|inst| !matches!(inst, ReserveAssetDeposited(..))) + .expect("no instruction ReserveAssetDeposited?") + .match_next_inst(|instr| match instr { + ReserveAssetDeposited(..) => Ok(()), + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("expected instruction ReserveAssetDeposited") + .match_next_inst(|instr| match instr { + ClearOrigin => Ok(()), + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("expected instruction ClearOrigin") + .match_next_inst(|instr| match instr { + BuyExecution { .. } => Ok(()), + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("expected instruction BuyExecution") + .match_next_inst(|instr| match instr { + DepositAsset { .. } => Ok(()), + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("expected instruction DepositAsset"); +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/README.md b/cumulus/parachains/runtimes/bridge-hubs/README.md index 487c601ef8402..56b6ad3ed6202 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/README.md +++ b/cumulus/parachains/runtimes/bridge-hubs/README.md @@ -2,13 +2,16 @@ - [Requirements for local run/testing](#requirements-for-local-runtesting) - [How to test local Rococo <-> Wococo bridge](#how-to-test-local-rococo---wococo-bridge) - [Run chains (Rococo + BridgeHub, Wococo + BridgeHub) with - zombienet](#run-chains-rococo--bridgehub-wococo--bridgehub-with-zombienet) + zombienet](#run-chains-rococo--bridgehub--assethub-wococo--bridgehub--assethub-with-zombienet) - [Run relayer (BridgeHubRococo, BridgeHubWococo)](#run-relayer-bridgehubrococo-bridgehubwococo) - [Run with script (alternative 1)](#run-with-script-alternative-1) - [Run with binary (alternative 2)](#run-with-binary-alternative-2) - [Send messages - transfer asset over bridge](#send-messages---transfer-asset-over-bridge) - [How to test live BridgeHubRococo/BridgeHubWococo](#how-to-test-live-bridgehubrococobridgehubwococo) - [How to test local BridgeHubKusama/BridgeHubPolkadot](#how-to-test-local-bridgehubkusamabridgehubpolkadot) + - [1. Run chains (Kusama + BridgeHub + AssetHub, Polkadot + BridgeHub + AssetHub) with zombienet](#1-run-chains-kusama--bridgehub--assethub-polkadot--bridgehub--assethub-with-zombienet) + - [2. Init bridge and run relayer (BridgeHubKusama, BridgeHubPolkadot)](#2-init-bridge-and-run-relayer-bridgehubkusama-bridgehubpolkadot) + - [Send messages - transfer asset over bridge (DOTs/KSMs)](#4-send-messages---transfer-asset-over-bridge-dotsksms) # Bridge-hub Parachains @@ -227,7 +230,60 @@ is going to be replaced by `pallet_xcm` usage) https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fwococo-wockmint-rpc.polkadot.io#/explorer - BridgeHubRococo (see `bridgeWococoMessages.MessagesDelivered`) - ## How to test local BridgeHubKusama/BridgeHubPolkadot -TODO: see `# !!! READ HERE` above +Check [requirements](#requirements-for-local-runtesting) for "sudo pallet + fast-runtime". + +### 1. Run chains (Kusama + BridgeHub + AssetHub, Polkadot + BridgeHub + AssetHub) with zombienet + +``` +# Kusama + BridgeHubKusama + AssetHubKusama +POLKADOT_BINARY_PATH=~/local_bridge_testing/bin/polkadot \ +POLKADOT_PARACHAIN_BINARY_PATH=~/local_bridge_testing/bin/polkadot-parachain \ +POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_KUSAMA=~/local_bridge_testing/bin/polkadot-parachain-asset-hub \ + ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./zombienet/bridge-hubs/bridge_hub_kusama_local_network.toml +``` + +``` +# Polkadot + BridgeHubPolkadot + AssetHubPolkadot +POLKADOT_BINARY_PATH=~/local_bridge_testing/bin/polkadot \ +POLKADOT_PARACHAIN_BINARY_PATH=~/local_bridge_testing/bin/polkadot-parachain \ +POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_POLKADOT=~/local_bridge_testing/bin/polkadot-parachain-asset-hub \ + ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./zombienet/bridge-hubs/bridge_hub_polkadot_local_network.toml +``` + +### 2. Init bridge and run relayer (BridgeHubKusama, BridgeHubPolkadot) + +``` +cd +./scripts/bridges_kusama_polkadot.sh run-relay +``` + +### 3. Initialize transfer asset over bridge (DOTs/KSMs) + +This initialization does several things: +- creates `ForeignAssets` for wrappedDOTs/wrappedKSMs +- drips SA for AssetHubKusama on AssetHubPolkadot (and vice versa) which holds reserved assets on source chains +``` +./scripts/bridges_kusama_polkadot.sh init-asset-hub-kusama-local +./scripts/bridges_kusama_polkadot.sh init-asset-hub-polkadot-local +``` + +### 4. Send messages - transfer asset over bridge (DOTs/KSMs) + +Do (asset) transfers: +``` +# KSMs from Kusama's Asset Hub to Polkadot's. +./scripts/bridges_kusama_polkadot.sh reserve-transfer-assets-from-asset-hub-kusama-local +``` +``` +# DOTs from Polkadot's Asset Hub to Kusama's. +./scripts/bridges_kusama_polkadot.sh reserve-transfer-assets-from-asset-hub-polkadot-local +``` + +- open explorers: (see zombienets) + - AssetHubKusama (see events `xcmpQueue.XcmpMessageSent`, `polkadotXcm.Attempted`) https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9910#/explorer + - BridgeHubKusama (see `bridgePolkadotMessages.MessageAccepted`) https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:8943#/explorer + - BridgeHubPolkadot (see `bridgeKusamaMessages.MessagesReceived`, `xcmpQueue.XcmpMessageSent`) https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:8945#/explorer + - AssetHubPolkadot (see `foreignAssets.Issued`, `xcmpQueue.Success`) https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9010#/explorer + - BridgeHubKusama (see `bridgePolkadotMessages.MessagesDelivered`) https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:8943#/explorer diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases.rs index aa7fa16a9791c..a3e36a39fd1e6 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases.rs @@ -249,6 +249,7 @@ pub fn message_dispatch_routing_works< XcmConfig: xcm_executor::Config, MessagesPalletInstance: 'static, ValidatorIdOf: From>, + ::AccountId: From, HrmpChannelOpener: frame_support::inherent::ProvideInherent< Call = cumulus_pallet_parachain_system::Call, >, @@ -272,7 +273,7 @@ pub fn message_dispatch_routing_works< let included_head = RuntimeHelper::::run_to_block( 2, - AccountId::from(alice), + AccountId::from(alice).into(), ); // 1. this message is sent from other global consensus with destination of this Runtime relay chain (UMP) let bridging_message = @@ -374,6 +375,7 @@ pub fn relayed_incoming_message_works>::BridgedChain as bp_runtime::Chain>::Hash>, ::AccountId: Into<<::RuntimeOrigin as OriginTrait>::AccountId>, + ::AccountId: From, AccountIdOf: From, >::InboundRelayer: From, { @@ -393,7 +395,7 @@ pub fn relayed_incoming_message_works::run_to_block( 2, - AccountId::from(alice), + AccountId::from(alice).into(), ); mock_open_hrmp_channel::( runtime_para_id.into(), @@ -591,6 +593,7 @@ pub fn complex_relay_extrinsic_works::AccountId: Into<<::RuntimeOrigin as OriginTrait>::AccountId>, AccountIdOf: From, + ::AccountId: From, >::InboundRelayer: From, ::RuntimeCall: From> @@ -622,7 +625,7 @@ pub fn complex_relay_extrinsic_works::run_to_block( 2, - AccountId::from(alice), + AccountId::from(alice).into(), ); let zero: BlockNumberFor = 0u32.into(); let genesis_hash = frame_system::Pallet::::block_hash(zero); @@ -928,7 +931,7 @@ pub mod test_data { ); /// Simulates `HaulBlobExporter` and all its wrapping and captures generated plain bytes, - /// which are transfered over bridge. + /// which are transferred over bridge. pub(crate) fn simulate_message_exporter_on_bridged_chain< SourceNetwork: Get, DestinationNetwork: Get, diff --git a/cumulus/parachains/runtimes/test-utils/Cargo.toml b/cumulus/parachains/runtimes/test-utils/Cargo.toml index 681e5e64d4128..6aec711f4a56d 100644 --- a/cumulus/parachains/runtimes/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/test-utils/Cargo.toml @@ -28,11 +28,11 @@ cumulus-pallet-xcmp-queue = { path = "../../../pallets/xcmp-queue", default-feat cumulus-pallet-dmp-queue = { path = "../../../pallets/dmp-queue", default-features = false } pallet-collator-selection = { path = "../../../pallets/collator-selection", default-features = false } parachains-common = { path = "../../common", default-features = false } +parachain-info = { path = "../../pallets/parachain-info", default-features = false } assets-common = { path = "../assets/common", default-features = false } cumulus-primitives-core = { path = "../../../primitives/core", default-features = false } cumulus-primitives-parachain-inherent = { path = "../../../primitives/parachain-inherent", default-features = false } cumulus-test-relay-sproof-builder = { path = "../../../test/relay-sproof-builder", default-features = false } -parachain-info = { path = "../../pallets/parachain-info", default-features = false } # Polkadot xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false} diff --git a/cumulus/parachains/runtimes/test-utils/src/lib.rs b/cumulus/parachains/runtimes/test-utils/src/lib.rs index 8289a80baa11a..3f13b78fb59ba 100644 --- a/cumulus/parachains/runtimes/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/test-utils/src/lib.rs @@ -24,11 +24,12 @@ use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use frame_support::{ dispatch::{DispatchResult, RawOrigin}, inherent::{InherentData, ProvideInherent}, + pallet_prelude::Get, traits::{OnFinalize, OnInitialize, OriginTrait, UnfilteredDispatchable}, weights::Weight, }; use frame_system::pallet_prelude::{BlockNumberFor, HeaderFor}; -use parachains_common::{AccountId, SLOT_DURATION}; +use parachains_common::SLOT_DURATION; use polkadot_parachain_primitives::primitives::{ HeadData, HrmpChannelId, RelayChainBlockNumber, XcmpMessageFormat, }; @@ -49,7 +50,7 @@ pub type AccountIdOf = ::AccountId; pub type ValidatorIdOf = ::ValidatorId; pub type SessionKeysOf = ::Keys; -pub struct CollatorSessionKeys< +pub struct CollatorSessionKey< Runtime: frame_system::Config + pallet_balances::Config + pallet_session::Config, > { collator: AccountIdOf, @@ -57,8 +58,14 @@ pub struct CollatorSessionKeys< key: SessionKeysOf, } +pub struct CollatorSessionKeys< + Runtime: frame_system::Config + pallet_balances::Config + pallet_session::Config, +> { + items: Vec>, +} + impl - CollatorSessionKeys + CollatorSessionKey { pub fn new( collator: AccountIdOf, @@ -67,14 +74,43 @@ impl Self { Self { collator, validator, key } } +} + +impl Default + for CollatorSessionKeys +{ + fn default() -> Self { + Self { items: vec![] } + } +} + +impl + CollatorSessionKeys +{ + pub fn new( + collator: AccountIdOf, + validator: ValidatorIdOf, + key: SessionKeysOf, + ) -> Self { + Self { items: vec![CollatorSessionKey::new(collator, validator, key)] } + } + + pub fn add(mut self, item: CollatorSessionKey) -> Self { + self.items.push(item); + self + } + pub fn collators(&self) -> Vec> { - vec![self.collator.clone()] + self.items.iter().map(|item| item.collator.clone()).collect::>() } pub fn session_keys( &self, ) -> Vec<(AccountIdOf, ValidatorIdOf, SessionKeysOf)> { - vec![(self.collator.clone(), self.validator.clone(), self.key.clone())] + self.items + .iter() + .map(|item| (item.collator.clone(), item.validator.clone(), item.key.clone())) + .collect::>() } } @@ -225,7 +261,7 @@ where AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, { - pub fn run_to_block(n: u32, author: AccountId) -> HeaderFor { + pub fn run_to_block(n: u32, author: AccountIdOf) -> HeaderFor { let mut last_header = None; loop { let block_number = frame_system::Pallet::::block_number(); @@ -360,7 +396,6 @@ impl { pub fn xcm_max_weight(from: XcmReceivedFrom) -> Weight { - use frame_support::traits::Get; match from { XcmReceivedFrom::Parent => ParachainSystem::ReservedDmpWeight::get(), XcmReceivedFrom::Sibling => ParachainSystem::ReservedXcmpWeight::get(), diff --git a/cumulus/scripts/bridges_kusama_polkadot.sh b/cumulus/scripts/bridges_kusama_polkadot.sh new file mode 100755 index 0000000000000..43ca5356d2c02 --- /dev/null +++ b/cumulus/scripts/bridges_kusama_polkadot.sh @@ -0,0 +1,195 @@ +#!/bin/bash + +# import common functions +source "$(dirname "$0")"/bridges_rococo_wococo.sh "import" + +# Address: 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY +# AccountId: [212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125] +ASSET_HUB_KUSAMA_ACCOUNT_SEED_FOR_LOCAL="//Alice" +# Address: 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY +# AccountId: [212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125] +ASSET_HUB_POLKADOT_ACCOUNT_ADDRESS_FOR_LOCAL="5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" + +# Expected sovereign accounts. +# +# Generated by: +# +# #[test] +# fn generate_sovereign_accounts() { +# use sp_core::crypto::Ss58Codec; +# +# parameter_types! { +# pub UniversalLocationAHK: InteriorMultiLocation = X2(GlobalConsensus(Kusama), Parachain(1000)); +# pub UniversalLocationAHP: InteriorMultiLocation = X2(GlobalConsensus(Polkadot), Parachain(1000)); +# } +# +# // SS58=0 +# println!("GLOBAL_CONSENSUS_KUSAMA_SOVEREIGN_ACCOUNT=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# GlobalConsensusConvertsFor::::convert_location( +# &MultiLocation { parents: 2, interior: X1(GlobalConsensus(Kusama)) }).unwrap() +# ).to_ss58check_with_version(0_u16.into()) +# ); +# println!("GLOBAL_CONSENSUS_KUSAMA_ASSET_HUB_KUSAMA_1000_SOVEREIGN_ACCOUNT=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# GlobalConsensusParachainConvertsFor::::convert_location( +# &MultiLocation { parents: 2, interior: X2(GlobalConsensus(Kusama), Parachain(1000)) }).unwrap() +# ).to_ss58check_with_version(0_u16.into()) +# ); +# +# // SS58=2 +# println!("GLOBAL_CONSENSUS_POLKADOT_SOVEREIGN_ACCOUNT=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# GlobalConsensusConvertsFor::::convert_location( +# &MultiLocation { parents: 2, interior: X1(GlobalConsensus(Polkadot)) }).unwrap() +# ).to_ss58check_with_version(2_u16.into()) +# ); +# println!("GLOBAL_CONSENSUS_POLKADOT_ASSET_HUB_POLKADOT_1000_SOVEREIGN_ACCOUNT=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# GlobalConsensusParachainConvertsFor::::convert_location( +# &MultiLocation { parents: 2, interior: X2(GlobalConsensus(Polkadot), Parachain(1000)) }).unwrap() +# ).to_ss58check_with_version(2_u16.into()) +# ); +# } +GLOBAL_CONSENSUS_KUSAMA_SOVEREIGN_ACCOUNT="14zcUAhP5XypiFQWA3b1AnGKrhZqR4XWUo4deWkwuN5y983G" +GLOBAL_CONSENSUS_KUSAMA_ASSET_HUB_KUSAMA_1000_SOVEREIGN_ACCOUNT="12GvRkNCmXFuaaziTJ2ZKAfa7MArKfLT2HYvLjQuepP3JuHf" +GLOBAL_CONSENSUS_POLKADOT_SOVEREIGN_ACCOUNT="FxqimVubBRPqJ8kTwb3wL7G4q645hEkBEnXPyttLsTrFc5Q" +GLOBAL_CONSENSUS_POLKADOT_ASSET_HUB_POLKADOT_1000_SOVEREIGN_ACCOUNT="FwGjEp7GXJXT9NjH8r4sqdyd8XZVogbxSs3iFakx4wFwJ5Y" + +function init_ksm_dot() { + ensure_relayer + + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + ~/local_bridge_testing/bin/substrate-relay init-bridge kusama-to-bridge-hub-polkadot \ + --source-host localhost \ + --source-port 9942 \ + --source-version-mode Auto \ + --target-host localhost \ + --target-port 8945 \ + --target-version-mode Auto \ + --target-signer //Bob +} + +function init_dot_ksm() { + ensure_relayer + + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + ~/local_bridge_testing/bin/substrate-relay init-bridge polkadot-to-bridge-hub-kusama \ + --source-host localhost \ + --source-port 9945 \ + --source-version-mode Auto \ + --target-host localhost \ + --target-port 8943 \ + --target-version-mode Auto \ + --target-signer //Bob +} + +function run_relay() { + ensure_relayer + +RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + ~/local_bridge_testing/bin/substrate-relay relay-headers-and-messages bridge-hub-kusama-bridge-hub-polkadot \ + --kusama-host localhost \ + --kusama-port 9942 \ + --kusama-version-mode Auto \ + --bridge-hub-kusama-host localhost \ + --bridge-hub-kusama-port 8943 \ + --bridge-hub-kusama-version-mode Auto \ + --bridge-hub-kusama-signer //Charlie \ + --polkadot-headers-to-bridge-hub-kusama-signer //Bob \ + --polkadot-parachains-to-bridge-hub-kusama-signer //Bob \ + --bridge-hub-kusama-transactions-mortality 4 \ + --polkadot-host localhost \ + --polkadot-port 9945 \ + --polkadot-version-mode Auto \ + --bridge-hub-polkadot-host localhost \ + --bridge-hub-polkadot-port 8945 \ + --bridge-hub-polkadot-version-mode Auto \ + --bridge-hub-polkadot-signer //Charlie \ + --kusama-headers-to-bridge-hub-polkadot-signer //Bob \ + --kusama-parachains-to-bridge-hub-polkadot-signer //Bob \ + --bridge-hub-polkadot-transactions-mortality 4 \ + --lane 00000000 +} + +case "$1" in + run-relay) + init_ksm_dot + init_dot_ksm + run_relay + ;; + init-asset-hub-kusama-local) + # create foreign assets for native Polkadot token (governance call on Kusama) + force_create_foreign_asset \ + "ws://127.0.0.1:9942" \ + "//Alice" \ + 1000 \ + "ws://127.0.0.1:9910" \ + "$(jq --null-input '{ "parents": 2, "interior": { "X1": { "GlobalConsensus": "Polkadot" } } }')" \ + "$GLOBAL_CONSENSUS_POLKADOT_SOVEREIGN_ACCOUNT" \ + 1000000000 \ + true + # drip SA which holds reserves + transfer_balance \ + "ws://127.0.0.1:9910" \ + "//Alice" \ + "$GLOBAL_CONSENSUS_POLKADOT_ASSET_HUB_POLKADOT_1000_SOVEREIGN_ACCOUNT" \ + $((1000000000 + 50000000000 * 20)) + ;; + init-asset-hub-polkadot-local) + # create foreign assets for native Polkadot token (governance call on Kusama) + force_create_foreign_asset \ + "ws://127.0.0.1:9945" \ + "//Alice" \ + 1000 \ + "ws://127.0.0.1:9010" \ + "$(jq --null-input '{ "parents": 2, "interior": { "X1": { "GlobalConsensus": "Kusama" } } }')" \ + "$GLOBAL_CONSENSUS_KUSAMA_SOVEREIGN_ACCOUNT" \ + 1000000000 \ + true + # drip SA which holds reserves + transfer_balance \ + "ws://127.0.0.1:9010" \ + "//Alice" \ + "$GLOBAL_CONSENSUS_KUSAMA_ASSET_HUB_KUSAMA_1000_SOVEREIGN_ACCOUNT" \ + $((1000000000 + 50000000000 * 20)) + ;; + reserve-transfer-assets-from-asset-hub-kusama-local) + ensure_polkadot_js_api + # send KSMs to Alice account on AHP + limited_reserve_transfer_assets \ + "ws://127.0.0.1:9910" \ + "//Alice" \ + "$(jq --null-input '{ "V3": { "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Polkadot" }, { "Parachain": 1000 } ] } } }')" \ + "$(jq --null-input '{ "V3": { "parents": 0, "interior": { "X1": { "AccountId32": { "id": [212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125] } } } } }')" \ + "$(jq --null-input '{ "V3": [ { "id": { "Concrete": { "parents": 1, "interior": "Here" } }, "fun": { "Fungible": 1000000000000 } } ] }')" \ + 0 \ + "Unlimited" + ;; + reserve-transfer-assets-from-asset-hub-polkadot-local) + ensure_polkadot_js_api + # send DOTs to Alice account on AHH + limited_reserve_transfer_assets \ + "ws://127.0.0.1:9010" \ + "//Alice" \ + "$(jq --null-input '{ "V3": { "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Kusama" }, { "Parachain": 1000 } ] } } }')" \ + "$(jq --null-input '{ "V3": { "parents": 0, "interior": { "X1": { "AccountId32": { "id": [212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125] } } } } }')" \ + "$(jq --null-input '{ "V3": [ { "id": { "Concrete": { "parents": 1, "interior": "Here" } }, "fun": { "Fungible": 2000000000000 } } ] }')" \ + 0 \ + "Unlimited" + ;; + stop) + pkill -f polkadot + pkill -f parachain + ;; + *) + echo "A command is require. Supported commands for: + Local (zombienet) run: + - run-relay + - init-asset-hub-kusama-local + - init-asset-hub-polkadot-local + - reserve-transfer-assets-from-asset-hub-kusama-local + - reserve-transfer-assets-from-asset-hub-polkadot-local"; + exit 1 + ;; +esac diff --git a/cumulus/scripts/bridges_rococo_wococo.sh b/cumulus/scripts/bridges_rococo_wococo.sh index 1117ed6810928..2ed43a4be64eb 100755 --- a/cumulus/scripts/bridges_rococo_wococo.sh +++ b/cumulus/scripts/bridges_rococo_wococo.sh @@ -270,38 +270,60 @@ function force_create_foreign_asset() { local relay_chain_seed=$2 local runtime_para_id=$3 local runtime_para_endpoint=$4 - local global_consensus=$5 + local asset_multilocation=$5 local asset_owner_account_id=$6 + local min_balance=$7 + local is_sufficient=$8 echo " calling force_create_foreign_asset:" echo " relay_url: ${relay_url}" echo " relay_chain_seed: ${relay_chain_seed}" echo " runtime_para_id: ${runtime_para_id}" echo " runtime_para_endpoint: ${runtime_para_endpoint}" - echo " global_consensus: ${global_consensus}" + echo " asset_multilocation: ${asset_multilocation}" echo " asset_owner_account_id: ${asset_owner_account_id}" + echo " min_balance: ${min_balance}" + echo " is_sufficient: ${is_sufficient}" echo " params:" # 1. generate data for Transact (ForeignAssets::force_create) - local asset_id=$(jq --null-input \ - --arg global_consensus "$global_consensus" \ - ' - { - "parents": 2, - "interior": { - "X1": { - "GlobalConsensus": $global_consensus, - } - } - } - ' - ) local tmp_output_file=$(mktemp) - generate_hex_encoded_call_data "force-create-asset" "${runtime_para_endpoint}" "${tmp_output_file}" "$asset_id" "$asset_owner_account_id" false "1000" + generate_hex_encoded_call_data "force-create-asset" "${runtime_para_endpoint}" "${tmp_output_file}" "$asset_multilocation" "$asset_owner_account_id" $is_sufficient $min_balance local hex_encoded_data=$(cat $tmp_output_file) + # 2. trigger governance call send_governance_transact "${relay_url}" "${relay_chain_seed}" "${runtime_para_id}" "${hex_encoded_data}" 200000000 12000 } +function limited_reserve_transfer_assets() { + local url=$1 + local seed=$2 + local destination=$3 + local beneficiary=$4 + local assets=$5 + local fee_asset_item=$6 + local weight_limit=$7 + echo " calling limited_reserve_transfer_assets:" + echo " url: ${url}" + echo " seed: ${seed}" + echo " destination: ${destination}" + echo " beneficiary: ${beneficiary}" + echo " assets: ${assets}" + echo " fee_asset_item: ${fee_asset_item}" + echo " weight_limit: ${weight_limit}" + echo "" + echo "--------------------------------------------------" + + polkadot-js-api \ + --ws "${url?}" \ + --seed "${seed?}" \ + tx.polkadotXcm.limitedReserveTransferAssets \ + "${destination}" \ + "${beneficiary}" \ + "${assets}" \ + "${fee_asset_item}" \ + "${weight_limit}" +} + function allow_assets_transfer_receive() { local relay_url=$1 local relay_chain_seed=$2 diff --git a/cumulus/zombienet/bridge-hubs/bridge_hub_kusama_local_network.toml b/cumulus/zombienet/bridge-hubs/bridge_hub_kusama_local_network.toml new file mode 100644 index 0000000000000..7f753d6dd5044 --- /dev/null +++ b/cumulus/zombienet/bridge-hubs/bridge_hub_kusama_local_network.toml @@ -0,0 +1,107 @@ +[settings] +node_spawn_timeout = 240 + +[relaychain] +default_command = "{{POLKADOT_BINARY_PATH}}" +default_args = [ "-lparachain=debug,xcm=trace" ] +chain = "kusama-local" + + [[relaychain.nodes]] + name = "alice-validator" + validator = true + rpc_port = 9932 + ws_port = 9942 + extra_args = ["--no-mdns --bootnodes {{'bob-validator'|zombie('multiAddress')}}"] + balance = 2000000000000 + + [[relaychain.nodes]] + name = "bob-validator" + validator = true + rpc_port = 9933 + ws_port = 9943 + extra_args = ["--no-mdns --bootnodes {{'alice-validator'|zombie('multiAddress')}}"] + balance = 2000000000000 + + [[relaychain.nodes]] + name = "charlie-validator" + validator = true + rpc_port = 9934 + ws_port = 9944 + extra_args = ["--no-mdns --bootnodes {{'alice-validator'|zombie('multiAddress')}}"] + balance = 2000000000000 + +[[parachains]] +id = 1002 +chain = "bridge-hub-kusama-local" +cumulus_based = true + + # run alice as parachain collator + [[parachains.collators]] + name = "alice-collator" + validator = true + command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + rpc_port = 8933 + ws_port = 8943 + args = [ + "-lparachain=debug,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace", + ] + extra_args = [ + "--force-authoring", "--no-mdns", "--bootnodes {{'bob-collator'|zombie('multiAddress')}}", + "-- --port 41333 --rpc-port 48933 --ws-port 48943 --no-mdns", "--bootnodes {{'alice-validator'|zombie('multiAddress')}}" + ] + + # run bob as parachain collator + [[parachains.collators]] + name = "bob-collator" + validator = true + command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + rpc_port = 8934 + ws_port = 8944 + args = [ + "-lparachain=trace,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace", + ] + extra_args = [ + "--force-authoring", "--no-mdns", "--bootnodes {{'alice-collator'|zombie('multiAddress')}}", + "-- --port 41334 --rpc-port 48934 --ws-port 48944 --no-mdns", "--bootnodes {{'bob-validator'|zombie('multiAddress')}}" + ] + +[[parachains]] +id = 1000 +chain = "asset-hub-kusama-local" +cumulus_based = true + + [[parachains.collators]] + name = "asset-hub-kusama-collator1" + rpc_port = 9911 + ws_port = 9910 + command = "{{POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_KUSAMA}}" + args = [ + "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace", + ] + extra_args = [ + "--no-mdns", "--bootnodes {{'asset-hub-kusama-collator2'|zombie('multiAddress')}}", + "-- --port 51333 --rpc-port 58933 --ws-port 58943 --no-mdns", "--bootnodes {{'alice-validator'|zombie('multiAddress')}}" + ] + + [[parachains.collators]] + name = "asset-hub-kusama-collator2" + command = "{{POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_KUSAMA}}" + args = [ + "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace", + ] + extra_args = [ + "--no-mdns", "--bootnodes {{'asset-hub-kusama-collator1'|zombie('multiAddress')}}", + "-- --port 51433 --rpc-port 58833 --ws-port 58843 --no-mdns", "--bootnodes {{'alice-validator'|zombie('multiAddress')}}" + ] + +[[hrmp_channels]] +sender = 1000 +recipient = 1002 +max_capacity = 4 +max_message_size = 524288 + +[[hrmp_channels]] +sender = 1002 +recipient = 1000 +max_capacity = 4 +max_message_size = 524288 diff --git a/cumulus/zombienet/bridge-hubs/bridge_hub_polkadot_local_network.toml b/cumulus/zombienet/bridge-hubs/bridge_hub_polkadot_local_network.toml new file mode 100644 index 0000000000000..320456a256528 --- /dev/null +++ b/cumulus/zombienet/bridge-hubs/bridge_hub_polkadot_local_network.toml @@ -0,0 +1,107 @@ +[settings] +node_spawn_timeout = 240 + +[relaychain] +default_command = "{{POLKADOT_BINARY_PATH}}" +default_args = [ "-lparachain=debug,xcm=trace" ] +chain = "polkadot-local" + + [[relaychain.nodes]] + name = "alice-validator-dot" + validator = true + rpc_port = 9935 + ws_port = 9945 + extra_args = ["--no-mdns --bootnodes {{'bob-validator-dot'|zombie('multiAddress')}}"] + balance = 2000000000000 + + [[relaychain.nodes]] + name = "bob-validator-dot" + validator = true + rpc_port = 9936 + ws_port = 9946 + extra_args = ["--no-mdns --bootnodes {{'alice-validator-dot'|zombie('multiAddress')}}"] + balance = 2000000000000 + + [[relaychain.nodes]] + name = "charlie-validator-dot" + validator = true + rpc_port = 9937 + ws_port = 9947 + extra_args = ["--no-mdns --bootnodes {{'alice-validator-dot'|zombie('multiAddress')}}"] + balance = 2000000000000 + +[[parachains]] +id = 1002 +chain = "bridge-hub-polkadot-local" +cumulus_based = true + + # run alice as parachain collator + [[parachains.collators]] + name = "alice-collator-dot" + validator = true + command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + rpc_port = 8935 + ws_port = 8945 + args = [ + "-lparachain=debug,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace", + ] + extra_args = [ + "--force-authoring", "--no-mdns", "--bootnodes {{'bob-collator-dot'|zombie('multiAddress')}}", + "-- --port 41335 --rpc-port 48935 --ws-port 48945 --no-mdns", "--bootnodes {{'alice-validator-dot'|zombie('multiAddress')}}" + ] + + # run bob as parachain collator + [[parachains.collators]] + name = "bob-collator-dot" + validator = true + command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + rpc_port = 8936 + ws_port = 8946 + args = [ + "-lparachain=trace,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace", + ] + extra_args = [ + "--force-authoring", "--no-mdns", "--bootnodes {{'alice-collator-dot'|zombie('multiAddress')}}", + "-- --port 41336 --rpc-port 48936 --ws-port 48946 --no-mdns", "--bootnodes {{'bob-validator-dot'|zombie('multiAddress')}}" + ] + +[[parachains]] +id = 1000 +chain = "asset-hub-polkadot-local" +cumulus_based = true + + [[parachains.collators]] + name = "asset-hub-polkadot-collator1" + rpc_port = 9011 + ws_port = 9010 + command = "{{POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_POLKADOT}}" + args = [ + "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace", + ] + extra_args = [ + "--no-mdns", "--bootnodes {{'asset-hub-polkadot-collator2'|zombie('multiAddress')}}", + "-- --port 31333 --rpc-port 38933 --ws-port 38943 --no-mdns", "--bootnodes {{'alice-validator-dot'|zombie('multiAddress')}}" + ] + + [[parachains.collators]] + name = "asset-hub-polkadot-collator2" + command = "{{POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_POLKADOT}}" + args = [ + "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace", + ] + extra_args = [ + "--no-mdns", "--bootnodes {{'asset-hub-polkadot-collator1'|zombie('multiAddress')}}", + "-- --port 31433 --rpc-port 38833 --ws-port 38843 --no-mdns", "--bootnodes {{'alice-validator-dot'|zombie('multiAddress')}}" + ] + +[[hrmp_channels]] +sender = 1000 +recipient = 1002 +max_capacity = 4 +max_message_size = 524288 + +[[hrmp_channels]] +sender = 1002 +recipient = 1000 +max_capacity = 4 +max_message_size = 524288