referrerpolicy=no-referrer-when-downgrade

pallet_xcm/
transfer_assets_validation.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Validation of the `transfer_assets` call.
18//! This validation is a temporary patch in preparation for the Asset Hub Migration (AHM).
19//! This module will be removed after the migration and the determined
20//! reserve location will be adjusted accordingly to be Asset Hub.
21//! For more information, see <https://github.com/paritytech/polkadot-sdk/issues/9054>.
22
23use crate::{Config, Error, Pallet};
24use alloc::vec::Vec;
25use hex_literal::hex;
26use sp_core::Get;
27use xcm::prelude::*;
28use xcm_executor::traits::TransferType;
29
30/// The genesis hash of the Paseo Relay Chain. Used to identify it.
31const PASEO_GENESIS_HASH: [u8; 32] =
32	hex!["77afd6190f1554ad45fd0d31aee62aacc33c6db0ea801129acb813f913e0764f"];
33
34impl<T: Config> Pallet<T> {
35	/// Check if network native asset reserve transfers should be blocked during Asset Hub
36	/// Migration.
37	///
38	/// During the Asset Hub Migration (AHM), the native network asset's reserve will move
39	/// from the Relay Chain to Asset Hub. The `transfer_assets` function automatically determines
40	/// reserves based on asset ID location, which would incorrectly assume Relay Chain as the
41	/// reserve.
42	///
43	/// This function blocks native network asset reserve transfers to prevent issues during
44	/// the migration.
45	/// Users should use `limited_reserve_transfer_assets`, `transfer_assets_using_type_and_then` or
46	/// `execute` instead, which allows explicit reserve specification.
47	pub(crate) fn ensure_network_asset_reserve_transfer_allowed(
48		assets: &Vec<Asset>,
49		fee_asset_index: usize,
50		assets_transfer_type: &TransferType,
51		fees_transfer_type: &TransferType,
52	) -> Result<(), Error<T>> {
53		// Extract fee asset and check both assets and fees separately.
54		let mut remaining_assets = assets.clone();
55		if fee_asset_index >= remaining_assets.len() {
56			return Err(Error::<T>::Empty);
57		}
58		let fee_asset = remaining_assets.remove(fee_asset_index);
59
60		// Check remaining assets with their transfer type.
61		Self::ensure_one_transfer_type_allowed(&remaining_assets, &assets_transfer_type)?;
62
63		// Check fee asset with its transfer type.
64		Self::ensure_one_transfer_type_allowed(&[fee_asset], &fees_transfer_type)?;
65
66		Ok(())
67	}
68
69	/// Checks that the transfer of `assets` is allowed.
70	///
71	/// Returns an error if `transfer_type` is a reserve transfer and the network's native asset is
72	/// being transferred. Allows the transfer otherwise.
73	fn ensure_one_transfer_type_allowed(
74		assets: &[Asset],
75		transfer_type: &TransferType,
76	) -> Result<(), Error<T>> {
77		// Check if any reserve transfer (LocalReserve, DestinationReserve, or RemoteReserve)
78		// is being attempted.
79		let is_reserve_transfer = matches!(
80			transfer_type,
81			TransferType::LocalReserve |
82				TransferType::DestinationReserve |
83				TransferType::RemoteReserve(_)
84		);
85
86		if !is_reserve_transfer {
87			// If not a reserve transfer (e.g., teleport), allow it.
88			return Ok(());
89		}
90
91		// Check if any asset is a network native asset.
92		for asset in assets {
93			if Self::is_network_native_asset(&asset.id) {
94				tracing::debug!(
95					target: "xcm::pallet_xcm::transfer_assets",
96					asset_id = ?asset.id, ?transfer_type,
97					"Network native asset reserve transfer blocked in preparation for the Asset Hub Migration. Use `transfer_assets_using_type_and_then` instead and explicitly mention the reserve."
98				);
99				// It's error-prone to try to determine the reserve in this circumstances.
100				return Err(Error::<T>::InvalidAssetUnknownReserve);
101			}
102		}
103
104		Ok(())
105	}
106
107	/// Check if the given asset ID represents a network native asset based on our
108	/// UniversalLocation.
109	///
110	/// Returns true if the asset is a native network asset (DOT, KSM, WND, PAS) that should be
111	/// blocked during Asset Hub Migration.
112	fn is_network_native_asset(asset_id: &AssetId) -> bool {
113		let universal_location = T::UniversalLocation::get();
114		let asset_location = &asset_id.0;
115
116		match universal_location.len() {
117			// Case 1: We are on the Relay Chain itself.
118			// UniversalLocation: GlobalConsensus(Network).
119			// Network asset ID: Here.
120			1 => {
121				if let Some(Junction::GlobalConsensus(network)) = universal_location.first() {
122					let is_target_network = match network {
123						NetworkId::Polkadot | NetworkId::Kusama => true,
124						NetworkId::ByGenesis(genesis_hash) => {
125							// Check if this is Westend by genesis hash
126							*genesis_hash == xcm::v5::WESTEND_GENESIS_HASH ||
127								*genesis_hash == PASEO_GENESIS_HASH ||
128								*genesis_hash == xcm::v5::ROCOCO_GENESIS_HASH // Used in tests.
129						},
130						_ => false,
131					};
132					is_target_network && asset_location.is_here()
133				} else {
134					false
135				}
136			},
137			// Case 2: We are on a parachain within one of the specified networks.
138			// UniversalLocation: GlobalConsensus(Network)/Parachain(id).
139			// Network asset ID: Parent.
140			2 => {
141				if let (Some(Junction::GlobalConsensus(network)), Some(Junction::Parachain(_))) =
142					(universal_location.first(), universal_location.last())
143				{
144					let is_target_network = match network {
145						NetworkId::Polkadot | NetworkId::Kusama => true,
146						NetworkId::ByGenesis(genesis_hash) => {
147							// Check if this is Westend by genesis hash
148							*genesis_hash == xcm::v5::WESTEND_GENESIS_HASH ||
149								*genesis_hash == PASEO_GENESIS_HASH ||
150								*genesis_hash == xcm::v5::ROCOCO_GENESIS_HASH // Used in tests.
151						},
152						_ => false,
153					};
154					is_target_network && *asset_location == Location::parent()
155				} else {
156					false
157				}
158			},
159			// Case 3: We are not on a relay or parachain. We return false.
160			_ => false,
161		}
162	}
163}