feat: Rebrand Polkadot/Substrate references to PezkuwiChain

This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
@@ -0,0 +1,82 @@
[package]
name = "bridge-hub-pezkuwichain-integration-tests"
version = "1.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
description = "Bridge Hub Pezkuwichain runtime integration tests with xcm-emulator"
publish = false
[lints]
workspace = true
[dependencies]
codec = { workspace = true }
hex-literal = { workspace = true, default-features = true }
scale-info = { features = ["derive"], workspace = true }
# Bizinikiwi
pezframe-support = { workspace = true }
pezpallet-asset-conversion = { workspace = true }
pezpallet-assets = { workspace = true }
pezpallet-balances = { workspace = true }
pezpallet-message-queue = { workspace = true, default-features = true }
pezsp-core = { workspace = true }
pezsp-runtime = { workspace = true }
# Pezkuwi
pezpallet-xcm = { workspace = true }
xcm = { workspace = true }
xcm-builder = { workspace = true }
xcm-executor = { workspace = true }
# Bridges
bp-asset-hub-pezkuwichain = { workspace = true }
pezpallet-bridge-messages = { workspace = true }
# Pezcumulus
asset-hub-pezkuwichain-runtime = { workspace = true }
pezcumulus-pezpallet-xcmp-queue = { workspace = true }
emulated-integration-tests-common = { workspace = true }
pezkuwichain-system-emulated-network = { workspace = true }
pezkuwichain-zagros-system-emulated-network = { workspace = true }
testnet-teyrchains-constants = { features = [
"pezkuwichain",
"zagros",
], workspace = true, default-features = true }
teyrchains-common = { workspace = true, default-features = true }
# Snowbridge
snowbridge-inbound-queue-primitives = { workspace = true }
snowbridge-outbound-queue-primitives = { workspace = true }
snowbridge-pezpallet-inbound-queue-fixtures = { workspace = true, default-features = true }
snowbridge-pezpallet-outbound-queue = { workspace = true }
snowbridge-pezpallet-system = { workspace = true }
[features]
runtime-benchmarks = [
"asset-hub-pezkuwichain-runtime/runtime-benchmarks",
"bp-asset-hub-pezkuwichain/runtime-benchmarks",
"pezcumulus-pezpallet-xcmp-queue/runtime-benchmarks",
"emulated-integration-tests-common/runtime-benchmarks",
"pezframe-support/runtime-benchmarks",
"pezpallet-asset-conversion/runtime-benchmarks",
"pezpallet-assets/runtime-benchmarks",
"pezpallet-balances/runtime-benchmarks",
"pezpallet-bridge-messages/runtime-benchmarks",
"pezpallet-message-queue/runtime-benchmarks",
"pezpallet-xcm/runtime-benchmarks",
"pezkuwichain-system-emulated-network/runtime-benchmarks",
"pezkuwichain-zagros-system-emulated-network/runtime-benchmarks",
"snowbridge-inbound-queue-primitives/runtime-benchmarks",
"snowbridge-outbound-queue-primitives/runtime-benchmarks",
"snowbridge-pezpallet-inbound-queue-fixtures/runtime-benchmarks",
"snowbridge-pezpallet-outbound-queue/runtime-benchmarks",
"snowbridge-pezpallet-system/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"testnet-teyrchains-constants/runtime-benchmarks",
"teyrchains-common/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
"xcm/runtime-benchmarks",
]
@@ -0,0 +1,89 @@
// 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.
#[cfg(test)]
mod imports {
// Bizinikiwi
pub(crate) use codec::Encode;
pub(crate) use pezframe_support::{assert_err, assert_ok, pezpallet_prelude::DispatchResult};
pub(crate) use pezsp_runtime::DispatchError;
// Pezkuwi
pub(crate) use xcm::{
latest::{ParentThen, PEZKUWICHAIN_GENESIS_HASH, ZAGROS_GENESIS_HASH},
prelude::{AccountId32 as AccountId32Junction, *},
};
pub(crate) use xcm_builder::ExternalConsensusLocationsConverterFor;
pub(crate) use xcm_executor::traits::TransferType;
// Pezcumulus
pub(crate) use emulated_integration_tests_common::{
accounts::ALICE,
impls::Inspect,
test_dry_run_transfer_across_pk_bridge, test_relay_is_trusted_teleporter,
test_teyrchain_is_trusted_teleporter, test_teyrchain_is_trusted_teleporter_for_relay,
xcm_emulator::{
assert_expected_events, bx, Chain, RelayChain as Relay, TestExt, Teyrchain as Para,
},
xcm_helpers::xcm_transact_paid_execution,
ASSETS_PALLET_ID, USDT_ID,
};
pub(crate) use pezkuwichain_zagros_system_emulated_network::{
asset_hub_pezkuwichain_emulated_chain::{
asset_hub_pezkuwichain_runtime::{
xcm_config::TreasuryAccount, ForeignAssetReserveData,
},
genesis::ED as ASSET_HUB_PEZKUWICHAIN_ED,
AssetHubPezkuwichainParaPallet as AssetHubPezkuwichainPallet,
},
asset_hub_zagros_emulated_chain::{
genesis::{AssetHubZagrosAssetOwner, ED as ASSET_HUB_ZAGROS_ED},
AssetHubZagrosParaPallet as AssetHubZagrosPallet,
},
bridge_hub_pezkuwichain_emulated_chain::{
genesis::ED as BRIDGE_HUB_PEZKUWICHAIN_ED, BridgeHubPezkuwichainExistentialDeposit,
BridgeHubPezkuwichainParaPallet as BridgeHubPezkuwichainPallet,
},
penpal_emulated_chain::{
penpal_runtime::xcm_config::{
CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub,
UniversalLocation as PenpalUniversalLocation,
},
PenpalAParaPallet as PenpalAPallet, PenpalAssetOwner,
},
pezkuwichain_emulated_chain::{
genesis::ED as PEZKUWICHAIN_ED, PezkuwichainRelayPallet as PezkuwichainPallet,
},
AssetHubPezkuwichainPara as AssetHubPezkuwichain,
AssetHubPezkuwichainParaReceiver as AssetHubPezkuwichainReceiver,
AssetHubPezkuwichainParaSender as AssetHubPezkuwichainSender,
AssetHubZagrosPara as AssetHubZagros, AssetHubZagrosParaReceiver as AssetHubZagrosReceiver,
AssetHubZagrosParaSender as AssetHubZagrosSender,
BridgeHubPezkuwichainPara as BridgeHubPezkuwichain,
BridgeHubPezkuwichainParaReceiver as BridgeHubPezkuwichainReceiver,
BridgeHubPezkuwichainParaSender as BridgeHubPezkuwichainSender,
BridgeHubZagrosPara as BridgeHubZagros, PenpalAPara as PenpalA,
PenpalAParaSender as PenpalASender, PezkuwichainRelay as Pezkuwichain,
PezkuwichainRelayReceiver as PezkuwichainReceiver,
PezkuwichainRelaySender as PezkuwichainSender,
};
pub(crate) use teyrchains_common::AccountId;
pub(crate) const ASSET_ID: u32 = 1;
pub(crate) const ASSET_MIN_BALANCE: u128 = 1000;
}
#[cfg(test)]
mod tests;
@@ -0,0 +1,617 @@
// 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::*;
fn send_assets_over_bridge<F: FnOnce()>(send_fn: F) {
// fund the AHR's SA on BHR for paying bridge delivery fees
BridgeHubPezkuwichain::fund_para_sovereign(
AssetHubPezkuwichain::para_id(),
10_000_000_000_000u128,
);
// set XCM versions
let local_asset_hub = PenpalA::sibling_location_of(AssetHubPezkuwichain::para_id());
PenpalA::force_xcm_version(local_asset_hub.clone(), XCM_VERSION);
AssetHubPezkuwichain::force_xcm_version(asset_hub_zagros_location(), XCM_VERSION);
BridgeHubPezkuwichain::force_xcm_version(bridge_hub_zagros_location(), XCM_VERSION);
// send message over bridge
send_fn();
// process and verify intermediary hops
assert_bridge_hub_pezkuwichain_message_accepted(true);
assert_bridge_hub_zagros_message_received();
}
fn set_up_rocs_for_penpal_pezkuwichain_through_ahr_to_ahw(
sender: &AccountId,
amount: u128,
) -> (Location, v5::Location) {
let roc_at_pezkuwichain_teyrchains = roc_at_ah_pezkuwichain();
let roc_at_asset_hub_zagros = bridged_roc_at_ah_zagros();
let reserves = vec![(asset_hub_pezkuwichain_global_location(), false).into()];
create_foreign_on_ah_zagros(roc_at_asset_hub_zagros.clone(), true, reserves);
let penpal_location = AssetHubPezkuwichain::sibling_location_of(PenpalA::para_id());
let sov_penpal_on_ahr = AssetHubPezkuwichain::sovereign_account_id_of(penpal_location);
// fund Penpal's sovereign account on AssetHub
AssetHubPezkuwichain::fund_accounts(vec![(sov_penpal_on_ahr.into(), amount * 2)]);
// fund Penpal's sender account
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
roc_at_pezkuwichain_teyrchains.clone(),
sender.clone(),
amount * 2,
);
(roc_at_pezkuwichain_teyrchains, roc_at_asset_hub_zagros)
}
fn send_assets_from_penpal_pezkuwichain_through_pezkuwichain_ah_to_zagros_ah(
destination: Location,
assets: (Assets, TransferType),
fees: (AssetId, TransferType),
custom_xcm_on_dest: Xcm<()>,
) {
send_assets_over_bridge(|| {
let sov_penpal_on_ahr = AssetHubPezkuwichain::sovereign_account_id_of(
AssetHubPezkuwichain::sibling_location_of(PenpalA::para_id()),
);
// send message over bridge
assert_ok!(PenpalA::execute_with(|| {
let signed_origin = <PenpalA as Chain>::RuntimeOrigin::signed(PenpalASender::get());
<PenpalA as PenpalAPallet>::PezkuwiXcm::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 Pezkuwichain hop
AssetHubPezkuwichain::execute_with(|| {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubPezkuwichain,
vec![
// Amount to reserve transfer is withdrawn from Penpal's sovereign account
RuntimeEvent::Balances(
pezpallet_balances::Event::Burned { who, .. }
) => {
who: *who == sov_penpal_on_ahr.clone().into(),
},
// Amount deposited in AHW's sovereign account
RuntimeEvent::Balances(pezpallet_balances::Event::Minted { who, .. }) => {
who: *who == TreasuryAccount::get(),
},
RuntimeEvent::XcmpQueue(
cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }
) => {},
]
);
});
});
}
#[test]
/// Test transfer of TYR from AssetHub Pezkuwichain to AssetHub Zagros.
fn send_roc_from_asset_hub_pezkuwichain_to_asset_hub_zagros() {
let amount = ASSET_HUB_PEZKUWICHAIN_ED * 1_000_000;
let sender = AssetHubPezkuwichainSender::get();
let receiver = AssetHubZagrosReceiver::get();
let roc_at_asset_hub_pezkuwichain = roc_at_ah_pezkuwichain();
let bridged_roc_at_asset_hub_zagros = bridged_roc_at_ah_zagros();
let reserves = vec![(asset_hub_pezkuwichain_global_location(), false).into()];
create_foreign_on_ah_zagros(bridged_roc_at_asset_hub_zagros.clone(), true, reserves);
set_up_pool_with_wnd_on_ah_zagros(bridged_roc_at_asset_hub_zagros.clone(), true);
let sov_ahw_on_ahr =
AssetHubPezkuwichain::sovereign_account_of_teyrchain_on_other_global_consensus(
ByGenesis(ZAGROS_GENESIS_HASH),
AssetHubZagros::para_id(),
);
let rocs_in_reserve_on_ahr_before =
<AssetHubPezkuwichain as Chain>::account_data_of(sov_ahw_on_ahr.clone()).free;
let sender_rocs_before = <AssetHubPezkuwichain as Chain>::account_data_of(sender.clone()).free;
let receiver_rocs_before =
foreign_balance_on_ah_zagros(bridged_roc_at_asset_hub_zagros.clone(), &receiver);
// send TYRs, use them for fees
send_assets_over_bridge(|| {
let destination = asset_hub_zagros_location();
let assets: Assets = (roc_at_asset_hub_pezkuwichain.clone(), amount).into();
let fee_idx = 0;
let transfer_type = TransferType::LocalReserve;
assert_ok!(send_assets_from_asset_hub_pezkuwichain(
destination,
assets,
fee_idx,
transfer_type
));
});
// verify expected events on final destination
let roc = Location::new(2, [GlobalConsensus(ByGenesis(PEZKUWICHAIN_GENESIS_HASH))]);
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
// issue TYRs on AHW
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == roc,
owner: owner == &receiver,
},
// message processed successfully
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
});
let sender_rocs_after = <AssetHubPezkuwichain as Chain>::account_data_of(sender.clone()).free;
let receiver_rocs_after =
foreign_balance_on_ah_zagros(bridged_roc_at_asset_hub_zagros, &receiver);
let rocs_in_reserve_on_ahr_after =
<AssetHubPezkuwichain as Chain>::account_data_of(sov_ahw_on_ahr.clone()).free;
// Sender's TYR balance is reduced
assert!(sender_rocs_before > sender_rocs_after);
// Receiver's TYR balance is increased
assert!(receiver_rocs_after > receiver_rocs_before);
// Reserve TYR balance is increased by sent amount
assert_eq!(rocs_in_reserve_on_ahr_after, rocs_in_reserve_on_ahr_before + amount);
}
#[test]
/// Send bridged assets "back" from AssetHub Pezkuwichain to AssetHub Zagros.
///
/// This mix of assets should cover the whole range:
/// - bridged native assets: TYR,
/// - bridged trust-based assets: USDT (exists only on Zagros, Pezkuwichain gets it from Zagros over
/// bridge),
/// - bridged foreign asset / double-bridged asset (other bridge / Snowfork): wETH (bridged from
/// Ethereum to Zagros over Snowbridge, then bridged over to Pezkuwichain through this bridge).
fn send_back_wnds_usdt_and_weth_from_asset_hub_pezkuwichain_to_asset_hub_zagros() {
let prefund_amount = 10_000_000_000_000u128;
let amount_to_send = ASSET_HUB_ZAGROS_ED * 1_000;
let sender = AssetHubPezkuwichainSender::get();
let receiver = AssetHubZagrosReceiver::get();
let wnd_at_asset_hub_pezkuwichain = bridged_wnd_at_ah_pezkuwichain();
let prefund_accounts = vec![(sender.clone(), prefund_amount)];
let reserves = vec![(asset_hub_zagros_location(), false).into()];
create_foreign_on_ah_pezkuwichain(
wnd_at_asset_hub_pezkuwichain.clone(),
true,
reserves,
prefund_accounts,
);
////////////////////////////////////////////////////////////
// Let's first send back just some ZGRs as a simple example
////////////////////////////////////////////////////////////
// fund the AHR's SA on AHW with the ZGR tokens held in reserve
let sov_ahr_on_ahw = AssetHubZagros::sovereign_account_of_teyrchain_on_other_global_consensus(
ByGenesis(PEZKUWICHAIN_GENESIS_HASH),
AssetHubPezkuwichain::para_id(),
);
AssetHubZagros::fund_accounts(vec![(sov_ahr_on_ahw.clone(), prefund_amount)]);
let wnds_in_reserve_on_ahw_before =
<AssetHubZagros as Chain>::account_data_of(sov_ahr_on_ahw.clone()).free;
assert_eq!(wnds_in_reserve_on_ahw_before, prefund_amount);
let sender_wnds_before =
foreign_balance_on_ah_pezkuwichain(wnd_at_asset_hub_pezkuwichain.clone(), &sender);
assert_eq!(sender_wnds_before, prefund_amount);
let receiver_wnds_before = <AssetHubZagros as Chain>::account_data_of(receiver.clone()).free;
// send back WNDs, use them for fees
send_assets_over_bridge(|| {
let destination = asset_hub_zagros_location();
let assets: Assets = (wnd_at_asset_hub_pezkuwichain.clone(), amount_to_send).into();
let fee_idx = 0;
let transfer_type = TransferType::DestinationReserve;
assert_ok!(send_assets_from_asset_hub_pezkuwichain(
destination,
assets,
fee_idx,
transfer_type
));
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
// ZGR is withdrawn from AHR's SA on AHW
RuntimeEvent::Balances(
pezpallet_balances::Event::Burned { who, amount }
) => {
who: *who == sov_ahr_on_ahw,
amount: *amount == amount_to_send,
},
// ZGRs deposited to beneficiary
RuntimeEvent::Balances(pezpallet_balances::Event::Minted { who, .. }) => {
who: who == &receiver,
},
// message processed successfully
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
});
let sender_wnds_after =
foreign_balance_on_ah_pezkuwichain(wnd_at_asset_hub_pezkuwichain, &sender);
let receiver_wnds_after = <AssetHubZagros as Chain>::account_data_of(receiver.clone()).free;
let wnds_in_reserve_on_ahw_after =
<AssetHubZagros as Chain>::account_data_of(sov_ahr_on_ahw).free;
// Sender's balance is reduced
assert!(sender_wnds_before > sender_wnds_after);
// Receiver's balance is increased
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 Zagros and Pezkuwichain AssetHubs
let bridged_weth_at_ah = weth_at_asset_hubs();
let bridged_usdt_at_asset_hub_pezkuwichain = bridged_usdt_at_ah_pezkuwichain();
// set up destination chain AH Zagros:
// create a ZGR/USDT pool to be able to pay fees with USDT (USDT created in genesis)
set_up_pool_with_wnd_on_ah_zagros(usdt_at_ah_zagros(), false);
// prefund AHR's sovereign account on AHW to be able to withdraw USDT and wETH from reserves
let sov_ahr_on_ahw = AssetHubZagros::sovereign_account_of_teyrchain_on_other_global_consensus(
ByGenesis(PEZKUWICHAIN_GENESIS_HASH),
AssetHubPezkuwichain::para_id(),
);
let sov_ahw_on_ahr =
AssetHubPezkuwichain::sovereign_account_of_teyrchain_on_other_global_consensus(
ByGenesis(ZAGROS_GENESIS_HASH),
AssetHubZagros::para_id(),
);
AssetHubZagros::mint_asset(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosAssetOwner::get()),
USDT_ID,
sov_ahr_on_ahw.clone(),
amount_to_send * 2,
);
AssetHubZagros::mint_foreign_asset(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(snowbridge_sovereign()),
bridged_weth_at_ah.clone(),
sov_ahr_on_ahw.clone(),
amount_to_send * 2,
);
AssetHubPezkuwichain::mint_foreign_asset(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(sov_ahw_on_ahr.clone()),
bridged_weth_at_ah.clone(),
sov_ahr_on_ahw,
prefund_amount,
);
AssetHubPezkuwichain::mint_foreign_asset(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(sov_ahw_on_ahr),
bridged_weth_at_ah.clone(),
sender.clone(),
prefund_amount,
);
// set up source chain AH Pezkuwichain:
// create wETH and USDT foreign assets on Pezkuwichain and prefund sender's account
let prefund_accounts = vec![(sender.clone(), amount_to_send * 2)];
let reserves = vec![(asset_hub_zagros_location(), false).into()];
create_foreign_on_ah_pezkuwichain(
bridged_usdt_at_asset_hub_pezkuwichain.clone(),
true,
reserves,
prefund_accounts,
);
// check balances before
let receiver_usdts_before = AssetHubZagros::execute_with(|| {
type Assets = <AssetHubZagros as AssetHubZagrosPallet>::Assets;
<Assets as Inspect<_>>::balance(USDT_ID, &receiver)
});
let receiver_weth_before = foreign_balance_on_ah_zagros(bridged_weth_at_ah.clone(), &receiver);
let usdt_id: AssetId =
Location::try_from(bridged_usdt_at_asset_hub_pezkuwichain).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!(AssetHubPezkuwichain::execute_with(|| {
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::PezkuwiXcm::transfer_assets_using_type_and_then(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(sender.into()),
bx!(asset_hub_zagros_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_pezkuwichain_message_accepted(true);
assert_bridge_hub_zagros_message_received();
AssetHubZagros::execute_with(|| {
AssetHubZagros::assert_xcmp_queue_success(None);
});
let receiver_usdts_after = AssetHubZagros::execute_with(|| {
type Assets = <AssetHubZagros as AssetHubZagrosPallet>::Assets;
<Assets as Inspect<_>>::balance(USDT_ID, &receiver)
});
let receiver_weth_after = foreign_balance_on_ah_zagros(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_rocs_from_penpal_pezkuwichain_through_asset_hub_pezkuwichain_to_asset_hub_zagros() {
let amount = ASSET_HUB_PEZKUWICHAIN_ED * 10_000_000;
let sender = PenpalASender::get();
let receiver = AssetHubZagrosReceiver::get();
let local_asset_hub = PenpalA::sibling_location_of(AssetHubPezkuwichain::para_id());
let (roc_at_pezkuwichain_teyrchains, roc_at_asset_hub_zagros) =
set_up_rocs_for_penpal_pezkuwichain_through_ahr_to_ahw(&sender, amount);
set_up_pool_with_wnd_on_ah_zagros(roc_at_asset_hub_zagros.clone(), true);
let sov_ahw_on_ahr =
AssetHubPezkuwichain::sovereign_account_of_teyrchain_on_other_global_consensus(
ByGenesis(ZAGROS_GENESIS_HASH),
AssetHubZagros::para_id(),
);
let rocs_in_reserve_on_ahr_before =
<AssetHubPezkuwichain as Chain>::account_data_of(sov_ahw_on_ahr.clone()).free;
let sender_rocs_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_at_pezkuwichain_teyrchains.clone(), &sender)
});
let receiver_rocs_before =
foreign_balance_on_ah_zagros(roc_at_asset_hub_zagros.clone(), &receiver);
// Send TYRs over bridge
{
let destination = asset_hub_zagros_location();
let assets: Assets = (roc_at_pezkuwichain_teyrchains.clone(), amount).into();
let asset_transfer_type = TransferType::RemoteReserve(local_asset_hub.clone().into());
let fees_id: AssetId = roc_at_pezkuwichain_teyrchains.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_pezkuwichain_through_pezkuwichain_ah_to_zagros_ah(
destination,
(assets, asset_transfer_type),
(fees_id, fees_transfer_type),
custom_xcm_on_dest,
);
}
// process AHW incoming message and check events
let roc = Location::new(2, [GlobalConsensus(ByGenesis(PEZKUWICHAIN_GENESIS_HASH))]);
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
// issue TYRs on AHW
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == roc,
owner: owner == &receiver,
},
// message processed successfully
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
});
let sender_rocs_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_at_pezkuwichain_teyrchains, &sender)
});
let receiver_rocs_after = foreign_balance_on_ah_zagros(roc_at_asset_hub_zagros, &receiver);
let rocs_in_reserve_on_ahr_after =
<AssetHubPezkuwichain as Chain>::account_data_of(sov_ahw_on_ahr.clone()).free;
// Sender's balance is reduced
assert!(sender_rocs_after < sender_rocs_before);
// Receiver's balance is increased
assert!(receiver_rocs_after > receiver_rocs_before);
// Reserve balance is increased by sent amount (less fess)
assert!(rocs_in_reserve_on_ahr_after > rocs_in_reserve_on_ahr_before);
assert!(rocs_in_reserve_on_ahr_after <= rocs_in_reserve_on_ahr_before + amount);
}
#[test]
fn send_back_wnds_from_penpal_pezkuwichain_through_asset_hub_pezkuwichain_to_asset_hub_zagros() {
let wnd_at_pezkuwichain_teyrchains = bridged_wnd_at_ah_pezkuwichain();
let amount = ASSET_HUB_PEZKUWICHAIN_ED * 10_000_000;
let sender = PenpalASender::get();
let receiver = AssetHubZagrosReceiver::get();
// set up TYRs for transfer
let (roc_at_pezkuwichain_teyrchains, _) =
set_up_rocs_for_penpal_pezkuwichain_through_ahr_to_ahw(&sender, amount);
// set up ZGRs for transfer
let penpal_location = AssetHubPezkuwichain::sibling_location_of(PenpalA::para_id());
let sov_penpal_on_ahr = AssetHubPezkuwichain::sovereign_account_id_of(penpal_location);
let prefund_accounts = vec![(sov_penpal_on_ahr, amount * 2)];
let reserves = vec![(asset_hub_zagros_location(), false).into()];
create_foreign_on_ah_pezkuwichain(
wnd_at_pezkuwichain_teyrchains.clone(),
true,
reserves,
prefund_accounts,
);
let asset_owner: AccountId = AssetHubPezkuwichain::account_id_of(ALICE);
PenpalA::force_create_foreign_asset(
wnd_at_pezkuwichain_teyrchains.clone(),
asset_owner.clone(),
true,
ASSET_MIN_BALANCE,
vec![(sender.clone(), amount * 2)],
);
// Configure source Penpal chain to trust local AH as reserve of bridged ZGR
PenpalA::execute_with(|| {
assert_ok!(<PenpalA as Chain>::System::set_storage(
<PenpalA as Chain>::RuntimeOrigin::root(),
vec![(
PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(),
wnd_at_pezkuwichain_teyrchains.encode(),
)],
));
});
// fund the AHR's SA on AHW with the ZGR tokens held in reserve
let sov_ahr_on_ahw = AssetHubZagros::sovereign_account_of_teyrchain_on_other_global_consensus(
NetworkId::ByGenesis(PEZKUWICHAIN_GENESIS_HASH),
AssetHubPezkuwichain::para_id(),
);
AssetHubZagros::fund_accounts(vec![(sov_ahr_on_ahw.clone(), amount * 2)]);
// balances before
let sender_wnds_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(
wnd_at_pezkuwichain_teyrchains.clone().into(),
&sender,
)
});
let receiver_wnds_before = <AssetHubZagros as Chain>::account_data_of(receiver.clone()).free;
// send ZGRs over the bridge, TYRs only used to pay fees on local AH, pay with ZGR on remote AH
{
let final_destination = asset_hub_zagros_location();
let intermediary_hop = PenpalA::sibling_location_of(AssetHubPezkuwichain::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 ZGR as fees on the final destination (AHW)
let remote_fees: Asset = (wnd_at_pezkuwichain_teyrchains.clone(), amount).into();
let remote_fees = remote_fees.reanchored(&final_destination, &context).unwrap();
// buy execution using WNDs, then deposit all remaining WNDs
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 Zagros) to the view of hop (Asset Hub Pezkuwichain)
let mut final_destination = final_destination.clone();
final_destination.reanchor(&intermediary_hop, &context).unwrap();
// reanchor ZGRs to the view of hop (Asset Hub Pezkuwichain)
let asset: Asset = (wnd_at_pezkuwichain_teyrchains.clone(), amount).into();
let asset = asset.reanchored(&intermediary_hop, &context).unwrap();
// on Asset Hub Pezkuwichain, forward a request to withdraw ZGRs from reserve on Asset Hub
// Zagros
let xcm_on_hop = Xcm::<()>(vec![InitiateReserveWithdraw {
assets: Definite(asset.into()), // WNDs
reserve: final_destination, // AHW
xcm: xcm_on_final_dest, // XCM to execute on AHW
}]);
// assets to send from Penpal and how they reach the intermediary hop
let assets: Assets = vec![
(wnd_at_pezkuwichain_teyrchains.clone(), amount).into(),
(roc_at_pezkuwichain_teyrchains.clone(), amount).into(),
]
.into();
let asset_transfer_type = TransferType::DestinationReserve;
let fees_id: AssetId = roc_at_pezkuwichain_teyrchains.into();
let fees_transfer_type = TransferType::DestinationReserve;
// initiate the transfer
send_assets_from_penpal_pezkuwichain_through_pezkuwichain_ah_to_zagros_ah(
intermediary_hop,
(assets, asset_transfer_type),
(fees_id, fees_transfer_type),
xcm_on_hop,
);
}
// process AHW incoming message and check events
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
// issue TYRs on AHW
RuntimeEvent::Balances(pezpallet_balances::Event::Issued { .. }) => {},
// message processed successfully
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
});
let sender_wnds_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_pezkuwichain_teyrchains.into(), &sender)
});
let receiver_wnds_after = <AssetHubZagros as Chain>::account_data_of(receiver).free;
// Sender's balance is reduced by sent "amount"
assert_eq!(sender_wnds_after, sender_wnds_before - amount);
// Receiver's balance is increased by no more than "amount"
assert!(receiver_wnds_after > receiver_wnds_before);
assert!(receiver_wnds_after <= receiver_wnds_before + amount);
}
#[test]
fn dry_run_transfer_to_zagros_sends_xcm_to_bridge_hub() {
test_dry_run_transfer_across_pk_bridge!(
AssetHubPezkuwichain,
BridgeHubPezkuwichain,
asset_hub_zagros_location()
);
}
@@ -0,0 +1,34 @@
// 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.
//! Tests related to claiming assets trapped during XCM execution.
use crate::imports::*;
use emulated_integration_tests_common::test_chain_can_claim_assets;
#[test]
fn assets_can_be_claimed() {
let amount = BridgeHubPezkuwichainExistentialDeposit::get();
let assets: Assets = (Parent, amount).into();
test_chain_can_claim_assets!(
AssetHubPezkuwichain,
RuntimeCall,
NetworkId::ByGenesis(PEZKUWICHAIN_GENESIS_HASH),
assets,
amount
);
}
@@ -0,0 +1,303 @@
// 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::imports::*;
use emulated_integration_tests_common::{snowbridge, snowbridge::WETH};
use testnet_teyrchains_constants::pezkuwichain::snowbridge::EthereumNetwork;
use xcm::opaque::v5;
use xcm_executor::traits::ConvertLocation;
mod asset_transfers;
mod claim_assets;
mod register_bridged_assets;
mod send_xcm;
mod teleport;
pub(crate) fn asset_hub_zagros_location() -> Location {
Location::new(
2,
[
GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)),
Teyrchain(AssetHubZagros::para_id().into()),
],
)
}
pub(crate) fn asset_hub_pezkuwichain_global_location() -> Location {
Location::new(
2,
[
GlobalConsensus(ByGenesis(PEZKUWICHAIN_GENESIS_HASH)),
Teyrchain(AssetHubPezkuwichain::para_id().into()),
],
)
}
pub(crate) fn bridge_hub_zagros_location() -> Location {
Location::new(
2,
[
GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)),
Teyrchain(BridgeHubZagros::para_id().into()),
],
)
}
// TYR and wTYR
pub(crate) fn roc_at_ah_pezkuwichain() -> Location {
Parent.into()
}
pub(crate) fn bridged_roc_at_ah_zagros() -> Location {
Location::new(2, [GlobalConsensus(ByGenesis(PEZKUWICHAIN_GENESIS_HASH))])
}
// ZGR and wWND
pub(crate) fn bridged_wnd_at_ah_pezkuwichain() -> Location {
Location::new(2, [GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH))])
}
// USDT and wUSDT
pub(crate) fn usdt_at_ah_zagros() -> Location {
Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())])
}
pub(crate) fn bridged_usdt_at_ah_pezkuwichain() -> Location {
Location::new(
2,
[
GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)),
Teyrchain(AssetHubZagros::para_id().into()),
PalletInstance(ASSETS_PALLET_ID),
GeneralIndex(USDT_ID.into()),
],
)
}
// wETH has same relative location on both Pezkuwichain and Zagros AssetHubs
pub(crate) fn weth_at_asset_hubs() -> Location {
Location::new(
2,
[
GlobalConsensus(Ethereum { chain_id: snowbridge::SEPOLIA_ID }),
AccountKey20 { network: None, key: WETH },
],
)
}
pub(crate) fn create_foreign_on_ah_pezkuwichain(
id: v5::Location,
sufficient: bool,
reserves: Vec<ForeignAssetReserveData>,
prefund_accounts: Vec<(AccountId, u128)>,
) {
let owner = AssetHubPezkuwichain::account_id_of(ALICE);
let min = ASSET_MIN_BALANCE;
AssetHubPezkuwichain::force_create_foreign_asset(
id.clone(),
owner.clone(),
sufficient,
min,
prefund_accounts,
);
AssetHubPezkuwichain::set_foreign_asset_reserves(id, owner, reserves);
}
pub(crate) fn create_foreign_on_ah_zagros(
id: v5::Location,
sufficient: bool,
reserves: Vec<ForeignAssetReserveData>,
) {
let owner = AssetHubZagros::account_id_of(ALICE);
AssetHubZagros::force_create_foreign_asset(
id.clone(),
owner.clone(),
sufficient,
ASSET_MIN_BALANCE,
vec![],
);
AssetHubZagros::set_foreign_asset_reserves(id, owner, reserves);
}
pub(crate) fn foreign_balance_on_ah_pezkuwichain(id: v5::Location, who: &AccountId) -> u128 {
AssetHubPezkuwichain::execute_with(|| {
type Assets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(id, who)
})
}
pub(crate) fn foreign_balance_on_ah_zagros(id: v5::Location, who: &AccountId) -> u128 {
AssetHubZagros::execute_with(|| {
type Assets = <AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(id, who)
})
}
// set up pool
pub(crate) fn set_up_pool_with_wnd_on_ah_zagros(asset: v5::Location, is_foreign: bool) {
let wnd: v5::Location = v5::Parent.into();
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
let owner = AssetHubZagrosSender::get();
let signed_owner = <AssetHubZagros as Chain>::RuntimeOrigin::signed(owner.clone());
if is_foreign {
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::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(GeneralIndex(id)) => *id as u32,
_ => unreachable!(),
};
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::Assets::mint(
signed_owner.clone(),
asset_id.into(),
owner.clone().into(),
3_000_000_000_000,
));
}
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::AssetConversion::create_pool(
signed_owner.clone(),
Box::new(wnd.clone()),
Box::new(asset.clone()),
));
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::PoolCreated { .. }) => {},
]
);
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::AssetConversion::add_liquidity(
signed_owner.clone(),
Box::new(wnd),
Box::new(asset),
1_000_000_000_000,
2_000_000_000_000,
1,
1,
owner.into()
));
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::LiquidityAdded {..}) => {},
]
);
});
}
pub(crate) fn send_assets_from_asset_hub_pezkuwichain(
destination: Location,
assets: Assets,
fee_idx: u32,
// For knowing what reserve to pick.
// We only allow using the same transfer type for assets and fees right now.
// And only `LocalReserve` or `DestinationReserve`.
transfer_type: TransferType,
) -> DispatchResult {
let signed_origin =
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(AssetHubPezkuwichainSender::get());
let beneficiary: Location =
AccountId32Junction { network: None, id: AssetHubZagrosReceiver::get().into() }.into();
type Runtime = <AssetHubPezkuwichain as Chain>::Runtime;
let remote_fee_id: AssetId = assets
.clone()
.into_inner()
.get(fee_idx as usize)
.ok_or(pezpallet_xcm::Error::<Runtime>::Empty)?
.clone()
.id;
AssetHubPezkuwichain::execute_with(|| {
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::PezkuwiXcm::transfer_assets_using_type_and_then(
signed_origin,
bx!(destination.into()),
bx!(assets.into()),
bx!(transfer_type.clone()),
bx!(remote_fee_id.into()),
bx!(transfer_type),
bx!(VersionedXcm::from(
Xcm::<()>::builder_unsafe().deposit_asset(AllCounted(1), beneficiary).build()
)),
WeightLimit::Unlimited,
)
})
}
pub(crate) fn assert_bridge_hub_pezkuwichain_message_accepted(expected_processed: bool) {
BridgeHubPezkuwichain::execute_with(|| {
type RuntimeEvent = <BridgeHubPezkuwichain as Chain>::RuntimeEvent;
if expected_processed {
assert_expected_events!(
BridgeHubPezkuwichain,
vec![
// pay for bridge fees
RuntimeEvent::Balances(pezpallet_balances::Event::Burned { .. }) => {},
// message exported
RuntimeEvent::BridgeZagrosMessages(
pezpallet_bridge_messages::Event::MessageAccepted { .. }
) => {},
// message processed successfully
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
} else {
assert_expected_events!(
BridgeHubPezkuwichain,
vec![
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed {
success: false,
..
}) => {},
]
);
}
});
}
pub(crate) fn assert_bridge_hub_zagros_message_received() {
BridgeHubZagros::execute_with(|| {
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
BridgeHubZagros,
vec![
// message sent to destination
RuntimeEvent::XcmpQueue(
cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }
) => {},
]
);
})
}
pub fn snowbridge_sovereign() -> pezsp_runtime::AccountId32 {
use asset_hub_pezkuwichain_runtime::xcm_config::UniversalLocation as AssetHubPezkuwichainUniversalLocation;
let ethereum_sovereign: AccountId = AssetHubPezkuwichain::execute_with(|| {
ExternalConsensusLocationsConverterFor::<
AssetHubPezkuwichainUniversalLocation,
[u8; 32],
>::convert_location(&Location::new(
2,
[xcm::v5::Junction::GlobalConsensus(EthereumNetwork::get())],
))
.unwrap()
.into()
});
ethereum_sovereign
}
@@ -0,0 +1,109 @@
// 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::{imports::*, tests::*};
const XCM_FEE: u128 = 4_000_000_000_000;
/// Tests the registering of a Pezkuwichain Asset as a bridged asset on Zagros Asset Hub.
#[test]
fn register_pezkuwichain_asset_on_wah_from_rah() {
let sa_of_rah_on_wah = AssetHubZagros::sovereign_account_of_teyrchain_on_other_global_consensus(
ByGenesis(PEZKUWICHAIN_GENESIS_HASH),
AssetHubPezkuwichain::para_id(),
);
// Pezkuwichain Asset Hub asset when bridged to Zagros Asset Hub.
let bridged_asset_at_wah = Location::new(
2,
[
GlobalConsensus(ByGenesis(PEZKUWICHAIN_GENESIS_HASH)),
Teyrchain(AssetHubPezkuwichain::para_id().into()),
PalletInstance(ASSETS_PALLET_ID),
GeneralIndex(ASSET_ID.into()),
],
);
// Encoded `create_asset` call to be executed in Zagros Asset Hub ForeignAssets pallet.
let call = AssetHubZagros::create_foreign_asset_call(
bridged_asset_at_wah.clone(),
ASSET_MIN_BALANCE,
sa_of_rah_on_wah.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_rah_on_wah.clone());
// SA-of-RAH-on-WAH needs to have balance to pay for fees and asset creation deposit
AssetHubZagros::fund_accounts(vec![(
sa_of_rah_on_wah.clone(),
ASSET_HUB_ZAGROS_ED * 10000000000,
)]);
let destination = asset_hub_zagros_location();
// fund the RAH's SA on RBH for paying bridge delivery fees
BridgeHubPezkuwichain::fund_para_sovereign(
AssetHubPezkuwichain::para_id(),
10_000_000_000_000u128,
);
// set XCM versions
AssetHubPezkuwichain::force_xcm_version(destination.clone(), XCM_VERSION);
BridgeHubPezkuwichain::force_xcm_version(bridge_hub_zagros_location(), XCM_VERSION);
let root_origin = <AssetHubPezkuwichain as Chain>::RuntimeOrigin::root();
AssetHubPezkuwichain::execute_with(|| {
assert_ok!(<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::PezkuwiXcm::send(
root_origin,
bx!(destination.into()),
bx!(xcm),
));
AssetHubPezkuwichain::assert_xcm_pallet_sent();
});
assert_bridge_hub_pezkuwichain_message_accepted(true);
assert_bridge_hub_zagros_message_received();
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
AssetHubZagros::assert_xcmp_queue_success(None);
assert_expected_events!(
AssetHubZagros,
vec![
// Burned the fee
RuntimeEvent::Balances(pezpallet_balances::Event::Burned { who, amount }) => {
who: *who == sa_of_rah_on_wah.clone(),
amount: *amount == fee_amount,
},
// Foreign Asset created
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Created { asset_id, creator, owner }) => {
asset_id: asset_id == &bridged_asset_at_wah,
creator: *creator == sa_of_rah_on_wah.clone(),
owner: *owner == sa_of_rah_on_wah,
},
// Unspent fee minted to origin
RuntimeEvent::Balances(pezpallet_balances::Event::Minted { who, .. }) => {
who: *who == sa_of_rah_on_wah.clone(),
},
]
);
type ForeignAssets = <AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets;
assert!(ForeignAssets::asset_exists(bridged_asset_at_wah));
});
}
@@ -0,0 +1,155 @@
// 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 pezkuwichain_system_emulated_network::pezkuwichain_emulated_chain::pezkuwichain_runtime::Dmp;
use crate::tests::*;
#[test]
fn send_xcm_from_pezkuwichain_relay_to_zagros_asset_hub_should_fail_on_not_applicable() {
// Init tests variables
// XcmPallet send arguments
let sudo_origin = <Pezkuwichain as Chain>::RuntimeOrigin::root();
let destination = Pezkuwichain::child_location_of(BridgeHubPezkuwichain::para_id()).into();
let weight_limit = WeightLimit::Unlimited;
let check_origin = None;
let remote_xcm = Xcm(vec![ClearOrigin]);
let xcm = VersionedXcm::from(Xcm(vec![
UnpaidExecution { weight_limit, check_origin },
ExportMessage {
network: ByGenesis(ZAGROS_GENESIS_HASH),
destination: [Teyrchain(AssetHubZagros::para_id().into())].into(),
xcm: remote_xcm,
},
]));
// Pezkuwichain Global Consensus
// Send XCM message from Relay Chain to Bridge Hub source Teyrchain
Pezkuwichain::execute_with(|| {
Dmp::make_teyrchain_reachable(BridgeHubPezkuwichain::para_id());
assert_ok!(<Pezkuwichain as PezkuwichainPallet>::XcmPallet::send(
sudo_origin,
bx!(destination),
bx!(xcm),
));
type RuntimeEvent = <Pezkuwichain as Chain>::RuntimeEvent;
assert_expected_events!(
Pezkuwichain,
vec![
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::Sent { .. }) => {},
]
);
});
// Receive XCM message in Bridge Hub source Teyrchain, it should fail, because we don't have
// opened bridge/lane.
assert_bridge_hub_pezkuwichain_message_accepted(false);
}
#[test]
fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() {
// prepare data
let destination = asset_hub_zagros_location();
let native_token = Location::parent();
let amount = ASSET_HUB_PEZKUWICHAIN_ED * 1_000;
// fund the AHR's SA on BHR for paying bridge delivery fees
BridgeHubPezkuwichain::fund_para_sovereign(
AssetHubPezkuwichain::para_id(),
10_000_000_000_000u128,
);
// fund sender
AssetHubPezkuwichain::fund_accounts(vec![(
AssetHubPezkuwichainSender::get().into(),
amount * 10,
)]);
// Initially set only default version on all runtimes
let newer_xcm_version = xcm::prelude::XCM_VERSION;
let older_xcm_version = newer_xcm_version - 1;
AssetHubPezkuwichain::force_default_xcm_version(Some(older_xcm_version));
BridgeHubPezkuwichain::force_default_xcm_version(Some(older_xcm_version));
BridgeHubZagros::force_default_xcm_version(Some(older_xcm_version));
AssetHubZagros::force_default_xcm_version(Some(older_xcm_version));
// send XCM from AssetHubPezkuwichain - fails - destination version not known
assert_err!(
send_assets_from_asset_hub_pezkuwichain(
destination.clone(),
(native_token.clone(), amount).into(),
0,
TransferType::LocalReserve
),
DispatchError::Module(pezsp_runtime::ModuleError {
index: 31,
error: [1, 0, 0, 0],
message: Some("SendFailure")
})
);
// set destination version
AssetHubPezkuwichain::force_xcm_version(destination.clone(), newer_xcm_version);
// set version with `ExportMessage` for BridgeHubPezkuwichain
AssetHubPezkuwichain::force_xcm_version(
ParentThen(Teyrchain(BridgeHubPezkuwichain::para_id().into()).into()).into(),
newer_xcm_version,
);
// send XCM from AssetHubPezkuwichain - ok
assert_ok!(send_assets_from_asset_hub_pezkuwichain(
destination.clone(),
(native_token.clone(), amount).into(),
0,
TransferType::LocalReserve
));
// `ExportMessage` on local BridgeHub - fails - remote BridgeHub version not known
assert_bridge_hub_pezkuwichain_message_accepted(false);
// set version for remote BridgeHub on BridgeHubPezkuwichain
BridgeHubPezkuwichain::force_xcm_version(bridge_hub_zagros_location(), newer_xcm_version);
// set version for AssetHubZagros on BridgeHubZagros
BridgeHubZagros::force_xcm_version(
ParentThen(Teyrchain(AssetHubZagros::para_id().into()).into()).into(),
newer_xcm_version,
);
// send XCM from AssetHubPezkuwichain - ok
assert_ok!(send_assets_from_asset_hub_pezkuwichain(
destination.clone(),
(native_token.clone(), amount).into(),
0,
TransferType::LocalReserve
));
assert_bridge_hub_pezkuwichain_message_accepted(true);
assert_bridge_hub_zagros_message_received();
// message delivered and processed at destination
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
// message processed with failure, but for this scenario it is ok, important is that was delivered
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: false, .. }
) => {},
]
);
});
}
@@ -0,0 +1,84 @@
// 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::imports::*;
#[test]
fn teleport_via_limited_teleport_assets_to_other_system_teyrchains_works() {
let amount = BRIDGE_HUB_PEZKUWICHAIN_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
BridgeHubPezkuwichain, // Origin
vec![AssetHubPezkuwichain], // Destinations
(native_asset, amount),
fee_asset_id,
limited_teleport_assets
);
}
#[test]
fn teleport_via_transfer_assets_to_other_system_teyrchains_works() {
let amount = BRIDGE_HUB_PEZKUWICHAIN_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
BridgeHubPezkuwichain, // Origin
vec![AssetHubPezkuwichain], // Destinations
(native_asset, amount),
fee_asset_id,
transfer_assets
);
}
#[test]
fn teleport_via_limited_teleport_assets_from_and_to_relay() {
let amount = PEZKUWICHAIN_ED * 100;
test_relay_is_trusted_teleporter!(
Pezkuwichain,
vec![BridgeHubPezkuwichain],
amount,
limited_teleport_assets
);
test_teyrchain_is_trusted_teleporter_for_relay!(
BridgeHubPezkuwichain,
Pezkuwichain,
amount,
limited_teleport_assets
);
}
#[test]
fn teleport_via_transfer_assets_from_and_to_relay() {
let amount = PEZKUWICHAIN_ED * 100;
test_relay_is_trusted_teleporter!(
Pezkuwichain,
vec![BridgeHubPezkuwichain],
amount,
transfer_assets
);
test_teyrchain_is_trusted_teleporter_for_relay!(
BridgeHubPezkuwichain,
Pezkuwichain,
amount,
transfer_assets
);
}
@@ -0,0 +1,100 @@
[package]
name = "bridge-hub-zagros-integration-tests"
version = "1.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
description = "Bridge Hub Zagros runtime integration tests with xcm-emulator"
publish = false
[lints]
workspace = true
[dependencies]
codec = { workspace = true }
hex-literal = { workspace = true, default-features = true }
scale-info = { workspace = true }
# Bizinikiwi
pezframe-support = { workspace = true }
pezpallet-asset-conversion = { workspace = true }
pezpallet-assets = { workspace = true }
pezpallet-balances = { workspace = true }
pezpallet-message-queue = { workspace = true, default-features = true }
pezsp-core = { workspace = true }
pezsp-io = { workspace = true }
pezsp-runtime = { workspace = true }
# Pezkuwi
pezpallet-xcm = { workspace = true }
xcm = { workspace = true }
xcm-builder = { workspace = true }
xcm-executor = { workspace = true }
# Bridges
pezpallet-bridge-messages = { workspace = true }
pezpallet-bridge-relayers = { workspace = true }
# Pezcumulus
asset-hub-zagros-runtime = { workspace = true }
bp-asset-hub-zagros = { workspace = true }
bridge-hub-common = { workspace = true }
bridge-hub-zagros-runtime = { workspace = true }
pezcumulus-pezpallet-teyrchain-system = { workspace = true }
pezcumulus-pezpallet-xcmp-queue = { workspace = true }
emulated-integration-tests-common = { workspace = true }
pezkuwichain-zagros-system-emulated-network = { workspace = true }
testnet-teyrchains-constants = { features = [
"pezkuwichain",
"zagros",
], workspace = true, default-features = true }
teyrchains-common = { workspace = true, default-features = true }
# Snowbridge
snowbridge-core = { workspace = true }
snowbridge-inbound-queue-primitives = { workspace = true }
snowbridge-outbound-queue-primitives = { workspace = true }
snowbridge-pezpallet-inbound-queue = { workspace = true }
snowbridge-pezpallet-inbound-queue-fixtures = { workspace = true }
snowbridge-pezpallet-inbound-queue-v2 = { workspace = true }
snowbridge-pezpallet-outbound-queue = { workspace = true }
snowbridge-pezpallet-outbound-queue-v2 = { workspace = true }
snowbridge-pezpallet-system = { workspace = true }
snowbridge-pezpallet-system-v2 = { workspace = true }
[features]
runtime-benchmarks = [
"asset-hub-zagros-runtime/runtime-benchmarks",
"bp-asset-hub-zagros/runtime-benchmarks",
"bridge-hub-common/runtime-benchmarks",
"bridge-hub-zagros-runtime/runtime-benchmarks",
"pezcumulus-pezpallet-teyrchain-system/runtime-benchmarks",
"pezcumulus-pezpallet-xcmp-queue/runtime-benchmarks",
"emulated-integration-tests-common/runtime-benchmarks",
"pezframe-support/runtime-benchmarks",
"pezpallet-asset-conversion/runtime-benchmarks",
"pezpallet-assets/runtime-benchmarks",
"pezpallet-balances/runtime-benchmarks",
"pezpallet-bridge-messages/runtime-benchmarks",
"pezpallet-bridge-relayers/runtime-benchmarks",
"pezpallet-message-queue/runtime-benchmarks",
"pezpallet-xcm/runtime-benchmarks",
"pezkuwichain-zagros-system-emulated-network/runtime-benchmarks",
"snowbridge-core/runtime-benchmarks",
"snowbridge-inbound-queue-primitives/runtime-benchmarks",
"snowbridge-outbound-queue-primitives/runtime-benchmarks",
"snowbridge-pezpallet-inbound-queue-fixtures/runtime-benchmarks",
"snowbridge-pezpallet-inbound-queue-v2/runtime-benchmarks",
"snowbridge-pezpallet-inbound-queue/runtime-benchmarks",
"snowbridge-pezpallet-outbound-queue-v2/runtime-benchmarks",
"snowbridge-pezpallet-outbound-queue/runtime-benchmarks",
"snowbridge-pezpallet-system-v2/runtime-benchmarks",
"snowbridge-pezpallet-system/runtime-benchmarks",
"pezsp-io/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"testnet-teyrchains-constants/runtime-benchmarks",
"teyrchains-common/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
"xcm/runtime-benchmarks",
]
@@ -0,0 +1,96 @@
// 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.
#[cfg(test)]
mod imports {
// Bizinikiwi
pub(crate) use codec::Encode;
pub(crate) use pezframe_support::{
assert_err, assert_ok, pezpallet_prelude::DispatchResult, BoundedVec,
};
pub(crate) use pezsp_core::H160;
pub(crate) use pezsp_runtime::DispatchError;
// Pezkuwi
pub(crate) use xcm::{
latest::{ParentThen, PEZKUWICHAIN_GENESIS_HASH, ZAGROS_GENESIS_HASH},
prelude::{AccountId32 as AccountId32Junction, *},
v5,
};
pub(crate) use xcm_executor::traits::TransferType;
// Pezcumulus
pub(crate) use emulated_integration_tests_common::{
accounts::ALICE,
create_pool_with_native_on,
impls::Inspect,
test_dry_run_transfer_across_pk_bridge, test_relay_is_trusted_teleporter,
test_teyrchain_is_trusted_teleporter, test_teyrchain_is_trusted_teleporter_for_relay,
xcm_emulator::{
assert_expected_events, bx, Chain, RelayChain as Relay, TestExt, Teyrchain as Para,
},
xcm_helpers::xcm_transact_paid_execution,
ASSETS_PALLET_ID, USDT_ID,
};
pub(crate) use pezkuwichain_zagros_system_emulated_network::{
asset_hub_pezkuwichain_emulated_chain::{
asset_hub_pezkuwichain_runtime::{
xcm_config::TreasuryAccount, ForeignAssetReserveData,
},
genesis::ED as ASSET_HUB_PEZKUWICHAIN_ED,
AssetHubPezkuwichainParaPallet as AssetHubPezkuwichainPallet,
},
asset_hub_zagros_emulated_chain::{
genesis::{AssetHubZagrosAssetOwner, ED as ASSET_HUB_ZAGROS_ED},
AssetHubZagrosParaPallet as AssetHubZagrosPallet,
},
bridge_hub_zagros_emulated_chain::{
bridge_hub_zagros_runtime, genesis::ED as BRIDGE_HUB_ZAGROS_ED,
BridgeHubZagrosExistentialDeposit, BridgeHubZagrosParaPallet as BridgeHubZagrosPallet,
BridgeHubZagrosRuntimeOrigin,
},
penpal_emulated_chain::{
self,
penpal_runtime::xcm_config::{
CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub,
LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub,
UniversalLocation as PenpalUniversalLocation,
},
PenpalAParaPallet as PenpalAPallet, PenpalAssetOwner,
PenpalBParaPallet as PenpalBPallet,
},
pezkuwichain_emulated_chain::PezkuwichainRelayPallet as PezkuwichainPallet,
zagros_emulated_chain::{genesis::ED as ZAGROS_ED, ZagrosRelayPallet as ZagrosPallet},
AssetHubPezkuwichainPara as AssetHubPezkuwichain,
AssetHubPezkuwichainParaReceiver as AssetHubPezkuwichainReceiver,
AssetHubPezkuwichainParaSender as AssetHubPezkuwichainSender,
AssetHubZagrosPara as AssetHubZagros, AssetHubZagrosParaReceiver as AssetHubZagrosReceiver,
AssetHubZagrosParaSender as AssetHubZagrosSender,
BridgeHubPezkuwichainPara as BridgeHubPezkuwichain, BridgeHubZagrosPara as BridgeHubZagros,
BridgeHubZagrosParaReceiver as BridgeHubZagrosReceiver,
BridgeHubZagrosParaSender as BridgeHubZagrosSender, PenpalAPara as PenpalA,
PenpalAParaReceiver as PenpalAReceiver, PenpalBPara as PenpalB,
PenpalBParaReceiver as PenpalBReceiver, PenpalBParaSender as PenpalBSender,
PezkuwichainRelay as Pezkuwichain, PezkuwichainRelayReceiver as PezkuwichainReceiver,
ZagrosRelay as Zagros, ZagrosRelayReceiver as ZagrosReceiver,
ZagrosRelaySender as ZagrosSender,
};
pub(crate) use teyrchains_common::AccountId;
pub(crate) const ASSET_ID: u32 = 1;
pub(crate) const ASSET_MIN_BALANCE: u128 = 1000;
}
#[cfg(test)]
mod tests;
@@ -0,0 +1,254 @@
// 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.
//! Tests related to XCM aliasing.
use crate::imports::*;
use bridge_hub_zagros_runtime::xcm_config::XcmConfig;
use emulated_integration_tests_common::{macros::AccountId, test_cross_chain_alias};
use pezframe_support::traits::ContainsPair;
use xcm::latest::Junctions::*;
const ALLOWED: bool = true;
const DENIED: bool = false;
const TELEPORT_FEES: bool = true;
const RESERVE_TRANSFER_FEES: bool = false;
#[test]
fn account_on_sibling_syschain_aliases_into_same_local_account() {
// origin and target are the same account on different chains
let origin: AccountId = [1; 32].into();
let target = origin.clone();
let fees = ZAGROS_ED * 10;
PenpalB::mint_foreign_asset(
<PenpalB as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
Location::parent(),
origin.clone(),
fees * 10,
);
// On Bridge Hub we don't want to support aliasing from other chains: there is no real world
// demand for it, only low-level power users (like relayers) directly interact with Bridge
// Hub. They don't need aliasing to operate cross-chain they can operate locally.
// Aliasing same account doesn't work on BH.
test_cross_chain_alias!(
vec![
// between AH and BH: denied
(AssetHubZagros, BridgeHubZagros, TELEPORT_FEES, DENIED),
// between Penpal and BH: denied
(PenpalB, BridgeHubZagros, RESERVE_TRANSFER_FEES, DENIED)
],
origin,
target,
fees
);
}
#[test]
fn account_on_sibling_syschain_cannot_alias_into_different_local_account() {
// origin and target are different accounts on different chains
let origin: AccountId = [1; 32].into();
let target: AccountId = [2; 32].into();
let fees = ZAGROS_ED * 10;
PenpalB::mint_foreign_asset(
<PenpalB as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
Location::parent(),
origin.clone(),
fees * 10,
);
// Aliasing different account on different chains
test_cross_chain_alias!(
vec![
// between AH and BH: denied
(AssetHubZagros, BridgeHubZagros, TELEPORT_FEES, DENIED),
// between Penpal and BH: denied
(PenpalB, BridgeHubZagros, RESERVE_TRANSFER_FEES, DENIED)
],
origin,
target,
fees
);
}
#[test]
fn aliasing_child_locations() {
BridgeHubZagros::execute_with(|| {
// Bridge Hub allows aliasing descendant of origin.
let origin = Location::new(1, X1([PalletInstance(8)].into()));
let target = Location::new(1, X2([PalletInstance(8), GeneralIndex(9)].into()));
assert!(<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
let origin = Location::new(1, X1([Teyrchain(8)].into()));
let target = Location::new(
1,
X2([Teyrchain(8), AccountId32 { network: None, id: [1u8; 32] }].into()),
);
assert!(<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
let origin = Location::new(1, X1([Teyrchain(8)].into()));
let target =
Location::new(1, X3([Teyrchain(8), PalletInstance(8), GeneralIndex(9)].into()));
assert!(<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
// Does not allow if not descendant.
let origin = Location::new(1, X1([PalletInstance(8)].into()));
let target = Location::new(0, X2([PalletInstance(8), GeneralIndex(9)].into()));
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
let origin = Location::new(1, X1([Teyrchain(8)].into()));
let target = Location::new(
0,
X2([Teyrchain(8), AccountId32 { network: None, id: [1u8; 32] }].into()),
);
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
let origin = Location::new(1, X1([Teyrchain(8)].into()));
let target = Location::new(0, X1([AccountId32 { network: None, id: [1u8; 32] }].into()));
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
let origin = Location::new(1, X1([AccountId32 { network: None, id: [1u8; 32] }].into()));
let target = Location::new(0, X1([AccountId32 { network: None, id: [1u8; 32] }].into()));
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
});
}
#[test]
fn asset_hub_root_aliases_anything() {
BridgeHubZagros::execute_with(|| {
// Does not allow AH root to alias other locations.
let origin = Location::new(1, X1([Teyrchain(1000)].into()));
let target = Location::new(1, X1([Teyrchain(2000)].into()));
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
let target = Location::new(1, X1([AccountId32 { network: None, id: [1u8; 32] }].into()));
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
let target = Location::new(
1,
X2([Teyrchain(8), AccountId32 { network: None, id: [1u8; 32] }].into()),
);
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
let target =
Location::new(1, X3([Teyrchain(42), PalletInstance(8), GeneralIndex(9)].into()));
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
let target = Location::new(2, X1([GlobalConsensus(Ethereum { chain_id: 1 })].into()));
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
let target = Location::new(2, X2([GlobalConsensus(Pezkuwi), Teyrchain(1000)].into()));
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
let target = Location::new(0, X2([PalletInstance(8), GeneralIndex(9)].into()));
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
// Other AH locations cannot alias anything.
let origin = Location::new(1, X2([Teyrchain(1000), GeneralIndex(9)].into()));
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
let origin = Location::new(1, X2([Teyrchain(1000), PalletInstance(9)].into()));
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
let origin = Location::new(
1,
X2([Teyrchain(1000), AccountId32 { network: None, id: [1u8; 32] }].into()),
);
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
// Other root locations cannot alias anything.
let origin = Location::new(1, Here);
let target = Location::new(2, X1([GlobalConsensus(Ethereum { chain_id: 1 })].into()));
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
let target = Location::new(2, X2([GlobalConsensus(Pezkuwi), Teyrchain(1000)].into()));
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
let target = Location::new(0, X2([PalletInstance(8), GeneralIndex(9)].into()));
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
let origin = Location::new(0, Here);
let target = Location::new(1, X1([Teyrchain(2000)].into()));
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
let origin = Location::new(1, X1([Teyrchain(1001)].into()));
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
let origin = Location::new(1, X1([Teyrchain(1002)].into()));
assert!(!<XcmConfig as xcm_executor::Config>::Aliasers::contains(&origin, &target));
});
}
#[test]
fn authorized_cross_chain_aliases() {
// origin and target are different accounts on different chains
let origin: AccountId = [100; 32].into();
let bad_origin: AccountId = [150; 32].into();
let target: AccountId = [200; 32].into();
let fees = ZAGROS_ED * 10;
let pal_admin = <PenpalB as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get());
PenpalB::mint_foreign_asset(pal_admin.clone(), Location::parent(), origin.clone(), fees * 10);
PenpalB::mint_foreign_asset(pal_admin, Location::parent(), bad_origin.clone(), fees * 10);
BridgeHubZagros::fund_accounts(vec![(target.clone(), fees * 10)]);
// let's authorize `origin` on Penpal to alias `target` on BridgeHub
BridgeHubZagros::execute_with(|| {
let penpal_origin = Location::new(
1,
X2([
Teyrchain(PenpalB::para_id().into()),
AccountId32 {
network: Some(ByGenesis(ZAGROS_GENESIS_HASH)),
id: origin.clone().into(),
},
]
.into()),
);
// `target` adds `penpal_origin` as authorized alias
assert_ok!(<BridgeHubZagros as BridgeHubZagrosPallet>::PezkuwiXcm::add_authorized_alias(
<BridgeHubZagros as Chain>::RuntimeOrigin::signed(target.clone()),
Box::new(penpal_origin.into()),
None
));
});
// Verify that unauthorized `bad_origin` cannot alias into `target`, from any chain.
test_cross_chain_alias!(
vec![
// between AH and BridgeHub: denied
(AssetHubZagros, BridgeHubZagros, TELEPORT_FEES, DENIED),
// between Penpal and BridgeHub: denied
(PenpalB, BridgeHubZagros, RESERVE_TRANSFER_FEES, DENIED)
],
bad_origin,
target,
fees
);
// Verify that only authorized `penpal::origin` can alias into `target`, while `origin` on other
// chains cannot.
test_cross_chain_alias!(
vec![
// between AH and BridgeHub: denied
(AssetHubZagros, BridgeHubZagros, TELEPORT_FEES, DENIED),
// between Penpal and BridgeHub: allowed
(PenpalB, BridgeHubZagros, RESERVE_TRANSFER_FEES, ALLOWED)
],
origin,
target,
fees
);
// remove authorization for `origin` on Penpal to alias `target` on BridgeHub
BridgeHubZagros::execute_with(|| {
// `target` removes all authorized aliases
assert_ok!(
<BridgeHubZagros as BridgeHubZagrosPallet>::PezkuwiXcm::remove_all_authorized_aliases(
<BridgeHubZagros as Chain>::RuntimeOrigin::signed(target.clone())
)
);
});
// Verify `penpal::origin` can no longer alias into `target` on BridgeHub.
test_cross_chain_alias!(
vec![(PenpalB, BridgeHubZagros, RESERVE_TRANSFER_FEES, DENIED)],
origin,
target,
fees
);
}
@@ -0,0 +1,34 @@
// 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.
//! Tests related to claiming assets trapped during XCM execution.
use crate::imports::*;
use emulated_integration_tests_common::test_chain_can_claim_assets;
#[test]
fn assets_can_be_claimed() {
let amount = BridgeHubZagrosExistentialDeposit::get();
let assets: Assets = (Parent, amount).into();
test_chain_can_claim_assets!(
AssetHubZagros,
RuntimeCall,
NetworkId::ByGenesis(ZAGROS_GENESIS_HASH),
assets,
amount
);
}
@@ -0,0 +1,241 @@
// 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::imports::*;
use emulated_integration_tests_common::snowbridge::{SEPOLIA_ID, WETH};
mod aliases;
mod asset_transfers;
mod claim_assets;
mod register_bridged_assets;
mod send_xcm;
mod snowbridge;
mod snowbridge_common;
// mod snowbridge_v2_inbound;
mod snowbridge_edge_case;
mod snowbridge_v2_inbound;
mod snowbridge_v2_inbound_to_pezkuwichain;
mod snowbridge_v2_outbound;
mod snowbridge_v2_outbound_edge_case;
mod snowbridge_v2_outbound_from_pezkuwichain;
mod snowbridge_v2_rewards;
mod teleport;
mod transact;
pub(crate) fn asset_hub_pezkuwichain_location() -> Location {
Location::new(
2,
[
GlobalConsensus(ByGenesis(PEZKUWICHAIN_GENESIS_HASH)),
Teyrchain(AssetHubPezkuwichain::para_id().into()),
],
)
}
pub(crate) fn asset_hub_zagros_global_location() -> Location {
Location::new(
2,
[
GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)),
Teyrchain(AssetHubZagros::para_id().into()),
],
)
}
pub(crate) fn bridge_hub_pezkuwichain_location() -> Location {
Location::new(
2,
[
GlobalConsensus(ByGenesis(PEZKUWICHAIN_GENESIS_HASH)),
Teyrchain(BridgeHubPezkuwichain::para_id().into()),
],
)
}
// ZGR and wWND
pub(crate) fn wnd_at_ah_zagros() -> Location {
Parent.into()
}
pub(crate) fn bridged_wnd_at_ah_pezkuwichain() -> Location {
Location::new(2, [GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH))])
}
// TYR and wTYR
pub(crate) fn bridged_roc_at_ah_zagros() -> Location {
Location::new(2, [GlobalConsensus(ByGenesis(PEZKUWICHAIN_GENESIS_HASH))])
}
// USDT and wUSDT
pub(crate) fn usdt_at_ah_zagros() -> Location {
Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())])
}
pub(crate) fn bridged_usdt_at_ah_pezkuwichain() -> Location {
Location::new(
2,
[
GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)),
Teyrchain(AssetHubZagros::para_id().into()),
PalletInstance(ASSETS_PALLET_ID),
GeneralIndex(USDT_ID.into()),
],
)
}
// wETH has same relative location on both Pezkuwichain and Zagros AssetHubs
pub(crate) fn weth_at_asset_hubs() -> Location {
Location::new(
2,
[
GlobalConsensus(Ethereum { chain_id: SEPOLIA_ID }),
AccountKey20 { network: None, key: WETH },
],
)
}
pub(crate) fn create_foreign_on_ah_pezkuwichain(
id: v5::Location,
sufficient: bool,
reserves: Vec<ForeignAssetReserveData>,
) {
let owner = AssetHubPezkuwichain::account_id_of(ALICE);
AssetHubPezkuwichain::force_create_foreign_asset(
id.clone(),
owner.clone(),
sufficient,
ASSET_MIN_BALANCE,
vec![],
);
AssetHubPezkuwichain::set_foreign_asset_reserves(id, owner, reserves);
}
pub(crate) fn create_foreign_on_ah_zagros(
id: v5::Location,
sufficient: bool,
reserves: Vec<ForeignAssetReserveData>,
prefund_accounts: Vec<(AccountId, u128)>,
) {
let owner = AssetHubZagros::account_id_of(ALICE);
let min = ASSET_MIN_BALANCE;
AssetHubZagros::force_create_foreign_asset(
id.clone(),
owner.clone(),
sufficient,
min,
prefund_accounts,
);
AssetHubZagros::set_foreign_asset_reserves(id, owner, reserves);
}
pub(crate) fn foreign_balance_on_ah_pezkuwichain(id: v5::Location, who: &AccountId) -> u128 {
AssetHubPezkuwichain::execute_with(|| {
type Assets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(id, who)
})
}
pub(crate) fn foreign_balance_on_ah_zagros(id: v5::Location, who: &AccountId) -> u128 {
AssetHubZagros::execute_with(|| {
type Assets = <AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(id, who)
})
}
pub(crate) fn send_assets_from_asset_hub_zagros(
destination: Location,
assets: Assets,
fee_idx: u32,
// For knowing what reserve to pick.
// We only allow using the same transfer type for assets and fees right now.
// And only `LocalReserve` or `DestinationReserve`.
transfer_type: TransferType,
) -> DispatchResult {
let signed_origin =
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosSender::get().into());
let beneficiary: Location =
AccountId32Junction { network: None, id: AssetHubPezkuwichainReceiver::get().into() }
.into();
type Runtime = <AssetHubZagros as Chain>::Runtime;
let remote_fee_id: AssetId = assets
.clone()
.into_inner()
.get(fee_idx as usize)
.ok_or(pezpallet_xcm::Error::<Runtime>::Empty)?
.clone()
.id;
AssetHubZagros::execute_with(|| {
<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::transfer_assets_using_type_and_then(
signed_origin,
bx!(destination.into()),
bx!(assets.into()),
bx!(transfer_type.clone()),
bx!(remote_fee_id.into()),
bx!(transfer_type),
bx!(VersionedXcm::from(
Xcm::<()>::builder_unsafe().deposit_asset(AllCounted(1), beneficiary).build()
)),
WeightLimit::Unlimited,
)
})
}
pub(crate) fn assert_bridge_hub_zagros_message_accepted(expected_processed: bool) {
BridgeHubZagros::execute_with(|| {
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
if expected_processed {
assert_expected_events!(
BridgeHubZagros,
vec![
// pay for bridge fees
RuntimeEvent::Balances(pezpallet_balances::Event::Burned { .. }) => {},
// message exported
RuntimeEvent::BridgePezkuwichainMessages(
pezpallet_bridge_messages::Event::MessageAccepted { .. }
) => {},
// message processed successfully
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
} else {
assert_expected_events!(
BridgeHubZagros,
vec![
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed {
success: false,
..
}) => {},
]
);
}
})
}
pub(crate) fn assert_bridge_hub_pezkuwichain_message_received() {
BridgeHubPezkuwichain::execute_with(|| {
type RuntimeEvent = <BridgeHubPezkuwichain as Chain>::RuntimeEvent;
assert_expected_events!(
BridgeHubPezkuwichain,
vec![
// message sent to destination
RuntimeEvent::XcmpQueue(
cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }
) => {},
]
);
})
}
@@ -0,0 +1,127 @@
// 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::{imports::*, tests::*};
const XCM_FEE: u128 = 40_000_000_000;
/// Tests the registering of a Zagros Asset as a bridged asset on Pezkuwichain Asset Hub.
#[test]
fn register_zagros_asset_on_rah_from_wah() {
// Zagros Asset Hub asset when bridged to Pezkuwichain Asset Hub.
let bridged_asset_at_rah = Location::new(
2,
[
GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)),
Teyrchain(AssetHubZagros::para_id().into()),
PalletInstance(ASSETS_PALLET_ID),
GeneralIndex(ASSET_ID.into()),
],
);
// Register above asset on Pezkuwichain AH from Zagros AH.
register_asset_on_rah_from_wah(bridged_asset_at_rah);
}
/// Tests the registering of an Ethereum Asset as a bridged asset on Pezkuwichain Asset Hub.
#[test]
fn register_ethereum_asset_on_rah_from_wah() {
// Ethereum asset when bridged to Pezkuwichain Asset Hub.
let token_id = H160::random();
let bridged_asset_at_rah = Location::new(
2,
[
GlobalConsensus(Ethereum { chain_id: SEPOLIA_ID }),
AccountKey20 { network: None, key: token_id.into() },
],
);
// Register above asset on Pezkuwichain AH from Zagros AH.
register_asset_on_rah_from_wah(bridged_asset_at_rah);
}
fn register_asset_on_rah_from_wah(bridged_asset_at_rah: Location) {
let sa_of_wah_on_rah =
AssetHubPezkuwichain::sovereign_account_of_teyrchain_on_other_global_consensus(
ByGenesis(ZAGROS_GENESIS_HASH),
AssetHubZagros::para_id(),
);
// Encoded `create_asset` call to be executed in Pezkuwichain Asset Hub ForeignAssets pallet.
let call = AssetHubPezkuwichain::create_foreign_asset_call(
bridged_asset_at_rah.clone(),
ASSET_MIN_BALANCE,
sa_of_wah_on_rah.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_wah_on_rah.clone());
// SA-of-WAH-on-RAH needs to have balance to pay for fees and asset creation deposit
AssetHubPezkuwichain::fund_accounts(vec![(
sa_of_wah_on_rah.clone(),
ASSET_HUB_PEZKUWICHAIN_ED * 10000000000,
)]);
let destination = asset_hub_pezkuwichain_location();
// fund the WAH's SA on WBH for paying bridge delivery fees
BridgeHubZagros::fund_para_sovereign(AssetHubZagros::para_id(), 10_000_000_000_000u128);
// set XCM versions
AssetHubZagros::force_xcm_version(destination.clone(), XCM_VERSION);
BridgeHubZagros::force_xcm_version(bridge_hub_pezkuwichain_location(), XCM_VERSION);
let root_origin = <AssetHubZagros as Chain>::RuntimeOrigin::root();
AssetHubZagros::execute_with(|| {
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::send(
root_origin,
bx!(destination.into()),
bx!(xcm),
));
AssetHubZagros::assert_xcm_pallet_sent();
});
assert_bridge_hub_zagros_message_accepted(true);
assert_bridge_hub_pezkuwichain_message_received();
AssetHubPezkuwichain::execute_with(|| {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
AssetHubPezkuwichain::assert_xcmp_queue_success(None);
assert_expected_events!(
AssetHubPezkuwichain,
vec![
// Burned the fee
RuntimeEvent::Balances(pezpallet_balances::Event::Burned { who, amount }) => {
who: *who == sa_of_wah_on_rah.clone(),
amount: *amount == fee_amount,
},
// Foreign Asset created
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Created { asset_id, creator, owner }) => {
asset_id: asset_id == &bridged_asset_at_rah,
creator: *creator == sa_of_wah_on_rah.clone(),
owner: *owner == sa_of_wah_on_rah,
},
// Unspent fee minted to origin
RuntimeEvent::Balances(pezpallet_balances::Event::Minted { who, .. }) => {
who: *who == sa_of_wah_on_rah.clone(),
},
]
);
type ForeignAssets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets;
assert!(ForeignAssets::asset_exists(bridged_asset_at_rah));
});
}
@@ -0,0 +1,200 @@
// 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 emulated_integration_tests_common::xcm_helpers::{
find_mq_processed_id, find_xcm_sent_message_id,
};
use pezkuwichain_zagros_system_emulated_network::zagros_emulated_chain::zagros_runtime::Dmp;
use std::collections::HashMap;
use crate::tests::*;
#[test]
fn send_xcm_from_zagros_relay_to_pezkuwichain_asset_hub_should_fail_on_not_applicable() {
// Init tests variables
// XcmPallet send arguments
let sudo_origin = <Zagros as Chain>::RuntimeOrigin::root();
let destination = Zagros::child_location_of(BridgeHubZagros::para_id()).into();
let weight_limit = WeightLimit::Unlimited;
let check_origin = None;
let remote_xcm = Xcm(vec![ClearOrigin]);
let xcm = VersionedXcm::from(Xcm(vec![
UnpaidExecution { weight_limit, check_origin },
ExportMessage {
network: ByGenesis(PEZKUWICHAIN_GENESIS_HASH),
destination: [Teyrchain(AssetHubPezkuwichain::para_id().into())].into(),
xcm: remote_xcm,
},
]));
// Zagros Global Consensus
// Send XCM message from Relay Chain to Bridge Hub source Teyrchain
Zagros::execute_with(|| {
Dmp::make_teyrchain_reachable(BridgeHubZagros::para_id());
assert_ok!(<Zagros as ZagrosPallet>::XcmPallet::send(
sudo_origin,
bx!(destination),
bx!(xcm),
));
type RuntimeEvent = <Zagros as Chain>::RuntimeEvent;
assert_expected_events!(
Zagros,
vec![
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::Sent { .. }) => {},
]
);
});
// Receive XCM message in Bridge Hub source Teyrchain, it should fail, because we don't have
// opened bridge/lane.
assert_bridge_hub_zagros_message_accepted(false);
}
#[test]
fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() {
// prepare data
let destination = asset_hub_pezkuwichain_location();
let native_token = Location::parent();
let amount = ASSET_HUB_ZAGROS_ED * 1_000;
// fund the AHR's SA on BHR for paying bridge delivery fees
BridgeHubZagros::fund_para_sovereign(AssetHubZagros::para_id(), 10_000_000_000_000u128);
// fund sender
AssetHubZagros::fund_accounts(vec![(AssetHubZagrosSender::get().into(), amount * 10)]);
// Initially set only default version on all runtimes
let newer_xcm_version = xcm::prelude::XCM_VERSION;
let older_xcm_version = newer_xcm_version - 1;
AssetHubPezkuwichain::force_default_xcm_version(Some(older_xcm_version));
BridgeHubPezkuwichain::force_default_xcm_version(Some(older_xcm_version));
BridgeHubZagros::force_default_xcm_version(Some(older_xcm_version));
AssetHubZagros::force_default_xcm_version(Some(older_xcm_version));
// send XCM from AssetHubZagros - fails - destination version not known
assert_err!(
send_assets_from_asset_hub_zagros(
destination.clone(),
(native_token.clone(), amount).into(),
0,
TransferType::LocalReserve
),
DispatchError::Module(pezsp_runtime::ModuleError {
index: 31,
error: [1, 0, 0, 0],
message: Some("SendFailure")
})
);
// set destination version
AssetHubZagros::force_xcm_version(destination.clone(), newer_xcm_version);
// set version with `ExportMessage` for BridgeHubZagros
AssetHubZagros::force_xcm_version(
ParentThen(Teyrchain(BridgeHubZagros::para_id().into()).into()).into(),
newer_xcm_version,
);
// send XCM from AssetHubZagros - ok
assert_ok!(send_assets_from_asset_hub_zagros(
destination.clone(),
(native_token.clone(), amount).into(),
0,
TransferType::LocalReserve
));
// `ExportMessage` on local BridgeHub - fails - remote BridgeHub version not known
assert_bridge_hub_zagros_message_accepted(false);
// set version for remote BridgeHub on BridgeHubZagros
BridgeHubZagros::force_xcm_version(bridge_hub_pezkuwichain_location(), newer_xcm_version);
// set version for AssetHubPezkuwichain on BridgeHubPezkuwichain
BridgeHubPezkuwichain::force_xcm_version(
ParentThen(Teyrchain(AssetHubPezkuwichain::para_id().into()).into()).into(),
newer_xcm_version,
);
// send XCM from AssetHubZagros - ok
assert_ok!(send_assets_from_asset_hub_zagros(
destination.clone(),
(native_token.clone(), amount).into(),
0,
TransferType::LocalReserve
));
assert_bridge_hub_zagros_message_accepted(true);
assert_bridge_hub_pezkuwichain_message_received();
// message delivered and processed at destination
AssetHubPezkuwichain::execute_with(|| {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubPezkuwichain,
vec![
// message processed with failure, but for this scenario it is ok, important is that was delivered
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: false, .. }
) => {},
]
);
});
}
#[test]
fn xcm_persists_set_topic_across_hops() {
for test_topic_id in [Some([42; 32]), None] {
// Reset tracked topic state before each run
let mut tracked_topic_ids = HashMap::new();
// Prepare test input
let sudo_origin = <Zagros as Chain>::RuntimeOrigin::root();
let destination = Zagros::child_location_of(BridgeHubZagros::para_id()).into();
let weight_limit = Unlimited;
let check_origin = None;
// Construct XCM with optional SetTopic
let mut message = vec![UnpaidExecution { weight_limit, check_origin }, ClearOrigin];
if let Some(topic_id) = test_topic_id {
message.push(SetTopic(topic_id));
}
let xcm = VersionedXcm::from(Xcm(message));
// Send XCM from Zagros to BridgeHubZagros
Zagros::execute_with(|| {
Dmp::make_teyrchain_reachable(BridgeHubZagros::para_id());
assert_ok!(<Zagros as ZagrosPallet>::XcmPallet::send(
sudo_origin.clone(),
bx!(destination),
bx!(xcm),
));
let msg_sent_id = find_xcm_sent_message_id::<Zagros>().expect("Missing Sent Event");
tracked_topic_ids.insert("Zagros", msg_sent_id.into());
});
BridgeHubZagros::execute_with(|| {
let mq_prc_id =
find_mq_processed_id::<BridgeHubZagros>().expect("Missing Processed Event");
tracked_topic_ids.insert("BridgeHubZagros", mq_prc_id);
});
// Assert exactly one consistent topic ID across all hops
let topic_id = tracked_topic_ids.get("Zagros");
assert_eq!(tracked_topic_ids.get("BridgeHubZagros"), topic_id);
if let Some(expected) = test_topic_id {
assert_eq!(topic_id, Some(&expected.into()));
}
}
}
@@ -0,0 +1,544 @@
// 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::{imports::*, tests::bridged_roc_at_ah_zagros};
use asset_hub_zagros_runtime::xcm_config::LocationToAccountId;
use emulated_integration_tests_common::{
snowbridge::{SEPOLIA_ID, WETH},
PenpalBTeleportableAssetLocation,
};
use pezframe_support::traits::fungibles::Mutate;
use hex_literal::hex;
use pezkuwichain_zagros_system_emulated_network::penpal_emulated_chain::{
penpal_runtime::xcm_config::{CheckingAccount, TELEPORTABLE_ASSET_ID},
PenpalAssetOwner,
};
use snowbridge_core::AssetMetadata;
use pezsp_core::H160;
use testnet_teyrchains_constants::zagros::snowbridge::EthereumNetwork;
use xcm_builder::ExternalConsensusLocationsConverterFor;
use xcm_executor::traits::ConvertLocation;
pub const INITIAL_FUND: u128 = 50_000_000_000_000;
pub const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e");
pub const AGENT_ADDRESS: [u8; 20] = hex!("90A987B944Cb1dCcE5564e5FDeCD7a54D3de27Fe");
pub const TOKEN_AMOUNT: u128 = 10_000_000_000_000;
pub const REMOTE_FEE_AMOUNT_IN_ETHER: u128 = 600_000_000_000;
pub const LOCAL_FEE_AMOUNT_IN_DOT: u128 = 800_000_000_000;
pub const EXECUTION_WEIGHT: u64 = 8_000_000_000;
pub fn beneficiary() -> Location {
Location::new(0, [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }])
}
pub fn asset_hub() -> Location {
Location::new(1, Teyrchain(AssetHubZagros::para_id().into()))
}
pub fn bridge_hub() -> Location {
Location::new(1, Teyrchain(BridgeHubZagros::para_id().into()))
}
pub fn fund_on_bh() {
let assethub_sovereign = BridgeHubZagros::sovereign_account_id_of(asset_hub());
BridgeHubZagros::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]);
}
pub fn register_assets_on_ah() {}
pub fn register_relay_token_on_bh() {
BridgeHubZagros::execute_with(|| {
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
type RuntimeOrigin = <BridgeHubZagros as Chain>::RuntimeOrigin;
// Register ZGR on BH
assert_ok!(<BridgeHubZagros as BridgeHubZagrosPallet>::EthereumSystem::register_token(
RuntimeOrigin::root(),
Box::new(VersionedLocation::from(Location::parent())),
AssetMetadata {
name: "wnd".as_bytes().to_vec().try_into().unwrap(),
symbol: "wnd".as_bytes().to_vec().try_into().unwrap(),
decimals: 12,
},
));
assert_expected_events!(
BridgeHubZagros,
vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::RegisterToken { .. }) => {},]
);
});
}
pub fn register_assets_on_penpal() {
let ethereum_sovereign: AccountId = snowbridge_sovereign();
PenpalB::execute_with(|| {
assert_ok!(<PenpalB as PenpalBPallet>::ForeignAssets::force_create(
<PenpalB as Chain>::RuntimeOrigin::root(),
weth_location().try_into().unwrap(),
ethereum_sovereign.clone().into(),
true,
1,
));
assert_ok!(<PenpalB as PenpalBPallet>::ForeignAssets::force_create(
<PenpalB as Chain>::RuntimeOrigin::root(),
ethereum().try_into().unwrap(),
ethereum_sovereign.into(),
true,
1,
));
});
}
pub fn register_foreign_asset(token_location: Location) {
let bridge_owner = snowbridge_sovereign();
AssetHubZagros::execute_with(|| {
type RuntimeOrigin = <AssetHubZagros as Chain>::RuntimeOrigin;
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::force_create(
RuntimeOrigin::root(),
token_location.clone().try_into().unwrap(),
bridge_owner.clone().into(),
true,
1000,
));
assert!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::asset_exists(
token_location.clone().try_into().unwrap(),
));
});
AssetHubZagros::set_foreign_asset_reserves(
token_location,
bridge_owner,
vec![(ethereum(), false).into()],
);
}
pub fn register_pal_on_ah() {
// Create PAL(i.e. native asset for penpal) on AH.
AssetHubZagros::execute_with(|| {
type RuntimeOrigin = <AssetHubZagros as Chain>::RuntimeOrigin;
let penpal_asset_id = Location::new(1, Teyrchain(PenpalB::para_id().into()));
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::force_create(
RuntimeOrigin::root(),
penpal_asset_id.clone(),
PenpalAssetOwner::get().into(),
false,
1_000_000,
));
assert!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::asset_exists(
penpal_asset_id.clone(),
));
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::mint_into(
penpal_asset_id.clone(),
&AssetHubZagrosReceiver::get(),
TOKEN_AMOUNT,
));
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::mint_into(
penpal_asset_id.clone(),
&AssetHubZagrosSender::get(),
TOKEN_AMOUNT,
));
});
}
pub fn penpal_root_sovereign() -> pezsp_runtime::AccountId32 {
let penpal_root_sovereign: AccountId = PenpalB::execute_with(|| {
use pezkuwichain_zagros_system_emulated_network::penpal_emulated_chain::penpal_runtime::xcm_config;
xcm_config::LocationToAccountId::convert_location(&xcm_config::RootLocation::get())
.unwrap()
.into()
});
penpal_root_sovereign
}
pub fn fund_on_penpal() {
let sudo_account = penpal_root_sovereign();
PenpalB::fund_accounts(vec![
(PenpalBReceiver::get(), INITIAL_FUND),
(PenpalBSender::get(), INITIAL_FUND),
(CheckingAccount::get(), INITIAL_FUND),
(sudo_account.clone(), INITIAL_FUND),
]);
PenpalB::execute_with(|| {
assert_ok!(<PenpalB as PenpalBPallet>::ForeignAssets::mint_into(
Location::parent(),
&PenpalBReceiver::get(),
INITIAL_FUND,
));
assert_ok!(<PenpalB as PenpalBPallet>::ForeignAssets::mint_into(
Location::parent(),
&PenpalBSender::get(),
INITIAL_FUND,
));
assert_ok!(<PenpalB as PenpalBPallet>::ForeignAssets::mint_into(
Location::parent(),
&sudo_account,
INITIAL_FUND,
));
});
PenpalB::execute_with(|| {
assert_ok!(<PenpalB as PenpalBPallet>::Assets::mint_into(
TELEPORTABLE_ASSET_ID,
&PenpalBReceiver::get(),
INITIAL_FUND,
));
assert_ok!(<PenpalB as PenpalBPallet>::Assets::mint_into(
TELEPORTABLE_ASSET_ID,
&PenpalBSender::get(),
INITIAL_FUND,
));
assert_ok!(<PenpalB as PenpalBPallet>::Assets::mint_into(
TELEPORTABLE_ASSET_ID,
&sudo_account,
INITIAL_FUND,
));
});
PenpalB::execute_with(|| {
assert_ok!(<PenpalB as PenpalBPallet>::ForeignAssets::mint_into(
weth_location().try_into().unwrap(),
&PenpalBReceiver::get(),
INITIAL_FUND,
));
assert_ok!(<PenpalB as PenpalBPallet>::ForeignAssets::mint_into(
weth_location().try_into().unwrap(),
&PenpalBSender::get(),
INITIAL_FUND,
));
assert_ok!(<PenpalB as PenpalBPallet>::ForeignAssets::mint_into(
weth_location().try_into().unwrap(),
&sudo_account,
INITIAL_FUND,
));
assert_ok!(<PenpalB as PenpalBPallet>::ForeignAssets::mint_into(
ethereum().try_into().unwrap(),
&PenpalBReceiver::get(),
INITIAL_FUND,
));
assert_ok!(<PenpalB as PenpalBPallet>::ForeignAssets::mint_into(
ethereum().try_into().unwrap(),
&PenpalBSender::get(),
INITIAL_FUND,
));
assert_ok!(<PenpalB as PenpalBPallet>::ForeignAssets::mint_into(
ethereum().try_into().unwrap(),
&sudo_account,
INITIAL_FUND,
));
});
}
pub fn set_trust_reserve_on_penpal() {
PenpalB::execute_with(|| {
assert_ok!(<PenpalB as Chain>::System::set_storage(
<PenpalB as Chain>::RuntimeOrigin::root(),
vec![(
PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(),
Location::new(2, [GlobalConsensus(Ethereum { chain_id: SEPOLIA_ID })]).encode(),
)],
));
});
}
pub fn fund_on_ah() {
AssetHubZagros::fund_accounts(vec![(AssetHubZagrosSender::get(), INITIAL_FUND)]);
AssetHubZagros::fund_accounts(vec![(AssetHubZagrosReceiver::get(), INITIAL_FUND)]);
let penpal_sovereign = AssetHubZagros::sovereign_account_id_of(
AssetHubZagros::sibling_location_of(PenpalB::para_id()),
);
let penpal_user_sovereign = LocationToAccountId::convert_location(&Location::new(
1,
[
Teyrchain(PenpalB::para_id().into()),
AccountId32 {
network: Some(ByGenesis(ZAGROS_GENESIS_HASH)),
id: PenpalBSender::get().into(),
},
],
))
.unwrap();
AssetHubZagros::execute_with(|| {
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::mint_into(
weth_location().try_into().unwrap(),
&penpal_sovereign,
INITIAL_FUND,
));
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::mint_into(
weth_location().try_into().unwrap(),
&penpal_user_sovereign,
INITIAL_FUND,
));
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::mint_into(
weth_location().try_into().unwrap(),
&AssetHubZagrosReceiver::get(),
INITIAL_FUND,
));
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::mint_into(
weth_location().try_into().unwrap(),
&AssetHubZagrosSender::get(),
INITIAL_FUND,
));
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::mint_into(
ethereum().try_into().unwrap(),
&penpal_sovereign,
INITIAL_FUND,
));
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::mint_into(
ethereum().try_into().unwrap(),
&penpal_user_sovereign,
INITIAL_FUND,
));
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::mint_into(
ethereum().try_into().unwrap(),
&AssetHubZagrosReceiver::get(),
INITIAL_FUND,
));
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::mint_into(
ethereum().try_into().unwrap(),
&AssetHubZagrosSender::get(),
INITIAL_FUND,
));
});
AssetHubZagros::fund_accounts(vec![(snowbridge_sovereign(), INITIAL_FUND)]);
AssetHubZagros::fund_accounts(vec![(penpal_sovereign.clone(), INITIAL_FUND)]);
AssetHubZagros::fund_accounts(vec![(penpal_user_sovereign.clone(), INITIAL_FUND)]);
}
pub fn create_pools_on_ah() {
// We create a pool between ZGR and WETH in AssetHub to support paying for fees with WETH.
let ethereum_sovereign = snowbridge_sovereign();
AssetHubZagros::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]);
PenpalB::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]);
create_pool_with_native_on!(
AssetHubZagros,
weth_location(),
true,
ethereum_sovereign.clone(),
1_000_000_000_000,
20_000_000_000
);
create_pool_with_native_on!(
AssetHubZagros,
ethereum(),
true,
ethereum_sovereign.clone(),
1_000_000_000_000,
20_000_000_000
);
}
pub(crate) fn set_up_eth_and_hez_pool() {
// We create a pool between ZGR and WETH in AssetHub to support paying for fees with WETH.
let ethereum_sovereign = snowbridge_sovereign();
AssetHubZagros::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]);
PenpalB::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]);
create_pool_with_native_on!(AssetHubZagros, eth_location(), true, ethereum_sovereign.clone());
}
pub(crate) fn set_up_eth_and_hez_pool_on_penpal() {
let ethereum_sovereign = snowbridge_sovereign();
AssetHubZagros::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]);
PenpalB::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]);
create_pool_with_native_on!(PenpalB, eth_location(), true, ethereum_sovereign.clone());
}
pub(crate) fn set_up_eth_and_hez_pool_on_pezkuwichain() {
let sa_of_wah_on_rah =
AssetHubPezkuwichain::sovereign_account_of_teyrchain_on_other_global_consensus(
ByGenesis(ZAGROS_GENESIS_HASH),
AssetHubZagros::para_id(),
);
AssetHubPezkuwichain::fund_accounts(vec![(sa_of_wah_on_rah.clone(), INITIAL_FUND)]);
create_pool_with_native_on!(
AssetHubPezkuwichain,
eth_location(),
true,
sa_of_wah_on_rah.clone()
);
}
pub fn register_pal_on_bh() {
BridgeHubZagros::execute_with(|| {
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
type RuntimeOrigin = <BridgeHubZagros as Chain>::RuntimeOrigin;
assert_ok!(<BridgeHubZagros as BridgeHubZagrosPallet>::EthereumSystem::register_token(
RuntimeOrigin::root(),
Box::new(VersionedLocation::from(PenpalBTeleportableAssetLocation::get())),
AssetMetadata {
name: "pal".as_bytes().to_vec().try_into().unwrap(),
symbol: "pal".as_bytes().to_vec().try_into().unwrap(),
decimals: 12,
},
));
assert_expected_events!(
BridgeHubZagros,
vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::RegisterToken { .. }) => {},]
);
});
}
pub fn snowbridge_sovereign() -> pezsp_runtime::AccountId32 {
use asset_hub_zagros_runtime::xcm_config::UniversalLocation as AssetHubZagrosUniversalLocation;
let ethereum_sovereign: AccountId = AssetHubZagros::execute_with(|| {
ExternalConsensusLocationsConverterFor::<
AssetHubZagrosUniversalLocation,
[u8; 32],
>::convert_location(&Location::new(
2,
[xcm::v5::Junction::GlobalConsensus(EthereumNetwork::get())],
))
.unwrap()
.into()
});
ethereum_sovereign
}
pub fn weth_location() -> Location {
erc20_token_location(WETH.into())
}
pub fn eth_location() -> Location {
Location::new(2, [GlobalConsensus(Ethereum { chain_id: SEPOLIA_ID })])
}
pub fn ethereum() -> Location {
eth_location()
}
pub fn erc20_token_location(token_id: H160) -> Location {
Location::new(
2,
[
GlobalConsensus(EthereumNetwork::get().into()),
AccountKey20 { network: None, key: token_id.into() },
],
)
}
// TYR and wTYR
pub(crate) fn roc_at_ah_pezkuwichain() -> Location {
Parent.into()
}
// set up pool
pub(crate) fn set_up_pool_with_wnd_on_ah_zagros(
asset: Location,
is_foreign: bool,
initial_fund: u128,
initial_liquidity: u128,
) {
let wnd: Location = Parent.into();
AssetHubZagros::fund_accounts(vec![(AssetHubZagrosSender::get(), initial_fund)]);
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
let owner = AssetHubZagrosSender::get();
let signed_owner = <AssetHubZagros as Chain>::RuntimeOrigin::signed(owner.clone());
if is_foreign {
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::mint(
signed_owner.clone(),
asset.clone().into(),
owner.clone().into(),
initial_fund,
));
} else {
let asset_id = match asset.interior.last() {
Some(GeneralIndex(id)) => *id as u32,
_ => unreachable!(),
};
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::Assets::mint(
signed_owner.clone(),
asset_id.into(),
owner.clone().into(),
initial_fund,
));
}
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::AssetConversion::create_pool(
signed_owner.clone(),
Box::new(wnd.clone()),
Box::new(asset.clone()),
));
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::PoolCreated { .. }) => {},
]
);
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::AssetConversion::add_liquidity(
signed_owner.clone(),
Box::new(wnd),
Box::new(asset),
initial_liquidity,
initial_liquidity,
1,
1,
owner.into()
));
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::LiquidityAdded {..}) => {},
]
);
});
}
pub fn register_roc_on_bh() {
BridgeHubZagros::execute_with(|| {
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
type RuntimeOrigin = <BridgeHubZagros as Chain>::RuntimeOrigin;
// Register TYR on BH
assert_ok!(<BridgeHubZagros as BridgeHubZagrosPallet>::EthereumSystem::register_token(
RuntimeOrigin::root(),
Box::new(VersionedLocation::from(bridged_roc_at_ah_zagros())),
AssetMetadata {
name: "roc".as_bytes().to_vec().try_into().unwrap(),
symbol: "roc".as_bytes().to_vec().try_into().unwrap(),
decimals: 12,
},
));
assert_expected_events!(
BridgeHubZagros,
vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::RegisterToken { .. }) => {},]
);
});
}
pub(crate) fn asset_hub_zagros_global_location() -> Location {
Location::new(
2,
[
GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)),
Teyrchain(AssetHubZagros::para_id().into()),
],
)
}
pub(crate) fn bridge_hub_zagros_location() -> Location {
Location::new(
2,
[
GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)),
Teyrchain(BridgeHubZagros::para_id().into()),
],
)
}
@@ -0,0 +1,296 @@
// 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::{imports::*, tests::snowbridge_common::*};
use bridge_hub_zagros_runtime::xcm_config::LocationToAccountId;
use emulated_integration_tests_common::snowbridge::{SEPOLIA_ID, WETH};
use snowbridge_core::AssetMetadata;
use snowbridge_pallet_system::Error;
use testnet_teyrchains_constants::zagros::snowbridge::EthereumNetwork;
use xcm_executor::traits::ConvertLocation;
// The user origin should be banned in ethereum_blob_exporter with error logs
// xcm::ethereum_blob_exporter: could not get teyrchain id from universal source
// 'X2([Teyrchain(1000), AccountId32 {...}])'
#[test]
fn user_send_message_directly_bypass_exporter_from_ah_will_fail() {
fund_on_bh();
register_assets_on_ah();
fund_on_ah();
create_pools_on_ah();
let sov_account_for_sender = LocationToAccountId::convert_location(&Location::new(
1,
[
Teyrchain(AssetHubZagros::para_id().into()),
AccountId32 {
network: Some(ByGenesis(ZAGROS_GENESIS_HASH)),
id: AssetHubZagrosSender::get().into(),
},
],
))
.unwrap();
BridgeHubZagros::fund_accounts(vec![(sov_account_for_sender, INITIAL_FUND)]);
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
type RuntimeOrigin = <AssetHubZagros as Chain>::RuntimeOrigin;
let local_fee_asset =
Asset { id: AssetId(Location::parent()), fun: Fungible(1_000_000_000_000) };
let weth_location_reanchored =
Location::new(0, [AccountKey20 { network: None, key: WETH.into() }]);
let weth_asset = Asset {
id: AssetId(weth_location_reanchored.clone()),
fun: Fungible(TOKEN_AMOUNT * 1_000_000_000),
};
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::send(
RuntimeOrigin::signed(AssetHubZagrosSender::get()),
bx!(VersionedLocation::from(bridge_hub())),
bx!(VersionedXcm::from(Xcm(vec![
WithdrawAsset(local_fee_asset.clone().into()),
BuyExecution { fees: local_fee_asset.clone(), weight_limit: Unlimited },
ExportMessage {
network: Ethereum { chain_id: SEPOLIA_ID },
destination: Here,
xcm: Xcm(vec![
WithdrawAsset(weth_asset.clone().into()),
DepositAsset { assets: Wild(All), beneficiary: beneficiary() },
SetTopic([0; 32]),
]),
},
]))),
));
assert_expected_events!(
AssetHubZagros,
vec![RuntimeEvent::PezkuwiXcm(pezpallet_xcm::Event::Sent{ .. }) => {},]
);
});
BridgeHubZagros::execute_with(|| {
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
BridgeHubZagros,
vec![RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed{ success:false, .. }) => {},]
);
});
}
// ENA is not allowed to be registered as PNA
#[test]
fn test_register_ena_on_bh_will_fail() {
BridgeHubZagros::execute_with(|| {
type RuntimeOrigin = <BridgeHubZagros as Chain>::RuntimeOrigin;
type Runtime = <BridgeHubZagros as Chain>::Runtime;
assert_ok!(<BridgeHubZagros as BridgeHubZagrosPallet>::Balances::force_set_balance(
RuntimeOrigin::root(),
pezsp_runtime::MultiAddress::Id(BridgeHubZagrosSender::get()),
INITIAL_FUND * 10,
));
assert_err!(
<BridgeHubZagros as BridgeHubZagrosPallet>::EthereumSystem::register_token(
RuntimeOrigin::root(),
Box::new(VersionedLocation::from(weth_location())),
AssetMetadata {
name: "weth".as_bytes().to_vec().try_into().unwrap(),
symbol: "weth".as_bytes().to_vec().try_into().unwrap(),
decimals: 18,
},
),
Error::<Runtime>::LocationConversionFailed
);
});
}
#[test]
fn user_exploit_with_arbitrary_message_will_fail() {
fund_on_bh();
register_assets_on_ah();
fund_on_ah();
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
type RuntimeOrigin = <AssetHubZagros as Chain>::RuntimeOrigin;
let remote_fee_asset_location: Location =
Location::new(2, [EthereumNetwork::get().into()]).into();
let remote_fee_asset: Asset = (remote_fee_asset_location.clone(), 1).into();
let assets = VersionedAssets::from(vec![remote_fee_asset]);
let exploited_weth = Asset {
id: AssetId(Location::new(0, [AccountKey20 { network: None, key: WETH.into() }])),
// A big amount without burning
fun: Fungible(TOKEN_AMOUNT * 1_000_000_000),
};
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::transfer_assets_using_type_and_then(
RuntimeOrigin::signed(AssetHubZagrosSender::get()),
bx!(VersionedLocation::from(ethereum())),
bx!(assets),
bx!(TransferType::DestinationReserve),
bx!(VersionedAssetId::from(remote_fee_asset_location.clone())),
bx!(TransferType::DestinationReserve),
// exploited_weth here is far more than the burnt, which means instructions inner
// are user provided and untrustworthy/dangerous!
// Currently it depends on EthereumBlobExporter on BH to check the message is legal
// and convert to Ethereum command.
bx!(VersionedXcm::from(Xcm(vec![
WithdrawAsset(exploited_weth.clone().into()),
DepositAsset { assets: Wild(All), beneficiary: beneficiary() },
SetTopic([0; 32]),
]))),
Unlimited
));
assert_expected_events!(
AssetHubZagros,
vec![RuntimeEvent::PezkuwiXcm(pezpallet_xcm::Event::Sent{ .. }) => {},]
);
});
BridgeHubZagros::execute_with(|| {
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
BridgeHubZagros,
vec![RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed{ success:false, .. }) => {},]
);
});
}
#[test]
fn export_from_system_teyrchain_but_not_root_will_fail() {
fund_on_bh();
register_assets_on_ah();
fund_on_ah();
create_pools_on_ah();
let sub_location = PalletInstance(100);
let assethub_pallet_sovereign = BridgeHubZagros::sovereign_account_id_of(Location::new(
1,
[Teyrchain(AssetHubZagros::para_id().into()), sub_location],
));
BridgeHubZagros::fund_accounts(vec![(assethub_pallet_sovereign.clone(), INITIAL_FUND)]);
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
type RuntimeOrigin = <AssetHubZagros as Chain>::RuntimeOrigin;
let local_fee_asset =
Asset { id: AssetId(Location::parent()), fun: Fungible(1_000_000_000_000) };
let weth_location_reanchored =
Location::new(0, [AccountKey20 { network: None, key: WETH.into() }]);
let weth_asset = Asset {
id: AssetId(weth_location_reanchored.clone()),
fun: Fungible(TOKEN_AMOUNT * 1_000_000_000),
};
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::send(
RuntimeOrigin::root(),
bx!(VersionedLocation::from(bridge_hub())),
bx!(VersionedXcm::from(Xcm(vec![
DescendOrigin(sub_location.into()),
WithdrawAsset(local_fee_asset.clone().into()),
BuyExecution { fees: local_fee_asset.clone(), weight_limit: Unlimited },
ExportMessage {
network: Ethereum { chain_id: SEPOLIA_ID },
destination: Here,
xcm: Xcm(vec![
WithdrawAsset(weth_asset.clone().into()),
DepositAsset { assets: Wild(All), beneficiary: beneficiary() },
SetTopic([0; 32]),
]),
},
]))),
));
assert_expected_events!(
AssetHubZagros,
vec![RuntimeEvent::PezkuwiXcm(pezpallet_xcm::Event::Sent{ .. }) => {},]
);
});
BridgeHubZagros::execute_with(|| {
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
BridgeHubZagros,
vec![RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed{ success:false, .. }) => {},]
);
});
}
#[test]
fn export_from_non_system_teyrchain_will_fail() {
let penpal_sovereign = BridgeHubZagros::sovereign_account_id_of(Location::new(
1,
[Teyrchain(PenpalB::para_id().into())],
));
BridgeHubZagros::fund_accounts(vec![(penpal_sovereign.clone(), INITIAL_FUND)]);
PenpalB::execute_with(|| {
type RuntimeEvent = <PenpalB as Chain>::RuntimeEvent;
type RuntimeOrigin = <PenpalB as Chain>::RuntimeOrigin;
let local_fee_asset =
Asset { id: AssetId(Location::here()), fun: Fungible(1_000_000_000_000) };
let weth_location_reanchored =
Location::new(0, [AccountKey20 { network: None, key: WETH.into() }]);
let weth_asset =
Asset { id: AssetId(weth_location_reanchored.clone()), fun: Fungible(TOKEN_AMOUNT) };
assert_ok!(<PenpalB as PenpalBPallet>::PezkuwiXcm::send(
RuntimeOrigin::root(),
bx!(VersionedLocation::from(bridge_hub())),
bx!(VersionedXcm::from(Xcm(vec![
WithdrawAsset(local_fee_asset.clone().into()),
BuyExecution { fees: local_fee_asset.clone(), weight_limit: Unlimited },
ExportMessage {
network: Ethereum { chain_id: SEPOLIA_ID },
destination: Here,
xcm: Xcm(vec![
WithdrawAsset(weth_asset.clone().into()),
DepositAsset { assets: Wild(All), beneficiary: beneficiary() },
SetTopic([0; 32]),
]),
},
]))),
));
assert_expected_events!(
PenpalB,
vec![RuntimeEvent::PezkuwiXcm(pezpallet_xcm::Event::Sent{ .. }) => {},]
);
});
BridgeHubZagros::execute_with(|| {
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
BridgeHubZagros,
vec![RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed{ success:false, origin, .. }) => {
origin: *origin == bridge_hub_common::AggregateMessageOrigin::Sibling(PenpalB::para_id()),
},]
);
});
}
@@ -0,0 +1,612 @@
// 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::{
imports::*,
tests::{
assert_bridge_hub_pezkuwichain_message_received, assert_bridge_hub_zagros_message_accepted,
asset_hub_pezkuwichain_location, bridged_roc_at_ah_zagros, create_foreign_on_ah_zagros,
snowbridge_common::{
asset_hub_zagros_global_location, erc20_token_location, eth_location,
register_foreign_asset, register_roc_on_bh, set_up_eth_and_hez_pool,
set_up_eth_and_hez_pool_on_pezkuwichain, set_up_pool_with_wnd_on_ah_zagros,
snowbridge_sovereign, TOKEN_AMOUNT,
},
},
};
use asset_hub_zagros_runtime::ForeignAssets;
use bridge_hub_zagros_runtime::{
bridge_common_config::BridgeReward, bridge_to_ethereum_config::EthereumGatewayAddress,
EthereumInboundQueueV2,
};
use codec::Encode;
use hex_literal::hex;
use snowbridge_core::TokenIdOf;
use snowbridge_inbound_queue_primitives::v2::{
EthereumAsset::{ForeignTokenERC20, NativeTokenERC20},
Message, XcmPayload,
};
use pezsp_core::{H160, H256};
use xcm::opaque::latest::AssetTransferFilter::{ReserveDeposit, ReserveWithdraw};
use xcm_executor::traits::ConvertLocation;
/// Calculates the XCM prologue fee for sending an XCM to AH.
const INITIAL_FUND: u128 = 500_000_000_000_000;
/// An ERC-20 token to be registered and sent.
const TOKEN_ID: [u8; 20] = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2");
#[test]
fn send_token_to_pezkuwichain_v2() {
let relayer_account = BridgeHubZagrosSender::get();
let relayer_reward = 1_500_000_000_000u128;
let token: H160 = TOKEN_ID.into();
let token_location = erc20_token_location(token);
let beneficiary_acc_id: H256 = H256::random();
let beneficiary_acc_bytes: [u8; 32] = beneficiary_acc_id.into();
let beneficiary =
Location::new(0, AccountId32 { network: None, id: beneficiary_acc_id.into() });
let claimer_acc_id = H256::random();
let claimer = AccountId32 { network: None, id: claimer_acc_id.into() };
let claimer_bytes = claimer.encode();
// set XCM versions
BridgeHubZagros::force_xcm_version(asset_hub_zagros_global_location(), XCM_VERSION);
BridgeHubZagros::force_xcm_version(asset_hub_pezkuwichain_location(), XCM_VERSION);
AssetHubZagros::force_xcm_version(asset_hub_pezkuwichain_location(), XCM_VERSION);
// To pay fees on Pezkuwichain.
let eth_fee_pezkuwichain_ah: xcm::prelude::Asset =
(eth_location(), 3_000_000_000_000u128).into();
// To satisfy ED
AssetHubPezkuwichain::fund_accounts(vec![(
pezsp_runtime::AccountId32::from(beneficiary_acc_bytes),
3_000_000_000_000,
)]);
BridgeHubZagros::fund_para_sovereign(AssetHubZagros::para_id(), INITIAL_FUND);
// Register the token on AH Zagros and Pezkuwichain
let snowbridge_sovereign = snowbridge_sovereign();
AssetHubPezkuwichain::execute_with(|| {
type RuntimeOrigin = <AssetHubPezkuwichain as Chain>::RuntimeOrigin;
assert_ok!(
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets::force_create(
RuntimeOrigin::root(),
token_location.clone().try_into().unwrap(),
snowbridge_sovereign.clone().into(),
true,
1000,
)
);
assert!(<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets::asset_exists(
token_location.clone().try_into().unwrap(),
));
});
register_foreign_asset(token_location.clone());
set_up_eth_and_hez_pool();
set_up_eth_and_hez_pool_on_pezkuwichain();
let token_transfer_value = 2_000_000_000_000u128;
let assets = vec![
// the token being transferred
NativeTokenERC20 { token_id: token.into(), value: token_transfer_value },
];
let token_asset_ah: xcm::prelude::Asset = (token_location.clone(), token_transfer_value).into();
BridgeHubZagros::execute_with(|| {
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
let instructions = vec![
// Send message to Pezkuwichain AH
InitiateTransfer {
// Pezkuwichain
destination: Location::new(
2,
[
GlobalConsensus(ByGenesis(xcm::latest::PEZKUWICHAIN_GENESIS_HASH)),
Teyrchain(1000u32),
],
),
remote_fees: Some(ReserveDeposit(Definite(
vec![eth_fee_pezkuwichain_ah.clone()].into(),
))),
preserve_origin: false,
assets: BoundedVec::truncate_from(vec![ReserveDeposit(Definite(
vec![token_asset_ah.clone()].into(),
))]),
remote_xcm: vec![
// Refund unspent fees
RefundSurplus,
// Deposit assets to beneficiary.
DepositAsset { assets: Wild(AllCounted(3)), beneficiary: beneficiary.clone() },
SetTopic(H256::random().into()),
]
.into(),
},
RefundSurplus,
DepositAsset {
assets: Wild(AllOf { id: AssetId(eth_location()), fun: WildFungibility::Fungible }),
beneficiary,
},
];
let xcm: Xcm<()> = instructions.into();
let versioned_message_xcm = VersionedXcm::V5(xcm);
let origin = EthereumGatewayAddress::get();
let message = Message {
gateway: origin,
nonce: 1,
origin,
assets,
xcm: XcmPayload::Raw(versioned_message_xcm.encode()),
claimer: Some(claimer_bytes),
value: 3_500_000_000_000u128,
execution_fee: 1_500_000_000_000u128,
relayer_fee: relayer_reward,
};
EthereumInboundQueueV2::process_message(relayer_account.clone(), message).unwrap();
assert_expected_events!(
BridgeHubZagros,
vec![
RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},
// Check that the relayer reward was registered.
RuntimeEvent::BridgeRelayers(pezpallet_bridge_relayers::Event::RewardRegistered { relayer, reward_kind, reward_balance }) => {
relayer: *relayer == relayer_account,
reward_kind: *reward_kind == BridgeReward::Snowbridge,
reward_balance: *reward_balance == relayer_reward,
},
]
);
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
// Check that the assets were issued on AssetHub
assert_expected_events!(
AssetHubZagros,
vec![
// Message processed successfully
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},
]
);
let events = AssetHubZagros::events();
// Check that no assets were trapped
assert!(
!events.iter().any(|event| matches!(
event,
RuntimeEvent::PezkuwiXcm(pezpallet_xcm::Event::AssetsTrapped { .. })
)),
"Assets were trapped, should not happen."
);
});
assert_bridge_hub_zagros_message_accepted(true);
assert_bridge_hub_pezkuwichain_message_received();
AssetHubPezkuwichain::execute_with(|| {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubPezkuwichain,
vec![
// Message processed successfully
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
// Token was issued to beneficiary
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == token_location,
owner: *owner == beneficiary_acc_bytes.into(),
},
// Leftover fees was deposited to beneficiary
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == eth_location(),
owner: *owner == beneficiary_acc_bytes.into(),
},
]
);
// Beneficiary received the token transfer value
assert_eq!(
ForeignAssets::balance(token_location, AccountId::from(beneficiary_acc_bytes)),
token_transfer_value
);
let events = AssetHubPezkuwichain::events();
// Check that no assets were trapped
assert!(
!events.iter().any(|event| matches!(
event,
RuntimeEvent::PezkuwiXcm(pezpallet_xcm::Event::AssetsTrapped { .. })
)),
"Assets were trapped on Pezkuwichain AssetHub, should not happen."
);
});
}
#[test]
fn send_ether_to_pezkuwichain_v2() {
let relayer_account = BridgeHubZagrosSender::get();
let relayer_reward = 1_500_000_000_000u128;
let beneficiary_acc_id: H256 = H256::random();
let beneficiary_acc_bytes: [u8; 32] = beneficiary_acc_id.into();
let beneficiary =
Location::new(0, AccountId32 { network: None, id: beneficiary_acc_id.into() });
let claimer_acc_id = H256::random();
let claimer = AccountId32 { network: None, id: claimer_acc_id.into() };
let claimer_bytes = claimer.encode();
// set XCM versions
BridgeHubZagros::force_xcm_version(asset_hub_zagros_global_location(), XCM_VERSION);
BridgeHubZagros::force_xcm_version(asset_hub_pezkuwichain_location(), XCM_VERSION);
AssetHubZagros::force_xcm_version(asset_hub_pezkuwichain_location(), XCM_VERSION);
// To pay fees on Pezkuwichain.
let eth_fee_pezkuwichain_ah: xcm::prelude::Asset =
(eth_location(), 2_000_000_000_000u128).into();
let ether_asset_ah: xcm::prelude::Asset = (eth_location(), 4_000_000_000_000u128).into();
BridgeHubZagros::fund_para_sovereign(AssetHubZagros::para_id(), INITIAL_FUND);
set_up_eth_and_hez_pool();
set_up_eth_and_hez_pool_on_pezkuwichain();
BridgeHubZagros::execute_with(|| {
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
let instructions = vec![
// Send message to Pezkuwichain AH
InitiateTransfer {
// Pezkuwichain
destination: Location::new(
2,
[
GlobalConsensus(ByGenesis(xcm::latest::PEZKUWICHAIN_GENESIS_HASH)),
Teyrchain(1000u32),
],
),
remote_fees: Some(ReserveDeposit(Definite(
vec![eth_fee_pezkuwichain_ah.clone()].into(),
))),
preserve_origin: false,
assets: BoundedVec::truncate_from(vec![ReserveDeposit(Definite(
vec![ether_asset_ah.clone()].into(),
))]),
remote_xcm: vec![
// Refund unspent fees
RefundSurplus,
// Deposit assets to beneficiary.
DepositAsset { assets: Wild(AllCounted(3)), beneficiary: beneficiary.clone() },
SetTopic(H256::random().into()),
]
.into(),
},
RefundSurplus,
DepositAsset {
assets: Wild(AllOf { id: AssetId(eth_location()), fun: WildFungibility::Fungible }),
beneficiary,
},
];
let xcm: Xcm<()> = instructions.into();
let versioned_message_xcm = VersionedXcm::V5(xcm);
let origin = EthereumGatewayAddress::get();
let message = Message {
gateway: origin,
nonce: 1,
origin,
assets: vec![],
xcm: XcmPayload::Raw(versioned_message_xcm.encode()),
claimer: Some(claimer_bytes),
value: 6_500_000_000_000u128,
execution_fee: 1_500_000_000_000u128,
relayer_fee: relayer_reward,
};
EthereumInboundQueueV2::process_message(relayer_account.clone(), message).unwrap();
assert_expected_events!(
BridgeHubZagros,
vec![
RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},
// Check that the relayer reward was registered.
RuntimeEvent::BridgeRelayers(pezpallet_bridge_relayers::Event::RewardRegistered { relayer, reward_kind, reward_balance }) => {
relayer: *relayer == relayer_account,
reward_kind: *reward_kind == BridgeReward::Snowbridge,
reward_balance: *reward_balance == relayer_reward,
},
]
);
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
// Check that the assets were issued on AssetHub
assert_expected_events!(
AssetHubZagros,
vec![
// Message processed successfully
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},
]
);
let events = AssetHubZagros::events();
// Check that no assets were trapped
assert!(
!events.iter().any(|event| matches!(
event,
RuntimeEvent::PezkuwiXcm(pezpallet_xcm::Event::AssetsTrapped { .. })
)),
"Assets were trapped, should not happen."
);
});
assert_bridge_hub_zagros_message_accepted(true);
assert_bridge_hub_pezkuwichain_message_received();
AssetHubPezkuwichain::execute_with(|| {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubPezkuwichain,
vec![
// Message processed successfully
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
// Ether was deposited to beneficiary
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == eth_location(),
owner: *owner == beneficiary_acc_bytes.into(),
},
]
);
let events = AssetHubPezkuwichain::events();
// Check that no assets were trapped
assert!(
!events.iter().any(|event| matches!(
event,
RuntimeEvent::PezkuwiXcm(pezpallet_xcm::Event::AssetsTrapped { .. })
)),
"Assets were trapped on Pezkuwichain AssetHub, should not happen."
);
});
}
#[test]
fn send_roc_from_ethereum_to_pezkuwichain() {
let initial_fund: u128 = 200_000_000_000_000;
let initial_liquidity: u128 = initial_fund / 2;
let relayer_account = BridgeHubZagrosSender::get();
let relayer_reward = 1_500_000_000_000u128;
let claimer = AccountId32 { network: None, id: H256::random().into() };
let claimer_bytes = claimer.encode();
let beneficiary = Location::new(
0,
AccountId32 { network: None, id: AssetHubPezkuwichainReceiver::get().into() },
);
BridgeHubZagros::fund_para_sovereign(AssetHubZagros::para_id(), INITIAL_FUND);
let ethereum_sovereign: AccountId = snowbridge_sovereign();
let bridged_roc_at_asset_hub_zagros = bridged_roc_at_ah_zagros();
create_foreign_on_ah_zagros(
bridged_roc_at_asset_hub_zagros.clone(),
true,
vec![(asset_hub_pezkuwichain_location(), false).into()],
vec![],
);
set_up_pool_with_wnd_on_ah_zagros(
bridged_roc_at_asset_hub_zagros.clone(),
true,
initial_fund,
initial_liquidity,
);
BridgeHubPezkuwichain::fund_para_sovereign(AssetHubPezkuwichain::para_id(), initial_fund);
AssetHubPezkuwichain::fund_accounts(vec![(AssetHubPezkuwichainSender::get(), initial_fund)]);
register_roc_on_bh();
set_up_eth_and_hez_pool();
set_up_eth_and_hez_pool_on_pezkuwichain();
// set XCM versions
BridgeHubZagros::force_xcm_version(asset_hub_zagros_global_location(), XCM_VERSION);
BridgeHubZagros::force_xcm_version(asset_hub_pezkuwichain_location(), XCM_VERSION);
AssetHubZagros::force_xcm_version(asset_hub_pezkuwichain_location(), XCM_VERSION);
let eth_fee_pezkuwichain_ah: xcm::prelude::Asset =
(eth_location(), 2_000_000_000_000u128).into();
let roc = Location::new(1, [GlobalConsensus(ByGenesis(PEZKUWICHAIN_GENESIS_HASH))]);
let token_id = TokenIdOf::convert_location(&roc).unwrap();
let roc_reachored: xcm::prelude::Asset =
(Location::new(2, [GlobalConsensus(ByGenesis(PEZKUWICHAIN_GENESIS_HASH))]), TOKEN_AMOUNT)
.into();
let assets = vec![
// the token being transferred
ForeignTokenERC20 { token_id: token_id.into(), value: TOKEN_AMOUNT },
];
AssetHubZagros::execute_with(|| {
// Mint the asset into the bridge sovereign account, to mimic locked funds
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::mint(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosAssetOwner::get()),
bridged_roc_at_asset_hub_zagros.clone().into(),
ethereum_sovereign.clone().into(),
TOKEN_AMOUNT,
));
});
// fund the AHW's SA on AHR with the TYR tokens held in reserve
let sov_ahw_on_ahr =
AssetHubPezkuwichain::sovereign_account_of_teyrchain_on_other_global_consensus(
ByGenesis(ZAGROS_GENESIS_HASH),
AssetHubZagros::para_id(),
);
AssetHubPezkuwichain::fund_accounts(vec![(sov_ahw_on_ahr.clone(), INITIAL_FUND)]);
BridgeHubZagros::execute_with(|| {
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
let instructions = vec![
// Send message to Pezkuwichain AH
InitiateTransfer {
// Pezkuwichain
destination: Location::new(
2,
[
GlobalConsensus(ByGenesis(xcm::latest::PEZKUWICHAIN_GENESIS_HASH)),
Teyrchain(1000u32),
],
),
remote_fees: Some(ReserveDeposit(Definite(
vec![eth_fee_pezkuwichain_ah.clone()].into(),
))),
preserve_origin: false,
assets: BoundedVec::truncate_from(vec![ReserveWithdraw(Definite(
vec![roc_reachored.clone()].into(),
))]),
remote_xcm: vec![
// Refund unspent fees
RefundSurplus,
// Deposit assets and leftover fees to beneficiary.
DepositAsset { assets: Wild(AllCounted(2)), beneficiary: beneficiary.clone() },
SetTopic(H256::random().into()),
]
.into(),
},
RefundSurplus,
DepositAsset {
assets: Wild(AllOf { id: AssetId(eth_location()), fun: WildFungibility::Fungible }),
beneficiary,
},
];
let xcm: Xcm<()> = instructions.into();
let versioned_message_xcm = VersionedXcm::V5(xcm);
let origin = EthereumGatewayAddress::get();
let message = Message {
gateway: origin,
nonce: 1,
origin,
assets,
xcm: XcmPayload::Raw(versioned_message_xcm.encode()),
claimer: Some(claimer_bytes),
value: 9_500_000_000_000u128,
execution_fee: 3_500_000_000_000u128,
relayer_fee: relayer_reward,
};
EthereumInboundQueueV2::process_message(relayer_account.clone(), message).unwrap();
assert_expected_events!(
BridgeHubZagros,
vec![
RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},
// Check that the relayer reward was registered.
RuntimeEvent::BridgeRelayers(pezpallet_bridge_relayers::Event::RewardRegistered { relayer, reward_kind, reward_balance }) => {
relayer: *relayer == relayer_account,
reward_kind: *reward_kind == BridgeReward::Snowbridge,
reward_balance: *reward_balance == relayer_reward,
},
]
);
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
// Message processed successfully
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
let events = AssetHubZagros::events();
// Check that no assets were trapped
assert!(
!events.iter().any(|event| matches!(
event,
RuntimeEvent::PezkuwiXcm(pezpallet_xcm::Event::AssetsTrapped { .. })
)),
"Assets were trapped, should not happen."
);
});
assert_bridge_hub_zagros_message_accepted(true);
assert_bridge_hub_pezkuwichain_message_received();
AssetHubPezkuwichain::execute_with(|| {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubPezkuwichain,
vec![
// TYR is withdrawn from AHW's SA on AHR
RuntimeEvent::Balances(
pezpallet_balances::Event::Burned { who, amount }
) => {
who: *who == sov_ahw_on_ahr,
amount: *amount == TOKEN_AMOUNT,
},
// TYRs deposited to beneficiary
RuntimeEvent::Balances(pezpallet_balances::Event::Minted { who, .. }) => {
who: *who == AssetHubPezkuwichainReceiver::get(),
},
// message processed successfully
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
let events = AssetHubPezkuwichain::events();
// Check that no assets were trapped
assert!(
!events.iter().any(|event| matches!(
event,
RuntimeEvent::PezkuwiXcm(pezpallet_xcm::Event::AssetsTrapped { .. })
)),
"Assets were trapped on Pezkuwichain AssetHub, should not happen."
);
});
}
@@ -0,0 +1,432 @@
// 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::{
imports::*,
tests::{
snowbridge_common::*,
snowbridge_v2_outbound::{EthereumSystemFrontend, EthereumSystemFrontendCall},
usdt_at_ah_zagros,
},
};
use emulated_integration_tests_common::snowbridge::{SEPOLIA_ID, WETH};
use pezframe_support::assert_noop;
use snowbridge_core::AssetMetadata;
use pezsp_runtime::DispatchError::BadOrigin;
use xcm::v5::AssetTransferFilter;
#[test]
fn register_penpal_a_asset_from_penpal_b_will_fail() {
fund_on_bh();
register_assets_on_ah();
fund_on_ah();
create_pools_on_ah();
set_trust_reserve_on_penpal();
register_assets_on_penpal();
fund_on_penpal();
let penpal_user_location = Location::new(
1,
[
Teyrchain(PenpalB::para_id().into()),
AccountId32 {
network: Some(ByGenesis(ZAGROS_GENESIS_HASH)),
id: PenpalBSender::get().into(),
},
],
);
let asset_location_on_penpal =
PenpalB::execute_with(|| PenpalLocalTeleportableToAssetHub::get());
let penpal_a_asset_at_asset_hub =
Location::new(1, [Junction::Teyrchain(PenpalA::para_id().into())])
.appended_with(asset_location_on_penpal)
.unwrap();
PenpalB::execute_with(|| {
type RuntimeOrigin = <PenpalB as Chain>::RuntimeOrigin;
let local_fee_asset_on_penpal =
Asset { id: AssetId(Location::parent()), fun: Fungible(LOCAL_FEE_AMOUNT_IN_DOT) };
let remote_fee_asset_on_ah =
Asset { id: AssetId(ethereum()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_ETHER) };
let remote_fee_asset_on_ethereum =
Asset { id: AssetId(ethereum()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_ETHER) };
let call = EthereumSystemFrontend::EthereumSystemFrontend(
EthereumSystemFrontendCall::RegisterToken {
asset_id: Box::new(VersionedLocation::from(penpal_a_asset_at_asset_hub)),
metadata: Default::default(),
fee_asset: remote_fee_asset_on_ethereum.clone(),
},
);
let assets = vec![
local_fee_asset_on_penpal.clone(),
remote_fee_asset_on_ah.clone(),
remote_fee_asset_on_ethereum.clone(),
];
let xcm = VersionedXcm::from(Xcm(vec![
WithdrawAsset(assets.clone().into()),
PayFees { asset: local_fee_asset_on_penpal.clone() },
InitiateTransfer {
destination: asset_hub(),
remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite(
remote_fee_asset_on_ah.clone().into(),
))),
preserve_origin: true,
assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveWithdraw(
Definite(remote_fee_asset_on_ethereum.clone().into()),
)]),
remote_xcm: Xcm(vec![
DepositAsset { assets: Wild(All), beneficiary: penpal_user_location },
Transact {
origin_kind: OriginKind::Xcm,
call: call.encode().into(),
fallback_max_weight: None,
},
]),
},
]));
assert_ok!(<PenpalB as PenpalBPallet>::PezkuwiXcm::execute(
RuntimeOrigin::root(),
bx!(xcm.clone()),
Weight::from(EXECUTION_WEIGHT),
));
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Burned { .. }) => {},]
);
});
// No events should be emitted on the bridge hub
BridgeHubZagros::execute_with(|| {
assert_expected_events!(BridgeHubZagros, vec![]);
});
}
#[test]
fn export_from_non_system_teyrchain_will_fail() {
let penpal_location = Location::new(1, [Teyrchain(PenpalB::para_id().into())]);
let penpal_sovereign = BridgeHubZagros::sovereign_account_id_of(penpal_location.clone());
BridgeHubZagros::fund_accounts(vec![(penpal_sovereign.clone(), INITIAL_FUND)]);
PenpalB::execute_with(|| {
type RuntimeEvent = <PenpalB as Chain>::RuntimeEvent;
type RuntimeOrigin = <PenpalB as Chain>::RuntimeOrigin;
let relay_fee_asset =
Asset { id: AssetId(Location::parent()), fun: Fungible(1_000_000_000_000) };
let weth_location_reanchored =
Location::new(0, [AccountKey20 { network: None, key: WETH.into() }]);
let weth_asset =
Asset { id: AssetId(weth_location_reanchored.clone()), fun: Fungible(TOKEN_AMOUNT) };
assert_ok!(<PenpalB as PenpalBPallet>::PezkuwiXcm::send(
RuntimeOrigin::root(),
bx!(VersionedLocation::from(bridge_hub())),
bx!(VersionedXcm::from(Xcm(vec![
WithdrawAsset(relay_fee_asset.clone().into()),
BuyExecution { fees: relay_fee_asset.clone(), weight_limit: Unlimited },
ExportMessage {
network: Ethereum { chain_id: SEPOLIA_ID },
destination: Here,
xcm: Xcm(vec![
AliasOrigin(penpal_location),
WithdrawAsset(weth_asset.clone().into()),
DepositAsset { assets: Wild(All), beneficiary: beneficiary() },
SetTopic([0; 32]),
]),
},
]))),
));
assert_expected_events!(
PenpalB,
vec![RuntimeEvent::PezkuwiXcm(pezpallet_xcm::Event::Sent{ .. }) => {},]
);
});
BridgeHubZagros::execute_with(|| {
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
BridgeHubZagros,
vec![RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed{ success: false, .. }) => {},]
);
});
}
#[test]
pub fn register_usdt_not_from_owner_on_asset_hub_will_fail() {
fund_on_bh();
register_assets_on_ah();
fund_on_ah();
AssetHubZagros::execute_with(|| {
type RuntimeOrigin = <AssetHubZagros as Chain>::RuntimeOrigin;
let fees_asset =
Asset { id: AssetId(ethereum()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_ETHER) };
assert_noop!(
<AssetHubZagros as AssetHubZagrosPallet>::SnowbridgeSystemFrontend::register_token(
// The owner is Alice, while AssetHubZagrosReceiver is Bob, so it should fail
RuntimeOrigin::signed(AssetHubZagrosReceiver::get()),
bx!(VersionedLocation::from(usdt_at_ah_zagros())),
AssetMetadata {
name: "usdt".as_bytes().to_vec().try_into().unwrap(),
symbol: "usdt".as_bytes().to_vec().try_into().unwrap(),
decimals: 6,
},
fees_asset
),
BadOrigin
);
});
}
#[test]
pub fn register_relay_token_from_asset_hub_user_origin_will_fail() {
fund_on_bh();
register_assets_on_ah();
fund_on_ah();
AssetHubZagros::execute_with(|| {
type RuntimeOrigin = <AssetHubZagros as Chain>::RuntimeOrigin;
let fees_asset =
Asset { id: AssetId(ethereum()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_ETHER) };
assert_noop!(
<AssetHubZagros as AssetHubZagrosPallet>::SnowbridgeSystemFrontend::register_token(
RuntimeOrigin::signed(AssetHubZagrosSender::get()),
bx!(VersionedLocation::from(Location { parents: 1, interior: [].into() })),
AssetMetadata {
name: "wnd".as_bytes().to_vec().try_into().unwrap(),
symbol: "wnd".as_bytes().to_vec().try_into().unwrap(),
decimals: 12,
},
fees_asset
),
BadOrigin
);
});
}
pub const INSUFFICIENT_REMOTE_FEE_AMOUNT: u128 = 1_000_000_000;
// Test that the asset trapping and claim flow work correctly.
#[test]
fn transfer_from_penpal_to_ethereum_trapped_on_ah_and_then_claim_can_work() {
create_pools_on_ah();
register_pal_on_ah();
register_pal_on_bh();
fund_on_ah();
// penpal
set_trust_reserve_on_penpal();
register_assets_on_penpal();
fund_on_penpal();
let penpal_user_location = Location::new(
1,
[
Teyrchain(PenpalB::para_id().into()),
AccountId32 {
network: Some(ByGenesis(ZAGROS_GENESIS_HASH)),
id: PenpalBSender::get().into(),
},
],
);
// Since fee is insufficient, asset got trapped on AH
PenpalB::execute_with(|| {
type RuntimeOrigin = <PenpalB as Chain>::RuntimeOrigin;
let local_fee_asset_on_penpal =
Asset { id: AssetId(Location::parent()), fun: Fungible(LOCAL_FEE_AMOUNT_IN_DOT) };
let remote_fee_asset_on_ah =
Asset { id: AssetId(ethereum()), fun: Fungible(INSUFFICIENT_REMOTE_FEE_AMOUNT) };
let remote_fee_asset_on_ethereum =
Asset { id: AssetId(ethereum()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_ETHER) };
let ena = Asset { id: AssetId(weth_location()), fun: Fungible(TOKEN_AMOUNT) };
let assets = vec![
local_fee_asset_on_penpal.clone(),
remote_fee_asset_on_ah.clone(),
remote_fee_asset_on_ethereum.clone(),
ena.clone(),
];
let xcm = VersionedXcm::from(Xcm(vec![
WithdrawAsset(assets.clone().into()),
PayFees { asset: local_fee_asset_on_penpal.clone() },
InitiateTransfer {
destination: asset_hub(),
remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite(
remote_fee_asset_on_ah.clone().into(),
))),
preserve_origin: true,
assets: BoundedVec::truncate_from(vec![
AssetTransferFilter::ReserveWithdraw(Definite(
remote_fee_asset_on_ethereum.clone().into(),
)),
AssetTransferFilter::ReserveWithdraw(Definite(ena.clone().into())),
]),
remote_xcm: Xcm(vec![
SetAppendix(Xcm(vec![SetHints {
hints: vec![AssetClaimer { location: penpal_user_location }]
.try_into()
.unwrap(),
}])),
InitiateTransfer {
destination: ethereum(),
remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite(
remote_fee_asset_on_ethereum.clone().into(),
))),
preserve_origin: true,
assets: BoundedVec::truncate_from(vec![
AssetTransferFilter::ReserveWithdraw(Definite(ena.clone().into())),
]),
remote_xcm: Xcm(vec![DepositAsset {
assets: Wild(All),
beneficiary: beneficiary(),
}]),
},
]),
},
]));
assert_ok!(<PenpalB as PenpalBPallet>::PezkuwiXcm::execute(
RuntimeOrigin::signed(PenpalBSender::get()),
bx!(xcm.clone()),
Weight::from(EXECUTION_WEIGHT),
));
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![RuntimeEvent::PezkuwiXcm(pezpallet_xcm::Event::ProcessXcmError { .. }) => {},]
);
});
// Claim the trapped asset and deposit on AH.
PenpalB::execute_with(|| {
type RuntimeOrigin = <PenpalB as Chain>::RuntimeOrigin;
let local_fee_asset_on_penpal =
Asset { id: AssetId(Location::parent()), fun: Fungible(LOCAL_FEE_AMOUNT_IN_DOT) };
let remote_fee_asset_on_ah =
Asset { id: AssetId(ethereum()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_ETHER) };
let assets = vec![local_fee_asset_on_penpal.clone(), remote_fee_asset_on_ah.clone()];
let xcm = VersionedXcm::from(Xcm(vec![
WithdrawAsset(assets.clone().into()),
PayFees { asset: local_fee_asset_on_penpal.clone() },
InitiateTransfer {
destination: asset_hub(),
remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite(
remote_fee_asset_on_ah.clone().into(),
))),
preserve_origin: true,
assets: BoundedVec::truncate_from(vec![]),
remote_xcm: Xcm(vec![
ClaimAsset {
assets: vec![
Asset { id: AssetId(ethereum()), fun: Fungible(600914043236) },
Asset { id: AssetId(weth_location()), fun: Fungible(TOKEN_AMOUNT) },
]
.into(),
ticket: GeneralIndex(5).into(),
},
RefundSurplus,
DepositAsset {
assets: Wild(All),
beneficiary: AssetHubZagrosReceiver::get().into(),
},
]),
},
]));
assert_ok!(<PenpalB as PenpalBPallet>::PezkuwiXcm::execute(
RuntimeOrigin::signed(PenpalBSender::get()),
bx!(xcm.clone()),
Weight::from(EXECUTION_WEIGHT),
));
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![RuntimeEvent::PezkuwiXcm(pezpallet_xcm::Event::AssetsClaimed { .. }) => {},]
);
});
}
// A malicious user attempted to exploit the bridge by manually adding an AliasOrigin in the
// remoteXcm, successfully routing to the V2 path, but ultimately failing at the BH Exporter.
#[test]
pub fn exploit_v2_route_with_legacy_v1_transfer_will_fail() {
create_pools_on_ah();
fund_on_ah();
let remote_fee_asset =
Asset { id: AssetId(eth_location()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_ETHER) };
let reserve_asset = Asset { id: AssetId(eth_location()), fun: Fungible(TOKEN_AMOUNT) };
let assets = vec![reserve_asset.clone(), remote_fee_asset.clone()];
let custom_xcm_on_dest = Xcm::<()>(vec![
AliasOrigin(Location::parent()),
DepositAsset { assets: Wild(AllCounted(2)), beneficiary: beneficiary() },
]);
assert_ok!(AssetHubZagros::execute_with(|| {
<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::transfer_assets_using_type_and_then(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosSender::get()),
bx!(eth_location().into()),
bx!(assets.into()),
bx!(TransferType::DestinationReserve),
bx!(AssetId(eth_location()).into()),
bx!(TransferType::DestinationReserve),
bx!(VersionedXcm::from(custom_xcm_on_dest)),
Unlimited,
)
}));
BridgeHubZagros::execute_with(|| {
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
// Check that the process failed in MessageQueue
assert_expected_events!(
BridgeHubZagros,
vec![
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed{ success: false, .. }) => {},
]
);
})
}
@@ -0,0 +1,372 @@
// 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::{
imports::*,
tests::{
asset_hub_pezkuwichain_location, bridged_roc_at_ah_zagros, create_foreign_on_ah_zagros,
snowbridge_common::*,
snowbridge_v2_outbound::{EthereumSystemFrontend, EthereumSystemFrontendCall},
},
};
use pezframe_support::traits::fungibles::Mutate;
use xcm::latest::AssetTransferFilter;
// set up pool
pub(crate) fn set_up_pool_with_wnd_on_ah_zagros(
asset: Location,
is_foreign: bool,
initial_fund: u128,
initial_liquidity: u128,
) {
let wnd: Location = Parent.into();
AssetHubZagros::fund_accounts(vec![(AssetHubZagrosSender::get(), initial_fund)]);
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
let owner = AssetHubZagrosSender::get();
let signed_owner = <AssetHubZagros as Chain>::RuntimeOrigin::signed(owner.clone());
if is_foreign {
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::mint(
signed_owner.clone(),
asset.clone().into(),
owner.clone().into(),
initial_fund,
));
} else {
let asset_id = match asset.interior.last() {
Some(GeneralIndex(id)) => *id as u32,
_ => unreachable!(),
};
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::Assets::mint(
signed_owner.clone(),
asset_id.into(),
owner.clone().into(),
initial_fund,
));
}
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::AssetConversion::create_pool(
signed_owner.clone(),
Box::new(wnd.clone()),
Box::new(asset.clone()),
));
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::PoolCreated { .. }) => {},
]
);
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::AssetConversion::add_liquidity(
signed_owner.clone(),
Box::new(wnd),
Box::new(asset),
initial_liquidity,
initial_liquidity,
1,
1,
owner.into()
));
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::LiquidityAdded {..}) => {},
]
);
});
}
pub(crate) fn assert_bridge_hub_pezkuwichain_message_accepted(expected_processed: bool) {
BridgeHubPezkuwichain::execute_with(|| {
type RuntimeEvent = <BridgeHubPezkuwichain as Chain>::RuntimeEvent;
if expected_processed {
assert_expected_events!(
BridgeHubPezkuwichain,
vec![
// pay for bridge fees
RuntimeEvent::Balances(pezpallet_balances::Event::Burned { .. }) => {},
// message exported
RuntimeEvent::BridgeZagrosMessages(
pezpallet_bridge_messages::Event::MessageAccepted { .. }
) => {},
// message processed successfully
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
} else {
assert_expected_events!(
BridgeHubPezkuwichain,
vec![
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed {
success: false,
..
}) => {},
]
);
}
});
}
pub(crate) fn assert_bridge_hub_zagros_message_received() {
BridgeHubZagros::execute_with(|| {
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
BridgeHubZagros,
vec![
// message sent to destination
RuntimeEvent::XcmpQueue(
cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }
) => {},
]
);
})
}
#[test]
fn send_roc_from_asset_hub_pezkuwichain_to_ethereum() {
let initial_fund: u128 = 200_000_000_000_000;
let initial_liquidity: u128 = initial_fund / 2;
let amount: u128 = initial_fund;
let roc_fee_amount: u128 = initial_liquidity / 2;
let wnd_amount_to_swap: u128 = initial_liquidity / 10;
let wnd_fee_amount: u128 = wnd_amount_to_swap / 10;
let ether_fee_amount: u128 = 4_000_000;
let sender = AssetHubPezkuwichainSender::get();
let roc_at_asset_hub_pezkuwichain = roc_at_ah_pezkuwichain();
let bridged_roc_at_asset_hub_zagros = bridged_roc_at_ah_zagros();
create_foreign_on_ah_zagros(
bridged_roc_at_asset_hub_zagros.clone(),
true,
vec![(asset_hub_pezkuwichain_location(), false).into()],
vec![],
);
set_up_pool_with_wnd_on_ah_zagros(
bridged_roc_at_asset_hub_zagros.clone(),
true,
initial_fund,
initial_liquidity,
);
let previous_owner = snowbridge_sovereign();
AssetHubZagros::execute_with(|| {
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::start_destroy(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(previous_owner),
ethereum()
));
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::finish_destroy(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagros::account_id_of(ALICE)),
ethereum()
));
});
create_foreign_on_ah_zagros(ethereum(), true, vec![(ethereum(), false).into()], vec![]);
set_up_pool_with_wnd_on_ah_zagros(ethereum(), true, initial_fund, initial_liquidity);
BridgeHubPezkuwichain::fund_para_sovereign(AssetHubPezkuwichain::para_id(), initial_fund);
AssetHubPezkuwichain::fund_accounts(vec![(AssetHubPezkuwichainSender::get(), initial_fund)]);
fund_on_bh();
register_roc_on_bh();
// set XCM versions
AssetHubPezkuwichain::force_xcm_version(asset_hub_zagros_global_location(), XCM_VERSION);
BridgeHubPezkuwichain::force_xcm_version(bridge_hub_zagros_location(), XCM_VERSION);
// send TYRs, use them for fees
let local_fee_asset: Asset = (roc_at_asset_hub_pezkuwichain.clone(), roc_fee_amount).into();
let remote_fee_on_zagros: Asset =
(roc_at_asset_hub_pezkuwichain.clone(), roc_fee_amount).into();
let assets: Assets = (roc_at_asset_hub_pezkuwichain.clone(), amount).into();
let reserved_asset_on_zagros: Asset =
(roc_at_asset_hub_pezkuwichain.clone(), amount - roc_fee_amount * 2).into();
let reserved_asset_on_zagros_reanchored: Asset =
(bridged_roc_at_asset_hub_zagros.clone(), (amount - roc_fee_amount * 2) / 2).into();
let xcm = VersionedXcm::from(Xcm(vec![
WithdrawAsset(assets.clone().into()),
PayFees { asset: local_fee_asset.clone() },
InitiateTransfer {
destination: asset_hub_zagros_global_location(),
remote_fees: Some(AssetTransferFilter::ReserveDeposit(Definite(
remote_fee_on_zagros.clone().into(),
))),
preserve_origin: true,
assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveDeposit(Definite(
reserved_asset_on_zagros.clone().into(),
))]),
remote_xcm: Xcm(vec![
// swap from roc to wnd
ExchangeAsset {
give: Definite(reserved_asset_on_zagros_reanchored.clone().into()),
want: (Parent, wnd_amount_to_swap).into(),
maximal: true,
},
// swap some wnd to ether
ExchangeAsset {
give: Definite((Parent, ether_fee_amount * 2).into()),
want: (ethereum(), ether_fee_amount).into(),
maximal: true,
},
PayFees { asset: (Parent, wnd_fee_amount).into() },
InitiateTransfer {
destination: ethereum(),
remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite(
Asset { id: AssetId(ethereum()), fun: Fungible(ether_fee_amount) }.into(),
))),
preserve_origin: true,
assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveDeposit(
Definite(reserved_asset_on_zagros_reanchored.clone().into()),
)]),
remote_xcm: Xcm(vec![DepositAsset {
assets: Wild(All),
beneficiary: beneficiary(),
}]),
},
]),
},
]));
let _ = AssetHubPezkuwichain::execute_with(|| {
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::PezkuwiXcm::execute(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(sender),
bx!(xcm),
Weight::from(EXECUTION_WEIGHT),
)
});
assert_bridge_hub_pezkuwichain_message_accepted(true);
assert_bridge_hub_zagros_message_received();
// verify expected events on final destination
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
// message processed successfully
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
});
BridgeHubZagros::execute_with(|| {
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
// Check that the Ethereum message was queue in the Outbound Queue
assert_expected_events!(
BridgeHubZagros,
vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageQueued{ .. }) => {},]
);
});
}
#[test]
fn register_pezkuwichain_asset_on_ethereum_from_rah() {
const XCM_FEE: u128 = 4_000_000_000_000;
let sa_of_rah_on_wah = AssetHubZagros::sovereign_account_of_teyrchain_on_other_global_consensus(
ByGenesis(PEZKUWICHAIN_GENESIS_HASH),
AssetHubPezkuwichain::para_id(),
);
// Pezkuwichain Asset Hub asset when bridged to Zagros Asset Hub.
let bridged_asset_at_wah = Location::new(
2,
[
GlobalConsensus(ByGenesis(PEZKUWICHAIN_GENESIS_HASH)),
Teyrchain(AssetHubPezkuwichain::para_id().into()),
PalletInstance(ASSETS_PALLET_ID),
GeneralIndex(ASSET_ID.into()),
],
);
AssetHubZagros::force_create_foreign_asset(
bridged_asset_at_wah.clone(),
sa_of_rah_on_wah.clone(),
true,
ASSET_MIN_BALANCE,
vec![],
);
let fee_asset = Asset { id: AssetId(ethereum()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_ETHER) };
let call =
EthereumSystemFrontend::EthereumSystemFrontend(EthereumSystemFrontendCall::RegisterToken {
asset_id: Box::new(VersionedLocation::from(bridged_asset_at_wah.clone())),
metadata: Default::default(),
fee_asset,
})
.encode();
let origin_kind = OriginKind::Xcm;
let fee_amount = XCM_FEE;
let fees = (Parent, fee_amount).into();
let xcm = xcm_transact_paid_execution(call.into(), origin_kind, fees, sa_of_rah_on_wah.clone());
// SA-of-RAH-on-WAH needs to have balance to pay for fees and asset creation deposit
AssetHubZagros::execute_with(|| {
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::mint_into(
ethereum().try_into().unwrap(),
&sa_of_rah_on_wah,
INITIAL_FUND,
));
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::Balances::force_set_balance(
<AssetHubZagros as Chain>::RuntimeOrigin::root(),
sa_of_rah_on_wah.into(),
INITIAL_FUND
));
});
let destination = asset_hub_zagros_global_location();
// fund the RAH's SA on RBH for paying bridge delivery fees
BridgeHubPezkuwichain::fund_para_sovereign(
AssetHubPezkuwichain::para_id(),
10_000_000_000_000u128,
);
// set XCM versions
AssetHubPezkuwichain::force_xcm_version(destination.clone(), XCM_VERSION);
BridgeHubPezkuwichain::force_xcm_version(bridge_hub_zagros_location(), XCM_VERSION);
let root_origin = <AssetHubPezkuwichain as Chain>::RuntimeOrigin::root();
AssetHubPezkuwichain::execute_with(|| {
assert_ok!(<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::PezkuwiXcm::send(
root_origin,
bx!(destination.into()),
bx!(xcm),
));
AssetHubPezkuwichain::assert_xcm_pallet_sent();
});
assert_bridge_hub_pezkuwichain_message_accepted(true);
assert_bridge_hub_zagros_message_received();
AssetHubZagros::execute_with(|| {
AssetHubZagros::assert_xcmp_queue_success(None);
});
BridgeHubZagros::execute_with(|| {
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
// Check that the Ethereum message was queue in the Outbound Queue
assert_expected_events!(
BridgeHubZagros,
vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageQueued{ .. }) => {},]
);
});
}
@@ -0,0 +1,152 @@
// 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_common::{eth_location, set_up_eth_and_hez_pool};
use bridge_hub_zagros_runtime::bridge_common_config::{BridgeReward, BridgeRewardBeneficiaries};
use emulated_integration_tests_common::snowbridge::ETHER_MIN_BALANCE;
use pezpallet_bridge_relayers::{Error::FailedToPayReward, RewardLedger};
use crate::imports::*;
const INITIAL_FUND: u128 = 5_000_000_000_000;
//1_000_000_000u128
#[test]
fn claim_rewards_works() {
let assethub_location = BridgeHubZagros::sibling_location_of(AssetHubZagros::para_id());
let assethub_sovereign = BridgeHubZagros::sovereign_account_id_of(assethub_location);
let relayer_account = BridgeHubZagrosSender::get();
let reward_address = AssetHubZagrosReceiver::get();
BridgeHubZagros::fund_accounts(vec![
(assethub_sovereign.clone(), INITIAL_FUND),
(relayer_account.clone(), INITIAL_FUND),
]);
set_up_eth_and_hez_pool();
BridgeHubZagros::execute_with(|| {
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
type RuntimeOrigin = <BridgeHubZagros as Chain>::RuntimeOrigin;
let reward_amount = ETHER_MIN_BALANCE * 2; // Reward should be more than Ether min balance
type BridgeRelayers = <BridgeHubZagros as BridgeHubZagrosPallet>::BridgeRelayers;
BridgeRelayers::register_reward(
(&relayer_account.clone()).into(),
BridgeReward::Snowbridge,
reward_amount,
);
// Check that the reward was registered.
assert_expected_events!(
BridgeHubZagros,
vec![
RuntimeEvent::BridgeRelayers(pezpallet_bridge_relayers::Event::RewardRegistered { relayer, reward_kind, reward_balance }) => {
relayer: *relayer == relayer_account,
reward_kind: *reward_kind == BridgeReward::Snowbridge,
reward_balance: *reward_balance == reward_amount,
},
]
);
let relayer_location = Location::new(
0,
[Junction::AccountId32 { id: reward_address.clone().into(), network: None }],
);
let reward_beneficiary =
BridgeRewardBeneficiaries::AssetHubLocation(VersionedLocation::V5(relayer_location));
let result = BridgeRelayers::claim_rewards_to(
RuntimeOrigin::signed(relayer_account.clone()),
BridgeReward::Snowbridge,
reward_beneficiary.clone(),
);
assert_ok!(result);
assert_expected_events!(
BridgeHubZagros,
vec![
// Check that the pay reward event was emitted on BH
RuntimeEvent::BridgeRelayers(pezpallet_bridge_relayers::Event::RewardPaid { relayer, reward_kind, reward_balance, beneficiary }) => {
relayer: *relayer == relayer_account,
reward_kind: *reward_kind == BridgeReward::Snowbridge,
reward_balance: *reward_balance == reward_amount,
beneficiary: *beneficiary == reward_beneficiary,
},
]
);
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
// Check that the reward was paid on AH
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == eth_location(),
owner: *owner == reward_address.clone().into(),
},
]
);
})
}
#[test]
fn claim_snowbridge_rewards_to_local_account_fails() {
let assethub_location = BridgeHubZagros::sibling_location_of(AssetHubZagros::para_id());
let assethub_sovereign = BridgeHubZagros::sovereign_account_id_of(assethub_location);
let relayer_account = BridgeHubZagrosSender::get();
let reward_address = AssetHubZagrosReceiver::get();
BridgeHubZagros::fund_accounts(vec![
(assethub_sovereign.clone(), INITIAL_FUND),
(relayer_account.clone(), INITIAL_FUND),
]);
set_up_eth_and_hez_pool();
BridgeHubZagros::execute_with(|| {
type Runtime = <BridgeHubZagros as Chain>::Runtime;
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
type RuntimeOrigin = <BridgeHubZagros as Chain>::RuntimeOrigin;
let reward_amount = ETHER_MIN_BALANCE * 2; // Reward should be more than Ether min balance
type BridgeRelayers = <BridgeHubZagros as BridgeHubZagrosPallet>::BridgeRelayers;
BridgeRelayers::register_reward(
&relayer_account.clone(),
BridgeReward::Snowbridge,
reward_amount,
);
// Check that the reward was registered.
assert_expected_events!(
BridgeHubZagros,
vec![
RuntimeEvent::BridgeRelayers(pezpallet_bridge_relayers::Event::RewardRegistered { relayer, reward_kind, reward_balance }) => {
relayer: *relayer == relayer_account,
reward_kind: *reward_kind == BridgeReward::Snowbridge,
reward_balance: *reward_balance == reward_amount,
},
]
);
let reward_beneficiary = BridgeRewardBeneficiaries::LocalAccount(reward_address);
let result = BridgeRelayers::claim_rewards_to(
RuntimeOrigin::signed(relayer_account.clone()),
BridgeReward::Snowbridge,
reward_beneficiary.clone(),
);
assert_err!(result, FailedToPayReward::<Runtime, ()>);
})
}
@@ -0,0 +1,79 @@
// 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::imports::*;
#[test]
fn teleport_via_limited_teleport_assets_to_other_system_teyrchains_works() {
let amount = BRIDGE_HUB_ZAGROS_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
BridgeHubZagros, // Origin
vec![AssetHubZagros], // Destinations
(native_asset, amount),
fee_asset_id,
limited_teleport_assets
);
}
#[test]
fn teleport_via_transfer_assets_to_other_system_teyrchains_works() {
let amount = BRIDGE_HUB_ZAGROS_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
BridgeHubZagros, // Origin
vec![AssetHubZagros], // Destinations
(native_asset, amount),
fee_asset_id,
transfer_assets
);
}
#[test]
fn teleport_via_limited_teleport_assets_from_and_to_relay() {
let amount = ZAGROS_ED * 100;
test_relay_is_trusted_teleporter!(
Zagros,
vec![BridgeHubZagros],
amount,
limited_teleport_assets
);
test_teyrchain_is_trusted_teleporter_for_relay!(
BridgeHubZagros,
Zagros,
amount,
limited_teleport_assets
);
}
#[test]
fn teleport_via_transfer_assets_from_and_to_relay() {
let amount = ZAGROS_ED * 100;
test_relay_is_trusted_teleporter!(Zagros, vec![BridgeHubZagros], amount, transfer_assets);
test_teyrchain_is_trusted_teleporter_for_relay!(
BridgeHubZagros,
Zagros,
amount,
transfer_assets
);
}
@@ -0,0 +1,248 @@
// 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_common::snowbridge_sovereign, *};
use pezsp_core::Get;
use xcm::latest::AssetTransferFilter;
const ETHEREUM_BOB: [u8; 20] = hex_literal::hex!("11b0b11000011b0b11000011b0b11000011b0b11");
/// Bob on Ethereum transacts on PenpalB, paying fees using WETH. XCM has to go through Asset Hub
/// as the reserve location of WETH. The original origin `Ethereum/Bob` is proxied by Asset Hub.
///
/// This particular test is not testing snowbridge, but only Bridge Hub, so the tested XCM flow from
/// Ethereum starts from Bridge Hub.
// TODO(https://github.com/pezkuwichain/pezkuwi-sdk/issues/149): Once Snowbridge supports Transact, start the flow from Ethereum and test completely e2e.
fn transfer_and_transact_in_same_xcm(
sender: Location,
weth: Asset,
destination: Location,
beneficiary: Location,
call: xcm::DoubleEncoded<()>,
) {
let signed_origin = <BridgeHubZagros as Chain>::RuntimeOrigin::root();
let context: InteriorLocation = [
GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)),
Teyrchain(<BridgeHubZagros as Para>::TeyrchainInfo::get().into()),
]
.into();
let asset_hub_location = BridgeHubZagros::sibling_location_of(AssetHubZagros::para_id());
// TODO(https://github.com/pezkuwichain/pezkuwi-sdk/issues/147): dry-run to get local fees, for now use hardcoded value.
let ah_fees_amount = 90_000_000_000u128; // current exact value 79_948_099_299
let fees_for_ah: Asset = (weth.id.clone(), ah_fees_amount).into();
// xcm to be executed at dest
let xcm_on_dest = Xcm(vec![
Transact { origin_kind: OriginKind::Xcm, call, fallback_max_weight: None },
ExpectTransactStatus(MaybeErrorCode::Success),
// since this is the last hop, we don't need to further use any assets previously
// reserved for fees (there are no further hops to cover delivery fees for); we
// RefundSurplus to get back any unspent fees
RefundSurplus,
DepositAsset { assets: Wild(All), beneficiary },
]);
let destination = destination.reanchored(&asset_hub_location, &context).unwrap();
let xcm_to_ah = Xcm::<()>(vec![
UnpaidExecution { check_origin: None, weight_limit: Unlimited },
DescendOrigin([PalletInstance(80)].into()), // snowbridge pallet
UniversalOrigin(GlobalConsensus(Ethereum { chain_id: SEPOLIA_ID })),
ReserveAssetDeposited(weth.clone().into()),
AliasOrigin(sender),
PayFees { asset: fees_for_ah },
InitiateTransfer {
destination,
// on the last hop we can just put everything in fees and `RefundSurplus` to get any
// unused back
remote_fees: Some(AssetTransferFilter::ReserveDeposit(Wild(All))),
preserve_origin: true,
assets: BoundedVec::new(),
remote_xcm: xcm_on_dest,
},
]);
<BridgeHubZagros as BridgeHubZagrosPallet>::PezkuwiXcm::send(
signed_origin,
bx!(asset_hub_location.into()),
bx!(xcm::VersionedXcm::from(xcm_to_ah.into())),
)
.unwrap();
}
/// Bob on Ethereum transacts on PenpalB, paying fees using WETH. XCM has to go through Asset Hub
/// as the reserve location of WETH. The original origin `Ethereum/Bob` is proxied by Asset Hub.
///
/// This particular test is not testing snowbridge, but only Bridge Hub, so the tested XCM flow from
/// Ethereum starts from Bridge Hub.
// TODO(https://github.com/pezkuwichain/pezkuwi-sdk/issues/149): Once Snowbridge supports Transact, start the flow from Ethereum and test completely e2e.
#[test]
fn transact_from_ethereum_to_penpalb_through_asset_hub() {
// Snowbridge doesn't support transact yet, we are emulating it by sending one from Bridge Hub
// as if it comes from Snowbridge.
let destination = BridgeHubZagros::sibling_location_of(PenpalB::para_id());
let sender = Location::new(
2,
[
GlobalConsensus(Ethereum { chain_id: SEPOLIA_ID }),
AccountKey20 { network: None, key: ETHEREUM_BOB },
],
);
let bridged_weth = weth_at_asset_hubs();
PenpalB::force_create_foreign_asset(
bridged_weth.clone(),
PenpalAssetOwner::get(),
true,
ASSET_MIN_BALANCE,
vec![],
);
// Configure source Penpal chain to trust local AH as reserve of bridged WETH
PenpalB::execute_with(|| {
assert_ok!(<PenpalB as Chain>::System::set_storage(
<PenpalB as Chain>::RuntimeOrigin::root(),
vec![(
PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(),
bridged_weth.encode(),
)],
));
});
let fee_amount_to_send: teyrchains_common::Balance = ASSET_HUB_ZAGROS_ED * 10000;
let sender_chain_as_seen_by_asset_hub =
Location::new(2, [GlobalConsensus(Ethereum { chain_id: SEPOLIA_ID })]);
let sov_of_sender_on_asset_hub = AssetHubZagros::execute_with(|| {
AssetHubZagros::sovereign_account_id_of(sender_chain_as_seen_by_asset_hub)
});
let receiver_as_seen_by_asset_hub = AssetHubZagros::sibling_location_of(PenpalB::para_id());
let sov_of_receiver_on_asset_hub = AssetHubZagros::execute_with(|| {
AssetHubZagros::sovereign_account_id_of(receiver_as_seen_by_asset_hub)
});
// Create SAs of sender and receiver on AHW with ED.
AssetHubZagros::fund_accounts(vec![
(sov_of_sender_on_asset_hub.clone().into(), ASSET_HUB_ZAGROS_ED),
(sov_of_receiver_on_asset_hub.clone().into(), ASSET_HUB_ZAGROS_ED),
(snowbridge_sovereign().into(), 10_000_000_000_00),
]);
// We create a pool between ZGR and WETH in AssetHub to support paying for fees with WETH.
let snowbridge_sovereign = snowbridge_sovereign();
create_pool_with_native_on!(
AssetHubZagros,
bridged_weth.clone(),
true,
snowbridge_sovereign,
1_000_000_000_000,
20_000_000_000
);
// We also need a pool between ZGR and WETH on PenpalB to support paying for fees with WETH.
create_pool_with_native_on!(
PenpalB,
bridged_weth.clone(),
true,
PenpalAssetOwner::get(),
1_000_000_000_000,
20_000_000_000
);
// Init values for Teyrchain Destination
let receiver = PenpalBReceiver::get();
// Query initial balances
let receiver_assets_before = PenpalB::execute_with(|| {
type Assets = <PenpalB as PenpalBPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(bridged_weth.clone(), &receiver)
});
// Now register a new asset on PenpalB from Ethereum/Bob account while paying fees using WETH
// (going through Asset Hub)
let weth_to_send: Asset = (bridged_weth.clone(), fee_amount_to_send).into();
// Silly example of a Transact: Bob creates his own foreign assset on PenpalB based on his
// Ethereum address
let foreign_asset_at_penpal_b = Location::new(
2,
[
GlobalConsensus(Ethereum { chain_id: SEPOLIA_ID }),
AccountKey20 { network: None, key: ETHEREUM_BOB },
],
);
// Encoded `create_asset` call to be executed in PenpalB
let call = PenpalB::create_foreign_asset_call(
foreign_asset_at_penpal_b.clone(),
ASSET_MIN_BALANCE,
receiver.clone(),
);
BridgeHubZagros::execute_with(|| {
// initiate transaction
transfer_and_transact_in_same_xcm(
sender.clone(),
weth_to_send,
destination,
receiver.clone().into(),
call,
);
});
AssetHubZagros::execute_with(|| {
asset_hub_hop_assertions();
});
PenpalB::execute_with(|| {
let expected_creator = PenpalB::sovereign_account_id_of(sender);
penpal_b_assertions(foreign_asset_at_penpal_b, expected_creator, receiver.clone());
});
// Query final balances
let receiver_assets_after = PenpalB::execute_with(|| {
type Assets = <PenpalB as PenpalBPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(bridged_weth, &receiver)
});
// Receiver's balance is increased
assert!(receiver_assets_after > receiver_assets_before);
}
fn asset_hub_hop_assertions() {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
// Deposited to receiver teyrchain SA
RuntimeEvent::ForeignAssets(
pezpallet_assets::Event::Deposited { .. }
) => {},
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
}
fn penpal_b_assertions(
expected_asset: Location,
expected_creator: AccountId,
expected_owner: AccountId,
) {
type RuntimeEvent = <PenpalB as Chain>::RuntimeEvent;
PenpalB::assert_xcmp_queue_success(None);
assert_expected_events!(
PenpalB,
vec![
RuntimeEvent::ForeignAssets(
pezpallet_assets::Event::Created { asset_id, creator, owner }
) => {
asset_id: *asset_id == expected_asset,
creator: *creator == expected_creator,
owner: *owner == expected_owner,
},
]
);
}