Skip to content
Merged
Changes from 3 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1a06690
fix: disable native network reserve transfers via transfer_assets
franciscoaguirre Jul 8, 2025
c359890
fix: reuse existing error to not make this a minor bump
franciscoaguirre Jul 9, 2025
467fdf4
fix: fmt
franciscoaguirre Jul 9, 2025
f1604da
Merge branch 'master' into transfer-assets-pre-ahm-patch
franciscoaguirre Jul 9, 2025
7d60f7d
fix: handle assets and fees independently
franciscoaguirre Jul 9, 2025
08b80fe
doc: add small comment about Paseo
franciscoaguirre Jul 9, 2025
3e26090
fix: remove external chain case
franciscoaguirre Jul 10, 2025
7c83445
fix: fmt
franciscoaguirre Jul 10, 2025
6740faa
fix: bidirectional transfer test
franciscoaguirre Jul 10, 2025
f33e148
test: add tests for blocking transfer_assets
franciscoaguirre Jul 10, 2025
4040d78
doc: small test comment changes
franciscoaguirre Jul 11, 2025
09675ad
chore: move patch to its own mod
franciscoaguirre Jul 11, 2025
f90ff12
chore: rename test file
franciscoaguirre Jul 11, 2025
768d4f6
doc: add prdoc
franciscoaguirre Jul 11, 2025
af28268
Merge branch 'master' into transfer-assets-pre-ahm-patch
franciscoaguirre Jul 11, 2025
05d0458
fix: fmt
franciscoaguirre Jul 11, 2025
d2472ed
chore: don't clone fee asset
franciscoaguirre Jul 11, 2025
8f97548
fix: taplo
franciscoaguirre Jul 11, 2025
ad195c6
fix: doc issues
franciscoaguirre Jul 11, 2025
19c4a8c
fix: xcm-docs test
franciscoaguirre Jul 14, 2025
c219704
Merge branch 'master' into transfer-assets-pre-ahm-patch
franciscoaguirre Jul 14, 2025
bef14a8
fix: use transfer_assets_using_type_and_then on rococo
franciscoaguirre Jul 14, 2025
c3e393c
fix: test in bridge-hub-westend-integration-tests
franciscoaguirre Jul 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions polkadot/xcm/pallet-xcm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,15 @@ pub mod pallet {
let (fees_transfer_type, assets_transfer_type) =
Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?;

// Check for network native asset reserve transfers in preparation for the Asset Hub
// Migration. This check will be removed after the migration and the determined
// reserve location adjusted accordingly. For more information, see https://github.com/paritytech/polkadot-sdk/issues/9054.
Self::ensure_network_asset_reserve_transfer_allowed(
&assets,
&assets_transfer_type,
&fees_transfer_type,
)?;

Self::do_transfer_assets(
origin,
dest,
Expand Down Expand Up @@ -2012,6 +2021,125 @@ impl<T: Config> Pallet<T> {
))
}

/// Check if the given asset ID represents a network native asset based on our
/// UniversalLocation.
///
/// Returns true if the asset is a native network asset (DOT, KSM, WND) that should be blocked
/// during Asset Hub Migration.
fn is_network_native_asset(asset_id: &AssetId) -> bool {
let universal_location = T::UniversalLocation::get();
let asset_location = &asset_id.0;

match universal_location.len() {
// Case 1: We are on the Relay Chain itself.
// UniversalLocation: GlobalConsensus(Network).
// Network asset ID: Here.
1 => {
if let Some(Junction::GlobalConsensus(network)) = universal_location.first() {
let is_target_network = match network {
NetworkId::Polkadot | NetworkId::Kusama => true,
NetworkId::ByGenesis(genesis_hash) => {
// Check if this is Westend by genesis hash
*genesis_hash == xcm::v5::WESTEND_GENESIS_HASH
},
_ => false,
};
is_target_network && asset_location.is_here()
} else {
false
}
},
// Case 2: We are on a parachain within one of the specified networks.
// UniversalLocation: GlobalConsensus(Network)/Parachain(id).
// Network asset ID: Parent.
2 => {
if let (Some(Junction::GlobalConsensus(network)), Some(Junction::Parachain(_))) =
(universal_location.first(), universal_location.last())
{
let is_target_network = match network {
NetworkId::Polkadot | NetworkId::Kusama => true,
NetworkId::ByGenesis(genesis_hash) => {
// Check if this is Westend by genesis hash
*genesis_hash == xcm::v5::WESTEND_GENESIS_HASH
},
_ => false,
};
is_target_network && *asset_location == Location::parent()
} else {
false
}
},
// Case 3: We are outside the specified networks (e.g., external chain).
// Check if the asset ID ends with GlobalConsensus(Polkadot|Kusama|Westend).
_ => {
if let Some(last_junction) = asset_location.interior.last() {
match last_junction {
Junction::GlobalConsensus(NetworkId::Polkadot) |
Junction::GlobalConsensus(NetworkId::Kusama) => true,
Junction::GlobalConsensus(NetworkId::ByGenesis(genesis_hash)) => {
// Check if this is Westend by genesis hash.
*genesis_hash == xcm::v5::WESTEND_GENESIS_HASH
},
_ => false,
}
} else {
false
}
},
}
}

/// Check if network native asset reserve transfers should be blocked during Asset Hub
/// Migration.
///
/// During the Asset Hub Migration (AHM), the native network asset's reserve will move
/// from the Relay Chain to Asset Hub. The `transfer_assets` function automatically determines
/// reserves based on asset ID location, which would incorrectly assume Relay Chain as the
/// reserve.
///
/// This function blocks native network asset reserve transfers to prevent issues during
/// the migration. Users should use `limited_reserve_transfer_assets` instead, which allows
/// explicit reserve specification.
fn ensure_network_asset_reserve_transfer_allowed(
assets: &[Asset],
assets_transfer_type: &TransferType,
fees_transfer_type: &TransferType,
) -> Result<(), Error<T>> {
// Check if any reserve transfer (LocalReserve, DestinationReserve, or RemoteReserve)
// is being attempted.
let is_reserve_transfer = matches!(
assets_transfer_type,
TransferType::LocalReserve |
TransferType::DestinationReserve |
TransferType::RemoteReserve(_)
) || matches!(
fees_transfer_type,
TransferType::LocalReserve |
TransferType::DestinationReserve |
TransferType::RemoteReserve(_)
);

if !is_reserve_transfer {
// If not a reserve transfer (e.g., teleport), allow it.
return Ok(());
}

// Check if any asset is a network native asset.
for asset in assets {
if Self::is_network_native_asset(&asset.id) {
tracing::debug!(
target: "xcm::pallet_xcm::transfer_assets",
asset_id = ?asset.id, ?assets_transfer_type, ?fees_transfer_type,
"Network native asset reserve transfer blocked during Asset Hub Migration. Use `limited_reserve_transfer_assets` instead."
);
// It's error-prone to try to determine the reserve in this circumstances.
return Err(Error::<T>::InvalidAssetUnknownReserve);
}
}

Ok(())
}

fn do_reserve_transfer_assets(
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
Expand Down
Loading