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}