Skip to content

Commit

Permalink
[AHs] Support registering assets on Asset Hubs over bridge (#5435)
Browse files Browse the repository at this point in the history
## Changes

Allows one Asset Hub on one side, to register assets on the other Asset
Hub over the bridge.

Rococo <> Ethereum test bridge will be dropped in favor of Westend <>
Ethereum test bridge. This PR also changes emulated tests to simulate
double bridging from Ethereum<>Westend<>Rococo.

## Tests

- [x] e2e test: Westend Asset Hub can register Westend and Ethereum
assets on Asset Hub Rococo
- [x] e2e test: Rococo Asset Hub can register Rococo assets on Asset Hub
Westend
- [x] e2e test (existing): Users on Ethereum can register Ethereum
assets on Asset Hub Westend
- [x] e2e test: transfer Rococo assets from Rococo to Westend and back
- [x] e2e test: transfer Westend assets from Westend to Rococo and back
- [x] e2e test: transfer Ethereum assets (bridged in through Snowbridge)
from Westend to Rococo and back

(cherry picked from commit 9714796)
  • Loading branch information
acatangiu authored and github-actions[bot] committed Nov 20, 2024
1 parent dba2dd5 commit 880e953
Show file tree
Hide file tree
Showing 14 changed files with 537 additions and 248 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ mod imports {
pub use xcm::{
latest::ParentThen,
prelude::{AccountId32 as AccountId32Junction, *},
v4,
v4::NetworkId::Westend as WestendId,
v4::{self, NetworkId::Westend as WestendId},
};
pub use xcm_executor::traits::TransferType;

Expand All @@ -38,17 +37,18 @@ mod imports {
xcm_emulator::{
assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, TestExt,
},
xcm_helpers::xcm_transact_paid_execution,
ASSETS_PALLET_ID, USDT_ID,
};
pub use parachains_common::AccountId;
pub use rococo_westend_system_emulated_network::{
asset_hub_rococo_emulated_chain::{
asset_hub_rococo_runtime::xcm_config as ahr_xcm_config,
genesis::{AssetHubRococoAssetOwner, ED as ASSET_HUB_ROCOCO_ED},
AssetHubRococoParaPallet as AssetHubRococoPallet,
genesis::ED as ASSET_HUB_ROCOCO_ED, AssetHubRococoParaPallet as AssetHubRococoPallet,
},
asset_hub_westend_emulated_chain::{
genesis::ED as ASSET_HUB_WESTEND_ED, AssetHubWestendParaPallet as AssetHubWestendPallet,
genesis::{AssetHubWestendAssetOwner, ED as ASSET_HUB_WESTEND_ED},
AssetHubWestendParaPallet as AssetHubWestendPallet,
},
bridge_hub_rococo_emulated_chain::{
genesis::ED as BRIDGE_HUB_ROCOCO_ED, BridgeHubRococoExistentialDeposit,
Expand Down Expand Up @@ -80,6 +80,7 @@ mod imports {
RococoRelayReceiver as RococoReceiver, RococoRelaySender as RococoSender,
};

pub const ASSET_ID: u32 = 1;
pub const ASSET_MIN_BALANCE: u128 = 1000;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,26 +113,17 @@ fn send_assets_from_penpal_rococo_through_rococo_ah_to_westend_ah(
}

#[test]
/// 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() {
/// Test transfer of ROC from AssetHub Rococo to AssetHub Westend.
fn send_roc_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 = roc_at_ah_rococo();
let bridged_roc_at_asset_hub_westend = bridged_roc_at_ah_westend();

create_foreign_on_ah_westend(bridged_roc_at_asset_hub_westend.clone(), true);
set_up_pool_with_wnd_on_ah_westend(bridged_roc_at_asset_hub_westend.clone());
set_up_pool_with_wnd_on_ah_westend(bridged_roc_at_asset_hub_westend.clone(), true);

////////////////////////////////////////////////////////////
// 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(),
Expand All @@ -146,8 +137,7 @@ fn send_roc_usdt_and_weth_from_asset_hub_rococo_to_asset_hub_westend() {
// send ROCs, use them for fees
send_assets_over_bridge(|| {
let destination = asset_hub_westend_location();
let assets: Assets =
(Location::try_from(roc_at_asset_hub_rococo.clone()).unwrap(), amount).into();
let assets: Assets = (roc_at_asset_hub_rococo.clone(), amount).into();
let fee_idx = 0;
assert_ok!(send_assets_from_asset_hub_rococo(destination, assets, fee_idx));
});
Expand Down Expand Up @@ -183,84 +173,18 @@ fn send_roc_usdt_and_weth_from_asset_hub_rococo_to_asset_hub_westend() {
assert!(receiver_rocs_after > receiver_rocs_before);
// 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();
// wETH has same relative location on both Rococo and Westend AssetHubs
let bridged_weth_at_ah = weth_at_asset_hubs();

// mint USDT in sender's account (USDT already created in genesis)
AssetHubRococo::mint_asset(
<AssetHubRococo as Chain>::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.clone(),
true,
vec![(sender.clone(), amount * 2)],
);
create_foreign_on_ah_westend(bridged_weth_at_ah.clone(), true);
create_foreign_on_ah_westend(bridged_usdt_at_asset_hub_westend.clone(), true);
set_up_pool_with_wnd_on_ah_westend(bridged_usdt_at_asset_hub_westend.clone());

let receiver_usdts_before =
foreign_balance_on_ah_westend(bridged_usdt_at_asset_hub_westend.clone(), &receiver);
let receiver_weth_before = foreign_balance_on_ah_westend(bridged_weth_at_ah.clone(), &receiver);

// send USDTs and wETHs
let assets: Assets = vec![
(usdt_at_asset_hub_rococo.clone(), amount).into(),
(Location::try_from(bridged_weth_at_ah.clone()).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(|| {
<AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
<AssetHubRococo as Chain>::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]
/// Send bridged WNDs "back" from AssetHub Rococo to AssetHub Westend.
fn send_back_wnds_from_asset_hub_rococo_to_asset_hub_westend() {
/// 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 Westend, Rococo gets it from Westend over
/// bridge),
/// - bridged foreign asset / double-bridged asset (other bridge / Snowfork): wETH (bridged from
/// Ethereum to Westend over Snowbridge, then bridged over to Rococo through this bridge).
fn send_back_wnds_usdt_and_weth_from_asset_hub_rococo_to_asset_hub_westend() {
let prefund_amount = 10_000_000_000_000u128;
let amount_to_send = ASSET_HUB_WESTEND_ED * 1_000;
let sender = AssetHubRococoSender::get();
Expand All @@ -269,6 +193,10 @@ fn send_back_wnds_from_asset_hub_rococo_to_asset_hub_westend() {
let prefund_accounts = vec![(sender.clone(), prefund_amount)];
create_foreign_on_ah_rococo(wnd_at_asset_hub_rococo.clone(), true, prefund_accounts);

////////////////////////////////////////////////////////////
// Let's first send back just some WNDs as a simple example
////////////////////////////////////////////////////////////

// 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(
Rococo,
Expand Down Expand Up @@ -317,7 +245,7 @@ fn send_back_wnds_from_asset_hub_rococo_to_asset_hub_westend() {
});

let sender_wnds_after = foreign_balance_on_ah_rococo(wnd_at_asset_hub_rococo, &sender);
let receiver_wnds_after = <AssetHubWestend as Chain>::account_data_of(receiver).free;
let receiver_wnds_after = <AssetHubWestend as Chain>::account_data_of(receiver.clone()).free;
let wnds_in_reserve_on_ahw_after =
<AssetHubWestend as Chain>::account_data_of(sov_ahr_on_ahw).free;

Expand All @@ -327,6 +255,96 @@ fn send_back_wnds_from_asset_hub_rococo_to_asset_hub_westend() {
assert!(receiver_wnds_after > receiver_wnds_before);
// Reserve balance is reduced by sent amount
assert_eq!(wnds_in_reserve_on_ahw_after, wnds_in_reserve_on_ahw_before - amount_to_send);

//////////////////////////////////////////////////////////////////
// Now let's send back over USDTs + wETH (and pay fees with USDT)
//////////////////////////////////////////////////////////////////

// wETH has same relative location on both Westend and Rococo AssetHubs
let bridged_weth_at_ah = weth_at_asset_hubs();
let bridged_usdt_at_asset_hub_rococo = bridged_usdt_at_ah_rococo();

// set up destination chain AH Westend:
// create a WND/USDT pool to be able to pay fees with USDT (USDT created in genesis)
set_up_pool_with_wnd_on_ah_westend(usdt_at_ah_westend(), false);
// create wETH on Westend (IRL it's already created by Snowbridge)
create_foreign_on_ah_westend(bridged_weth_at_ah.clone(), true);
// prefund AHR's sovereign account on AHW to be able to withdraw USDT and wETH from reserves
let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus(
Rococo,
AssetHubRococo::para_id(),
);
AssetHubWestend::mint_asset(
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendAssetOwner::get()),
USDT_ID,
sov_ahr_on_ahw.clone(),
amount_to_send * 2,
);
AssetHubWestend::mint_foreign_asset(
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestend::account_id_of(ALICE)),
bridged_weth_at_ah.clone(),
sov_ahr_on_ahw,
amount_to_send * 2,
);

// set up source chain AH Rococo:
// create wETH and USDT foreign assets on Rococo and prefund sender's account
let prefund_accounts = vec![(sender.clone(), amount_to_send * 2)];
create_foreign_on_ah_rococo(bridged_weth_at_ah.clone(), true, prefund_accounts.clone());
create_foreign_on_ah_rococo(bridged_usdt_at_asset_hub_rococo.clone(), true, prefund_accounts);

// check balances before
let receiver_usdts_before = AssetHubWestend::execute_with(|| {
type Assets = <AssetHubWestend as AssetHubWestendPallet>::Assets;
<Assets as Inspect<_>>::balance(USDT_ID, &receiver)
});
let receiver_weth_before = foreign_balance_on_ah_westend(bridged_weth_at_ah.clone(), &receiver);

let usdt_id: AssetId = Location::try_from(bridged_usdt_at_asset_hub_rococo).unwrap().into();
// send USDTs and wETHs
let assets: Assets = vec![
(usdt_id.clone(), amount_to_send).into(),
(Location::try_from(bridged_weth_at_ah.clone()).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!(AssetHubRococo::execute_with(|| {
<AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
<AssetHubRococo as Chain>::RuntimeOrigin::signed(sender.into()),
bx!(asset_hub_westend_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_rococo_message_accepted(true);
assert_bridge_hub_westend_message_received();
AssetHubWestend::execute_with(|| {
AssetHubWestend::assert_xcmp_queue_success(None);
});

let receiver_usdts_after = AssetHubWestend::execute_with(|| {
type Assets = <AssetHubWestend as AssetHubWestendPallet>::Assets;
<Assets as Inspect<_>>::balance(USDT_ID, &receiver)
});
let receiver_weth_after = foreign_balance_on_ah_westend(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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::imports::*;

mod asset_transfers;
mod claim_assets;
mod register_bridged_assets;
mod send_xcm;
mod snowbridge;
mod teleport;
Expand Down Expand Up @@ -45,15 +46,15 @@ pub(crate) fn bridged_wnd_at_ah_rococo() -> Location {
}

// USDT and wUSDT
pub(crate) fn usdt_at_ah_rococo() -> Location {
pub(crate) fn usdt_at_ah_westend() -> Location {
Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())])
}
pub(crate) fn bridged_usdt_at_ah_westend() -> Location {
pub(crate) fn bridged_usdt_at_ah_rococo() -> Location {
Location::new(
2,
[
GlobalConsensus(Rococo),
Parachain(AssetHubRococo::para_id().into()),
GlobalConsensus(Westend),
Parachain(AssetHubWestend::para_id().into()),
PalletInstance(ASSETS_PALLET_ID),
GeneralIndex(USDT_ID.into()),
],
Expand Down Expand Up @@ -100,23 +101,36 @@ pub(crate) fn foreign_balance_on_ah_westend(id: v4::Location, who: &AccountId) -
}

// set up pool
pub(crate) fn set_up_pool_with_wnd_on_ah_westend(foreign_asset: v4::Location) {
pub(crate) fn set_up_pool_with_wnd_on_ah_westend(asset: v4::Location, is_foreign: bool) {
let wnd: v4::Location = v4::Parent.into();
AssetHubWestend::execute_with(|| {
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
let owner = AssetHubWestendSender::get();
let signed_owner = <AssetHubWestend as Chain>::RuntimeOrigin::signed(owner.clone());

assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::ForeignAssets::mint(
signed_owner.clone(),
foreign_asset.clone().into(),
owner.clone().into(),
3_000_000_000_000,
));
if is_foreign {
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::ForeignAssets::mint(
signed_owner.clone(),
asset.clone().into(),
owner.clone().into(),
3_000_000_000_000,
));
} else {
let asset_id = match asset.interior.last() {
Some(v4::Junction::GeneralIndex(id)) => *id as u32,
_ => unreachable!(),
};
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::Assets::mint(
signed_owner.clone(),
asset_id.into(),
owner.clone().into(),
3_000_000_000_000,
));
}
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::create_pool(
signed_owner.clone(),
Box::new(wnd.clone()),
Box::new(foreign_asset.clone()),
Box::new(asset.clone()),
));
assert_expected_events!(
AssetHubWestend,
Expand All @@ -127,7 +141,7 @@ pub(crate) fn set_up_pool_with_wnd_on_ah_westend(foreign_asset: v4::Location) {
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::add_liquidity(
signed_owner.clone(),
Box::new(wnd),
Box::new(foreign_asset),
Box::new(asset),
1_000_000_000_000,
2_000_000_000_000,
1,
Expand All @@ -149,7 +163,7 @@ pub(crate) fn send_assets_from_asset_hub_rococo(
fee_idx: u32,
) -> DispatchResult {
let signed_origin =
<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get().into());
<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get());
let beneficiary: Location =
AccountId32Junction { network: None, id: AssetHubWestendReceiver::get().into() }.into();

Expand Down
Loading

0 comments on commit 880e953

Please sign in to comment.