diff --git a/xcm-simulator/src/lib.rs b/xcm-simulator/src/lib.rs index 8f1380df..42a89a17 100644 --- a/xcm-simulator/src/lib.rs +++ b/xcm-simulator/src/lib.rs @@ -17,6 +17,7 @@ mod laosish; mod parachain; mod relay_chain; +mod parachain_teleporter; use hex_literal::hex; @@ -32,6 +33,7 @@ pub const INITIAL_BALANCE: u128 = 1_000_000_000; const PARA_A_ID: u32 = 1; const PARA_B_ID: u32 = 2; +const PARA_TELEPORTER_ID: u32 = 7; const PARA_LAOSISH_ID: u32 = 3; decl_test_parachain! { @@ -52,6 +54,15 @@ decl_test_parachain! { } } +decl_test_parachain! { + pub struct ParaTeleporter { + Runtime = parachain::Runtime, + XcmpMessageHandler = parachain::MsgQueue, + DmpMessageHandler = parachain::MsgQueue, + new_ext = para_ext(PARA_TELEPORTER_ID), + } +} + decl_test_parachain! { pub struct Laosish { Runtime = laosish::Runtime, @@ -79,6 +90,7 @@ decl_test_network! { parachains = vec![ (PARA_A_ID, ParaA), (PARA_B_ID, ParaB), + (PARA_TELEPORTER_ID, ParaTeleporter), (PARA_LAOSISH_ID, Laosish), ], } @@ -185,6 +197,7 @@ pub fn relay_ext() -> sp_io::TestExternalities { pub type RelayChainPalletXcm = pallet_xcm::Pallet; pub type ParachainPalletXcm = pallet_xcm::Pallet; +pub type ParachainTeleporterPalletXcm = pallet_xcm::Pallet; pub type LaosishPalletXcm = pallet_xcm::Pallet; #[frame_support::pallet] diff --git a/xcm-simulator/src/parachain_teleporter.rs b/xcm-simulator/src/parachain_teleporter.rs new file mode 100644 index 00000000..c59b56de --- /dev/null +++ b/xcm-simulator/src/parachain_teleporter.rs @@ -0,0 +1,387 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Parachain runtime mock. + +use core::marker::PhantomData; +use frame_support::{ + construct_runtime, derive_impl, parameter_types, + traits::{ContainsPair, EnsureOrigin, EnsureOriginWithArg, Everything, EverythingBut, Nothing}, + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, +}; + +use frame_system::EnsureRoot; +use sp_core::ConstU32; +use sp_runtime::{ + traits::{Get, IdentityLookup}, + AccountId32, +}; +use sp_std::prelude::*; + +use crate::mock_msg_queue; +use assets_common::{foreign_creators::ForeignCreators, matching::FromSiblingParachain}; +use frame_support::traits::AsEnsureOriginWithArg; +use frame_system::EnsureSigned; +use pallet_xcm::XcmPassthrough; +use parachains_common::AssetIdForTrustBackedAssets; +use polkadot_parachain_primitives::primitives::Sibling; +use sp_runtime::codec; +use xcm::latest::prelude::*; +use xcm_builder::{ + Account32Hash, AccountId32Aliases, AllowUnpaidExecutionFrom, ConvertedConcreteId, + EnsureDecodableXcm, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, + FrameTransactionalProcessor, FungibleAdapter, GlobalConsensusParachainConvertsFor, IsConcrete, + NativeAsset, NoChecking, NonFungiblesAdapter, ParentIsPreset, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, MintLocation +}; +use xcm_executor::{ + traits::{ConvertLocation, JustTry}, + Config, XcmExecutor, +}; + +pub type SovereignAccountOf = ( + SiblingParachainConvertsVia, + AccountId32Aliases, + ParentIsPreset, +); + +pub type AccountId = AccountId32; +pub type Balance = u128; + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type Nonce = u64; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type BlockHashCount = BlockHashCount; + type AccountData = pallet_balances::AccountData; +} + +parameter_types! { + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + type MaxFreezes = ConstU32<0>; +} + +#[cfg(feature = "runtime-benchmarks")] +pub struct UniquesHelper; +#[cfg(feature = "runtime-benchmarks")] +impl pallet_uniques::BenchmarkHelper for UniquesHelper { + fn collection(i: u16) -> Location { + GeneralIndex(i as u128).into() + } + fn item(i: u16) -> AssetInstance { + AssetInstance::Index(i as u128) + } +} + +impl pallet_uniques::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type CollectionId = Location; + type ItemId = AssetInstance; + type Currency = Balances; + type CreateOrigin = ForeignCreatorsUnique; + type ForceOrigin = frame_system::EnsureRoot; + type CollectionDeposit = frame_support::traits::ConstU128<1_000>; + type ItemDeposit = frame_support::traits::ConstU128<1_000>; + type MetadataDepositBase = frame_support::traits::ConstU128<1_000>; + type AttributeDepositBase = frame_support::traits::ConstU128<1_000>; + type DepositPerByte = frame_support::traits::ConstU128<1>; + type StringLimit = ConstU32<64>; + type KeyLimit = ConstU32<64>; + type ValueLimit = ConstU32<128>; + type Locker = (); + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type Helper = UniquesHelper; +} + +// `EnsureOriginWithArg` impl for `CreateOrigin` which allows only XCM origins +// which are locations containing the class location. +pub struct ForeignCreatorsUnique; +impl EnsureOriginWithArg for ForeignCreatorsUnique { + type Success = AccountId; + + fn try_origin( + o: RuntimeOrigin, + a: &Location, + ) -> sp_std::result::Result { + let origin_location = pallet_xcm::EnsureXcm::::try_origin(o.clone())?; + if !a.starts_with(&origin_location) { + return Err(o) + } + SovereignAccountOf::convert_location(&origin_location).ok_or(o) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(a: &Location) -> Result { + Ok(pallet_xcm::Origin::Xcm(a.clone()).into()) + } +} + +parameter_types! { + pub const ReservedXcmpWeight: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4), 0); + pub const ReservedDmpWeight: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4), 0); +} + +parameter_types! { + pub const RelayLocation: Location = Location::parent(); + pub const RelayNetwork: NetworkId = NetworkId::Kusama; + pub UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(MsgQueue::parachain_id().into())].into(); + pub HereLocation: Location = Location::here(); + pub CheckingAccount: AccountId = PolkadotXcm::check_account(); + pub Checking: (AccountId, MintLocation) = (CheckingAccount::get(), MintLocation::Local); +} + +pub type LocationToAccountId = ( + ParentIsPreset, + SiblingParachainConvertsVia, + AccountId32Aliases, + Account32Hash<(), AccountId>, +); + +pub type XcmOriginToCallOrigin = ( + SovereignSignedViaLocation, + SignedAccountId32AsNative, + XcmPassthrough, +); + +parameter_types! { + pub const UnitWeightCost: Weight = Weight::from_parts(1, 1); + pub KsmPerSecondPerByte: (AssetId, u128, u128) = (AssetId(Parent.into()), 1, 1); + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; + pub ForeignPrefix: Location = (Parent,).into(); +} + +/// Means for transacting assets on this chain. +pub type LocalAssetTransactor = FungibleAdapter< + // Use this currency: + Balances, + // Use this currency when it is a fungible asset matching the given location or name: + IsConcrete, + // Do a simple pun to convert an AccountId20 Location into a native chain account ID: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We track any teleports. + Checking, +>; + +pub type XcmRouter = EnsureDecodableXcm>; +pub type Barrier = AllowUnpaidExecutionFrom; +parameter_types! { + pub NativeToken: AssetId = AssetId(Location::here()); + pub NativeTokenFilter: AssetFilter = Wild(AllOf { fun: WildFungible, id: NativeToken::get() }); + pub AssetHubLocation: Location = Location::new(1, [Parachain(crate::PARA_A_ID)]); + pub AssetHubTrustedTeleporter: (AssetFilter, Location) = (NativeTokenFilter::get(), AssetHubLocation::get()); +} + +pub type TrustedTeleporters = xcm_builder::Case; + +pub struct XcmConfig; +impl Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = XcmOriginToCallOrigin; + type IsReserve = (); + type IsTeleporter = TrustedTeleporters; + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfFungible; + type ResponseHandler = (); + type AssetTrap = (); + type AssetLocker = PolkadotXcm; + type AssetExchanger = (); + type AssetClaims = (); + type SubscriptionService = (); + type PalletInstancesInfo = (); + type FeeManager = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; + type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); +} + +impl mock_msg_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; +} + +pub type LocalOriginToLocation = SignedToAccountId32; + +pub struct TrustedLockerCase(PhantomData); +impl> ContainsPair for TrustedLockerCase { + fn contains(origin: &Location, asset: &Asset) -> bool { + let (o, a) = T::get(); + a.matches(asset) && &o == origin + } +} + +parameter_types! { + pub RelayTokenForRelay: (Location, AssetFilter) = (Parent.into(), Wild(AllOf { id: AssetId(Parent.into()), fun: WildFungible })); +} + +pub type TrustedLockers = TrustedLockerCase; + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + type ExecuteXcmOrigin = EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Nothing; + type XcmReserveTransferFilter = Everything; + type Weigher = FixedWeightBounds; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = TrustedLockers; + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = ConstU32<8>; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); + type WeightInfo = pallet_xcm::TestWeightInfo; + type AdminOrigin = EnsureRoot; +} + +pub type ForeignCreatorsSovereignAccountOf = ( + SiblingParachainConvertsVia, + AccountId32Aliases, + ParentIsPreset, + GlobalConsensusParachainConvertsFor, +); + +// Called "Trust Backed" assets because these are generally registered by some account, and users of +// the asset assume it has some claimed backing. The pallet is called `Assets` in +// `construct_runtime` to avoid breaking changes on storage reads. +pub type TrustBackedAssetsInstance = pallet_assets::Instance1; +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = AssetIdForTrustBackedAssets; + type AssetIdParameter = codec::Compact; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = EnsureRoot; + type AssetDeposit = frame_support::traits::ConstU128<1_000>; + type MetadataDepositBase = frame_support::traits::ConstU128<1_000>; + type MetadataDepositPerByte = frame_support::traits::ConstU128<1_000>; + type ApprovalDeposit = ExistentialDeposit; + type StringLimit = frame_support::traits::ConstU32<50>; + type Freezer = (); + type Extra = (); + type WeightInfo = (); + type CallbackHandle = (); + type AssetAccountDeposit = frame_support::traits::ConstU128<1_000>; + type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +/// Simple conversion of `u32` into an `AssetId` for use in benchmarking. +pub struct XcmBenchmarkHelper; +#[cfg(feature = "runtime-benchmarks")] +impl pallet_assets::BenchmarkHelper for XcmBenchmarkHelper { + fn create_asset_id_parameter(id: u32) -> xcm::v3::Location { + xcm::v3::Location::new(1, xcm::v3::Junction::Parachain(id)) + } +} + +/// Assets managed by some foreign location. Note: we do not declare a `ForeignAssetsCall` type, as +/// this type is used in proxy definitions. We assume that a foreign location would not want to set +/// an individual, local account as a proxy for the issuance of their assets. This issuance should +/// be managed by the foreign location's governance. +pub type ForeignAssetsInstance = pallet_assets::Instance2; +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = xcm::v3::Location; + type AssetIdParameter = xcm::v3::Location; + type Currency = Balances; + type CreateOrigin = ForeignCreators< + FromSiblingParachain, xcm::v3::Location>, + ForeignCreatorsSovereignAccountOf, + AccountId, + xcm::v3::Location, + >; + type ForceOrigin = EnsureRoot; + type AssetDeposit = frame_support::traits::ConstU128<1_000>; + type MetadataDepositBase = frame_support::traits::ConstU128<1_000>; + type MetadataDepositPerByte = frame_support::traits::ConstU128<1_000>; + type ApprovalDeposit = ExistentialDeposit; + type StringLimit = ConstU32<64>; + type Freezer = (); + type Extra = (); + type WeightInfo = (); + type CallbackHandle = (); + type AssetAccountDeposit = frame_support::traits::ConstU128<1_000>; + type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = XcmBenchmarkHelper; +} + +impl parachain_info::Config for Runtime {} + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime + { + System: frame_system, + ParachainInfo: parachain_info, + Balances: pallet_balances, + MsgQueue: mock_msg_queue, + PolkadotXcm: pallet_xcm, + ForeignUniques: pallet_uniques, + Assets: pallet_assets:: = 50, + ForeignAssets: pallet_assets:: = 53, + } +); diff --git a/xcm-simulator/src/tests/mod.rs b/xcm-simulator/src/tests/mod.rs index d6d28576..2d984a4e 100644 --- a/xcm-simulator/src/tests/mod.rs +++ b/xcm-simulator/src/tests/mod.rs @@ -9,6 +9,8 @@ mod laosish_xcm; pub type ForeignAssetsCall = pallet_assets::Call; +pub type TeleportAssetsCall = + pallet_assets::Call; pub type TrustBackedAssetsCall = pallet_assets::Call; @@ -690,3 +692,40 @@ fn xcmp_create_foreign_asset() { ))); }); } + +#[test] +fn teleport_para_teleport_to_para_a() { + MockNet::reset(); + + let para_teleporter_native_asset_location = + xcm::v3::Location::new(1, [xcm::v3::Junction::Parachain(PARA_TELEPORTER_ID)]); + + let create_asset = parachain_teleporter::RuntimeCall::ForeignAssets(TeleportAssetsCall::create { + id: para_teleporter_native_asset_location, + admin: sibling_account_id(PARA_TELEPORTER_ID), + min_balance: 1000, + }); + + ParaTeleporter::execute_with(|| { + assert_ok!(ParachainTeleporterPalletXcm::send_xcm( + Here, + (Parent, Parachain(PARA_A_ID)), + Xcm(vec![Transact { + origin_kind: OriginKind::Xcm, + require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + call: create_asset.encode().into(), + }]), + )); + }); + + let amount = 1_000; + + assert_ok!(ParachainTeleporterPalletXcm::limited_teleport_assets( + parachain_teleporter::RuntimeOrigin::signed(ALICE.into()), + Box::new(Parachain(PARA_A_ID).into()), + Box::new(AccountId32 { network: None, id: ALICE.into() }.into()), + Box::new((Here, amount).into()), + 0, + WeightLimit::Limited(Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024)), + )); +}