From 016f394854a3fad6762913a0f208cece181c34fe Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Fri, 28 Jun 2024 12:09:38 +0300 Subject: [PATCH] [Rococo<>Westend bridge] Allow any asset over the lane between the two Asset Hubs (#4888) On Westend Asset Hub, we allow Rococo Asset Hub to act as reserve for any asset native to the Rococo or Ethereum ecosystems (practically providing Westend access to Ethereum assets through double bridging: W<>R<>Eth). On Rococo Asset Hub, we allow Westend Asset Hub to act as reserve for any asset native to the Westend ecosystem. We also allow Ethereum contracts to act as reserves for the foreign assets identified by the same respective contracts locations. - [x] add emulated tests for various assets (native, trust-based, foreign/bridged) going AHR -> AHW, - [x] add equivalent tests for the other direction AHW -> AHR. This PR is a prerequisite to doing the same for Polkadot<>Kusama bridge. --- Cargo.lock | 1 + .../assets/asset-hub-rococo/src/genesis.rs | 7 +- .../emulated/common/src/lib.rs | 7 +- .../asset-hub-rococo/src/tests/treasury.rs | 12 +- .../bridges/bridge-hub-rococo/src/lib.rs | 4 +- .../src/tests/asset_transfers.rs | 283 ++++++++--------- .../bridge-hub-rococo/src/tests/mod.rs | 122 ++++++- .../bridge-hub-rococo/src/tests/send_xcm.rs | 16 +- .../bridge-hub-rococo/src/tests/snowbridge.rs | 4 +- .../bridges/bridge-hub-westend/Cargo.toml | 1 + .../bridges/bridge-hub-westend/src/lib.rs | 4 +- .../src/tests/asset_transfers.rs | 299 ++++++++++-------- .../bridge-hub-westend/src/tests/mod.rs | 141 ++++++++- .../bridge-hub-westend/src/tests/send_xcm.rs | 16 +- .../assets/asset-hub-rococo/src/xcm_config.rs | 34 +- .../asset-hub-westend/src/xcm_config.rs | 36 +-- .../runtimes/assets/common/src/matching.rs | 44 +-- prdoc/pr_4888.prdoc | 35 ++ 18 files changed, 680 insertions(+), 386 deletions(-) create mode 100644 prdoc/pr_4888.prdoc diff --git a/Cargo.lock b/Cargo.lock index 7796a2d2cf2c..8af41e3eae58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2252,6 +2252,7 @@ dependencies = [ "cumulus-pallet-xcmp-queue", "emulated-integration-tests-common", "frame-support", + "hex-literal", "pallet-asset-conversion", "pallet-assets", "pallet-balances", diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs index e5378b35f5e4..3a87322664d9 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs @@ -21,7 +21,7 @@ use sp_core::{sr25519, storage::Storage}; use emulated_integration_tests_common::{ accounts, build_genesis_storage, collators, get_account_id_from_seed, PenpalSiblingSovereignAccount, PenpalTeleportableAssetLocation, RESERVABLE_ASSET_ID, - SAFE_XCM_VERSION, + SAFE_XCM_VERSION, USDT_ID, }; use parachains_common::{AccountId, Balance}; @@ -68,7 +68,10 @@ pub fn genesis() -> Storage { ..Default::default() }, assets: asset_hub_rococo_runtime::AssetsConfig { - assets: vec![(RESERVABLE_ASSET_ID, AssetHubRococoAssetOwner::get(), true, ED)], + assets: vec![ + (RESERVABLE_ASSET_ID, AssetHubRococoAssetOwner::get(), true, ED), + (USDT_ID, AssetHubRococoAssetOwner::get(), true, ED), + ], ..Default::default() }, foreign_assets: asset_hub_rococo_runtime::ForeignAssetsConfig { diff --git a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs index 4a9d3b3a5aaf..7077fbbb0a9a 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs @@ -51,11 +51,14 @@ pub const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION; type AccountPublic = ::Signer; -// This asset is added to AH as Asset and reserved transfer between Parachain and AH +// (trust-backed) Asset registered on AH and reserve-transferred between Parachain and AH pub const RESERVABLE_ASSET_ID: u32 = 1; -// This asset is added to AH as ForeignAsset and teleported between Penpal and AH +// ForeignAsset registered on AH and teleported between Penpal and AH pub const TELEPORTABLE_ASSET_ID: u32 = 2; +// USDT registered on AH as (trust-backed) Asset and reserve-transferred between Parachain and AH +pub const USDT_ID: u32 = 1984; + pub const PENPAL_ID: u32 = 2000; pub const ASSETS_PALLET_ID: u8 = 50; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/treasury.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/treasury.rs index 01bf40ae8fdf..809641c626fa 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/treasury.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/treasury.rs @@ -20,7 +20,7 @@ use frame_support::{ sp_runtime::traits::Dispatchable, traits::{ fungible::Inspect, - fungibles::{Create, Inspect as FungiblesInspect, Mutate}, + fungibles::{Inspect as FungiblesInspect, Mutate}, }, }; use parachains_common::AccountId; @@ -163,7 +163,7 @@ fn spend_roc_on_asset_hub() { #[test] fn create_and_claim_treasury_spend_in_usdt() { const ASSET_ID: u32 = 1984; - const SPEND_AMOUNT: u128 = 1_000_000; + const SPEND_AMOUNT: u128 = 10_000_000; // treasury location from a sibling parachain. let treasury_location: Location = Location::new(1, PalletInstance(18)); // treasury account on a sibling parachain. @@ -190,13 +190,7 @@ fn create_and_claim_treasury_spend_in_usdt() { AssetHubRococo::execute_with(|| { type Assets = ::Assets; - // create an asset class and mint some assets to the treasury account. - assert_ok!(>::create( - ASSET_ID, - treasury_account.clone(), - true, - SPEND_AMOUNT / 2 - )); + // USDT created at genesis, mint some assets to the treasury account. assert_ok!(>::mint_into(ASSET_ID, &treasury_account, SPEND_AMOUNT * 4)); // beneficiary has zero balance. assert_eq!(>::balance(ASSET_ID, &alice,), 0u128,); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs index 0415af580ef8..abfbd9b45a98 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs @@ -35,11 +35,13 @@ mod imports { xcm_emulator::{ assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, TestExt, }, + ASSETS_PALLET_ID, USDT_ID, }; pub use parachains_common::AccountId; pub use rococo_westend_system_emulated_network::{ asset_hub_rococo_emulated_chain::{ - genesis::ED as ASSET_HUB_ROCOCO_ED, AssetHubRococoParaPallet as AssetHubRococoPallet, + genesis::{AssetHubRococoAssetOwner, ED as ASSET_HUB_ROCOCO_ED}, + AssetHubRococoParaPallet as AssetHubRococoPallet, }, asset_hub_westend_emulated_chain::{ genesis::ED as ASSET_HUB_WESTEND_ED, AssetHubWestendParaPallet as AssetHubWestendPallet, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs index 87fb70e4de23..995c992c70f4 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs @@ -15,7 +15,7 @@ use crate::tests::*; -fn send_asset_from_asset_hub_rococo_to_asset_hub_westend(id: Location, amount: u128) { +fn send_assets_from_asset_hub_rococo_to_asset_hub_westend(assets: Assets, fee_idx: u32) { let destination = asset_hub_westend_location(); // fund the AHR's SA on BHR for paying bridge transport fees @@ -26,7 +26,7 @@ fn send_asset_from_asset_hub_rococo_to_asset_hub_westend(id: Location, amount: u BridgeHubRococo::force_xcm_version(bridge_hub_westend_location(), XCM_VERSION); // send message over bridge - assert_ok!(send_asset_from_asset_hub_rococo(destination, (id, amount))); + assert_ok!(send_assets_from_asset_hub_rococo(destination, assets, fee_idx)); assert_bridge_hub_rococo_message_accepted(true); assert_bridge_hub_westend_message_received(); } @@ -103,80 +103,42 @@ fn send_asset_from_penpal_rococo_through_local_asset_hub_to_westend_asset_hub( } #[test] -fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() { - let roc_at_asset_hub_rococo: v3::Location = v3::Parent.into(); - let roc_at_asset_hub_westend = - v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Rococo)]); - let owner: AccountId = AssetHubWestend::account_id_of(ALICE); - AssetHubWestend::force_create_foreign_asset( - roc_at_asset_hub_westend, - owner, - true, - ASSET_MIN_BALANCE, - vec![], - ); +/// Test transfer of ROC, USDT and wETH from AssetHub Rococo to AssetHub Westend. +/// +/// This mix of assets should cover the whole range: +/// - native assets: ROC, +/// - trust-based assets: USDT (exists only on Rococo, Westend gets it from Rococo over bridge), +/// - foreign asset / bridged asset (other bridge / Snowfork): wETH (bridged from Ethereum to Rococo +/// over Snowbridge, then bridged over to Westend through this bridge). +fn send_roc_usdt_and_weth_from_asset_hub_rococo_to_asset_hub_westend() { + let amount = ASSET_HUB_ROCOCO_ED * 1_000_000; + let sender = AssetHubRococoSender::get(); + let receiver = AssetHubWestendReceiver::get(); + let roc_at_asset_hub_rococo: v3::Location = roc_at_ah_rococo().try_into().unwrap(); + let bridged_roc_at_asset_hub_westend = bridged_roc_at_ah_westend().try_into().unwrap(); + + create_foreign_on_ah_westend(bridged_roc_at_asset_hub_westend, true); + set_up_pool_with_wnd_on_ah_westend(bridged_roc_at_asset_hub_westend); + + //////////////////////////////////////////////////////////// + // Let's first send over just some ROCs as a simple example + //////////////////////////////////////////////////////////// let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( Westend, AssetHubWestend::para_id(), ); - - AssetHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - - // setup a pool to pay xcm fees with `roc_at_asset_hub_westend` tokens - assert_ok!(::ForeignAssets::mint( - ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - roc_at_asset_hub_westend.into(), - AssetHubWestendSender::get().into(), - 3_000_000_000_000, - )); - - assert_ok!(::AssetConversion::create_pool( - ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - Box::new(xcm::v3::Parent.into()), - Box::new(roc_at_asset_hub_westend), - )); - - assert_expected_events!( - AssetHubWestend, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, - ] - ); - - assert_ok!(::AssetConversion::add_liquidity( - ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - Box::new(xcm::v3::Parent.into()), - Box::new(roc_at_asset_hub_westend), - 1_000_000_000_000, - 2_000_000_000_000, - 1, - 1, - AssetHubWestendSender::get().into() - )); - - assert_expected_events!( - AssetHubWestend, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, - ] - ); - }); - let rocs_in_reserve_on_ahr_before = ::account_data_of(sov_ahw_on_ahr.clone()).free; - let sender_rocs_before = - ::account_data_of(AssetHubRococoSender::get()).free; - let receiver_rocs_before = AssetHubWestend::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(roc_at_asset_hub_westend, &AssetHubWestendReceiver::get()) - }); + let sender_rocs_before = ::account_data_of(sender.clone()).free; + let receiver_rocs_before = + foreign_balance_on_ah_westend(bridged_roc_at_asset_hub_westend, &receiver); - let amount = ASSET_HUB_ROCOCO_ED * 1_000_000; - send_asset_from_asset_hub_rococo_to_asset_hub_westend( - roc_at_asset_hub_rococo.try_into().unwrap(), - amount, - ); + // send ROCs, use them for fees + let assets: Assets = (Location::try_from(roc_at_asset_hub_rococo).unwrap(), amount).into(); + let fee_index = 0; + send_assets_from_asset_hub_rococo_to_asset_hub_westend(assets, fee_index); + + // verify expected events on final destination AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; assert_expected_events!( @@ -185,7 +147,7 @@ fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() { // issue ROCs on AHW RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { asset_id: *asset_id == roc_at_asset_hub_rococo, - owner: *owner == AssetHubWestendReceiver::get(), + owner: owner == &receiver, }, // message processed successfully RuntimeEvent::MessageQueue( @@ -195,36 +157,99 @@ fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() { ); }); - let sender_rocs_after = - ::account_data_of(AssetHubRococoSender::get()).free; - let receiver_rocs_after = AssetHubWestend::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(roc_at_asset_hub_westend, &AssetHubWestendReceiver::get()) - }); + let sender_rocs_after = ::account_data_of(sender.clone()).free; + let receiver_rocs_after = + foreign_balance_on_ah_westend(bridged_roc_at_asset_hub_westend, &receiver); let rocs_in_reserve_on_ahr_after = ::account_data_of(sov_ahw_on_ahr.clone()).free; - // Sender's balance is reduced + // Sender's ROC balance is reduced assert!(sender_rocs_before > sender_rocs_after); - // Receiver's balance is increased + // Receiver's ROC balance is increased assert!(receiver_rocs_after > receiver_rocs_before); - // Reserve balance is increased by sent amount + // Reserve ROC balance is increased by sent amount assert_eq!(rocs_in_reserve_on_ahr_after, rocs_in_reserve_on_ahr_before + amount); + + ///////////////////////////////////////////////////////////// + // Now let's send over USDTs + wETH (and pay fees with USDT) + ///////////////////////////////////////////////////////////// + + let usdt_at_asset_hub_rococo = usdt_at_ah_rococo(); + let bridged_usdt_at_asset_hub_westend = bridged_usdt_at_ah_westend().try_into().unwrap(); + // wETH has same relative location on both Rococo and Westend AssetHubs + let bridged_weth_at_ah = weth_at_asset_hubs().try_into().unwrap(); + + // mint USDT in sender's account (USDT already created in genesis) + AssetHubRococo::mint_asset( + ::RuntimeOrigin::signed(AssetHubRococoAssetOwner::get()), + USDT_ID, + sender.clone(), + amount * 2, + ); + // create wETH at src and dest and prefund sender's account + create_foreign_on_ah_rococo(bridged_weth_at_ah, true, vec![(sender.clone(), amount * 2)]); + create_foreign_on_ah_westend(bridged_weth_at_ah, true); + create_foreign_on_ah_westend(bridged_usdt_at_asset_hub_westend, true); + set_up_pool_with_wnd_on_ah_westend(bridged_usdt_at_asset_hub_westend); + + let receiver_usdts_before = + foreign_balance_on_ah_westend(bridged_usdt_at_asset_hub_westend, &receiver); + let receiver_weth_before = foreign_balance_on_ah_westend(bridged_weth_at_ah, &receiver); + + // send USDTs and wETHs + let assets: Assets = vec![ + (usdt_at_asset_hub_rococo.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_rococo.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!(AssetHubRococo::execute_with(|| { + ::PolkadotXcm::transfer_assets_using_type_and_then( + ::RuntimeOrigin::signed(sender.into()), + bx!(asset_hub_westend_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_rococo_message_accepted(true); + assert_bridge_hub_westend_message_received(); + AssetHubWestend::execute_with(|| { + AssetHubWestend::assert_xcmp_queue_success(None); + }); + + let receiver_usdts_after = + foreign_balance_on_ah_westend(bridged_usdt_at_asset_hub_westend, &receiver); + let receiver_weth_after = foreign_balance_on_ah_westend(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_wnds_from_asset_hub_rococo_to_asset_hub_westend() { +/// Send bridged WNDs "back" from AssetHub Rococo to AssetHub Westend. +fn send_back_wnds_from_asset_hub_rococo_to_asset_hub_westend() { let prefund_amount = 10_000_000_000_000u128; - let wnd_at_asset_hub_rococo = - v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Westend)]); - let owner: AccountId = AssetHubRococo::account_id_of(ALICE); - AssetHubRococo::force_create_foreign_asset( - wnd_at_asset_hub_rococo, - owner, - true, - ASSET_MIN_BALANCE, - vec![(AssetHubRococoSender::get(), prefund_amount)], - ); + let sender = AssetHubRococoSender::get(); + let receiver = AssetHubWestendReceiver::get(); + let wnd_at_asset_hub_rococo = bridged_wnd_at_ah_rococo(); + let wnd_at_asset_hub_rococo_v3 = wnd_at_asset_hub_rococo.clone().try_into().unwrap(); + let prefund_accounts = vec![(sender.clone(), prefund_amount)]; + create_foreign_on_ah_rococo(wnd_at_asset_hub_rococo_v3, true, prefund_accounts); // fund the AHR's SA on AHW with the WND tokens held in reserve let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( @@ -236,19 +261,17 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() { let wnds_in_reserve_on_ahw_before = ::account_data_of(sov_ahr_on_ahw.clone()).free; assert_eq!(wnds_in_reserve_on_ahw_before, prefund_amount); - let sender_wnds_before = AssetHubRococo::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(wnd_at_asset_hub_rococo, &AssetHubRococoSender::get()) - }); + + let sender_wnds_before = foreign_balance_on_ah_rococo(wnd_at_asset_hub_rococo_v3, &sender); assert_eq!(sender_wnds_before, prefund_amount); - let receiver_wnds_before = - ::account_data_of(AssetHubWestendReceiver::get()).free; + let receiver_wnds_before = ::account_data_of(receiver.clone()).free; let amount_to_send = ASSET_HUB_WESTEND_ED * 1_000; - send_asset_from_asset_hub_rococo_to_asset_hub_westend( - Location::try_from(wnd_at_asset_hub_rococo).unwrap(), - amount_to_send, + send_assets_from_asset_hub_rococo_to_asset_hub_westend( + (wnd_at_asset_hub_rococo, amount_to_send).into(), + 0, ); + AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; assert_expected_events!( @@ -263,7 +286,7 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() { }, // WNDs deposited to beneficiary RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { - who: *who == AssetHubWestendReceiver::get(), + who: who == &receiver, }, // message processed successfully RuntimeEvent::MessageQueue( @@ -273,12 +296,8 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() { ); }); - let sender_wnds_after = AssetHubRococo::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(wnd_at_asset_hub_rococo, &AssetHubRococoSender::get()) - }); - let receiver_wnds_after = - ::account_data_of(AssetHubWestendReceiver::get()).free; + let sender_wnds_after = foreign_balance_on_ah_rococo(wnd_at_asset_hub_rococo_v3, &sender); + let receiver_wnds_after = ::account_data_of(receiver).free; let wnds_in_reserve_on_ahw_after = ::account_data_of(sov_ahr_on_ahw).free; @@ -292,22 +311,13 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() { #[test] fn send_rocs_from_penpal_rococo_through_asset_hub_rococo_to_asset_hub_westend() { - let roc_at_rococo_parachains: Location = Parent.into(); - let roc_at_asset_hub_westend = Location::new(2, [Junction::GlobalConsensus(NetworkId::Rococo)]); - let owner: AccountId = AssetHubWestend::account_id_of(ALICE); - AssetHubWestend::force_create_foreign_asset( - roc_at_asset_hub_westend.clone().try_into().unwrap(), - owner, - true, - ASSET_MIN_BALANCE, - vec![], - ); - let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( - Westend, - AssetHubWestend::para_id(), - ); + let roc_at_rococo_parachains = roc_at_ah_rococo(); + let roc_at_asset_hub_westend = bridged_roc_at_ah_westend().try_into().unwrap(); + create_foreign_on_ah_westend(roc_at_asset_hub_westend, true); let amount = ASSET_HUB_ROCOCO_ED * 10_000_000; + let sender = PenpalASender::get(); + let receiver = AssetHubWestendReceiver::get(); let penpal_location = AssetHubRococo::sibling_location_of(PenpalA::para_id()); let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_location); // fund Penpal's sovereign account on AssetHub @@ -316,26 +326,23 @@ fn send_rocs_from_penpal_rococo_through_asset_hub_rococo_to_asset_hub_westend() PenpalA::mint_foreign_asset( ::RuntimeOrigin::signed(PenpalAssetOwner::get()), roc_at_rococo_parachains.clone(), - PenpalASender::get(), + sender.clone(), amount * 2, ); + let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( + Westend, + AssetHubWestend::para_id(), + ); let rocs_in_reserve_on_ahr_before = ::account_data_of(sov_ahw_on_ahr.clone()).free; let sender_rocs_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance( - roc_at_rococo_parachains.clone(), - &PenpalASender::get(), - ) - }); - let receiver_rocs_before = AssetHubWestend::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance( - roc_at_asset_hub_westend.clone().try_into().unwrap(), - &AssetHubWestendReceiver::get(), - ) + >::balance(roc_at_rococo_parachains.clone(), &sender) }); + let receiver_rocs_before = foreign_balance_on_ah_westend(roc_at_asset_hub_westend, &receiver); + + // Send ROCs over bridge send_asset_from_penpal_rococo_through_local_asset_hub_to_westend_asset_hub( roc_at_rococo_parachains.clone(), amount, @@ -349,7 +356,7 @@ fn send_rocs_from_penpal_rococo_through_asset_hub_rococo_to_asset_hub_westend() // issue ROCs on AHW RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { asset_id: *asset_id == roc_at_rococo_parachains.clone().try_into().unwrap(), - owner: *owner == AssetHubWestendReceiver::get(), + owner: owner == &receiver, }, // message processed successfully RuntimeEvent::MessageQueue( @@ -361,15 +368,9 @@ fn send_rocs_from_penpal_rococo_through_asset_hub_rococo_to_asset_hub_westend() let sender_rocs_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(roc_at_rococo_parachains, &PenpalASender::get()) - }); - let receiver_rocs_after = AssetHubWestend::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance( - roc_at_asset_hub_westend.try_into().unwrap(), - &AssetHubWestendReceiver::get(), - ) + >::balance(roc_at_rococo_parachains, &sender) }); + let receiver_rocs_after = foreign_balance_on_ah_westend(roc_at_asset_hub_westend, &receiver); let rocs_in_reserve_on_ahr_after = ::account_data_of(sov_ahw_on_ahr.clone()).free; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs index 88dad06434b0..58c52e1328c8 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs @@ -21,39 +21,141 @@ mod snowbridge; mod teleport; pub(crate) fn asset_hub_westend_location() -> Location { + Location::new(2, [GlobalConsensus(Westend), Parachain(AssetHubWestend::para_id().into())]) +} +pub(crate) fn bridge_hub_westend_location() -> Location { + Location::new(2, [GlobalConsensus(Westend), Parachain(BridgeHubWestend::para_id().into())]) +} + +// ROC and wROC +pub(crate) fn roc_at_ah_rococo() -> Location { + Parent.into() +} +pub(crate) fn bridged_roc_at_ah_westend() -> Location { + Location::new(2, [GlobalConsensus(Rococo)]) +} + +// wWND +pub(crate) fn bridged_wnd_at_ah_rococo() -> Location { + Location::new(2, [GlobalConsensus(Westend)]) +} + +// USDT and wUSDT +pub(crate) fn usdt_at_ah_rococo() -> Location { + Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())]) +} +pub(crate) fn bridged_usdt_at_ah_westend() -> Location { Location::new( 2, - [GlobalConsensus(NetworkId::Westend), Parachain(AssetHubWestend::para_id().into())], + [ + GlobalConsensus(Rococo), + Parachain(AssetHubRococo::para_id().into()), + PalletInstance(ASSETS_PALLET_ID), + GeneralIndex(USDT_ID.into()), + ], ) } -pub(crate) fn bridge_hub_westend_location() -> Location { +// wETH has same relative location on both Rococo and Westend AssetHubs +pub(crate) fn weth_at_asset_hubs() -> Location { Location::new( 2, - [GlobalConsensus(NetworkId::Westend), Parachain(BridgeHubWestend::para_id().into())], + [ + GlobalConsensus(Ethereum { chain_id: snowbridge::CHAIN_ID }), + AccountKey20 { network: None, key: snowbridge::WETH }, + ], ) } -pub(crate) fn send_asset_from_asset_hub_rococo( +pub(crate) fn create_foreign_on_ah_rococo( + id: v3::Location, + sufficient: bool, + prefund_accounts: Vec<(AccountId, u128)>, +) { + let owner = AssetHubRococo::account_id_of(ALICE); + let min = ASSET_MIN_BALANCE; + AssetHubRococo::force_create_foreign_asset(id, owner, sufficient, min, prefund_accounts); +} + +pub(crate) fn create_foreign_on_ah_westend(id: v3::Location, sufficient: bool) { + let owner = AssetHubWestend::account_id_of(ALICE); + AssetHubWestend::force_create_foreign_asset(id, owner, sufficient, ASSET_MIN_BALANCE, vec![]); +} + +pub(crate) fn foreign_balance_on_ah_rococo(id: v3::Location, who: &AccountId) -> u128 { + AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(id, who) + }) +} +pub(crate) fn foreign_balance_on_ah_westend(id: v3::Location, who: &AccountId) -> u128 { + AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(id, who) + }) +} + +// set up pool +pub(crate) fn set_up_pool_with_wnd_on_ah_westend(foreign_asset: v3::Location) { + let wnd: v3::Location = v3::Parent.into(); + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let owner = AssetHubWestendSender::get(); + let signed_owner = ::RuntimeOrigin::signed(owner.clone()); + + assert_ok!(::ForeignAssets::mint( + signed_owner.clone(), + foreign_asset.into(), + owner.clone().into(), + 3_000_000_000_000, + )); + assert_ok!(::AssetConversion::create_pool( + signed_owner.clone(), + Box::new(wnd), + Box::new(foreign_asset), + )); + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + assert_ok!(::AssetConversion::add_liquidity( + signed_owner.clone(), + Box::new(wnd), + Box::new(foreign_asset), + 1_000_000_000_000, + 2_000_000_000_000, + 1, + 1, + owner.into() + )); + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, + ] + ); + }); +} + +pub(crate) fn send_assets_from_asset_hub_rococo( destination: Location, - (id, amount): (Location, u128), + assets: Assets, + fee_idx: u32, ) -> DispatchResult { let signed_origin = ::RuntimeOrigin::signed(AssetHubRococoSender::get().into()); - let beneficiary: Location = AccountId32Junction { network: None, id: AssetHubWestendReceiver::get().into() }.into(); - let assets: Assets = (id, amount).into(); - let fee_asset_item = 0; - AssetHubRococo::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/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs index 78788634e6ff..652447fa5601 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/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 AssetHubRococo - fails - destination version not known assert_err!( - send_asset_from_asset_hub_rococo(destination.clone(), (native_token.clone(), amount)), + send_assets_from_asset_hub_rococo( + 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 AssetHubRococo - ok - assert_ok!(send_asset_from_asset_hub_rococo( + assert_ok!(send_assets_from_asset_hub_rococo( 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 AssetHubRococo - ok - assert_ok!(send_asset_from_asset_hub_rococo( + assert_ok!(send_assets_from_asset_hub_rococo( destination.clone(), - (native_token.clone(), amount) + (native_token.clone(), amount).into(), + 0, )); assert_bridge_hub_rococo_message_accepted(true); assert_bridge_hub_westend_message_received(); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs index 8196b27cfe02..e2f62fdb94ed 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs @@ -34,10 +34,10 @@ use sp_runtime::{DispatchError::Token, TokenError::FundsUnavailable}; use testnet_parachains_constants::rococo::snowbridge::EthereumNetwork; const INITIAL_FUND: u128 = 5_000_000_000 * ROCOCO_ED; -const CHAIN_ID: u64 = 11155111; +pub const CHAIN_ID: u64 = 11155111; const TREASURY_ACCOUNT: [u8; 32] = hex!("6d6f646c70792f74727372790000000000000000000000000000000000000000"); -const WETH: [u8; 20] = hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"); +pub const WETH: [u8; 20] = hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"); const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); const INSUFFICIENT_XCM_FEE: u128 = 1000; const XCM_FEE: u128 = 4_000_000_000; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml index 9ea8d0b81bdb..4be226f0dc7b 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml @@ -11,6 +11,7 @@ publish = false workspace = true [dependencies] +hex-literal = { workspace = true, default-features = true } # Substrate frame-support = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs index 36b846e10313..b92a22cef628 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs @@ -36,11 +36,13 @@ mod imports { xcm_emulator::{ assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, TestExt, }, + ASSETS_PALLET_ID, USDT_ID, }; pub use parachains_common::AccountId; pub use rococo_westend_system_emulated_network::{ asset_hub_rococo_emulated_chain::{ - genesis::ED as ASSET_HUB_ROCOCO_ED, AssetHubRococoParaPallet as AssetHubRococoPallet, + genesis::{AssetHubRococoAssetOwner, ED as ASSET_HUB_ROCOCO_ED}, + AssetHubRococoParaPallet as AssetHubRococoPallet, }, asset_hub_westend_emulated_chain::{ genesis::ED as ASSET_HUB_WESTEND_ED, AssetHubWestendParaPallet as AssetHubWestendPallet, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs index 597e77d9049c..63ccb0ff4862 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs @@ -14,7 +14,7 @@ // limitations under the License. use crate::tests::*; -fn send_asset_from_asset_hub_westend_to_asset_hub_rococo(id: Location, amount: u128) { +fn send_assets_from_asset_hub_westend_to_asset_hub_rococo(assets: Assets, fee_idx: u32) { let destination = asset_hub_rococo_location(); // fund the AHW's SA on BHW for paying bridge transport fees @@ -25,7 +25,7 @@ fn send_asset_from_asset_hub_westend_to_asset_hub_rococo(id: Location, amount: u BridgeHubWestend::force_xcm_version(bridge_hub_rococo_location(), XCM_VERSION); // send message over bridge - assert_ok!(send_asset_from_asset_hub_westend(destination, (id, amount))); + assert_ok!(send_assets_from_asset_hub_westend(destination, assets, fee_idx)); assert_bridge_hub_westend_message_accepted(true); assert_bridge_hub_rococo_message_received(); } @@ -102,77 +102,33 @@ fn send_asset_from_penpal_westend_through_local_asset_hub_to_rococo_asset_hub( } #[test] +/// Test transfer of WND from AssetHub Westend to AssetHub Rococo. fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() { - let wnd_at_asset_hub_westend: Location = Parent.into(); - let wnd_at_asset_hub_rococo = - v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Westend)]); - let owner: AccountId = AssetHubRococo::account_id_of(ALICE); - AssetHubRococo::force_create_foreign_asset( - wnd_at_asset_hub_rococo, - owner, - true, - ASSET_MIN_BALANCE, - vec![], - ); + let amount = ASSET_HUB_WESTEND_ED * 1_000; + let sender = AssetHubWestendSender::get(); + let receiver = AssetHubRococoReceiver::get(); + let wnd_at_asset_hub_westend = wnd_at_ah_westend(); + let bridged_wnd_at_asset_hub_rococo = bridged_wnd_at_ah_rococo().try_into().unwrap(); + create_foreign_on_ah_rococo(bridged_wnd_at_asset_hub_rococo, true); + + set_up_pool_with_roc_on_ah_rococo(bridged_wnd_at_asset_hub_rococo, true); + let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( Rococo, AssetHubRococo::para_id(), ); - - AssetHubRococo::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - - // setup a pool to pay xcm fees with `wnd_at_asset_hub_rococo` tokens - assert_ok!(::ForeignAssets::mint( - ::RuntimeOrigin::signed(AssetHubRococoSender::get()), - wnd_at_asset_hub_rococo.into(), - AssetHubRococoSender::get().into(), - 3_000_000_000_000, - )); - - assert_ok!(::AssetConversion::create_pool( - ::RuntimeOrigin::signed(AssetHubRococoSender::get()), - Box::new(xcm::v3::Parent.into()), - Box::new(wnd_at_asset_hub_rococo), - )); - - assert_expected_events!( - AssetHubRococo, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, - ] - ); - - assert_ok!(::AssetConversion::add_liquidity( - ::RuntimeOrigin::signed(AssetHubRococoSender::get()), - Box::new(xcm::v3::Parent.into()), - Box::new(wnd_at_asset_hub_rococo), - 1_000_000_000_000, - 2_000_000_000_000, - 1, - 1, - AssetHubRococoSender::get().into() - )); - - assert_expected_events!( - AssetHubRococo, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, - ] - ); - }); - let wnds_in_reserve_on_ahw_before = ::account_data_of(sov_ahr_on_ahw.clone()).free; - let sender_wnds_before = - ::account_data_of(AssetHubWestendSender::get()).free; - let receiver_wnds_before = AssetHubRococo::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(wnd_at_asset_hub_rococo, &AssetHubRococoReceiver::get()) - }); + let sender_wnds_before = ::account_data_of(sender.clone()).free; + let receiver_wnds_before = + foreign_balance_on_ah_rococo(bridged_wnd_at_asset_hub_rococo, &receiver); - let amount = ASSET_HUB_WESTEND_ED * 1_000; - send_asset_from_asset_hub_westend_to_asset_hub_rococo(wnd_at_asset_hub_westend, amount); + // send WNDs, use them for fees + let assets: Assets = (wnd_at_asset_hub_westend, amount).into(); + let fee_index = 0; + send_assets_from_asset_hub_westend_to_asset_hub_rococo(assets, fee_index); + + // verify expected events on final destination AssetHubRococo::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; assert_expected_events!( @@ -180,8 +136,8 @@ fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() { vec![ // issue WNDs on AHR RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == wnd_at_asset_hub_rococo, - owner: *owner == AssetHubRococoReceiver::get(), + asset_id: *asset_id == bridged_wnd_at_asset_hub_rococo, + owner: *owner == receiver, }, // message processed successfully RuntimeEvent::MessageQueue( @@ -191,12 +147,9 @@ fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() { ); }); - let sender_wnds_after = - ::account_data_of(AssetHubWestendSender::get()).free; - let receiver_wnds_after = AssetHubRococo::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(wnd_at_asset_hub_rococo, &AssetHubRococoReceiver::get()) - }); + let sender_wnds_after = ::account_data_of(sender).free; + let receiver_wnds_after = + foreign_balance_on_ah_rococo(bridged_wnd_at_asset_hub_rococo, &receiver); let wnds_in_reserve_on_ahw_after = ::account_data_of(sov_ahr_on_ahw).free; @@ -209,18 +162,27 @@ fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() { } #[test] -fn send_rocs_from_asset_hub_westend_to_asset_hub_rococo() { +/// Send bridged assets "back" from AssetHub Rococo to AssetHub Westend. +/// +/// This mix of assets should cover the whole range: +/// - bridged native assets: ROC, +/// - bridged trust-based assets: USDT (exists only on Rococo, Westend gets it from Rococo over +/// bridge), +/// - bridged foreign asset / double-bridged asset (other bridge / Snowfork): wETH (bridged from +/// Ethereum to Rococo over Snowbridge, then bridged over to Westend through this bridge). +fn send_back_rocs_usdt_and_weth_from_asset_hub_westend_to_asset_hub_rococo() { let prefund_amount = 10_000_000_000_000u128; - let roc_at_asset_hub_westend = - v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Rococo)]); - let owner: AccountId = AssetHubWestend::account_id_of(ALICE); - AssetHubWestend::force_create_foreign_asset( - roc_at_asset_hub_westend, - owner, - true, - ASSET_MIN_BALANCE, - vec![(AssetHubWestendSender::get(), prefund_amount)], - ); + let sender = AssetHubWestendSender::get(); + let receiver = AssetHubRococoReceiver::get(); + let bridged_roc_at_asset_hub_westend = bridged_roc_at_ah_westend(); + let bridged_roc_at_asset_hub_westend_v3 = + bridged_roc_at_asset_hub_westend.clone().try_into().unwrap(); + let prefund_accounts = vec![(sender.clone(), prefund_amount)]; + create_foreign_on_ah_westend(bridged_roc_at_asset_hub_westend_v3, true, prefund_accounts); + + //////////////////////////////////////////////////////////// + // Let's first send back just some ROCs as a simple example + //////////////////////////////////////////////////////////// // fund the AHW's SA on AHR with the ROC tokens held in reserve let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( @@ -232,18 +194,16 @@ fn send_rocs_from_asset_hub_westend_to_asset_hub_rococo() { let rocs_in_reserve_on_ahr_before = ::account_data_of(sov_ahw_on_ahr.clone()).free; assert_eq!(rocs_in_reserve_on_ahr_before, prefund_amount); - let sender_rocs_before = AssetHubWestend::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(roc_at_asset_hub_westend, &AssetHubWestendSender::get()) - }); + + let sender_rocs_before = + foreign_balance_on_ah_westend(bridged_roc_at_asset_hub_westend_v3, &sender); assert_eq!(sender_rocs_before, prefund_amount); - let receiver_rocs_before = - ::account_data_of(AssetHubRococoReceiver::get()).free; + let receiver_rocs_before = ::account_data_of(receiver.clone()).free; let amount_to_send = ASSET_HUB_ROCOCO_ED * 1_000; - send_asset_from_asset_hub_westend_to_asset_hub_rococo( - roc_at_asset_hub_westend.try_into().unwrap(), - amount_to_send, + send_assets_from_asset_hub_westend_to_asset_hub_rococo( + (bridged_roc_at_asset_hub_westend, amount_to_send).into(), + 0, ); AssetHubRococo::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; @@ -259,7 +219,7 @@ fn send_rocs_from_asset_hub_westend_to_asset_hub_rococo() { }, // ROCs deposited to beneficiary RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { - who: *who == AssetHubRococoReceiver::get(), + who: *who == receiver, }, // message processed successfully RuntimeEvent::MessageQueue( @@ -269,12 +229,9 @@ fn send_rocs_from_asset_hub_westend_to_asset_hub_rococo() { ); }); - let sender_rocs_after = AssetHubWestend::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance(roc_at_asset_hub_westend, &AssetHubWestendSender::get()) - }); - let receiver_rocs_after = - ::account_data_of(AssetHubRococoReceiver::get()).free; + let sender_rocs_after = + foreign_balance_on_ah_westend(bridged_roc_at_asset_hub_westend_v3, &sender); + let receiver_rocs_after = ::account_data_of(receiver.clone()).free; let rocs_in_reserve_on_ahr_after = ::account_data_of(sov_ahw_on_ahr.clone()).free; @@ -284,26 +241,107 @@ fn send_rocs_from_asset_hub_westend_to_asset_hub_rococo() { assert!(receiver_rocs_after > receiver_rocs_before); // Reserve balance is reduced by sent amount assert_eq!(rocs_in_reserve_on_ahr_after, rocs_in_reserve_on_ahr_before - amount_to_send); + + ////////////////////////////////////////////////////////////////// + // Now let's send back over USDTs + wETH (and pay fees with USDT) + ////////////////////////////////////////////////////////////////// + + // wETH has same relative location on both Rococo and Westend AssetHubs + let bridged_weth_at_ah = weth_at_asset_hubs().try_into().unwrap(); + let bridged_usdt_at_asset_hub_westend = bridged_usdt_at_ah_westend().try_into().unwrap(); + + // set up destination chain AH Rococo: + // create a ROC/USDT pool to be able to pay fees with USDT (USDT created in genesis) + set_up_pool_with_roc_on_ah_rococo(usdt_at_ah_rococo().try_into().unwrap(), false); + // create wETH on Rococo (IRL it's already created by Snowbridge) + create_foreign_on_ah_rococo(bridged_weth_at_ah, true); + // prefund AHW's sovereign account on AHR to be able to withdraw USDT and wETH from reserves + let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( + Westend, + AssetHubWestend::para_id(), + ); + AssetHubRococo::mint_asset( + ::RuntimeOrigin::signed(AssetHubRococoAssetOwner::get()), + USDT_ID, + sov_ahw_on_ahr.clone(), + amount_to_send * 2, + ); + AssetHubRococo::mint_foreign_asset( + ::RuntimeOrigin::signed(AssetHubRococo::account_id_of(ALICE)), + bridged_weth_at_ah, + sov_ahw_on_ahr, + amount_to_send * 2, + ); + + // set up source chain AH Westend: + // create wETH and USDT foreign assets on Westend and prefund sender's account + let prefund_accounts = vec![(sender.clone(), amount_to_send * 2)]; + create_foreign_on_ah_westend(bridged_weth_at_ah, true, prefund_accounts.clone()); + create_foreign_on_ah_westend(bridged_usdt_at_asset_hub_westend, true, prefund_accounts); + + // check balances before + let receiver_usdts_before = AssetHubRococo::execute_with(|| { + type Assets = ::Assets; + >::balance(USDT_ID, &receiver) + }); + let receiver_weth_before = foreign_balance_on_ah_rococo(bridged_weth_at_ah, &receiver); + + let usdt_id: AssetId = Location::try_from(bridged_usdt_at_asset_hub_westend).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!(AssetHubWestend::execute_with(|| { + ::PolkadotXcm::transfer_assets_using_type_and_then( + ::RuntimeOrigin::signed(sender.into()), + bx!(asset_hub_rococo_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_westend_message_accepted(true); + assert_bridge_hub_rococo_message_received(); + AssetHubRococo::execute_with(|| { + AssetHubRococo::assert_xcmp_queue_success(None); + }); + + let receiver_usdts_after = AssetHubRococo::execute_with(|| { + type Assets = ::Assets; + >::balance(USDT_ID, &receiver) + }); + let receiver_weth_after = foreign_balance_on_ah_rococo(bridged_weth_at_ah, &receiver); + + // 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_wnds_from_penpal_westend_through_asset_hub_westend_to_asset_hub_rococo() { - let wnd_at_westend_parachains: Location = Parent.into(); - let wnd_at_asset_hub_rococo = Location::new(2, [Junction::GlobalConsensus(NetworkId::Westend)]); - let owner: AccountId = AssetHubRococo::account_id_of(ALICE); - AssetHubRococo::force_create_foreign_asset( - wnd_at_asset_hub_rococo.clone().try_into().unwrap(), - owner, - true, - ASSET_MIN_BALANCE, - vec![], - ); - let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( - Rococo, - AssetHubRococo::para_id(), - ); + let wnd_at_westend_parachains = wnd_at_ah_westend(); + let wnd_at_asset_hub_rococo = bridged_wnd_at_ah_rococo().try_into().unwrap(); + create_foreign_on_ah_rococo(wnd_at_asset_hub_rococo, true); let amount = ASSET_HUB_WESTEND_ED * 10_000_000; + let sender = PenpalBSender::get(); + let receiver = AssetHubRococoReceiver::get(); let penpal_location = AssetHubWestend::sibling_location_of(PenpalB::para_id()); let sov_penpal_on_ahw = AssetHubWestend::sovereign_account_id_of(penpal_location); // fund Penpal's sovereign account on AssetHub @@ -312,26 +350,23 @@ fn send_wnds_from_penpal_westend_through_asset_hub_westend_to_asset_hub_rococo() PenpalB::mint_foreign_asset( ::RuntimeOrigin::signed(PenpalAssetOwner::get()), wnd_at_westend_parachains.clone(), - PenpalBSender::get(), + sender.clone(), amount * 2, ); + let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( + Rococo, + AssetHubRococo::para_id(), + ); let wnds_in_reserve_on_ahw_before = ::account_data_of(sov_ahr_on_ahw.clone()).free; let sender_wnds_before = PenpalB::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance( - wnd_at_westend_parachains.clone(), - &PenpalBSender::get(), - ) - }); - let receiver_wnds_before = AssetHubRococo::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance( - wnd_at_asset_hub_rococo.clone().try_into().unwrap(), - &AssetHubRococoReceiver::get(), - ) + >::balance(wnd_at_westend_parachains.clone(), &sender) }); + let receiver_wnds_before = foreign_balance_on_ah_rococo(wnd_at_asset_hub_rococo, &receiver); + + // Send WNDs over bridge send_asset_from_penpal_westend_through_local_asset_hub_to_rococo_asset_hub( wnd_at_westend_parachains.clone(), amount, @@ -345,7 +380,7 @@ fn send_wnds_from_penpal_westend_through_asset_hub_westend_to_asset_hub_rococo() // issue WNDs on AHR RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { asset_id: *asset_id == wnd_at_westend_parachains.clone().try_into().unwrap(), - owner: *owner == AssetHubRococoReceiver::get(), + owner: owner == &receiver, }, // message processed successfully RuntimeEvent::MessageQueue( @@ -357,15 +392,9 @@ fn send_wnds_from_penpal_westend_through_asset_hub_westend_to_asset_hub_rococo() let sender_wnds_after = PenpalB::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(wnd_at_westend_parachains, &PenpalBSender::get()) - }); - let receiver_wnds_after = AssetHubRococo::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance( - wnd_at_asset_hub_rococo.try_into().unwrap(), - &AssetHubRococoReceiver::get(), - ) + >::balance(wnd_at_westend_parachains, &sender) }); + let receiver_wnds_after = foreign_balance_on_ah_rococo(wnd_at_asset_hub_rococo, &receiver); let wnds_in_reserve_on_ahw_after = ::account_data_of(sov_ahr_on_ahw.clone()).free; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs index b781d6e987ca..92e864229a9c 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs @@ -19,40 +19,161 @@ mod asset_transfers; 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_rococo_location() -> Location { + Location::new(2, [GlobalConsensus(Rococo), Parachain(AssetHubRococo::para_id().into())]) +} + +pub(crate) fn bridge_hub_rococo_location() -> Location { + Location::new(2, [GlobalConsensus(Rococo), Parachain(BridgeHubRococo::para_id().into())]) +} + +// WND and wWND +pub(crate) fn wnd_at_ah_westend() -> Location { + Parent.into() +} +pub(crate) fn bridged_wnd_at_ah_rococo() -> Location { + Location::new(2, [GlobalConsensus(Westend)]) +} + +// wROC +pub(crate) fn bridged_roc_at_ah_westend() -> Location { + Location::new(2, [GlobalConsensus(Rococo)]) +} + +// USDT and wUSDT +pub(crate) fn usdt_at_ah_rococo() -> Location { + Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())]) +} +pub(crate) fn bridged_usdt_at_ah_westend() -> Location { Location::new( 2, - [GlobalConsensus(NetworkId::Rococo), Parachain(AssetHubRococo::para_id().into())], + [ + GlobalConsensus(Rococo), + Parachain(AssetHubRococo::para_id().into()), + PalletInstance(ASSETS_PALLET_ID), + GeneralIndex(USDT_ID.into()), + ], ) } -pub(crate) fn bridge_hub_rococo_location() -> Location { +// wETH has same relative location on both Rococo and Westend AssetHubs +pub(crate) fn weth_at_asset_hubs() -> Location { Location::new( 2, - [GlobalConsensus(NetworkId::Rococo), Parachain(BridgeHubRococo::para_id().into())], + [ + GlobalConsensus(Ethereum { chain_id: snowbridge::CHAIN_ID }), + AccountKey20 { network: None, key: snowbridge::WETH }, + ], ) } -pub(crate) fn send_asset_from_asset_hub_westend( +pub(crate) fn create_foreign_on_ah_rococo(id: v3::Location, sufficient: bool) { + let owner = AssetHubRococo::account_id_of(ALICE); + AssetHubRococo::force_create_foreign_asset(id, owner, sufficient, ASSET_MIN_BALANCE, vec![]); +} + +pub(crate) fn create_foreign_on_ah_westend( + id: v3::Location, + sufficient: bool, + prefund_accounts: Vec<(AccountId, u128)>, +) { + let owner = AssetHubWestend::account_id_of(ALICE); + let min = ASSET_MIN_BALANCE; + AssetHubWestend::force_create_foreign_asset(id, owner, sufficient, min, prefund_accounts); +} + +pub(crate) fn foreign_balance_on_ah_rococo(id: v3::Location, who: &AccountId) -> u128 { + AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(id, who) + }) +} +pub(crate) fn foreign_balance_on_ah_westend(id: v3::Location, who: &AccountId) -> u128 { + AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(id, who) + }) +} + +// set up pool +pub(crate) fn set_up_pool_with_roc_on_ah_rococo(asset: v3::Location, is_foreign: bool) { + let roc: v3::Location = v3::Parent.into(); + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let owner = AssetHubRococoSender::get(); + let signed_owner = ::RuntimeOrigin::signed(owner.clone()); + + if is_foreign { + assert_ok!(::ForeignAssets::mint( + signed_owner.clone(), + asset.into(), + owner.clone().into(), + 3_000_000_000_000, + )); + } else { + let asset_id = match asset.interior.split_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(roc), + Box::new(asset), + )); + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + assert_ok!(::AssetConversion::add_liquidity( + signed_owner.clone(), + Box::new(roc), + Box::new(asset), + 1_000_000_000_000, + 2_000_000_000_000, + 1, + 1, + owner.into() + )); + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, + ] + ); + }); +} + +pub(crate) fn send_assets_from_asset_hub_westend( destination: Location, - (id, amount): (Location, u128), + assets: Assets, + fee_idx: u32, ) -> DispatchResult { let signed_origin = ::RuntimeOrigin::signed(AssetHubWestendSender::get().into()); - let beneficiary: Location = AccountId32Junction { network: None, id: AssetHubRococoReceiver::get().into() }.into(); - let assets: Assets = (id, amount).into(); - let fee_asset_item = 0; - AssetHubWestend::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/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs index 8539df97be93..dee411bea8b7 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/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 AssetHubWestend - fails - destination version not known assert_err!( - send_asset_from_asset_hub_westend(destination.clone(), (native_token.clone(), amount)), + send_assets_from_asset_hub_westend( + 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 AssetHubWestend - ok - assert_ok!(send_asset_from_asset_hub_westend( + assert_ok!(send_assets_from_asset_hub_westend( 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 AssetHubWestend - ok - assert_ok!(send_asset_from_asset_hub_westend( + assert_ok!(send_assets_from_asset_hub_westend( destination.clone(), - (native_token.clone(), amount) + (native_token.clone(), amount).into(), + 0 )); assert_bridge_hub_westend_message_accepted(true); assert_bridge_hub_rococo_message_received(); diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs index cf5a3905e581..03de2c971b7f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs @@ -337,10 +337,11 @@ 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). Asset Hub may _act_ as a reserve location for ROC and assets created - // under `pallet-assets`. Users must use teleport where allowed (e.g. ROC with the Relay Chain). + // held). On Rococo Asset Hub, we allow Westend Asset Hub to act as reserve for any asset native + // to the Westend ecosystem. We also allow Ethereum contracts to act as reserves for the foreign + // assets identified by the same respective contracts locations. type IsReserve = ( - bridging::to_westend::IsTrustedBridgedReserveLocationForConcreteAsset, + bridging::to_westend::WestendAssetFromAssetHubWestend, bridging::to_ethereum::IsTrustedBridgedReserveLocationForForeignAsset, ); type IsTeleporter = TrustedTeleporters; @@ -568,13 +569,12 @@ pub mod bridging { ); pub const WestendNetwork: NetworkId = NetworkId::Westend; - pub AssetHubWestend: Location = Location::new(2, [GlobalConsensus(WestendNetwork::get()), Parachain(bp_asset_hub_westend::ASSET_HUB_WESTEND_PARACHAIN_ID)]); + pub WestendEcosystem: Location = Location::new(2, [GlobalConsensus(WestendNetwork::get())]); pub WndLocation: Location = Location::new(2, [GlobalConsensus(WestendNetwork::get())]); - - pub WndFromAssetHubWestend: (AssetFilter, Location) = ( - Wild(AllOf { fun: WildFungible, id: AssetId(WndLocation::get()) }), - AssetHubWestend::get() - ); + pub AssetHubWestend: Location = Location::new(2, [ + GlobalConsensus(WestendNetwork::get()), + Parachain(bp_asset_hub_westend::ASSET_HUB_WESTEND_PARACHAIN_ID) + ]); /// Set up exporters configuration. /// `Option` represents static "base fee" which is used for total delivery fee calculation. @@ -607,17 +607,9 @@ pub mod bridging { } } - /// Trusted reserve locations filter for `xcm_executor::Config::IsReserve`. - /// Locations from which the runtime accepts reserved assets. - pub type IsTrustedBridgedReserveLocationForConcreteAsset = - matching::IsTrustedBridgedReserveLocationForConcreteAsset< - UniversalLocation, - ( - // allow receive WND from AssetHubWestend - xcm_builder::Case, - // and nothing else - ), - >; + /// Allow any asset native to the Westend ecosystem if it comes from Westend Asset Hub. + pub type WestendAssetFromAssetHubWestend = + matching::RemoteAssetFromLocation, AssetHubWestend>; impl Contains for ToWestendXcmRouter { fn contains(call: &RuntimeCall) -> bool { @@ -672,7 +664,7 @@ pub mod bridging { } pub type IsTrustedBridgedReserveLocationForForeignAsset = - matching::IsForeignConcreteAsset>; + IsForeignConcreteAsset>; impl Contains<(Location, Junction)> for UniversalAliases { fn contains(alias: &(Location, Junction)) -> bool { diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index ff1fc99cba8a..fc2e68c599fd 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs @@ -357,9 +357,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). Asset Hub may _act_ as a reserve location for WND and assets created - // under `pallet-assets`. Users must use teleport where allowed (e.g. WND with the Relay Chain). - type IsReserve = (bridging::to_rococo::IsTrustedBridgedReserveLocationForConcreteAsset,); + // held). On Westend Asset Hub, we allow Rococo Asset Hub to act as reserve for any asset native + // to the Rococo or Ethereum ecosystems. + type IsReserve = (bridging::to_rococo::RococoOrEthereumAssetFromAssetHubRococo,); type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; type Barrier = Barrier; @@ -569,13 +569,14 @@ pub mod bridging { ); pub const RococoNetwork: NetworkId = NetworkId::Rococo; - pub AssetHubRococo: Location = Location::new(2, [GlobalConsensus(RococoNetwork::get()), Parachain(bp_asset_hub_rococo::ASSET_HUB_ROCOCO_PARACHAIN_ID)]); + pub const EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 }; + pub RococoEcosystem: Location = Location::new(2, [GlobalConsensus(RococoNetwork::get())]); pub RocLocation: Location = Location::new(2, [GlobalConsensus(RococoNetwork::get())]); - - pub RocFromAssetHubRococo: (AssetFilter, Location) = ( - Wild(AllOf { fun: WildFungible, id: AssetId(RocLocation::get()) }), - AssetHubRococo::get() - ); + pub EthereumEcosystem: Location = Location::new(2, [GlobalConsensus(EthereumNetwork::get())]); + pub AssetHubRococo: Location = Location::new(2, [ + GlobalConsensus(RococoNetwork::get()), + Parachain(bp_asset_hub_rococo::ASSET_HUB_ROCOCO_PARACHAIN_ID) + ]); /// Set up exporters configuration. /// `Option` represents static "base fee" which is used for total delivery fee calculation. @@ -608,17 +609,12 @@ 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 ROC from AssetHubRococo - xcm_builder::Case, - // and nothing else - ), - >; + /// Allow any asset native to the Rococo or Ethereum ecosystems if it comes from Rococo + /// Asset Hub. + pub type RococoOrEthereumAssetFromAssetHubRococo = matching::RemoteAssetFromLocation< + (StartsWith, StartsWith), + AssetHubRococo, + >; impl Contains for ToRococoXcmRouter { fn contains(call: &RuntimeCall) -> bool { diff --git a/cumulus/parachains/runtimes/assets/common/src/matching.rs b/cumulus/parachains/runtimes/assets/common/src/matching.rs index 3aad88e177ca..f356cb541315 100644 --- a/cumulus/parachains/runtimes/assets/common/src/matching.rs +++ b/cumulus/parachains/runtimes/assets/common/src/matching.rs @@ -14,7 +14,10 @@ // limitations under the License. use cumulus_primitives_core::ParaId; -use frame_support::{pallet_prelude::Get, traits::ContainsPair}; +use frame_support::{ + pallet_prelude::Get, + traits::{Contains, ContainsPair}, +}; use xcm::prelude::*; use xcm_builder::ensure_is_remote; @@ -94,36 +97,33 @@ impl< } } -/// Adapter verifies if it is allowed to receive `Asset` from `Location`. -/// -/// Note: `Location` has to be from a different global consensus. -pub struct IsTrustedBridgedReserveLocationForConcreteAsset( - sp_std::marker::PhantomData<(UniversalLocation, Reserves)>, +/// 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, Reserves: ContainsPair> - ContainsPair - for IsTrustedBridgedReserveLocationForConcreteAsset +impl, OriginLocation: Get> + ContainsPair for RemoteAssetFromLocation { fn contains(asset: &Asset, origin: &Location) -> bool { - let universal_source = UniversalLocation::get(); - log::trace!( - target: "xcm::contains", - "IsTrustedBridgedReserveLocationForConcreteAsset asset: {:?}, origin: {:?}, universal_source: {:?}", - asset, origin, universal_source - ); - - // check remote origin - if ensure_is_remote(universal_source.clone(), origin.clone()).is_err() { + let expected_origin = OriginLocation::get(); + // ensure `origin` is expected `OriginLocation` + if !expected_origin.eq(origin) { log::trace!( target: "xcm::contains", - "IsTrustedBridgedReserveLocationForConcreteAsset origin: {:?} is not remote to the universal_source: {:?}", - origin, universal_source + "RemoteAssetFromLocation asset: {:?}, origin: {:?} is not from expected {:?}", + asset, origin, expected_origin, ); return false + } else { + log::trace!( + target: "xcm::contains", + "RemoteAssetFromLocation asset: {asset:?}, origin: {origin:?}", + ); } - // check asset according to the configured reserve locations - Reserves::contains(asset, origin) + // ensure `asset` is from remote consensus listed in `AssetsAllowedNetworks` + AssetsAllowedNetworks::contains(&asset.id.0) } } diff --git a/prdoc/pr_4888.prdoc b/prdoc/pr_4888.prdoc new file mode 100644 index 000000000000..e8cfb25d924d --- /dev/null +++ b/prdoc/pr_4888.prdoc @@ -0,0 +1,35 @@ +title: "Allow any asset over the bridge lane between the two Asset Hubs" + +doc: + - audience: Runtime User + description: | + Allow all Rococo-native, Westend-native and Ethereum-native assets to flow over + the bridge between the Rococo and Westend AssetHubs. + + On Rococo Asset Hub, we allow Westend Asset Hub to act as reserve for any asset + native to the Westend ecosystem. + We also allow Ethereum contracts to act as reserves for the foreign assets + identified by the same respective contracts locations (on the other side of Snowbridge). + + On Westend Asset Hub, we allow Rococo Asset Hub to act as reserve for any asset + native to the Rococo or Ethereum ecosystems (practically providing Westend access + to Ethereum assets through double bridging: Ethereum <> Rococo <> Westend). + +crates: + - name: assets-common + bump: major + - name: asset-hub-rococo-runtime + bump: major + - name: asset-hub-westend-runtime + bump: major + - name: asset-hub-rococo-emulated-chain + bump: minor + - name: asset-hub-rococo-integration-tests + bump: minor + - name: bridge-hub-rococo-integration-tests + bump: minor + - name: bridge-hub-westend-integration-tests + bump: minor + - name: emulated-integration-tests-common + bump: minor +