diff --git a/Cargo.lock b/Cargo.lock index 40594efe42d3a509e035238ea8bd89e3e42262eb..6ad92da6958d08a18206b3d2b3e8287f36c469c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2868,6 +2868,7 @@ version = "1.0.0" dependencies = [ "asset-hub-westend-runtime", "bridge-hub-westend-runtime", + "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "emulated-integration-tests-common", "frame-support 28.0.0", diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml index f718e7e77f597723c2a53dac3552bb103bab96d9..05c7021d380aeb0eaec892dd00cecc102c836f02 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml @@ -37,6 +37,7 @@ pallet-bridge-messages = { workspace = true } # Cumulus asset-hub-westend-runtime = { workspace = true } bridge-hub-westend-runtime = { workspace = true } +cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } emulated-integration-tests-common = { workspace = true } parachains-common = { workspace = true, default-features = true } 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 3d4d4f58e3b54f2baf8fc9dcf5683e9b3be4532f..cd5e22372f0e645469cdee8c8fb47ab901456ea4 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 @@ -61,8 +61,10 @@ mod imports { LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub, UniversalLocation as PenpalUniversalLocation, }, - PenpalAssetOwner, PenpalBParaPallet as PenpalBPallet, + PenpalAParaPallet as PenpalAPallet, PenpalAssetOwner, + PenpalBParaPallet as PenpalBPallet, }, + rococo_emulated_chain::RococoRelayPallet as RococoPallet, westend_emulated_chain::{ genesis::ED as WESTEND_ED, westend_runtime::xcm_config::XcmConfig as WestendXcmConfig, WestendRelayPallet as WestendPallet, @@ -73,10 +75,11 @@ mod imports { AssetHubWestendParaSender as AssetHubWestendSender, BridgeHubRococoPara as BridgeHubRococo, BridgeHubWestendPara as BridgeHubWestend, BridgeHubWestendParaReceiver as BridgeHubWestendReceiver, - BridgeHubWestendParaSender as BridgeHubWestendSender, PenpalBPara as PenpalB, + BridgeHubWestendParaSender as BridgeHubWestendSender, PenpalAPara as PenpalA, + PenpalAParaReceiver as PenpalAReceiver, PenpalBPara as PenpalB, PenpalBParaReceiver as PenpalBReceiver, PenpalBParaSender as PenpalBSender, - WestendRelay as Westend, WestendRelayReceiver as WestendReceiver, - WestendRelaySender as WestendSender, + RococoRelay as Rococo, RococoRelayReceiver as RococoReceiver, WestendRelay as Westend, + WestendRelayReceiver as WestendReceiver, WestendRelaySender as WestendSender, }; pub const ASSET_ID: u32 = 1; 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 a73c1280b406a7154c05707a4dfe53946ac9c1c9..6da4de550fb5f5dcf5b949ccd5297fd4e2897f01 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 @@ -14,6 +14,7 @@ // limitations under the License. use crate::{create_pool_with_native_on, tests::*}; +use emulated_integration_tests_common::macros::Dmp; use xcm::latest::AssetTransferFilter; fn send_assets_over_bridge<F: FnOnce()>(send_fn: F) { @@ -41,6 +42,12 @@ fn set_up_wnds_for_penpal_westend_through_ahw_to_ahr( let wnd_at_westend_parachains = wnd_at_ah_westend(); let wnd_at_asset_hub_rococo = bridged_wnd_at_ah_rococo(); create_foreign_on_ah_rococo(wnd_at_asset_hub_rococo.clone(), true); + create_pool_with_native_on!( + AssetHubRococo, + wnd_at_asset_hub_rococo.clone(), + true, + AssetHubRococoSender::get() + ); let penpal_location = AssetHubWestend::sibling_location_of(PenpalB::para_id()); let sov_penpal_on_ahw = AssetHubWestend::sovereign_account_id_of(penpal_location); @@ -416,6 +423,295 @@ fn send_wnds_from_penpal_westend_through_asset_hub_westend_to_asset_hub_rococo() assert!(wnds_in_reserve_on_ahw_after <= wnds_in_reserve_on_ahw_before + amount); } +#[test] +fn send_wnds_from_penpal_westend_through_asset_hub_westend_to_asset_hub_rococo_to_penpal_rococo() { + let amount = ASSET_HUB_WESTEND_ED * 10_000_000; + let sender = PenpalBSender::get(); + let receiver = PenpalAReceiver::get(); + let local_asset_hub = PenpalB::sibling_location_of(AssetHubWestend::para_id()); + // create foreign WND on remote paras + let (wnd_at_westend_parachains, wnd_at_rococo_parachains) = + set_up_wnds_for_penpal_westend_through_ahw_to_ahr(&sender, amount); + let asset_owner: AccountId = AssetHubRococo::account_id_of(ALICE); + // create foreign WND on remote paras + PenpalA::force_create_foreign_asset( + wnd_at_rococo_parachains.clone(), + asset_owner.clone(), + true, + ASSET_MIN_BALANCE, + vec![], + ); + // Configure destination Penpal chain to trust its sibling AH as reserve of bridged WND + PenpalA::execute_with(|| { + assert_ok!(<PenpalA as Chain>::System::set_storage( + <PenpalA as Chain>::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + wnd_at_rococo_parachains.encode(), + )], + )); + }); + create_pool_with_native_on!(PenpalA, wnd_at_rococo_parachains.clone(), true, asset_owner); + + let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( + ByGenesis(ROCOCO_GENESIS_HASH), + AssetHubRococo::para_id(), + ); + let wnds_in_reserve_on_ahw_before = + <AssetHubWestend as Chain>::account_data_of(sov_ahr_on_ahw.clone()).free; + let sender_wnds_before = PenpalB::execute_with(|| { + type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets; + <ForeignAssets as Inspect<_>>::balance(wnd_at_westend_parachains.clone(), &sender) + }); + let receiver_wnds_before = PenpalA::execute_with(|| { + type Assets = <PenpalA as PenpalAPallet>::ForeignAssets; + <Assets as Inspect<_>>::balance(wnd_at_rococo_parachains.clone(), &receiver) + }); + + // Send WNDs over bridge + { + let destination = asset_hub_rococo_location(); + let assets: Assets = (wnd_at_westend_parachains.clone(), amount).into(); + let asset_transfer_type = TransferType::RemoteReserve(local_asset_hub.clone().into()); + let fees_id: AssetId = wnd_at_westend_parachains.clone().into(); + let fees_transfer_type = TransferType::RemoteReserve(local_asset_hub.into()); + let remote_fees = (bridged_wnd_at_ah_rococo(), amount / 2).into(); + let beneficiary: Location = + AccountId32Junction { network: None, id: receiver.clone().into() }.into(); + let custom_xcm_on_penpal_dest = Xcm::<()>(vec![ + BuyExecution { fees: remote_fees, weight_limit: Unlimited }, + DepositAsset { assets: Wild(AllCounted(assets.len() as u32)), beneficiary }, + ]); + let pp_loc_from_ah = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let custom_xcm_on_remote_ah = Xcm::<()>(vec![ + // BuyExecution { fees: remote_fees, weight_limit: Unlimited }, + DepositReserveAsset { + assets: Wild(AllCounted(1)), + dest: pp_loc_from_ah, + xcm: custom_xcm_on_penpal_dest, + }, + ]); + send_assets_from_penpal_westend_through_westend_ah_to_rococo_ah( + destination, + (assets, asset_transfer_type), + (fees_id, fees_transfer_type), + custom_xcm_on_remote_ah, + ); + } + + // process AHR incoming message and check events + AssetHubRococo::execute_with(|| { + type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent; + assert_expected_events!( + AssetHubRococo, + vec![ + // issue WNDs on AHR + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + PenpalA::execute_with(|| { + PenpalA::assert_xcmp_queue_success(None); + }); + + let sender_wnds_after = PenpalB::execute_with(|| { + type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets; + <ForeignAssets as Inspect<_>>::balance(wnd_at_westend_parachains, &sender) + }); + let receiver_wnds_after = PenpalA::execute_with(|| { + type Assets = <PenpalA as PenpalAPallet>::ForeignAssets; + <Assets as Inspect<_>>::balance(wnd_at_rococo_parachains, &receiver) + }); + let wnds_in_reserve_on_ahw_after = + <AssetHubWestend as Chain>::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); +} + +#[test] +fn send_wnds_from_westend_relay_through_asset_hub_westend_to_asset_hub_rococo_to_penpal_rococo() { + let amount = WESTEND_ED * 1_000; + let sender = WestendSender::get(); + let receiver = PenpalAReceiver::get(); + let local_asset_hub = Westend::child_location_of(AssetHubWestend::para_id()); + + let wnd_at_westend_parachains = wnd_at_ah_westend(); + let wnd_at_rococo_parachains = bridged_wnd_at_ah_rococo(); + // create foreign WND on AH Rococo + create_foreign_on_ah_rococo(wnd_at_rococo_parachains.clone(), true); + create_pool_with_native_on!( + AssetHubRococo, + wnd_at_rococo_parachains.clone(), + true, + AssetHubRococoSender::get() + ); + // create foreign WND on Penpal Rococo + let asset_owner: AccountId = AssetHubRococo::account_id_of(ALICE); + PenpalA::force_create_foreign_asset( + wnd_at_rococo_parachains.clone(), + asset_owner.clone(), + true, + ASSET_MIN_BALANCE, + vec![], + ); + // Configure destination Penpal chain to trust its sibling AH as reserve of bridged WND + PenpalA::execute_with(|| { + assert_ok!(<PenpalA as Chain>::System::set_storage( + <PenpalA as Chain>::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + wnd_at_rococo_parachains.encode(), + )], + )); + }); + create_pool_with_native_on!(PenpalA, wnd_at_rococo_parachains.clone(), true, asset_owner); + + Westend::execute_with(|| { + let root_origin = <Westend as Chain>::RuntimeOrigin::root(); + <Westend as WestendPallet>::XcmPallet::force_xcm_version( + root_origin, + bx!(local_asset_hub.clone()), + XCM_VERSION, + ) + }) + .unwrap(); + AssetHubRococo::force_xcm_version( + AssetHubRococo::sibling_location_of(PenpalA::para_id()), + XCM_VERSION, + ); + + let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( + ByGenesis(ROCOCO_GENESIS_HASH), + AssetHubRococo::para_id(), + ); + let wnds_in_reserve_on_ahw_before = + <AssetHubWestend as Chain>::account_data_of(sov_ahr_on_ahw.clone()).free; + let sender_wnds_before = <Westend as Chain>::account_data_of(sender.clone()).free; + let receiver_wnds_before = PenpalA::execute_with(|| { + type Assets = <PenpalA as PenpalAPallet>::ForeignAssets; + <Assets as Inspect<_>>::balance(wnd_at_rococo_parachains.clone(), &receiver) + }); + + // Send WNDs from Westend to AHW over bridge to AHR then onto Penpal parachain + { + let beneficiary: Location = + AccountId32Junction { network: None, id: receiver.clone().into() }.into(); + // executes on Westend Relay + let kusama_xcm = Xcm::<()>(vec![ + WithdrawAsset((Location::here(), amount).into()), + SetFeesMode { jit_withdraw: true }, + InitiateTeleport { + assets: Wild(AllCounted(1)), + dest: local_asset_hub, + // executes on Westend Asset Hub + xcm: Xcm::<()>(vec![ + BuyExecution { + fees: (wnd_at_westend_parachains, amount / 2).into(), + weight_limit: Unlimited, + }, + DepositReserveAsset { + assets: Wild(AllCounted(1)), + dest: asset_hub_rococo_location(), + // executes on Rococo Asset Hub + xcm: Xcm::<()>(vec![ + BuyExecution { + fees: (wnd_at_rococo_parachains.clone(), amount / 2).into(), + weight_limit: Unlimited, + }, + DepositReserveAsset { + assets: Wild(AllCounted(1)), + dest: AssetHubRococo::sibling_location_of(PenpalA::para_id()), + // executes on Rococo Penpal + xcm: Xcm::<()>(vec![ + BuyExecution { + fees: (wnd_at_rococo_parachains.clone(), amount / 2).into(), + weight_limit: Unlimited, + }, + DepositAsset { assets: Wild(AllCounted(1)), beneficiary }, + ]), + }, + ]), + }, + ]), + }, + ]); + send_assets_over_bridge(|| { + // send message over bridge + assert_ok!(Westend::execute_with(|| { + Dmp::<<Westend as Chain>::Runtime>::make_parachain_reachable( + AssetHubWestend::para_id(), + ); + let signed_origin = <Westend as Chain>::RuntimeOrigin::signed(WestendSender::get()); + <Westend as WestendPallet>::XcmPallet::execute( + signed_origin, + bx!(xcm::VersionedXcm::V5(kusama_xcm.into())), + Weight::MAX, + ) + })); + AssetHubWestend::execute_with(|| { + type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; + assert_expected_events!( + AssetHubWestend, + vec![ + // 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 { .. } + ) => {}, + ] + ); + }); + }); + } + + // process AHR incoming message and check events + AssetHubRococo::execute_with(|| { + type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent; + assert_expected_events!( + AssetHubRococo, + vec![ + // issue WNDs on AHR + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + PenpalA::execute_with(|| { + PenpalA::assert_xcmp_queue_success(None); + }); + + let sender_wnds_after = <Westend as Chain>::account_data_of(sender.clone()).free; + let receiver_wnds_after = PenpalA::execute_with(|| { + type Assets = <PenpalA as PenpalAPallet>::ForeignAssets; + <Assets as Inspect<_>>::balance(wnd_at_rococo_parachains, &receiver) + }); + let wnds_in_reserve_on_ahw_after = + <AssetHubWestend as Chain>::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); +} + #[test] fn send_back_rocs_from_penpal_westend_through_asset_hub_westend_to_asset_hub_rococo() { let roc_at_westend_parachains = bridged_roc_at_ah_westend(); @@ -429,8 +725,8 @@ fn send_back_rocs_from_penpal_westend_through_asset_hub_westend_to_asset_hub_roc // set up ROCs for transfer let penpal_location = AssetHubWestend::sibling_location_of(PenpalB::para_id()); - let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of(penpal_location); - let prefund_accounts = vec![(sov_penpal_on_ahr, amount * 2)]; + let sov_penpal_on_ahw = AssetHubWestend::sovereign_account_id_of(penpal_location); + let prefund_accounts = vec![(sov_penpal_on_ahw, amount * 2)]; create_foreign_on_ah_westend(roc_at_westend_parachains.clone(), true, prefund_accounts); let asset_owner: AccountId = AssetHubWestend::account_id_of(ALICE); PenpalB::force_create_foreign_asset( @@ -543,6 +839,372 @@ fn send_back_rocs_from_penpal_westend_through_asset_hub_westend_to_asset_hub_roc assert!(receiver_rocs_after <= receiver_rocs_before + amount); } +#[test] +fn send_back_rocs_from_penpal_westend_through_asset_hub_westend_to_asset_hub_rococo_to_penpal_rococo( +) { + let roc_at_westend_parachains = bridged_roc_at_ah_westend(); + let roc_at_rococo_parachains = Location::parent(); + let amount = ASSET_HUB_WESTEND_ED * 10_000_000; + let sender = PenpalBSender::get(); + let receiver = PenpalAReceiver::get(); + + // set up ROCs for transfer + let penpal_location = AssetHubWestend::sibling_location_of(PenpalB::para_id()); + let sov_penpal_on_ahw = AssetHubWestend::sovereign_account_id_of(penpal_location); + let prefund_accounts = vec![(sov_penpal_on_ahw.clone(), amount * 2)]; + create_foreign_on_ah_westend(roc_at_westend_parachains.clone(), true, prefund_accounts); + create_pool_with_native_on!( + AssetHubWestend, + roc_at_westend_parachains.clone(), + true, + AssetHubRococoSender::get() + ); + let asset_owner: AccountId = AssetHubWestend::account_id_of(ALICE); + // Fund WNDs on Westend Penpal + PenpalB::mint_foreign_asset( + <PenpalB as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()), + Location::parent(), + sender.clone(), + amount, + ); + // Create and fund bridged ROCs on Westend Penpal + PenpalB::force_create_foreign_asset( + roc_at_westend_parachains.clone(), + asset_owner.clone(), + true, + ASSET_MIN_BALANCE, + vec![(sender.clone(), amount * 2)], + ); + // Configure source Penpal chain to trust local AH as reserve of bridged ROC + PenpalB::execute_with(|| { + assert_ok!(<PenpalB as Chain>::System::set_storage( + <PenpalB as Chain>::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + roc_at_westend_parachains.encode(), + )], + )); + }); + + // 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( + ByGenesis(WESTEND_GENESIS_HASH), + AssetHubWestend::para_id(), + ); + AssetHubRococo::fund_accounts(vec![(sov_ahw_on_ahr.clone(), amount * 2)]); + + // balances before + let sender_rocs_before = PenpalB::execute_with(|| { + type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets; + <ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains.clone().into(), &sender) + }); + let receiver_rocs_before = PenpalA::execute_with(|| { + type Assets = <PenpalA as PenpalAPallet>::ForeignAssets; + <Assets as Inspect<_>>::balance(roc_at_rococo_parachains.clone(), &receiver) + }); + + // send ROCs over the bridge, all fees paid with ROC along the way + { + let local_asset_hub = PenpalB::sibling_location_of(AssetHubWestend::para_id()); + let beneficiary: Location = + AccountId32Junction { network: None, id: receiver.clone().into() }.into(); + // executes on Penpal Westend + let xcm = Xcm::<()>(vec![ + WithdrawAsset((roc_at_westend_parachains.clone(), amount).into()), + SetFeesMode { jit_withdraw: true }, + InitiateReserveWithdraw { + assets: Wild(AllCounted(1)), + reserve: local_asset_hub, + // executes on Westend Asset Hub + xcm: Xcm::<()>(vec![ + BuyExecution { + fees: (roc_at_westend_parachains.clone(), amount / 2).into(), + weight_limit: Unlimited, + }, + InitiateReserveWithdraw { + assets: Wild(AllCounted(1)), + reserve: asset_hub_rococo_location(), + // executes on Rococo Asset Hub + xcm: Xcm::<()>(vec![ + BuyExecution { + fees: (roc_at_rococo_parachains.clone(), amount / 2).into(), + weight_limit: Unlimited, + }, + DepositReserveAsset { + assets: Wild(AllCounted(1)), + dest: AssetHubRococo::sibling_location_of(PenpalA::para_id()), + // executes on Rococo Penpal + xcm: Xcm::<()>(vec![ + BuyExecution { + fees: (roc_at_rococo_parachains.clone(), amount / 2).into(), + weight_limit: Unlimited, + }, + DepositAsset { assets: Wild(AllCounted(1)), beneficiary }, + ]), + }, + ]), + }, + ]), + }, + ]); + send_assets_over_bridge(|| { + // send message over bridge + assert_ok!(PenpalB::execute_with(|| { + let signed_origin = <PenpalB as Chain>::RuntimeOrigin::signed(sender.clone()); + <PenpalB as PenpalBPallet>::PolkadotXcm::execute( + signed_origin, + bx!(xcm::VersionedXcm::V5(xcm.into())), + Weight::MAX, + ) + })); + AssetHubWestend::execute_with(|| { + type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount to reserve transfer is withdrawn from Penpal's sovereign account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, .. } + ) => { + asset_id: asset_id == &roc_at_westend_parachains, + owner: owner == &sov_penpal_on_ahw, + }, + RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + ) => {}, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + }); + } + + // process AHR incoming message and check events + AssetHubRococo::execute_with(|| { + type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent; + assert_expected_events!( + AssetHubRococo, + vec![ + // burn ROCs from AHW's SA on AHR + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, .. } + ) => { + who: *who == sov_ahw_on_ahr.clone().into(), + }, + // sent message to sibling Penpal + RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + ) => {}, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + PenpalA::execute_with(|| { + PenpalA::assert_xcmp_queue_success(None); + }); + + let sender_rocs_after = PenpalB::execute_with(|| { + type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets; + <ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains.into(), &sender) + }); + let receiver_rocs_after = PenpalA::execute_with(|| { + type Assets = <PenpalA as PenpalAPallet>::ForeignAssets; + <Assets as Inspect<_>>::balance(roc_at_rococo_parachains.clone(), &receiver) + }); + + // Sender's balance is reduced by sent "amount" + assert_eq!(sender_rocs_after, sender_rocs_before - amount); + // Receiver's balance is increased by no more than "amount" + assert!(receiver_rocs_after > receiver_rocs_before); + assert!(receiver_rocs_after <= receiver_rocs_before + amount); +} + +#[test] +fn send_back_rocs_from_penpal_westend_through_asset_hub_westend_to_asset_hub_rococo_to_rococo_relay( +) { + let roc_at_westend_parachains = bridged_roc_at_ah_westend(); + let roc_at_rococo_parachains = Location::parent(); + let amount = ASSET_HUB_WESTEND_ED * 10_000_000; + let sender = PenpalBSender::get(); + let receiver = RococoReceiver::get(); + + // set up ROCs for transfer + let penpal_location = AssetHubWestend::sibling_location_of(PenpalB::para_id()); + let sov_penpal_on_ahw = AssetHubWestend::sovereign_account_id_of(penpal_location); + let prefund_accounts = vec![(sov_penpal_on_ahw.clone(), amount * 2)]; + create_foreign_on_ah_westend(roc_at_westend_parachains.clone(), true, prefund_accounts); + create_pool_with_native_on!( + AssetHubWestend, + roc_at_westend_parachains.clone(), + true, + AssetHubRococoSender::get() + ); + let asset_owner: AccountId = AssetHubWestend::account_id_of(ALICE); + // Fund WNDs on Westend Penpal + PenpalB::mint_foreign_asset( + <PenpalB as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()), + Location::parent(), + sender.clone(), + amount, + ); + // Create and fund bridged ROCs on Westend Penpal + PenpalB::force_create_foreign_asset( + roc_at_westend_parachains.clone(), + asset_owner.clone(), + true, + ASSET_MIN_BALANCE, + vec![(sender.clone(), amount * 2)], + ); + // Configure source Penpal chain to trust local AH as reserve of bridged ROC + PenpalB::execute_with(|| { + assert_ok!(<PenpalB as Chain>::System::set_storage( + <PenpalB as Chain>::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + roc_at_westend_parachains.encode(), + )], + )); + }); + + // 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( + ByGenesis(WESTEND_GENESIS_HASH), + AssetHubWestend::para_id(), + ); + AssetHubRococo::fund_accounts(vec![(sov_ahw_on_ahr.clone(), amount * 2)]); + + // fund Rococo Relay check account so we can teleport back to it + Rococo::fund_accounts(vec![(<Rococo as RococoPallet>::XcmPallet::check_account(), amount)]); + + // balances before + let sender_rocs_before = PenpalB::execute_with(|| { + type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets; + <ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains.clone().into(), &sender) + }); + let receiver_rocs_before = <Rococo as Chain>::account_data_of(receiver.clone()).free; + + // send ROCs over the bridge, all fees paid with ROC along the way + { + let local_asset_hub = PenpalB::sibling_location_of(AssetHubWestend::para_id()); + let beneficiary: Location = + AccountId32Junction { network: None, id: receiver.clone().into() }.into(); + // executes on Penpal Westend + let xcm = Xcm::<()>(vec![ + WithdrawAsset((roc_at_westend_parachains.clone(), amount).into()), + SetFeesMode { jit_withdraw: true }, + InitiateReserveWithdraw { + assets: Wild(AllCounted(1)), + reserve: local_asset_hub, + // executes on Westend Asset Hub + xcm: Xcm::<()>(vec![ + BuyExecution { + fees: (roc_at_westend_parachains.clone(), amount / 2).into(), + weight_limit: Unlimited, + }, + InitiateReserveWithdraw { + assets: Wild(AllCounted(1)), + reserve: asset_hub_rococo_location(), + // executes on Rococo Asset Hub + xcm: Xcm::<()>(vec![ + BuyExecution { + fees: (roc_at_rococo_parachains.clone(), amount / 2).into(), + weight_limit: Unlimited, + }, + InitiateTeleport { + assets: Wild(AllCounted(1)), + dest: Location::parent(), + // executes on Rococo Relay + xcm: Xcm::<()>(vec![ + BuyExecution { + fees: (Location::here(), amount / 2).into(), + weight_limit: Unlimited, + }, + DepositAsset { assets: Wild(AllCounted(1)), beneficiary }, + ]), + }, + ]), + }, + ]), + }, + ]); + send_assets_over_bridge(|| { + // send message over bridge + assert_ok!(PenpalB::execute_with(|| { + let signed_origin = <PenpalB as Chain>::RuntimeOrigin::signed(sender.clone()); + <PenpalB as PenpalBPallet>::PolkadotXcm::execute( + signed_origin, + bx!(xcm::VersionedXcm::V5(xcm.into())), + Weight::MAX, + ) + })); + AssetHubWestend::execute_with(|| { + type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount to reserve transfer is withdrawn from Penpal's sovereign account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, .. } + ) => { + asset_id: asset_id == &roc_at_westend_parachains, + owner: owner == &sov_penpal_on_ahw, + }, + RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + ) => {}, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + }); + } + + // process AHR incoming message and check events + AssetHubRococo::execute_with(|| { + type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent; + assert_expected_events!( + AssetHubRococo, + vec![ + // burn ROCs from AHW's SA on AHR + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, .. } + ) => { + who: *who == sov_ahw_on_ahr.clone().into(), + }, + // sent message to Rococo Relay + RuntimeEvent::ParachainSystem( + cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. } + ) => {}, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + let sender_rocs_after = PenpalB::execute_with(|| { + type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets; + <ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains.into(), &sender) + }); + let receiver_rocs_after = <Rococo as Chain>::account_data_of(receiver.clone()).free; + + // Sender's balance is reduced by sent "amount" + assert_eq!(sender_rocs_after, sender_rocs_before - amount); + // Receiver's balance is increased by no more than "amount" + assert!(receiver_rocs_after > receiver_rocs_before); + assert!(receiver_rocs_after <= receiver_rocs_before + amount); +} + #[test] fn dry_run_transfer_to_rococo_sends_xcm_to_bridge_hub() { test_dry_run_transfer_across_pk_bridge!(