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))
+ }
+}