diff --git a/bridges/modules/xcm-bridge-hub-router/src/lib.rs b/bridges/modules/xcm-bridge-hub-router/src/lib.rs index 5d0be41b1b55..c6008802ae9a 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/lib.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/lib.rs @@ -191,6 +191,10 @@ pub mod pallet { impl, I: 'static> Pallet { /// Called when new message is sent (queued to local outbound XCM queue) over the bridge. pub(crate) fn on_message_sent_to_bridge(message_size: u32) { + log::trace!( + target: LOG_TARGET, + "on_message_sent_to_bridge - message_size: {message_size:?}", + ); let _ = Bridge::::try_mutate(|bridge| { let is_channel_with_bridge_hub_congested = T::WithBridgeHubChannel::is_congested(); let is_bridge_congested = bridge.is_congested; @@ -238,14 +242,16 @@ impl, I: 'static> ExporterFor for Pallet { remote_location: &InteriorLocation, message: &Xcm<()>, ) -> Option<(Location, Option)> { + log::trace!( + target: LOG_TARGET, + "exporter_for - network: {network:?}, remote_location: {remote_location:?}, msg: {message:?}", + ); // ensure that the message is sent to the expected bridged network (if specified). if let Some(bridged_network) = T::BridgedNetworkId::get() { if *network != bridged_network { log::trace!( target: LOG_TARGET, - "Router with bridged_network_id {:?} does not support bridging to network {:?}!", - bridged_network, - network, + "Router with bridged_network_id {bridged_network:?} does not support bridging to network {network:?}!", ); return None } @@ -300,7 +306,7 @@ impl, I: 'static> ExporterFor for Pallet { log::info!( target: LOG_TARGET, - "Going to send message to {:?} ({} bytes) over bridge. Computed bridge fee {:?} using fee factor {}", + "Validate send message to {:?} ({} bytes) over bridge. Computed bridge fee {:?} using fee factor {}", (network, remote_location), message_size, fee, @@ -321,6 +327,7 @@ impl, I: 'static> SendXcm for Pallet { dest: &mut Option, xcm: &mut Option>, ) -> SendResult { + log::trace!(target: LOG_TARGET, "validate - msg: {xcm:?}, destination: {dest:?}"); // `dest` and `xcm` are required here let dest_ref = dest.as_ref().ok_or(SendError::MissingArgument)?; let xcm_ref = xcm.as_ref().ok_or(SendError::MissingArgument)?; @@ -366,6 +373,7 @@ impl, I: 'static> SendXcm for Pallet { // increase delivery fee factor if required Self::on_message_sent_to_bridge(message_size); + log::trace!(target: LOG_TARGET, "deliver - message sent, xcm_hash: {xcm_hash:?}"); Ok(xcm_hash) } } diff --git a/cumulus/pallets/xcmp-queue/src/lib.rs b/cumulus/pallets/xcmp-queue/src/lib.rs index b4cd925d540e..deced13a9e81 100644 --- a/cumulus/pallets/xcmp-queue/src/lib.rs +++ b/cumulus/pallets/xcmp-queue/src/lib.rs @@ -942,7 +942,10 @@ impl SendXcm for Pallet { Self::deposit_event(Event::XcmpMessageSent { message_hash: hash }); Ok(hash) }, - Err(e) => Err(SendError::Transport(e.into())), + Err(e) => { + log::error!(target: LOG_TARGET, "Deliver error: {e:?}"); + Err(SendError::Transport(e.into())) + }, } } } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs index d81ab8143ddb..6bcf0f004b69 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs @@ -80,15 +80,14 @@ pub fn genesis(para_id: u32) -> Storage { assets: vec![ // Relay Native asset representation ( - Location::try_from(RelayLocation::get()).expect("conversion works"), + Location::try_from(RelayLocation::get()).unwrap(), PenpalAssetOwner::get(), true, ED, ), // Sufficient AssetHub asset representation ( - Location::try_from(LocalReservableFromAssetHub::get()) - .expect("conversion works"), + Location::try_from(LocalReservableFromAssetHub::get()).unwrap(), PenpalAssetOwner::get(), true, ED, diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs index 0b49c7a3e091..b41ed88eb117 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs @@ -16,16 +16,19 @@ mod genesis; pub use genesis::{genesis, PenpalAssetOwner, PenpalSudoAccount, ED, PARA_ID_A, PARA_ID_B}; pub use penpal_runtime::xcm_config::{ - CustomizableAssetFromSystemAssetHub, LocalTeleportableToAssetHub, XcmConfig, + CustomizableAssetFromSystemAssetHub, RelayNetworkId as PenpalRelayNetworkId, }; // Substrate use frame_support::traits::OnInitialize; +use sp_core::Encode; // Cumulus use emulated_integration_tests_common::{ impl_accounts_helpers_for_parachain, impl_assert_events_helpers_for_parachain, - impl_assets_helpers_for_parachain, impls::Parachain, xcm_emulator::decl_test_parachains, + impl_assets_helpers_for_parachain, impl_xcm_helpers_for_parachain, + impls::{NetworkId, Parachain}, + xcm_emulator::decl_test_parachains, }; // Penpal Parachain declaration @@ -34,6 +37,10 @@ decl_test_parachains! { genesis = genesis(PARA_ID_A), on_init = { penpal_runtime::AuraExt::on_initialize(1); + frame_support::assert_ok!(penpal_runtime::System::set_storage( + penpal_runtime::RuntimeOrigin::root(), + vec![(PenpalRelayNetworkId::key().to_vec(), NetworkId::Rococo.encode())], + )); }, runtime = penpal_runtime, core = { @@ -53,6 +60,10 @@ decl_test_parachains! { genesis = genesis(PARA_ID_B), on_init = { penpal_runtime::AuraExt::on_initialize(1); + frame_support::assert_ok!(penpal_runtime::System::set_storage( + penpal_runtime::RuntimeOrigin::root(), + vec![(PenpalRelayNetworkId::key().to_vec(), NetworkId::Westend.encode())], + )); }, runtime = penpal_runtime, core = { @@ -77,3 +88,5 @@ impl_assert_events_helpers_for_parachain!(PenpalA); impl_assert_events_helpers_for_parachain!(PenpalB); impl_assets_helpers_for_parachain!(PenpalA); impl_assets_helpers_for_parachain!(PenpalB); +impl_xcm_helpers_for_parachain!(PenpalA); +impl_xcm_helpers_for_parachain!(PenpalB); diff --git a/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/src/lib.rs b/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/src/lib.rs index ee8b038a364d..d87bc5aa9633 100644 --- a/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/src/lib.rs @@ -25,7 +25,7 @@ use asset_hub_rococo_emulated_chain::AssetHubRococo; use asset_hub_westend_emulated_chain::AssetHubWestend; use bridge_hub_rococo_emulated_chain::BridgeHubRococo; use bridge_hub_westend_emulated_chain::BridgeHubWestend; -use penpal_emulated_chain::PenpalA; +use penpal_emulated_chain::{PenpalA, PenpalB}; use rococo_emulated_chain::Rococo; use westend_emulated_chain::Westend; @@ -48,13 +48,13 @@ decl_test_networks! { PenpalA, ], bridge = RococoWestendMockBridge - }, pub struct WestendMockNet { relay_chain = Westend, parachains = vec![ AssetHubWestend, BridgeHubWestend, + PenpalB, ], bridge = WestendRococoMockBridge }, @@ -96,5 +96,6 @@ decl_test_sender_receiver_accounts_parameter_types! { WestendRelay { sender: ALICE, receiver: BOB }, AssetHubWestendPara { sender: ALICE, receiver: BOB }, BridgeHubWestendPara { sender: ALICE, receiver: BOB }, - PenpalAPara { sender: ALICE, receiver: BOB } + PenpalAPara { sender: ALICE, receiver: BOB }, + PenpalBPara { sender: ALICE, receiver: BOB } } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs index a5a4914e21d8..322c6cf1f228 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs @@ -30,6 +30,7 @@ mod imports { prelude::{AccountId32 as AccountId32Junction, *}, v3, }; + pub use xcm_executor::traits::TransferType; // Cumulus pub use asset_test_utils::xcm_helpers; @@ -81,6 +82,7 @@ mod imports { pub type SystemParaToParaTest = Test; pub type ParaToSystemParaTest = Test; pub type ParaToParaThroughRelayTest = Test; + pub type ParaToParaThroughAHTest = Test; } #[cfg(test)] diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/foreign_assets_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/foreign_assets_transfers.rs new file mode 100644 index 000000000000..6444e9de82e8 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/foreign_assets_transfers.rs @@ -0,0 +1,608 @@ +// 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 super::reserve_transfer::*; +use crate::{ + imports::*, + tests::teleport::do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt, +}; + +fn para_to_para_assethub_hop_assertions(t: ParaToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + let sov_penpal_a_on_ah = AssetHubRococo::sovereign_account_id_of( + AssetHubRococo::sibling_location_of(PenpalA::para_id()), + ); + let sov_penpal_b_on_ah = AssetHubRococo::sovereign_account_id_of( + AssetHubRococo::sibling_location_of(PenpalB::para_id()), + ); + + assert_expected_events!( + AssetHubRococo, + vec![ + // Withdrawn from sender parachain SA + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_penpal_a_on_ah, + amount: *amount == t.args.amount, + }, + // Deposited to receiver parachain SA + RuntimeEvent::Balances( + pallet_balances::Event::Minted { who, .. } + ) => { + who: *who == sov_penpal_b_on_ah, + }, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + +fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + ::PolkadotXcm::transfer_assets_using_type( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + bx!(TransferType::LocalReserve), + bx!(fee.id.into()), + bx!(TransferType::LocalReserve), + t.args.weight_limit, + ) +} + +fn para_to_ah_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + ::PolkadotXcm::transfer_assets_using_type( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + bx!(TransferType::DestinationReserve), + bx!(fee.id.into()), + bx!(TransferType::DestinationReserve), + t.args.weight_limit, + ) +} + +fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let asset_hub_location: Location = PenpalA::sibling_location_of(AssetHubRococo::para_id()); + ::PolkadotXcm::transfer_assets_using_type( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())), + bx!(fee.id.into()), + bx!(TransferType::RemoteReserve(asset_hub_location.into())), + t.args.weight_limit, + ) +} + +fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + ::PolkadotXcm::transfer_assets_using_type( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + bx!(TransferType::Teleport), + bx!(fee.id.into()), + bx!(TransferType::DestinationReserve), + t.args.weight_limit, + ) +} + +fn asset_hub_to_para_teleport_foreign_assets(t: SystemParaToParaTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + ::PolkadotXcm::transfer_assets_using_type( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + bx!(TransferType::Teleport), + bx!(fee.id.into()), + bx!(TransferType::LocalReserve), + t.args.weight_limit, + ) +} + +// =========================================================================== +// ======= Transfer - Native + Bridged Assets - AssetHub->Parachain ========== +// =========================================================================== +/// Transfers of native asset plus bridged asset from AssetHub to some Parachain +/// while paying fees using native asset. +#[test] +fn transfer_foreign_assets_from_asset_hub_to_para() { + let destination = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let sender = AssetHubRococoSender::get(); + let native_amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000; + let native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); + let receiver = PenpalAReceiver::get(); + let assets_owner = PenpalAssetOwner::get(); + // Foreign asset used: bridged WND + let foreign_amount_to_send = ASSET_HUB_ROCOCO_ED * 10_000_000; + let wnd_at_rococo_parachains = + v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Westend)]); + let wnd_at_rococo_parachains_latest: Location = wnd_at_rococo_parachains.try_into().unwrap(); + + // Configure destination chain to trust AH as reserve of WND + PenpalA::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Westend)]).encode(), + )], + )); + }); + PenpalA::force_create_foreign_asset( + wnd_at_rococo_parachains, + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + AssetHubRococo::force_create_foreign_asset( + wnd_at_rococo_parachains, + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + AssetHubRococo::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner), + wnd_at_rococo_parachains, + sender.clone(), + foreign_amount_to_send * 2, + ); + + // Assets to send + let assets: Vec = vec![ + (Parent, native_amount_to_send).into(), + (wnd_at_rococo_parachains_latest, foreign_amount_to_send).into(), + ]; + let fee_asset_id = AssetId(Parent.into()); + let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32; + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination.clone(), + receiver.clone(), + native_amount_to_send, + assets.into(), + None, + fee_asset_item, + ), + }; + let mut test = SystemParaToParaTest::new(test_args); + + // Query initial balances + let sender_balance_before = test.sender.balance; + let sender_wnds_before = AssetHubRococo::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains, &sender) + }); + let receiver_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(native_asset_location.into(), &receiver) + }); + let receiver_wnds_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains, &receiver) + }); + + // Set assertions and dispatchables + test.set_assertion::(system_para_to_para_sender_assertions); + test.set_assertion::(system_para_to_para_receiver_assertions); + test.set_dispatchable::(ah_to_para_transfer_assets); + test.assert(); + + // Query final balances + let sender_balance_after = test.sender.balance; + let sender_wnds_after = AssetHubRococo::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains, &sender) + }); + let receiver_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(native_asset_location, &receiver) + }); + let receiver_wnds_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains, &receiver) + }); + + // Sender's balance is reduced by amount sent plus delivery fees + assert!(sender_balance_after < sender_balance_before - native_amount_to_send); + // Sender's balance is reduced by foreign amount sent + assert_eq!(sender_wnds_after, sender_wnds_before - foreign_amount_to_send); + // Receiver's assets is increased + assert!(receiver_assets_after > receiver_assets_before); + // Receiver's assets increased by `amount_to_send - delivery_fees - bought_execution`; + // `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but + // should be non-zero + assert!(receiver_assets_after < receiver_assets_before + native_amount_to_send); + // Receiver's balance is increased by foreign amount sent + assert_eq!(receiver_wnds_after, receiver_wnds_before + foreign_amount_to_send); +} + +/// Reserve Transfers of native asset from Parachain to System Parachain should work +// =========================================================================== +// ======= Transfer - Native + Bridged Assets - Parachain->AssetHub ========== +// =========================================================================== +/// Transfers of native asset plus bridged asset from some Parachain to AssetHub +/// while paying fees using native asset. +#[test] +fn transfer_foreign_assets_from_para_to_asset_hub() { + // Init values for Parachain + let destination = PenpalA::sibling_location_of(AssetHubRococo::para_id()); + let sender = PenpalASender::get(); + let native_amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000; + let native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); + let assets_owner = PenpalAssetOwner::get(); + + // Foreign asset used: bridged WND + let foreign_amount_to_send = ASSET_HUB_ROCOCO_ED * 10_000_000; + let wnd_at_rococo_parachains = + v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Westend)]); + let wnd_at_rococo_parachains_latest: Location = wnd_at_rococo_parachains.try_into().unwrap(); + + // Configure destination chain to trust AH as reserve of WND + PenpalA::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Westend)]).encode(), + )], + )); + }); + PenpalA::force_create_foreign_asset( + wnd_at_rococo_parachains, + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + AssetHubRococo::force_create_foreign_asset( + wnd_at_rococo_parachains, + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner.clone()), + native_asset_location, + sender.clone(), + native_amount_to_send * 2, + ); + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner.clone()), + wnd_at_rococo_parachains, + sender.clone(), + foreign_amount_to_send * 2, + ); + + // Init values for System Parachain + let receiver = AssetHubRococoReceiver::get(); + let penpal_location_as_seen_by_ahr = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_location_as_seen_by_ahr); + + // fund Parachain's SA on AssetHub with the assets held in reserve + AssetHubRococo::fund_accounts(vec![( + sov_penpal_on_ahr.clone().into(), + native_amount_to_send * 2, + )]); + AssetHubRococo::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner), + wnd_at_rococo_parachains, + sov_penpal_on_ahr, + foreign_amount_to_send * 2, + ); + + // Assets to send + let assets: Vec = vec![ + (Parent, native_amount_to_send).into(), + (wnd_at_rococo_parachains_latest, foreign_amount_to_send).into(), + ]; + let fee_asset_id = AssetId(Parent.into()); + let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32; + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination.clone(), + receiver.clone(), + native_amount_to_send, + assets.into(), + None, + fee_asset_item, + ), + }; + let mut test = ParaToSystemParaTest::new(test_args); + + // Query initial balances + let sender_native_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(native_asset_location, &sender) + }); + let sender_wnds_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains, &sender) + }); + let receiver_native_before = test.receiver.balance; + let receiver_wnds_before = AssetHubRococo::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains, &receiver) + }); + + // Set assertions and dispatchables + test.set_assertion::(para_to_system_para_sender_assertions); + test.set_assertion::(para_to_system_para_receiver_assertions); + test.set_dispatchable::(para_to_ah_transfer_assets); + test.assert(); + + // Query final balances + let sender_native_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(native_asset_location, &sender) + }); + let sender_wnds_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains, &sender) + }); + let receiver_native_after = test.receiver.balance; + let receiver_wnds_after = AssetHubRococo::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains, &receiver) + }); + + // Sender's balance is reduced by amount sent plus delivery fees + assert!(sender_native_after < sender_native_before - native_amount_to_send); + // Sender's balance is reduced by foreign amount sent + assert_eq!(sender_wnds_after, sender_wnds_before - foreign_amount_to_send); + // Receiver's balance is increased + assert!(receiver_native_after > receiver_native_before); + // Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`; + // `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but + // should be non-zero + assert!(receiver_native_after < receiver_native_before + native_amount_to_send); + // Receiver's balance is increased by foreign amount sent + assert_eq!(receiver_wnds_after, receiver_wnds_before + foreign_amount_to_send); +} + +// ============================================================================== +// ===== Transfer - Native + Bridged Assets - Parachain->AssetHub->Parachain ==== +// ============================================================================== +/// Transfers of native asset plus bridged asset from Parachain to Parachain +/// (through AssetHub reserve) with fees paid using native asset. +#[test] +fn transfer_foreign_assets_from_para_to_para_through_asset_hub() { + // Init values for Parachain Origin + let destination = PenpalA::sibling_location_of(PenpalB::para_id()); + let sender = PenpalASender::get(); + let roc_to_send: Balance = ROCOCO_ED * 10000; + let assets_owner = PenpalAssetOwner::get(); + let roc_location = v3::Location::try_from(RelayLocation::get()).unwrap(); + let roc_location_latest: Location = roc_location.try_into().unwrap(); + let sender_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let sov_of_sender_on_ah = AssetHubRococo::sovereign_account_id_of(sender_as_seen_by_ah); + let receiver_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalB::para_id()); + let sov_of_receiver_on_ah = AssetHubRococo::sovereign_account_id_of(receiver_as_seen_by_ah); + let wnd_to_send = ASSET_HUB_ROCOCO_ED * 10_000_000; + + // Configure destination chain to trust AH as reserve of WND + PenpalB::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Westend)]).encode(), + )], + )); + }); + + // Register WND as foreign asset and transfer it around the Rococo ecosystem + let wnd_at_rococo_parachains = + v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Westend)]); + let wnd_at_rococo_parachains_latest: Location = wnd_at_rococo_parachains.try_into().unwrap(); + AssetHubRococo::force_create_foreign_asset( + wnd_at_rococo_parachains, + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + PenpalA::force_create_foreign_asset( + wnd_at_rococo_parachains, + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + PenpalB::force_create_foreign_asset( + wnd_at_rococo_parachains, + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner.clone()), + roc_location, + sender.clone(), + roc_to_send * 2, + ); + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner.clone()), + wnd_at_rococo_parachains, + sender.clone(), + wnd_to_send * 2, + ); + // fund the Parachain Origin's SA on Asset Hub with the assets held in reserve + AssetHubRococo::fund_accounts(vec![(sov_of_sender_on_ah.clone().into(), roc_to_send * 2)]); + AssetHubRococo::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner), + wnd_at_rococo_parachains, + sov_of_sender_on_ah.clone(), + wnd_to_send * 2, + ); + + // Init values for Parachain Destination + let receiver = PenpalBReceiver::get(); + + // Assets to send + let assets: Vec = vec![ + (roc_location_latest.clone(), roc_to_send).into(), + (wnd_at_rococo_parachains_latest, wnd_to_send).into(), + ]; + let fee_asset_id: AssetId = roc_location_latest.into(); + let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32; + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination, + receiver.clone(), + roc_to_send, + assets.into(), + None, + fee_asset_item, + ), + }; + let mut test = ParaToParaThroughAHTest::new(test_args); + + // Query initial balances + let sender_rocs_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_location, &sender) + }); + let sender_wnds_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains, &sender) + }); + let rocs_in_sender_reserve_on_ahr_before = + ::account_data_of(sov_of_sender_on_ah.clone()).free; + let wnds_in_sender_reserve_on_ahr_before = AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains, &sov_of_sender_on_ah) + }); + let rocs_in_receiver_reserve_on_ahr_before = + ::account_data_of(sov_of_receiver_on_ah.clone()).free; + let wnds_in_receiver_reserve_on_ahr_before = AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains, &sov_of_receiver_on_ah) + }); + let receiver_rocs_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_location, &receiver) + }); + let receiver_wnds_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains, &receiver) + }); + + // Set assertions and dispatchables + test.set_assertion::(para_to_para_through_hop_sender_assertions); + test.set_assertion::(para_to_para_assethub_hop_assertions); + test.set_assertion::(para_to_para_through_hop_receiver_assertions); + test.set_dispatchable::(para_to_para_transfer_assets_through_ah); + test.assert(); + + // Query final balances + let sender_rocs_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_location, &sender) + }); + let sender_wnds_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains, &sender) + }); + let wnds_in_sender_reserve_on_ahr_after = AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains, &sov_of_sender_on_ah) + }); + let rocs_in_sender_reserve_on_ahr_after = + ::account_data_of(sov_of_sender_on_ah).free; + let wnds_in_receiver_reserve_on_ahr_after = AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains, &sov_of_receiver_on_ah) + }); + let rocs_in_receiver_reserve_on_ahr_after = + ::account_data_of(sov_of_receiver_on_ah).free; + let receiver_rocs_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_location, &receiver) + }); + let receiver_wnds_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains, &receiver) + }); + + // Sender's balance is reduced by amount sent plus delivery fees + assert!(sender_rocs_after < sender_rocs_before - roc_to_send); + assert_eq!(sender_wnds_after, sender_wnds_before - wnd_to_send); + // Sovereign accounts on reserve are changed accordingly + assert_eq!( + rocs_in_sender_reserve_on_ahr_after, + rocs_in_sender_reserve_on_ahr_before - roc_to_send + ); + assert_eq!( + wnds_in_sender_reserve_on_ahr_after, + wnds_in_sender_reserve_on_ahr_before - wnd_to_send + ); + assert!(rocs_in_receiver_reserve_on_ahr_after > rocs_in_receiver_reserve_on_ahr_before); + assert_eq!( + wnds_in_receiver_reserve_on_ahr_after, + wnds_in_receiver_reserve_on_ahr_before + wnd_to_send + ); + // Receiver's balance is increased + assert!(receiver_rocs_after > receiver_rocs_before); + assert_eq!(receiver_wnds_after, receiver_wnds_before + wnd_to_send); +} + +// ============================================================================================== +// ==== Bidirectional Transfer - Native + Teleportable Foreign Assets - Parachain<->AssetHub ==== +// ============================================================================================== +/// Transfers of native asset plus teleportable foreign asset from Parachain to AssetHub and back +/// with fees paid using native asset. +#[test] +fn bidirectional_teleport_foreign_asset_between_para_and_asset_hub_using_explicit_transfer_types() { + do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt( + para_to_asset_hub_teleport_foreign_assets, + asset_hub_to_para_teleport_foreign_assets, + ); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs index b3841af0e6c3..2402989225af 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod foreign_assets_transfers; mod reserve_transfer; mod send; mod set_xcm_versions; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs index a0738839087a..2a341c2e5159 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs @@ -47,7 +47,7 @@ fn para_to_relay_sender_assertions(t: ParaToRelayTest) { RuntimeEvent::ForeignAssets( pallet_assets::Event::Burned { asset_id, owner, balance, .. } ) => { - asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).unwrap(), owner: *owner == t.sender.account_id, balance: *balance == t.args.amount, }, @@ -55,70 +55,91 @@ fn para_to_relay_sender_assertions(t: ParaToRelayTest) { ); } -fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { +pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { type RuntimeEvent = ::RuntimeEvent; - - AssetHubRococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( - 864_610_000, - 8_799, - ))); - + AssetHubRococo::assert_xcm_pallet_attempted_complete(None); + + let sov_acc_of_dest = AssetHubRococo::sovereign_account_id_of(t.args.dest.clone()); + for (idx, asset) in t.args.assets.into_inner().into_iter().enumerate() { + let expected_id = asset.id.0.clone().try_into().unwrap(); + let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); + if idx == t.args.fee_asset_item as usize { + assert_expected_events!( + AssetHubRococo, + vec![ + // Amount of native asset is transferred to Parachain's Sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Transfer { from, to, amount } + ) => { + from: *from == t.sender.account_id, + to: *to == sov_acc_of_dest, + amount: *amount == asset_amount, + }, + ] + ); + } else { + assert_expected_events!( + AssetHubRococo, + vec![ + // Amount of foreign asset is transferred to Parachain's Sovereign account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Transferred { asset_id, from, to, amount }, + ) => { + asset_id: *asset_id == expected_id, + from: *from == t.sender.account_id, + to: *to == sov_acc_of_dest, + amount: *amount == asset_amount, + }, + ] + ); + } + } assert_expected_events!( AssetHubRococo, vec![ - // Amount to reserve transfer is transferred to Parachain's Sovereign account - RuntimeEvent::Balances( - pallet_balances::Event::Transfer { from, to, amount } - ) => { - from: *from == t.sender.account_id, - to: *to == AssetHubRococo::sovereign_account_id_of( - t.args.dest.clone() - ), - amount: *amount == t.args.amount, - }, // Transport fees are paid - RuntimeEvent::PolkadotXcm( - pallet_xcm::Event::FeesPaid { .. } - ) => {}, + RuntimeEvent::PolkadotXcm(pallet_xcm::Event::FeesPaid { .. }) => {}, ] ); AssetHubRococo::assert_xcm_pallet_sent(); } -fn system_para_to_para_receiver_assertions(t: SystemParaToParaTest) { +pub fn system_para_to_para_receiver_assertions(t: SystemParaToParaTest) { type RuntimeEvent = ::RuntimeEvent; - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); - PenpalA::assert_xcmp_queue_success(None); - - assert_expected_events!( - PenpalA, - vec![ - RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == system_para_native_asset_location, - owner: *owner == t.receiver.account_id, - }, - ] - ); + for asset in t.args.assets.into_inner().into_iter() { + let expected_id = asset.id.0.try_into().unwrap(); + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.receiver.account_id, + }, + ] + ); + } } -fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) { +pub fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; - PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8_799))); - assert_expected_events!( - PenpalA, - vec![ - // Amount to reserve transfer is transferred to Parachain's Sovereign account - RuntimeEvent::ForeignAssets( - pallet_assets::Event::Burned { asset_id, owner, balance, .. } - ) => { - asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), - owner: *owner == t.sender.account_id, - balance: *balance == t.args.amount, - }, - ] - ); + PenpalA::assert_xcm_pallet_attempted_complete(None); + for asset in t.args.assets.into_inner().into_iter() { + let expected_id = asset.id.0.try_into().unwrap(); + let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance } + ) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.sender.account_id, + balance: *balance == asset_amount, + }, + ] + ); + } } fn para_to_relay_receiver_assertions(t: ParaToRelayTest) { @@ -150,25 +171,57 @@ fn para_to_relay_receiver_assertions(t: ParaToRelayTest) { ); } -fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) { +pub fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; - let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of( - AssetHubRococo::sibling_location_of(PenpalA::para_id()), - ); - AssetHubRococo::assert_xcmp_queue_success(None); + let sov_acc_of_penpal = AssetHubRococo::sovereign_account_id_of(t.args.dest.clone()); + for (idx, asset) in t.args.assets.into_inner().into_iter().enumerate() { + let expected_id = asset.id.0.clone().try_into().unwrap(); + let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); + if idx == t.args.fee_asset_item as usize { + assert_expected_events!( + AssetHubRococo, + vec![ + // Amount of native is withdrawn from Parachain's Sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_acc_of_penpal.clone().into(), + amount: *amount == asset_amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == t.receiver.account_id, + }, + ] + ); + } else { + assert_expected_events!( + AssetHubRococo, + vec![ + // Amount of foreign asset is transferred from Parachain's Sovereign account + // to Receiver's account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance }, + ) => { + asset_id: *asset_id == expected_id, + owner: *owner == sov_acc_of_penpal, + balance: *balance == asset_amount, + }, + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Issued { asset_id, owner, amount }, + ) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.receiver.account_id, + amount: *amount == asset_amount, + }, + ] + ); + } + } assert_expected_events!( AssetHubRococo, vec![ - // Amount to reserve transfer is withdrawn from Parachain's Sovereign account - RuntimeEvent::Balances( - pallet_balances::Event::Burned { who, amount } - ) => { - who: *who == sov_penpal_on_ahr.clone().into(), - amount: *amount == t.args.amount, - }, - RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } ) => {}, @@ -212,10 +265,9 @@ fn system_para_to_para_assets_sender_assertions(t: SystemParaToParaTest) { fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); let reservable_asset_location = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap(); PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8799))); assert_expected_events!( PenpalA, @@ -246,13 +298,13 @@ fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) { fn system_para_to_para_assets_receiver_assertions(t: SystemParaToParaTest) { type RuntimeEvent = ::RuntimeEvent; let system_para_asset_location = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap(); PenpalA::assert_xcmp_queue_success(None); assert_expected_events!( PenpalA, vec![ RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).unwrap(), owner: *owner == t.receiver.account_id, }, RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { @@ -304,7 +356,7 @@ fn relay_to_para_assets_receiver_assertions(t: RelayToParaTest) { PenpalA, vec![ RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).unwrap(), owner: *owner == t.receiver.account_id, }, RuntimeEvent::MessageQueue( @@ -314,29 +366,27 @@ fn relay_to_para_assets_receiver_assertions(t: RelayToParaTest) { ); } -fn para_to_para_through_relay_sender_assertions(t: ParaToParaThroughRelayTest) { +pub fn para_to_para_through_hop_sender_assertions(t: Test) { type RuntimeEvent = ::RuntimeEvent; - let relay_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); - PenpalA::assert_xcm_pallet_attempted_complete(None); - // XCM sent to relay reserve - PenpalA::assert_parachain_system_ump_sent(); - - assert_expected_events!( - PenpalA, - vec![ - // Amount to reserve transfer is transferred to Parachain's Sovereign account - RuntimeEvent::ForeignAssets( - pallet_assets::Event::Burned { asset_id, owner, balance }, - ) => { - asset_id: *asset_id == relay_asset_location, - owner: *owner == t.sender.account_id, - balance: *balance == t.args.amount, - }, - ] - ); + for asset in t.args.assets.into_inner() { + let expected_id = asset.id.0.clone().try_into().unwrap(); + let amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); + assert_expected_events!( + PenpalA, + vec![ + // Amount to reserve transfer is transferred to Parachain's Sovereign account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance }, + ) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.sender.account_id, + balance: *balance == amount, + }, + ] + ); + } } fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) { @@ -369,22 +419,22 @@ fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) { ); } -fn para_to_para_through_relay_receiver_assertions(t: ParaToParaThroughRelayTest) { +pub fn para_to_para_through_hop_receiver_assertions(t: Test) { type RuntimeEvent = ::RuntimeEvent; - let relay_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); PenpalB::assert_xcmp_queue_success(None); - - assert_expected_events!( - PenpalB, - vec![ - RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == relay_asset_location, - owner: *owner == t.receiver.account_id, - }, - ] - ); + for asset in t.args.assets.into_inner().into_iter() { + let expected_id = asset.id.0.try_into().unwrap(); + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.receiver.account_id, + }, + ] + ); + } } fn relay_to_para_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult { @@ -526,8 +576,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() { let amount_to_send: Balance = ROCOCO_ED * 1000; // Init values fot Parachain - let relay_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let relay_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); let receiver = PenpalAReceiver::get(); // Init Test @@ -577,8 +626,7 @@ fn reserve_transfer_native_asset_from_para_to_relay() { let amount_to_send: Balance = ROCOCO_ED * 1000; let assets: Assets = (Parent, amount_to_send).into(); let asset_owner = PenpalAssetOwner::get(); - let relay_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let relay_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); // fund Parachain's sender account PenpalA::mint_foreign_asset( @@ -654,8 +702,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() { let assets: Assets = (Parent, amount_to_send).into(); // Init values for Parachain - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); let receiver = PenpalAReceiver::get(); // Init Test @@ -711,8 +758,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() { let sender = PenpalASender::get(); let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000; let assets: Assets = (Parent, amount_to_send).into(); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); let asset_owner = PenpalAssetOwner::get(); // fund Parachain's sender account @@ -776,9 +822,9 @@ fn reserve_transfer_native_asset_from_para_to_system_para() { assert!(receiver_balance_after < receiver_balance_before + amount_to_send); } -// ========================================================================= -// ======= Reserve Transfers - Non-system Asset - AssetHub<>Parachain ====== -// ========================================================================= +// ================================================================================== +// ======= Reserve Transfers - Native + Non-system Asset - AssetHub<>Parachain ====== +// ================================================================================== /// Reserve Transfers of a local asset and native asset from System Parachain to Parachain should /// work #[test] @@ -817,10 +863,9 @@ fn reserve_transfer_assets_from_system_para_to_para() { // Init values for Parachain let receiver = PenpalAReceiver::get(); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); let system_para_foreign_asset_location = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap(); // Init Test let para_test_args = TestContext { @@ -905,10 +950,9 @@ fn reserve_transfer_assets_from_para_to_system_para() { let penpal_asset_owner = PenpalAssetOwner::get(); let penpal_asset_owner_signer = ::RuntimeOrigin::signed(penpal_asset_owner); let asset_location_on_penpal = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap(); let asset_location_on_penpal_latest: Location = asset_location_on_penpal.try_into().unwrap(); - let system_asset_location_on_penpal = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_asset_location_on_penpal = v3::Location::try_from(RelayLocation::get()).unwrap(); let assets: Assets = vec![ (Parent, fee_amount_to_send).into(), (asset_location_on_penpal_latest, asset_amount_to_send).into(), @@ -938,10 +982,9 @@ fn reserve_transfer_assets_from_para_to_system_para() { let receiver = AssetHubRococoReceiver::get(); let penpal_location_as_seen_by_ahr = AssetHubRococo::sibling_location_of(PenpalA::para_id()); let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_location_as_seen_by_ahr); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); let system_para_foreign_asset_location = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap(); let ah_asset_owner = AssetHubRococoAssetOwner::get(); let ah_asset_owner_signer = ::RuntimeOrigin::signed(ah_asset_owner); @@ -1029,15 +1072,14 @@ fn reserve_transfer_assets_from_para_to_system_para() { /// Reserve Transfers of native asset from Parachain to Parachain (through Relay reserve) should /// work #[test] -fn reserve_transfer_native_asset_from_para_to_para_trough_relay() { +fn reserve_transfer_native_asset_from_para_to_para_through_relay() { // Init values for Parachain Origin let destination = PenpalA::sibling_location_of(PenpalB::para_id()); let sender = PenpalASender::get(); let amount_to_send: Balance = ROCOCO_ED * 10000; let asset_owner = PenpalAssetOwner::get(); let assets = (Parent, amount_to_send).into(); - let relay_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let relay_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); let sender_as_seen_by_relay = Rococo::child_location_of(PenpalA::para_id()); let sov_of_sender_on_relay = Rococo::sovereign_account_id_of(sender_as_seen_by_relay); @@ -1074,9 +1116,9 @@ fn reserve_transfer_native_asset_from_para_to_para_trough_relay() { }); // Set assertions and dispatchables - test.set_assertion::(para_to_para_through_relay_sender_assertions); + test.set_assertion::(para_to_para_through_hop_sender_assertions); test.set_assertion::(para_to_para_relay_hop_assertions); - test.set_assertion::(para_to_para_through_relay_receiver_assertions); + test.set_assertion::(para_to_para_through_hop_receiver_assertions); test.set_dispatchable::(para_to_para_through_relay_limited_reserve_transfer_assets); test.assert(); diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs index e13300b7c114..cea1b8aefbd5 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs @@ -112,10 +112,9 @@ fn swap_locally_on_chain_using_local_assets() { #[test] fn swap_locally_on_chain_using_foreign_assets() { - let asset_native = - Box::new(v3::Location::try_from(RelayLocation::get()).expect("conversion works")); + let asset_native = Box::new(v3::Location::try_from(RelayLocation::get()).unwrap()); let asset_location_on_penpal = - v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion works"); + v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).unwrap(); let foreign_asset_at_asset_hub_rococo = v3::Location::new(1, [v3::Junction::Parachain(PenpalA::para_id().into())]) .appended_with(asset_location_on_penpal) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs index 1cbb7fb8c193..dbfd7c50f61a 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs @@ -110,8 +110,7 @@ fn para_dest_assertions(t: RelayToSystemParaTest) { fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); let expected_asset_id = t.args.asset_id.unwrap(); let (_, expected_asset_amount) = non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); @@ -204,8 +203,7 @@ fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) { let (_, expected_asset_amount) = non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); let checking_account = ::PolkadotXcm::check_account(); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); PenpalA::assert_xcmp_queue_success(None); @@ -414,22 +412,23 @@ fn teleport_to_other_system_parachains_works() { ); } -/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets should work -/// (using native reserve-based transfer for fees) -#[test] -fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { +/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets while paying +/// fees using (reserve transferred) native asset. +pub fn do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt( + para_to_ah_dispatchable: fn(ParaToSystemParaTest) -> DispatchResult, + ah_to_para_dispatchable: fn(SystemParaToParaTest) -> DispatchResult, +) { // Init values for Parachain let fee_amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000; let asset_location_on_penpal = - v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion works"); + v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).unwrap(); let asset_id_on_penpal = match asset_location_on_penpal.last() { Some(v3::Junction::GeneralIndex(id)) => *id as u32, _ => unreachable!(), }; let asset_amount_to_send = ASSET_HUB_ROCOCO_ED * 1000; let asset_owner = PenpalAssetOwner::get(); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); let sender = PenpalASender::get(); let penpal_check_account = ::PolkadotXcm::check_account(); let ah_as_seen_by_penpal = PenpalA::sibling_location_of(AssetHubRococo::para_id()); @@ -515,7 +514,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { penpal_to_ah.set_assertion::(penpal_to_ah_foreign_assets_sender_assertions); penpal_to_ah.set_assertion::(penpal_to_ah_foreign_assets_receiver_assertions); - penpal_to_ah.set_dispatchable::(para_to_system_para_transfer_assets); + penpal_to_ah.set_dispatchable::(para_to_ah_dispatchable); penpal_to_ah.assert(); let penpal_sender_balance_after = PenpalA::execute_with(|| { @@ -622,7 +621,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { ah_to_penpal.set_assertion::(ah_to_penpal_foreign_assets_sender_assertions); ah_to_penpal.set_assertion::(ah_to_penpal_foreign_assets_receiver_assertions); - ah_to_penpal.set_dispatchable::(system_para_to_para_transfer_assets); + ah_to_penpal.set_dispatchable::(ah_to_para_dispatchable); ah_to_penpal.assert(); let ah_sender_balance_after = ah_to_penpal.sender.balance; @@ -660,3 +659,13 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { // Receiver's balance is increased by exact amount assert_eq!(penpal_receiver_assets_after, penpal_receiver_assets_before + asset_amount_to_send); } + +/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets should work +/// (using native reserve-based transfer for fees) +#[test] +fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { + do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt( + para_to_system_para_transfer_assets, + system_para_to_para_transfer_assets, + ); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs index c9f5fe0647e1..e687251c14f9 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs @@ -30,6 +30,7 @@ mod imports { prelude::{AccountId32 as AccountId32Junction, *}, v3, }; + pub use xcm_executor::traits::TransferType; // Cumulus pub use asset_test_utils::xcm_helpers; @@ -85,6 +86,7 @@ mod imports { pub type SystemParaToParaTest = Test; pub type ParaToSystemParaTest = Test; pub type ParaToParaThroughRelayTest = Test; + pub type ParaToParaThroughAHTest = Test; } #[cfg(test)] diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/foreign_assets_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/foreign_assets_transfers.rs new file mode 100644 index 000000000000..4f8c9bf7f9c1 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/foreign_assets_transfers.rs @@ -0,0 +1,609 @@ +// 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 super::reserve_transfer::*; +use crate::{ + imports::*, + tests::teleport::do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt, +}; + +fn para_to_para_assethub_hop_assertions(t: ParaToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + let sov_penpal_a_on_ah = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalA::para_id()), + ); + let sov_penpal_b_on_ah = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalB::para_id()), + ); + + assert_expected_events!( + AssetHubWestend, + vec![ + // Withdrawn from sender parachain SA + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_penpal_a_on_ah, + amount: *amount == t.args.amount, + }, + // Deposited to receiver parachain SA + RuntimeEvent::Balances( + pallet_balances::Event::Minted { who, .. } + ) => { + who: *who == sov_penpal_b_on_ah, + }, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + +fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + ::PolkadotXcm::transfer_assets_using_type( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + bx!(TransferType::LocalReserve), + bx!(fee.id.into()), + bx!(TransferType::LocalReserve), + t.args.weight_limit, + ) +} + +fn para_to_ah_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + ::PolkadotXcm::transfer_assets_using_type( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + bx!(TransferType::DestinationReserve), + bx!(fee.id.into()), + bx!(TransferType::DestinationReserve), + t.args.weight_limit, + ) +} + +fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let asset_hub_location: Location = PenpalA::sibling_location_of(AssetHubWestend::para_id()); + ::PolkadotXcm::transfer_assets_using_type( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())), + bx!(fee.id.into()), + bx!(TransferType::RemoteReserve(asset_hub_location.into())), + t.args.weight_limit, + ) +} + +fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + ::PolkadotXcm::transfer_assets_using_type( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + bx!(TransferType::Teleport), + bx!(fee.id.into()), + bx!(TransferType::DestinationReserve), + t.args.weight_limit, + ) +} + +fn asset_hub_to_para_teleport_foreign_assets(t: SystemParaToParaTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + ::PolkadotXcm::transfer_assets_using_type( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + bx!(TransferType::Teleport), + bx!(fee.id.into()), + bx!(TransferType::LocalReserve), + t.args.weight_limit, + ) +} + +// =========================================================================== +// ======= Transfer - Native + Bridged Assets - AssetHub->Parachain ========== +// =========================================================================== +/// Transfers of native asset plus bridged asset from AssetHub to some Parachain +/// while paying fees using native asset. +#[test] +fn transfer_foreign_assets_from_asset_hub_to_para() { + let destination = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let sender = AssetHubWestendSender::get(); + let native_amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000; + let native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); + let receiver = PenpalAReceiver::get(); + let assets_owner = PenpalAssetOwner::get(); + // Foreign asset used: bridged ROC + let foreign_amount_to_send = ASSET_HUB_WESTEND_ED * 10_000_000; + let roc_at_westend_parachains = + v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Rococo)]); + let roc_at_westend_parachains_latest: Location = roc_at_westend_parachains.try_into().unwrap(); + + // Configure destination chain to trust AH as reserve of ROC + PenpalA::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Rococo)]).encode(), + )], + )); + }); + PenpalA::force_create_foreign_asset( + roc_at_westend_parachains, + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + AssetHubWestend::force_create_foreign_asset( + roc_at_westend_parachains, + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + AssetHubWestend::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner), + roc_at_westend_parachains, + sender.clone(), + foreign_amount_to_send * 2, + ); + + // Assets to send + let assets: Vec = vec![ + (Parent, native_amount_to_send).into(), + (roc_at_westend_parachains_latest, foreign_amount_to_send).into(), + ]; + let fee_asset_id = AssetId(Parent.into()); + let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32; + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination.clone(), + receiver.clone(), + native_amount_to_send, + assets.into(), + None, + fee_asset_item, + ), + }; + let mut test = SystemParaToParaTest::new(test_args); + + // Query initial balances + let sender_balance_before = test.sender.balance; + let sender_rocs_before = AssetHubWestend::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains, &sender) + }); + let receiver_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(native_asset_location.into(), &receiver) + }); + let receiver_rocs_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains, &receiver) + }); + + // Set assertions and dispatchables + test.set_assertion::(system_para_to_para_sender_assertions); + test.set_assertion::(system_para_to_para_receiver_assertions); + test.set_dispatchable::(ah_to_para_transfer_assets); + test.assert(); + + // Query final balances + let sender_balance_after = test.sender.balance; + let sender_rocs_after = AssetHubWestend::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains, &sender) + }); + let receiver_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(native_asset_location, &receiver) + }); + let receiver_rocs_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains, &receiver) + }); + + // Sender's balance is reduced by amount sent plus delivery fees + assert!(sender_balance_after < sender_balance_before - native_amount_to_send); + // Sender's balance is reduced by foreign amount sent + assert_eq!(sender_rocs_after, sender_rocs_before - foreign_amount_to_send); + // Receiver's assets is increased + assert!(receiver_assets_after > receiver_assets_before); + // Receiver's assets increased by `amount_to_send - delivery_fees - bought_execution`; + // `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but + // should be non-zero + assert!(receiver_assets_after < receiver_assets_before + native_amount_to_send); + // Receiver's balance is increased by foreign amount sent + assert_eq!(receiver_rocs_after, receiver_rocs_before + foreign_amount_to_send); +} + +/// Reserve Transfers of native asset from Parachain to System Parachain should work +// =========================================================================== +// ======= Transfer - Native + Bridged Assets - Parachain->AssetHub ========== +// =========================================================================== +/// Transfers of native asset plus bridged asset from some Parachain to AssetHub +/// while paying fees using native asset. +#[test] +fn transfer_foreign_assets_from_para_to_asset_hub() { + // Init values for Parachain + let destination = PenpalA::sibling_location_of(AssetHubWestend::para_id()); + let sender = PenpalASender::get(); + let native_amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 10000; + let native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); + let assets_owner = PenpalAssetOwner::get(); + + // Foreign asset used: bridged ROC + let foreign_amount_to_send = ASSET_HUB_WESTEND_ED * 10_000_000; + let roc_at_westend_parachains = + v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Rococo)]); + let roc_at_westend_parachains_latest: Location = roc_at_westend_parachains.try_into().unwrap(); + + // Configure destination chain to trust AH as reserve of ROC + PenpalA::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Rococo)]).encode(), + )], + )); + }); + PenpalA::force_create_foreign_asset( + roc_at_westend_parachains, + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + AssetHubWestend::force_create_foreign_asset( + roc_at_westend_parachains, + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner.clone()), + native_asset_location, + sender.clone(), + native_amount_to_send * 2, + ); + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner.clone()), + roc_at_westend_parachains, + sender.clone(), + foreign_amount_to_send * 2, + ); + + // Init values for System Parachain + let receiver = AssetHubWestendReceiver::get(); + let penpal_location_as_seen_by_ahr = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ahr = + AssetHubWestend::sovereign_account_id_of(penpal_location_as_seen_by_ahr); + + // fund Parachain's SA on AssetHub with the assets held in reserve + AssetHubWestend::fund_accounts(vec![( + sov_penpal_on_ahr.clone().into(), + native_amount_to_send * 2, + )]); + AssetHubWestend::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner), + roc_at_westend_parachains, + sov_penpal_on_ahr, + foreign_amount_to_send * 2, + ); + + // Assets to send + let assets: Vec = vec![ + (Parent, native_amount_to_send).into(), + (roc_at_westend_parachains_latest, foreign_amount_to_send).into(), + ]; + let fee_asset_id = AssetId(Parent.into()); + let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32; + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination.clone(), + receiver.clone(), + native_amount_to_send, + assets.into(), + None, + fee_asset_item, + ), + }; + let mut test = ParaToSystemParaTest::new(test_args); + + // Query initial balances + let sender_native_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(native_asset_location, &sender) + }); + let sender_rocs_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains, &sender) + }); + let receiver_native_before = test.receiver.balance; + let receiver_rocs_before = AssetHubWestend::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains, &receiver) + }); + + // Set assertions and dispatchables + test.set_assertion::(para_to_system_para_sender_assertions); + test.set_assertion::(para_to_system_para_receiver_assertions); + test.set_dispatchable::(para_to_ah_transfer_assets); + test.assert(); + + // Query final balances + let sender_native_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(native_asset_location, &sender) + }); + let sender_rocs_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains, &sender) + }); + let receiver_native_after = test.receiver.balance; + let receiver_rocs_after = AssetHubWestend::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains, &receiver) + }); + + // Sender's balance is reduced by amount sent plus delivery fees + assert!(sender_native_after < sender_native_before - native_amount_to_send); + // Sender's balance is reduced by foreign amount sent + assert_eq!(sender_rocs_after, sender_rocs_before - foreign_amount_to_send); + // Receiver's balance is increased + assert!(receiver_native_after > receiver_native_before); + // Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`; + // `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but + // should be non-zero + assert!(receiver_native_after < receiver_native_before + native_amount_to_send); + // Receiver's balance is increased by foreign amount sent + assert_eq!(receiver_rocs_after, receiver_rocs_before + foreign_amount_to_send); +} + +// ============================================================================== +// ===== Transfer - Native + Bridged Assets - Parachain->AssetHub->Parachain ==== +// ============================================================================== +/// Transfers of native asset plus bridged asset from Parachain to Parachain +/// (through AssetHub reserve) with fees paid using native asset. +#[test] +fn transfer_foreign_assets_from_para_to_para_through_asset_hub() { + // Init values for Parachain Origin + let destination = PenpalA::sibling_location_of(PenpalB::para_id()); + let sender = PenpalASender::get(); + let wnd_to_send: Balance = WESTEND_ED * 10000; + let assets_owner = PenpalAssetOwner::get(); + let wnd_location = v3::Location::try_from(RelayLocation::get()).unwrap(); + let wnd_location_latest: Location = wnd_location.try_into().unwrap(); + let sender_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let sov_of_sender_on_ah = AssetHubWestend::sovereign_account_id_of(sender_as_seen_by_ah); + let receiver_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalB::para_id()); + let sov_of_receiver_on_ah = AssetHubWestend::sovereign_account_id_of(receiver_as_seen_by_ah); + let roc_to_send = ASSET_HUB_WESTEND_ED * 10_000_000; + + // Configure destination chain to trust AH as reserve of ROC + PenpalB::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Rococo)]).encode(), + )], + )); + }); + + // Register ROC as foreign asset and transfer it around the Westend ecosystem + let roc_at_westend_parachains = + v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Rococo)]); + let roc_at_westend_parachains_latest: Location = roc_at_westend_parachains.try_into().unwrap(); + AssetHubWestend::force_create_foreign_asset( + roc_at_westend_parachains, + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + PenpalA::force_create_foreign_asset( + roc_at_westend_parachains, + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + PenpalB::force_create_foreign_asset( + roc_at_westend_parachains, + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner.clone()), + wnd_location, + sender.clone(), + wnd_to_send * 2, + ); + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner.clone()), + roc_at_westend_parachains, + sender.clone(), + roc_to_send * 2, + ); + // fund the Parachain Origin's SA on Asset Hub with the assets held in reserve + AssetHubWestend::fund_accounts(vec![(sov_of_sender_on_ah.clone().into(), wnd_to_send * 2)]); + AssetHubWestend::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner), + roc_at_westend_parachains, + sov_of_sender_on_ah.clone(), + roc_to_send * 2, + ); + + // Init values for Parachain Destination + let receiver = PenpalBReceiver::get(); + + // Assets to send + let assets: Vec = vec![ + (wnd_location_latest.clone(), wnd_to_send).into(), + (roc_at_westend_parachains_latest, roc_to_send).into(), + ]; + let fee_asset_id: AssetId = wnd_location_latest.into(); + let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32; + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination, + receiver.clone(), + wnd_to_send, + assets.into(), + None, + fee_asset_item, + ), + }; + let mut test = ParaToParaThroughAHTest::new(test_args); + + // Query initial balances + let sender_wnds_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_location, &sender) + }); + let sender_rocs_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains, &sender) + }); + let wnds_in_sender_reserve_on_ah_before = + ::account_data_of(sov_of_sender_on_ah.clone()).free; + let rocs_in_sender_reserve_on_ah_before = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(roc_at_westend_parachains, &sov_of_sender_on_ah) + }); + let wnds_in_receiver_reserve_on_ah_before = + ::account_data_of(sov_of_receiver_on_ah.clone()).free; + let rocs_in_receiver_reserve_on_ah_before = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(roc_at_westend_parachains, &sov_of_receiver_on_ah) + }); + let receiver_wnds_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_location, &receiver) + }); + let receiver_rocs_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains, &receiver) + }); + + // Set assertions and dispatchables + test.set_assertion::(para_to_para_through_hop_sender_assertions); + test.set_assertion::(para_to_para_assethub_hop_assertions); + test.set_assertion::(para_to_para_through_hop_receiver_assertions); + test.set_dispatchable::(para_to_para_transfer_assets_through_ah); + test.assert(); + + // Query final balances + let sender_wnds_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_location, &sender) + }); + let sender_rocs_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains, &sender) + }); + let rocs_in_sender_reserve_on_ah_after = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(roc_at_westend_parachains, &sov_of_sender_on_ah) + }); + let wnds_in_sender_reserve_on_ah_after = + ::account_data_of(sov_of_sender_on_ah).free; + let rocs_in_receiver_reserve_on_ah_after = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(roc_at_westend_parachains, &sov_of_receiver_on_ah) + }); + let wnds_in_receiver_reserve_on_ah_after = + ::account_data_of(sov_of_receiver_on_ah).free; + let receiver_wnds_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_location, &receiver) + }); + let receiver_rocs_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains, &receiver) + }); + + // Sender's balance is reduced by amount sent plus delivery fees + assert!(sender_wnds_after < sender_wnds_before - wnd_to_send); + assert_eq!(sender_rocs_after, sender_rocs_before - roc_to_send); + // Sovereign accounts on reserve are changed accordingly + assert_eq!( + wnds_in_sender_reserve_on_ah_after, + wnds_in_sender_reserve_on_ah_before - wnd_to_send + ); + assert_eq!( + rocs_in_sender_reserve_on_ah_after, + rocs_in_sender_reserve_on_ah_before - roc_to_send + ); + assert!(wnds_in_receiver_reserve_on_ah_after > wnds_in_receiver_reserve_on_ah_before); + assert_eq!( + rocs_in_receiver_reserve_on_ah_after, + rocs_in_receiver_reserve_on_ah_before + roc_to_send + ); + // Receiver's balance is increased + assert!(receiver_wnds_after > receiver_wnds_before); + assert_eq!(receiver_rocs_after, receiver_rocs_before + roc_to_send); +} + +// ============================================================================================== +// ==== Bidirectional Transfer - Native + Teleportable Foreign Assets - Parachain<->AssetHub ==== +// ============================================================================================== +/// Transfers of native asset plus teleportable foreign asset from Parachain to AssetHub and back +/// with fees paid using native asset. +#[test] +fn bidirectional_teleport_foreign_asset_between_para_and_asset_hub_using_explicit_transfer_types() { + do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt( + para_to_asset_hub_teleport_foreign_assets, + asset_hub_to_para_teleport_foreign_assets, + ); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs index 3cd7c9c46d69..e463e21e9e52 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs @@ -14,6 +14,7 @@ // limitations under the License. mod fellowship_treasury; +mod foreign_assets_transfers; mod reserve_transfer; mod send; mod set_xcm_versions; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs index a26dfef8e8e7..0677a77e3447 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs @@ -47,7 +47,7 @@ fn para_to_relay_sender_assertions(t: ParaToRelayTest) { RuntimeEvent::ForeignAssets( pallet_assets::Event::Burned { asset_id, owner, balance, .. } ) => { - asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).unwrap(), owner: *owner == t.sender.account_id, balance: *balance == t.args.amount, }, @@ -55,70 +55,91 @@ fn para_to_relay_sender_assertions(t: ParaToRelayTest) { ); } -fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { +pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { type RuntimeEvent = ::RuntimeEvent; - - AssetHubWestend::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( - 864_610_000, - 8_799, - ))); - + AssetHubWestend::assert_xcm_pallet_attempted_complete(None); + + let sov_acc_of_dest = AssetHubWestend::sovereign_account_id_of(t.args.dest.clone()); + for (idx, asset) in t.args.assets.into_inner().into_iter().enumerate() { + let expected_id = asset.id.0.clone().try_into().unwrap(); + let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); + if idx == t.args.fee_asset_item as usize { + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount of native asset is transferred to Parachain's Sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Transfer { from, to, amount } + ) => { + from: *from == t.sender.account_id, + to: *to == sov_acc_of_dest, + amount: *amount == asset_amount, + }, + ] + ); + } else { + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount of foreign asset is transferred to Parachain's Sovereign account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Transferred { asset_id, from, to, amount }, + ) => { + asset_id: *asset_id == expected_id, + from: *from == t.sender.account_id, + to: *to == sov_acc_of_dest, + amount: *amount == asset_amount, + }, + ] + ); + } + } assert_expected_events!( AssetHubWestend, vec![ - // Amount to reserve transfer is transferred to Parachain's Sovereign account - RuntimeEvent::Balances( - pallet_balances::Event::Transfer { from, to, amount } - ) => { - from: *from == t.sender.account_id, - to: *to == AssetHubWestend::sovereign_account_id_of( - t.args.dest.clone() - ), - amount: *amount == t.args.amount, - }, // Transport fees are paid - RuntimeEvent::PolkadotXcm( - pallet_xcm::Event::FeesPaid { .. } - ) => {}, + RuntimeEvent::PolkadotXcm(pallet_xcm::Event::FeesPaid { .. }) => {}, ] ); AssetHubWestend::assert_xcm_pallet_sent(); } -fn system_para_to_para_receiver_assertions(t: SystemParaToParaTest) { +pub fn system_para_to_para_receiver_assertions(t: SystemParaToParaTest) { type RuntimeEvent = ::RuntimeEvent; - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); - PenpalA::assert_xcmp_queue_success(None); - - assert_expected_events!( - PenpalA, - vec![ - RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == system_para_native_asset_location, - owner: *owner == t.receiver.account_id, - }, - ] - ); + for asset in t.args.assets.into_inner().into_iter() { + let expected_id = asset.id.0.try_into().unwrap(); + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.receiver.account_id, + }, + ] + ); + } } -fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) { +pub fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; - PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8_799))); - assert_expected_events!( - PenpalA, - vec![ - // Amount to reserve transfer is transferred to Parachain's Sovereign account - RuntimeEvent::ForeignAssets( - pallet_assets::Event::Burned { asset_id, owner, balance, .. } - ) => { - asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), - owner: *owner == t.sender.account_id, - balance: *balance == t.args.amount, - }, - ] - ); + PenpalA::assert_xcm_pallet_attempted_complete(None); + for asset in t.args.assets.into_inner().into_iter() { + let expected_id = asset.id.0.try_into().unwrap(); + let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance } + ) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.sender.account_id, + balance: *balance == asset_amount, + }, + ] + ); + } } fn para_to_relay_receiver_assertions(t: ParaToRelayTest) { @@ -150,25 +171,57 @@ fn para_to_relay_receiver_assertions(t: ParaToRelayTest) { ); } -fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) { +pub fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; - let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of( - AssetHubWestend::sibling_location_of(PenpalA::para_id()), - ); - AssetHubWestend::assert_xcmp_queue_success(None); + let sov_acc_of_penpal = AssetHubWestend::sovereign_account_id_of(t.args.dest.clone()); + for (idx, asset) in t.args.assets.into_inner().into_iter().enumerate() { + let expected_id = asset.id.0.clone().try_into().unwrap(); + let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); + if idx == t.args.fee_asset_item as usize { + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount of native is withdrawn from Parachain's Sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_acc_of_penpal.clone().into(), + amount: *amount == asset_amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == t.receiver.account_id, + }, + ] + ); + } else { + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount of foreign asset is transferred from Parachain's Sovereign account + // to Receiver's account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance }, + ) => { + asset_id: *asset_id == expected_id, + owner: *owner == sov_acc_of_penpal, + balance: *balance == asset_amount, + }, + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Issued { asset_id, owner, amount }, + ) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.receiver.account_id, + amount: *amount == asset_amount, + }, + ] + ); + } + } assert_expected_events!( AssetHubWestend, vec![ - // Amount to reserve transfer is withdrawn from Parachain's Sovereign account - RuntimeEvent::Balances( - pallet_balances::Event::Burned { who, amount } - ) => { - who: *who == sov_penpal_on_ahr.clone().into(), - amount: *amount == t.args.amount, - }, - RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } ) => {}, @@ -212,10 +265,9 @@ fn system_para_to_para_assets_sender_assertions(t: SystemParaToParaTest) { fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); let reservable_asset_location = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap(); PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8799))); assert_expected_events!( PenpalA, @@ -246,13 +298,13 @@ fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) { fn system_para_to_para_assets_receiver_assertions(t: SystemParaToParaTest) { type RuntimeEvent = ::RuntimeEvent; let system_para_asset_location = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap(); PenpalA::assert_xcmp_queue_success(None); assert_expected_events!( PenpalA, vec![ RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).unwrap(), owner: *owner == t.receiver.account_id, }, RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { @@ -304,7 +356,7 @@ fn relay_to_para_assets_receiver_assertions(t: RelayToParaTest) { PenpalA, vec![ RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).unwrap(), owner: *owner == t.receiver.account_id, }, RuntimeEvent::MessageQueue( @@ -314,29 +366,27 @@ fn relay_to_para_assets_receiver_assertions(t: RelayToParaTest) { ); } -fn para_to_para_through_relay_sender_assertions(t: ParaToParaThroughRelayTest) { +pub fn para_to_para_through_hop_sender_assertions(t: Test) { type RuntimeEvent = ::RuntimeEvent; - - let relay_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); - PenpalA::assert_xcm_pallet_attempted_complete(None); - // XCM sent to relay reserve - PenpalA::assert_parachain_system_ump_sent(); - assert_expected_events!( - PenpalA, - vec![ - // Amount to reserve transfer is transferred to Parachain's Sovereign account - RuntimeEvent::ForeignAssets( - pallet_assets::Event::Burned { asset_id, owner, balance }, - ) => { - asset_id: *asset_id == relay_asset_location, - owner: *owner == t.sender.account_id, - balance: *balance == t.args.amount, - }, - ] - ); + for asset in t.args.assets.into_inner() { + let expected_id = asset.id.0.clone().try_into().unwrap(); + let amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); + assert_expected_events!( + PenpalA, + vec![ + // Amount to reserve transfer is transferred to Parachain's Sovereign account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance }, + ) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.sender.account_id, + balance: *balance == amount, + }, + ] + ); + } } fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) { @@ -369,22 +419,22 @@ fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) { ); } -fn para_to_para_through_relay_receiver_assertions(t: ParaToParaThroughRelayTest) { +pub fn para_to_para_through_hop_receiver_assertions(t: Test) { type RuntimeEvent = ::RuntimeEvent; - let relay_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); PenpalB::assert_xcmp_queue_success(None); - - assert_expected_events!( - PenpalB, - vec![ - RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == relay_asset_location, - owner: *owner == t.receiver.account_id, - }, - ] - ); + for asset in t.args.assets.into_inner().into_iter() { + let expected_id = asset.id.0.try_into().unwrap(); + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.receiver.account_id, + }, + ] + ); + } } fn relay_to_para_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult { @@ -526,8 +576,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() { let amount_to_send: Balance = WESTEND_ED * 1000; // Init values fot Parachain - let relay_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let relay_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); let receiver = PenpalAReceiver::get(); // Init Test @@ -577,8 +626,7 @@ fn reserve_transfer_native_asset_from_para_to_relay() { let amount_to_send: Balance = WESTEND_ED * 1000; let assets: Assets = (Parent, amount_to_send).into(); let asset_owner = PenpalAssetOwner::get(); - let relay_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let relay_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); // fund Parachain's sender account PenpalA::mint_foreign_asset( @@ -654,8 +702,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() { let assets: Assets = (Parent, amount_to_send).into(); // Init values for Parachain - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); let receiver = PenpalAReceiver::get(); // Init Test @@ -711,8 +758,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() { let sender = PenpalASender::get(); let amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000; let assets: Assets = (Parent, amount_to_send).into(); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); let asset_owner = PenpalAssetOwner::get(); // fund Parachain's sender account @@ -818,10 +864,9 @@ fn reserve_transfer_assets_from_system_para_to_para() { // Init values for Parachain let receiver = PenpalAReceiver::get(); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); let system_para_foreign_asset_location = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap(); // Init Test let para_test_args = TestContext { @@ -906,10 +951,9 @@ fn reserve_transfer_assets_from_para_to_system_para() { let penpal_asset_owner = PenpalAssetOwner::get(); let penpal_asset_owner_signer = ::RuntimeOrigin::signed(penpal_asset_owner); let asset_location_on_penpal = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap(); let asset_location_on_penpal_latest: Location = asset_location_on_penpal.try_into().unwrap(); - let system_asset_location_on_penpal = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_asset_location_on_penpal = v3::Location::try_from(RelayLocation::get()).unwrap(); let assets: Assets = vec![ (Parent, fee_amount_to_send).into(), (asset_location_on_penpal_latest, asset_amount_to_send).into(), @@ -940,10 +984,9 @@ fn reserve_transfer_assets_from_para_to_system_para() { let penpal_location_as_seen_by_ahr = AssetHubWestend::sibling_location_of(PenpalA::para_id()); let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of(penpal_location_as_seen_by_ahr); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); let system_para_foreign_asset_location = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap(); let ah_asset_owner = AssetHubWestendAssetOwner::get(); let ah_asset_owner_signer = ::RuntimeOrigin::signed(ah_asset_owner); @@ -1031,15 +1074,14 @@ fn reserve_transfer_assets_from_para_to_system_para() { /// Reserve Transfers of native asset from Parachain to Parachain (through Relay reserve) should /// work #[test] -fn reserve_transfer_native_asset_from_para_to_para_trough_relay() { +fn reserve_transfer_native_asset_from_para_to_para_through_relay() { // Init values for Parachain Origin let destination = PenpalA::sibling_location_of(PenpalB::para_id()); let sender = PenpalASender::get(); let amount_to_send: Balance = WESTEND_ED * 10000; let asset_owner = PenpalAssetOwner::get(); let assets = (Parent, amount_to_send).into(); - let relay_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let relay_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); let sender_as_seen_by_relay = Westend::child_location_of(PenpalA::para_id()); let sov_of_sender_on_relay = Westend::sovereign_account_id_of(sender_as_seen_by_relay); @@ -1076,9 +1118,9 @@ fn reserve_transfer_native_asset_from_para_to_para_trough_relay() { }); // Set assertions and dispatchables - test.set_assertion::(para_to_para_through_relay_sender_assertions); + test.set_assertion::(para_to_para_through_hop_sender_assertions); test.set_assertion::(para_to_para_relay_hop_assertions); - test.set_assertion::(para_to_para_through_relay_receiver_assertions); + test.set_assertion::(para_to_para_through_hop_receiver_assertions); test.set_dispatchable::(para_to_para_through_relay_limited_reserve_transfer_assets); test.assert(); diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs index aa673c03483a..8996893f0f12 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs @@ -111,8 +111,7 @@ fn swap_locally_on_chain_using_local_assets() { #[test] fn swap_locally_on_chain_using_foreign_assets() { - let asset_native = - Box::new(v3::Location::try_from(RelayLocation::get()).expect("conversion works")); + let asset_native = Box::new(v3::Location::try_from(RelayLocation::get()).unwrap()); let asset_location_on_penpal = v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion_works"); let foreign_asset_at_asset_hub_westend = diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs index ac518d2ed4a4..aee2c1b4001d 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs @@ -110,8 +110,7 @@ fn para_dest_assertions(t: RelayToSystemParaTest) { fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); let expected_asset_id = t.args.asset_id.unwrap(); let (_, expected_asset_amount) = non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); @@ -204,8 +203,7 @@ fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) { let (_, expected_asset_amount) = non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); let checking_account = ::PolkadotXcm::check_account(); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); PenpalA::assert_xcmp_queue_success(None); @@ -414,22 +412,23 @@ fn teleport_to_other_system_parachains_works() { ); } -/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets should work -/// (using native reserve-based transfer for fees) -#[test] -fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { +/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets while paying +/// fees using (reserve transferred) native asset. +pub fn do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt( + para_to_ah_dispatchable: fn(ParaToSystemParaTest) -> DispatchResult, + ah_to_para_dispatchable: fn(SystemParaToParaTest) -> DispatchResult, +) { // Init values for Parachain let fee_amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 100; let asset_location_on_penpal = - v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion works"); + v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).unwrap(); let asset_id_on_penpal = match asset_location_on_penpal.last() { Some(v3::Junction::GeneralIndex(id)) => *id as u32, _ => unreachable!(), }; let asset_amount_to_send = ASSET_HUB_WESTEND_ED * 100; let asset_owner = PenpalAssetOwner::get(); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap(); let sender = PenpalASender::get(); let penpal_check_account = ::PolkadotXcm::check_account(); let ah_as_seen_by_penpal = PenpalA::sibling_location_of(AssetHubWestend::para_id()); @@ -518,7 +517,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { penpal_to_ah.set_assertion::(penpal_to_ah_foreign_assets_sender_assertions); penpal_to_ah.set_assertion::(penpal_to_ah_foreign_assets_receiver_assertions); - penpal_to_ah.set_dispatchable::(para_to_system_para_transfer_assets); + penpal_to_ah.set_dispatchable::(para_to_ah_dispatchable); penpal_to_ah.assert(); let penpal_sender_balance_after = PenpalA::execute_with(|| { @@ -625,7 +624,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { ah_to_penpal.set_assertion::(ah_to_penpal_foreign_assets_sender_assertions); ah_to_penpal.set_assertion::(ah_to_penpal_foreign_assets_receiver_assertions); - ah_to_penpal.set_dispatchable::(system_para_to_para_transfer_assets); + ah_to_penpal.set_dispatchable::(ah_to_para_dispatchable); ah_to_penpal.assert(); let ah_sender_balance_after = ah_to_penpal.sender.balance; @@ -663,3 +662,13 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { // Receiver's balance is increased by exact amount assert_eq!(penpal_receiver_assets_after, penpal_receiver_assets_before + asset_amount_to_send); } + +/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets should work +/// (using native reserve-based transfer for fees) +#[test] +fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { + do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt( + para_to_system_para_transfer_assets, + system_para_to_para_transfer_assets, + ); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs index b5e19cf3fa3a..0415af580ef8 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs @@ -25,6 +25,7 @@ mod imports { prelude::{AccountId32 as AccountId32Junction, *}, v3::{self, NetworkId::Westend as WestendId}, }; + pub use xcm_executor::traits::TransferType; // Cumulus pub use emulated_integration_tests_common::{ @@ -46,7 +47,7 @@ mod imports { bridge_hub_rococo_emulated_chain::{ genesis::ED as BRIDGE_HUB_ROCOCO_ED, BridgeHubRococoParaPallet as BridgeHubRococoPallet, }, - penpal_emulated_chain::PenpalAParaPallet as PenpalAPallet, + penpal_emulated_chain::{PenpalAParaPallet as PenpalAPallet, PenpalAssetOwner}, rococo_emulated_chain::{genesis::ED as ROCOCO_ED, RococoRelayPallet as RococoPallet}, AssetHubRococoPara as AssetHubRococo, AssetHubRococoParaReceiver as AssetHubRococoReceiver, AssetHubRococoParaSender as AssetHubRococoSender, AssetHubWestendPara as AssetHubWestend, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs index 787a82ed32f7..314f02b868c6 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs @@ -31,6 +31,73 @@ fn send_asset_from_asset_hub_rococo_to_asset_hub_westend(id: Location, amount: u assert_bridge_hub_westend_message_received(); } +fn send_asset_from_penpal_rococo_through_local_asset_hub_to_westend_asset_hub( + id: Location, + transfer_amount: u128, +) { + let destination = asset_hub_westend_location(); + let local_asset_hub: Location = PenpalA::sibling_location_of(AssetHubRococo::para_id()); + let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of( + AssetHubRococo::sibling_location_of(PenpalA::para_id()), + ); + let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( + Westend, + AssetHubWestend::para_id(), + ); + + // fund the AHR's SA on BHR for paying bridge transport fees + BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128); + + // set XCM versions + PenpalA::force_xcm_version(local_asset_hub.clone(), XCM_VERSION); + AssetHubRococo::force_xcm_version(destination.clone(), XCM_VERSION); + BridgeHubRococo::force_xcm_version(bridge_hub_westend_location(), XCM_VERSION); + + // send message over bridge + assert_ok!(PenpalA::execute_with(|| { + let signed_origin = ::RuntimeOrigin::signed(PenpalASender::get()); + let beneficiary: Location = + AccountId32Junction { network: None, id: AssetHubWestendReceiver::get().into() }.into(); + let assets: Assets = (id.clone(), transfer_amount).into(); + let fees_id: AssetId = id.into(); + + ::PolkadotXcm::transfer_assets_using_type( + signed_origin, + bx!(destination.into()), + bx!(beneficiary.into()), + bx!(assets.clone().into()), + bx!(TransferType::RemoteReserve(local_asset_hub.clone().into())), + bx!(fees_id.into()), + bx!(TransferType::RemoteReserve(local_asset_hub.into())), + WeightLimit::Unlimited, + ) + })); + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubRococo, + vec![ + // Amount to reserve transfer is withdrawn from Penpal's sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_penpal_on_ahr.clone().into(), + amount: *amount == transfer_amount, + }, + // Amount deposited in AHW's sovereign account + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == sov_ahw_on_ahr.clone().into(), + }, + RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + ) => {}, + ] + ); + }); + assert_bridge_hub_rococo_message_accepted(true); + assert_bridge_hub_westend_message_received(); +} + #[test] fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() { let roc_at_asset_hub_rococo: v3::Location = v3::Parent.into(); @@ -45,7 +112,7 @@ fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() { vec![], ); let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( - NetworkId::Westend, + Westend, AssetHubWestend::para_id(), ); @@ -135,7 +202,7 @@ fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() { assert!(sender_rocs_before > sender_rocs_after); // Receiver's balance is increased assert!(receiver_rocs_after > receiver_rocs_before); - // Reserve balance is reduced by sent amount + // Reserve balance is increased by sent amount assert_eq!(rocs_in_reserve_on_ahr_after, rocs_in_reserve_on_ahr_before + amount); } @@ -144,7 +211,7 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() { let prefund_amount = 10_000_000_000_000u128; let wnd_at_asset_hub_rococo = v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Westend)]); - let owner: AccountId = AssetHubWestend::account_id_of(ALICE); + let owner: AccountId = AssetHubRococo::account_id_of(ALICE); AssetHubRococo::force_create_foreign_asset( wnd_at_asset_hub_rococo, owner, @@ -155,7 +222,7 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() { // fund the AHR's SA on AHW with the WND tokens held in reserve let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( - NetworkId::Rococo, + Rococo, AssetHubRococo::para_id(), ); AssetHubWestend::fund_accounts(vec![(sov_ahr_on_ahw.clone(), prefund_amount)]); @@ -217,3 +284,94 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() { // Reserve balance is reduced by sent amount assert_eq!(wnds_in_reserve_on_ahw_after, wnds_in_reserve_on_ahw_before - amount_to_send); } + +#[test] +fn send_rocs_from_penpal_rococo_through_asset_hub_rococo_to_asset_hub_westend() { + let roc_at_rococo_parachains: v3::Location = v3::Parent.into(); + let roc_at_asset_hub_westend = + v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Rococo)]); + let roc_at_rococo_parachains_latest: Location = roc_at_rococo_parachains.try_into().unwrap(); + let owner: AccountId = AssetHubWestend::account_id_of(ALICE); + AssetHubWestend::force_create_foreign_asset( + roc_at_asset_hub_westend, + owner, + true, + ASSET_MIN_BALANCE, + vec![], + ); + let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( + Westend, + AssetHubWestend::para_id(), + ); + + let amount = ASSET_HUB_ROCOCO_ED * 10_000_000; + let penpal_location = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_location); + // fund Penpal's sovereign account on AssetHub + AssetHubRococo::fund_accounts(vec![(sov_penpal_on_ahr.into(), amount * 2)]); + // fund Penpal's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + roc_at_rococo_parachains, + PenpalASender::get(), + amount * 2, + ); + + let rocs_in_reserve_on_ahr_before = + ::account_data_of(sov_ahw_on_ahr.clone()).free; + let sender_rocs_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + roc_at_rococo_parachains.into(), + &PenpalASender::get(), + ) + }); + let receiver_rocs_before = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(roc_at_asset_hub_westend, &AssetHubWestendReceiver::get()) + }); + send_asset_from_penpal_rococo_through_local_asset_hub_to_westend_asset_hub( + roc_at_rococo_parachains_latest, + amount, + ); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubWestend, + vec![ + // issue ROCs on AHW + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == roc_at_rococo_parachains, + owner: *owner == AssetHubWestendReceiver::get(), + }, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + let sender_rocs_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + roc_at_rococo_parachains.into(), + &PenpalASender::get(), + ) + }); + let receiver_rocs_after = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(roc_at_asset_hub_westend, &AssetHubWestendReceiver::get()) + }); + let rocs_in_reserve_on_ahr_after = + ::account_data_of(sov_ahw_on_ahr.clone()).free; + + // Sender's balance is reduced + assert!(sender_rocs_after < sender_rocs_before); + // Receiver's balance is increased + assert!(receiver_rocs_after > receiver_rocs_before); + // Reserve balance is increased by sent amount (less fess) + assert!(rocs_in_reserve_on_ahr_after > rocs_in_reserve_on_ahr_before); + assert!(rocs_in_reserve_on_ahr_after <= rocs_in_reserve_on_ahr_before + amount); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs index 780ba57f78a1..695b45708460 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs @@ -381,10 +381,8 @@ fn send_token_from_ethereum_to_penpal() { #[test] fn send_weth_asset_from_asset_hub_to_ethereum() { use asset_hub_rococo_runtime::xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee; - let assethub_sovereign = BridgeHubRococo::sovereign_account_id_of(Location::new( - 1, - [Parachain(AssetHubRococo::para_id().into())], - )); + let assethub_location = BridgeHubRococo::sibling_location_of(AssetHubRococo::para_id()); + let assethub_sovereign = BridgeHubRococo::sovereign_account_id_of(assethub_location); AssetHubRococo::force_default_xcm_version(Some(XCM_VERSION)); BridgeHubRococo::force_default_xcm_version(Some(XCM_VERSION)); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs index 60c31ce5a4ae..36b846e10313 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs @@ -26,6 +26,7 @@ mod imports { v3, v4::NetworkId::Rococo as RococoId, }; + pub use xcm_executor::traits::TransferType; // Cumulus pub use emulated_integration_tests_common::{ @@ -48,13 +49,15 @@ mod imports { genesis::ED as BRIDGE_HUB_WESTEND_ED, BridgeHubWestendParaPallet as BridgeHubWestendPallet, }, + penpal_emulated_chain::{PenpalAssetOwner, PenpalBParaPallet as PenpalBPallet}, westend_emulated_chain::WestendRelayPallet as WestendPallet, AssetHubRococoPara as AssetHubRococo, AssetHubRococoParaReceiver as AssetHubRococoReceiver, AssetHubRococoParaSender as AssetHubRococoSender, AssetHubWestendPara as AssetHubWestend, AssetHubWestendParaReceiver as AssetHubWestendReceiver, AssetHubWestendParaSender as AssetHubWestendSender, BridgeHubRococoPara as BridgeHubRococo, BridgeHubWestendPara as BridgeHubWestend, - BridgeHubWestendParaSender as BridgeHubWestendSender, WestendRelay as Westend, + BridgeHubWestendParaSender as BridgeHubWestendSender, PenpalBPara as PenpalB, + PenpalBParaSender as PenpalBSender, WestendRelay as Westend, }; pub const ASSET_MIN_BALANCE: u128 = 1000; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs index 5b0990973d21..f76a4224b90a 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs @@ -30,6 +30,73 @@ fn send_asset_from_asset_hub_westend_to_asset_hub_rococo(id: Location, amount: u assert_bridge_hub_rococo_message_received(); } +fn send_asset_from_penpal_westend_through_local_asset_hub_to_rococo_asset_hub( + id: Location, + transfer_amount: u128, +) { + let destination = asset_hub_rococo_location(); + let local_asset_hub: Location = PenpalB::sibling_location_of(AssetHubWestend::para_id()); + let sov_penpal_on_ahw = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalB::para_id()), + ); + let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( + Rococo, + AssetHubRococo::para_id(), + ); + + // fund the AHW's SA on BHW for paying bridge transport fees + BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), 10_000_000_000_000u128); + + // set XCM versions + PenpalB::force_xcm_version(local_asset_hub.clone(), XCM_VERSION); + AssetHubWestend::force_xcm_version(destination.clone(), XCM_VERSION); + BridgeHubWestend::force_xcm_version(bridge_hub_rococo_location(), XCM_VERSION); + + // send message over bridge + assert_ok!(PenpalB::execute_with(|| { + let signed_origin = ::RuntimeOrigin::signed(PenpalBSender::get()); + let beneficiary: Location = + AccountId32Junction { network: None, id: AssetHubRococoReceiver::get().into() }.into(); + let assets: Assets = (id.clone(), transfer_amount).into(); + let fees_id: AssetId = id.into(); + + ::PolkadotXcm::transfer_assets_using_type( + signed_origin, + bx!(destination.into()), + bx!(beneficiary.into()), + bx!(assets.into()), + bx!(TransferType::RemoteReserve(local_asset_hub.clone().into())), + bx!(fees_id.into()), + bx!(TransferType::RemoteReserve(local_asset_hub.into())), + WeightLimit::Unlimited, + ) + })); + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount to reserve transfer is withdrawn from Penpal's sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_penpal_on_ahw.clone().into(), + amount: *amount == transfer_amount, + }, + // Amount deposited in AHR's sovereign account + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == sov_ahr_on_ahw.clone().into(), + }, + RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + ) => {}, + ] + ); + }); + assert_bridge_hub_westend_message_accepted(true); + assert_bridge_hub_rococo_message_received(); +} + #[test] fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() { let wnd_at_asset_hub_westend: Location = Parent.into(); @@ -44,7 +111,7 @@ fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() { vec![], ); let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( - NetworkId::Rococo, + Rococo, AssetHubRococo::para_id(), ); @@ -153,7 +220,7 @@ fn send_rocs_from_asset_hub_westend_to_asset_hub_rococo() { // fund the AHW's SA on AHR with the ROC tokens held in reserve let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( - NetworkId::Westend, + Westend, AssetHubWestend::para_id(), ); AssetHubRococo::fund_accounts(vec![(sov_ahw_on_ahr.clone(), prefund_amount)]); @@ -215,3 +282,94 @@ fn send_rocs_from_asset_hub_westend_to_asset_hub_rococo() { // Reserve balance is reduced by sent amount assert_eq!(rocs_in_reserve_on_ahr_after, rocs_in_reserve_on_ahr_before - amount_to_send); } + +#[test] +fn send_wnds_from_penpal_westend_through_asset_hub_westend_to_asset_hub_rococo() { + let wnd_at_westend_parachains: v3::Location = v3::Parent.into(); + let wnd_at_asset_hub_rococo = + v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Westend)]); + let wnd_at_westend_parachains_latest: Location = wnd_at_westend_parachains.try_into().unwrap(); + let owner: AccountId = AssetHubRococo::account_id_of(ALICE); + AssetHubRococo::force_create_foreign_asset( + wnd_at_asset_hub_rococo, + owner, + true, + ASSET_MIN_BALANCE, + vec![], + ); + let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( + Rococo, + AssetHubRococo::para_id(), + ); + + let amount = ASSET_HUB_WESTEND_ED * 10_000_000; + let penpal_location = AssetHubWestend::sibling_location_of(PenpalB::para_id()); + let sov_penpal_on_ahw = AssetHubWestend::sovereign_account_id_of(penpal_location); + // fund Penpal's sovereign account on AssetHub + AssetHubWestend::fund_accounts(vec![(sov_penpal_on_ahw.into(), amount * 2)]); + // fund Penpal's sender account + PenpalB::mint_foreign_asset( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + wnd_at_westend_parachains, + PenpalBSender::get(), + amount * 2, + ); + + let wnds_in_reserve_on_ahw_before = + ::account_data_of(sov_ahr_on_ahw.clone()).free; + let sender_wnds_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + wnd_at_westend_parachains.into(), + &PenpalBSender::get(), + ) + }); + let receiver_wnds_before = AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(wnd_at_asset_hub_rococo, &AssetHubRococoReceiver::get()) + }); + send_asset_from_penpal_westend_through_local_asset_hub_to_rococo_asset_hub( + wnd_at_westend_parachains_latest, + amount, + ); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubRococo, + vec![ + // issue WNDs on AHR + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == wnd_at_westend_parachains, + owner: *owner == AssetHubRococoReceiver::get(), + }, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + let sender_wnds_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + wnd_at_westend_parachains.into(), + &PenpalBSender::get(), + ) + }); + let receiver_wnds_after = AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(wnd_at_asset_hub_rococo, &AssetHubRococoReceiver::get()) + }); + let wnds_in_reserve_on_ahw_after = + ::account_data_of(sov_ahr_on_ahw.clone()).free; + + // Sender's balance is reduced + assert!(sender_wnds_after < sender_wnds_before); + // Receiver's balance is increased + assert!(receiver_wnds_after > receiver_wnds_before); + // Reserve balance is increased by sent amount (less fess) + assert!(wnds_in_reserve_on_ahw_after > wnds_in_reserve_on_ahw_before); + assert!(wnds_in_reserve_on_ahw_after <= wnds_in_reserve_on_ahw_before + amount); +} diff --git a/cumulus/parachains/runtimes/assets/common/src/matching.rs b/cumulus/parachains/runtimes/assets/common/src/matching.rs index 478bba4565dc..3aad88e177ca 100644 --- a/cumulus/parachains/runtimes/assets/common/src/matching.rs +++ b/cumulus/parachains/runtimes/assets/common/src/matching.rs @@ -113,17 +113,14 @@ impl, Reserves: ContainsPair devolved, - Err(_) => { - log::trace!( - target: "xcm::contains", - "IsTrustedBridgedReserveLocationForConcreteAsset origin: {:?} is not remote to the universal_source: {:?}", - origin, universal_source - ); - return false - }, - }; + if ensure_is_remote(universal_source.clone(), origin.clone()).is_err() { + log::trace!( + target: "xcm::contains", + "IsTrustedBridgedReserveLocationForConcreteAsset origin: {:?} is not remote to the universal_source: {:?}", + origin, universal_source + ); + return false + } // check asset according to the configured reserve locations Reserves::contains(asset, origin) diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index c12372abbe90..a0a007234eb7 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -58,9 +58,16 @@ parameter_types! { pub const RelayLocation: Location = Location::parent(); // Local native currency which is stored in `pallet_balances`` pub const PenpalNativeCurrency: Location = Location::here(); - pub const RelayNetwork: Option = None; + // The Penpal runtime is utilized for testing with various environment setups. + // This storage item allows us to customize the `NetworkId` where Penpal is deployed. + // By default, it is set to `NetworkId::Rococo` and can be changed using `System::set_storage`. + pub storage RelayNetworkId: NetworkId = NetworkId::Westend; + pub RelayNetwork: Option = Some(RelayNetworkId::get()); pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); - pub UniversalLocation: InteriorLocation = [Parachain(ParachainInfo::parachain_id().into())].into(); + pub UniversalLocation: InteriorLocation = [ + GlobalConsensus(RelayNetworkId::get()), + Parachain(ParachainInfo::parachain_id().into()) + ].into(); pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating(); } diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index ef255068734a..cf22b86cf82c 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -1299,64 +1299,20 @@ pub mod pallet { ); ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); - let mut assets = assets.into_inner(); + let assets = assets.into_inner(); let fee_asset_item = fee_asset_item as usize; - let fees = assets.get(fee_asset_item as usize).ok_or(Error::::Empty)?.clone(); // Find transfer types for fee and non-fee assets. let (fees_transfer_type, assets_transfer_type) = Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?; - // local and remote XCM programs to potentially handle fees separately - let fees = if fees_transfer_type == assets_transfer_type { - // no need for custom fees instructions, fees are batched with assets - FeesHandling::Batched { fees } - } else { - // Disallow _remote reserves_ unless assets & fees have same remote reserve (covered - // by branch above). The reason for this is that we'd need to send XCMs to separate - // chains with no guarantee of delivery order on final destination; therefore we - // cannot guarantee to have fees in place on final destination chain to pay for - // assets transfer. - ensure!( - !matches!(assets_transfer_type, TransferType::RemoteReserve(_)), - Error::::InvalidAssetUnsupportedReserve - ); - let weight_limit = weight_limit.clone(); - // remove `fees` from `assets` and build separate fees transfer instructions to be - // added to assets transfers XCM programs - let fees = assets.remove(fee_asset_item); - let (local_xcm, remote_xcm) = match fees_transfer_type { - TransferType::LocalReserve => Self::local_reserve_fees_instructions( - origin.clone(), - dest.clone(), - fees, - weight_limit, - )?, - TransferType::DestinationReserve => - Self::destination_reserve_fees_instructions( - origin.clone(), - dest.clone(), - fees, - weight_limit, - )?, - TransferType::Teleport => Self::teleport_fees_instructions( - origin.clone(), - dest.clone(), - fees, - weight_limit, - )?, - TransferType::RemoteReserve(_) => - return Err(Error::::InvalidAssetUnsupportedReserve.into()), - }; - FeesHandling::Separate { local_xcm, remote_xcm } - }; - - Self::build_and_execute_xcm_transfer_type( + Self::do_transfer_assets( origin, dest, beneficiary, assets, assets_transfer_type, - fees, + fee_asset_item, + fees_transfer_type, weight_limit, ) } @@ -1443,6 +1399,87 @@ pub mod pallet { >::send_blob(origin, dest, encoded_message)?; Ok(()) } + + /// Transfer assets from the local chain to the destination chain using explicit transfer + /// types for assets and fees. + /// + /// `assets` must have same reserve location or may be teleportable to `dest`. Caller must + /// provide the `assets_transfer_type` to be used for `assets`: + /// - `TransferType::LocalReserve`: transfer assets to sovereign account of destination + /// chain and forward a notification XCM to `dest` to mint and deposit reserve-based + /// assets to `beneficiary`. + /// - `TransferType::DestinationReserve`: burn local assets and forward a notification to + /// `dest` chain to withdraw the reserve assets from this chain's sovereign account and + /// deposit them to `beneficiary`. + /// - `TransferType::RemoteReserve(reserve)`: burn local assets, forward XCM to `reserve` + /// chain to move reserves from this chain's SA to `dest` chain's SA, and forward another + /// XCM to `dest` to mint and deposit reserve-based assets to `beneficiary`. Typically + /// the remote `reserve` is Asset Hub. + /// - `TransferType::Teleport`: burn local assets and forward XCM to `dest` chain to + /// mint/teleport assets and deposit them to `beneficiary`. + /// + /// Fee payment on the source, destination and all intermediary hops, is specified through + /// `fees_id`, but make sure enough of the specified `fees_id` asset is included in the + /// given list of `assets`. `fees_id` should be enough to pay for `weight_limit`. If more + /// weight is needed than `weight_limit`, then the operation will fail and the sent assets + /// may be at risk. + /// + /// `fees_id` may use different transfer type than rest of `assets` and can be specified + /// through `fees_transfer_type`. + /// + /// - `origin`: Must be capable of withdrawing the `assets` and executing XCM. + /// - `dest`: Destination context for the assets. Will typically be `[Parent, + /// Parachain(..)]` to send from parachain to parachain, or `[Parachain(..)]` to send from + /// relay to parachain, or `(parents: 2, (GlobalConsensus(..), ..))` to send from + /// parachain across a bridge to another ecosystem destination. + /// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will + /// generally be an `AccountId32` value. + /// - `assets`: The assets to be withdrawn. This should include the assets used to pay the + /// fee on the `dest` (and possibly reserve) chains. + /// - `assets_transfer_type`: The XCM `TransferType` used to transfer the `assets`. + /// - `fees_id`: One of the included `assets` to be be used to pay fees. + /// - `fees_transfer_type`: The XCM `TransferType` used to transfer the `fees` assets. + /// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase. + #[pallet::call_index(15)] + #[pallet::weight(T::WeightInfo::transfer_assets())] + pub fn transfer_assets_using_type( + origin: OriginFor, + dest: Box, + beneficiary: Box, + assets: Box, + assets_transfer_type: Box, + fees_id: Box, + fees_transfer_type: Box, + weight_limit: WeightLimit, + ) -> DispatchResult { + let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; + let dest: Location = (*dest).try_into().map_err(|()| Error::::BadVersion)?; + let beneficiary: Location = + (*beneficiary).try_into().map_err(|()| Error::::BadVersion)?; + let assets: Assets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; + let fees_id: AssetId = (*fees_id).try_into().map_err(|()| Error::::BadVersion)?; + log::debug!( + target: "xcm::pallet_xcm::transfer_assets_using_type", + "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?} through {:?}, fees-id {:?} through {:?}", + origin_location, dest, beneficiary, assets, assets_transfer_type, fees_id, fees_transfer_type, + ); + + let assets = assets.into_inner(); + ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); + + let fee_asset_index = + assets.iter().position(|a| a.id == fees_id).ok_or(Error::::FeesNotMet)?; + Self::do_transfer_assets( + origin_location, + dest, + beneficiary, + assets, + *assets_transfer_type, + fee_asset_index, + *fees_transfer_type, + weight_limit, + ) + } } } @@ -1607,15 +1644,16 @@ impl Pallet { // Ensure all assets (including fees) have same reserve location. ensure!(assets_transfer_type == fees_transfer_type, Error::::TooManyReserves); - Self::build_and_execute_xcm_transfer_type( - origin, - dest, + let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type( + origin.clone(), + dest.clone(), beneficiary, assets, assets_transfer_type, FeesHandling::Batched { fees }, weight_limit, - ) + )?; + Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm) } fn do_teleport_assets( @@ -1648,18 +1686,85 @@ impl Pallet { } let fees = assets.get(fee_asset_item as usize).ok_or(Error::::Empty)?.clone(); - Self::build_and_execute_xcm_transfer_type( - origin_location, - dest, + let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type( + origin_location.clone(), + dest.clone(), beneficiary, assets, TransferType::Teleport, FeesHandling::Batched { fees }, weight_limit, - ) + )?; + Self::execute_xcm_transfer(origin_location, dest, local_xcm, remote_xcm) + } + + fn do_transfer_assets( + origin: Location, + dest: Location, + beneficiary: Location, + mut assets: Vec, + assets_transfer_type: TransferType, + fee_asset_index: usize, + fees_transfer_type: TransferType, + weight_limit: WeightLimit, + ) -> DispatchResult { + // local and remote XCM programs to potentially handle fees separately + let fees = if fees_transfer_type == assets_transfer_type { + let fees = assets.get(fee_asset_index).ok_or(Error::::Empty)?.clone(); + // no need for custom fees instructions, fees are batched with assets + FeesHandling::Batched { fees } + } else { + // Disallow _remote reserves_ unless assets & fees have same remote reserve (covered + // by branch above). The reason for this is that we'd need to send XCMs to separate + // chains with no guarantee of delivery order on final destination; therefore we + // cannot guarantee to have fees in place on final destination chain to pay for + // assets transfer. + ensure!( + !matches!(assets_transfer_type, TransferType::RemoteReserve(_)), + Error::::InvalidAssetUnsupportedReserve + ); + let weight_limit = weight_limit.clone(); + // remove `fees` from `assets` and build separate fees transfer instructions to be + // added to assets transfers XCM programs + let fees = assets.remove(fee_asset_index); + let (local_xcm, remote_xcm) = match fees_transfer_type { + TransferType::LocalReserve => Self::local_reserve_fees_instructions( + origin.clone(), + dest.clone(), + fees, + weight_limit, + )?, + TransferType::DestinationReserve => Self::destination_reserve_fees_instructions( + origin.clone(), + dest.clone(), + fees, + weight_limit, + )?, + TransferType::Teleport => Self::teleport_fees_instructions( + origin.clone(), + dest.clone(), + fees, + weight_limit, + )?, + TransferType::RemoteReserve(_) => + return Err(Error::::InvalidAssetUnsupportedReserve.into()), + }; + FeesHandling::Separate { local_xcm, remote_xcm } + }; + + let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type( + origin.clone(), + dest.clone(), + beneficiary, + assets, + assets_transfer_type, + fees, + weight_limit, + )?; + Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm) } - fn build_and_execute_xcm_transfer_type( + fn build_xcm_transfer_type( origin: Location, dest: Location, beneficiary: Location, @@ -1667,14 +1772,14 @@ impl Pallet { transfer_type: TransferType, fees: FeesHandling, weight_limit: WeightLimit, - ) -> DispatchResult { + ) -> Result<(Xcm<::RuntimeCall>, Option>), Error> { log::debug!( - target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type", + target: "xcm::pallet_xcm::build_xcm_transfer_type", "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, transfer_type {:?}, \ fees_handling {:?}, weight_limit: {:?}", origin, dest, beneficiary, assets, transfer_type, fees, weight_limit, ); - let (mut local_xcm, remote_xcm) = match transfer_type { + Ok(match transfer_type { TransferType::LocalReserve => { let (local, remote) = Self::local_reserve_transfer_programs( origin.clone(), @@ -1704,7 +1809,7 @@ impl Pallet { }; let local = Self::remote_reserve_transfer_program( origin.clone(), - reserve, + reserve.try_into().map_err(|()| Error::::BadVersion)?, dest.clone(), beneficiary, assets, @@ -1724,7 +1829,21 @@ impl Pallet { )?; (local, Some(remote)) }, - }; + }) + } + + fn execute_xcm_transfer( + origin: Location, + dest: Location, + mut local_xcm: Xcm<::RuntimeCall>, + remote_xcm: Option>, + ) -> DispatchResult { + log::debug!( + target: "xcm::pallet_xcm::execute_xcm_transfer", + "origin {:?}, dest {:?}, local_xcm {:?}, remote_xcm {:?}", + origin, dest, local_xcm, remote_xcm, + ); + let weight = T::Weigher::weight(&mut local_xcm).map_err(|()| Error::::UnweighableMessage)?; let mut hash = local_xcm.using_encoded(sp_io::hashing::blake2_256); @@ -1738,7 +1857,7 @@ impl Pallet { Self::deposit_event(Event::Attempted { outcome: outcome.clone() }); outcome.ensure_complete().map_err(|error| { log::error!( - target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type", + target: "xcm::pallet_xcm::execute_xcm_transfer", "XCM execution failed with error {:?}", error ); Error::::LocalExecutionIncomplete @@ -1750,7 +1869,7 @@ impl Pallet { if origin != Here.into_location() { Self::charge_fees(origin.clone(), price).map_err(|error| { log::error!( - target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type", + target: "xcm::pallet_xcm::execute_xcm_transfer", "Unable to charge fee with error {:?}", error ); Error::::FeesNotMet @@ -1935,6 +2054,7 @@ impl Pallet { ]); // handle fees Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?; + // deposit all remaining assets in holding to `beneficiary` location xcm_on_dest .inner_mut() diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 81b81fe6a171..e673a46c4ac6 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -347,6 +347,9 @@ impl XcmExecutor { msg: Xcm<()>, reason: FeeReason, ) -> Result { + log::trace!( + target: "xcm::send", "Sending msg: {msg:?}, to destination: {dest:?}, (reason: {reason:?})" + ); let (ticket, fee) = validate_send::(dest, msg)?; self.take_fee(fee, reason)?; Config::XcmSender::deliver(ticket).map_err(Into::into) diff --git a/polkadot/xcm/xcm-executor/src/traits/asset_transfer.rs b/polkadot/xcm/xcm-executor/src/traits/asset_transfer.rs index 5da3d1da37c8..6d72eaf680fd 100644 --- a/polkadot/xcm/xcm-executor/src/traits/asset_transfer.rs +++ b/polkadot/xcm/xcm-executor/src/traits/asset_transfer.rs @@ -30,7 +30,7 @@ pub enum Error { } /// Specify which type of asset transfer is required for a particular `(asset, dest)` combination. -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo)] pub enum TransferType { /// should teleport `asset` to `dest` Teleport, @@ -39,7 +39,7 @@ pub enum TransferType { /// should reserve-transfer `asset` to `dest`, using `dest` as reserve DestinationReserve, /// should reserve-transfer `asset` to `dest`, using remote chain `Location` as reserve - RemoteReserve(Location), + RemoteReserve(VersionedLocation), } /// A trait for identifying asset transfer type based on `IsTeleporter` and `IsReserve` @@ -77,7 +77,7 @@ pub trait XcmAssetTransfers { Ok(TransferType::LocalReserve) } else if Self::IsReserve::contains(asset, &asset_location) { // remote location that is recognized as reserve location for asset - Ok(TransferType::RemoteReserve(asset_location)) + Ok(TransferType::RemoteReserve(asset_location.into())) } else { // remote location that is not configured either as teleporter or reserve => cannot // determine asset reserve diff --git a/prdoc/pr_3695.prdoc b/prdoc/pr_3695.prdoc new file mode 100644 index 000000000000..2c2c2b2e6917 --- /dev/null +++ b/prdoc/pr_3695.prdoc @@ -0,0 +1,38 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "pallet-xcm: add new extrinsic for asset transfers using explicit reserve" + +doc: + - audience: Runtime User + description: | + pallet-xcm has a new extrinsic `transfer_assets_using_type` for transferring + assets from local chain to destination chain using an explicit XCM transfer + types for transferring the assets and the fees: + - `TransferType::LocalReserve`: transfer assets to sovereign account of destination + chain and forward a notification XCM to `dest` to mint and deposit reserve-based + assets to `beneficiary`. + - `TransferType::DestinationReserve`: burn local assets and forward a notification to + `dest` chain to withdraw the reserve assets from this chain's sovereign account and + deposit them to `beneficiary`. + - `TransferType::RemoteReserve(reserve)`: burn local assets, forward XCM to `reserve` + chain to move reserves from this chain's SA to `dest` chain's SA, and forward another + XCM to `dest` to mint and deposit reserve-based assets to `beneficiary`. Typically + the remote `reserve` is Asset Hub. + - `TransferType::Teleport`: burn local assets and forward XCM to `dest` chain to + mint/teleport assets and deposit them to `beneficiary`. + By default, an asset's reserve is its origin chain. But sometimes we may want to + explicitly use another chain as reserve (as long as allowed by runtime IsReserve + filter). + This is very helpful for transferring assets with multiple configured reserves + (such as Asset Hub ForeignAssets), when the transfer strictly depends on the used + reserve location. + + E.g. For transferring a bridged Foreign Assets between local parachains, Asset Hub + or the parachain that bridged the asset over must be used as the reserve location. + Same when transferring bridged assets back across the bridge, the local bridging + parachain must be used as the explicit reserve location. + +crates: +- name: pallet-xcm + bump: minor