From 4c9f71ef23742b5c39944e520146a729ffdc9e03 Mon Sep 17 00:00:00 2001 From: Karol Kokoszka Date: Fri, 11 Jul 2025 12:38:51 +0200 Subject: [PATCH 01/10] InitiateTransfer with Transact (PenpalA -> AH -> PenpalA) --- .../assets/asset-hub-westend/src/tests/mod.rs | 12 ++ .../asset-hub-westend/src/tests/transact.rs | 190 +++++++++++++++++- 2 files changed, 201 insertions(+), 1 deletion(-) 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 f46dc66d24129..ea46f8b14937e 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 @@ -41,6 +41,18 @@ macro_rules! foreign_balance_on { }; } +#[macro_export] +macro_rules! assets_balance_on { + ( $chain:ident, $id:expr, $who:expr ) => { + emulated_integration_tests_common::impls::paste::paste! { + <$chain>::execute_with(|| { + type Assets = <$chain as [<$chain Pallet>]>::Assets; + >::balance($id, $who) + }) + } + }; +} + #[macro_export] macro_rules! create_pool_with_wnd_on { // default amounts diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs index ad886dbe5e24d..ddcde9823fa9d 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{create_pool_with_wnd_on, foreign_balance_on, imports::*}; +use crate::{assets_balance_on, create_pool_with_wnd_on, foreign_balance_on, imports::*}; use frame_support::traits::tokens::fungibles::Mutate; use xcm_builder::{DescribeAllTerminal, DescribeFamily, HashedDescription}; use xcm_executor::traits::ConvertLocation; @@ -201,6 +201,194 @@ fn transact_from_para_to_para_through_asset_hub() { assert!(receiver_assets_after > receiver_assets_before); } +#[test] +fn transact_from_para_to_asset_hub_and_back_to_para() { + let sender = PenpalASender::get(); + let sov_of_sender_on_asset_hub = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalA::para_id()), + ); + let wnd_from_parachain_pov: Location = RelayLocation::get(); + let usdt_asset_hub_pov = + Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())]); + let usdt_penpal_pov = PenpalUsdtFromAssetHub::get(); + let amount_of_wnd_to_transfer_to_ah = WESTEND_ED * 1_000_000_000; + let amount_of_usdt_we_want_from_exchange = 1_000_000_000; + let sender_as_seen_from_ah = Location::new( + 1, + [ + Parachain(2000), + AccountId32 { + network: Some(NetworkId::ByGenesis(ROCOCO_GENESIS_HASH)), + id: sender.clone().into(), + }, + ], + ); + + // SA-of-Penpal-on-AHW should contain WND amount equal at least the amount that will be + // transferred-in to AH Since AH is the reserve for WND + AssetHubWestend::fund_accounts(vec![( + sov_of_sender_on_asset_hub.clone().into(), + ASSET_HUB_WESTEND_ED + amount_of_wnd_to_transfer_to_ah, + )]); + // Give the sender enough WND + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + wnd_from_parachain_pov.clone(), + sender.clone(), + amount_of_wnd_to_transfer_to_ah, + ); + + // We create a pool between WND and USDT in AssetHub so we can do the exchange + create_pool_with_wnd_on!( + AssetHubWestend, + usdt_asset_hub_pov.clone(), + false, + AssetHubWestendSender::get(), + 1_000_000_000_000, + 20_000_000_000 + ); + + // We add auhtorized alias on AH so sender from Penpal can AliasOrigin into itself on AH + // (instead of aliasing into Sovereign Account of sender) + AssetHubWestend::execute_with(|| { + assert_ok!(::PolkadotXcm::add_authorized_alias( + ::RuntimeOrigin::signed(sender.clone()), + Box::new(sender_as_seen_from_ah.into()), + None + )); + }); + + // Query initial balances + let sender_usdt_on_penpal_before = + foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender); + let sender_usdt_on_ah_before = assets_balance_on!(AssetHubWestend, USDT_ID.clone(), &sender); + + // Encoded `swap_tokens_for_exact_tokens` call to be executed in AH + let call = ::RuntimeCall::AssetConversion( + pallet_asset_conversion::Call::swap_tokens_for_exact_tokens { + path: vec![ + Box::new(wnd_from_parachain_pov.clone()), + Box::new(usdt_asset_hub_pov.clone()), + ], + amount_out: amount_of_usdt_we_want_from_exchange, + amount_in_max: 1_000_000_000_000, + send_to: sender.clone(), + keep_alive: true, + }, + ) + .encode() + .into(); + + let asset_hub_location_penpal_pov = PenpalA::sibling_location_of(AssetHubWestend::para_id()); + let penpal_location_ah_pov = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + + PenpalA::execute_with(|| { + let sender_signed_origin = ::RuntimeOrigin::signed(sender.clone()); + + let local_fees_amount = 80_000_000_000_000u128; + let remote_fees_amount = 90_000_000_000_000u128; + + let penpal_local_fees: Asset = (wnd_from_parachain_pov.clone(), local_fees_amount).into(); + let ah_remote_fees: Asset = (wnd_from_parachain_pov.clone(), remote_fees_amount).into(); + let penpal_remote_fees: Asset = (wnd_from_parachain_pov.clone(), remote_fees_amount).into(); + let wnd_to_withdraw: Asset = + (wnd_from_parachain_pov.clone(), amount_of_wnd_to_transfer_to_ah).into(); + + // xcm to be executed by penpal, sent by ah + let xcm_back_on_penpal = Xcm(vec![ + RefundSurplus, + DepositAsset { assets: Wild(All), beneficiary: sender.clone().into() }, + ]); + // xcm to be executed by ah, sent by penpal + let xcm_on_ah = Xcm(vec![ + AliasOrigin(Location::new( + 0, + [AccountId32 { network: None, id: sender.clone().into() }], + )), + Transact { origin_kind: OriginKind::SovereignAccount, call, fallback_max_weight: None }, + ExpectTransactStatus(MaybeErrorCode::Success), + WithdrawAsset((usdt_asset_hub_pov.clone(), 1_000_000_000).into()), + InitiateTransfer { + destination: penpal_location_ah_pov, + remote_fees: Some(AssetTransferFilter::ReserveDeposit( + penpal_remote_fees.clone().into(), + )), + preserve_origin: true, + assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveDeposit(Wild( + All, + ))]), + remote_xcm: xcm_back_on_penpal, + }, + ]); + // xcm to be executed locally on penpal as starting point + let xcm = Xcm::<()>(vec![ + WithdrawAsset(wnd_to_withdraw.into()), + PayFees { asset: penpal_local_fees }, + InitiateTransfer { + destination: asset_hub_location_penpal_pov, + remote_fees: Some(AssetTransferFilter::ReserveWithdraw( + ah_remote_fees.clone().into(), + )), + preserve_origin: true, + assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveWithdraw( + Wild(All), + )]), + remote_xcm: xcm_on_ah, + }, + ]); + // initiate transaction + ::PolkadotXcm::execute( + sender_signed_origin, + bx!(xcm::VersionedXcm::from(xcm.into())), + Weight::MAX, + ) + .unwrap(); + + // verify expected events; + PenpalA::assert_xcm_pallet_attempted_complete(None); + }); + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + RuntimeEvent::AssetConversion( + pallet_asset_conversion::Event::SwapExecuted { amount_out, ..} + ) => { amount_out: *amount_out == amount_of_usdt_we_want_from_exchange, }, + ] + ); + }); + + PenpalA::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + // Query final balances + let sender_usdt_on_ah_after = assets_balance_on!(AssetHubWestend, USDT_ID.clone(), &sender); + let sender_usdt_on_penpal_after = + foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender); + + // Receiver's balance is increased by usdt amount we got from exchange + assert_eq!( + sender_usdt_on_penpal_after, + sender_usdt_on_penpal_before + amount_of_usdt_we_want_from_exchange + ); + // Usdt amount on senders account AH side should stay the same i.e. all usdt came from exchange + // not free balance + assert_eq!(sender_usdt_on_ah_before, sender_usdt_on_ah_after); +} + fn asset_hub_hop_assertions(sender_sa: AccountId) { type RuntimeEvent = ::RuntimeEvent; assert_expected_events!( From 123d8ec4333fecddb887326198ba8048c9116155 Mon Sep 17 00:00:00 2001 From: Karol Kokoszka Date: Fri, 11 Jul 2025 17:38:30 +0200 Subject: [PATCH 02/10] InitiateTransfer with ExchangeAssets (PenpalA -> AH -> PenpalA) --- .../asset-hub-westend/src/tests/transact.rs | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs index ddcde9823fa9d..29caa34756766 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs @@ -389,6 +389,156 @@ fn transact_from_para_to_asset_hub_and_back_to_para() { assert_eq!(sender_usdt_on_ah_before, sender_usdt_on_ah_after); } +#[test] +fn transact_from_para_to_asset_hub_and_back_to_para_using_exchange_asset() { + let sender = PenpalASender::get(); + let sov_of_sender_on_asset_hub = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalA::para_id()), + ); + let wnd_from_parachain_pov: Location = RelayLocation::get(); + let usdt_asset_hub_pov = + Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())]); + let usdt_penpal_pov = PenpalUsdtFromAssetHub::get(); + let amount_of_wnd_to_transfer_to_ah = WESTEND_ED * 1_000_000_000; + let amount_of_usdt_we_want_from_exchange = 1_000_000_000; + let sender_as_seen_from_ah = Location::new( + 1, + [ + Parachain(2000), + AccountId32 { + network: Some(NetworkId::ByGenesis(ROCOCO_GENESIS_HASH)), + id: sender.clone().into(), + }, + ], + ); + + // SA-of-Penpal-on-AHW should contain WND amount equal at least the amount that will be + // transferred-in to AH Since AH is the reserve for WND + AssetHubWestend::fund_accounts(vec![( + sov_of_sender_on_asset_hub.clone().into(), + ASSET_HUB_WESTEND_ED + amount_of_wnd_to_transfer_to_ah, + )]); + // Give the sender enough WND + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + wnd_from_parachain_pov.clone(), + sender.clone(), + amount_of_wnd_to_transfer_to_ah, + ); + + // We create a pool between WND and USDT in AssetHub so we can do the exchange + create_pool_with_wnd_on!( + AssetHubWestend, + usdt_asset_hub_pov.clone(), + false, + AssetHubWestendSender::get(), + 1_000_000_000_000, + 20_000_000_000 + ); + + // We add auhtorized alias on AH so sender from Penpal can AliasOrigin into itself on AH + // (instead of aliasing into Sovereign Account of sender) + AssetHubWestend::execute_with(|| { + assert_ok!(::PolkadotXcm::add_authorized_alias( + ::RuntimeOrigin::signed(sender.clone()), + Box::new(sender_as_seen_from_ah.into()), + None + )); + }); + + // Query initial balances + let sender_usdt_on_penpal_before = + foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender); + let sender_usdt_on_ah_before = assets_balance_on!(AssetHubWestend, USDT_ID.clone(), &sender); + + + let asset_hub_location_penpal_pov = PenpalA::sibling_location_of(AssetHubWestend::para_id()); + let penpal_location_ah_pov = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + + PenpalA::execute_with(|| { + let sender_signed_origin = ::RuntimeOrigin::signed(sender.clone()); + + let local_fees_amount = 80_000_000_000_000u128; + let remote_fees_amount = 200_000_000_000_000u128; + + let penpal_local_fees: Asset = (wnd_from_parachain_pov.clone(), local_fees_amount).into(); + let ah_remote_fees: Asset = (wnd_from_parachain_pov.clone(), remote_fees_amount).into(); + let penpal_remote_fees: Asset = (wnd_from_parachain_pov.clone(), remote_fees_amount).into(); + let wnd_to_withdraw: Asset = + (wnd_from_parachain_pov.clone(), amount_of_wnd_to_transfer_to_ah).into(); + + // xcm to be executed by penpal, sent by ah + let xcm_back_on_penpal = Xcm(vec![ + RefundSurplus, + DepositAsset { assets: Wild(All), beneficiary: sender.clone().into() }, + ]); + // xcm to be executed by ah, sent by penpal + let xcm_on_ah = Xcm(vec![ + ExchangeAsset { + give: Definite((wnd_from_parachain_pov.clone(), 100_000_000_000u128).into()), + want: (usdt_asset_hub_pov.clone(), amount_of_usdt_we_want_from_exchange).into(), + maximal: false, + }, + InitiateTransfer { + destination: penpal_location_ah_pov, + remote_fees: Some(AssetTransferFilter::ReserveDeposit( + penpal_remote_fees.clone().into(), + )), + preserve_origin: true, + assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveDeposit(Wild( + All, + ))]), + remote_xcm: xcm_back_on_penpal, + }, + ]); + // xcm to be executed locally on penpal as starting point + let xcm = Xcm::<()>(vec![ + WithdrawAsset(wnd_to_withdraw.into()), + PayFees { asset: penpal_local_fees }, + InitiateTransfer { + destination: asset_hub_location_penpal_pov, + remote_fees: Some(AssetTransferFilter::ReserveWithdraw( + ah_remote_fees.clone().into(), + )), + preserve_origin: false, + assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveWithdraw( + Wild(All), + )]), + remote_xcm: xcm_on_ah, + }, + ]); + // initiate transaction + ::PolkadotXcm::execute( + sender_signed_origin, + bx!(xcm::VersionedXcm::from(xcm.into())), + Weight::MAX, + ) + .unwrap(); + + // verify expected events; + PenpalA::assert_xcm_pallet_attempted_complete(None); + }); + AssetHubWestend::execute_with(|| { + }); + + PenpalA::execute_with(|| { + }); + + // Query final balances + let sender_usdt_on_ah_after = assets_balance_on!(AssetHubWestend, USDT_ID.clone(), &sender); + let sender_usdt_on_penpal_after = + foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender); + + // Receiver's balance is increased by usdt amount we got from exchange + assert_eq!( + sender_usdt_on_penpal_after, + sender_usdt_on_penpal_before + amount_of_usdt_we_want_from_exchange + ); + // Usdt amount on senders account AH side should stay the same i.e. all usdt came from exchange + // not free balance + assert_eq!(sender_usdt_on_ah_before, sender_usdt_on_ah_after); +} + fn asset_hub_hop_assertions(sender_sa: AccountId) { type RuntimeEvent = ::RuntimeEvent; assert_expected_events!( From 26ca549ed71ce8a97362b46efbb01e311628b012 Mon Sep 17 00:00:00 2001 From: Karol Kokoszka Date: Mon, 14 Jul 2025 15:56:00 +0200 Subject: [PATCH 03/10] Improvements and cleanup --- .../src/tests/exchange_asset.rs | 156 ++++++++++++++++- .../assets/asset-hub-westend/src/tests/mod.rs | 4 +- .../asset-hub-westend/src/tests/transact.rs | 160 +----------------- polkadot/xcm/xcm-executor/src/lib.rs | 4 + 4 files changed, 169 insertions(+), 155 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/exchange_asset.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/exchange_asset.rs index 695ecff9f63a3..0a0e029eb3bc4 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/exchange_asset.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/exchange_asset.rs @@ -14,7 +14,7 @@ // limitations under the License. use crate::{ - create_pool_with_wnd_on, + assets_balance_on, create_pool_with_wnd_on, foreign_balance_on, imports::{ asset_hub_westend_runtime::{ExistentialDeposit, Runtime}, *, @@ -156,3 +156,157 @@ fn test_exchange_asset( } }); } + +#[test] +fn exchange_asset_from_penpal_via_asset_hub_back_to_penpal() { + let sender = PenpalASender::get(); + let sov_of_sender_on_asset_hub = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalA::para_id()), + ); + let wnd_from_parachain_pov: Location = RelayLocation::get(); + let usdt_asset_hub_pov = + Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())]); + let usdt_penpal_pov = PenpalUsdtFromAssetHub::get(); + let amount_of_wnd_to_transfer_to_ah = WESTEND_ED * 1_000_000_000; + let amount_of_usdt_we_want_from_exchange = 1_000_000_000; + + // SA-of-Penpal-on-AHW should contain WND amount equal at least the amount that will be + // transferred-in to AH Since AH is the reserve for WND + AssetHubWestend::fund_accounts(vec![( + sov_of_sender_on_asset_hub.clone().into(), + ASSET_HUB_WESTEND_ED + amount_of_wnd_to_transfer_to_ah, + )]); + // Give the sender enough WND + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + wnd_from_parachain_pov.clone(), + sender.clone(), + amount_of_wnd_to_transfer_to_ah, + ); + + // We create a pool between WND and USDT in AssetHub so we can do the exchange + create_pool_with_wnd_on!( + AssetHubWestend, + usdt_asset_hub_pov.clone(), + false, + AssetHubWestendSender::get(), + 1_000_000_000_000, + 20_000_000_000 + ); + + // Query initial balances + let sender_usdt_on_penpal_before = + foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender); + let sender_usdt_on_ah_before = assets_balance_on!(AssetHubWestend, USDT_ID.clone(), &sender); + + let asset_hub_location_penpal_pov = PenpalA::sibling_location_of(AssetHubWestend::para_id()); + let penpal_location_ah_pov = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + + PenpalA::execute_with(|| { + let sender_signed_origin = ::RuntimeOrigin::signed(sender.clone()); + + let local_fees_amount = 80_000_000_000_000u128; + let remote_fees_amount = 200_000_000_000_000u128; + + let penpal_local_fees: Asset = (wnd_from_parachain_pov.clone(), local_fees_amount).into(); + let ah_remote_fees: Asset = (wnd_from_parachain_pov.clone(), remote_fees_amount).into(); + let penpal_remote_fees: Asset = (wnd_from_parachain_pov.clone(), remote_fees_amount).into(); + let wnd_to_withdraw: Asset = + (wnd_from_parachain_pov.clone(), amount_of_wnd_to_transfer_to_ah).into(); + + // xcm to be executed by penpal, sent by ah + let xcm_back_on_penpal = Xcm(vec![ + RefundSurplus, + DepositAsset { assets: Wild(All), beneficiary: sender.clone().into() }, + ]); + // xcm to be executed by ah, sent by penpal + let xcm_on_ah = Xcm(vec![ + ExchangeAsset { + give: Definite((wnd_from_parachain_pov.clone(), 100_000_000_000u128).into()), + want: (usdt_asset_hub_pov.clone(), amount_of_usdt_we_want_from_exchange).into(), + maximal: false, + }, + InitiateTransfer { + destination: penpal_location_ah_pov, + remote_fees: Some(AssetTransferFilter::ReserveDeposit( + penpal_remote_fees.clone().into(), + )), + preserve_origin: false, + assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveDeposit(Wild( + All, + ))]), + remote_xcm: xcm_back_on_penpal, + }, + RefundSurplus, + DepositAsset { assets: Wild(All), beneficiary: sender.clone().into() }, + ]); + // xcm to be executed locally on penpal as starting point + let xcm = Xcm::<()>(vec![ + WithdrawAsset(wnd_to_withdraw.into()), + PayFees { asset: penpal_local_fees }, + InitiateTransfer { + destination: asset_hub_location_penpal_pov, + remote_fees: Some(AssetTransferFilter::ReserveWithdraw( + ah_remote_fees.clone().into(), + )), + preserve_origin: false, + assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveWithdraw( + Wild(All), + )]), + remote_xcm: xcm_on_ah, + }, + RefundSurplus, + DepositAsset { assets: Wild(All), beneficiary: sender.clone().into() }, + ]); + // initiate transaction + ::PolkadotXcm::execute( + sender_signed_origin, + bx!(xcm::VersionedXcm::from(xcm.into())), + Weight::MAX, + ) + .unwrap(); + + // verify expected events; + PenpalA::assert_xcm_pallet_attempted_complete(None); + }); + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + RuntimeEvent::AssetConversion( + pallet_asset_conversion::Event::SwapCreditExecuted { amount_out, ..} + ) => { amount_out: *amount_out == amount_of_usdt_we_want_from_exchange, }, + ] + ); + }); + + PenpalA::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + // Query final balances + let sender_usdt_on_ah_after = assets_balance_on!(AssetHubWestend, USDT_ID.clone(), &sender); + let sender_usdt_on_penpal_after = + foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender); + + // Receiver's balance is increased by usdt amount we got from exchange + assert_eq!( + sender_usdt_on_penpal_after, + sender_usdt_on_penpal_before + amount_of_usdt_we_want_from_exchange + ); + // Usdt amount on senders account AH side should stay the same i.e. all usdt came from exchange + // not free balance + assert_eq!(sender_usdt_on_ah_before, sender_usdt_on_ah_after); +} 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 ea46f8b14937e..888340fe58f7e 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 @@ -35,7 +35,7 @@ macro_rules! foreign_balance_on { emulated_integration_tests_common::impls::paste::paste! { <$chain>::execute_with(|| { type ForeignAssets = <$chain as [<$chain Pallet>]>::ForeignAssets; - >::balance($id, $who) + >::balance($id, $who) }) } }; @@ -47,7 +47,7 @@ macro_rules! assets_balance_on { emulated_integration_tests_common::impls::paste::paste! { <$chain>::execute_with(|| { type Assets = <$chain as [<$chain Pallet>]>::Assets; - >::balance($id, $who) + >::balance($id, $who) }) } }; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs index 29caa34756766..14b2937a675b9 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs @@ -301,24 +301,28 @@ fn transact_from_para_to_asset_hub_and_back_to_para() { ]); // xcm to be executed by ah, sent by penpal let xcm_on_ah = Xcm(vec![ + // aliasing into sender itself, as opposed to sender's sovereign account + // its possible due to add_authorized_alias above AliasOrigin(Location::new( 0, [AccountId32 { network: None, id: sender.clone().into() }], )), Transact { origin_kind: OriginKind::SovereignAccount, call, fallback_max_weight: None }, ExpectTransactStatus(MaybeErrorCode::Success), - WithdrawAsset((usdt_asset_hub_pov.clone(), 1_000_000_000).into()), + WithdrawAsset((usdt_asset_hub_pov.clone(), amount_of_usdt_we_want_from_exchange).into()), InitiateTransfer { destination: penpal_location_ah_pov, remote_fees: Some(AssetTransferFilter::ReserveDeposit( penpal_remote_fees.clone().into(), )), - preserve_origin: true, + preserve_origin: false, assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveDeposit(Wild( All, ))]), remote_xcm: xcm_back_on_penpal, }, + RefundSurplus, + DepositAsset { assets: Wild(All), beneficiary: sender.clone().into() }, ]); // xcm to be executed locally on penpal as starting point let xcm = Xcm::<()>(vec![ @@ -335,6 +339,8 @@ fn transact_from_para_to_asset_hub_and_back_to_para() { )]), remote_xcm: xcm_on_ah, }, + RefundSurplus, + DepositAsset { assets: Wild(All), beneficiary: sender.clone().into() }, ]); // initiate transaction ::PolkadotXcm::execute( @@ -389,156 +395,6 @@ fn transact_from_para_to_asset_hub_and_back_to_para() { assert_eq!(sender_usdt_on_ah_before, sender_usdt_on_ah_after); } -#[test] -fn transact_from_para_to_asset_hub_and_back_to_para_using_exchange_asset() { - let sender = PenpalASender::get(); - let sov_of_sender_on_asset_hub = AssetHubWestend::sovereign_account_id_of( - AssetHubWestend::sibling_location_of(PenpalA::para_id()), - ); - let wnd_from_parachain_pov: Location = RelayLocation::get(); - let usdt_asset_hub_pov = - Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())]); - let usdt_penpal_pov = PenpalUsdtFromAssetHub::get(); - let amount_of_wnd_to_transfer_to_ah = WESTEND_ED * 1_000_000_000; - let amount_of_usdt_we_want_from_exchange = 1_000_000_000; - let sender_as_seen_from_ah = Location::new( - 1, - [ - Parachain(2000), - AccountId32 { - network: Some(NetworkId::ByGenesis(ROCOCO_GENESIS_HASH)), - id: sender.clone().into(), - }, - ], - ); - - // SA-of-Penpal-on-AHW should contain WND amount equal at least the amount that will be - // transferred-in to AH Since AH is the reserve for WND - AssetHubWestend::fund_accounts(vec![( - sov_of_sender_on_asset_hub.clone().into(), - ASSET_HUB_WESTEND_ED + amount_of_wnd_to_transfer_to_ah, - )]); - // Give the sender enough WND - PenpalA::mint_foreign_asset( - ::RuntimeOrigin::signed(PenpalAssetOwner::get()), - wnd_from_parachain_pov.clone(), - sender.clone(), - amount_of_wnd_to_transfer_to_ah, - ); - - // We create a pool between WND and USDT in AssetHub so we can do the exchange - create_pool_with_wnd_on!( - AssetHubWestend, - usdt_asset_hub_pov.clone(), - false, - AssetHubWestendSender::get(), - 1_000_000_000_000, - 20_000_000_000 - ); - - // We add auhtorized alias on AH so sender from Penpal can AliasOrigin into itself on AH - // (instead of aliasing into Sovereign Account of sender) - AssetHubWestend::execute_with(|| { - assert_ok!(::PolkadotXcm::add_authorized_alias( - ::RuntimeOrigin::signed(sender.clone()), - Box::new(sender_as_seen_from_ah.into()), - None - )); - }); - - // Query initial balances - let sender_usdt_on_penpal_before = - foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender); - let sender_usdt_on_ah_before = assets_balance_on!(AssetHubWestend, USDT_ID.clone(), &sender); - - - let asset_hub_location_penpal_pov = PenpalA::sibling_location_of(AssetHubWestend::para_id()); - let penpal_location_ah_pov = AssetHubWestend::sibling_location_of(PenpalA::para_id()); - - PenpalA::execute_with(|| { - let sender_signed_origin = ::RuntimeOrigin::signed(sender.clone()); - - let local_fees_amount = 80_000_000_000_000u128; - let remote_fees_amount = 200_000_000_000_000u128; - - let penpal_local_fees: Asset = (wnd_from_parachain_pov.clone(), local_fees_amount).into(); - let ah_remote_fees: Asset = (wnd_from_parachain_pov.clone(), remote_fees_amount).into(); - let penpal_remote_fees: Asset = (wnd_from_parachain_pov.clone(), remote_fees_amount).into(); - let wnd_to_withdraw: Asset = - (wnd_from_parachain_pov.clone(), amount_of_wnd_to_transfer_to_ah).into(); - - // xcm to be executed by penpal, sent by ah - let xcm_back_on_penpal = Xcm(vec![ - RefundSurplus, - DepositAsset { assets: Wild(All), beneficiary: sender.clone().into() }, - ]); - // xcm to be executed by ah, sent by penpal - let xcm_on_ah = Xcm(vec![ - ExchangeAsset { - give: Definite((wnd_from_parachain_pov.clone(), 100_000_000_000u128).into()), - want: (usdt_asset_hub_pov.clone(), amount_of_usdt_we_want_from_exchange).into(), - maximal: false, - }, - InitiateTransfer { - destination: penpal_location_ah_pov, - remote_fees: Some(AssetTransferFilter::ReserveDeposit( - penpal_remote_fees.clone().into(), - )), - preserve_origin: true, - assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveDeposit(Wild( - All, - ))]), - remote_xcm: xcm_back_on_penpal, - }, - ]); - // xcm to be executed locally on penpal as starting point - let xcm = Xcm::<()>(vec![ - WithdrawAsset(wnd_to_withdraw.into()), - PayFees { asset: penpal_local_fees }, - InitiateTransfer { - destination: asset_hub_location_penpal_pov, - remote_fees: Some(AssetTransferFilter::ReserveWithdraw( - ah_remote_fees.clone().into(), - )), - preserve_origin: false, - assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveWithdraw( - Wild(All), - )]), - remote_xcm: xcm_on_ah, - }, - ]); - // initiate transaction - ::PolkadotXcm::execute( - sender_signed_origin, - bx!(xcm::VersionedXcm::from(xcm.into())), - Weight::MAX, - ) - .unwrap(); - - // verify expected events; - PenpalA::assert_xcm_pallet_attempted_complete(None); - }); - AssetHubWestend::execute_with(|| { - }); - - PenpalA::execute_with(|| { - }); - - // Query final balances - let sender_usdt_on_ah_after = assets_balance_on!(AssetHubWestend, USDT_ID.clone(), &sender); - let sender_usdt_on_penpal_after = - foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender); - - // Receiver's balance is increased by usdt amount we got from exchange - assert_eq!( - sender_usdt_on_penpal_after, - sender_usdt_on_penpal_before + amount_of_usdt_we_want_from_exchange - ); - // Usdt amount on senders account AH side should stay the same i.e. all usdt came from exchange - // not free balance - assert_eq!(sender_usdt_on_ah_before, sender_usdt_on_ah_after); -} - fn asset_hub_hop_assertions(sender_sa: AccountId) { type RuntimeEvent = ::RuntimeEvent; assert_expected_events!( diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 6b9837d627e0f..b4efab9349a4b 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -562,6 +562,10 @@ impl XcmExecutor { // If there are any leftover `fees`, merge them with `holding`. if !self.fees.is_empty() { let leftover_fees = self.fees.saturating_take(Wild(All)); + tracing::trace!( + target: "xcm::refund_surplus", + ?leftover_fees, + ); self.holding.subsume_assets(leftover_fees); } tracing::trace!( From ba85afac12ecb6d00ea1ccbdfc76d51df8c67e86 Mon Sep 17 00:00:00 2001 From: Karol Kokoszka Date: Mon, 14 Jul 2025 16:14:04 +0200 Subject: [PATCH 04/10] Formatting fixes --- .../tests/assets/asset-hub-westend/src/tests/transact.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs index 14b2937a675b9..ffb2e4cb2ce5f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs @@ -309,7 +309,9 @@ fn transact_from_para_to_asset_hub_and_back_to_para() { )), Transact { origin_kind: OriginKind::SovereignAccount, call, fallback_max_weight: None }, ExpectTransactStatus(MaybeErrorCode::Success), - WithdrawAsset((usdt_asset_hub_pov.clone(), amount_of_usdt_we_want_from_exchange).into()), + WithdrawAsset( + (usdt_asset_hub_pov.clone(), amount_of_usdt_we_want_from_exchange).into(), + ), InitiateTransfer { destination: penpal_location_ah_pov, remote_fees: Some(AssetTransferFilter::ReserveDeposit( From 9d9505643a35f5637bd32078ee7e4436a8a8a8f0 Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 14:17:13 +0000 Subject: [PATCH 05/10] Update from github-actions[bot] running command 'prdoc --audience runtime_dev --bump patch' --- prdoc/pr_9195.prdoc | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 prdoc/pr_9195.prdoc diff --git a/prdoc/pr_9195.prdoc b/prdoc/pr_9195.prdoc new file mode 100644 index 0000000000000..6c687ac5a7fd9 --- /dev/null +++ b/prdoc/pr_9195.prdoc @@ -0,0 +1,27 @@ +title: XCMv5 asset exchange test scenarios +doc: +- audience: Runtime Dev + description: |- + Relates to: #9093 + Requires: #9179 + + This PR introduces emulated test scenarios: + + #### [Scenario 1] + (Penpal -> AH -> Penpal) to showcase usage of remote `Transact` to swap assets remotely on AssetHub. + + 1. Prepare sovereign accounts funds, create pools, prepare aliasing rules + 2. Send WND from Penpal to AssetHub (AH being remote reserve for WND) + 3. Alias into sender account and exchange WNDs for USDT using `Transact` with `swap_tokens_for_exact_tokens` call inside + 4. Send USDT and leftover WND back to Penpal + + #### [Scenario 2] + (Penpal -> AH -> Penpal) to showcase same as above but this time using `ExchangeAsset` XCM instruction instead of `Transact`: + + 1. Prepare sovereign accounts funds, create pools + 2. Send WND from Penpal to AssetHub (AH being remote reserve for WND) + 3. Exchange WNDs for USDT using `ExchangeAsset` + 4. Send USDT and leftover WND back to Penpal +crates: +- name: staging-xcm-executor + bump: patch From 29e51c9596bf2f91d10cac482dff58cc7bb5e1d1 Mon Sep 17 00:00:00 2001 From: Karol Kokoszka Date: Mon, 14 Jul 2025 16:55:41 +0200 Subject: [PATCH 06/10] Clippy fixes and prdoc improvements --- .../src/tests/exchange_asset.rs | 4 ++-- .../asset-hub-westend/src/tests/transact.rs | 4 ++-- prdoc/pr_9195.prdoc | 20 +------------------ 3 files changed, 5 insertions(+), 23 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/exchange_asset.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/exchange_asset.rs index 0a0e029eb3bc4..bb1ca59c63122 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/exchange_asset.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/exchange_asset.rs @@ -197,7 +197,7 @@ fn exchange_asset_from_penpal_via_asset_hub_back_to_penpal() { // Query initial balances let sender_usdt_on_penpal_before = foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender); - let sender_usdt_on_ah_before = assets_balance_on!(AssetHubWestend, USDT_ID.clone(), &sender); + let sender_usdt_on_ah_before = assets_balance_on!(AssetHubWestend, USDT_ID, &sender); let asset_hub_location_penpal_pov = PenpalA::sibling_location_of(AssetHubWestend::para_id()); let penpal_location_ah_pov = AssetHubWestend::sibling_location_of(PenpalA::para_id()); @@ -297,7 +297,7 @@ fn exchange_asset_from_penpal_via_asset_hub_back_to_penpal() { }); // Query final balances - let sender_usdt_on_ah_after = assets_balance_on!(AssetHubWestend, USDT_ID.clone(), &sender); + let sender_usdt_on_ah_after = assets_balance_on!(AssetHubWestend, USDT_ID, &sender); let sender_usdt_on_penpal_after = foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender); diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs index ffb2e4cb2ce5f..36242e7950c90 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs @@ -261,7 +261,7 @@ fn transact_from_para_to_asset_hub_and_back_to_para() { // Query initial balances let sender_usdt_on_penpal_before = foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender); - let sender_usdt_on_ah_before = assets_balance_on!(AssetHubWestend, USDT_ID.clone(), &sender); + let sender_usdt_on_ah_before = assets_balance_on!(AssetHubWestend, USDT_ID, &sender); // Encoded `swap_tokens_for_exact_tokens` call to be executed in AH let call = ::RuntimeCall::AssetConversion( @@ -383,7 +383,7 @@ fn transact_from_para_to_asset_hub_and_back_to_para() { }); // Query final balances - let sender_usdt_on_ah_after = assets_balance_on!(AssetHubWestend, USDT_ID.clone(), &sender); + let sender_usdt_on_ah_after = assets_balance_on!(AssetHubWestend, USDT_ID, &sender); let sender_usdt_on_penpal_after = foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender); diff --git a/prdoc/pr_9195.prdoc b/prdoc/pr_9195.prdoc index 6c687ac5a7fd9..582804c5dd5cf 100644 --- a/prdoc/pr_9195.prdoc +++ b/prdoc/pr_9195.prdoc @@ -2,26 +2,8 @@ title: XCMv5 asset exchange test scenarios doc: - audience: Runtime Dev description: |- - Relates to: #9093 - Requires: #9179 + Emulated test scenarios added to cover asset exchanging via Transact or ExchangeAsset instruction using XCMv5 capabilities - This PR introduces emulated test scenarios: - - #### [Scenario 1] - (Penpal -> AH -> Penpal) to showcase usage of remote `Transact` to swap assets remotely on AssetHub. - - 1. Prepare sovereign accounts funds, create pools, prepare aliasing rules - 2. Send WND from Penpal to AssetHub (AH being remote reserve for WND) - 3. Alias into sender account and exchange WNDs for USDT using `Transact` with `swap_tokens_for_exact_tokens` call inside - 4. Send USDT and leftover WND back to Penpal - - #### [Scenario 2] - (Penpal -> AH -> Penpal) to showcase same as above but this time using `ExchangeAsset` XCM instruction instead of `Transact`: - - 1. Prepare sovereign accounts funds, create pools - 2. Send WND from Penpal to AssetHub (AH being remote reserve for WND) - 3. Exchange WNDs for USDT using `ExchangeAsset` - 4. Send USDT and leftover WND back to Penpal crates: - name: staging-xcm-executor bump: patch From 6896724ad530a858385a849cea6994dd249dee11 Mon Sep 17 00:00:00 2001 From: Karol Kokoszka Date: Wed, 16 Jul 2025 11:09:07 +0200 Subject: [PATCH 07/10] InitiateTransfer with Transact (PenpalA -> AH -> PenpalA) without aliasing --- .../asset-hub-westend/src/tests/transact.rs | 211 +++++++++++++++++- 1 file changed, 207 insertions(+), 4 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs index 36242e7950c90..e7be71a933431 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs @@ -202,7 +202,7 @@ fn transact_from_para_to_para_through_asset_hub() { } #[test] -fn transact_from_para_to_asset_hub_and_back_to_para() { +fn transact_from_para_to_asset_hub_and_back_to_para_with_authorized_alias() { let sender = PenpalASender::get(); let sov_of_sender_on_asset_hub = AssetHubWestend::sovereign_account_id_of( AssetHubWestend::sibling_location_of(PenpalA::para_id()), @@ -211,8 +211,9 @@ fn transact_from_para_to_asset_hub_and_back_to_para() { let usdt_asset_hub_pov = Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())]); let usdt_penpal_pov = PenpalUsdtFromAssetHub::get(); - let amount_of_wnd_to_transfer_to_ah = WESTEND_ED * 1_000_000_000; - let amount_of_usdt_we_want_from_exchange = 1_000_000_000; + let amount_of_wnd_to_transfer_to_ah = WESTEND_ED * 1_000_000_000u128; + let amount_of_usdt_we_want_from_exchange = 1_000_000_000u128; + let max_amount_of_wnd_we_allow_for_exchange = 1_000_000_000_000u128; let sender_as_seen_from_ah = Location::new( 1, [ @@ -271,7 +272,7 @@ fn transact_from_para_to_asset_hub_and_back_to_para() { Box::new(usdt_asset_hub_pov.clone()), ], amount_out: amount_of_usdt_we_want_from_exchange, - amount_in_max: 1_000_000_000_000, + amount_in_max: max_amount_of_wnd_we_allow_for_exchange, send_to: sender.clone(), keep_alive: true, }, @@ -307,6 +308,13 @@ fn transact_from_para_to_asset_hub_and_back_to_para() { 0, [AccountId32 { network: None, id: sender.clone().into() }], )), + DepositAsset { + assets: Definite( + (wnd_from_parachain_pov.clone(), max_amount_of_wnd_we_allow_for_exchange) + .into(), + ), + beneficiary: sender.clone().into(), + }, Transact { origin_kind: OriginKind::SovereignAccount, call, fallback_max_weight: None }, ExpectTransactStatus(MaybeErrorCode::Success), WithdrawAsset( @@ -397,6 +405,201 @@ fn transact_from_para_to_asset_hub_and_back_to_para() { assert_eq!(sender_usdt_on_ah_before, sender_usdt_on_ah_after); } +#[test] +fn transact_from_para_to_asset_hub_and_back_to_para() { + let sender = PenpalASender::get(); + let sov_of_penpal_on_asset_hub = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalA::para_id()), + ); + let wnd_from_parachain_pov: Location = RelayLocation::get(); + let usdt_asset_hub_pov = + Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())]); + let usdt_penpal_pov = PenpalUsdtFromAssetHub::get(); + let amount_of_wnd_to_transfer_to_ah = WESTEND_ED * 1_000_000_000u128; + let amount_of_usdt_we_want_from_exchange = 1_000_000_000u128; + let max_amount_of_wnd_we_allow_for_exchange = 1_000_000_000_000u128; + let sender_as_seen_from_ah = Location::new( + 1, + [ + Parachain(2000), + AccountId32 { + network: Some(NetworkId::ByGenesis(ROCOCO_GENESIS_HASH)), + id: sender.clone().into(), + }, + ], + ); + let sov_of_sender_on_asset_hub = + AssetHubWestend::sovereign_account_id_of(sender_as_seen_from_ah.clone()); + + // SA-of-Penpal-on-AHW should contain WND amount equal at least the amount that will be + // transferred-in to AH Since AH is the reserve for WND + AssetHubWestend::fund_accounts(vec![( + sov_of_penpal_on_asset_hub.clone().into(), + ASSET_HUB_WESTEND_ED + amount_of_wnd_to_transfer_to_ah, + )]); + // Give the sender enough WND + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + wnd_from_parachain_pov.clone(), + sender.clone(), + amount_of_wnd_to_transfer_to_ah, + ); + + // We create a pool between WND and USDT in AssetHub so we can do the exchange + create_pool_with_wnd_on!( + AssetHubWestend, + usdt_asset_hub_pov.clone(), + false, + AssetHubWestendSender::get(), + 1_000_000_000_000, + 20_000_000_000 + ); + + // Query initial balances + let sender_usdt_on_penpal_before = + foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender); + let sender_usdt_on_ah_before = + assets_balance_on!(AssetHubWestend, USDT_ID, &sov_of_sender_on_asset_hub); + + // Encoded `swap_tokens_for_exact_tokens` call to be executed in AH + let call = ::RuntimeCall::AssetConversion( + pallet_asset_conversion::Call::swap_tokens_for_exact_tokens { + path: vec![ + Box::new(wnd_from_parachain_pov.clone()), + Box::new(usdt_asset_hub_pov.clone()), + ], + amount_out: amount_of_usdt_we_want_from_exchange, + amount_in_max: max_amount_of_wnd_we_allow_for_exchange, + send_to: sov_of_sender_on_asset_hub.clone(), + keep_alive: false, + }, + ) + .encode() + .into(); + + let asset_hub_location_penpal_pov = PenpalA::sibling_location_of(AssetHubWestend::para_id()); + let penpal_location_ah_pov = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + + PenpalA::execute_with(|| { + let sender_signed_origin = ::RuntimeOrigin::signed(sender.clone()); + + let local_fees_amount = 80_000_000_000_000u128; + let remote_fees_amount = 90_000_000_000_000u128; + + let penpal_local_fees: Asset = (wnd_from_parachain_pov.clone(), local_fees_amount).into(); + let ah_remote_fees: Asset = (wnd_from_parachain_pov.clone(), remote_fees_amount).into(); + let penpal_remote_fees: Asset = (wnd_from_parachain_pov.clone(), remote_fees_amount).into(); + let wnd_to_withdraw: Asset = + (wnd_from_parachain_pov.clone(), amount_of_wnd_to_transfer_to_ah).into(); + + // xcm to be executed by penpal, sent by ah + let xcm_back_on_penpal = Xcm(vec![ + RefundSurplus, + DepositAsset { assets: Wild(All), beneficiary: sender.clone().into() }, + ]); + // xcm to be executed by ah, sent by penpal + let xcm_on_ah = Xcm(vec![ + DepositAsset { + assets: Definite( + (wnd_from_parachain_pov.clone(), max_amount_of_wnd_we_allow_for_exchange) + .into(), + ), + beneficiary: sov_of_sender_on_asset_hub.clone().into(), + }, + Transact { origin_kind: OriginKind::SovereignAccount, call, fallback_max_weight: None }, + ExpectTransactStatus(MaybeErrorCode::Success), + WithdrawAsset( + (usdt_asset_hub_pov.clone(), amount_of_usdt_we_want_from_exchange).into(), + ), + InitiateTransfer { + destination: penpal_location_ah_pov, + remote_fees: Some(AssetTransferFilter::ReserveDeposit( + penpal_remote_fees.clone().into(), + )), + preserve_origin: false, + assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveDeposit(Wild( + All, + ))]), + remote_xcm: xcm_back_on_penpal, + }, + RefundSurplus, + DepositAsset { + assets: Wild(All), + beneficiary: sov_of_sender_on_asset_hub.clone().into(), + }, + ]); + // xcm to be executed locally on penpal as starting point + let xcm = Xcm::<()>(vec![ + WithdrawAsset(wnd_to_withdraw.into()), + PayFees { asset: penpal_local_fees }, + InitiateTransfer { + destination: asset_hub_location_penpal_pov, + remote_fees: Some(AssetTransferFilter::ReserveWithdraw( + ah_remote_fees.clone().into(), + )), + preserve_origin: true, + assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveWithdraw( + Wild(All), + )]), + remote_xcm: xcm_on_ah, + }, + RefundSurplus, + DepositAsset { assets: Wild(All), beneficiary: sender.clone().into() }, + ]); + // initiate transaction + ::PolkadotXcm::execute( + sender_signed_origin, + bx!(xcm::VersionedXcm::from(xcm.into())), + Weight::MAX, + ) + .unwrap(); + + // verify expected events; + PenpalA::assert_xcm_pallet_attempted_complete(None); + }); + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + RuntimeEvent::AssetConversion( + pallet_asset_conversion::Event::SwapExecuted { amount_out, ..} + ) => { amount_out: *amount_out == amount_of_usdt_we_want_from_exchange, }, + ] + ); + }); + + PenpalA::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + // Query final balances + let sender_usdt_on_ah_after = + assets_balance_on!(AssetHubWestend, USDT_ID, &sov_of_sender_on_asset_hub); + let sender_usdt_on_penpal_after = + foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender); + + // Receiver's balance is increased by usdt amount we got from exchange + assert_eq!( + sender_usdt_on_penpal_after, + sender_usdt_on_penpal_before + amount_of_usdt_we_want_from_exchange + ); + // Usdt amount on senders account AH side should stay the same i.e. all usdt came from exchange + // not free balance + assert_eq!(sender_usdt_on_ah_before, sender_usdt_on_ah_after); +} + fn asset_hub_hop_assertions(sender_sa: AccountId) { type RuntimeEvent = ::RuntimeEvent; assert_expected_events!( From a115add42b5e52a143505737a9dad13a91d264ee Mon Sep 17 00:00:00 2001 From: Karol Kokoszka Date: Thu, 24 Jul 2025 12:30:44 +0200 Subject: [PATCH 08/10] Introduce TopicIdTracker in tests --- .../tests/assets/asset-hub-westend/src/lib.rs | 4 ++- .../src/tests/exchange_asset.rs | 16 ++++++++++ .../asset-hub-westend/src/tests/transact.rs | 32 +++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) 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 4f41680838483..64245240cb899 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 @@ -44,8 +44,10 @@ mod imports { TestArgs, TestContext, TestExt, }, xcm_helpers::{ - fee_asset, get_amount_from_versioned_assets, non_fee_asset, xcm_transact_paid_execution, + fee_asset, find_mq_processed_id, find_xcm_sent_message_id, + get_amount_from_versioned_assets, non_fee_asset, xcm_transact_paid_execution, }, + xcm_simulator::helpers::TopicIdTracker, PenpalATeleportableAssetLocation, ASSETS_PALLET_ID, RESERVABLE_ASSET_ID, USDT_ID, XCM_V3, }; pub(crate) use parachains_common::{AccountId, Balance}; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/exchange_asset.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/exchange_asset.rs index bb1ca59c63122..46612dbee62ee 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/exchange_asset.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/exchange_asset.rs @@ -170,6 +170,8 @@ fn exchange_asset_from_penpal_via_asset_hub_back_to_penpal() { let amount_of_wnd_to_transfer_to_ah = WESTEND_ED * 1_000_000_000; let amount_of_usdt_we_want_from_exchange = 1_000_000_000; + let mut topic_id_tracker = TopicIdTracker::new(); + // SA-of-Penpal-on-AHW should contain WND amount equal at least the amount that will be // transferred-in to AH Since AH is the reserve for WND AssetHubWestend::fund_accounts(vec![( @@ -268,6 +270,9 @@ fn exchange_asset_from_penpal_via_asset_hub_back_to_penpal() { // verify expected events; PenpalA::assert_xcm_pallet_attempted_complete(None); + + let msg_sent_id = find_xcm_sent_message_id::().expect("Missing Sent Event"); + topic_id_tracker.insert("PenpalA_sent", msg_sent_id.into()); }); AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; @@ -282,6 +287,12 @@ fn exchange_asset_from_penpal_via_asset_hub_back_to_penpal() { ) => { amount_out: *amount_out == amount_of_usdt_we_want_from_exchange, }, ] ); + + let mq_prc_id = find_mq_processed_id::().expect("Missing Processed Event"); + topic_id_tracker.insert("AssetHubWestend_received", mq_prc_id); + let msg_sent_id = + find_xcm_sent_message_id::().expect("Missing Sent Event"); + topic_id_tracker.insert("AssetHubWestend_sent", msg_sent_id.into()); }); PenpalA::execute_with(|| { @@ -294,8 +305,13 @@ fn exchange_asset_from_penpal_via_asset_hub_back_to_penpal() { ) => {}, ] ); + + let mq_prc_id = find_mq_processed_id::().expect("Missing Processed Event"); + topic_id_tracker.insert("PenpalA_received", mq_prc_id); }); + topic_id_tracker.assert_unique(); + // Query final balances let sender_usdt_on_ah_after = assets_balance_on!(AssetHubWestend, USDT_ID, &sender); let sender_usdt_on_penpal_after = diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs index e7be71a933431..b1352d9f1c8e3 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs @@ -225,6 +225,8 @@ fn transact_from_para_to_asset_hub_and_back_to_para_with_authorized_alias() { ], ); + let mut topic_id_tracker = TopicIdTracker::new(); + // SA-of-Penpal-on-AHW should contain WND amount equal at least the amount that will be // transferred-in to AH Since AH is the reserve for WND AssetHubWestend::fund_accounts(vec![( @@ -362,6 +364,9 @@ fn transact_from_para_to_asset_hub_and_back_to_para_with_authorized_alias() { // verify expected events; PenpalA::assert_xcm_pallet_attempted_complete(None); + + let msg_sent_id = find_xcm_sent_message_id::().expect("Missing Sent Event"); + topic_id_tracker.insert("PenpalA_sent", msg_sent_id.into()); }); AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; @@ -376,6 +381,12 @@ fn transact_from_para_to_asset_hub_and_back_to_para_with_authorized_alias() { ) => { amount_out: *amount_out == amount_of_usdt_we_want_from_exchange, }, ] ); + + let mq_prc_id = find_mq_processed_id::().expect("Missing Processed Event"); + topic_id_tracker.insert("AssetHubWestend_received", mq_prc_id); + let msg_sent_id = + find_xcm_sent_message_id::().expect("Missing Sent Event"); + topic_id_tracker.insert("AssetHubWestend_sent", msg_sent_id.into()); }); PenpalA::execute_with(|| { @@ -388,8 +399,13 @@ fn transact_from_para_to_asset_hub_and_back_to_para_with_authorized_alias() { ) => {}, ] ); + + let mq_prc_id = find_mq_processed_id::().expect("Missing Processed Event"); + topic_id_tracker.insert("PenpalA_received", mq_prc_id); }); + topic_id_tracker.assert_unique(); + // Query final balances let sender_usdt_on_ah_after = assets_balance_on!(AssetHubWestend, USDT_ID, &sender); let sender_usdt_on_penpal_after = @@ -431,6 +447,8 @@ fn transact_from_para_to_asset_hub_and_back_to_para() { let sov_of_sender_on_asset_hub = AssetHubWestend::sovereign_account_id_of(sender_as_seen_from_ah.clone()); + let mut topic_id_tracker = TopicIdTracker::new(); + // SA-of-Penpal-on-AHW should contain WND amount equal at least the amount that will be // transferred-in to AH Since AH is the reserve for WND AssetHubWestend::fund_accounts(vec![( @@ -556,6 +574,9 @@ fn transact_from_para_to_asset_hub_and_back_to_para() { // verify expected events; PenpalA::assert_xcm_pallet_attempted_complete(None); + + let msg_sent_id = find_xcm_sent_message_id::().expect("Missing Sent Event"); + topic_id_tracker.insert("PenpalA_sent", msg_sent_id.into()); }); AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; @@ -570,6 +591,12 @@ fn transact_from_para_to_asset_hub_and_back_to_para() { ) => { amount_out: *amount_out == amount_of_usdt_we_want_from_exchange, }, ] ); + + let mq_prc_id = find_mq_processed_id::().expect("Missing Processed Event"); + topic_id_tracker.insert("AssetHubWestend_received", mq_prc_id); + let msg_sent_id = + find_xcm_sent_message_id::().expect("Missing Sent Event"); + topic_id_tracker.insert("AssetHubWestend_sent", msg_sent_id.into()); }); PenpalA::execute_with(|| { @@ -582,8 +609,13 @@ fn transact_from_para_to_asset_hub_and_back_to_para() { ) => {}, ] ); + + let mq_prc_id = find_mq_processed_id::().expect("Missing Processed Event"); + topic_id_tracker.insert("PenpalA_received", mq_prc_id); }); + topic_id_tracker.assert_unique(); + // Query final balances let sender_usdt_on_ah_after = assets_balance_on!(AssetHubWestend, USDT_ID, &sov_of_sender_on_asset_hub); From 990b42b9d468ebf183a6ec6c45752f5ec4def7f4 Mon Sep 17 00:00:00 2001 From: Karol Kokoszka Date: Thu, 31 Jul 2025 10:48:09 +0200 Subject: [PATCH 09/10] Apply suggestions from code review Co-authored-by: Adrian Catangiu --- .../tests/assets/asset-hub-westend/src/tests/transact.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs index b1352d9f1c8e3..fb485e5f57cb4 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs @@ -202,7 +202,7 @@ fn transact_from_para_to_para_through_asset_hub() { } #[test] -fn transact_from_para_to_asset_hub_and_back_to_para_with_authorized_alias() { +fn transact_using_authorized_alias_from_para_to_asset_hub_and_back_to_para() { let sender = PenpalASender::get(); let sov_of_sender_on_asset_hub = AssetHubWestend::sovereign_account_id_of( AssetHubWestend::sibling_location_of(PenpalA::para_id()), @@ -422,7 +422,7 @@ fn transact_from_para_to_asset_hub_and_back_to_para_with_authorized_alias() { } #[test] -fn transact_from_para_to_asset_hub_and_back_to_para() { +fn transact_using_sov_account_from_para_to_asset_hub_and_back_to_para() { let sender = PenpalASender::get(); let sov_of_penpal_on_asset_hub = AssetHubWestend::sovereign_account_id_of( AssetHubWestend::sibling_location_of(PenpalA::para_id()), From 0e735eafc6f8808ceea4691cbd3c663941f4ee67 Mon Sep 17 00:00:00 2001 From: Karol Kokoszka Date: Thu, 31 Jul 2025 11:09:59 +0200 Subject: [PATCH 10/10] Fix variable names for sov accounts --- .../assets/asset-hub-westend/src/tests/exchange_asset.rs | 4 ++-- .../tests/assets/asset-hub-westend/src/tests/transact.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/exchange_asset.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/exchange_asset.rs index 46612dbee62ee..800fa967aa475 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/exchange_asset.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/exchange_asset.rs @@ -160,7 +160,7 @@ fn test_exchange_asset( #[test] fn exchange_asset_from_penpal_via_asset_hub_back_to_penpal() { let sender = PenpalASender::get(); - let sov_of_sender_on_asset_hub = AssetHubWestend::sovereign_account_id_of( + let sov_of_penpal_on_asset_hub = AssetHubWestend::sovereign_account_id_of( AssetHubWestend::sibling_location_of(PenpalA::para_id()), ); let wnd_from_parachain_pov: Location = RelayLocation::get(); @@ -175,7 +175,7 @@ fn exchange_asset_from_penpal_via_asset_hub_back_to_penpal() { // SA-of-Penpal-on-AHW should contain WND amount equal at least the amount that will be // transferred-in to AH Since AH is the reserve for WND AssetHubWestend::fund_accounts(vec![( - sov_of_sender_on_asset_hub.clone().into(), + sov_of_penpal_on_asset_hub.clone().into(), ASSET_HUB_WESTEND_ED + amount_of_wnd_to_transfer_to_ah, )]); // Give the sender enough WND diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs index fb485e5f57cb4..6ebcb621f0687 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs @@ -204,7 +204,7 @@ fn transact_from_para_to_para_through_asset_hub() { #[test] fn transact_using_authorized_alias_from_para_to_asset_hub_and_back_to_para() { let sender = PenpalASender::get(); - let sov_of_sender_on_asset_hub = AssetHubWestend::sovereign_account_id_of( + let sov_of_penpal_on_asset_hub = AssetHubWestend::sovereign_account_id_of( AssetHubWestend::sibling_location_of(PenpalA::para_id()), ); let wnd_from_parachain_pov: Location = RelayLocation::get(); @@ -230,7 +230,7 @@ fn transact_using_authorized_alias_from_para_to_asset_hub_and_back_to_para() { // SA-of-Penpal-on-AHW should contain WND amount equal at least the amount that will be // transferred-in to AH Since AH is the reserve for WND AssetHubWestend::fund_accounts(vec![( - sov_of_sender_on_asset_hub.clone().into(), + sov_of_penpal_on_asset_hub.clone().into(), ASSET_HUB_WESTEND_ED + amount_of_wnd_to_transfer_to_ah, )]); // Give the sender enough WND