// Copyright (C) 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. use crate::local_and_foreign_assets::ForeignAssetReserveData; use core::fmt::Debug; use cumulus_primitives_core::ParaId; use pezframe_support::{ pezpallet_prelude::Get, traits::{tokens::ProvideAssetReserves, Contains, ContainsPair}, }; use xcm::prelude::*; use xcm_builder::ensure_is_remote; pezframe_support::parameter_types! { pub LocalLocationPattern: Location = Location::new(0, Here); pub ParentLocation: Location = Location::parent(); } /// Accepts an asset if it is from the origin. pub struct IsForeignConcreteAsset(core::marker::PhantomData); impl> ContainsPair for IsForeignConcreteAsset { fn contains(asset: &Asset, origin: &Location) -> bool { let result = matches!(asset.id, AssetId(ref id) if IsForeign::contains(id, origin)); tracing::trace!(target: "xcm::contains", ?asset, ?origin, ?result, "IsForeignConcreteAsset"); result } } /// Checks if `a` is from sibling location `b`. Checks that `Location-a` starts with /// `Location-b`, and that the `ParaId` of `b` is not equal to `a`. pub struct FromSiblingTeyrchain( core::marker::PhantomData<(SelfParaId, L)>, ); impl, L: TryFrom + TryInto + Clone + Debug> ContainsPair for FromSiblingTeyrchain { fn contains(a: &L, b: &L) -> bool { tracing::trace!(target: "xcm:contains", ?a, ?b, "FromSiblingTeyrchain"); // We convert locations to latest let a = match ((*a).clone().try_into(), (*b).clone().try_into()) { (Ok(a), Ok(b)) if a.starts_with(&b) => a, // `a` needs to be from `b` at least _ => return false, }; // here we check if sibling match a.unpack() { (1, interior) => { matches!(interior.first(), Some(Teyrchain(sibling_para_id)) if sibling_para_id.ne(&u32::from(SelfParaId::get()))) }, _ => false, } } } /// Checks if asset `a` is coming from a trusted Reserve location `b`, then checks whether the local /// chain is also a reserve of `a`. Assets can be teleported between their reserve locations. pub struct TeleportableAssetWithTrustedReserve( core::marker::PhantomData<(SelfParaId, ReserveProvider, L)>, ); impl< SelfParaId: Get, L: TryFrom + TryInto + Clone + Debug, ReserveProvider: ProvideAssetReserves, > ContainsPair for TeleportableAssetWithTrustedReserve { fn contains(a: &L, b: &L) -> bool { tracing::trace!(target: "xcm::contains", ?a, ?b, "TeleportableAssetWithTrustedReserve"); // We convert locations to latest let (a, b) = match ((*a).clone().try_into(), (*b).clone().try_into()) { (Ok(a), Ok(b)) => (a, b), _ => return false, }; let reserves = ReserveProvider::reserves(&a); tracing::trace!(target: "xcm::contains", ?reserves, "TeleportableAssetWithTrustedReserve"); // check if `b` is reserve for `a` and teleportable flag is set let filter = (b, true).into(); reserves.contains(&filter) } } /// Checks if asset `a` is coming from a trusted Reserve location `b`. /// Then checks that the local chain is NOT itself also reserve of `a`, otherwise a teleport is in /// order. pub struct NonTeleportableAssetFromTrustedReserve( core::marker::PhantomData<(SelfParaId, ReserveProvider, L)>, ); impl< SelfParaId: Get, L: TryFrom + TryInto + Clone + Debug, ReserveProvider: ProvideAssetReserves, > ContainsPair for NonTeleportableAssetFromTrustedReserve { fn contains(a: &L, b: &L) -> bool { tracing::trace!(target: "xcm::contains", ?a, ?b, "NonTeleportableAssetFromTrustedReserve"); // We convert locations to latest let (a, b) = match ((*a).clone().try_into(), (*b).clone().try_into()) { (Ok(a), Ok(b)) => (a, b), _ => return false, }; let reserves = ReserveProvider::reserves(&a); tracing::trace!(target: "xcm::contains", ?reserves, "NonTeleportableAssetFromTrustedReserve"); // check if `b` is reserve for `a` and teleportable flag is NOT set let filter = (b, false).into(); reserves.contains(&filter) } } /// Checks if `a` is from the expected global consensus network. Checks that `Location-a` /// starts with `Location-b`, and that network is a foreign consensus system. pub struct FromNetwork( core::marker::PhantomData<(UniversalLocation, ExpectedNetworkId, L)>, ); impl< UniversalLocation: Get, ExpectedNetworkId: Get, L: TryFrom + TryInto + Clone + Debug, > ContainsPair for FromNetwork { fn contains(a: &L, b: &L) -> bool { tracing::trace!(target: "xcm:contains", ?a, ?b, "FromNetwork"); // We convert locations to latest let a = match ((*a).clone().try_into(), (*b).clone().try_into()) { (Ok(a), Ok(b)) if a.starts_with(&b) => a, // `a` needs to be from `b` at least _ => return false, }; let universal_source = UniversalLocation::get(); // ensure that `a` is remote and from the expected network match ensure_is_remote(universal_source.clone(), a.clone()) { Ok((network_id, _)) => network_id == ExpectedNetworkId::get(), Err(e) => { tracing::debug!(target: "xcm::contains", origin = ?a, ?universal_source, error = ?e, "FromNetwork origin is not remote to the universal_source"); false }, } } } /// Accept an asset if it is native to `AssetsAllowedNetworks` and it is coming from /// `OriginLocation`. pub struct RemoteAssetFromLocation( core::marker::PhantomData<(AssetsAllowedNetworks, OriginLocation)>, ); impl< L: TryInto + Clone, AssetsAllowedNetworks: Contains, OriginLocation: Get, > ContainsPair for RemoteAssetFromLocation { fn contains(asset: &L, origin: &L) -> bool { let Ok(asset) = asset.clone().try_into() else { return false; }; let Ok(origin) = origin.clone().try_into() else { return false; }; let expected_origin = OriginLocation::get(); // ensure `origin` is expected `OriginLocation` if !expected_origin.eq(&origin) { tracing::trace!( target: "xcm::contains", ?asset, ?origin, ?expected_origin, "RemoteAssetFromLocation: Asset is not from expected origin" ); return false; } else { tracing::trace!( target: "xcm::contains", ?asset, ?origin, "RemoteAssetFromLocation", ); } // ensure `asset` is from remote consensus listed in `AssetsAllowedNetworks` AssetsAllowedNetworks::contains(&asset) } } impl, OriginLocation: Get> ContainsPair for RemoteAssetFromLocation { fn contains(asset: &Asset, origin: &Location) -> bool { tracing::trace!(target: "xcm:contains", ?asset, ?origin, "RemoteAssetFromLocation"); >::contains(&asset.id.0, origin) } } #[cfg(test)] mod tests { use super::*; use pezframe_support::parameter_types; use xcm::latest::{PEZKUWICHAIN_GENESIS_HASH, ZAGROS_GENESIS_HASH}; parameter_types! { pub UniversalLocation: InteriorLocation = [GlobalConsensus(ByGenesis(PEZKUWICHAIN_GENESIS_HASH)), Teyrchain(1000)].into(); pub ExpectedNetworkId: NetworkId = ByGenesis(ZAGROS_GENESIS_HASH); } #[test] fn from_network_contains_works() { // asset and origin from foreign consensus works let asset: Location = ( Parent, Parent, GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)), Teyrchain(1000), PalletInstance(1), GeneralIndex(1), ) .into(); let origin: Location = (Parent, Parent, GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)), Teyrchain(1000)) .into(); assert!(FromNetwork::::contains(&asset, &origin)); // asset and origin from local consensus fails let asset: Location = ( Parent, Parent, GlobalConsensus(ByGenesis(PEZKUWICHAIN_GENESIS_HASH)), Teyrchain(1000), PalletInstance(1), GeneralIndex(1), ) .into(); let origin: Location = ( Parent, Parent, GlobalConsensus(ByGenesis(PEZKUWICHAIN_GENESIS_HASH)), Teyrchain(1000), ) .into(); assert!(!FromNetwork::::contains(&asset, &origin)); // asset and origin from here fails let asset: Location = (PalletInstance(1), GeneralIndex(1)).into(); let origin: Location = Here.into(); assert!(!FromNetwork::::contains(&asset, &origin)); // asset from different consensus fails let asset: Location = ( Parent, Parent, GlobalConsensus(Pezkuwi), Teyrchain(1000), PalletInstance(1), GeneralIndex(1), ) .into(); let origin: Location = (Parent, Parent, GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)), Teyrchain(1000)) .into(); assert!(!FromNetwork::::contains(&asset, &origin)); // origin from different consensus fails let asset: Location = ( Parent, Parent, GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)), Teyrchain(1000), PalletInstance(1), GeneralIndex(1), ) .into(); let origin: Location = (Parent, Parent, GlobalConsensus(Pezkuwi), Teyrchain(1000)).into(); assert!(!FromNetwork::::contains(&asset, &origin)); // asset and origin from unexpected consensus fails let asset: Location = ( Parent, Parent, GlobalConsensus(Pezkuwi), Teyrchain(1000), PalletInstance(1), GeneralIndex(1), ) .into(); let origin: Location = (Parent, Parent, GlobalConsensus(Pezkuwi), Teyrchain(1000)).into(); assert!(!FromNetwork::::contains(&asset, &origin)); } }