diff --git a/Cargo.lock b/Cargo.lock index bc1c9f3..8025ecf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5493,6 +5493,23 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-xcm-executor-utils" +version = "0.1.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "staging-xcm", +] + [[package]] name = "parity-db" version = "0.4.12" diff --git a/Cargo.toml b/Cargo.toml index ed56f1b..1ef0a0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "client/orchestrator-chain-interface", "container-chain-pallets/*", "container-chain-primitives/*", + "pallets/*", "primitives/*", "test-sproof-builder" ] diff --git a/pallets/xcm-executor-utils/Cargo.toml b/pallets/xcm-executor-utils/Cargo.toml new file mode 100644 index 0000000..b822076 --- /dev/null +++ b/pallets/xcm-executor-utils/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "pallet-xcm-executor-utils" +authors = { workspace = true } +description = "XCM Executor configuration utility pallet" +edition = "2021" +license = "GPL-3.0-only" +version = "0.1.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +serde = { workspace = true, features = ["derive"] } + +frame-benchmarking = { workspace = true, optional = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +parity-scale-codec = { workspace = true, features = [ + "derive", + "max-encoded-len", +] } +scale-info = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +staging-xcm = { workspace = true } + +[features] +default = ["std"] +std = [ + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "parity-scale-codec/std", + "scale-info/std", + "serde/std", + "sp-runtime/std", + "sp-std/std", + "staging-xcm/std", +] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] diff --git a/pallets/xcm-executor-utils/src/benchmarks.rs b/pallets/xcm-executor-utils/src/benchmarks.rs new file mode 100644 index 0000000..c197a8c --- /dev/null +++ b/pallets/xcm-executor-utils/src/benchmarks.rs @@ -0,0 +1,121 @@ +// Copyright (C) Moondance Labs Ltd. +// This file is part of Tanssi. + +// Tanssi 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. + +// Tanssi 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 Tanssi. If not, see + +#![cfg(feature = "runtime-benchmarks")] + +//! Benchmarking +use { + crate::{Call, Config, DefaultTrustPolicy, MultiLocation, Pallet, TrustPolicy}, + frame_benchmarking::{impl_benchmark_test_suite, v2::*}, + frame_system::RawOrigin, + sp_std::vec, + staging_xcm::v3::Junctions::Here, +}; + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn set_reserve_policy() -> Result<(), BenchmarkError> { + #[extrinsic_call] + _( + RawOrigin::Root, + MultiLocation { + parents: 1, + interior: Here, + }, + TrustPolicy::DefaultTrustPolicy(DefaultTrustPolicy::Never), + ); + + // assert!( + Ok(()) + } + + #[benchmark] + fn remove_reserve_policy() -> Result<(), BenchmarkError> { + let _ = Pallet::::set_reserve_policy( + RawOrigin::Root.into(), + MultiLocation { + parents: 1, + interior: Here, + }, + TrustPolicy::DefaultTrustPolicy(DefaultTrustPolicy::Never), + ); + + #[extrinsic_call] + _( + RawOrigin::Root, + MultiLocation { + parents: 1, + interior: Here, + }, + ); + assert!(Pallet::::reserve_policy(MultiLocation { + parents: 1, + interior: Here, + }) + .is_none()); + + Ok(()) + } + + #[benchmark] + fn set_teleport_policy() -> Result<(), BenchmarkError> { + #[extrinsic_call] + _( + RawOrigin::Root, + MultiLocation { + parents: 1, + interior: Here, + }, + TrustPolicy::DefaultTrustPolicy(DefaultTrustPolicy::Never), + ); + + // assert!( + Ok(()) + } + + #[benchmark] + fn remove_teleport_policy() -> Result<(), BenchmarkError> { + let _ = Pallet::::set_teleport_policy( + RawOrigin::Root.into(), + MultiLocation { + parents: 1, + interior: Here, + }, + TrustPolicy::DefaultTrustPolicy(DefaultTrustPolicy::Never), + ); + + #[extrinsic_call] + _( + RawOrigin::Root, + MultiLocation { + parents: 1, + interior: Here, + }, + ); + assert!(Pallet::::teleport_policy(MultiLocation { + parents: 1, + interior: Here, + }) + .is_none()); + + Ok(()) + } + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/pallets/xcm-executor-utils/src/filters.rs b/pallets/xcm-executor-utils/src/filters.rs new file mode 100644 index 0000000..1aeb4d2 --- /dev/null +++ b/pallets/xcm-executor-utils/src/filters.rs @@ -0,0 +1,334 @@ +// Copyright (C) Moondance Labs Ltd. +// This file is part of Tanssi. + +// Tanssi 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. + +// Tanssi 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 Tanssi. If not, see . + +use { + crate::{Config, DefaultTrustPolicy, TrustPolicy}, + frame_support::{pallet_prelude::*, traits::ContainsPair}, + staging_xcm::v3::{ + AssetId, + Junction::Parachain, + Junctions::{Here, X1}, + MultiAsset, MultiLocation, + }, +}; + +fn apply_policy( + asset: &MultiAsset, + origin: &MultiLocation, + maybe_origin_policy: Option>, + default_policy: DefaultTrustPolicy, +) -> bool { + if let Some(origin_policy) = maybe_origin_policy { + match origin_policy { + TrustPolicy::AllowedAssets(allowed_assets) => allowed_assets.contains(&asset.id), + TrustPolicy::DefaultTrustPolicy(origin_default_policy) => match origin_default_policy { + DefaultTrustPolicy::All => true, + DefaultTrustPolicy::AllNative => NativeAssetReserve::contains(asset, origin), + DefaultTrustPolicy::Never => false, + }, + } + } else { + match default_policy { + DefaultTrustPolicy::All => true, + DefaultTrustPolicy::AllNative => NativeAssetReserve::contains(asset, origin), + DefaultTrustPolicy::Never => false, + } + } +} + +pub struct IsReserveFilter(PhantomData); +impl ContainsPair for IsReserveFilter +where + T: Config, +{ + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { + let maybe_origin_policy = crate::Pallet::::reserve_policy(origin); + let default_policy = ::ReserveDefaultTrustPolicy::get(); + + apply_policy::(asset, origin, maybe_origin_policy, default_policy) + } +} + +pub struct IsTeleportFilter(PhantomData); +impl ContainsPair for IsTeleportFilter +where + T: Config, +{ + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { + let maybe_origin_policy = crate::Pallet::::teleport_policy(origin); + let default_policy = ::TeleportDefaultTrustPolicy::get(); + + apply_policy::(asset, origin, maybe_origin_policy, default_policy) + } +} + +// TODO: this should probably move to somewhere in the polkadot-sdk repo +pub struct NativeAssetReserve; +impl ContainsPair for NativeAssetReserve { + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { + let reserve = if let AssetId::Concrete(location) = &asset.id { + if location.parents == 0 && !matches!(location.first_interior(), Some(Parachain(_))) { + Some(MultiLocation::here()) + } else { + location.chain_part() + } + } else { + None + }; + + if let Some(ref reserve) = reserve { + if reserve == origin { + return true; + } + } + false + } +} + +pub trait Parse { + /// Returns the "chain" location part. It could be parent, sibling + /// parachain, or child parachain. + fn chain_part(&self) -> Option; + /// Returns "non-chain" location part. + fn non_chain_part(&self) -> Option; +} + +impl Parse for MultiLocation { + fn chain_part(&self) -> Option { + match (self.parents, self.first_interior()) { + // sibling parachain + (1, Some(Parachain(id))) => Some(MultiLocation::new(1, X1(Parachain(*id)))), + // parent + (1, _) => Some(MultiLocation::parent()), + // children parachain + (0, Some(Parachain(id))) => Some(MultiLocation::new(0, X1(Parachain(*id)))), + _ => None, + } + } + + fn non_chain_part(&self) -> Option { + let mut junctions = *self.interior(); + while matches!(junctions.first(), Some(Parachain(_))) { + let _ = junctions.take_first(); + } + + if junctions != Here { + Some(MultiLocation::new(0, junctions)) + } else { + None + } + } +} + +#[cfg(test)] +mod test { + use { + super::*, + crate::mock::{mock_all::TestAll, mock_all_native::TestAllNative, mock_never::TestNever}, + staging_xcm::latest::Fungibility::Fungible, + }; + + #[test] + fn default_policy_all_allows_any() { + let parent_multilocation = MultiLocation::parent(); + let grandparent_asset = MultiAsset { + id: AssetId::Concrete(MultiLocation::grandparent()), + fun: Fungible(1_000), + }; + + assert!(apply_policy::( + &grandparent_asset, + &parent_multilocation, + None, + ::ReserveDefaultTrustPolicy::get(), + )); + } + + #[test] + fn default_policy_all_native_allows_native() { + let parent_multilocation = MultiLocation::parent(); + let parent_asset = MultiAsset { + id: AssetId::Concrete(MultiLocation::parent()), + fun: Fungible(1_000), + }; + + assert!(apply_policy::( + &parent_asset, + &parent_multilocation, + None, + ::ReserveDefaultTrustPolicy::get(), + )); + } + + #[test] + fn default_policy_all_native_rejects_non_native() { + let parent_multilocation = MultiLocation::parent(); + let grandparent_asset = MultiAsset { + id: AssetId::Concrete(MultiLocation::grandparent()), + fun: Fungible(1_000), + }; + + assert_eq!( + apply_policy::( + &grandparent_asset, + &parent_multilocation, + None, + ::ReserveDefaultTrustPolicy::get(), + ), + false + ); + } + + #[test] + fn default_policy_never_rejects_any() { + let parent_multilocation = MultiLocation::parent(); + let parent_asset = MultiAsset { + id: AssetId::Concrete(MultiLocation::parent()), + fun: Fungible(1_000), + }; + + assert_eq!( + apply_policy::( + &parent_asset, + &parent_multilocation, + None, + ::ReserveDefaultTrustPolicy::get(), + ), + false + ); + } + + #[test] + fn policy_all_allows_any() { + let default_policy = DefaultTrustPolicy::Never; + + let parent_multilocation = MultiLocation::parent(); + let grandparent_asset = MultiAsset { + id: AssetId::Concrete(MultiLocation::grandparent()), + fun: Fungible(1_000), + }; + + let origin_policy = Some(TrustPolicy::DefaultTrustPolicy(DefaultTrustPolicy::All)); + + assert!(apply_policy::( + &grandparent_asset, + &parent_multilocation, + origin_policy, + default_policy + )); + } + + #[test] + fn policy_all_native_allows_native_asset() { + let default_policy = DefaultTrustPolicy::Never; + + let parent_multilocation = MultiLocation::parent(); + let parent_asset = MultiAsset { + id: AssetId::Concrete(MultiLocation::parent()), + fun: Fungible(1_000), + }; + + let origin_policy = Some(TrustPolicy::DefaultTrustPolicy( + DefaultTrustPolicy::AllNative, + )); + + assert!(apply_policy::( + &parent_asset, + &parent_multilocation, + origin_policy, + default_policy + )); + } + + #[test] + fn policy_all_native_rejects_non_native_asset() { + let default_policy = DefaultTrustPolicy::Never; + + let parent_multilocation = MultiLocation::parent(); + let grandparent_asset = MultiAsset { + id: AssetId::Concrete(MultiLocation::grandparent()), + fun: Fungible(1_000), + }; + + let origin_policy = Some(TrustPolicy::DefaultTrustPolicy( + DefaultTrustPolicy::AllNative, + )); + + assert_eq!( + apply_policy::( + &grandparent_asset, + &parent_multilocation, + origin_policy, + default_policy + ), + false + ); + } + + #[test] + fn policy_custom_allows_allowed_asset() { + let default_policy = DefaultTrustPolicy::Never; + + let parent_multilocation = MultiLocation::parent(); + let grandparent_asset = MultiAsset { + id: AssetId::Concrete(MultiLocation::grandparent()), + fun: Fungible(1_000), + }; + + // Only allow grandparent_asset + let origin_policy = Some(TrustPolicy::AllowedAssets( + BoundedVec::try_from(vec![grandparent_asset.id]).unwrap(), + )); + + assert!(apply_policy::( + &grandparent_asset, + &parent_multilocation, + origin_policy, + default_policy + )); + } + + #[test] + fn policy_custom_reject_not_allowed_asset() { + let default_policy = DefaultTrustPolicy::Never; + + let parent_multilocation = MultiLocation::parent(); + let parent_asset = MultiAsset { + id: AssetId::Concrete(MultiLocation::parent()), + fun: Fungible(1_000), + }; + let grandparent_asset = MultiAsset { + id: AssetId::Concrete(MultiLocation::grandparent()), + fun: Fungible(1_000), + }; + + // Only allow grandparent_asset + let origin_policy = Some(TrustPolicy::AllowedAssets( + BoundedVec::try_from(vec![grandparent_asset.id]).unwrap(), + )); + + // parent_asset should be rejected + assert_eq!( + apply_policy::( + &parent_asset, + &parent_multilocation, + origin_policy, + default_policy + ), + false + ); + } +} diff --git a/pallets/xcm-executor-utils/src/lib.rs b/pallets/xcm-executor-utils/src/lib.rs new file mode 100644 index 0000000..bccffb1 --- /dev/null +++ b/pallets/xcm-executor-utils/src/lib.rs @@ -0,0 +1,252 @@ +// Copyright (C) Moondance Labs Ltd. +// This file is part of Tanssi. + +// Tanssi 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. + +// Tanssi 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 Tanssi. If not, see . + +//! # XCM Executor Utils Pallet +//! +//! This is a utility pallet to help set the runtime parameters of XcmExecutor. +//! Currently it offers an intuitive, on-chain maanger to set trust policies on +//! incoming assets though `IsReserveFilter` and `IsTeleporterFilter`. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; +pub mod weights; + +#[cfg(any(test, feature = "runtime-benchmarks"))] +mod benchmarks; + +pub mod filters; + +pub use pallet::*; + +use { + crate::weights::WeightInfo, + frame_support::{pallet_prelude::*, DefaultNoBound}, + frame_system::pallet_prelude::*, + serde::{Deserialize, Serialize}, + staging_xcm::latest::{AssetId, MultiLocation}, +}; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use sp_runtime::BoundedVec; + use sp_std::vec::Vec; + + // Default trust policies for incoming assets + #[derive( + PartialEq, + Eq, + Clone, + Encode, + Decode, + RuntimeDebug, + TypeInfo, + MaxEncodedLen, + Deserialize, + Serialize, + )] + pub enum DefaultTrustPolicy { + // Allow all incoming assets + All, + // Only allow assets native of the origin + AllNative, + // Do not allow any assets + Never, + } + + #[derive( + DebugNoBound, + CloneNoBound, + EqNoBound, + PartialEqNoBound, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, + Deserialize, + Serialize, + )] + #[serde(bound = "")] + #[scale_info(skip_type_params(MaxAssets))] + pub enum TrustPolicy> { + DefaultTrustPolicy(DefaultTrustPolicy), + AllowedAssets(BoundedVec), + } + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + // Maximum number of allowed assets per origin on AllowedAssets policies + type TrustPolicyMaxAssets: Get; + + type ReserveDefaultTrustPolicy: Get; + + type SetReserveTrustOrigin: EnsureOrigin; + + type TeleportDefaultTrustPolicy: Get; + + type SetTeleportTrustOrigin: EnsureOrigin; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + NotValidOrigin, + } + + #[pallet::storage] + #[pallet::getter(fn reserve_policy)] + pub(super) type ReservePolicy = StorageMap< + _, + Blake2_128Concat, + MultiLocation, + TrustPolicy, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn teleport_policy)] + pub(super) type TeleportPolicy = StorageMap< + _, + Blake2_128Concat, + MultiLocation, + TrustPolicy, + OptionQuery, + >; + + #[pallet::genesis_config] + #[derive(DefaultNoBound)] + pub struct GenesisConfig { + pub reserve_policies: Vec<(MultiLocation, TrustPolicy)>, + pub teleport_policies: Vec<(MultiLocation, TrustPolicy)>, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + assert!( + self.reserve_policies.len() < T::TrustPolicyMaxAssets::get() as usize, + "Reserve policies should be less than FilterPolicyMaxAssets" + ); + + assert!( + self.teleport_policies.len() < T::TrustPolicyMaxAssets::get() as usize, + "Teleport policies should be less than FilterPolicyMaxAssets" + ); + + for (origin, policy) in self.reserve_policies.iter() { + ReservePolicy::::insert(origin, policy); + } + + for (origin, policy) in self.teleport_policies.iter() { + TeleportPolicy::::insert(origin, policy); + } + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + ReservePolicySet { origin: MultiLocation }, + ReservePolicyRemoved { origin: MultiLocation }, + TeleportPolicySet { origin: MultiLocation }, + TeleportPolicyRemoved { origin: MultiLocation }, + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight((T::WeightInfo::set_reserve_policy(), DispatchClass::Mandatory))] + pub fn set_reserve_policy( + origin: OriginFor, + origin_multilocation: MultiLocation, + policy: TrustPolicy, + ) -> DispatchResult { + T::SetReserveTrustOrigin::ensure_origin(origin)?; + + ReservePolicy::::insert(origin_multilocation, policy); + + Self::deposit_event(Event::ReservePolicySet { + origin: origin_multilocation, + }); + + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight((T::WeightInfo::remove_reserve_policy(), DispatchClass::Mandatory))] + pub fn remove_reserve_policy( + origin: OriginFor, + origin_multilocation: MultiLocation, + ) -> DispatchResult { + T::SetReserveTrustOrigin::ensure_origin(origin)?; + + ReservePolicy::::take(origin_multilocation).ok_or(Error::::NotValidOrigin)?; + + Self::deposit_event(Event::ReservePolicyRemoved { + origin: origin_multilocation, + }); + + Ok(()) + } + + #[pallet::call_index(2)] + #[pallet::weight((T::WeightInfo::set_teleport_policy(), DispatchClass::Mandatory))] + pub fn set_teleport_policy( + origin: OriginFor, + origin_multilocation: MultiLocation, + policy: TrustPolicy, + ) -> DispatchResult { + T::SetTeleportTrustOrigin::ensure_origin(origin)?; + + TeleportPolicy::::insert(origin_multilocation, policy); + + Self::deposit_event(Event::TeleportPolicySet { + origin: origin_multilocation, + }); + + Ok(()) + } + + #[pallet::call_index(3)] + #[pallet::weight((T::WeightInfo::remove_teleport_policy(), DispatchClass::Mandatory))] + pub fn remove_teleport_policy( + origin: OriginFor, + origin_multilocation: MultiLocation, + ) -> DispatchResult { + T::SetTeleportTrustOrigin::ensure_origin(origin)?; + + TeleportPolicy::::take(origin_multilocation).ok_or(Error::::NotValidOrigin)?; + + Self::deposit_event(Event::TeleportPolicyRemoved { + origin: origin_multilocation, + }); + + Ok(()) + } + } +} diff --git a/pallets/xcm-executor-utils/src/mock.rs b/pallets/xcm-executor-utils/src/mock.rs new file mode 100644 index 0000000..4a1756b --- /dev/null +++ b/pallets/xcm-executor-utils/src/mock.rs @@ -0,0 +1,205 @@ +// Copyright (C) Moondance Labs Ltd. +// This file is part of Tanssi. + +// Tanssi 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. + +// Tanssi 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 Tanssi. If not, see + +use { + crate::{self as pallet_xcm_executor_utils, DefaultTrustPolicy}, + frame_support::{ + construct_runtime, parameter_types, + traits::{ConstU16, ConstU64}, + }, + frame_system::{self as system, EnsureRoot}, + sp_core::H256, + sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, + }, +}; + +pub mod mock_never { + use super::*; + + type Block = frame_system::mocking::MockBlock; + + // Configure a mock runtime to test the pallet. + construct_runtime!( + pub enum TestNever + { + System: frame_system, + XcmExecutorUtils: pallet_xcm_executor_utils, + } + ); + + impl system::Config for TestNever { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Block = Block; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + } + + parameter_types! { + pub const MaxAssetsMock: u32 = 100u32; + pub const DefaultPolicyNever: DefaultTrustPolicy = DefaultTrustPolicy::Never; + } + + impl pallet_xcm_executor_utils::Config for TestNever { + type RuntimeEvent = RuntimeEvent; + type SetReserveTrustOrigin = EnsureRoot; + type SetTeleportTrustOrigin = EnsureRoot; + type ReserveDefaultTrustPolicy = DefaultPolicyNever; + type TeleportDefaultTrustPolicy = DefaultPolicyNever; + type TrustPolicyMaxAssets = MaxAssetsMock; + type WeightInfo = (); + } + + // Build genesis storage according to the mock runtime. + pub fn new_test_ext() -> sp_io::TestExternalities { + system::GenesisConfig::::default() + .build_storage() + .unwrap() + .into() + } +} + +pub mod mock_all { + use super::*; + + type Block = frame_system::mocking::MockBlock; + + // Configure a mock runtime to test the pallet. + construct_runtime!( + pub enum TestAll + { + System: frame_system, + XcmExecutorUtils: pallet_xcm_executor_utils, + } + ); + + impl system::Config for TestAll { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Block = Block; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + } + + parameter_types! { + pub const MaxAssetsMock: u32 = 100u32; + pub const DefaultPolicyAll: DefaultTrustPolicy = DefaultTrustPolicy::All; + } + + impl pallet_xcm_executor_utils::Config for TestAll { + type RuntimeEvent = RuntimeEvent; + type SetReserveTrustOrigin = EnsureRoot; + type SetTeleportTrustOrigin = EnsureRoot; + type ReserveDefaultTrustPolicy = DefaultPolicyAll; + type TeleportDefaultTrustPolicy = DefaultPolicyAll; + type TrustPolicyMaxAssets = MaxAssetsMock; + type WeightInfo = (); + } +} + +pub mod mock_all_native { + use super::*; + + type Block = frame_system::mocking::MockBlock; + + // Configure a mock runtime to test the pallet. + construct_runtime!( + pub enum TestAllNative + { + System: frame_system, + XcmExecutorUtils: pallet_xcm_executor_utils, + } + ); + + impl system::Config for TestAllNative { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Block = Block; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + } + + parameter_types! { + pub const MaxAssetsMock: u32 = 100u32; + pub const DefaultPolicyAllNative: DefaultTrustPolicy = DefaultTrustPolicy::AllNative; + } + + impl pallet_xcm_executor_utils::Config for TestAllNative { + type RuntimeEvent = RuntimeEvent; + type SetReserveTrustOrigin = EnsureRoot; + type SetTeleportTrustOrigin = EnsureRoot; + type ReserveDefaultTrustPolicy = DefaultPolicyAllNative; + type TeleportDefaultTrustPolicy = DefaultPolicyAllNative; + type TrustPolicyMaxAssets = MaxAssetsMock; + type WeightInfo = (); + } +} diff --git a/pallets/xcm-executor-utils/src/tests.rs b/pallets/xcm-executor-utils/src/tests.rs new file mode 100644 index 0000000..569e1b7 --- /dev/null +++ b/pallets/xcm-executor-utils/src/tests.rs @@ -0,0 +1,145 @@ +// Copyright (C) Moondance Labs Ltd. +// This file is part of Tanssi. + +// Tanssi 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. + +// Tanssi 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 Tanssi. If not, see + +use { + super::*, + crate::{ + filters::{IsReserveFilter, IsTeleportFilter}, + mock::mock_never::{new_test_ext, RuntimeOrigin, TestNever, XcmExecutorUtils}, + }, + frame_support::traits::ContainsPair, + staging_xcm::latest::{Fungibility::Fungible, MultiAsset}, +}; + +#[test] +fn reserve_policy_can_be_set_and_removed() { + new_test_ext().execute_with(|| { + let origin_multilocation = MultiLocation::parent(); + let trust_policy = TrustPolicy::DefaultTrustPolicy(DefaultTrustPolicy::Never); + + let _ = XcmExecutorUtils::set_reserve_policy( + RuntimeOrigin::root(), + origin_multilocation, + trust_policy.clone(), + ); + + assert_eq!( + XcmExecutorUtils::reserve_policy(origin_multilocation), + Some(trust_policy) + ); + + let _ = + XcmExecutorUtils::remove_reserve_policy(RuntimeOrigin::root(), origin_multilocation); + + assert!(XcmExecutorUtils::reserve_policy(origin_multilocation).is_none()); + }); +} + +#[test] +fn teleport_policy_can_be_set_and_removed() { + new_test_ext().execute_with(|| { + let origin_multilocation = MultiLocation::parent(); + let trust_policy = TrustPolicy::DefaultTrustPolicy(DefaultTrustPolicy::Never); + + let _ = XcmExecutorUtils::set_teleport_policy( + RuntimeOrigin::root(), + origin_multilocation, + trust_policy.clone(), + ); + + assert_eq!( + XcmExecutorUtils::teleport_policy(origin_multilocation), + Some(trust_policy) + ); + + let _ = + XcmExecutorUtils::remove_teleport_policy(RuntimeOrigin::root(), origin_multilocation); + + assert!(XcmExecutorUtils::teleport_policy(origin_multilocation).is_none()); + }); +} + +#[test] +fn reserve_policy_is_applied() { + new_test_ext().execute_with(|| { + let parent_multilocation = MultiLocation::parent(); + + let parent_asset = MultiAsset { + id: AssetId::Concrete(MultiLocation::parent()), + fun: Fungible(1_000), + }; + + let grandparent_asset = MultiAsset { + id: AssetId::Concrete(MultiLocation::grandparent()), + fun: Fungible(1_000), + }; + + // Only allow grandparent_asset + let _ = XcmExecutorUtils::set_reserve_policy( + RuntimeOrigin::root(), + parent_multilocation, + TrustPolicy::AllowedAssets(BoundedVec::try_from(vec![grandparent_asset.id]).unwrap()), + ); + + // Should allow grandparent_asset + assert!(filters::IsReserveFilter::::contains( + &grandparent_asset, + &parent_multilocation + )); + + // Should reject parent_asset + assert_eq!( + IsReserveFilter::::contains(&parent_asset, &parent_multilocation), + false + ); + }); +} + +#[test] +fn teleport_policy_is_applied() { + new_test_ext().execute_with(|| { + let parent_multilocation = MultiLocation::parent(); + + let parent_asset = MultiAsset { + id: AssetId::Concrete(MultiLocation::parent()), + fun: Fungible(1_000), + }; + + let grandparent_asset = MultiAsset { + id: AssetId::Concrete(MultiLocation::grandparent()), + fun: Fungible(1_000), + }; + + // Only allow grandparent_asset + let _ = XcmExecutorUtils::set_teleport_policy( + RuntimeOrigin::root(), + parent_multilocation, + TrustPolicy::AllowedAssets(BoundedVec::try_from(vec![grandparent_asset.id]).unwrap()), + ); + + // Should allow grandparent_asset + assert!(IsTeleportFilter::::contains( + &grandparent_asset, + &parent_multilocation + ),); + + // Should reject parent_asset + assert_eq!( + IsTeleportFilter::::contains(&parent_asset, &parent_multilocation), + false + ); + }); +} diff --git a/pallets/xcm-executor-utils/src/weights.rs b/pallets/xcm-executor-utils/src/weights.rs new file mode 100644 index 0000000..1645e1c --- /dev/null +++ b/pallets/xcm-executor-utils/src/weights.rs @@ -0,0 +1,152 @@ +// Copyright (C) Moondance Labs Ltd. +// This file is part of Tanssi. + +// Tanssi 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. + +// Tanssi 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 Tanssi. If not, see + + +//! Autogenerated weights for pallet_xcm_executor_utils +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2024-01-24, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `LAPTOP-6JV2D4GI`, CPU: `13th Gen Intel(R) Core(TM) i9-13950HX` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 + +// Executed Command: +// ./target/release/tanssi-node +// benchmark +// pallet +// --execution=wasm +// --wasm-execution=compiled +// --pallet +// pallet_xcm_executor_utils +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template=./benchmarking/frame-weight-template.hbs +// --json-file +// raw.json +// --output +// weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_xcm_executor_utils. +pub trait WeightInfo { + fn set_reserve_policy() -> Weight; + fn remove_reserve_policy() -> Weight; + fn set_teleport_policy() -> Weight; + fn remove_teleport_policy() -> Weight; +} + +/// Weights for pallet_xcm_executor_utils using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `XcmExecutorUtils::ReservePolicy` (r:0 w:1) + /// Proof: `XcmExecutorUtils::ReservePolicy` (`max_values`: None, `max_size`: Some(1826), added: 4301, mode: `MaxEncodedLen`) + fn set_reserve_policy() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 4_583_000 picoseconds. + Weight::from_parts(4_951_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `XcmExecutorUtils::ReservePolicy` (r:1 w:1) + /// Proof: `XcmExecutorUtils::ReservePolicy` (`max_values`: None, `max_size`: Some(1826), added: 4301, mode: `MaxEncodedLen`) + fn remove_reserve_policy() -> Weight { + // Proof Size summary in bytes: + // Measured: `87` + // Estimated: `5291` + // Minimum execution time: 7_133_000 picoseconds. + Weight::from_parts(7_677_000, 5291) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `XcmExecutorUtils::TeleportPolicy` (r:0 w:1) + /// Proof: `XcmExecutorUtils::TeleportPolicy` (`max_values`: None, `max_size`: Some(1826), added: 4301, mode: `MaxEncodedLen`) + fn set_teleport_policy() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 4_767_000 picoseconds. + Weight::from_parts(4_963_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `XcmExecutorUtils::TeleportPolicy` (r:1 w:1) + /// Proof: `XcmExecutorUtils::TeleportPolicy` (`max_values`: None, `max_size`: Some(1826), added: 4301, mode: `MaxEncodedLen`) + fn remove_teleport_policy() -> Weight { + // Proof Size summary in bytes: + // Measured: `87` + // Estimated: `5291` + // Minimum execution time: 7_995_000 picoseconds. + Weight::from_parts(8_496_000, 5291) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: `XcmExecutorUtils::ReservePolicy` (r:0 w:1) + /// Proof: `XcmExecutorUtils::ReservePolicy` (`max_values`: None, `max_size`: Some(1826), added: 4301, mode: `MaxEncodedLen`) + fn set_reserve_policy() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 4_583_000 picoseconds. + Weight::from_parts(4_951_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `XcmExecutorUtils::ReservePolicy` (r:1 w:1) + /// Proof: `XcmExecutorUtils::ReservePolicy` (`max_values`: None, `max_size`: Some(1826), added: 4301, mode: `MaxEncodedLen`) + fn remove_reserve_policy() -> Weight { + // Proof Size summary in bytes: + // Measured: `87` + // Estimated: `5291` + // Minimum execution time: 7_133_000 picoseconds. + Weight::from_parts(7_677_000, 5291) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `XcmExecutorUtils::TeleportPolicy` (r:0 w:1) + /// Proof: `XcmExecutorUtils::TeleportPolicy` (`max_values`: None, `max_size`: Some(1826), added: 4301, mode: `MaxEncodedLen`) + fn set_teleport_policy() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 4_767_000 picoseconds. + Weight::from_parts(4_963_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `XcmExecutorUtils::TeleportPolicy` (r:1 w:1) + /// Proof: `XcmExecutorUtils::TeleportPolicy` (`max_values`: None, `max_size`: Some(1826), added: 4301, mode: `MaxEncodedLen`) + fn remove_teleport_policy() -> Weight { + // Proof Size summary in bytes: + // Measured: `87` + // Estimated: `5291` + // Minimum execution time: 7_995_000 picoseconds. + Weight::from_parts(8_496_000, 5291) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } +}