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 695ecff9f63a3..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 @@ -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,173 @@ fn test_exchange_asset( } }); } + +#[test] +fn exchange_asset_from_penpal_via_asset_hub_back_to_penpal() { + 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_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![( + 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, &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); + + 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; + 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, }, + ] + ); + + 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(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + + 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 = + 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 22bfebddefee2..111c3ddf4b0cd 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 @@ -36,7 +36,19 @@ 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) + }) + } + }; +} + +#[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) }) } }; 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..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 @@ -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,437 @@ fn transact_from_para_to_para_through_asset_hub() { assert!(receiver_assets_after > receiver_assets_before); } +#[test] +fn transact_using_authorized_alias_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 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![( + 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 + ); + + // 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, &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: max_amount_of_wnd_we_allow_for_exchange, + 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![ + // 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() }], + )), + 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( + (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: 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: 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); + + 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; + 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, }, + ] + ); + + 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(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + + 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 = + 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); +} + +#[test] +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()), + ); + 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()); + + 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![( + 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); + + 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; + 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, }, + ] + ); + + 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(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + + 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); + 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!( diff --git a/prdoc/pr_9195.prdoc b/prdoc/pr_9195.prdoc new file mode 100644 index 0000000000000..582804c5dd5cf --- /dev/null +++ b/prdoc/pr_9195.prdoc @@ -0,0 +1,9 @@ +title: XCMv5 asset exchange test scenarios +doc: +- audience: Runtime Dev + description: |- + Emulated test scenarios added to cover asset exchanging via Transact or ExchangeAsset instruction using XCMv5 capabilities + +crates: +- name: staging-xcm-executor + bump: patch