diff --git a/CHANGELOG.md b/CHANGELOG.md index dfc3118520..c0e3ac1198 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Include the Core and Salary pallets into the Fellowship proxy ([polkadot-fellows/runtimes#454](https://github.com/polkadot-fellows/runtimes/pull/454)) - Add new community democracy and treasuries pallets to Encointer ([polkadot-fellows/runtimes#456](https://github.com/polkadot-fellows/runtimes/pull/456)) - Change target block time for Encointer to 6s ([polkadot-fellows/runtimes#462](https://github.com/polkadot-fellows/runtimes/pull/462)) +- Asset Hubs: allow Polkadot, Kusama and Ethereum assets across P<>K bridge ([polkadot-fellows/runtimes#421](https://github.com/polkadot-fellows/runtimes/pull/421)). ### Fixed diff --git a/Cargo.lock b/Cargo.lock index ea1847038f..30daa2294e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -680,6 +680,7 @@ dependencies = [ "primitive-types", "scale-info", "serde_json", + "snowbridge-router-primitives", "sp-api", "sp-block-builder", "sp-consensus-aura", diff --git a/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs b/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs index 09fe2aa0e3..c0d1f71c7c 100644 --- a/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs +++ b/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs @@ -15,18 +15,25 @@ mod genesis; pub use genesis::{genesis, PenpalAssetOwner, ED, PARA_ID_A, PARA_ID_B}; -pub use penpal_runtime::xcm_config::{ - CustomizableAssetFromSystemAssetHub, LocalReservableFromAssetHub, LocalTeleportableToAssetHub, - XcmConfig, ASSETS_PALLET_ID, RESERVABLE_ASSET_ID, TELEPORTABLE_ASSET_ID, +pub use penpal_runtime::{ + self, + xcm_config::{ + CustomizableAssetFromSystemAssetHub, LocalReservableFromAssetHub, + LocalTeleportableToAssetHub, RelayNetworkId as PenpalRelayNetworkId, XcmConfig, + ASSETS_PALLET_ID, RESERVABLE_ASSET_ID, TELEPORTABLE_ASSET_ID, + }, }; // Substrate use frame_support::traits::OnInitialize; +use sp_core::Encode; // Cumulus use emulated_integration_tests_common::{ impl_accounts_helpers_for_parachain, impl_assert_events_helpers_for_parachain, - impl_assets_helpers_for_parachain, impl_foreign_assets_helpers_for_parachain, impls::Parachain, + impl_assets_helpers_for_parachain, impl_foreign_assets_helpers_for_parachain, + impl_xcm_helpers_for_parachain, + impls::{NetworkId, Parachain}, xcm_emulator::decl_test_parachains, }; @@ -36,6 +43,10 @@ decl_test_parachains! { genesis = genesis(PARA_ID_A), on_init = { penpal_runtime::AuraExt::on_initialize(1); + frame_support::assert_ok!(penpal_runtime::System::set_storage( + penpal_runtime::RuntimeOrigin::root(), + vec![(PenpalRelayNetworkId::key().to_vec(), NetworkId::Kusama.encode())], + )); }, runtime = penpal_runtime, core = { @@ -55,6 +66,10 @@ decl_test_parachains! { genesis = genesis(PARA_ID_B), on_init = { penpal_runtime::AuraExt::on_initialize(1); + frame_support::assert_ok!(penpal_runtime::System::set_storage( + penpal_runtime::RuntimeOrigin::root(), + vec![(PenpalRelayNetworkId::key().to_vec(), NetworkId::Polkadot.encode())], + )); }, runtime = penpal_runtime, core = { @@ -75,9 +90,11 @@ decl_test_parachains! { // Penpal implementation impl_accounts_helpers_for_parachain!(PenpalA); impl_accounts_helpers_for_parachain!(PenpalB); +impl_assert_events_helpers_for_parachain!(PenpalA); +impl_assert_events_helpers_for_parachain!(PenpalB); impl_assets_helpers_for_parachain!(PenpalA); impl_assets_helpers_for_parachain!(PenpalB); impl_foreign_assets_helpers_for_parachain!(PenpalA, xcm::latest::Location); impl_foreign_assets_helpers_for_parachain!(PenpalB, xcm::latest::Location); -impl_assert_events_helpers_for_parachain!(PenpalA); -impl_assert_events_helpers_for_parachain!(PenpalB); +impl_xcm_helpers_for_parachain!(PenpalA); +impl_xcm_helpers_for_parachain!(PenpalB); diff --git a/integration-tests/emulated/networks/kusama-polkadot-system/src/lib.rs b/integration-tests/emulated/networks/kusama-polkadot-system/src/lib.rs index 247ace6b3e..eebf786e02 100644 --- a/integration-tests/emulated/networks/kusama-polkadot-system/src/lib.rs +++ b/integration-tests/emulated/networks/kusama-polkadot-system/src/lib.rs @@ -97,5 +97,6 @@ decl_test_sender_receiver_accounts_parameter_types! { PolkadotRelay { sender: ALICE, receiver: BOB }, AssetHubPolkadotPara { sender: ALICE, receiver: BOB }, BridgeHubPolkadotPara { sender: ALICE, receiver: BOB }, - PenpalAPara { sender: ALICE, receiver: BOB } + PenpalAPara { sender: ALICE, receiver: BOB }, + PenpalBPara { sender: ALICE, receiver: BOB } } diff --git a/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/lib.rs b/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/lib.rs index 674bf32bb9..8b8610d4f7 100644 --- a/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/lib.rs +++ b/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/lib.rs @@ -26,6 +26,7 @@ pub use xcm::{ NetworkId::{Kusama as KusamaId, Polkadot as PolkadotId}, }, }; +pub use xcm_executor::traits::TransferType; // Bridges pub use bp_messages::LaneId; @@ -40,37 +41,42 @@ pub use emulated_integration_tests_common::{ RelayChain as Relay, Test, TestArgs, TestContext, TestExt, }, xcm_helpers::{xcm_transact_paid_execution, xcm_transact_unpaid_execution}, - PROOF_SIZE_THRESHOLD, REF_TIME_THRESHOLD, XCM_V3, + ASSETS_PALLET_ID, PROOF_SIZE_THRESHOLD, REF_TIME_THRESHOLD, XCM_V3, }; pub use kusama_polkadot_system_emulated_network::{ asset_hub_kusama_emulated_chain::{ genesis::ED as ASSET_HUB_KUSAMA_ED, AssetHubKusamaParaPallet as AssetHubKusamaPallet, }, asset_hub_polkadot_emulated_chain::{ - genesis::ED as ASSET_HUB_POLKADOT_ED, AssetHubPolkadotParaPallet as AssetHubPolkadotPallet, + genesis::{AssetHubPolkadotAssetOwner, ED as ASSET_HUB_POLKADOT_ED}, + AssetHubPolkadotParaPallet as AssetHubPolkadotPallet, }, bridge_hub_kusama_emulated_chain::{ genesis::ED as BRIDGE_HUB_KUSAMA_ED, BridgeHubKusamaParaPallet as BridgeHubKusamaPallet, }, kusama_emulated_chain::{genesis::ED as KUSAMA_ED, KusamaRelayPallet as KusamaPallet}, + penpal_emulated_chain::{ + penpal_runtime::xcm_config::{ + CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub, + UniversalLocation as PenpalUniversalLocation, + }, + PenpalAParaPallet as PenpalAPallet, PenpalAssetOwner, + }, AssetHubKusamaPara as AssetHubKusama, AssetHubKusamaParaReceiver as AssetHubKusamaReceiver, AssetHubKusamaParaSender as AssetHubKusamaSender, AssetHubPolkadotPara as AssetHubPolkadot, AssetHubPolkadotParaReceiver as AssetHubPolkadotReceiver, AssetHubPolkadotParaSender as AssetHubPolkadotSender, BridgeHubKusamaPara as BridgeHubKusama, + BridgeHubKusamaParaReceiver as BridgeHubKusamaReceiver, BridgeHubKusamaParaSender as BridgeHubKusamaSender, BridgeHubPolkadotPara as BridgeHubPolkadot, KusamaRelay as Kusama, KusamaRelayReceiver as KusamaReceiver, - KusamaRelaySender as KusamaSender, -}; -pub use kusama_system_emulated_network::{ - penpal_emulated_chain::PenpalAParaPallet as PenpalAPallet, - BridgeHubKusamaParaReceiver as BridgeHubKusamaReceiver, PenpalAPara as PenpalA, + KusamaRelaySender as KusamaSender, PenpalAPara as PenpalA, PenpalAParaReceiver as PenpalAReceiver, PenpalAParaSender as PenpalASender, }; pub use parachains_common::{AccountId, Balance}; pub const ASSET_ID: u32 = 1; pub const ASSET_MIN_BALANCE: u128 = 1000; -pub const ASSETS_PALLET_ID: u8 = 50; +pub const USDT_ID: u32 = 1984; #[cfg(test)] mod tests; diff --git a/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/asset_transfers.rs b/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/asset_transfers.rs index 2d4a33ac9d..ed19440c46 100644 --- a/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/asset_transfers.rs +++ b/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/asset_transfers.rs @@ -14,136 +14,140 @@ // limitations under the License. use crate::tests::*; -use bridge_hub_kusama_runtime::RuntimeEvent; -use frame_support::{dispatch::RawOrigin, traits::fungible::Mutate}; -use xcm_runtime_apis::dry_run::runtime_decl_for_dry_run_api::DryRunApiV1; -fn send_asset_from_asset_hub_kusama_to_asset_hub_polkadot(id: Location, amount: u128) { - let destination = asset_hub_polkadot_location(); - - // fund the AHK's SA on BHK for paying bridge transport fees +fn send_assets_over_bridge(send_fn: F) { + // fund the KAH's SA on BHR for paying bridge transport fees BridgeHubKusama::fund_para_sovereign(AssetHubKusama::para_id(), 10_000_000_000_000u128); // set XCM versions - AssetHubKusama::force_xcm_version(destination.clone(), XCM_VERSION); + let local_asset_hub = PenpalA::sibling_location_of(AssetHubKusama::para_id()); + PenpalA::force_xcm_version(local_asset_hub.clone(), XCM_VERSION); + AssetHubKusama::force_xcm_version(asset_hub_polkadot_location(), XCM_VERSION); BridgeHubKusama::force_xcm_version(bridge_hub_polkadot_location(), XCM_VERSION); // send message over bridge - assert_ok!(send_asset_from_asset_hub_kusama(destination, (id, amount))); + send_fn(); + + // process and verify intermediary hops assert_bridge_hub_kusama_message_accepted(true); assert_bridge_hub_polkadot_message_received(); } -fn dry_run_send_asset_from_asset_hub_kusama_to_asset_hub_polkadot(id: Location, amount: u128) { - let destination = asset_hub_polkadot_location(); - - // fund the AHK's SA on BHK for paying bridge transport fees - BridgeHubKusama::fund_para_sovereign(AssetHubKusama::para_id(), 10_000_000_000_000u128); - - // set XCM versions - AssetHubKusama::force_xcm_version(destination.clone(), XCM_VERSION); - BridgeHubKusama::force_xcm_version(bridge_hub_polkadot_location(), XCM_VERSION); - - let beneficiary: Location = - AccountId32Junction { id: AssetHubPolkadotReceiver::get().into(), network: None }.into(); - let assets: Assets = (id, amount).into(); - let fee_asset_item = 0; - let call = - send_asset_from_asset_hub_kusama_call(destination, beneficiary, assets, fee_asset_item); - - // `remote_message` should contain `ExportMessage` - let remote_message = AssetHubKusama::execute_with(|| { - type Runtime = ::Runtime; - type OriginCaller = ::OriginCaller; - - let origin = OriginCaller::system(RawOrigin::Signed(AssetHubKusamaSender::get())); - let result = Runtime::dry_run_call(origin, call).unwrap(); - - // We filter the result to get only the messages we are interested in. - let (_, messages_to_query) = result - .forwarded_xcms - .into_iter() - .find(|(destination, _)| { - *destination == VersionedLocation::from(Location::new(1, [Parachain(1002)])) - }) - .unwrap(); - assert_eq!(messages_to_query.len(), 1); - - messages_to_query[0].clone() - }); +fn set_up_ksm_for_penpal_kusama_through_kah_to_pah( + sender: &AccountId, + amount: u128, +) -> (Location, v3::Location) { + let ksm_at_kusama_parachains = ksm_at_ah_kusama(); + let ksm_at_asset_hub_polkadot = v3::Location::try_from(bridged_ksm_at_ah_polkadot()).unwrap(); + create_foreign_on_ah_polkadot(ksm_at_asset_hub_polkadot, true); + + let penpal_location = AssetHubKusama::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_kah = AssetHubKusama::sovereign_account_id_of(penpal_location); + // fund Penpal's sovereign account on AssetHub + AssetHubKusama::fund_accounts(vec![(sov_penpal_on_kah, amount * 2)]); + // fund Penpal's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + ksm_at_kusama_parachains.clone(), + sender.clone(), + amount * 2, + ); + (ksm_at_kusama_parachains, ksm_at_asset_hub_polkadot) +} - // dry run extracted `remote_message` on local BridgeHub - BridgeHubKusama::execute_with(|| { - type Runtime = ::Runtime; - type RuntimeCall = ::RuntimeCall; - - // We have to do this to turn `VersionedXcm<()>` into `VersionedXcm`. - let xcm_program = VersionedXcm::from(Xcm::::from( - remote_message.clone().try_into().unwrap(), - )); - - // dry run program - let asset_hub_as_seen_by_bridge_hub: Location = Location::new(1, [Parachain(1000)]); - let result = - Runtime::dry_run_xcm(asset_hub_as_seen_by_bridge_hub.into(), xcm_program).unwrap(); - - // check dry run result - assert_ok!(result.execution_result.ensure_complete()); - assert!(result.emitted_events.iter().any(|event| matches!( - event, - RuntimeEvent::BridgePolkadotMessages( - pallet_bridge_messages::Event::MessageAccepted { .. } +fn send_assets_from_penpal_kusama_through_kusama_ah_to_polkadot_ah( + destination: Location, + assets: (Assets, TransferType), + fees: (AssetId, TransferType), + custom_xcm_on_dest: Xcm<()>, +) { + send_assets_over_bridge(|| { + let sov_penpal_on_kah = AssetHubKusama::sovereign_account_id_of( + AssetHubKusama::sibling_location_of(PenpalA::para_id()), + ); + let sov_pah_on_kah = + AssetHubKusama::sovereign_account_of_parachain_on_other_global_consensus( + Polkadot, + AssetHubPolkadot::para_id(), + ); + // send message over bridge + assert_ok!(PenpalA::execute_with(|| { + let signed_origin = ::RuntimeOrigin::signed(PenpalASender::get()); + ::PolkadotXcm::transfer_assets_using_type_and_then( + signed_origin, + bx!(destination.into()), + bx!(assets.0.into()), + bx!(assets.1), + bx!(fees.0.into()), + bx!(fees.1), + bx!(VersionedXcm::from(custom_xcm_on_dest)), + WeightLimit::Unlimited, ) - ))); + })); + // verify intermediary AH Kusama hop + AssetHubKusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubKusama, + vec![ + // Amount to reserve transfer is withdrawn from Penpal's sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, .. } + ) => { + who: *who == sov_penpal_on_kah.clone(), + }, + // Amount deposited in PAH's sovereign account + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == sov_pah_on_kah.clone(), + }, + RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + ) => {}, + ] + ); + }); }); - - // After dry-running we reset. - AssetHubKusama::reset_ext(); - BridgeHubKusama::reset_ext(); } #[test] -fn send_ksms_from_asset_hub_kusama_to_asset_hub_polkadot() { - let ksm_at_asset_hub_kusama: v3::Location = v3::Parent.into(); - let ksm_at_asset_hub_polkadot = - v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Kusama)]); - let owner: AccountId = AssetHubPolkadot::account_id_of(ALICE); - AssetHubPolkadot::force_create_foreign_asset( - ksm_at_asset_hub_polkadot, - owner, - true, - ASSET_MIN_BALANCE, - vec![], - ); +/// Test transfer of KSM from AssetHub Kusama to AssetHub Polkadot. +fn send_ksm_from_asset_hub_kusama_to_asset_hub_polkadot() { + let amount = ASSET_HUB_KUSAMA_ED * 1_000; + let sender = AssetHubKusamaSender::get(); + let receiver = AssetHubPolkadotReceiver::get(); + let ksm_at_asset_hub_kusama = v3::Location::try_from(ksm_at_ah_kusama()).unwrap(); + let bridged_ksm_at_ah_polkadot = v3::Location::try_from(bridged_ksm_at_ah_polkadot()).unwrap(); + + create_foreign_on_ah_polkadot(bridged_ksm_at_ah_polkadot, true); + set_up_pool_with_dot_on_ah_polkadot(bridged_ksm_at_ah_polkadot, true); + let sov_ahp_on_ahk = AssetHubKusama::sovereign_account_of_parachain_on_other_global_consensus( - NetworkId::Polkadot, + Polkadot, AssetHubPolkadot::para_id(), ); - let ksms_in_reserve_on_ahk_before = ::account_data_of(sov_ahp_on_ahk.clone()).free; - let sender_ksms_before = - ::account_data_of(AssetHubKusamaSender::get()).free; - let receiver_ksms_before = AssetHubPolkadot::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(ksm_at_asset_hub_polkadot, &AssetHubPolkadotReceiver::get()) + let sender_ksms_before = ::account_data_of(sender.clone()).free; + let receiver_ksms_before = + foreign_balance_on_ah_polkadot(bridged_ksm_at_ah_polkadot, &receiver); + + let ksm_at_ah_kusama_latest = ksm_at_ah_kusama(); + + // send KSMs, use them for fees + send_assets_over_bridge(|| { + let destination = asset_hub_polkadot_location(); + let assets: Assets = (ksm_at_ah_kusama_latest, amount).into(); + let fee_idx = 0; + assert_ok!(send_assets_from_asset_hub_kusama(destination, assets, fee_idx)); }); - let ksm_at_asset_hub_kusama_latest: Location = ksm_at_asset_hub_kusama.try_into().unwrap(); - let amount = ASSET_HUB_KUSAMA_ED * 1_000; - // First dry-run. - dry_run_send_asset_from_asset_hub_kusama_to_asset_hub_polkadot( - ksm_at_asset_hub_kusama_latest.clone(), - amount, - ); - // Then send. - send_asset_from_asset_hub_kusama_to_asset_hub_polkadot(ksm_at_asset_hub_kusama_latest, amount); + // verify expected events on final destination AssetHubPolkadot::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; assert_expected_events!( AssetHubPolkadot, vec![ - // issue KSMs on AHP + // issue KSMs on PAH RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { asset_id: *asset_id == ksm_at_asset_hub_kusama, owner: *owner == AssetHubPolkadotReceiver::get(), @@ -156,12 +160,8 @@ fn send_ksms_from_asset_hub_kusama_to_asset_hub_polkadot() { ); }); - let sender_ksms_after = - ::account_data_of(AssetHubKusamaSender::get()).free; - let receiver_ksms_after = AssetHubPolkadot::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(ksm_at_asset_hub_polkadot, &AssetHubPolkadotReceiver::get()) - }); + let sender_ksms_after = ::account_data_of(sender.clone()).free; + let receiver_ksms_after = foreign_balance_on_ah_polkadot(bridged_ksm_at_ah_polkadot, &receiver); let ksms_in_reserve_on_ahk_after = ::account_data_of(sov_ahp_on_ahk.clone()).free; @@ -174,58 +174,65 @@ fn send_ksms_from_asset_hub_kusama_to_asset_hub_polkadot() { } #[test] -fn send_dots_from_asset_hub_kusama_to_asset_hub_polkadot() { +/// Send bridged assets "back" from AssetHub Kusama to AssetHub Polkadot. +/// +/// This mix of assets should cover the whole range: +/// - bridged native assets: KSM, +/// - bridged trust-based assets: USDT (exists only on Polkadot, Kusama gets it from Polkadot over +/// bridge), +/// - bridged foreign asset / double-bridged asset (other bridge / Snowfork): wETH (bridged from +/// Ethereum to Polkadot over Snowbridge, then bridged over to Kusama through this bridge). +fn send_back_dot_usdt_and_weth_from_asset_hub_kusama_to_asset_hub_polkadot() { let prefund_amount = 10_000_000_000_000u128; - let dot_at_asset_hub_kusama = - v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Polkadot)]); - let owner: AccountId = AssetHubPolkadot::account_id_of(ALICE); - AssetHubKusama::force_create_foreign_asset( - dot_at_asset_hub_kusama, - owner, - true, - ASSET_MIN_BALANCE, - vec![(AssetHubKusamaSender::get(), prefund_amount)], - ); - - // fund the AHK's SA on AHP with the DOT tokens held in reserve - let sov_ahk_on_ahp = AssetHubPolkadot::sovereign_account_of_parachain_on_other_global_consensus( - NetworkId::Kusama, + let amount_to_send = ASSET_HUB_POLKADOT_ED * 1_000; + let sender = AssetHubKusamaSender::get(); + let receiver = AssetHubPolkadotReceiver::get(); + let dot_at_asset_hub_kusama = v3::Location::try_from(bridged_dot_at_ah_kusama()).unwrap(); + let prefund_accounts = vec![(sender.clone(), prefund_amount)]; + create_foreign_on_ah_kusama(dot_at_asset_hub_kusama, true, prefund_accounts); + + //////////////////////////////////////////////////////////// + // Let's first send back just some DOTs as a simple example + //////////////////////////////////////////////////////////// + + // fund the KAH's SA on PAH with the DOT tokens held in reserve + let sov_kah_on_pah = AssetHubPolkadot::sovereign_account_of_parachain_on_other_global_consensus( + Kusama, AssetHubKusama::para_id(), ); - AssetHubPolkadot::fund_accounts(vec![(sov_ahk_on_ahp.clone(), prefund_amount)]); - - let dots_in_reserve_on_ahp_before = - ::account_data_of(sov_ahk_on_ahp.clone()).free; - assert_eq!(dots_in_reserve_on_ahp_before, prefund_amount); - let sender_dots_before = AssetHubKusama::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(dot_at_asset_hub_kusama, &AssetHubKusamaSender::get()) + AssetHubPolkadot::fund_accounts(vec![(sov_kah_on_pah.clone(), prefund_amount)]); + + let dot_in_reserve_on_pah_before = + ::account_data_of(sov_kah_on_pah.clone()).free; + assert_eq!(dot_in_reserve_on_pah_before, prefund_amount); + + let sender_dot_before = foreign_balance_on_ah_kusama(dot_at_asset_hub_kusama, &sender); + assert_eq!(sender_dot_before, prefund_amount); + let receiver_dot_before = ::account_data_of(receiver.clone()).free; + + // send back DOTs, use them for fees + send_assets_over_bridge(|| { + let destination = asset_hub_polkadot_location(); + let assets: Assets = (bridged_dot_at_ah_kusama(), amount_to_send).into(); + let fee_idx = 0; + assert_ok!(send_assets_from_asset_hub_kusama(destination, assets, fee_idx)); }); - assert_eq!(sender_dots_before, prefund_amount); - let receiver_dots_before = - ::account_data_of(AssetHubPolkadotReceiver::get()).free; - let dot_at_asset_hub_kusama_latest: Location = dot_at_asset_hub_kusama.try_into().unwrap(); - let amount_to_send = ASSET_HUB_POLKADOT_ED * 1_000; - send_asset_from_asset_hub_kusama_to_asset_hub_polkadot( - dot_at_asset_hub_kusama_latest.clone(), - amount_to_send, - ); AssetHubPolkadot::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; assert_expected_events!( AssetHubPolkadot, vec![ - // DOT is withdrawn from AHK's SA on AHP + // DOT is withdrawn from KAH's SA on PAH RuntimeEvent::Balances( pallet_balances::Event::Burned { who, amount } ) => { - who: *who == sov_ahk_on_ahp, + who: *who == sov_kah_on_pah, amount: *amount == amount_to_send, }, // DOTs deposited to beneficiary RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { - who: *who == AssetHubPolkadotReceiver::get(), + who: who == &receiver, }, // message processed successfully RuntimeEvent::MessageQueue( @@ -235,110 +242,162 @@ fn send_dots_from_asset_hub_kusama_to_asset_hub_polkadot() { ); }); - let sender_dots_after = AssetHubKusama::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(dot_at_asset_hub_kusama, &AssetHubKusamaSender::get()) - }); - let receiver_dots_after = - ::account_data_of(AssetHubPolkadotReceiver::get()).free; - let dots_in_reserve_on_ahp_after = - ::account_data_of(sov_ahk_on_ahp).free; + let sender_dot_after = foreign_balance_on_ah_kusama(dot_at_asset_hub_kusama, &sender); + let receiver_dot_after = ::account_data_of(receiver.clone()).free; + let dot_in_reserve_on_pah_after = + ::account_data_of(sov_kah_on_pah).free; // Sender's balance is reduced - assert!(sender_dots_before > sender_dots_after); + assert!(sender_dot_before > sender_dot_after); // Receiver's balance is increased - assert!(receiver_dots_after > receiver_dots_before); + assert!(receiver_dot_after > receiver_dot_before); // Reserve balance is reduced by sent amount - assert_eq!(dots_in_reserve_on_ahp_after, dots_in_reserve_on_ahp_before - amount_to_send); -} - -#[test] -fn send_ksms_from_asset_hub_kusama_to_asset_hub_polkadot_fee_from_pool() { - let ksm_at_asset_hub_kusama: v3::Location = v3::Parent.into(); - let ksm_at_asset_hub_polkadot = - v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Kusama)]); - let owner: AccountId = AssetHubPolkadot::account_id_of(ALICE); - AssetHubPolkadot::force_create_foreign_asset( - ksm_at_asset_hub_polkadot, - owner, - false, - ASSET_MIN_BALANCE, - vec![], + assert_eq!(dot_in_reserve_on_pah_after, dot_in_reserve_on_pah_before - amount_to_send); + + ////////////////////////////////////////////////////////////////// + // Now let's send back over USDTs + wETH (and pay fees with USDT) + ////////////////////////////////////////////////////////////////// + + // wETH has same relative location on both Polkadot and Kusama AssetHubs + let bridged_weth_at_ah = v3::Location::try_from(weth_at_asset_hubs()).unwrap(); + let bridged_usdt_at_asset_hub_kusama = + v3::Location::try_from(bridged_usdt_at_ah_kusama()).unwrap(); + + // set up destination chain AH Polkadot: + // create a DOT/USDT pool to be able to pay fees with USDT (USDT created in genesis) + set_up_pool_with_dot_on_ah_polkadot(usdt_at_ah_polkadot().try_into().unwrap(), false); + // create wETH on Polkadot (IRL it's already created by Snowbridge) + create_foreign_on_ah_polkadot(bridged_weth_at_ah, true); + // prefund KAH's sovereign account on PAH to be able to withdraw USDT and wETH from reserves + let sov_kah_on_pah = AssetHubPolkadot::sovereign_account_of_parachain_on_other_global_consensus( + Kusama, + AssetHubKusama::para_id(), ); - let sov_ahp_on_ahk = AssetHubKusama::sovereign_account_of_parachain_on_other_global_consensus( - NetworkId::Polkadot, - AssetHubPolkadot::para_id(), + AssetHubPolkadot::mint_asset( + ::RuntimeOrigin::signed(AssetHubPolkadotAssetOwner::get()), + USDT_ID, + sov_kah_on_pah.clone(), + amount_to_send * 2, + ); + AssetHubPolkadot::mint_foreign_asset( + ::RuntimeOrigin::signed(AssetHubPolkadot::account_id_of(ALICE)), + bridged_weth_at_ah, + sov_kah_on_pah, + amount_to_send * 2, ); - AssetHubPolkadot::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - - // setup a pool to pay xcm fees with `ksm_at_asset_hub_polkadot` tokens - assert_ok!(::ForeignAssets::mint( - ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), - ksm_at_asset_hub_polkadot, - AssetHubPolkadotSender::get().into(), - 3_000_000_000_000, - )); - - ::Balances::set_balance( - &AssetHubPolkadotSender::get(), - 3_000_000_000_000, - ); - - assert_ok!(::AssetConversion::create_pool( - ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), - Box::new(ksm_at_asset_hub_kusama), - Box::new(ksm_at_asset_hub_polkadot), - )); + // set up source chain AH Kusama: + // create wETH and USDT foreign assets on Kusama and prefund sender's account + let prefund_accounts = vec![(sender.clone(), amount_to_send * 2)]; + create_foreign_on_ah_kusama(bridged_weth_at_ah, true, prefund_accounts.clone()); + create_foreign_on_ah_kusama(bridged_usdt_at_asset_hub_kusama, true, prefund_accounts); - assert_expected_events!( - AssetHubPolkadot, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, - ] - ); - - assert_ok!(::AssetConversion::add_liquidity( - ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), - Box::new(ksm_at_asset_hub_kusama), - Box::new(ksm_at_asset_hub_polkadot), - 1_000_000_000_000, - 2_000_000_000_000, - 1, - 1, - AssetHubPolkadotSender::get() - )); + // check balances before + let receiver_usdts_before = AssetHubPolkadot::execute_with(|| { + type Assets = ::Assets; + >::balance(USDT_ID, &receiver) + }); + let receiver_weth_before = foreign_balance_on_ah_polkadot(bridged_weth_at_ah, &receiver); + + let usdt_id: AssetId = Location::try_from(bridged_usdt_at_asset_hub_kusama).unwrap().into(); + // send USDTs and wETHs + let assets: Assets = vec![ + (usdt_id.clone(), amount_to_send).into(), + (Location::try_from(bridged_weth_at_ah).unwrap(), amount_to_send).into(), + ] + .into(); + // use USDT for fees + let fee = usdt_id; + + // use the more involved transfer extrinsic + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(assets.len() as u32)), + beneficiary: AccountId32Junction { network: None, id: receiver.clone().into() }.into(), + }]); + assert_ok!(AssetHubKusama::execute_with(|| { + ::PolkadotXcm::transfer_assets_using_type_and_then( + ::RuntimeOrigin::signed(sender), + bx!(asset_hub_polkadot_location().into()), + bx!(assets.into()), + bx!(TransferType::DestinationReserve), + bx!(fee.into()), + bx!(TransferType::DestinationReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), + WeightLimit::Unlimited, + ) + })); + // verify hops (also advances the message through the hops) + assert_bridge_hub_kusama_message_accepted(true); + assert_bridge_hub_polkadot_message_received(); + AssetHubPolkadot::execute_with(|| { + AssetHubPolkadot::assert_xcmp_queue_success(None); + }); - assert_expected_events!( - AssetHubPolkadot, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, - ] - ); + let receiver_usdts_after = AssetHubPolkadot::execute_with(|| { + type Assets = ::Assets; + >::balance(USDT_ID, &receiver) }); + let receiver_weth_after = foreign_balance_on_ah_polkadot(bridged_weth_at_ah, &receiver); - let ksms_in_reserve_on_ahk_before = - ::account_data_of(sov_ahp_on_ahk.clone()).free; - let sender_ksms_before = - ::account_data_of(AssetHubKusamaSender::get()).free; - let receiver_ksms_before = AssetHubPolkadot::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(ksm_at_asset_hub_polkadot, &AssetHubPolkadotReceiver::get()) + // Receiver's USDT balance is increased by almost `amount_to_send` (minus fees) + assert!(receiver_usdts_after > receiver_usdts_before); + assert!(receiver_usdts_after < receiver_usdts_before + amount_to_send); + // Receiver's wETH balance is increased by `amount_to_send` + assert_eq!(receiver_weth_after, receiver_weth_before + amount_to_send); +} + +#[test] +fn send_ksm_from_penpal_kusama_through_asset_hub_kusama_to_asset_hub_polkadot() { + let amount = ASSET_HUB_KUSAMA_ED * 10_000_000; + let sender = PenpalASender::get(); + let receiver = AssetHubPolkadotReceiver::get(); + let local_asset_hub = PenpalA::sibling_location_of(AssetHubKusama::para_id()); + let (ksm_at_kusama_parachains, ksm_at_asset_hub_polkadot) = + set_up_ksm_for_penpal_kusama_through_kah_to_pah(&sender, amount); + + let sov_pah_on_kah = AssetHubKusama::sovereign_account_of_parachain_on_other_global_consensus( + Polkadot, + AssetHubPolkadot::para_id(), + ); + let ksm_in_reserve_on_kah_before = + ::account_data_of(sov_pah_on_kah.clone()).free; + let sender_ksm_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(ksm_at_kusama_parachains.clone(), &sender) }); + let receiver_ksm_before = foreign_balance_on_ah_polkadot(ksm_at_asset_hub_polkadot, &receiver); + + // Send KSMs over bridge + { + let destination = asset_hub_polkadot_location(); + let assets: Assets = (ksm_at_kusama_parachains.clone(), amount).into(); + let asset_transfer_type = TransferType::RemoteReserve(local_asset_hub.clone().into()); + let fees_id: AssetId = ksm_at_kusama_parachains.clone().into(); + let fees_transfer_type = TransferType::RemoteReserve(local_asset_hub.into()); + let beneficiary: Location = + AccountId32Junction { network: None, id: receiver.clone().into() }.into(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(assets.len() as u32)), + beneficiary, + }]); + send_assets_from_penpal_kusama_through_kusama_ah_to_polkadot_ah( + destination, + (assets, asset_transfer_type), + (fees_id, fees_transfer_type), + custom_xcm_on_dest, + ); + } - let ksm_at_asset_hub_kusama_latest: Location = ksm_at_asset_hub_kusama.try_into().unwrap(); - let amount = ASSET_HUB_KUSAMA_ED * 1_000; - send_asset_from_asset_hub_kusama_to_asset_hub_polkadot(ksm_at_asset_hub_kusama_latest, amount); + // process PAH incoming message and check events AssetHubPolkadot::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; assert_expected_events!( AssetHubPolkadot, vec![ - // issue KSMs on AHP + // issue KSMs on PAH RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == ksm_at_asset_hub_kusama, - owner: *owner == AssetHubPolkadotReceiver::get(), + asset_id: *asset_id == v3::Location::try_from(ksm_at_kusama_parachains.clone()).unwrap(), + owner: owner == &receiver, }, // message processed successfully RuntimeEvent::MessageQueue( @@ -348,122 +407,122 @@ fn send_ksms_from_asset_hub_kusama_to_asset_hub_polkadot_fee_from_pool() { ); }); - let sender_ksms_after = - ::account_data_of(AssetHubKusamaSender::get()).free; - let receiver_ksms_after = AssetHubPolkadot::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(ksm_at_asset_hub_polkadot, &AssetHubPolkadotReceiver::get()) + let sender_ksm_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(ksm_at_kusama_parachains, &sender) }); - let ksms_in_reserve_on_ahk_after = - ::account_data_of(sov_ahp_on_ahk.clone()).free; + let receiver_ksm_after = foreign_balance_on_ah_polkadot(ksm_at_asset_hub_polkadot, &receiver); + let ksm_in_reserve_on_kah_after = + ::account_data_of(sov_pah_on_kah.clone()).free; // Sender's balance is reduced - assert!(sender_ksms_before >= sender_ksms_after + amount); + assert!(sender_ksm_after < sender_ksm_before); // Receiver's balance is increased - assert!(receiver_ksms_after > receiver_ksms_before); - // Reserve balance has increased by sent amount - assert_eq!(ksms_in_reserve_on_ahk_after, ksms_in_reserve_on_ahk_before + amount); + assert!(receiver_ksm_after > receiver_ksm_before); + // Reserve balance is increased by sent amount (less fess) + assert!(ksm_in_reserve_on_kah_after > ksm_in_reserve_on_kah_before); + assert!(ksm_in_reserve_on_kah_after <= ksm_in_reserve_on_kah_before + amount); } #[test] -fn send_dots_from_asset_hub_polkadot_to_asset_hub_kusama_fee_from_pool() { - let prefund_amount = 10_000_000_000_000u128; - let dot_at_asset_hub_kusama = - v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Polkadot)]); - let owner: AccountId = AssetHubPolkadot::account_id_of(BOB); - AssetHubKusama::force_create_foreign_asset( - dot_at_asset_hub_kusama, - owner.clone(), - false, +fn send_back_dot_from_penpal_kusama_through_asset_hub_kusama_to_asset_hub_polkadot() { + let dot_at_kusama_parachains_latest = bridged_dot_at_ah_kusama(); + let dot_at_kusama_parachains = + v3::Location::try_from(dot_at_kusama_parachains_latest.clone()).unwrap(); + let amount = ASSET_HUB_KUSAMA_ED * 10_000_000; + let sender = PenpalASender::get(); + let receiver = AssetHubPolkadotReceiver::get(); + + // set up KSMs for transfer + let (ksm_at_kusama_parachains, _) = + set_up_ksm_for_penpal_kusama_through_kah_to_pah(&sender, amount); + + // set up DOTs for transfer + let penpal_location = AssetHubKusama::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_kah = AssetHubKusama::sovereign_account_id_of(penpal_location); + let prefund_accounts = vec![(sov_penpal_on_kah, amount * 2)]; + create_foreign_on_ah_kusama(dot_at_kusama_parachains, true, prefund_accounts); + let asset_owner: AccountId = AssetHubKusama::account_id_of(ALICE); + PenpalA::force_create_foreign_asset( + dot_at_kusama_parachains_latest.clone(), + asset_owner.clone(), + true, ASSET_MIN_BALANCE, - vec![(AssetHubKusamaSender::get(), prefund_amount)], + vec![(sender.clone(), amount * 2)], ); - AssetHubKusama::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - - // setup a pool to pay xcm fees with `dot_at_asset_hub_kusama` tokens - assert_ok!(::ForeignAssets::mint( - ::RuntimeOrigin::signed(owner.clone()), - dot_at_asset_hub_kusama, - owner.clone().into(), - 3_000_000_000_000, - )); - - ::Balances::set_balance(&owner, 3_000_000_000_000); - - assert_ok!(::AssetConversion::create_pool( - ::RuntimeOrigin::signed(owner.clone()), - Box::new(xcm::v3::Parent.into()), - Box::new(dot_at_asset_hub_kusama), - )); - - assert_expected_events!( - AssetHubKusama, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, - ] - ); - - assert_ok!(::AssetConversion::add_liquidity( - ::RuntimeOrigin::signed(owner.clone()), - Box::new(xcm::v3::Parent.into()), - Box::new(dot_at_asset_hub_kusama), - 1_000_000_000_000, - 2_000_000_000_000, - 1, - 1, - owner - )); - - assert_expected_events!( - AssetHubKusama, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, - ] - ); - }); - - // fund the AHK's SA on AHP with the DOT tokens held in reserve - let sov_ahk_on_ahp = AssetHubPolkadot::sovereign_account_of_parachain_on_other_global_consensus( + // fund the KAH's SA on PAH with the DOT tokens held in reserve + let sov_kah_on_pah = AssetHubPolkadot::sovereign_account_of_parachain_on_other_global_consensus( NetworkId::Kusama, AssetHubKusama::para_id(), ); - AssetHubPolkadot::fund_accounts(vec![(sov_ahk_on_ahp.clone(), prefund_amount)]); - - let dots_in_reserve_on_ahp_before = - ::account_data_of(sov_ahk_on_ahp.clone()).free; - assert_eq!(dots_in_reserve_on_ahp_before, prefund_amount); - let sender_dots_before = AssetHubKusama::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(dot_at_asset_hub_kusama, &AssetHubKusamaSender::get()) + AssetHubPolkadot::fund_accounts(vec![(sov_kah_on_pah.clone(), amount * 2)]); + + // balances before + let sender_dot_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(dot_at_kusama_parachains_latest.clone(), &sender) }); - assert_eq!(sender_dots_before, prefund_amount); - let receiver_dots_before = - ::account_data_of(AssetHubPolkadotReceiver::get()).free; + let receiver_dot_before = ::account_data_of(receiver.clone()).free; + + // send DOTs over the bridge, KSMs only used to pay fees on local AH, pay with DOT on remote AH + { + let final_destination = asset_hub_polkadot_location(); + let intermediary_hop = PenpalA::sibling_location_of(AssetHubKusama::para_id()); + let context = PenpalA::execute_with(PenpalUniversalLocation::get); + + // what happens at final destination + let beneficiary = AccountId32Junction { network: None, id: receiver.clone().into() }.into(); + // use DOT as fees on the final destination (PAH) + let remote_fees: Asset = (dot_at_kusama_parachains_latest.clone(), amount).into(); + let remote_fees = remote_fees.reanchored(&final_destination, &context).unwrap(); + // buy execution using DOTs, then deposit all remaining DOTs + let xcm_on_final_dest = Xcm::<()>(vec![ + BuyExecution { fees: remote_fees, weight_limit: WeightLimit::Unlimited }, + DepositAsset { assets: Wild(AllCounted(1)), beneficiary }, + ]); + + // what happens at intermediary hop + // reanchor final dest (Asset Hub Polkadot) to the view of hop (Asset Hub Kusama) + let mut final_destination = final_destination.clone(); + final_destination.reanchor(&intermediary_hop, &context).unwrap(); + // reanchor DOTs to the view of hop (Asset Hub Kusama) + let asset: Asset = (dot_at_kusama_parachains_latest.clone(), amount).into(); + let asset = asset.reanchored(&intermediary_hop, &context).unwrap(); + // on Asset Hub Kusama, forward a request to withdraw DOTs from reserve on Asset Hub + // Polkadot + let xcm_on_hop = Xcm::<()>(vec![InitiateReserveWithdraw { + assets: Definite(asset.into()), // DOTs + reserve: final_destination, // PAH + xcm: xcm_on_final_dest, // XCM to execute on PAH + }]); + // assets to send from Penpal and how they reach the intermediary hop + let assets: Assets = vec![ + (dot_at_kusama_parachains_latest.clone(), amount).into(), + (ksm_at_kusama_parachains.clone(), amount).into(), + ] + .into(); + let asset_transfer_type = TransferType::DestinationReserve; + let fees_id: AssetId = ksm_at_kusama_parachains.into(); + let fees_transfer_type = TransferType::DestinationReserve; + + // initiate the transfer + send_assets_from_penpal_kusama_through_kusama_ah_to_polkadot_ah( + intermediary_hop, + (assets, asset_transfer_type), + (fees_id, fees_transfer_type), + xcm_on_hop, + ); + } - let dot_at_asset_hub_kusama_latest: Location = dot_at_asset_hub_kusama.try_into().unwrap(); - let amount_to_send = ASSET_HUB_POLKADOT_ED * 1_000; - send_asset_from_asset_hub_kusama_to_asset_hub_polkadot( - dot_at_asset_hub_kusama_latest.clone(), - amount_to_send, - ); + // process PAH incoming message and check events AssetHubPolkadot::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; assert_expected_events!( AssetHubPolkadot, vec![ - // DOT is withdrawn from AHK's SA on AHP - RuntimeEvent::Balances( - pallet_balances::Event::Burned { who, amount } - ) => { - who: *who == sov_ahk_on_ahp, - amount: *amount == amount_to_send, - }, - // DOTs deposited to beneficiary - RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { - who: *who == AssetHubPolkadotReceiver::get(), - }, + // issue KSMs on PAH + RuntimeEvent::Balances(pallet_balances::Event::Issued { .. }) => {}, // message processed successfully RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } @@ -472,19 +531,15 @@ fn send_dots_from_asset_hub_polkadot_to_asset_hub_kusama_fee_from_pool() { ); }); - let sender_dots_after = AssetHubKusama::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(dot_at_asset_hub_kusama, &AssetHubKusamaSender::get()) + let sender_dot_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(dot_at_kusama_parachains_latest, &sender) }); - let receiver_dots_after = - ::account_data_of(AssetHubPolkadotReceiver::get()).free; - let dots_in_reserve_on_ahp_after = - ::account_data_of(sov_ahk_on_ahp).free; + let receiver_dot_after = ::account_data_of(receiver).free; - // Sender's balance is reduced - assert!(sender_dots_before >= sender_dots_after + amount_to_send); - // Receiver's balance is increased - assert!(receiver_dots_after > receiver_dots_before); - // Reserve balance is reduced by sent amount - assert_eq!(dots_in_reserve_on_ahp_after, dots_in_reserve_on_ahp_before - amount_to_send); + // Sender's balance is reduced by sent "amount" + assert_eq!(sender_dot_after, sender_dot_before - amount); + // Receiver's balance is increased by no more than "amount" + assert!(receiver_dot_after > receiver_dot_before); + assert!(receiver_dot_after <= receiver_dot_before + amount); } diff --git a/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/mod.rs b/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/mod.rs index 811ebeb200..3856201712 100644 --- a/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/mod.rs +++ b/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/mod.rs @@ -17,63 +17,170 @@ use crate::*; mod asset_transfers; mod claim_assets; +mod register_bridged_assets; mod send_xcm; mod teleport; +mod snowbridge { + pub const CHAIN_ID: u64 = 11155111; + pub const WETH: [u8; 20] = hex_literal::hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"); +} + pub(crate) fn asset_hub_polkadot_location() -> Location { + Location::new(2, [GlobalConsensus(Polkadot), Parachain(AssetHubPolkadot::para_id().into())]) +} + +pub(crate) fn bridge_hub_polkadot_location() -> Location { + Location::new(2, [GlobalConsensus(Polkadot), Parachain(BridgeHubPolkadot::para_id().into())]) +} + +// KSM and wKSM +pub(crate) fn ksm_at_ah_kusama() -> Location { + Parent.into() +} +pub(crate) fn bridged_ksm_at_ah_polkadot() -> Location { + Location::new(2, [GlobalConsensus(Kusama)]) +} + +// wDOT +pub(crate) fn bridged_dot_at_ah_kusama() -> Location { + Location::new(2, [GlobalConsensus(Polkadot)]) +} + +// USDT and wUSDT +pub(crate) fn usdt_at_ah_polkadot() -> Location { + Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())]) +} +pub(crate) fn bridged_usdt_at_ah_kusama() -> Location { Location::new( 2, - [GlobalConsensus(NetworkId::Polkadot), Parachain(AssetHubPolkadot::para_id().into())], + [ + GlobalConsensus(Polkadot), + Parachain(AssetHubPolkadot::para_id().into()), + PalletInstance(ASSETS_PALLET_ID), + GeneralIndex(USDT_ID.into()), + ], ) } -pub(crate) fn bridge_hub_polkadot_location() -> Location { +// wETH has same relative location on both Kusama and Polkadot AssetHubs +pub(crate) fn weth_at_asset_hubs() -> Location { Location::new( 2, - [GlobalConsensus(NetworkId::Polkadot), Parachain(BridgeHubPolkadot::para_id().into())], + [ + GlobalConsensus(Ethereum { chain_id: snowbridge::CHAIN_ID }), + AccountKey20 { network: None, key: snowbridge::WETH }, + ], ) } -pub(crate) fn send_asset_from_asset_hub_kusama( +pub(crate) fn create_foreign_on_ah_kusama( + id: v3::Location, + sufficient: bool, + prefund_accounts: Vec<(AccountId, u128)>, +) { + let owner = AssetHubKusama::account_id_of(ALICE); + let min = ASSET_MIN_BALANCE; + AssetHubKusama::force_create_foreign_asset(id, owner, sufficient, min, prefund_accounts); +} + +pub(crate) fn create_foreign_on_ah_polkadot(id: v3::Location, sufficient: bool) { + let owner = AssetHubPolkadot::account_id_of(ALICE); + AssetHubPolkadot::force_create_foreign_asset(id, owner, sufficient, ASSET_MIN_BALANCE, vec![]); +} + +pub(crate) fn foreign_balance_on_ah_kusama(id: v3::Location, who: &AccountId) -> u128 { + AssetHubKusama::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(id, who) + }) +} +pub(crate) fn foreign_balance_on_ah_polkadot(id: v3::Location, who: &AccountId) -> u128 { + AssetHubPolkadot::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(id, who) + }) +} + +// set up pool +pub(crate) fn set_up_pool_with_dot_on_ah_polkadot(asset: v3::Location, is_foreign: bool) { + let dot: v3::Location = v3::Parent.into(); + AssetHubPolkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let owner = AssetHubPolkadotSender::get(); + let signed_owner = ::RuntimeOrigin::signed(owner.clone()); + + if is_foreign { + assert_ok!(::ForeignAssets::mint( + signed_owner.clone(), + asset, + owner.clone().into(), + 3_000_000_000_000, + )); + } else { + let asset_id = match asset.interior.last() { + Some(v3::Junction::GeneralIndex(id)) => *id as u32, + _ => unreachable!(), + }; + assert_ok!(::Assets::mint( + signed_owner.clone(), + asset_id.into(), + owner.clone().into(), + 3_000_000_000_000, + )); + } + assert_ok!(::AssetConversion::create_pool( + signed_owner.clone(), + Box::new(dot), + Box::new(asset), + )); + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + assert_ok!(::AssetConversion::add_liquidity( + signed_owner.clone(), + Box::new(dot), + Box::new(asset), + 1_000_000_000_000, + 2_000_000_000_000, + 1, + 1, + owner, + )); + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, + ] + ); + }); +} + +pub(crate) fn send_assets_from_asset_hub_kusama( destination: Location, - (id, amount): (Location, u128), + assets: Assets, + fee_idx: u32, ) -> DispatchResult { let signed_origin = ::RuntimeOrigin::signed(AssetHubKusamaSender::get()); - let beneficiary: Location = AccountId32Junction { network: None, id: AssetHubPolkadotReceiver::get().into() }.into(); - let assets: Assets = (id, amount).into(); - let fee_asset_item = 0; - AssetHubKusama::execute_with(|| { - let call = - send_asset_from_asset_hub_kusama_call(destination, beneficiary, assets, fee_asset_item); - match call.dispatch(signed_origin) { - Ok(_) => Ok(()), - Err(error_with_post_info) => Err(error_with_post_info.error), - } + ::PolkadotXcm::limited_reserve_transfer_assets( + signed_origin, + bx!(destination.into()), + bx!(beneficiary.into()), + bx!(assets.into()), + fee_idx, + WeightLimit::Unlimited, + ) }) } -pub(crate) fn send_asset_from_asset_hub_kusama_call( - destination: Location, - beneficiary: Location, - assets: Assets, - fee_asset_item: u32, -) -> ::RuntimeCall { - ::RuntimeCall::PolkadotXcm( - pallet_xcm::Call::limited_reserve_transfer_assets { - dest: bx!(destination.into()), - beneficiary: bx!(beneficiary.into()), - assets: bx!(assets.into()), - fee_asset_item, - weight_limit: WeightLimit::Unlimited, - }, - ) -} - pub(crate) fn assert_bridge_hub_kusama_message_accepted(expected_processed: bool) { BridgeHubKusama::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; diff --git a/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/register_bridged_assets.rs b/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/register_bridged_assets.rs new file mode 100644 index 0000000000..c719589e9c --- /dev/null +++ b/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/register_bridged_assets.rs @@ -0,0 +1,107 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::tests::*; + +const XCM_FEE: u128 = 40_000_000_000; + +/// Tests the registering of a Kusama Asset as a bridged asset on Polkadot Asset Hub. +#[test] +fn register_kusama_asset_on_pah_from_kah() { + let sa_of_kah_on_pah = + AssetHubPolkadot::sovereign_account_of_parachain_on_other_global_consensus( + Kusama, + AssetHubKusama::para_id(), + ); + + // Kusama Asset Hub asset when bridged to Polkadot Asset Hub. + let bridged_asset_at_pah = v3::Location::new( + 2, + [ + v3::Junction::GlobalConsensus(v3::NetworkId::Kusama), + v3::Junction::Parachain(AssetHubKusama::para_id().into()), + v3::Junction::PalletInstance(ASSETS_PALLET_ID), + v3::Junction::GeneralIndex(ASSET_ID.into()), + ], + ); + + // Encoded `create_asset` call to be executed in Polkadot Asset Hub ForeignAssets pallet. + let call = AssetHubPolkadot::create_foreign_asset_call( + bridged_asset_at_pah, + ASSET_MIN_BALANCE, + sa_of_kah_on_pah.clone(), + ); + + let origin_kind = OriginKind::Xcm; + let fee_amount = XCM_FEE; + let fees = (Parent, fee_amount).into(); + + let xcm = xcm_transact_paid_execution(call, origin_kind, fees, sa_of_kah_on_pah.clone()); + + // SA-of-KAH-on-PAH needs to have balance to pay for fees and asset creation deposit + AssetHubPolkadot::fund_accounts(vec![( + sa_of_kah_on_pah.clone(), + ASSET_HUB_POLKADOT_ED * 10000000000, + )]); + + let destination = asset_hub_polkadot_location(); + + // fund the KAH's SA on KBH for paying bridge transport fees + BridgeHubKusama::fund_para_sovereign(AssetHubKusama::para_id(), 10_000_000_000_000u128); + + // set XCM versions + AssetHubKusama::force_xcm_version(destination.clone(), XCM_VERSION); + BridgeHubKusama::force_xcm_version(bridge_hub_polkadot_location(), XCM_VERSION); + + let root_origin = ::RuntimeOrigin::root(); + AssetHubKusama::execute_with(|| { + assert_ok!(::PolkadotXcm::send( + root_origin, + bx!(destination.into()), + bx!(xcm), + )); + + AssetHubKusama::assert_xcm_pallet_sent(); + }); + + assert_bridge_hub_kusama_message_accepted(true); + assert_bridge_hub_polkadot_message_received(); + AssetHubPolkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + AssetHubPolkadot::assert_xcmp_queue_success(None); + assert_expected_events!( + AssetHubPolkadot, + vec![ + // Burned the fee + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { + who: *who == sa_of_kah_on_pah.clone(), + amount: *amount == fee_amount, + }, + // Foreign Asset created + RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { asset_id, creator, owner }) => { + asset_id: *asset_id == bridged_asset_at_pah, + creator: *creator == sa_of_kah_on_pah.clone(), + owner: *owner == sa_of_kah_on_pah, + }, + // Unspent fee minted to origin + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == sa_of_kah_on_pah.clone(), + }, + ] + ); + type ForeignAssets = ::ForeignAssets; + assert!(ForeignAssets::asset_exists(bridged_asset_at_pah)); + }); +} diff --git a/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/send_xcm.rs b/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/send_xcm.rs index 59eacd0517..a2de082dc1 100644 --- a/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/send_xcm.rs +++ b/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/send_xcm.rs @@ -81,7 +81,11 @@ fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() { // send XCM from AssetHubKusama - fails - destination version not known assert_err!( - send_asset_from_asset_hub_kusama(destination.clone(), (native_token.clone(), amount)), + send_assets_from_asset_hub_kusama( + destination.clone(), + (native_token.clone(), amount).into(), + 0 + ), DispatchError::Module(sp_runtime::ModuleError { index: 31, error: [1, 0, 0, 0], @@ -98,9 +102,10 @@ fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() { newer_xcm_version, ); // send XCM from AssetHubKusama - ok - assert_ok!(send_asset_from_asset_hub_kusama( + assert_ok!(send_assets_from_asset_hub_kusama( destination.clone(), - (native_token.clone(), amount) + (native_token.clone(), amount).into(), + 0 )); // `ExportMessage` on local BridgeHub - fails - remote BridgeHub version not known @@ -115,9 +120,10 @@ fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() { ); // send XCM from AssetHubKusama - ok - assert_ok!(send_asset_from_asset_hub_kusama( + assert_ok!(send_assets_from_asset_hub_kusama( destination.clone(), - (native_token.clone(), amount) + (native_token.clone(), amount).into(), + 0 )); assert_bridge_hub_kusama_message_accepted(true); assert_bridge_hub_polkadot_message_received(); diff --git a/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/lib.rs b/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/lib.rs index 5ab2ff4e60..14d75e66f6 100644 --- a/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/lib.rs +++ b/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/lib.rs @@ -26,6 +26,7 @@ pub use xcm::{ NetworkId::{Kusama as KusamaId, Polkadot as PolkadotId}, }, }; +pub use xcm_executor::traits::TransferType; // Bridges pub use bp_messages::LaneId; @@ -40,38 +41,44 @@ pub use emulated_integration_tests_common::{ RelayChain as Relay, Test, TestArgs, TestContext, TestExt, }, xcm_helpers::{xcm_transact_paid_execution, xcm_transact_unpaid_execution}, - PROOF_SIZE_THRESHOLD, REF_TIME_THRESHOLD, XCM_V3, + ASSETS_PALLET_ID, PROOF_SIZE_THRESHOLD, REF_TIME_THRESHOLD, XCM_V3, }; pub use kusama_polkadot_system_emulated_network::{ asset_hub_kusama_emulated_chain::{ genesis::ED as ASSET_HUB_KUSAMA_ED, AssetHubKusamaParaPallet as AssetHubKusamaPallet, }, asset_hub_polkadot_emulated_chain::{ - genesis::ED as ASSET_HUB_POLKADOT_ED, AssetHubPolkadotParaPallet as AssetHubPolkadotPallet, + genesis::{AssetHubPolkadotAssetOwner, ED as ASSET_HUB_POLKADOT_ED}, + AssetHubPolkadotParaPallet as AssetHubPolkadotPallet, }, bridge_hub_polkadot_emulated_chain::{ genesis::ED as BRIDGE_HUB_POLKADOT_ED, BridgeHubPolkadotParaPallet as BridgeHubPolkadotPallet, }, + penpal_emulated_chain::{ + penpal_runtime::xcm_config::{ + CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub, + UniversalLocation as PenpalUniversalLocation, + }, + PenpalAssetOwner, PenpalBParaPallet as PenpalBPallet, + }, polkadot_emulated_chain::{genesis::ED as POLKADOT_ED, PolkadotRelayPallet as PolkadotPallet}, AssetHubKusamaPara as AssetHubKusama, AssetHubKusamaParaReceiver as AssetHubKusamaReceiver, - AssetHubPolkadotPara as AssetHubPolkadot, + AssetHubKusamaParaSender as AssetHubKusamaSender, AssetHubPolkadotPara as AssetHubPolkadot, AssetHubPolkadotParaReceiver as AssetHubPolkadotReceiver, AssetHubPolkadotParaSender as AssetHubPolkadotSender, BridgeHubKusamaPara as BridgeHubKusama, BridgeHubPolkadotPara as BridgeHubPolkadot, - BridgeHubPolkadotParaSender as BridgeHubPolkadotSender, PolkadotRelay as Polkadot, - PolkadotRelayReceiver as PolkadotReceiver, PolkadotRelaySender as PolkadotSender, -}; -pub use parachains_common::{AccountId, Balance}; -pub use polkadot_system_emulated_network::{ - penpal_emulated_chain::PenpalBParaPallet as PenpalBPallet, - BridgeHubPolkadotParaReceiver as BridgeHubPolkadotReceiver, PenpalBPara as PenpalB, + BridgeHubPolkadotParaReceiver as BridgeHubPolkadotReceiver, + BridgeHubPolkadotParaSender as BridgeHubPolkadotSender, PenpalBPara as PenpalB, PenpalBParaReceiver as PenpalBReceiver, PenpalBParaSender as PenpalBSender, + PolkadotRelay as Polkadot, PolkadotRelayReceiver as PolkadotReceiver, + PolkadotRelaySender as PolkadotSender, }; +pub use parachains_common::{AccountId, Balance}; pub const ASSET_ID: u32 = 1; pub const ASSET_MIN_BALANCE: u128 = 1000; -pub const ASSETS_PALLET_ID: u8 = 50; +pub const USDT_ID: u32 = 1984; #[cfg(test)] mod tests; diff --git a/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/asset_transfers.rs b/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/asset_transfers.rs index 31bcaa398e..b41169bdbe 100644 --- a/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/asset_transfers.rs +++ b/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/asset_transfers.rs @@ -14,66 +14,152 @@ // limitations under the License. use crate::tests::*; -use frame_support::traits::fungible::Mutate; -fn send_asset_from_asset_hub_polkadot_to_asset_hub_kusama(id: Location, amount: u128) { - let destination = asset_hub_kusama_location(); - - // fund the AHP's SA on BHP for paying bridge transport fees +fn send_assets_over_bridge(send_fn: F) { + // fund the PAH's SA on PBH for paying bridge transport fees BridgeHubPolkadot::fund_para_sovereign(AssetHubPolkadot::para_id(), 10_000_000_000_000u128); // set XCM versions - AssetHubPolkadot::force_xcm_version(destination.clone(), XCM_VERSION); + let local_asset_hub = PenpalB::sibling_location_of(AssetHubPolkadot::para_id()); + PenpalB::force_xcm_version(local_asset_hub.clone(), XCM_VERSION); + AssetHubPolkadot::force_xcm_version(asset_hub_kusama_location(), XCM_VERSION); BridgeHubPolkadot::force_xcm_version(bridge_hub_kusama_location(), XCM_VERSION); // send message over bridge - assert_ok!(send_asset_from_asset_hub_polkadot(destination, (id, amount))); + send_fn(); + + // process and verify intermediary hops assert_bridge_hub_polkadot_message_accepted(true); assert_bridge_hub_kusama_message_received(); } -#[test] -fn send_dots_from_asset_hub_polkadot_to_asset_hub_kusama() { - let dot_at_asset_hub_polkadot: v3::Location = v3::Parent.into(); - let dot_at_asset_hub_kusama = - v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Polkadot)]); - let owner: AccountId = AssetHubKusama::account_id_of(ALICE); - AssetHubKusama::force_create_foreign_asset( - dot_at_asset_hub_kusama, - owner, - true, - ASSET_MIN_BALANCE, - vec![], - ); - let sov_ahk_on_ahp = AssetHubPolkadot::sovereign_account_of_parachain_on_other_global_consensus( - NetworkId::Kusama, - AssetHubKusama::para_id(), +fn set_up_dot_for_penpal_polkadot_through_pah_to_kah( + sender: &AccountId, + amount: u128, +) -> (Location, v3::Location) { + let dot_at_polkadot_parachains = dot_at_ah_polkadot(); + let dot_at_asset_hub_kusama = v3::Location::try_from(bridged_dot_at_ah_kusama()).unwrap(); + create_foreign_on_ah_kusama(dot_at_asset_hub_kusama, true); + + let penpal_location = AssetHubPolkadot::sibling_location_of(PenpalB::para_id()); + let sov_penpal_on_pah = AssetHubPolkadot::sovereign_account_id_of(penpal_location); + // fund Penpal's sovereign account on AssetHub + AssetHubPolkadot::fund_accounts(vec![(sov_penpal_on_pah, amount * 2)]); + // fund Penpal's sender account + PenpalB::mint_foreign_asset( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + dot_at_polkadot_parachains.clone(), + sender.clone(), + amount * 2, ); + (dot_at_polkadot_parachains, dot_at_asset_hub_kusama) +} - let dots_in_reserve_on_ahp_before = - ::account_data_of(sov_ahk_on_ahp.clone()).free; - let sender_dots_before = - ::account_data_of(AssetHubPolkadotSender::get()).free; - let receiver_dots_before = AssetHubKusama::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(dot_at_asset_hub_kusama, &AssetHubKusamaReceiver::get()) +fn send_assets_from_penpal_polkadot_through_polkadot_ah_to_kusama_ah( + destination: Location, + assets: (Assets, TransferType), + fees: (AssetId, TransferType), + custom_xcm_on_dest: Xcm<()>, +) { + send_assets_over_bridge(|| { + let sov_penpal_on_pah = AssetHubPolkadot::sovereign_account_id_of( + AssetHubPolkadot::sibling_location_of(PenpalB::para_id()), + ); + let sov_kah_on_pah = + AssetHubPolkadot::sovereign_account_of_parachain_on_other_global_consensus( + Kusama, + AssetHubKusama::para_id(), + ); + + // send message over bridge + assert_ok!(PenpalB::execute_with(|| { + let signed_origin = ::RuntimeOrigin::signed(PenpalBSender::get()); + ::PolkadotXcm::transfer_assets_using_type_and_then( + signed_origin, + bx!(destination.into()), + bx!(assets.0.into()), + bx!(assets.1), + bx!(fees.0.into()), + bx!(fees.1), + bx!(VersionedXcm::from(custom_xcm_on_dest)), + WeightLimit::Unlimited, + ) + })); + // verify intermediary AH Polkadot hop + AssetHubPolkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubPolkadot, + vec![ + // Amount to reserve transfer is withdrawn from Penpal's sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, .. } + ) => { + who: *who == sov_penpal_on_pah.clone(), + }, + // Amount deposited in KAH's sovereign account + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == sov_kah_on_pah.clone(), + }, + RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + ) => {}, + ] + ); + }); }); +} - let dot_at_asset_hub_polkadot_latest: Location = dot_at_asset_hub_polkadot.try_into().unwrap(); +#[test] +/// Test transfer of DOT, USDT and wETH from AssetHub Polkadot to AssetHub Kusama. +/// +/// This mix of assets should cover the whole range: +/// - native assets: DOT, +/// - trust-based assets: USDT (exists only on Polkadot, Kusama gets it from Polkadot over bridge), +/// - foreign asset / bridged asset (other bridge / Snowfork): wETH (bridged from Ethereum to +/// Polkadot over Snowbridge, then bridged over to Kusama through this bridge). +fn send_dot_usdt_and_weth_from_asset_hub_polkadot_to_asset_hub_kusama() { let amount = ASSET_HUB_POLKADOT_ED * 1_000; - send_asset_from_asset_hub_polkadot_to_asset_hub_kusama( - dot_at_asset_hub_polkadot_latest, - amount, + let sender = AssetHubPolkadotSender::get(); + let receiver = AssetHubKusamaReceiver::get(); + let dot_at_asset_hub_polkadot = dot_at_ah_polkadot(); + let bridged_dot_at_asset_hub_kusama = + v3::Location::try_from(bridged_dot_at_ah_kusama()).unwrap(); + + create_foreign_on_ah_kusama(bridged_dot_at_asset_hub_kusama, true); + set_up_pool_with_ksm_on_ah_kusama(bridged_dot_at_asset_hub_kusama, true); + + //////////////////////////////////////////////////////////// + // Let's first send over just some DOTs as a simple example + //////////////////////////////////////////////////////////// + let sov_kah_on_pah = AssetHubPolkadot::sovereign_account_of_parachain_on_other_global_consensus( + Kusama, + AssetHubKusama::para_id(), ); + let dot_in_reserve_on_pah_before = + ::account_data_of(sov_kah_on_pah.clone()).free; + let sender_dot_before = ::account_data_of(sender.clone()).free; + let receiver_dot_before = + foreign_balance_on_ah_kusama(bridged_dot_at_asset_hub_kusama, &receiver); + + // send DOTs, use them for fees + send_assets_over_bridge(|| { + let destination = asset_hub_kusama_location(); + let assets: Assets = (dot_at_asset_hub_polkadot, amount).into(); + let fee_idx = 0; + assert_ok!(send_assets_from_asset_hub_polkadot(destination, assets, fee_idx)); + }); + + // verify expected events on final destination AssetHubKusama::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; assert_expected_events!( AssetHubKusama, vec![ - // issue DOTs on AHK + // issue DOTs on KAH RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == dot_at_asset_hub_kusama, - owner: *owner == AssetHubKusamaReceiver::get(), + asset_id: *asset_id == bridged_dot_at_asset_hub_kusama, + owner: *owner == receiver, }, // message processed successfully RuntimeEvent::MessageQueue( @@ -83,76 +169,141 @@ fn send_dots_from_asset_hub_polkadot_to_asset_hub_kusama() { ); }); - let sender_dots_after = - ::account_data_of(AssetHubPolkadotSender::get()).free; - let receiver_dots_after = AssetHubKusama::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(dot_at_asset_hub_kusama, &AssetHubKusamaReceiver::get()) - }); - let dots_in_reserve_on_ahp_after = - ::account_data_of(sov_ahk_on_ahp).free; + let sender_dot_after = ::account_data_of(sender.clone()).free; + let receiver_dot_after = + foreign_balance_on_ah_kusama(bridged_dot_at_asset_hub_kusama, &receiver); + let dot_in_reserve_on_pah_after = + ::account_data_of(sov_kah_on_pah).free; // Sender's balance is reduced - assert!(sender_dots_before > sender_dots_after); + assert!(sender_dot_before > sender_dot_after); // Receiver's balance is increased - assert!(receiver_dots_after > receiver_dots_before); + assert!(receiver_dot_after > receiver_dot_before); // Reserve balance is increased by sent amount - assert_eq!(dots_in_reserve_on_ahp_after, dots_in_reserve_on_ahp_before + amount); + assert_eq!(dot_in_reserve_on_pah_after, dot_in_reserve_on_pah_before + amount); + + ///////////////////////////////////////////////////////////// + // Now let's send over USDTs + wETH (and pay fees with USDT) + ///////////////////////////////////////////////////////////// + let usdt_at_asset_hub_polkadot = usdt_at_ah_polkadot(); + let bridged_usdt_at_asset_hub_kusama = + v3::Location::try_from(bridged_usdt_at_ah_kusama()).unwrap(); + // wETH has same relative location on both Polkadot and Kusama AssetHubs + let bridged_weth_at_ah = v3::Location::try_from(weth_at_asset_hubs()).unwrap(); + + // mint USDT in sender's account (USDT already created in genesis) + AssetHubPolkadot::mint_asset( + ::RuntimeOrigin::signed(AssetHubPolkadotAssetOwner::get()), + USDT_ID, + sender.clone(), + amount * 2, + ); + // create wETH at src and dest and prefund sender's account + create_foreign_on_ah_polkadot(bridged_weth_at_ah, true, vec![(sender.clone(), amount * 2)]); + create_foreign_on_ah_kusama(bridged_weth_at_ah, true); + create_foreign_on_ah_kusama(bridged_usdt_at_asset_hub_kusama, true); + set_up_pool_with_ksm_on_ah_kusama(bridged_usdt_at_asset_hub_kusama, true); + + let receiver_usdts_before = + foreign_balance_on_ah_kusama(bridged_usdt_at_asset_hub_kusama, &receiver); + let receiver_weth_before = foreign_balance_on_ah_kusama(bridged_weth_at_ah, &receiver); + + // send USDTs and wETHs + let assets: Assets = vec![ + (usdt_at_asset_hub_polkadot.clone(), amount).into(), + (Location::try_from(bridged_weth_at_ah).unwrap(), amount).into(), + ] + .into(); + // use USDT for fees + let fee: AssetId = usdt_at_asset_hub_polkadot.into(); + + // use the more involved transfer extrinsic + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(assets.len() as u32)), + beneficiary: AccountId32Junction { network: None, id: receiver.clone().into() }.into(), + }]); + assert_ok!(AssetHubPolkadot::execute_with(|| { + ::PolkadotXcm::transfer_assets_using_type_and_then( + ::RuntimeOrigin::signed(sender), + bx!(asset_hub_kusama_location().into()), + bx!(assets.into()), + bx!(TransferType::LocalReserve), + bx!(fee.into()), + bx!(TransferType::LocalReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), + WeightLimit::Unlimited, + ) + })); + // verify hops (also advances the message through the hops) + assert_bridge_hub_polkadot_message_accepted(true); + assert_bridge_hub_kusama_message_received(); + AssetHubKusama::execute_with(|| { + AssetHubKusama::assert_xcmp_queue_success(None); + }); + + let receiver_usdts_after = + foreign_balance_on_ah_kusama(bridged_usdt_at_asset_hub_kusama, &receiver); + let receiver_weth_after = foreign_balance_on_ah_kusama(bridged_weth_at_ah, &receiver); + + // Receiver's USDT balance is increased by almost `amount` (minus fees) + assert!(receiver_usdts_after > receiver_usdts_before); + assert!(receiver_usdts_after < receiver_usdts_before + amount); + // Receiver's wETH balance is increased by sent amount + assert_eq!(receiver_weth_after, receiver_weth_before + amount); } #[test] -fn send_ksms_from_asset_hub_polkadot_to_asset_hub_kusama() { +/// Send bridged KSM "back" from AssetHub Polkadot to AssetHub Kusama. +fn send_back_ksm_from_asset_hub_polkadot_to_asset_hub_kusama() { let prefund_amount = 10_000_000_000_000u128; - let ksm_at_asset_hub_polkadot = - v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Kusama)]); - let owner: AccountId = AssetHubPolkadot::account_id_of(ALICE); - AssetHubPolkadot::force_create_foreign_asset( - ksm_at_asset_hub_polkadot, - owner, - true, - ASSET_MIN_BALANCE, - vec![(AssetHubPolkadotSender::get(), prefund_amount)], - ); - - // fund the AHP's SA on AHK with the KSM tokens held in reserve - let sov_ahp_on_ahk = AssetHubKusama::sovereign_account_of_parachain_on_other_global_consensus( - NetworkId::Polkadot, + let amount_to_send = ASSET_HUB_KUSAMA_ED * 1_000; + let sender = AssetHubPolkadotSender::get(); + let receiver = AssetHubKusamaReceiver::get(); + let bridged_ksm_at_asset_hub_polkadot = + v3::Location::try_from(bridged_ksm_at_ah_polkadot()).unwrap(); + let prefund_accounts = vec![(sender.clone(), prefund_amount)]; + create_foreign_on_ah_polkadot(bridged_ksm_at_asset_hub_polkadot, true, prefund_accounts); + + // fund the PAH's SA on KAH with the KSM tokens held in reserve + let sov_pah_on_kah = AssetHubKusama::sovereign_account_of_parachain_on_other_global_consensus( + Polkadot, AssetHubPolkadot::para_id(), ); - AssetHubKusama::fund_accounts(vec![(sov_ahp_on_ahk.clone(), prefund_amount)]); - - let ksms_in_reserve_on_ahk_before = - ::account_data_of(sov_ahp_on_ahk.clone()).free; - assert_eq!(ksms_in_reserve_on_ahk_before, prefund_amount); - let sender_ksms_before = AssetHubPolkadot::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(ksm_at_asset_hub_polkadot, &AssetHubPolkadotSender::get()) + AssetHubKusama::fund_accounts(vec![(sov_pah_on_kah.clone(), prefund_amount)]); + + let ksm_in_reserve_on_kah_before = + ::account_data_of(sov_pah_on_kah.clone()).free; + assert_eq!(ksm_in_reserve_on_kah_before, prefund_amount); + + let sender_ksm_before = + foreign_balance_on_ah_polkadot(bridged_ksm_at_asset_hub_polkadot, &sender); + assert_eq!(sender_ksm_before, prefund_amount); + let receiver_ksm_before = ::account_data_of(receiver.clone()).free; + + // send back KSMs, use them for fees + send_assets_over_bridge(|| { + let destination = asset_hub_kusama_location(); + let assets: Assets = + (Location::try_from(bridged_ksm_at_asset_hub_polkadot).unwrap(), amount_to_send).into(); + let fee_idx = 0; + assert_ok!(send_assets_from_asset_hub_polkadot(destination, assets, fee_idx)); }); - assert_eq!(sender_ksms_before, prefund_amount); - let receiver_ksms_before = - ::account_data_of(AssetHubKusamaReceiver::get()).free; - let ksm_at_asset_hub_polkadot_latest: Location = ksm_at_asset_hub_polkadot.try_into().unwrap(); - let amount_to_send = ASSET_HUB_KUSAMA_ED * 1_000; - send_asset_from_asset_hub_polkadot_to_asset_hub_kusama( - ksm_at_asset_hub_polkadot_latest, - amount_to_send, - ); AssetHubKusama::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; assert_expected_events!( AssetHubKusama, vec![ - // KSM is withdrawn from AHP's SA on AHK + // KSM is withdrawn from PAH's SA on KAH RuntimeEvent::Balances( pallet_balances::Event::Burned { who, amount } ) => { - who: *who == sov_ahp_on_ahk, + who: *who == sov_pah_on_kah, amount: *amount == amount_to_send, }, // KSMs deposited to beneficiary RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { - who: *who == AssetHubKusamaReceiver::get(), + who: *who == receiver, }, // message processed successfully RuntimeEvent::MessageQueue( @@ -162,110 +313,72 @@ fn send_ksms_from_asset_hub_polkadot_to_asset_hub_kusama() { ); }); - let sender_ksms_after = AssetHubPolkadot::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(ksm_at_asset_hub_polkadot, &AssetHubPolkadotSender::get()) - }); - let receiver_ksms_after = - ::account_data_of(AssetHubKusamaReceiver::get()).free; - let ksms_in_reserve_on_ahk_after = - ::account_data_of(sov_ahp_on_ahk.clone()).free; + let sender_ksm_after = + foreign_balance_on_ah_polkadot(bridged_ksm_at_asset_hub_polkadot, &sender); + let receiver_ksm_after = ::account_data_of(receiver.clone()).free; + let ksm_in_reserve_on_kah_after = + ::account_data_of(sov_pah_on_kah.clone()).free; // Sender's balance is reduced - assert!(sender_ksms_before > sender_ksms_after); + assert!(sender_ksm_before > sender_ksm_after); // Receiver's balance is increased - assert!(receiver_ksms_after > receiver_ksms_before); + assert!(receiver_ksm_after > receiver_ksm_before); // Reserve balance is reduced by sent amount - assert_eq!(ksms_in_reserve_on_ahk_after, ksms_in_reserve_on_ahk_before - amount_to_send); + assert_eq!(ksm_in_reserve_on_kah_after, ksm_in_reserve_on_kah_before - amount_to_send); } #[test] -fn send_dots_from_asset_hub_polkadot_to_asset_hub_kusama_fee_from_pool() { - let dot_at_asset_hub_polkadot: v3::Location = v3::Parent.into(); - let dot_at_asset_hub_kusama = - v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Polkadot)]); - let owner: AccountId = AssetHubKusama::account_id_of(BOB); - AssetHubKusama::force_create_foreign_asset( - dot_at_asset_hub_kusama, - owner.clone(), - false, - ASSET_MIN_BALANCE, - vec![], - ); - let sov_ahk_on_ahp = AssetHubPolkadot::sovereign_account_of_parachain_on_other_global_consensus( - NetworkId::Kusama, +fn send_dot_from_penpal_polkadot_through_asset_hub_polkadot_to_asset_hub_kusama() { + let amount = ASSET_HUB_POLKADOT_ED * 10_000_000; + let sender = PenpalBSender::get(); + let receiver = AssetHubKusamaReceiver::get(); + let local_asset_hub = PenpalB::sibling_location_of(AssetHubPolkadot::para_id()); + let (dot_at_polkadot_parachains, dot_at_asset_hub_kusama) = + set_up_dot_for_penpal_polkadot_through_pah_to_kah(&sender, amount); + + let sov_kah_on_pah = AssetHubPolkadot::sovereign_account_of_parachain_on_other_global_consensus( + Kusama, AssetHubKusama::para_id(), ); - - AssetHubKusama::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - - // setup a pool to pay xcm fees with `dot_at_asset_hub_kusama` tokens - assert_ok!(::ForeignAssets::mint( - ::RuntimeOrigin::signed(owner.clone()), - dot_at_asset_hub_kusama, - owner.clone().into(), - 3_000_000_000_000, - )); - - ::Balances::set_balance(&owner, 3_000_000_000_000); - - assert_ok!(::AssetConversion::create_pool( - ::RuntimeOrigin::signed(owner.clone()), - Box::new(dot_at_asset_hub_polkadot), - Box::new(dot_at_asset_hub_kusama), - )); - - assert_expected_events!( - AssetHubKusama, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, - ] - ); - - assert_ok!(::AssetConversion::add_liquidity( - ::RuntimeOrigin::signed(owner.clone()), - Box::new(dot_at_asset_hub_polkadot), - Box::new(dot_at_asset_hub_kusama), - 1_000_000_000_000, - 2_000_000_000_000, - 1, - 1, - owner - )); - - assert_expected_events!( - AssetHubKusama, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, - ] - ); - }); - - let dots_in_reserve_on_ahp_before = - ::account_data_of(sov_ahk_on_ahp.clone()).free; - let sender_dots_before = - ::account_data_of(AssetHubPolkadotSender::get()).free; - let receiver_dots_before = AssetHubKusama::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(dot_at_asset_hub_kusama, &AssetHubKusamaReceiver::get()) + let dot_in_reserve_on_pah_before = + ::account_data_of(sov_kah_on_pah.clone()).free; + let sender_dot_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(dot_at_polkadot_parachains.clone(), &sender) }); + let receiver_dot_before = foreign_balance_on_ah_kusama(dot_at_asset_hub_kusama, &receiver); + + // Send DOTs over bridge + { + let destination = asset_hub_kusama_location(); + let assets: Assets = (dot_at_polkadot_parachains.clone(), amount).into(); + let asset_transfer_type = TransferType::RemoteReserve(local_asset_hub.clone().into()); + let fees_id: AssetId = dot_at_polkadot_parachains.clone().into(); + let fees_transfer_type = TransferType::RemoteReserve(local_asset_hub.into()); + let beneficiary: Location = + AccountId32Junction { network: None, id: receiver.clone().into() }.into(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(assets.len() as u32)), + beneficiary, + }]); + send_assets_from_penpal_polkadot_through_polkadot_ah_to_kusama_ah( + destination, + (assets, asset_transfer_type), + (fees_id, fees_transfer_type), + custom_xcm_on_dest, + ); + } - let dot_at_asset_hub_polkadot_latest: Location = dot_at_asset_hub_polkadot.try_into().unwrap(); - let amount = ASSET_HUB_POLKADOT_ED * 1_000; - send_asset_from_asset_hub_polkadot_to_asset_hub_kusama( - dot_at_asset_hub_polkadot_latest, - amount, - ); + // process KAH incoming message and check events AssetHubKusama::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; assert_expected_events!( AssetHubKusama, vec![ - // issue DOTs on AHK + // issue DOTs on KAH RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == dot_at_asset_hub_kusama, - owner: *owner == AssetHubKusamaReceiver::get(), + asset_id: *asset_id == v3::Location::try_from(dot_at_polkadot_parachains.clone()).unwrap(), + owner: owner == &receiver, }, // message processed successfully RuntimeEvent::MessageQueue( @@ -275,125 +388,125 @@ fn send_dots_from_asset_hub_polkadot_to_asset_hub_kusama_fee_from_pool() { ); }); - let sender_dots_after = - ::account_data_of(AssetHubPolkadotSender::get()).free; - let receiver_dots_after = AssetHubKusama::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(dot_at_asset_hub_kusama, &AssetHubKusamaReceiver::get()) + let sender_dot_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(dot_at_polkadot_parachains, &sender) }); - let dots_in_reserve_on_ahp_after = - ::account_data_of(sov_ahk_on_ahp).free; + let receiver_dot_after = foreign_balance_on_ah_kusama(dot_at_asset_hub_kusama, &receiver); + let dot_in_reserve_on_pah_after = + ::account_data_of(sov_kah_on_pah.clone()).free; // Sender's balance is reduced - assert!(sender_dots_before >= sender_dots_after + amount); + assert!(sender_dot_after < sender_dot_before); // Receiver's balance is increased - assert!(receiver_dots_after > receiver_dots_before); - // Reserve balance is increased by sent amount - assert_eq!(dots_in_reserve_on_ahp_after, dots_in_reserve_on_ahp_before + amount); + assert!(receiver_dot_after > receiver_dot_before); + // Reserve balance is increased by sent amount (less fess) + assert!(dot_in_reserve_on_pah_after > dot_in_reserve_on_pah_before); + assert!(dot_in_reserve_on_pah_after <= dot_in_reserve_on_pah_before + amount); } #[test] -fn send_ksms_from_asset_hub_polkadot_to_asset_hub_kusama_fee_from_pool() { - let prefund_amount = 10_000_000_000_000u128; - let ksm_at_asset_hub_polkadot = - v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Kusama)]); - let owner: AccountId = AssetHubPolkadot::account_id_of(BOB); - AssetHubPolkadot::force_create_foreign_asset( - ksm_at_asset_hub_polkadot, - owner.clone(), - false, +fn send_back_ksm_from_penpal_polkadot_through_asset_hub_polkadot_to_asset_hub_kusama() { + let ksm_at_polkadot_parachains = v3::Location::try_from(bridged_ksm_at_ah_polkadot()).unwrap(); + let amount = ASSET_HUB_POLKADOT_ED * 10_000_000; + let sender = PenpalBSender::get(); + let receiver = AssetHubKusamaReceiver::get(); + + // set up DOTs for transfer + let (dot_at_polkadot_parachains, _) = + set_up_dot_for_penpal_polkadot_through_pah_to_kah(&sender, amount); + + // set up KSMs for transfer + let penpal_location = AssetHubPolkadot::sibling_location_of(PenpalB::para_id()); + let sov_penpal_on_kah = AssetHubPolkadot::sovereign_account_id_of(penpal_location); + let prefund_accounts = vec![(sov_penpal_on_kah, amount * 2)]; + create_foreign_on_ah_polkadot(ksm_at_polkadot_parachains, true, prefund_accounts); + let asset_owner: AccountId = AssetHubPolkadot::account_id_of(ALICE); + PenpalB::force_create_foreign_asset( + ksm_at_polkadot_parachains.try_into().unwrap(), + asset_owner.clone(), + true, ASSET_MIN_BALANCE, - vec![(AssetHubPolkadotSender::get(), prefund_amount)], + vec![(sender.clone(), amount * 2)], ); - AssetHubPolkadot::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - - // setup a pool to pay xcm fees with `ksm_at_asset_hub_polkadot` tokens - assert_ok!(::ForeignAssets::mint( - ::RuntimeOrigin::signed(owner.clone()), - ksm_at_asset_hub_polkadot, - owner.clone().into(), - 3_000_000_000_000, - )); - - ::Balances::set_balance( - &owner, - 3_000_000_000_000, - ); - - assert_ok!(::AssetConversion::create_pool( - ::RuntimeOrigin::signed(owner.clone()), - Box::new(xcm::v3::Parent.into()), - Box::new(ksm_at_asset_hub_polkadot), - )); - - assert_expected_events!( - AssetHubPolkadot, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, - ] - ); - - assert_ok!(::AssetConversion::add_liquidity( - ::RuntimeOrigin::signed(owner.clone()), - Box::new(xcm::v3::Parent.into()), - Box::new(ksm_at_asset_hub_polkadot), - 1_000_000_000_000, - 2_000_000_000_000, - 1, - 1, - owner.clone() - )); - - assert_expected_events!( - AssetHubPolkadot, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, - ] - ); - }); - - // fund the AHP's SA on AHK with the KSM tokens held in reserve - let sov_ahp_on_ahk = AssetHubKusama::sovereign_account_of_parachain_on_other_global_consensus( - NetworkId::Polkadot, + // fund the PAH's SA on KAH with the KSM tokens held in reserve + let sov_pah_on_kah = AssetHubKusama::sovereign_account_of_parachain_on_other_global_consensus( + Polkadot, AssetHubPolkadot::para_id(), ); - AssetHubKusama::fund_accounts(vec![(sov_ahp_on_ahk.clone(), prefund_amount)]); - - let ksms_in_reserve_on_ahk_before = - ::account_data_of(sov_ahp_on_ahk.clone()).free; - assert_eq!(ksms_in_reserve_on_ahk_before, prefund_amount); - let sender_ksms_before = AssetHubPolkadot::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(ksm_at_asset_hub_polkadot, &AssetHubPolkadotSender::get()) + AssetHubKusama::fund_accounts(vec![(sov_pah_on_kah.clone(), amount * 2)]); + + // balances before + let sender_ksm_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + ksm_at_polkadot_parachains.try_into().unwrap(), + &sender, + ) }); - assert_eq!(sender_ksms_before, prefund_amount); - let receiver_ksms_before = - ::account_data_of(AssetHubKusamaReceiver::get()).free; + let receiver_ksm_before = ::account_data_of(receiver.clone()).free; + + let ksm_at_polkadot_parachains_latest: Location = + ksm_at_polkadot_parachains.try_into().unwrap(); + // send KSMs over the bridge, DOTs only used to pay fees on local AH, pay with KSM on remote AH + { + let final_destination = asset_hub_kusama_location(); + let intermediary_hop = PenpalB::sibling_location_of(AssetHubPolkadot::para_id()); + let context = PenpalB::execute_with(PenpalUniversalLocation::get); + + // what happens at final destination + let beneficiary = AccountId32Junction { network: None, id: receiver.clone().into() }.into(); + // use KSM as fees on the final destination (PAH) + let remote_fees: Asset = (ksm_at_polkadot_parachains_latest.clone(), amount).into(); + let remote_fees = remote_fees.reanchored(&final_destination, &context).unwrap(); + // buy execution using KSMs, then deposit all remaining KSMs + let xcm_on_final_dest = Xcm::<()>(vec![ + BuyExecution { fees: remote_fees, weight_limit: WeightLimit::Unlimited }, + DepositAsset { assets: Wild(AllCounted(1)), beneficiary }, + ]); + + // what happens at intermediary hop + // reanchor final dest (Asset Hub Kusama) to the view of hop (Asset Hub Polkadot) + let mut final_destination = final_destination.clone(); + final_destination.reanchor(&intermediary_hop, &context).unwrap(); + // reanchor KSMs to the view of hop (Asset Hub Polkadot) + let asset: Asset = (ksm_at_polkadot_parachains_latest.clone(), amount).into(); + let asset = asset.reanchored(&intermediary_hop, &context).unwrap(); + // on Asset Hub Polkadot, forward a request to withdraw KSMs from reserve on Asset Hub + // Kusama + let xcm_on_hop = Xcm::<()>(vec![InitiateReserveWithdraw { + assets: Definite(asset.into()), // KSMs + reserve: final_destination, // KAH + xcm: xcm_on_final_dest, // XCM to execute on KAH + }]); + // assets to send from Penpal and how they reach the intermediary hop + let assets: Assets = vec![ + (ksm_at_polkadot_parachains_latest, amount).into(), + (dot_at_polkadot_parachains.clone(), amount).into(), + ] + .into(); + let asset_transfer_type = TransferType::DestinationReserve; + let fees_id: AssetId = dot_at_polkadot_parachains.into(); + let fees_transfer_type = TransferType::DestinationReserve; + + // initiate the transfer + send_assets_from_penpal_polkadot_through_polkadot_ah_to_kusama_ah( + intermediary_hop, + (assets, asset_transfer_type), + (fees_id, fees_transfer_type), + xcm_on_hop, + ); + } - let ksm_at_asset_hub_polkadot_latest: Location = ksm_at_asset_hub_polkadot.try_into().unwrap(); - let amount_to_send = ASSET_HUB_KUSAMA_ED * 1_000; - send_asset_from_asset_hub_polkadot_to_asset_hub_kusama( - ksm_at_asset_hub_polkadot_latest, - amount_to_send, - ); + // process KAH incoming message and check events AssetHubKusama::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; assert_expected_events!( AssetHubKusama, vec![ - // KSM is withdrawn from AHP's SA on AHK - RuntimeEvent::Balances( - pallet_balances::Event::Burned { who, amount } - ) => { - who: *who == sov_ahp_on_ahk, - amount: *amount == amount_to_send, - }, - // KSMs deposited to beneficiary - RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { - who: *who == AssetHubKusamaReceiver::get(), - }, + // issue DOTs on KAH + RuntimeEvent::Balances(pallet_balances::Event::Issued { .. }) => {}, // message processed successfully RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } @@ -402,19 +515,18 @@ fn send_ksms_from_asset_hub_polkadot_to_asset_hub_kusama_fee_from_pool() { ); }); - let sender_ksms_after = AssetHubPolkadot::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(ksm_at_asset_hub_polkadot, &AssetHubPolkadotSender::get()) + let sender_ksm_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + ksm_at_polkadot_parachains.try_into().unwrap(), + &sender, + ) }); - let receiver_ksms_after = - ::account_data_of(AssetHubKusamaReceiver::get()).free; - let ksms_in_reserve_on_ahk_after = - ::account_data_of(sov_ahp_on_ahk.clone()).free; + let receiver_ksm_after = ::account_data_of(receiver).free; - // Sender's balance is reduced - assert!(sender_ksms_before >= sender_ksms_after + amount_to_send); - // Receiver's balance is increased - assert!(receiver_ksms_after > receiver_ksms_before); - // Reserve balance is reduced by sent amount - assert_eq!(ksms_in_reserve_on_ahk_after, ksms_in_reserve_on_ahk_before - amount_to_send); + // Sender's balance is reduced by sent "amount" + assert_eq!(sender_ksm_after, sender_ksm_before - amount); + // Receiver's balance is increased by no more than "amount" + assert!(receiver_ksm_after > receiver_ksm_before); + assert!(receiver_ksm_after <= receiver_ksm_before + amount); } diff --git a/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/mod.rs b/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/mod.rs index 86138cf32c..98b09fac52 100644 --- a/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/mod.rs +++ b/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/mod.rs @@ -17,44 +17,161 @@ use crate::*; mod asset_transfers; mod claim_assets; +mod register_bridged_assets; mod send_xcm; mod snowbridge; mod teleport; pub(crate) fn asset_hub_kusama_location() -> Location { + Location::new(2, [GlobalConsensus(Kusama), Parachain(AssetHubKusama::para_id().into())]) +} + +pub(crate) fn bridge_hub_kusama_location() -> Location { + Location::new(2, [GlobalConsensus(Kusama), Parachain(BridgeHubKusama::para_id().into())]) +} + +// DOT and wDOT +pub(crate) fn dot_at_ah_polkadot() -> Location { + Parent.into() +} +pub(crate) fn bridged_dot_at_ah_kusama() -> Location { + Location::new(2, [GlobalConsensus(Polkadot)]) +} + +// wKSM +pub(crate) fn bridged_ksm_at_ah_polkadot() -> Location { + Location::new(2, [GlobalConsensus(Kusama)]) +} + +// USDT and wUSDT +pub(crate) fn usdt_at_ah_polkadot() -> Location { + Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())]) +} +pub(crate) fn bridged_usdt_at_ah_kusama() -> Location { Location::new( 2, - [GlobalConsensus(NetworkId::Kusama), Parachain(AssetHubKusama::para_id().into())], + [ + GlobalConsensus(Polkadot), + Parachain(AssetHubPolkadot::para_id().into()), + PalletInstance(ASSETS_PALLET_ID), + GeneralIndex(USDT_ID.into()), + ], ) } -pub(crate) fn bridge_hub_kusama_location() -> Location { +// wETH has same relative location on both Kusama and Polkadot AssetHubs +pub(crate) fn weth_at_asset_hubs() -> Location { Location::new( 2, - [GlobalConsensus(NetworkId::Kusama), Parachain(BridgeHubKusama::para_id().into())], + [ + GlobalConsensus(Ethereum { chain_id: snowbridge::CHAIN_ID }), + AccountKey20 { network: None, key: snowbridge::WETH }, + ], ) } -pub(crate) fn send_asset_from_asset_hub_polkadot( +pub(crate) fn create_foreign_on_ah_kusama(id: v3::Location, sufficient: bool) { + let owner = AssetHubKusama::account_id_of(ALICE); + AssetHubKusama::force_create_foreign_asset(id, owner, sufficient, ASSET_MIN_BALANCE, vec![]); +} + +pub(crate) fn create_foreign_on_ah_polkadot( + id: v3::Location, + sufficient: bool, + prefund_accounts: Vec<(AccountId, u128)>, +) { + let owner = AssetHubPolkadot::account_id_of(ALICE); + let min = ASSET_MIN_BALANCE; + AssetHubPolkadot::force_create_foreign_asset(id, owner, sufficient, min, prefund_accounts); +} + +pub(crate) fn foreign_balance_on_ah_kusama(id: v3::Location, who: &AccountId) -> u128 { + AssetHubKusama::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(id, who) + }) +} +pub(crate) fn foreign_balance_on_ah_polkadot(id: v3::Location, who: &AccountId) -> u128 { + AssetHubPolkadot::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(id, who) + }) +} + +// set up pool +pub(crate) fn set_up_pool_with_ksm_on_ah_kusama(asset: v3::Location, is_foreign: bool) { + let ksm: v3::Location = v3::Parent.into(); + AssetHubKusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let owner = AssetHubKusamaSender::get(); + let signed_owner = ::RuntimeOrigin::signed(owner.clone()); + + if is_foreign { + assert_ok!(::ForeignAssets::mint( + signed_owner.clone(), + asset, + owner.clone().into(), + 3_000_000_000_000, + )); + } else { + let asset_id = match asset.interior.last() { + Some(v3::Junction::GeneralIndex(id)) => *id as u32, + _ => unreachable!(), + }; + assert_ok!(::Assets::mint( + signed_owner.clone(), + asset_id.into(), + owner.clone().into(), + 3_000_000_000_000, + )); + } + assert_ok!(::AssetConversion::create_pool( + signed_owner.clone(), + Box::new(ksm), + Box::new(asset), + )); + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + assert_ok!(::AssetConversion::add_liquidity( + signed_owner.clone(), + Box::new(ksm), + Box::new(asset), + 1_000_000_000_000, + 2_000_000_000_000, + 1, + 1, + owner, + )); + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, + ] + ); + }); +} + +pub(crate) fn send_assets_from_asset_hub_polkadot( destination: Location, - (id, amount): (Location, u128), + assets: Assets, + fee_idx: u32, ) -> DispatchResult { let signed_origin = ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()); - let beneficiary: Location = AccountId32Junction { network: None, id: AssetHubKusamaReceiver::get().into() }.into(); - let assets: Assets = (id, amount).into(); - let fee_asset_item = 0; - AssetHubPolkadot::execute_with(|| { ::PolkadotXcm::limited_reserve_transfer_assets( signed_origin, bx!(destination.into()), bx!(beneficiary.into()), bx!(assets.into()), - fee_asset_item, + fee_idx, WeightLimit::Unlimited, ) }) diff --git a/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/register_bridged_assets.rs b/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/register_bridged_assets.rs new file mode 100644 index 0000000000..c9c690a2d8 --- /dev/null +++ b/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/register_bridged_assets.rs @@ -0,0 +1,128 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::tests::{ + snowbridge::{CHAIN_ID, WETH}, + *, +}; + +const XCM_FEE: u128 = 40_000_000_000; + +/// Tests the registering of a Polkadot Asset as a bridged asset on Kusama Asset Hub. +#[test] +fn register_polkadot_asset_on_kah_from_pah() { + // Polkadot Asset Hub asset when bridged to Kusama Asset Hub. + let bridged_asset_at_kah = v3::Location::new( + 2, + [ + v3::Junction::GlobalConsensus(v3::NetworkId::Polkadot), + v3::Junction::Parachain(AssetHubPolkadot::para_id().into()), + v3::Junction::PalletInstance(ASSETS_PALLET_ID), + v3::Junction::GeneralIndex(ASSET_ID.into()), + ], + ); + // Register above asset on Kusama AH from Polkadot AH. + register_asset_on_kah_from_pah(bridged_asset_at_kah); +} + +/// Tests the registering of an Ethereum Asset as a bridged asset on Kusama Asset Hub. +#[test] +fn register_ethereum_asset_on_kah_from_pah() { + // Ethereum asset when bridged to Kusama Asset Hub. + let bridged_asset_at_kah = v3::Location::new( + 2, + [ + v3::Junction::GlobalConsensus(v3::NetworkId::Ethereum { chain_id: CHAIN_ID }), + v3::Junction::AccountKey20 { network: None, key: WETH }, + ], + ); + // Register above asset on Kusama AH from Polkadot AH. + register_asset_on_kah_from_pah(bridged_asset_at_kah); +} + +fn register_asset_on_kah_from_pah(bridged_asset_at_kah: v3::Location) { + let sa_of_pah_on_kah = AssetHubKusama::sovereign_account_of_parachain_on_other_global_consensus( + Polkadot, + AssetHubPolkadot::para_id(), + ); + + // Encoded `create_asset` call to be executed in Kusama Asset Hub ForeignAssets pallet. + let call = AssetHubKusama::create_foreign_asset_call( + bridged_asset_at_kah, + ASSET_MIN_BALANCE, + sa_of_pah_on_kah.clone(), + ); + + let origin_kind = OriginKind::Xcm; + let fee_amount = XCM_FEE; + let fees = (Parent, fee_amount).into(); + + let xcm = xcm_transact_paid_execution(call, origin_kind, fees, sa_of_pah_on_kah.clone()); + + // SA-of-PAH-on-KAH needs to have balance to pay for fees and asset creation deposit + AssetHubKusama::fund_accounts(vec![( + sa_of_pah_on_kah.clone(), + ASSET_HUB_KUSAMA_ED * 10000000000, + )]); + + let destination = asset_hub_kusama_location(); + + // fund the PAH's SA on PBH for paying bridge transport fees + BridgeHubPolkadot::fund_para_sovereign(AssetHubPolkadot::para_id(), 10_000_000_000_000u128); + + // set XCM versions + AssetHubPolkadot::force_xcm_version(destination.clone(), XCM_VERSION); + BridgeHubPolkadot::force_xcm_version(bridge_hub_kusama_location(), XCM_VERSION); + + let root_origin = ::RuntimeOrigin::root(); + AssetHubPolkadot::execute_with(|| { + assert_ok!(::PolkadotXcm::send( + root_origin, + bx!(destination.into()), + bx!(xcm), + )); + + AssetHubPolkadot::assert_xcm_pallet_sent(); + }); + + assert_bridge_hub_polkadot_message_accepted(true); + assert_bridge_hub_kusama_message_received(); + AssetHubKusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + AssetHubKusama::assert_xcmp_queue_success(None); + assert_expected_events!( + AssetHubKusama, + vec![ + // Burned the fee + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { + who: *who == sa_of_pah_on_kah.clone(), + amount: *amount == fee_amount, + }, + // Foreign Asset created + RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { asset_id, creator, owner }) => { + asset_id: *asset_id == bridged_asset_at_kah, + creator: *creator == sa_of_pah_on_kah.clone(), + owner: *owner == sa_of_pah_on_kah, + }, + // Unspent fee minted to origin + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == sa_of_pah_on_kah.clone(), + }, + ] + ); + type ForeignAssets = ::ForeignAssets; + assert!(ForeignAssets::asset_exists(bridged_asset_at_kah)); + }); +} diff --git a/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/send_xcm.rs b/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/send_xcm.rs index 568fedece7..7bea665eb3 100644 --- a/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/send_xcm.rs +++ b/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/send_xcm.rs @@ -81,7 +81,11 @@ fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() { // send XCM from AssetHubPolkadot - fails - destination version not known assert_err!( - send_asset_from_asset_hub_polkadot(destination.clone(), (native_token.clone(), amount)), + send_assets_from_asset_hub_polkadot( + destination.clone(), + (native_token.clone(), amount).into(), + 0 + ), DispatchError::Module(sp_runtime::ModuleError { index: 31, error: [1, 0, 0, 0], @@ -98,9 +102,10 @@ fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() { newer_xcm_version, ); // send XCM from AssetHubPolkadot - ok - assert_ok!(send_asset_from_asset_hub_polkadot( + assert_ok!(send_assets_from_asset_hub_polkadot( destination.clone(), - (native_token.clone(), amount) + (native_token.clone(), amount).into(), + 0 )); // `ExportMessage` on local BridgeHub - fails - remote BridgeHub version not known @@ -115,9 +120,10 @@ fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() { ); // send XCM from AssetHubPolkadot - ok - assert_ok!(send_asset_from_asset_hub_polkadot( + assert_ok!(send_assets_from_asset_hub_polkadot( destination.clone(), - (native_token.clone(), amount) + (native_token.clone(), amount).into(), + 0 )); assert_bridge_hub_polkadot_message_accepted(true); assert_bridge_hub_kusama_message_received(); diff --git a/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/snowbridge.rs b/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/snowbridge.rs index 0cbbc31350..c6374c2cfd 100644 --- a/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/snowbridge.rs +++ b/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/snowbridge.rs @@ -48,11 +48,12 @@ use sp_core::{H160, H256, U256}; use sp_runtime::{DispatchError::Token, FixedU128, TokenError::FundsUnavailable}; use system_parachains_constants::polkadot::currency::UNITS; +pub const CHAIN_ID: u64 = 1; +pub const WETH: [u8; 20] = hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"); +pub const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); +pub const GATEWAY_ADDRESS: [u8; 20] = hex!("EDa338E4dC46038493b885327842fD3E301CaB39"); + const INITIAL_FUND: u128 = 5_000_000_000 * POLKADOT_ED; -const CHAIN_ID: u64 = 1; -const WETH: [u8; 20] = hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"); -const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); -const GATEWAY_ADDRESS: [u8; 20] = hex!("EDa338E4dC46038493b885327842fD3E301CaB39"); const INSUFFICIENT_XCM_FEE: u128 = 1000; const XCM_FEE: u128 = 4_000_000_000; const WETH_AMOUNT: u128 = 1_000_000_000; diff --git a/system-parachains/asset-hubs/asset-hub-kusama/Cargo.toml b/system-parachains/asset-hubs/asset-hub-kusama/Cargo.toml index 5e1465ea62..637720d361 100644 --- a/system-parachains/asset-hubs/asset-hub-kusama/Cargo.toml +++ b/system-parachains/asset-hubs/asset-hub-kusama/Cargo.toml @@ -101,6 +101,7 @@ assets-common = { workspace = true } # Bridges pallet-xcm-bridge-hub-router = { workspace = true } +snowbridge-router-primitives = { workspace = true } [dev-dependencies] asset-test-utils = { workspace = true } @@ -151,6 +152,7 @@ runtime-benchmarks = [ "parachains-common/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", + "snowbridge-router-primitives/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", @@ -248,6 +250,7 @@ std = [ "primitive-types/std", "scale-info/std", "serde_json/std", + "snowbridge-router-primitives/std", "sp-api/std", "sp-block-builder/std", "sp-consensus-aura/std", diff --git a/system-parachains/asset-hubs/asset-hub-kusama/src/lib.rs b/system-parachains/asset-hubs/asset-hub-kusama/src/lib.rs index 4515b3d2a0..153604e1d1 100644 --- a/system-parachains/asset-hubs/asset-hub-kusama/src/lib.rs +++ b/system-parachains/asset-hubs/asset-hub-kusama/src/lib.rs @@ -443,7 +443,10 @@ impl pallet_assets::Config for Runtime { type AssetIdParameter = xcm::v3::Location; type Currency = Balances; type CreateOrigin = ForeignCreators< - FromSiblingParachain, xcm::v3::Location>, + ( + FromSiblingParachain, xcm::v3::Location>, + xcm_config::bridging::to_polkadot::PolkadotOrEthereumAssetFromAssetHubPolkadot, + ), ForeignCreatorsSovereignAccountOf, AccountId, xcm::v3::Location, @@ -1611,7 +1614,10 @@ impl_runtime_apis! { pub TrustedReserve: Option<(Location, Asset)> = Some( ( xcm_config::bridging::to_polkadot::AssetHubPolkadot::get(), - Asset::from((xcm_config::bridging::to_polkadot::DotLocation::get(), 1000000000000 as u128)) + Asset::from(( + xcm_config::bridging::to_polkadot::DotLocation::get(), + 10000000000 as u128, + )) ) ); } diff --git a/system-parachains/asset-hubs/asset-hub-kusama/src/xcm_config.rs b/system-parachains/asset-hubs/asset-hub-kusama/src/xcm_config.rs index 6b39e1ed43..2da556f7ce 100644 --- a/system-parachains/asset-hubs/asset-hub-kusama/src/xcm_config.rs +++ b/system-parachains/asset-hubs/asset-hub-kusama/src/xcm_config.rs @@ -25,10 +25,11 @@ use assets_common::{ TrustBackedAssetsAsLocation, }; use frame_support::{ + pallet_prelude::Get, parameter_types, traits::{ tokens::imbalance::{ResolveAssetTo, ResolveTo}, - ConstU32, Contains, Equals, Everything, Nothing, PalletInfoAccess, + ConstU32, Contains, ContainsPair, Equals, Everything, Nothing, PalletInfoAccess, }, }; use frame_system::EnsureRoot; @@ -38,6 +39,7 @@ use parachains_common::xcm_config::{ ParentRelayOrSiblingParachains, RelayOrOtherSystemParachains, }; use polkadot_parachain_primitives::primitives::Sibling; +use snowbridge_router_primitives::inbound::GlobalConsensusEthereumConvertsFor; use sp_runtime::traits::{AccountIdConversion, ConvertInto}; use system_parachains_constants::TREASURY_PALLET_ID; use xcm::latest::prelude::*; @@ -99,6 +101,9 @@ pub type LocationToAccountId = ( // Different global consensus parachain sovereign account. // (Used for over-bridge transfers and reserve processing) GlobalConsensusParachainConvertsFor, + // Ethereum contract sovereign account. + // (Used to get convert ethereum contract locations to sovereign account) + GlobalConsensusEthereumConvertsFor, ); /// Means for transacting the native currency on this chain. @@ -302,8 +307,9 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; // Asset Hub trusts only particular, pre-configured bridged locations from a different consensus // as reserve locations (we trust the Bridge Hub to relay the message that a reserve is being - // held). Kusama Asset Hub accepts PolkadotAssetHub as a reserve location for DOT. - type IsReserve = (bridging::to_polkadot::IsTrustedBridgedReserveLocationForConcreteAsset,); + // held). On Kusama Asset Hub, we allow Polkadot Asset Hub to act as reserve for any asset + // native to the Polkadot or Ethereum ecosystems. + type IsReserve = (bridging::to_polkadot::PolkadotOrEthereumAssetFromAssetHubPolkadot,); type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; type Barrier = Barrier; @@ -452,6 +458,8 @@ pub type ForeignCreatorsSovereignAccountOf = ( SiblingParachainConvertsVia, AccountId32Aliases, ParentIsPreset, + GlobalConsensusEthereumConvertsFor, + GlobalConsensusParachainConvertsFor, ); /// Simple conversion of `u32` into an `AssetId` for use in benchmarking. @@ -466,7 +474,6 @@ impl pallet_assets::BenchmarkHelper for XcmBenchmarkHelper { /// All configuration related to bridging pub mod bridging { use super::*; - use assets_common::matching; use sp_std::collections::btree_set::BTreeSet; use xcm_builder::NetworkExportTableItem; @@ -507,6 +514,9 @@ pub mod bridging { ); pub const PolkadotNetwork: NetworkId = NetworkId::Polkadot; + pub const EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 1 }; + pub EthereumEcosystem: Location = Location::new(2, [GlobalConsensus(EthereumNetwork::get())]); + pub DotLocation: Location = Location::new(2, [GlobalConsensus(PolkadotNetwork::get())]); pub AssetHubPolkadot: Location = Location::new( 2, [ @@ -514,12 +524,6 @@ pub mod bridging { Parachain(polkadot_runtime_constants::system_parachain::ASSET_HUB_ID), ], ); - pub DotLocation: Location = Location::new(2, GlobalConsensus(PolkadotNetwork::get())); - - pub DotFromAssetHubPolkadot: (AssetFilter, Location) = ( - Wild(AllOf { fun: WildFungible, id: AssetId(DotLocation::get()) }), - AssetHubPolkadot::get() - ); /// Set up exporters configuration. /// `Option` represents static "base fee" which is used for total delivery fee calculation. @@ -552,17 +556,59 @@ pub mod bridging { } } - /// Reserve locations filter for `xcm_executor::Config::IsReserve`. - /// Locations from which the runtime accepts reserved assets. - pub type IsTrustedBridgedReserveLocationForConcreteAsset = - matching::IsTrustedBridgedReserveLocationForConcreteAsset< - UniversalLocation, - ( - // allow receive DOT from AssetHubPolkadot - xcm_builder::Case, - // and nothing else - ), - >; + /// Allow any asset native to the Polkadot or Ethereum ecosystems if it comes from Polkadot + /// Asset Hub. + pub type PolkadotOrEthereumAssetFromAssetHubPolkadot = RemoteAssetFromLocation< + (StartsWith, StartsWith), + AssetHubPolkadot, + >; + + // TODO: get this from `assets_common v0.17.1` when SDK deps are upgraded + /// Accept an asset if it is native to `AssetsAllowedNetworks` and it is coming from + /// `OriginLocation`. + pub struct RemoteAssetFromLocation( + sp_std::marker::PhantomData<(AssetsAllowedNetworks, OriginLocation)>, + ); + impl< + L: TryInto + Clone, + AssetsAllowedNetworks: Contains, + OriginLocation: Get, + > ContainsPair for RemoteAssetFromLocation + { + fn contains(asset: &L, origin: &L) -> bool { + let Ok(asset) = asset.clone().try_into() else { + return false; + }; + let Ok(origin) = origin.clone().try_into() else { + return false; + }; + + let expected_origin = OriginLocation::get(); + // ensure `origin` is expected `OriginLocation` + if !expected_origin.eq(&origin) { + log::trace!( + target: "xcm::contains", + "RemoteAssetFromLocation asset: {asset:?}, origin: {origin:?} is not from expected {expected_origin:?}" + ); + return false; + } else { + log::trace!( + target: "xcm::contains", + "RemoteAssetFromLocation asset: {asset:?}, origin: {origin:?}", + ); + } + + // ensure `asset` is from remote consensus listed in `AssetsAllowedNetworks` + AssetsAllowedNetworks::contains(&asset) + } + } + impl, OriginLocation: Get> + ContainsPair for RemoteAssetFromLocation + { + fn contains(asset: &Asset, origin: &Location) -> bool { + >::contains(&asset.id.0, origin) + } + } } /// Benchmarks helper for bridging configuration. diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs b/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs index 18e634b06f..b1d831e3ed 100644 --- a/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs +++ b/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs @@ -371,6 +371,7 @@ impl pallet_assets::Config for Runtime { xcm_config::bridging::to_ethereum::EthereumNetwork, xcm::v3::Location, >, + xcm_config::bridging::to_kusama::KusamaAssetFromAssetHubKusama, ), ForeignCreatorsSovereignAccountOf, AccountId, @@ -1616,7 +1617,10 @@ impl_runtime_apis! { pub TrustedReserve: Option<(Location, Asset)> = Some( ( xcm_config::bridging::to_kusama::AssetHubKusama::get(), - Asset::from((xcm_config::bridging::to_kusama::KsmLocation::get(), 1000000000000 as u128)) + Asset::from(( + xcm_config::bridging::to_kusama::KsmLocation::get(), + 1000000000000 as u128 + )) ) ); } diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/src/xcm_config.rs b/system-parachains/asset-hubs/asset-hub-polkadot/src/xcm_config.rs index c1076523eb..c127a0f17c 100644 --- a/system-parachains/asset-hubs/asset-hub-polkadot/src/xcm_config.rs +++ b/system-parachains/asset-hubs/asset-hub-polkadot/src/xcm_config.rs @@ -25,10 +25,11 @@ use assets_common::{ TrustBackedAssetsAsLocation, }; use frame_support::{ + pallet_prelude::Get, parameter_types, traits::{ tokens::imbalance::{ResolveAssetTo, ResolveTo}, - ConstU32, Contains, Equals, Everything, Nothing, PalletInfoAccess, + ConstU32, Contains, ContainsPair, Equals, Everything, Nothing, PalletInfoAccess, }, }; use frame_system::EnsureRoot; @@ -372,8 +373,8 @@ impl xcm_executor::Config for XcmConfig { // held). Asset Hub may _act_ as a reserve location for DOT and assets created // under `pallet-assets`. Users must use teleport where allowed (e.g. DOT with the Relay Chain). type IsReserve = ( - bridging::to_kusama::IsTrustedBridgedReserveLocationForConcreteAsset, - bridging::to_ethereum::IsTrustedBridgedReserveLocationForForeignAsset, + bridging::to_kusama::KusamaAssetFromAssetHubKusama, + bridging::to_ethereum::EthereumAssetFromEthereum, ); type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; @@ -532,6 +533,7 @@ pub type ForeignCreatorsSovereignAccountOf = ( AccountId32Aliases, ParentIsPreset, GlobalConsensusEthereumConvertsFor, + GlobalConsensusParachainConvertsFor, ); /// Simple conversion of `u32` into an `AssetId` for use in benchmarking. @@ -546,7 +548,6 @@ impl pallet_assets::BenchmarkHelper for XcmBenchmarkHelper { /// All configuration related to bridging pub mod bridging { use super::*; - use assets_common::matching; use sp_std::collections::btree_set::BTreeSet; use xcm_builder::NetworkExportTableItem; @@ -596,11 +597,6 @@ pub mod bridging { ); pub KsmLocation: Location = Location::new(2, GlobalConsensus(KusamaNetwork::get())); - pub KsmFromAssetHubKusama: (AssetFilter, Location) = ( - Wild(AllOf { fun: WildFungible, id: AssetId(KsmLocation::get()) }), - AssetHubKusama::get() - ); - /// Set up exporters configuration. /// `Option` represents static "base fee" which is used for total delivery fee calculation. pub BridgeTable: sp_std::vec::Vec = sp_std::vec![ @@ -631,18 +627,56 @@ pub mod bridging { UniversalAliases::get().contains(alias) } } - - /// Reserve locations filter for `xcm_executor::Config::IsReserve`. - /// Locations from which the runtime accepts reserved assets. - pub type IsTrustedBridgedReserveLocationForConcreteAsset = - matching::IsTrustedBridgedReserveLocationForConcreteAsset< - UniversalLocation, - ( - // allow receive KSM from AssetHubKusama - xcm_builder::Case, - // and nothing else - ), - >; + /// Allow any asset native to the Kusama ecosystem if it comes from Kusama Asset Hub. + pub type KusamaAssetFromAssetHubKusama = + RemoteAssetFromLocation, AssetHubKusama>; + + // TODO: get this from `assets_common v0.17.1` when SDK deps are upgraded + /// Accept an asset if it is native to `AssetsAllowedNetworks` and it is coming from + /// `OriginLocation`. + pub struct RemoteAssetFromLocation( + sp_std::marker::PhantomData<(AssetsAllowedNetworks, OriginLocation)>, + ); + impl< + L: TryInto + Clone, + AssetsAllowedNetworks: Contains, + OriginLocation: Get, + > ContainsPair for RemoteAssetFromLocation + { + fn contains(asset: &L, origin: &L) -> bool { + let Ok(asset) = asset.clone().try_into() else { + return false; + }; + let Ok(origin) = origin.clone().try_into() else { + return false; + }; + + let expected_origin = OriginLocation::get(); + // ensure `origin` is expected `OriginLocation` + if !expected_origin.eq(&origin) { + log::trace!( + target: "xcm::contains", + "RemoteAssetFromLocation asset: {asset:?}, origin: {origin:?} is not from expected {expected_origin:?}" + ); + return false; + } else { + log::trace!( + target: "xcm::contains", + "RemoteAssetFromLocation asset: {asset:?}, origin: {origin:?}", + ); + } + + // ensure `asset` is from remote consensus listed in `AssetsAllowedNetworks` + AssetsAllowedNetworks::contains(&asset) + } + } + impl, OriginLocation: Get> + ContainsPair for RemoteAssetFromLocation + { + fn contains(asset: &Asset, origin: &Location) -> bool { + >::contains(&asset.id.0, origin) + } + } } pub mod to_ethereum { @@ -686,8 +720,8 @@ pub mod bridging { ); } - pub type IsTrustedBridgedReserveLocationForForeignAsset = - matching::IsForeignConcreteAsset>; + pub type EthereumAssetFromEthereum = + IsForeignConcreteAsset>; impl Contains<(Location, Junction)> for UniversalAliases { fn contains(alias: &(Location, Junction)) -> bool {