assets-common 0.27.0

Assets common utilities
Documentation
// 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 frame_support::{
	pallet_prelude::Get,
	traits::{tokens::ProvideAssetReserves, Contains, ContainsPair},
};
use xcm::prelude::*;
use xcm_builder::ensure_is_remote;

frame_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<IsForeign>(core::marker::PhantomData<IsForeign>);
impl<IsForeign: ContainsPair<Location, Location>> ContainsPair<Asset, Location>
	for IsForeignConcreteAsset<IsForeign>
{
	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 FromSiblingParachain<SelfParaId, L = Location>(
	core::marker::PhantomData<(SelfParaId, L)>,
);
impl<SelfParaId: Get<ParaId>, L: TryFrom<Location> + TryInto<Location> + Clone + Debug>
	ContainsPair<L, L> for FromSiblingParachain<SelfParaId, L>
{
	fn contains(a: &L, b: &L) -> bool {
		tracing::trace!(target: "xcm:contains", ?a, ?b, "FromSiblingParachain");
		// 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(Parachain(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<SelfParaId, ReserveProvider, L = Location>(
	core::marker::PhantomData<(SelfParaId, ReserveProvider, L)>,
);
impl<
		SelfParaId: Get<ParaId>,
		L: TryFrom<Location> + TryInto<Location> + Clone + Debug,
		ReserveProvider: ProvideAssetReserves<Location, ForeignAssetReserveData>,
	> ContainsPair<L, L> for TeleportableAssetWithTrustedReserve<SelfParaId, ReserveProvider, L>
{
	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<SelfParaId, ReserveProvider, L = Location>(
	core::marker::PhantomData<(SelfParaId, ReserveProvider, L)>,
);
impl<
		SelfParaId: Get<ParaId>,
		L: TryFrom<Location> + TryInto<Location> + Clone + Debug,
		ReserveProvider: ProvideAssetReserves<Location, ForeignAssetReserveData>,
	> ContainsPair<L, L> for NonTeleportableAssetFromTrustedReserve<SelfParaId, ReserveProvider, L>
{
	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<UniversalLocation, ExpectedNetworkId, L = Location>(
	core::marker::PhantomData<(UniversalLocation, ExpectedNetworkId, L)>,
);
impl<
		UniversalLocation: Get<InteriorLocation>,
		ExpectedNetworkId: Get<NetworkId>,
		L: TryFrom<Location> + TryInto<Location> + Clone + Debug,
	> ContainsPair<L, L> for FromNetwork<UniversalLocation, ExpectedNetworkId, L>
{
	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<AssetsAllowedNetworks, OriginLocation>(
	core::marker::PhantomData<(AssetsAllowedNetworks, OriginLocation)>,
);
impl<
		L: TryInto<Location> + Clone,
		AssetsAllowedNetworks: Contains<Location>,
		OriginLocation: Get<Location>,
	> ContainsPair<L, L> for RemoteAssetFromLocation<AssetsAllowedNetworks, OriginLocation>
{
	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<AssetsAllowedNetworks: Contains<Location>, OriginLocation: Get<Location>>
	ContainsPair<Asset, Location> for RemoteAssetFromLocation<AssetsAllowedNetworks, OriginLocation>
{
	fn contains(asset: &Asset, origin: &Location) -> bool {
		tracing::trace!(target: "xcm:contains", ?asset, ?origin, "RemoteAssetFromLocation");
		<Self as ContainsPair<Location, Location>>::contains(&asset.id.0, origin)
	}
}

#[cfg(test)]
mod tests {
	use super::*;
	use frame_support::parameter_types;
	use xcm::latest::{ROCOCO_GENESIS_HASH, WESTEND_GENESIS_HASH};

	parameter_types! {
		pub UniversalLocation: InteriorLocation = [GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH)), Parachain(1000)].into();
		pub ExpectedNetworkId: NetworkId = ByGenesis(WESTEND_GENESIS_HASH);
	}

	#[test]
	fn from_network_contains_works() {
		// asset and origin from foreign consensus works
		let asset: Location = (
			Parent,
			Parent,
			GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
			Parachain(1000),
			PalletInstance(1),
			GeneralIndex(1),
		)
			.into();
		let origin: Location =
			(Parent, Parent, GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1000))
				.into();
		assert!(FromNetwork::<UniversalLocation, ExpectedNetworkId>::contains(&asset, &origin));

		// asset and origin from local consensus fails
		let asset: Location = (
			Parent,
			Parent,
			GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH)),
			Parachain(1000),
			PalletInstance(1),
			GeneralIndex(1),
		)
			.into();
		let origin: Location =
			(Parent, Parent, GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH)), Parachain(1000))
				.into();
		assert!(!FromNetwork::<UniversalLocation, ExpectedNetworkId>::contains(&asset, &origin));

		// asset and origin from here fails
		let asset: Location = (PalletInstance(1), GeneralIndex(1)).into();
		let origin: Location = Here.into();
		assert!(!FromNetwork::<UniversalLocation, ExpectedNetworkId>::contains(&asset, &origin));

		// asset from different consensus fails
		let asset: Location = (
			Parent,
			Parent,
			GlobalConsensus(Polkadot),
			Parachain(1000),
			PalletInstance(1),
			GeneralIndex(1),
		)
			.into();
		let origin: Location =
			(Parent, Parent, GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1000))
				.into();
		assert!(!FromNetwork::<UniversalLocation, ExpectedNetworkId>::contains(&asset, &origin));

		// origin from different consensus fails
		let asset: Location = (
			Parent,
			Parent,
			GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
			Parachain(1000),
			PalletInstance(1),
			GeneralIndex(1),
		)
			.into();
		let origin: Location = (Parent, Parent, GlobalConsensus(Polkadot), Parachain(1000)).into();
		assert!(!FromNetwork::<UniversalLocation, ExpectedNetworkId>::contains(&asset, &origin));

		// asset and origin from unexpected consensus fails
		let asset: Location = (
			Parent,
			Parent,
			GlobalConsensus(Polkadot),
			Parachain(1000),
			PalletInstance(1),
			GeneralIndex(1),
		)
			.into();
		let origin: Location = (Parent, Parent, GlobalConsensus(Polkadot), Parachain(1000)).into();
		assert!(!FromNetwork::<UniversalLocation, ExpectedNetworkId>::contains(&asset, &origin));
	}
}