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,68 @@
[package]
name = "asset-hub-pezkuwichain-integration-tests"
version = "1.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
description = "Asset Hub Pezkuwichain runtime integration tests with xcm-emulator"
publish = false
[lints]
workspace = true
[dependencies]
assert_matches = { workspace = true }
codec = { workspace = true }
# Bizinikiwi
pezframe-support = { workspace = true }
pezframe-system = { workspace = true }
pezpallet-asset-conversion = { workspace = true }
pezpallet-asset-rewards = { workspace = true }
pezpallet-assets = { workspace = true }
pezpallet-balances = { workspace = true }
pezpallet-message-queue = { workspace = true }
pezpallet-treasury = { workspace = true }
pezpallet-utility = { workspace = true }
pezsp-core = { workspace = true }
pezsp-runtime = { workspace = true }
# Pezkuwi
pezpallet-xcm = { workspace = true }
pezkuwi-runtime-common = { workspace = true, default-features = true }
pezkuwichain-runtime-constants = { workspace = true, default-features = true }
xcm = { workspace = true }
xcm-executor = { workspace = true }
xcm-runtime-apis = { workspace = true, default-features = true }
# Pezcumulus
asset-test-utils = { workspace = true, default-features = true }
pezcumulus-pezpallet-teyrchain-system = { workspace = true }
emulated-integration-tests-common = { workspace = true }
pezkuwichain-system-emulated-network = { workspace = true }
teyrchains-common = { workspace = true, default-features = true }
[features]
runtime-benchmarks = [
"asset-test-utils/runtime-benchmarks",
"pezcumulus-pezpallet-teyrchain-system/runtime-benchmarks",
"emulated-integration-tests-common/runtime-benchmarks",
"pezframe-support/runtime-benchmarks",
"pezframe-system/runtime-benchmarks",
"pezpallet-asset-conversion/runtime-benchmarks",
"pezpallet-asset-rewards/runtime-benchmarks",
"pezpallet-assets/runtime-benchmarks",
"pezpallet-balances/runtime-benchmarks",
"pezpallet-message-queue/runtime-benchmarks",
"pezpallet-treasury/runtime-benchmarks",
"pezpallet-utility/runtime-benchmarks",
"pezpallet-xcm/runtime-benchmarks",
"pezkuwi-runtime-common/runtime-benchmarks",
"pezkuwichain-runtime-constants/runtime-benchmarks",
"pezkuwichain-system-emulated-network/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"teyrchains-common/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
"xcm-runtime-apis/runtime-benchmarks",
"xcm/runtime-benchmarks",
]
@@ -0,0 +1,111 @@
// 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 {
pub(crate) use codec::Encode;
// Bizinikiwi
pub(crate) use pezframe_support::{
assert_err, assert_ok,
pezpallet_prelude::Weight,
pezsp_runtime::{DispatchError, DispatchResult, ModuleError},
traits::fungibles::Inspect,
};
// Pezkuwi
pub(crate) use xcm::{
latest::{PEZKUWICHAIN_GENESIS_HASH, ZAGROS_GENESIS_HASH},
prelude::{AccountId32 as AccountId32Junction, *},
};
pub(crate) use xcm_executor::traits::TransferType;
// Pezcumulus
pub(crate) use asset_test_utils::xcm_helpers;
pub(crate) use emulated_integration_tests_common::{
accounts::DUMMY_EMPTY,
test_relay_is_trusted_teleporter, test_teyrchain_is_trusted_teleporter,
test_teyrchain_is_trusted_teleporter_for_relay,
test_xcm_fee_querying_apis_work_for_asset_hub,
xcm_emulator::{
assert_expected_events, bx, Chain, RelayChain as Relay, Test, TestArgs, TestContext,
TestExt, Teyrchain as Para,
},
xcm_helpers::{
fee_asset, get_amount_from_versioned_assets, non_fee_asset, xcm_transact_paid_execution,
},
PenpalATeleportableAssetLocation, ASSETS_PALLET_ID, RESERVABLE_ASSET_ID, XCM_V3,
};
pub(crate) use pezkuwichain_system_emulated_network::{
asset_hub_pezkuwichain_emulated_chain::{
asset_hub_pezkuwichain_runtime::{
self,
xcm_config::{
self as ahr_xcm_config, TokenLocation as RelayLocation, TreasuryAccount,
XcmConfig as AssetHubPezkuwichainXcmConfig,
},
AssetConversionOrigin as AssetHubPezkuwichainAssetConversionOrigin,
ExistentialDeposit as AssetHubPezkuwichainExistentialDeposit,
},
genesis::{AssetHubPezkuwichainAssetOwner, ED as ASSET_HUB_PEZKUWICHAIN_ED},
AssetHubPezkuwichainParaPallet as AssetHubPezkuwichainPallet,
},
penpal_emulated_chain::{
penpal_runtime::xcm_config::{
CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub,
LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub,
LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub,
UsdtFromAssetHub as PenpalUsdtFromAssetHub,
},
PenpalAParaPallet as PenpalAPallet, PenpalAssetOwner,
PenpalBParaPallet as PenpalBPallet, ED as PENPAL_ED,
},
pezkuwichain_emulated_chain::{
genesis::ED as PEZKUWICHAIN_ED,
pezkuwichain_runtime::{
governance as pezkuwichain_governance,
governance::pezpallet_custom_origins::Origin::Treasurer,
xcm_config::UniversalLocation as PezkuwichainUniversalLocation, Dmp,
OriginCaller as PezkuwichainOriginCaller,
},
PezkuwichainRelayPallet as PezkuwichainPallet,
},
AssetHubPezkuwichainPara as AssetHubPezkuwichain,
AssetHubPezkuwichainParaReceiver as AssetHubPezkuwichainReceiver,
AssetHubPezkuwichainParaSender as AssetHubPezkuwichainSender,
BridgeHubPezkuwichainPara as BridgeHubPezkuwichain,
BridgeHubPezkuwichainParaReceiver as BridgeHubPezkuwichainReceiver, PenpalAPara as PenpalA,
PenpalAParaReceiver as PenpalAReceiver, PenpalAParaSender as PenpalASender,
PenpalBPara as PenpalB, PenpalBParaReceiver as PenpalBReceiver,
PezkuwichainRelay as Pezkuwichain, PezkuwichainRelayReceiver as PezkuwichainReceiver,
PezkuwichainRelaySender as PezkuwichainSender,
};
pub(crate) use teyrchains_common::Balance;
pub(crate) const ASSET_ID: u32 = 3;
pub(crate) const ASSET_MIN_BALANCE: u128 = 1000;
pub(crate) type RelayToParaTest = Test<Pezkuwichain, PenpalA>;
pub(crate) type ParaToRelayTest = Test<PenpalA, Pezkuwichain>;
pub(crate) type SystemParaToRelayTest = Test<AssetHubPezkuwichain, Pezkuwichain>;
pub(crate) type SystemParaToParaTest = Test<AssetHubPezkuwichain, PenpalA>;
pub(crate) type ParaToSystemParaTest = Test<PenpalA, AssetHubPezkuwichain>;
pub(crate) type ParaToParaThroughRelayTest = Test<PenpalA, PenpalB, Pezkuwichain>;
pub(crate) type ParaToParaThroughAHTest = Test<PenpalA, PenpalB, AssetHubPezkuwichain>;
pub(crate) type RelayToParaThroughAHTest = Test<Pezkuwichain, PenpalA, AssetHubPezkuwichain>;
}
#[cfg(test)]
mod tests;
@@ -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 = AssetHubPezkuwichainExistentialDeposit::get();
let assets: Assets = (Parent, amount).into();
test_chain_can_claim_assets!(
AssetHubPezkuwichain,
RuntimeCall,
NetworkId::ByGenesis(PEZKUWICHAIN_GENESIS_HASH),
assets,
amount
);
}
@@ -0,0 +1,868 @@
// 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 super::reserve_transfer::*;
use crate::{
imports::*,
tests::teleport::do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt,
};
fn para_to_para_assethub_hop_assertions(t: ParaToParaThroughAHTest) {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
let sov_penpal_a_on_ah = AssetHubPezkuwichain::sovereign_account_id_of(
AssetHubPezkuwichain::sibling_location_of(PenpalA::para_id()),
);
let sov_penpal_b_on_ah = AssetHubPezkuwichain::sovereign_account_id_of(
AssetHubPezkuwichain::sibling_location_of(PenpalB::para_id()),
);
assert_expected_events!(
AssetHubPezkuwichain,
vec![
// Withdrawn from sender teyrchain SA
RuntimeEvent::Balances(
pezpallet_balances::Event::Burned { who, amount }
) => {
who: *who == sov_penpal_a_on_ah,
amount: *amount == t.args.amount,
},
// Deposited to receiver teyrchain SA
RuntimeEvent::Balances(
pezpallet_balances::Event::Minted { who, .. }
) => {
who: *who == sov_penpal_b_on_ah,
},
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
}
fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult {
let fee: Asset = t
.args
.assets
.inner()
.iter()
.find(|a| a.id == t.args.fee_asset_id)
.cloned()
.unwrap();
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
assets: Wild(AllCounted(t.args.assets.len() as u32)),
beneficiary: t.args.beneficiary,
}]);
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::PezkuwiXcm::transfer_assets_using_type_and_then(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.assets.into()),
bx!(TransferType::LocalReserve),
bx!(fee.id.into()),
bx!(TransferType::LocalReserve),
bx!(VersionedXcm::from(custom_xcm_on_dest)),
t.args.weight_limit,
)
}
fn para_to_ah_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult {
let fee: Asset = t
.args
.assets
.inner()
.iter()
.find(|a| a.id == t.args.fee_asset_id)
.cloned()
.unwrap();
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
assets: Wild(AllCounted(t.args.assets.len() as u32)),
beneficiary: t.args.beneficiary,
}]);
<PenpalA as PenpalAPallet>::PezkuwiXcm::transfer_assets_using_type_and_then(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.assets.into()),
bx!(TransferType::DestinationReserve),
bx!(fee.id.into()),
bx!(TransferType::DestinationReserve),
bx!(VersionedXcm::from(custom_xcm_on_dest)),
t.args.weight_limit,
)
}
fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> DispatchResult {
let fee: Asset = t
.args
.assets
.inner()
.iter()
.find(|a| a.id == t.args.fee_asset_id)
.cloned()
.unwrap();
let asset_hub_location: Location =
PenpalA::sibling_location_of(AssetHubPezkuwichain::para_id());
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
assets: Wild(AllCounted(t.args.assets.len() as u32)),
beneficiary: t.args.beneficiary,
}]);
<PenpalA as PenpalAPallet>::PezkuwiXcm::transfer_assets_using_type_and_then(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.assets.into()),
bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())),
bx!(fee.id.into()),
bx!(TransferType::RemoteReserve(asset_hub_location.into())),
bx!(VersionedXcm::from(custom_xcm_on_dest)),
t.args.weight_limit,
)
}
fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> DispatchResult {
let fee: Asset = t
.args
.assets
.inner()
.iter()
.find(|a| a.id == t.args.fee_asset_id)
.cloned()
.unwrap();
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
assets: Wild(AllCounted(t.args.assets.len() as u32)),
beneficiary: t.args.beneficiary,
}]);
<PenpalA as PenpalAPallet>::PezkuwiXcm::transfer_assets_using_type_and_then(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.assets.into()),
bx!(TransferType::Teleport),
bx!(fee.id.into()),
bx!(TransferType::DestinationReserve),
bx!(VersionedXcm::from(custom_xcm_on_dest)),
t.args.weight_limit,
)
}
fn asset_hub_to_para_teleport_foreign_assets(t: SystemParaToParaTest) -> DispatchResult {
let fee: Asset = t
.args
.assets
.inner()
.iter()
.find(|a| a.id == t.args.fee_asset_id)
.cloned()
.unwrap();
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
assets: Wild(AllCounted(t.args.assets.len() as u32)),
beneficiary: t.args.beneficiary,
}]);
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::PezkuwiXcm::transfer_assets_using_type_and_then(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.assets.into()),
bx!(TransferType::Teleport),
bx!(fee.id.into()),
bx!(TransferType::LocalReserve),
bx!(VersionedXcm::from(custom_xcm_on_dest)),
t.args.weight_limit,
)
}
// ===========================================================================
// ======= Transfer - Native + Bridged Assets - AssetHub->Teyrchain ==========
// ===========================================================================
/// Transfers of native asset plus bridged asset from AssetHub to some Teyrchain
/// while paying fees using native asset.
#[test]
fn transfer_foreign_assets_from_asset_hub_to_para() {
let destination = AssetHubPezkuwichain::sibling_location_of(PenpalA::para_id());
let sender = AssetHubPezkuwichainSender::get();
let native_amount_to_send: Balance = ASSET_HUB_PEZKUWICHAIN_ED * 10000;
let native_asset_location = RelayLocation::get();
let receiver = PenpalAReceiver::get();
let assets_owner = PenpalAssetOwner::get();
// Foreign asset used: bridged ZGR
let foreign_amount_to_send = ASSET_HUB_PEZKUWICHAIN_ED * 10_000_000;
let wnd_at_pezkuwichain_teyrchains =
Location::new(2, [Junction::GlobalConsensus(NetworkId::ByGenesis(ZAGROS_GENESIS_HASH))]);
// Configure destination chain to trust AH as reserve of ZGR
PenpalA::execute_with(|| {
assert_ok!(<PenpalA as Chain>::System::set_storage(
<PenpalA as Chain>::RuntimeOrigin::root(),
vec![(
PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(),
Location::new(2, [GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH))]).encode(),
)],
));
});
PenpalA::force_create_foreign_asset(
wnd_at_pezkuwichain_teyrchains.clone(),
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
AssetHubPezkuwichain::force_create_foreign_asset(
wnd_at_pezkuwichain_teyrchains.clone().try_into().unwrap(),
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
AssetHubPezkuwichain::mint_foreign_asset(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(assets_owner),
wnd_at_pezkuwichain_teyrchains.clone().try_into().unwrap(),
sender.clone(),
foreign_amount_to_send * 2,
);
// Assets to send
let assets: Vec<Asset> = vec![
(Parent, native_amount_to_send).into(),
(wnd_at_pezkuwichain_teyrchains.clone(), foreign_amount_to_send).into(),
];
let fee_asset_id = AssetId(Parent.into());
// Init Test
let test_args = TestContext {
sender: sender.clone(),
receiver: receiver.clone(),
args: TestArgs::new_para(
destination.clone(),
receiver.clone(),
native_amount_to_send,
assets.into(),
None,
fee_asset_id,
),
};
let mut test = SystemParaToParaTest::new(test_args);
// Query initial balances
let sender_balance_before = test.sender.balance;
let sender_wnds_before = AssetHubPezkuwichain::execute_with(|| {
type ForeignAssets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(
wnd_at_pezkuwichain_teyrchains.clone().try_into().unwrap(),
&sender,
)
});
let receiver_assets_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(native_asset_location.clone(), &receiver)
});
let receiver_wnds_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_pezkuwichain_teyrchains.clone(), &receiver)
});
// Set assertions and dispatchables
test.set_assertion::<AssetHubPezkuwichain>(system_para_to_para_sender_assertions);
test.set_assertion::<PenpalA>(system_para_to_para_receiver_assertions);
test.set_dispatchable::<AssetHubPezkuwichain>(ah_to_para_transfer_assets);
test.assert();
// Query final balances
let sender_balance_after = test.sender.balance;
let sender_wnds_after = AssetHubPezkuwichain::execute_with(|| {
type ForeignAssets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(
wnd_at_pezkuwichain_teyrchains.clone().try_into().unwrap(),
&sender,
)
});
let receiver_assets_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(native_asset_location, &receiver)
});
let receiver_wnds_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_pezkuwichain_teyrchains, &receiver)
});
// Sender's balance is reduced by amount sent plus delivery fees
assert!(sender_balance_after < sender_balance_before - native_amount_to_send);
// Sender's balance is reduced by foreign amount sent
assert_eq!(sender_wnds_after, sender_wnds_before - foreign_amount_to_send);
// Receiver's assets is increased
assert!(receiver_assets_after > receiver_assets_before);
// Receiver's assets increased by `amount_to_send - delivery_fees - bought_execution`;
// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
// should be non-zero
assert!(receiver_assets_after < receiver_assets_before + native_amount_to_send);
// Receiver's balance is increased by foreign amount sent
assert_eq!(receiver_wnds_after, receiver_wnds_before + foreign_amount_to_send);
}
/// Reserve Transfers of native asset from Teyrchain to System Teyrchain should work
// ===========================================================================
// ======= Transfer - Native + Bridged Assets - Teyrchain->AssetHub ==========
// ===========================================================================
/// Transfers of native asset plus bridged asset from some Teyrchain to AssetHub
/// while paying fees using native asset.
#[test]
fn transfer_foreign_assets_from_para_to_asset_hub() {
// Init values for Teyrchain
let destination = PenpalA::sibling_location_of(AssetHubPezkuwichain::para_id());
let sender = PenpalASender::get();
let native_amount_to_send: Balance = ASSET_HUB_PEZKUWICHAIN_ED * 10000;
let native_asset_location = RelayLocation::get();
let assets_owner = PenpalAssetOwner::get();
// Foreign asset used: bridged ZGR
let foreign_amount_to_send = ASSET_HUB_PEZKUWICHAIN_ED * 10_000_000;
let wnd_at_pezkuwichain_teyrchains =
Location::new(2, [Junction::GlobalConsensus(NetworkId::ByGenesis(ZAGROS_GENESIS_HASH))]);
// Configure destination chain to trust AH as reserve of ZGR
PenpalA::execute_with(|| {
assert_ok!(<PenpalA as Chain>::System::set_storage(
<PenpalA as Chain>::RuntimeOrigin::root(),
vec![(
PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(),
Location::new(2, [GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH))]).encode(),
)],
));
});
PenpalA::force_create_foreign_asset(
wnd_at_pezkuwichain_teyrchains.clone(),
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
AssetHubPezkuwichain::force_create_foreign_asset(
wnd_at_pezkuwichain_teyrchains.clone().try_into().unwrap(),
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
// fund Teyrchain's sender account
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
native_asset_location.clone(),
sender.clone(),
native_amount_to_send * 2,
);
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
wnd_at_pezkuwichain_teyrchains.clone(),
sender.clone(),
foreign_amount_to_send * 2,
);
// Init values for System Teyrchain
let receiver = AssetHubPezkuwichainReceiver::get();
let penpal_location_as_seen_by_ahr =
AssetHubPezkuwichain::sibling_location_of(PenpalA::para_id());
let sov_penpal_on_ahr =
AssetHubPezkuwichain::sovereign_account_id_of(penpal_location_as_seen_by_ahr);
// fund Teyrchain's SA on AssetHub with the assets held in reserve
AssetHubPezkuwichain::fund_accounts(vec![(
sov_penpal_on_ahr.clone().into(),
native_amount_to_send * 2,
)]);
AssetHubPezkuwichain::mint_foreign_asset(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(assets_owner),
wnd_at_pezkuwichain_teyrchains.clone().try_into().unwrap(),
sov_penpal_on_ahr,
foreign_amount_to_send * 2,
);
// Assets to send
let assets: Vec<Asset> = vec![
(Parent, native_amount_to_send).into(),
(wnd_at_pezkuwichain_teyrchains.clone(), foreign_amount_to_send).into(),
];
let fee_asset_id = AssetId(Parent.into());
// Init Test
let test_args = TestContext {
sender: sender.clone(),
receiver: receiver.clone(),
args: TestArgs::new_para(
destination.clone(),
receiver.clone(),
native_amount_to_send,
assets.into(),
None,
fee_asset_id,
),
};
let mut test = ParaToSystemParaTest::new(test_args);
// Query initial balances
let sender_native_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(native_asset_location.clone(), &sender)
});
let sender_wnds_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_pezkuwichain_teyrchains.clone(), &sender)
});
let receiver_native_before = test.receiver.balance;
let receiver_wnds_before = AssetHubPezkuwichain::execute_with(|| {
type ForeignAssets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(
wnd_at_pezkuwichain_teyrchains.clone().try_into().unwrap(),
&receiver,
)
});
// Set assertions and dispatchables
test.set_assertion::<PenpalA>(para_to_system_para_sender_assertions);
test.set_assertion::<AssetHubPezkuwichain>(para_to_system_para_receiver_assertions);
test.set_dispatchable::<PenpalA>(para_to_ah_transfer_assets);
test.assert();
// Query final balances
let sender_native_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(native_asset_location, &sender)
});
let sender_wnds_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_pezkuwichain_teyrchains.clone(), &sender)
});
let receiver_native_after = test.receiver.balance;
let receiver_wnds_after = AssetHubPezkuwichain::execute_with(|| {
type ForeignAssets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(
wnd_at_pezkuwichain_teyrchains.try_into().unwrap(),
&receiver,
)
});
// Sender's balance is reduced by amount sent plus delivery fees
assert!(sender_native_after < sender_native_before - native_amount_to_send);
// Sender's balance is reduced by foreign amount sent
assert_eq!(sender_wnds_after, sender_wnds_before - foreign_amount_to_send);
// Receiver's balance is increased
assert!(receiver_native_after > receiver_native_before);
// Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`;
// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
// should be non-zero
assert!(receiver_native_after < receiver_native_before + native_amount_to_send);
// Receiver's balance is increased by foreign amount sent
assert_eq!(receiver_wnds_after, receiver_wnds_before + foreign_amount_to_send);
}
// ==============================================================================
// ===== Transfer - Native + Bridged Assets - Teyrchain->AssetHub->Teyrchain ====
// ==============================================================================
/// Transfers of native asset plus bridged asset from Teyrchain to Teyrchain
/// (through AssetHub reserve) with fees paid using native asset.
#[test]
fn transfer_foreign_assets_from_para_to_para_through_asset_hub() {
// Init values for Teyrchain Origin
let destination = PenpalA::sibling_location_of(PenpalB::para_id());
let sender = PenpalASender::get();
let roc_to_send: Balance = PEZKUWICHAIN_ED * 10000;
let assets_owner = PenpalAssetOwner::get();
let roc_location = RelayLocation::get();
let sender_as_seen_by_ah = AssetHubPezkuwichain::sibling_location_of(PenpalA::para_id());
let sov_of_sender_on_ah = AssetHubPezkuwichain::sovereign_account_id_of(sender_as_seen_by_ah);
let receiver_as_seen_by_ah = AssetHubPezkuwichain::sibling_location_of(PenpalB::para_id());
let sov_of_receiver_on_ah =
AssetHubPezkuwichain::sovereign_account_id_of(receiver_as_seen_by_ah);
let wnd_to_send = ASSET_HUB_PEZKUWICHAIN_ED * 10_000_000;
// Configure source and destination chains to trust AH as reserve of ZGR
PenpalA::execute_with(|| {
assert_ok!(<PenpalA as Chain>::System::set_storage(
<PenpalA as Chain>::RuntimeOrigin::root(),
vec![(
PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(),
Location::new(2, [GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH))]).encode(),
)],
));
});
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(ByGenesis(ZAGROS_GENESIS_HASH))]).encode(),
)],
));
});
// Register ZGR as foreign asset and transfer it around the Pezkuwichain ecosystem
let wnd_at_pezkuwichain_teyrchains =
Location::new(2, [Junction::GlobalConsensus(NetworkId::ByGenesis(ZAGROS_GENESIS_HASH))]);
AssetHubPezkuwichain::force_create_foreign_asset(
wnd_at_pezkuwichain_teyrchains.clone().try_into().unwrap(),
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
PenpalA::force_create_foreign_asset(
wnd_at_pezkuwichain_teyrchains.clone(),
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
PenpalB::force_create_foreign_asset(
wnd_at_pezkuwichain_teyrchains.clone(),
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
// fund Teyrchain's sender account
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
roc_location.clone(),
sender.clone(),
roc_to_send * 2,
);
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
wnd_at_pezkuwichain_teyrchains.clone(),
sender.clone(),
wnd_to_send * 2,
);
// fund the Teyrchain Origin's SA on Asset Hub with the assets held in reserve
AssetHubPezkuwichain::fund_accounts(vec![(
sov_of_sender_on_ah.clone().into(),
roc_to_send * 2,
)]);
AssetHubPezkuwichain::mint_foreign_asset(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(assets_owner),
wnd_at_pezkuwichain_teyrchains.clone().try_into().unwrap(),
sov_of_sender_on_ah.clone(),
wnd_to_send * 2,
);
// Init values for Teyrchain Destination
let receiver = PenpalBReceiver::get();
// Assets to send
let assets: Vec<Asset> = vec![
(roc_location.clone(), roc_to_send).into(),
(wnd_at_pezkuwichain_teyrchains.clone(), wnd_to_send).into(),
];
let fee_asset_id: AssetId = roc_location.clone().into();
// Init Test
let test_args = TestContext {
sender: sender.clone(),
receiver: receiver.clone(),
args: TestArgs::new_para(
destination,
receiver.clone(),
roc_to_send,
assets.into(),
None,
fee_asset_id,
),
};
let mut test = ParaToParaThroughAHTest::new(test_args);
// Query initial balances
let sender_rocs_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_location.clone(), &sender)
});
let sender_wnds_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_pezkuwichain_teyrchains.clone(), &sender)
});
let rocs_in_sender_reserve_on_ahr_before =
<AssetHubPezkuwichain as Chain>::account_data_of(sov_of_sender_on_ah.clone()).free;
let wnds_in_sender_reserve_on_ahr_before = AssetHubPezkuwichain::execute_with(|| {
type Assets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(
wnd_at_pezkuwichain_teyrchains.clone().try_into().unwrap(),
&sov_of_sender_on_ah,
)
});
let rocs_in_receiver_reserve_on_ahr_before =
<AssetHubPezkuwichain as Chain>::account_data_of(sov_of_receiver_on_ah.clone()).free;
let wnds_in_receiver_reserve_on_ahr_before = AssetHubPezkuwichain::execute_with(|| {
type Assets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(
wnd_at_pezkuwichain_teyrchains.clone().try_into().unwrap(),
&sov_of_receiver_on_ah,
)
});
let receiver_rocs_before = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_location.clone(), &receiver)
});
let receiver_wnds_before = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_pezkuwichain_teyrchains.clone(), &receiver)
});
// Set assertions and dispatchables
test.set_assertion::<PenpalA>(para_to_para_through_hop_sender_assertions);
test.set_assertion::<AssetHubPezkuwichain>(para_to_para_assethub_hop_assertions);
test.set_assertion::<PenpalB>(para_to_para_through_hop_receiver_assertions);
test.set_dispatchable::<PenpalA>(para_to_para_transfer_assets_through_ah);
test.assert();
// Query final balances
let sender_rocs_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_location.clone(), &sender)
});
let sender_wnds_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_pezkuwichain_teyrchains.clone(), &sender)
});
let wnds_in_sender_reserve_on_ahr_after = AssetHubPezkuwichain::execute_with(|| {
type Assets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(
wnd_at_pezkuwichain_teyrchains.clone().try_into().unwrap(),
&sov_of_sender_on_ah,
)
});
let rocs_in_sender_reserve_on_ahr_after =
<AssetHubPezkuwichain as Chain>::account_data_of(sov_of_sender_on_ah).free;
let wnds_in_receiver_reserve_on_ahr_after = AssetHubPezkuwichain::execute_with(|| {
type Assets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(
wnd_at_pezkuwichain_teyrchains.clone().try_into().unwrap(),
&sov_of_receiver_on_ah,
)
});
let rocs_in_receiver_reserve_on_ahr_after =
<AssetHubPezkuwichain as Chain>::account_data_of(sov_of_receiver_on_ah).free;
let receiver_rocs_after = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_location, &receiver)
});
let receiver_wnds_after = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_pezkuwichain_teyrchains, &receiver)
});
// Sender's balance is reduced by amount sent plus delivery fees
assert!(sender_rocs_after < sender_rocs_before - roc_to_send);
assert_eq!(sender_wnds_after, sender_wnds_before - wnd_to_send);
// Sovereign accounts on reserve are changed accordingly
assert_eq!(
rocs_in_sender_reserve_on_ahr_after,
rocs_in_sender_reserve_on_ahr_before - roc_to_send
);
assert_eq!(
wnds_in_sender_reserve_on_ahr_after,
wnds_in_sender_reserve_on_ahr_before - wnd_to_send
);
assert!(rocs_in_receiver_reserve_on_ahr_after > rocs_in_receiver_reserve_on_ahr_before);
assert_eq!(
wnds_in_receiver_reserve_on_ahr_after,
wnds_in_receiver_reserve_on_ahr_before + wnd_to_send
);
// Receiver's balance is increased
assert!(receiver_rocs_after > receiver_rocs_before);
assert_eq!(receiver_wnds_after, receiver_wnds_before + wnd_to_send);
}
// ==============================================================================================
// ==== Bidirectional Transfer - Native + Teleportable Foreign Assets - Teyrchain<->AssetHub ====
// ==============================================================================================
/// Transfers of native asset plus teleportable foreign asset from Teyrchain to AssetHub and back
/// with fees paid using native asset.
#[test]
fn bidirectional_teleport_foreign_asset_between_para_and_asset_hub_using_explicit_transfer_types() {
do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt(
para_to_asset_hub_teleport_foreign_assets,
asset_hub_to_para_teleport_foreign_assets,
);
}
// ===============================================================
// ===== Transfer - Native Asset - Relay->AssetHub->Teyrchain ====
// ===============================================================
/// Transfers of native asset Relay to Teyrchain (using AssetHub reserve). Teyrchains want to avoid
/// managing SAs on all system chains, thus want all their HEZ-in-reserve to be held in their
/// Sovereign Account on Asset Hub.
#[test]
fn transfer_native_asset_from_relay_to_para_through_asset_hub() {
// Init values for Relay
let destination = Pezkuwichain::child_location_of(PenpalA::para_id());
let sender = PezkuwichainSender::get();
let amount_to_send: Balance = PEZKUWICHAIN_ED * 1000;
// Init values for Teyrchain
let relay_native_asset_location = RelayLocation::get();
let receiver = PenpalAReceiver::get();
// Init Test
let test_args = TestContext {
sender,
receiver: receiver.clone(),
args: TestArgs::new_relay(destination.clone(), receiver.clone(), amount_to_send),
};
let mut test = RelayToParaThroughAHTest::new(test_args);
let sov_penpal_on_ah = AssetHubPezkuwichain::sovereign_account_id_of(
AssetHubPezkuwichain::sibling_location_of(PenpalA::para_id()),
);
// Query initial balances
let sender_balance_before = test.sender.balance;
let sov_penpal_on_ah_before = AssetHubPezkuwichain::execute_with(|| {
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::Balances::free_balance(
sov_penpal_on_ah.clone(),
)
});
let receiver_assets_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &receiver)
});
fn relay_assertions(t: RelayToParaThroughAHTest) {
type RuntimeEvent = <Pezkuwichain as Chain>::RuntimeEvent;
Pezkuwichain::assert_xcm_pallet_attempted_complete(None);
assert_expected_events!(
Pezkuwichain,
vec![
// Amount to teleport is withdrawn from Sender
RuntimeEvent::Balances(pezpallet_balances::Event::Burned { who, amount }) => {
who: *who == t.sender.account_id,
amount: *amount == t.args.amount,
},
// Amount to teleport is deposited in Relay's `CheckAccount`
RuntimeEvent::Balances(pezpallet_balances::Event::Minted { who, amount }) => {
who: *who == <Pezkuwichain as PezkuwichainPallet>::XcmPallet::check_account(),
amount: *amount == t.args.amount,
},
]
);
}
fn asset_hub_assertions(_: RelayToParaThroughAHTest) {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
let sov_penpal_on_ah = AssetHubPezkuwichain::sovereign_account_id_of(
AssetHubPezkuwichain::sibling_location_of(PenpalA::para_id()),
);
assert_expected_events!(
AssetHubPezkuwichain,
vec![
// Deposited to receiver teyrchain SA
RuntimeEvent::Balances(
pezpallet_balances::Event::Minted { who, .. }
) => {
who: *who == sov_penpal_on_ah,
},
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
}
fn penpal_assertions(t: RelayToParaThroughAHTest) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
// Assets in t are relative to the relay chain. The asset here should be relative to
// Penpal, so parents: 1.
let expected_id: Location = Location { parents: 1, interior: Here };
assert_expected_events!(
PenpalA,
vec![
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == expected_id,
owner: *owner == t.receiver.account_id,
},
]
);
}
fn transfer_assets_dispatchable(t: RelayToParaThroughAHTest) -> DispatchResult {
let fee: Asset = t
.args
.assets
.inner()
.iter()
.find(|a| a.id == t.args.fee_asset_id)
.cloned()
.unwrap();
let asset_hub_location = Pezkuwichain::child_location_of(AssetHubPezkuwichain::para_id());
let context = PezkuwichainUniversalLocation::get();
// reanchor fees to the view of destination (Penpal)
let mut remote_fees = fee.clone().reanchored(&t.args.dest, &context).unwrap();
if let Fungible(ref mut amount) = remote_fees.fun {
// we already spent some fees along the way, just use half of what we started with
*amount = *amount / 2;
}
let xcm_on_final_dest = Xcm::<()>(vec![
BuyExecution { fees: remote_fees, weight_limit: t.args.weight_limit.clone() },
DepositAsset {
assets: Wild(AllCounted(t.args.assets.len() as u32)),
beneficiary: t.args.beneficiary,
},
]);
// reanchor final dest (Penpal) to the view of hop (Asset Hub)
let mut dest = t.args.dest.clone();
dest.reanchor(&asset_hub_location, &context).unwrap();
// on Asset Hub, forward assets to Penpal
let xcm_on_hop = Xcm::<()>(vec![DepositReserveAsset {
assets: Wild(AllCounted(t.args.assets.len() as u32)),
dest,
xcm: xcm_on_final_dest,
}]);
Dmp::make_teyrchain_reachable(AssetHubPezkuwichain::para_id());
// First leg is a teleport, from there a local-reserve-transfer to final dest
<Pezkuwichain as PezkuwichainPallet>::XcmPallet::transfer_assets_using_type_and_then(
t.signed_origin,
bx!(asset_hub_location.into()),
bx!(t.args.assets.into()),
bx!(TransferType::Teleport),
bx!(fee.id.into()),
bx!(TransferType::Teleport),
bx!(VersionedXcm::from(xcm_on_hop)),
t.args.weight_limit,
)
}
// Set assertions and dispatchables
test.set_assertion::<Pezkuwichain>(relay_assertions);
test.set_assertion::<AssetHubPezkuwichain>(asset_hub_assertions);
test.set_assertion::<PenpalA>(penpal_assertions);
test.set_dispatchable::<Pezkuwichain>(transfer_assets_dispatchable);
test.assert();
// Query final balances
let sender_balance_after = test.sender.balance;
let sov_penpal_on_ah_after = AssetHubPezkuwichain::execute_with(|| {
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::Balances::free_balance(
sov_penpal_on_ah,
)
});
let receiver_assets_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &receiver)
});
// Sender's balance is reduced by amount sent plus delivery fees
assert!(sender_balance_after < sender_balance_before - amount_to_send);
// SA on AH balance is increased
assert!(sov_penpal_on_ah_after > sov_penpal_on_ah_before);
// Receiver's asset balance is increased
assert!(receiver_assets_after > receiver_assets_before);
// Receiver's asset balance increased by `amount_to_send - delivery_fees - bought_execution`;
// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
// should be non-zero
assert!(receiver_assets_after < receiver_assets_before + amount_to_send);
}
@@ -0,0 +1,102 @@
// 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.
mod claim_assets;
mod hybrid_transfers;
mod reserve_transfer;
mod reward_pool;
mod send;
mod set_xcm_versions;
mod swap;
mod teleport;
mod treasury;
mod xcm_fee_estimation;
#[macro_export]
macro_rules! create_pool_with_roc_on {
// default amounts
( $chain:ident, $asset_id:expr, $is_foreign:expr, $asset_owner:expr ) => {
$crate::create_pool_with_roc_on!(
$chain,
$asset_id,
$is_foreign,
$asset_owner,
1_000_000_000_000,
2_000_000_000_000
);
};
// custom amounts
( $chain:ident, $asset_id:expr, $is_foreign:expr, $asset_owner:expr, $roc_amount:expr, $asset_amount:expr ) => {
emulated_integration_tests_common::impls::paste::paste! {
<$chain>::execute_with(|| {
type RuntimeEvent = <$chain as Chain>::RuntimeEvent;
let owner = $asset_owner;
let signed_owner = <$chain as Chain>::RuntimeOrigin::signed(owner.clone());
let roc_location: Location = Parent.into();
if $is_foreign {
assert_ok!(<$chain as [<$chain Pallet>]>::ForeignAssets::mint(
signed_owner.clone(),
$asset_id.clone().into(),
owner.clone().into(),
10_000_000_000_000, // For it to have more than enough.
));
} else {
let asset_id = match $asset_id.interior.last() {
Some(GeneralIndex(id)) => *id as u32,
_ => unreachable!(),
};
assert_ok!(<$chain as [<$chain Pallet>]>::Assets::mint(
signed_owner.clone(),
asset_id.into(),
owner.clone().into(),
10_000_000_000_000, // For it to have more than enough.
));
}
assert_ok!(<$chain as [<$chain Pallet>]>::AssetConversion::create_pool(
signed_owner.clone(),
Box::new(roc_location.clone()),
Box::new($asset_id.clone()),
));
assert_expected_events!(
$chain,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::PoolCreated { .. }) => {},
]
);
assert_ok!(<$chain as [<$chain Pallet>]>::AssetConversion::add_liquidity(
signed_owner,
Box::new(roc_location),
Box::new($asset_id),
$roc_amount,
$asset_amount,
0,
0,
owner.into()
));
assert_expected_events!(
$chain,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::LiquidityAdded { .. }) => {},
]
);
});
}
};
}
@@ -0,0 +1,114 @@
// 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 pezframe_support::{pezsp_runtime::traits::Dispatchable, traits::schedule::DispatchTime};
use xcm_executor::traits::ConvertLocation;
#[test]
fn treasury_creates_asset_reward_pool() {
AssetHubPezkuwichain::execute_with(|| {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
type Balances = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::Balances;
let treasurer =
Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]);
let treasurer_account =
ahr_xcm_config::LocationToAccountId::convert_location(&treasurer).unwrap();
assert_ok!(Balances::force_set_balance(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::root(),
treasurer_account.clone().into(),
ASSET_HUB_PEZKUWICHAIN_ED * 100_000,
));
let events = AssetHubPezkuwichain::events();
match events.iter().last() {
Some(RuntimeEvent::Balances(pezpallet_balances::Event::BalanceSet { who, .. })) => {
assert_eq!(*who, treasurer_account)
},
_ => panic!("Expected Balances::BalanceSet event"),
}
});
Pezkuwichain::execute_with(|| {
type AssetHubPezkuwichainRuntimeCall = <AssetHubPezkuwichain as Chain>::RuntimeCall;
type AssetHubPezkuwichainRuntime = <AssetHubPezkuwichain as Chain>::Runtime;
type PezkuwichainRuntimeCall = <Pezkuwichain as Chain>::RuntimeCall;
type PezkuwichainRuntime = <Pezkuwichain as Chain>::Runtime;
type PezkuwichainRuntimeEvent = <Pezkuwichain as Chain>::RuntimeEvent;
type PezkuwichainRuntimeOrigin = <Pezkuwichain as Chain>::RuntimeOrigin;
Dmp::make_teyrchain_reachable(AssetHubPezkuwichain::para_id());
let staked_asset_id = bx!(RelayLocation::get());
let reward_asset_id = bx!(RelayLocation::get());
let reward_rate_per_block = 1_000_000_000;
let lifetime = 1_000_000_000;
let admin = None;
let create_pool_call =
PezkuwichainRuntimeCall::XcmPallet(pezpallet_xcm::Call::<PezkuwichainRuntime>::send {
dest: bx!(VersionedLocation::V4(
xcm::v4::Junction::Teyrchain(AssetHubPezkuwichain::para_id().into()).into()
)),
message: bx!(VersionedXcm::V5(Xcm(vec![
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
Transact {
origin_kind: OriginKind::SovereignAccount,
fallback_max_weight: None,
call: AssetHubPezkuwichainRuntimeCall::AssetRewards(
pezpallet_asset_rewards::Call::<AssetHubPezkuwichainRuntime>::create_pool {
staked_asset_id,
reward_asset_id,
reward_rate_per_block,
expiry: DispatchTime::After(lifetime),
admin
}
)
.encode()
.into(),
}
]))),
});
let treasury_origin: PezkuwichainRuntimeOrigin = Treasurer.into();
assert_ok!(create_pool_call.dispatch(treasury_origin));
assert_expected_events!(
Pezkuwichain,
vec![
PezkuwichainRuntimeEvent::XcmPallet(pezpallet_xcm::Event::Sent { .. }) => {},
]
);
});
AssetHubPezkuwichain::execute_with(|| {
type Runtime = <AssetHubPezkuwichain as Chain>::Runtime;
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
assert_eq!(1, pezpallet_asset_rewards::Pools::<Runtime>::iter().count());
let events = AssetHubPezkuwichain::events();
match events.iter().last() {
Some(RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed {
success: true,
..
})) => (),
_ => panic!("Expected MessageQueue::Processed event"),
}
});
}
@@ -0,0 +1,197 @@
// 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::{create_pool_with_roc_on, imports::*};
/// Relay Chain should be able to execute `Transact` instructions in System Teyrchain
/// when `OriginKind::Superuser`.
#[test]
fn send_transact_as_superuser_from_relay_to_asset_hub_works() {
AssetHubPezkuwichain::force_create_asset_from_relay_as_root(
ASSET_ID,
ASSET_MIN_BALANCE,
true,
AssetHubPezkuwichainSender::get().into(),
Some(Weight::from_parts(144_933_000, 3675)),
)
}
/// We tests two things here:
/// - Teyrchain should be able to send XCM paying its fee at Asset Hub using system asset
/// - Teyrchain should be able to create a new Foreign Asset at Asset Hub
#[test]
fn send_xcm_from_para_to_asset_hub_paying_fee_with_system_asset() {
let para_sovereign_account = AssetHubPezkuwichain::sovereign_account_id_of(
AssetHubPezkuwichain::sibling_location_of(PenpalA::para_id()),
);
let asset_location_on_penpal = Location::new(
0,
[Junction::PalletInstance(ASSETS_PALLET_ID), Junction::GeneralIndex(ASSET_ID.into())],
);
let foreign_asset_at_asset_hub =
Location::new(1, [Junction::Teyrchain(PenpalA::para_id().into())])
.appended_with(asset_location_on_penpal)
.unwrap();
// Encoded `create_asset` call to be executed in AssetHub
let call = AssetHubPezkuwichain::create_foreign_asset_call(
foreign_asset_at_asset_hub.clone(),
ASSET_MIN_BALANCE,
para_sovereign_account.clone(),
);
let origin_kind = OriginKind::Xcm;
let fee_amount = ASSET_HUB_PEZKUWICHAIN_ED * 1000000;
let system_asset = (Parent, fee_amount).into();
let root_origin = <PenpalA as Chain>::RuntimeOrigin::root();
let system_para_destination =
PenpalA::sibling_location_of(AssetHubPezkuwichain::para_id()).into();
let xcm = xcm_transact_paid_execution(
call,
origin_kind,
system_asset,
para_sovereign_account.clone(),
);
// SA-of-Penpal-on-AHR needs to have balance to pay for fees and asset creation deposit
AssetHubPezkuwichain::fund_accounts(vec![(
para_sovereign_account.clone().into(),
ASSET_HUB_PEZKUWICHAIN_ED * 10000000000,
)]);
PenpalA::execute_with(|| {
assert_ok!(<PenpalA as PenpalAPallet>::PezkuwiXcm::send(
root_origin,
bx!(system_para_destination),
bx!(xcm),
));
PenpalA::assert_xcm_pallet_sent();
});
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 == para_sovereign_account,
amount: *amount == fee_amount,
},
// Foreign Asset created
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Created { asset_id, creator, owner }) => {
asset_id: *asset_id == foreign_asset_at_asset_hub,
creator: *creator == para_sovereign_account.clone(),
owner: *owner == para_sovereign_account,
},
]
);
type ForeignAssets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets;
assert!(ForeignAssets::asset_exists(foreign_asset_at_asset_hub));
});
}
/// We tests two things here:
/// - Teyrchain should be able to send XCM paying its fee at Asset Hub using sufficient asset
/// - Teyrchain should be able to create a new Asset at Asset Hub
#[test]
fn send_xcm_from_para_to_asset_hub_paying_fee_with_sufficient_asset() {
let para_sovereign_account = AssetHubPezkuwichain::sovereign_account_id_of(
AssetHubPezkuwichain::sibling_location_of(PenpalA::para_id()),
);
// Force create and mint sufficient assets for Teyrchain's sovereign account
AssetHubPezkuwichain::force_create_and_mint_asset(
ASSET_ID,
ASSET_MIN_BALANCE,
true,
para_sovereign_account.clone(),
Some(Weight::from_parts(144_933_000, 3675)),
ASSET_MIN_BALANCE * 1000000000,
);
// Just a different `asset_id`` that does not exist yet
let new_asset_id = ASSET_ID + 1;
// Encoded `create_asset` call to be executed in AssetHub
let call = AssetHubPezkuwichain::create_asset_call(
new_asset_id,
ASSET_MIN_BALANCE,
para_sovereign_account.clone(),
);
let origin_kind = OriginKind::SovereignAccount;
let fee_amount = ASSET_MIN_BALANCE * 1000000;
let asset =
([PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())], fee_amount).into();
let asset_location =
Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())]);
let root_origin = <PenpalA as Chain>::RuntimeOrigin::root();
let system_para_destination =
PenpalA::sibling_location_of(AssetHubPezkuwichain::para_id()).into();
let xcm = xcm_transact_paid_execution(call, origin_kind, asset, para_sovereign_account.clone());
// SA-of-Penpal-on-AHR needs to have balance to pay for asset creation deposit
AssetHubPezkuwichain::fund_accounts(vec![(
para_sovereign_account.clone().into(),
ASSET_HUB_PEZKUWICHAIN_ED * 10000000000,
)]);
create_pool_with_roc_on!(
AssetHubPezkuwichain,
asset_location,
false,
para_sovereign_account.clone(),
9_000_000_000_000_000,
9_000_000_000_000
);
PenpalA::execute_with(|| {
assert_ok!(<PenpalA as PenpalAPallet>::PezkuwiXcm::send(
root_origin,
bx!(system_para_destination),
bx!(xcm),
));
PenpalA::assert_xcm_pallet_sent();
});
AssetHubPezkuwichain::execute_with(|| {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
AssetHubPezkuwichain::assert_xcmp_queue_success(None);
assert_expected_events!(
AssetHubPezkuwichain,
vec![
// Burned the fee
RuntimeEvent::Assets(pezpallet_assets::Event::Burned { asset_id, owner, balance }) => {
asset_id: *asset_id == ASSET_ID,
owner: *owner == para_sovereign_account,
balance: *balance == fee_amount,
},
// Asset created
RuntimeEvent::Assets(pezpallet_assets::Event::Created { asset_id, creator, owner }) => {
asset_id: *asset_id == new_asset_id,
creator: *creator == para_sovereign_account.clone(),
owner: *owner == para_sovereign_account,
},
]
);
});
}
@@ -0,0 +1,83 @@
// 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 relay_sets_system_para_xcm_supported_version() {
// Init tests variables
let sudo_origin = <Pezkuwichain as Chain>::RuntimeOrigin::root();
let system_para_destination: Location =
Pezkuwichain::child_location_of(AssetHubPezkuwichain::para_id());
// Relay Chain sets supported version for Asset Teyrchain
Pezkuwichain::execute_with(|| {
assert_ok!(<Pezkuwichain as PezkuwichainPallet>::XcmPallet::force_xcm_version(
sudo_origin,
bx!(system_para_destination.clone()),
XCM_V3
));
type RuntimeEvent = <Pezkuwichain as Chain>::RuntimeEvent;
assert_expected_events!(
Pezkuwichain,
vec![
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::SupportedVersionChanged {
location,
version: XCM_V3
}) => { location: *location == system_para_destination, },
]
);
});
}
#[test]
fn system_para_sets_relay_xcm_supported_version() {
// Init test variables
let parent_location = AssetHubPezkuwichain::parent_location();
let force_xcm_version_call =
<AssetHubPezkuwichain as Chain>::RuntimeCall::PezkuwiXcm(pezpallet_xcm::Call::<
<AssetHubPezkuwichain as Chain>::Runtime,
>::force_xcm_version {
location: bx!(parent_location.clone()),
version: XCM_V3,
})
.encode()
.into();
// System Teyrchain sets supported version for Relay Chain through it
Pezkuwichain::send_unpaid_transact_to_teyrchain_as_root(
AssetHubPezkuwichain::para_id(),
force_xcm_version_call,
);
// System Teyrchain receive the XCM message
AssetHubPezkuwichain::execute_with(|| {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
AssetHubPezkuwichain::assert_dmp_queue_complete(Some(Weight::from_parts(115_294_000, 0)));
assert_expected_events!(
AssetHubPezkuwichain,
vec![
RuntimeEvent::PezkuwiXcm(pezpallet_xcm::Event::SupportedVersionChanged {
location,
version: XCM_V3
}) => { location: *location == parent_location, },
]
);
});
}
@@ -0,0 +1,449 @@
// 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 swap_locally_on_chain_using_local_assets() {
let asset_native = Box::new(Location::try_from(RelayLocation::get()).unwrap());
let asset_one = Box::new(Location::new(
0,
[Junction::PalletInstance(ASSETS_PALLET_ID), Junction::GeneralIndex(ASSET_ID.into())],
));
AssetHubPezkuwichain::execute_with(|| {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
assert_ok!(<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::Assets::create(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(
AssetHubPezkuwichainSender::get()
),
ASSET_ID.into(),
AssetHubPezkuwichainSender::get().into(),
1000,
));
assert!(<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::Assets::asset_exists(
ASSET_ID
));
assert_ok!(<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::Assets::mint(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(
AssetHubPezkuwichainSender::get()
),
ASSET_ID.into(),
AssetHubPezkuwichainSender::get().into(),
100_000_000_000_000,
));
assert_ok!(
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::AssetConversion::create_pool(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(
AssetHubPezkuwichainSender::get()
),
asset_native.clone(),
asset_one.clone(),
)
);
assert_expected_events!(
AssetHubPezkuwichain,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::PoolCreated { .. }) => {},
]
);
assert_ok!(
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::AssetConversion::add_liquidity(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(
AssetHubPezkuwichainSender::get()
),
asset_native.clone(),
asset_one.clone(),
1_000_000_000_000,
2_000_000_000_000,
0,
0,
AssetHubPezkuwichainSender::get().into()
)
);
assert_expected_events!(
AssetHubPezkuwichain,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, },
]
);
let path = vec![asset_native.clone(), asset_one.clone()];
assert_ok!(
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::AssetConversion::swap_exact_tokens_for_tokens(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(AssetHubPezkuwichainSender::get()),
path,
100,
1,
AssetHubPezkuwichainSender::get().into(),
true
)
);
assert_expected_events!(
AssetHubPezkuwichain,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::SwapExecuted { amount_in, amount_out, .. }) => {
amount_in: *amount_in == 100,
amount_out: *amount_out == 199,
},
]
);
assert_ok!(
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::AssetConversion::remove_liquidity(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(
AssetHubPezkuwichainSender::get()
),
asset_native,
asset_one,
1414213562273 - ASSET_HUB_PEZKUWICHAIN_ED * 2, /* all but the 2 EDs can't be
* retrieved. */
0,
0,
AssetHubPezkuwichainSender::get().into(),
)
);
});
}
#[test]
fn swap_locally_on_chain_using_foreign_assets() {
let asset_native = Box::new(Location::try_from(RelayLocation::get()).unwrap());
let asset_location_on_penpal = PenpalA::execute_with(|| {
Location::try_from(PenpalLocalTeleportableToAssetHub::get()).unwrap()
});
let foreign_asset_at_asset_hub_pezkuwichain =
Location::new(1, [Junction::Teyrchain(PenpalA::para_id().into())])
.appended_with(asset_location_on_penpal)
.unwrap();
let penpal_as_seen_by_ah = AssetHubPezkuwichain::sibling_location_of(PenpalA::para_id());
let sov_penpal_on_ahr = AssetHubPezkuwichain::sovereign_account_id_of(penpal_as_seen_by_ah);
AssetHubPezkuwichain::fund_accounts(vec![
// An account to swap dot for something else.
(AssetHubPezkuwichainSender::get().into(), 5_000_000 * ASSET_HUB_PEZKUWICHAIN_ED),
// Penpal's sovereign account in AH should have some balance
(sov_penpal_on_ahr.clone().into(), 100_000_000 * ASSET_HUB_PEZKUWICHAIN_ED),
]);
AssetHubPezkuwichain::execute_with(|| {
// 0: No need to create foreign asset as it exists in genesis.
//
// 1: Mint foreign asset on asset_hub_pezkuwichain:
//
// (While it might be nice to use batch,
// currently that's disabled due to safe call filters.)
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
// 1. Mint foreign asset (in reality this should be a teleport or some such)
assert_ok!(<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets::mint(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(
sov_penpal_on_ahr.clone().into()
),
foreign_asset_at_asset_hub_pezkuwichain.clone(),
sov_penpal_on_ahr.clone().into(),
ASSET_HUB_PEZKUWICHAIN_ED * 3_000_000_000_000,
));
assert_expected_events!(
AssetHubPezkuwichain,
vec![
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Issued { .. }) => {},
]
);
// 2. Create pool:
assert_ok!(
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::AssetConversion::create_pool(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(
AssetHubPezkuwichainSender::get()
),
asset_native.clone(),
Box::new(foreign_asset_at_asset_hub_pezkuwichain.clone()),
)
);
assert_expected_events!(
AssetHubPezkuwichain,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::PoolCreated { .. }) => {},
]
);
// 3. Add liquidity:
assert_ok!(
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::AssetConversion::add_liquidity(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()),
asset_native.clone(),
Box::new(foreign_asset_at_asset_hub_pezkuwichain.clone()),
1_000_000_000_000,
2_000_000_000_000,
0,
0,
sov_penpal_on_ahr.clone().into()
)
);
assert_expected_events!(
AssetHubPezkuwichain,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => {
lp_token_minted: *lp_token_minted == 1414213562273,
},
]
);
// 4. Swap!
let path =
vec![asset_native.clone(), Box::new(foreign_asset_at_asset_hub_pezkuwichain.clone())];
assert_ok!(
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::AssetConversion::swap_exact_tokens_for_tokens(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(AssetHubPezkuwichainSender::get()),
path,
100000 * ASSET_HUB_PEZKUWICHAIN_ED,
1000 * ASSET_HUB_PEZKUWICHAIN_ED,
AssetHubPezkuwichainSender::get().into(),
true
)
);
assert_expected_events!(
AssetHubPezkuwichain,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::SwapExecuted { amount_in, amount_out, .. },) => {
amount_in: *amount_in == 333333300000,
amount_out: *amount_out == 498874118173,
},
]
);
// 5. Remove liquidity
assert_ok!(
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::AssetConversion::remove_liquidity(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()),
asset_native.clone(),
Box::new(foreign_asset_at_asset_hub_pezkuwichain.clone()),
1414213562273 - ASSET_HUB_PEZKUWICHAIN_ED * 2, /* all but the 2 EDs can't be
* retrieved. */
0,
0,
sov_penpal_on_ahr.clone().into(),
)
);
});
}
#[test]
fn cannot_create_pool_from_pool_assets() {
let asset_native = RelayLocation::get();
let mut asset_one = ahr_xcm_config::PoolAssetsPalletLocation::get();
asset_one.append_with(GeneralIndex(ASSET_ID.into())).expect("pool assets");
AssetHubPezkuwichain::execute_with(|| {
let pool_owner_account_id = AssetHubPezkuwichainAssetConversionOrigin::get();
assert_ok!(<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::PoolAssets::create(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(pool_owner_account_id.clone()),
ASSET_ID.into(),
pool_owner_account_id.clone().into(),
1000,
));
assert!(<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::PoolAssets::asset_exists(
ASSET_ID
));
assert_ok!(<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::PoolAssets::mint(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(pool_owner_account_id),
ASSET_ID.into(),
AssetHubPezkuwichainSender::get().into(),
3_000_000_000_000,
));
assert_matches::assert_matches!(
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::AssetConversion::create_pool(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(AssetHubPezkuwichainSender::get()),
Box::new(Location::try_from(asset_native).unwrap()),
Box::new(Location::try_from(asset_one).unwrap()),
),
Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("Unknown"))
);
});
}
#[test]
fn pay_xcm_fee_with_some_asset_swapped_for_native() {
let asset_native = Location::try_from(RelayLocation::get()).unwrap();
let asset_one = Location {
parents: 0,
interior: [
Junction::PalletInstance(ASSETS_PALLET_ID),
Junction::GeneralIndex(ASSET_ID.into()),
]
.into(),
};
let penpal = AssetHubPezkuwichain::sovereign_account_id_of(
AssetHubPezkuwichain::sibling_location_of(PenpalA::para_id()),
);
AssetHubPezkuwichain::execute_with(|| {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
// set up pool with ASSET_ID <> NATIVE pair
assert_ok!(<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::Assets::create(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(
AssetHubPezkuwichainSender::get()
),
ASSET_ID.into(),
AssetHubPezkuwichainSender::get().into(),
ASSET_MIN_BALANCE,
));
assert!(<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::Assets::asset_exists(
ASSET_ID
));
assert_ok!(<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::Assets::mint(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(
AssetHubPezkuwichainSender::get()
),
ASSET_ID.into(),
AssetHubPezkuwichainSender::get().into(),
3_000_000_000_000,
));
assert_ok!(
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::AssetConversion::create_pool(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(
AssetHubPezkuwichainSender::get()
),
Box::new(asset_native.clone()),
Box::new(asset_one.clone()),
)
);
assert_expected_events!(
AssetHubPezkuwichain,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::PoolCreated { .. }) => {},
]
);
assert_ok!(
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::AssetConversion::add_liquidity(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(
AssetHubPezkuwichainSender::get()
),
Box::new(asset_native),
Box::new(asset_one),
1_000_000_000_000,
2_000_000_000_000,
0,
0,
AssetHubPezkuwichainSender::get().into()
)
);
assert_expected_events!(
AssetHubPezkuwichain,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, },
]
);
// ensure `penpal` sovereign account has no native tokens and mint some `ASSET_ID`
assert_eq!(
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::Balances::free_balance(
penpal.clone()
),
0
);
assert_ok!(<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::Assets::touch_other(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(
AssetHubPezkuwichainSender::get()
),
ASSET_ID.into(),
penpal.clone().into(),
));
assert_ok!(<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::Assets::mint(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(
AssetHubPezkuwichainSender::get()
),
ASSET_ID.into(),
penpal.clone().into(),
10_000_000_000_000,
));
});
PenpalA::execute_with(|| {
// send xcm transact from `penpal` account while paying with `ASSET_ID` tokens on
// `AssetHubPezkuwichain`
let call = <AssetHubPezkuwichain as Chain>::RuntimeCall::System(pezframe_system::Call::<
<AssetHubPezkuwichain as Chain>::Runtime,
>::remark {
remark: vec![],
})
.encode()
.into();
let penpal_root = <PenpalA as Chain>::RuntimeOrigin::root();
let fee_amount = 4_000_000_000_000u128;
let asset_one =
([PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())], fee_amount).into();
let asset_hub_location =
PenpalA::sibling_location_of(AssetHubPezkuwichain::para_id()).into();
let xcm = xcm_transact_paid_execution(
call,
OriginKind::SovereignAccount,
asset_one,
penpal.clone(),
);
assert_ok!(<PenpalA as PenpalAPallet>::PezkuwiXcm::send(
penpal_root,
bx!(asset_hub_location),
bx!(xcm),
));
PenpalA::assert_xcm_pallet_sent();
});
AssetHubPezkuwichain::execute_with(|| {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
AssetHubPezkuwichain::assert_xcmp_queue_success(None);
assert_expected_events!(
AssetHubPezkuwichain,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::SwapCreditExecuted { .. },) => {},
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed { success: true,.. }) => {},
]
);
});
}
#[test]
fn xcm_fee_querying_apis_work() {
test_xcm_fee_querying_apis_work_for_asset_hub!(AssetHubPezkuwichain);
}
@@ -0,0 +1,643 @@
// 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::*;
fn relay_dest_assertions_fail(_t: SystemParaToRelayTest) {
Pezkuwichain::assert_ump_queue_processed(
false,
Some(AssetHubPezkuwichain::para_id()),
Some(Weight::from_parts(157_718_000, 3_593)),
);
}
fn para_origin_assertions(t: SystemParaToRelayTest) {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
AssetHubPezkuwichain::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(
730_053_000,
4_000,
)));
AssetHubPezkuwichain::assert_teyrchain_system_ump_sent();
assert_expected_events!(
AssetHubPezkuwichain,
vec![
// Amount is withdrawn from Sender's account
RuntimeEvent::Balances(pezpallet_balances::Event::Burned { who, amount }) => {
who: *who == t.sender.account_id,
amount: *amount == t.args.amount,
},
]
);
}
fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
let system_para_native_asset_location = RelayLocation::get();
let expected_asset_id = t.args.asset_id.unwrap();
let (_, expected_asset_amount) = non_fee_asset(&t.args.assets, &t.args.fee_asset_id).unwrap();
PenpalA::assert_xcm_pallet_attempted_complete(None);
assert_expected_events!(
PenpalA,
vec![
RuntimeEvent::ForeignAssets(
pezpallet_assets::Event::Burned { asset_id, owner, .. }
) => {
asset_id: *asset_id == system_para_native_asset_location,
owner: *owner == t.sender.account_id,
},
RuntimeEvent::Assets(pezpallet_assets::Event::Burned { asset_id, owner, balance }) => {
asset_id: *asset_id == expected_asset_id,
owner: *owner == t.sender.account_id,
balance: *balance == expected_asset_amount,
},
]
);
}
fn penpal_to_ah_foreign_assets_receiver_assertions(t: ParaToSystemParaTest) {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
let sov_penpal_on_ahr = AssetHubPezkuwichain::sovereign_account_id_of(
AssetHubPezkuwichain::sibling_location_of(PenpalA::para_id()),
);
let (_, expected_foreign_asset_amount) =
non_fee_asset(&t.args.assets, &t.args.fee_asset_id).unwrap();
let (_, fee_asset_amount) = fee_asset(&t.args.assets, &t.args.fee_asset_id).unwrap();
AssetHubPezkuwichain::assert_xcmp_queue_success(None);
assert_expected_events!(
AssetHubPezkuwichain,
vec![
// native asset reserve transfer for paying fees, withdrawn from Penpal's sov account
RuntimeEvent::Balances(
pezpallet_balances::Event::Burned { who, amount }
) => {
who: *who == sov_penpal_on_ahr.clone().into(),
amount: *amount == fee_asset_amount,
},
RuntimeEvent::Balances(pezpallet_balances::Event::Minted { who, .. }) => {
who: *who == t.receiver.account_id,
},
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Issued { asset_id, owner, amount }) => {
asset_id: *asset_id == PenpalATeleportableAssetLocation::get(),
owner: *owner == t.receiver.account_id,
amount: *amount == expected_foreign_asset_amount,
},
RuntimeEvent::Balances(pezpallet_balances::Event::Deposit { .. }) => {},
]
);
}
fn ah_to_penpal_foreign_assets_sender_assertions(t: SystemParaToParaTest) {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
AssetHubPezkuwichain::assert_xcm_pallet_attempted_complete(None);
let (expected_foreign_asset_id, expected_foreign_asset_amount) =
non_fee_asset(&t.args.assets, &t.args.fee_asset_id).unwrap();
let (_, fee_asset_amount) = fee_asset(&t.args.assets, &t.args.fee_asset_id).unwrap();
assert_expected_events!(
AssetHubPezkuwichain,
vec![
// native asset used for fees is transferred to Teyrchain's Sovereign account as reserve
RuntimeEvent::Balances(
pezpallet_balances::Event::Transfer { from, to, amount }
) => {
from: *from == t.sender.account_id,
to: *to == AssetHubPezkuwichain::sovereign_account_id_of(
t.args.dest.clone()
),
amount: *amount == fee_asset_amount,
},
// foreign asset is burned locally as part of teleportation
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Burned { asset_id, owner, balance }) => {
asset_id: *asset_id == expected_foreign_asset_id,
owner: *owner == t.sender.account_id,
balance: *balance == expected_foreign_asset_amount,
},
]
);
}
fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
let expected_asset_id = t.args.asset_id.unwrap();
let (_, expected_asset_amount) = non_fee_asset(&t.args.assets, &t.args.fee_asset_id).unwrap();
let checking_account = <PenpalA as PenpalAPallet>::PezkuwiXcm::check_account();
let system_para_native_asset_location = RelayLocation::get();
PenpalA::assert_xcmp_queue_success(None);
assert_expected_events!(
PenpalA,
vec![
// checking account burns local asset as part of incoming teleport
RuntimeEvent::Assets(pezpallet_assets::Event::Burned { asset_id, owner, balance }) => {
asset_id: *asset_id == expected_asset_id,
owner: *owner == checking_account,
balance: *balance == expected_asset_amount,
},
// local asset is teleported into account of receiver
RuntimeEvent::Assets(pezpallet_assets::Event::Issued { asset_id, owner, amount }) => {
asset_id: *asset_id == expected_asset_id,
owner: *owner == t.receiver.account_id,
amount: *amount == expected_asset_amount,
},
// native asset for fee is deposited to receiver
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == system_para_native_asset_location,
owner: *owner == t.receiver.account_id,
},
]
);
}
fn system_para_limited_teleport_assets(t: SystemParaToRelayTest) -> DispatchResult {
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::PezkuwiXcm::limited_teleport_assets(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.beneficiary.into()),
bx!(t.args.assets.into()),
bx!(t.args.fee_asset_id.into()),
t.args.weight_limit,
)
}
fn para_to_system_para_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult {
<PenpalA as PenpalAPallet>::PezkuwiXcm::transfer_assets_using_type_and_then(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.assets.into()),
bx!(TransferType::Teleport),
bx!(t.args.fee_asset_id.into()),
bx!(TransferType::DestinationReserve),
bx!(VersionedXcm::from(
Xcm::<()>::builder_unsafe()
.deposit_asset(AllCounted(2), t.args.beneficiary)
.build()
)),
t.args.weight_limit,
)
}
fn system_para_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult {
<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::PezkuwiXcm::transfer_assets_using_type_and_then(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.assets.into()),
bx!(TransferType::Teleport),
bx!(t.args.fee_asset_id.into()),
bx!(TransferType::LocalReserve),
bx!(VersionedXcm::from(
Xcm::<()>::builder_unsafe()
.deposit_asset(AllCounted(2), t.args.beneficiary)
.build()
)),
t.args.weight_limit,
)
}
#[test]
fn teleport_via_limited_teleport_assets_to_other_system_teyrchains_works() {
let amount = ASSET_HUB_PEZKUWICHAIN_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
AssetHubPezkuwichain, // Origin
vec![BridgeHubPezkuwichain], // Destinations
(native_asset, amount),
fee_asset_id,
limited_teleport_assets
);
}
#[test]
fn teleport_via_transfer_assets_to_other_system_teyrchains_works() {
let amount = ASSET_HUB_PEZKUWICHAIN_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
AssetHubPezkuwichain, // Origin
vec![BridgeHubPezkuwichain], // 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![AssetHubPezkuwichain],
amount,
limited_teleport_assets
);
test_teyrchain_is_trusted_teleporter_for_relay!(
AssetHubPezkuwichain,
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![AssetHubPezkuwichain],
amount,
transfer_assets
);
test_teyrchain_is_trusted_teleporter_for_relay!(
AssetHubPezkuwichain,
Pezkuwichain,
amount,
transfer_assets
);
}
/// Limited Teleport of native asset from System Teyrchain to Relay Chain
/// shouldn't work when there is not enough balance in Relay Chain's `CheckAccount`
#[test]
fn limited_teleport_native_assets_from_system_para_to_relay_fails() {
// Init values for Relay Chain
let amount_to_send: Balance = ASSET_HUB_PEZKUWICHAIN_ED * 1000;
let destination = AssetHubPezkuwichain::parent_location().into();
let beneficiary_id = PezkuwichainReceiver::get().into();
let assets = (Parent, amount_to_send).into();
let fee_asset_id: AssetId = Parent.into();
let test_args = TestContext {
sender: AssetHubPezkuwichainSender::get(),
receiver: PezkuwichainReceiver::get(),
args: TestArgs::new_para(
destination,
beneficiary_id,
amount_to_send,
assets,
None,
fee_asset_id.clone(),
),
};
let mut test = SystemParaToRelayTest::new(test_args);
let sender_balance_before = test.sender.balance;
let receiver_balance_before = test.receiver.balance;
test.set_assertion::<AssetHubPezkuwichain>(para_origin_assertions);
test.set_assertion::<Pezkuwichain>(relay_dest_assertions_fail);
test.set_dispatchable::<AssetHubPezkuwichain>(system_para_limited_teleport_assets);
test.assert();
let sender_balance_after = test.sender.balance;
let receiver_balance_after = test.receiver.balance;
let delivery_fees = AssetHubPezkuwichain::execute_with(|| {
xcm_helpers::teleport_assets_delivery_fees::<
<AssetHubPezkuwichainXcmConfig as xcm_executor::Config>::XcmSender,
>(
test.args.assets.clone(),
fee_asset_id,
test.args.weight_limit,
test.args.beneficiary,
test.args.dest,
)
});
// Sender's balance is reduced
assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after);
// Receiver's balance does not change
assert_eq!(receiver_balance_after, receiver_balance_before);
}
/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets while paying
/// fees using (reserve transferred) native asset.
pub fn do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt(
para_to_ah_dispatchable: fn(ParaToSystemParaTest) -> DispatchResult,
ah_to_para_dispatchable: fn(SystemParaToParaTest) -> DispatchResult,
) {
// Init values for Teyrchain
let fee_amount_to_send: Balance = ASSET_HUB_PEZKUWICHAIN_ED * 10000;
let asset_location_on_penpal =
PenpalA::execute_with(|| PenpalLocalTeleportableToAssetHub::get());
let asset_id_on_penpal = match asset_location_on_penpal.last() {
Some(Junction::GeneralIndex(id)) => *id as u32,
_ => unreachable!(),
};
let asset_amount_to_send = ASSET_HUB_PEZKUWICHAIN_ED * 1000;
let asset_owner = PenpalAssetOwner::get();
let system_para_native_asset_location = RelayLocation::get();
let sender = PenpalASender::get();
let penpal_check_account = <PenpalA as PenpalAPallet>::PezkuwiXcm::check_account();
let ah_as_seen_by_penpal = PenpalA::sibling_location_of(AssetHubPezkuwichain::para_id());
let penpal_assets: Assets = vec![
(Parent, fee_amount_to_send).into(),
(asset_location_on_penpal.clone(), asset_amount_to_send).into(),
]
.into();
let fee_asset_id: AssetId = Parent.into();
// fund Teyrchain's sender account
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner.clone()),
system_para_native_asset_location.clone(),
sender.clone(),
fee_amount_to_send * 2,
);
// No need to create the asset (only mint) as it exists in genesis.
PenpalA::mint_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner.clone()),
asset_id_on_penpal,
sender.clone(),
asset_amount_to_send,
);
// fund Teyrchain's check account to be able to teleport
PenpalA::fund_accounts(vec![(
penpal_check_account.clone().into(),
ASSET_HUB_PEZKUWICHAIN_ED * 1000,
)]);
// prefund SA of Penpal on AssetHub with enough native tokens to pay for fees
let penpal_as_seen_by_ah = AssetHubPezkuwichain::sibling_location_of(PenpalA::para_id());
let sov_penpal_on_ah = AssetHubPezkuwichain::sovereign_account_id_of(penpal_as_seen_by_ah);
AssetHubPezkuwichain::fund_accounts(vec![(
sov_penpal_on_ah.clone().into(),
ASSET_HUB_PEZKUWICHAIN_ED * 100_000_000_000,
)]);
// Init values for System Teyrchain
let foreign_asset_at_asset_hub_pezkuwichain =
Location::new(1, [Junction::Teyrchain(PenpalA::para_id().into())])
.appended_with(asset_location_on_penpal)
.unwrap();
let penpal_to_ah_beneficiary_id = AssetHubPezkuwichainReceiver::get();
// Penpal to AH test args
let penpal_to_ah_test_args = TestContext {
sender: PenpalASender::get(),
receiver: AssetHubPezkuwichainReceiver::get(),
args: TestArgs::new_para(
ah_as_seen_by_penpal,
penpal_to_ah_beneficiary_id,
asset_amount_to_send,
penpal_assets,
Some(asset_id_on_penpal),
fee_asset_id,
),
};
let mut penpal_to_ah = ParaToSystemParaTest::new(penpal_to_ah_test_args);
let penpal_sender_balance_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(
system_para_native_asset_location.clone(),
&PenpalASender::get(),
)
});
let ah_receiver_balance_before = penpal_to_ah.receiver.balance;
let penpal_sender_assets_before = PenpalA::execute_with(|| {
type Assets = <PenpalA as PenpalAPallet>::Assets;
<Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalASender::get())
});
let ah_receiver_assets_before = AssetHubPezkuwichain::execute_with(|| {
type Assets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(
foreign_asset_at_asset_hub_pezkuwichain.clone().try_into().unwrap(),
&AssetHubPezkuwichainReceiver::get(),
)
});
penpal_to_ah.set_assertion::<PenpalA>(penpal_to_ah_foreign_assets_sender_assertions);
penpal_to_ah
.set_assertion::<AssetHubPezkuwichain>(penpal_to_ah_foreign_assets_receiver_assertions);
penpal_to_ah.set_dispatchable::<PenpalA>(para_to_ah_dispatchable);
penpal_to_ah.assert();
let penpal_sender_balance_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(
system_para_native_asset_location.clone(),
&PenpalASender::get(),
)
});
let ah_receiver_balance_after = penpal_to_ah.receiver.balance;
let penpal_sender_assets_after = PenpalA::execute_with(|| {
type Assets = <PenpalA as PenpalAPallet>::Assets;
<Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalASender::get())
});
let ah_receiver_assets_after = AssetHubPezkuwichain::execute_with(|| {
type Assets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(
foreign_asset_at_asset_hub_pezkuwichain.clone().try_into().unwrap(),
&AssetHubPezkuwichainReceiver::get(),
)
});
// Sender's balance is reduced
assert!(penpal_sender_balance_after < penpal_sender_balance_before);
// Receiver's balance is increased
assert!(ah_receiver_balance_after > ah_receiver_balance_before);
// Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`;
// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
// should be non-zero
assert!(ah_receiver_balance_after < ah_receiver_balance_before + fee_amount_to_send);
// Sender's balance is reduced by exact amount
assert_eq!(penpal_sender_assets_before - asset_amount_to_send, penpal_sender_assets_after);
// Receiver's balance is increased by exact amount
assert_eq!(ah_receiver_assets_after, ah_receiver_assets_before + asset_amount_to_send);
///////////////////////////////////////////////////////////////////////
// Now test transferring foreign assets back from AssetHub to Penpal //
///////////////////////////////////////////////////////////////////////
// Move funds on AH from AHReceiver to AHSender
AssetHubPezkuwichain::execute_with(|| {
type ForeignAssets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets;
assert_ok!(ForeignAssets::transfer(
<AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(
AssetHubPezkuwichainReceiver::get()
),
foreign_asset_at_asset_hub_pezkuwichain.clone().try_into().unwrap(),
AssetHubPezkuwichainSender::get().into(),
asset_amount_to_send,
));
});
let ah_to_penpal_beneficiary_id = PenpalAReceiver::get();
let penpal_as_seen_by_ah = AssetHubPezkuwichain::sibling_location_of(PenpalA::para_id());
let ah_assets: Assets = vec![
(Parent, fee_amount_to_send).into(),
(foreign_asset_at_asset_hub_pezkuwichain.clone(), asset_amount_to_send).into(),
]
.into();
let fee_asset_id: AssetId = Parent.into();
// AH to Penpal test args
let ah_to_penpal_test_args = TestContext {
sender: AssetHubPezkuwichainSender::get(),
receiver: PenpalAReceiver::get(),
args: TestArgs::new_para(
penpal_as_seen_by_ah,
ah_to_penpal_beneficiary_id,
asset_amount_to_send,
ah_assets,
Some(asset_id_on_penpal),
fee_asset_id,
),
};
let mut ah_to_penpal = SystemParaToParaTest::new(ah_to_penpal_test_args);
let ah_sender_balance_before = ah_to_penpal.sender.balance;
let penpal_receiver_balance_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(
system_para_native_asset_location.clone(),
&PenpalAReceiver::get(),
)
});
let ah_sender_assets_before = AssetHubPezkuwichain::execute_with(|| {
type ForeignAssets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(
foreign_asset_at_asset_hub_pezkuwichain.clone().try_into().unwrap(),
&AssetHubPezkuwichainSender::get(),
)
});
let penpal_receiver_assets_before = PenpalA::execute_with(|| {
type Assets = <PenpalA as PenpalAPallet>::Assets;
<Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalAReceiver::get())
});
ah_to_penpal
.set_assertion::<AssetHubPezkuwichain>(ah_to_penpal_foreign_assets_sender_assertions);
ah_to_penpal.set_assertion::<PenpalA>(ah_to_penpal_foreign_assets_receiver_assertions);
ah_to_penpal.set_dispatchable::<AssetHubPezkuwichain>(ah_to_para_dispatchable);
ah_to_penpal.assert();
let ah_sender_balance_after = ah_to_penpal.sender.balance;
let penpal_receiver_balance_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(
system_para_native_asset_location,
&PenpalAReceiver::get(),
)
});
let ah_sender_assets_after = AssetHubPezkuwichain::execute_with(|| {
type ForeignAssets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(
foreign_asset_at_asset_hub_pezkuwichain.try_into().unwrap(),
&AssetHubPezkuwichainSender::get(),
)
});
let penpal_receiver_assets_after = PenpalA::execute_with(|| {
type Assets = <PenpalA as PenpalAPallet>::Assets;
<Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalAReceiver::get())
});
// Sender's balance is reduced
assert!(ah_sender_balance_after < ah_sender_balance_before);
// Receiver's balance is increased
assert!(penpal_receiver_balance_after > penpal_receiver_balance_before);
// Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`;
// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
// should be non-zero
assert!(penpal_receiver_balance_after < penpal_receiver_balance_before + fee_amount_to_send);
// Sender's balance is reduced by exact amount
assert_eq!(ah_sender_assets_before - asset_amount_to_send, ah_sender_assets_after);
// Receiver's balance is increased by exact amount
assert_eq!(penpal_receiver_assets_after, penpal_receiver_assets_before + asset_amount_to_send);
}
/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets should work
/// (using native reserve-based transfer for fees)
#[test]
fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt(
para_to_system_para_transfer_assets,
system_para_to_para_transfer_assets,
);
}
/// Teleport Native Asset from AssetHub to Teyrchain fails.
#[test]
fn teleport_to_untrusted_chain_fails() {
// Init values for Teyrchain Origin
let destination = AssetHubPezkuwichain::sibling_location_of(PenpalA::para_id());
let signed_origin = <AssetHubPezkuwichain as Chain>::RuntimeOrigin::signed(
AssetHubPezkuwichainSender::get().into(),
);
let roc_to_send: Balance = PEZKUWICHAIN_ED * 10000;
let roc_location = RelayLocation::get();
// Assets to send
let assets: Vec<Asset> = vec![(roc_location.clone(), roc_to_send).into()];
let fee_id: AssetId = roc_location.into();
// this should fail
AssetHubPezkuwichain::execute_with(|| {
let result = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::PezkuwiXcm::transfer_assets_using_type_and_then(
signed_origin.clone(),
bx!(destination.clone().into()),
bx!(assets.clone().into()),
bx!(TransferType::Teleport),
bx!(fee_id.into()),
bx!(TransferType::Teleport),
bx!(VersionedXcm::from(Xcm::<()>::new())),
Unlimited,
);
assert_err!(
result,
DispatchError::Module(pezsp_runtime::ModuleError {
index: 31,
error: [2, 0, 0, 0],
message: Some("Filtered")
})
);
});
// this should also fail
AssetHubPezkuwichain::execute_with(|| {
let xcm: Xcm<asset_hub_pezkuwichain_runtime::RuntimeCall> = Xcm(vec![
WithdrawAsset(assets.into()),
InitiateTeleport { assets: Wild(All), dest: destination, xcm: Xcm::<()>::new() },
]);
let result = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::PezkuwiXcm::execute(
signed_origin,
bx!(xcm::VersionedXcm::from(xcm)),
Weight::MAX,
);
assert!(result.is_err());
});
}
@@ -0,0 +1,264 @@
// 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::{
accounts::{ALICE, BOB},
USDT_ID,
};
use pezframe_support::{
dispatch::RawOrigin,
pezsp_runtime::traits::Dispatchable,
traits::{
fungible::Inspect,
fungibles::{Inspect as FungiblesInspect, Mutate},
},
};
use pezkuwi_runtime_common::impls::VersionedLocatableAsset;
use pezkuwichain_runtime_constants::currency::GRAND;
use teyrchains_common::AccountId;
use xcm_executor::traits::ConvertLocation;
// Fund Treasury account on Asset Hub from Treasury account on Relay Chain with TYRs.
#[test]
fn spend_roc_on_asset_hub() {
// initial treasury balance on Asset Hub in TYRs.
let treasury_balance = 9_000 * GRAND;
// the balance spend on Asset Hub.
let treasury_spend_balance = 1_000 * GRAND;
let init_alice_balance = AssetHubPezkuwichain::execute_with(|| {
<<AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::Balances as Inspect<_>>::balance(
&AssetHubPezkuwichain::account_id_of(ALICE),
)
});
Pezkuwichain::execute_with(|| {
type RuntimeEvent = <Pezkuwichain as Chain>::RuntimeEvent;
type RuntimeCall = <Pezkuwichain as Chain>::RuntimeCall;
type Runtime = <Pezkuwichain as Chain>::Runtime;
type Balances = <Pezkuwichain as PezkuwichainPallet>::Balances;
type Treasury = <Pezkuwichain as PezkuwichainPallet>::Treasury;
// Fund Treasury account on Asset Hub with TYRs.
let root = <Pezkuwichain as Chain>::RuntimeOrigin::root();
let treasury_account = Treasury::account_id();
// Mint assets to Treasury account on Relay Chain.
assert_ok!(Balances::force_set_balance(
root.clone(),
treasury_account.clone().into(),
treasury_balance * 2,
));
Dmp::make_teyrchain_reachable(1000);
let native_asset = Location::here();
let asset_hub_location: Location = [Teyrchain(1000)].into();
let treasury_location: Location = (Parent, PalletInstance(18)).into();
let teleport_call = RuntimeCall::Utility(pezpallet_utility::Call::<Runtime>::dispatch_as {
as_origin: bx!(PezkuwichainOriginCaller::system(RawOrigin::Signed(treasury_account))),
call: bx!(RuntimeCall::XcmPallet(pezpallet_xcm::Call::<Runtime>::teleport_assets {
dest: bx!(VersionedLocation::from(asset_hub_location.clone())),
beneficiary: bx!(VersionedLocation::from(treasury_location)),
assets: bx!(VersionedAssets::from(Assets::from(Asset {
id: native_asset.clone().into(),
fun: treasury_balance.into()
}))),
fee_asset_id: bx!(native_asset.into()),
})),
});
// Dispatched from Root to `dispatch_as` `Signed(treasury_account)`.
assert_ok!(teleport_call.dispatch(root));
assert_expected_events!(
Pezkuwichain,
vec![
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::Sent { .. }) => {},
]
);
});
Pezkuwichain::execute_with(|| {
type RuntimeEvent = <Pezkuwichain as Chain>::RuntimeEvent;
type RuntimeCall = <Pezkuwichain as Chain>::RuntimeCall;
type RuntimeOrigin = <Pezkuwichain as Chain>::RuntimeOrigin;
type Runtime = <Pezkuwichain as Chain>::Runtime;
type Treasury = <Pezkuwichain as PezkuwichainPallet>::Treasury;
// Fund Alice account from Pezkuwichain Treasury account on Asset Hub.
let treasury_origin: RuntimeOrigin =
pezkuwichain_governance::pezpallet_custom_origins::Origin::Treasurer.into();
let alice_location: Location = [Junction::AccountId32 {
network: None,
id: Pezkuwichain::account_id_of(ALICE).into(),
}]
.into();
let asset_hub_location: Location = [Teyrchain(1000)].into();
let native_asset = Location::parent();
let treasury_spend_call = RuntimeCall::Treasury(pezpallet_treasury::Call::<Runtime>::spend {
asset_kind: bx!(VersionedLocatableAsset::from((
asset_hub_location.clone(),
native_asset.into()
))),
amount: treasury_spend_balance,
beneficiary: bx!(VersionedLocation::from(alice_location)),
valid_from: None,
});
assert_ok!(treasury_spend_call.dispatch(treasury_origin));
// Claim the spend.
let bob_signed = RuntimeOrigin::signed(Pezkuwichain::account_id_of(BOB));
assert_ok!(Treasury::payout(bob_signed.clone(), 0));
assert_expected_events!(
Pezkuwichain,
vec![
RuntimeEvent::Treasury(pezpallet_treasury::Event::AssetSpendApproved { .. }) => {},
RuntimeEvent::Treasury(pezpallet_treasury::Event::Paid { .. }) => {},
]
);
});
AssetHubPezkuwichain::execute_with(|| {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
type Balances = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::Balances;
// Ensure that the funds deposited to Alice account.
let alice_account = AssetHubPezkuwichain::account_id_of(ALICE);
assert_eq!(
<Balances as Inspect<_>>::balance(&alice_account),
treasury_spend_balance + init_alice_balance
);
// Assert events triggered by xcm pay program:
// 1. treasury asset transferred to spend beneficiary;
// 2. response to Relay Chain Treasury pallet instance sent back;
// 3. XCM program completed;
assert_expected_events!(
AssetHubPezkuwichain,
vec![
RuntimeEvent::Balances(pezpallet_balances::Event::Transfer { .. }) => {},
RuntimeEvent::TeyrchainSystem(cumulus_pallet_teyrchain_system::Event::UpwardMessageSent { .. }) => {},
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed { success: true ,.. }) => {},
]
);
});
}
#[test]
fn create_and_claim_treasury_spend_in_usdt() {
const SPEND_AMOUNT: u128 = 10_000_000;
// treasury location from a sibling teyrchain.
let treasury_location: Location = Location::new(1, PalletInstance(18));
// treasury account on a sibling teyrchain.
let treasury_account =
ahr_xcm_config::LocationToAccountId::convert_location(&treasury_location).unwrap();
let asset_hub_location = Location::new(0, Teyrchain(AssetHubPezkuwichain::para_id().into()));
let root = <Pezkuwichain as Chain>::RuntimeOrigin::root();
// asset kind to be spent from the treasury.
let asset_kind: VersionedLocatableAsset =
(asset_hub_location, AssetId((PalletInstance(50), GeneralIndex(USDT_ID.into())).into()))
.into();
// treasury spend beneficiary.
let alice: AccountId = Pezkuwichain::account_id_of(ALICE);
let bob: AccountId = Pezkuwichain::account_id_of(BOB);
let bob_signed = <Pezkuwichain as Chain>::RuntimeOrigin::signed(bob.clone());
AssetHubPezkuwichain::execute_with(|| {
type Assets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::Assets;
// USDT created at genesis, mint some assets to the treasury account.
assert_ok!(<Assets as Mutate<_>>::mint_into(USDT_ID, &treasury_account, SPEND_AMOUNT * 4));
// beneficiary has zero balance.
assert_eq!(<Assets as FungiblesInspect<_>>::balance(USDT_ID, &alice,), 0u128,);
});
Pezkuwichain::execute_with(|| {
type RuntimeEvent = <Pezkuwichain as Chain>::RuntimeEvent;
type Treasury = <Pezkuwichain as PezkuwichainPallet>::Treasury;
type AssetRate = <Pezkuwichain as PezkuwichainPallet>::AssetRate;
// create a conversion rate from `asset_kind` to the native currency.
assert_ok!(AssetRate::create(root.clone(), Box::new(asset_kind.clone()), 2.into()));
Dmp::make_teyrchain_reachable(1000);
// create and approve a treasury spend.
assert_ok!(Treasury::spend(
root,
Box::new(asset_kind),
SPEND_AMOUNT,
Box::new(Location::new(0, Into::<[u8; 32]>::into(alice.clone())).into()),
None,
));
// claim the spend.
assert_ok!(Treasury::payout(bob_signed.clone(), 0));
assert_expected_events!(
Pezkuwichain,
vec![
RuntimeEvent::Treasury(pezpallet_treasury::Event::Paid { .. }) => {},
]
);
});
AssetHubPezkuwichain::execute_with(|| {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
type Assets = <AssetHubPezkuwichain as AssetHubPezkuwichainPallet>::Assets;
// assert events triggered by xcm pay program
// 1. treasury asset transferred to spend beneficiary
// 2. response to Relay Chain treasury pallet instance sent back
// 3. XCM program completed
assert_expected_events!(
AssetHubPezkuwichain,
vec![
RuntimeEvent::Assets(pezpallet_assets::Event::Transferred { asset_id: id, from, to, amount }) => {
id: id == &USDT_ID,
from: from == &treasury_account,
to: to == &alice,
amount: amount == &SPEND_AMOUNT,
},
RuntimeEvent::TeyrchainSystem(cumulus_pallet_teyrchain_system::Event::UpwardMessageSent { .. }) => {},
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed { success: true ,.. }) => {},
]
);
// beneficiary received the assets from the treasury.
assert_eq!(<Assets as FungiblesInspect<_>>::balance(USDT_ID, &alice,), SPEND_AMOUNT,);
});
Pezkuwichain::execute_with(|| {
type RuntimeEvent = <Pezkuwichain as Chain>::RuntimeEvent;
type Treasury = <Pezkuwichain as PezkuwichainPallet>::Treasury;
// check the payment status to ensure the response from the AssetHub was received.
assert_ok!(Treasury::check_status(bob_signed, 0));
assert_expected_events!(
Pezkuwichain,
vec![
RuntimeEvent::Treasury(pezpallet_treasury::Event::SpendProcessed { .. }) => {},
]
);
});
}
@@ -0,0 +1,298 @@
// 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 for XCM fee estimation in the runtime.
use crate::imports::*;
use emulated_integration_tests_common::test_can_estimate_and_pay_exact_fees;
use pezframe_support::dispatch::RawOrigin;
use xcm_runtime_apis::{
dry_run::runtime_decl_for_dry_run_api::DryRunApiV2,
fees::runtime_decl_for_xcm_payment_api::XcmPaymentApiV2,
};
fn sender_assertions(test: ParaToParaThroughAHTest) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
PenpalA::assert_xcm_pallet_attempted_complete(None);
assert_expected_events!(
PenpalA,
vec![
RuntimeEvent::ForeignAssets(
pezpallet_assets::Event::Burned { asset_id, owner, balance }
) => {
asset_id: *asset_id == Location::new(1, []),
owner: *owner == test.sender.account_id,
balance: *balance == test.args.amount,
},
]
);
}
fn hop_assertions(test: ParaToParaThroughAHTest) {
type RuntimeEvent = <AssetHubPezkuwichain as Chain>::RuntimeEvent;
AssetHubPezkuwichain::assert_xcmp_queue_success(None);
assert_expected_events!(
AssetHubPezkuwichain,
vec![
RuntimeEvent::Balances(
pezpallet_balances::Event::Burned { amount, .. }
) => {
amount: *amount > test.args.amount * 90/100,
},
]
);
}
fn receiver_assertions(test: ParaToParaThroughAHTest) {
type RuntimeEvent = <PenpalB as Chain>::RuntimeEvent;
PenpalB::assert_xcmp_queue_success(None);
assert_expected_events!(
PenpalB,
vec![
RuntimeEvent::ForeignAssets(
pezpallet_assets::Event::Issued { asset_id, owner, .. }
) => {
asset_id: *asset_id == Location::new(1, []),
owner: *owner == test.receiver.account_id,
},
]
);
}
fn transfer_assets_para_to_para_through_ah_call(
test: ParaToParaThroughAHTest,
) -> <PenpalA as Chain>::RuntimeCall {
type RuntimeCall = <PenpalA as Chain>::RuntimeCall;
let asset_hub_location: Location =
PenpalB::sibling_location_of(AssetHubPezkuwichain::para_id());
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
assets: Wild(AllCounted(test.args.assets.len() as u32)),
beneficiary: test.args.beneficiary,
}]);
RuntimeCall::PezkuwiXcm(pezpallet_xcm::Call::transfer_assets_using_type_and_then {
dest: bx!(test.args.dest.into()),
assets: bx!(test.args.assets.clone().into()),
assets_transfer_type: bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())),
remote_fees_id: bx!(VersionedAssetId::from(AssetId(Location::new(1, [])))),
fees_transfer_type: bx!(TransferType::RemoteReserve(asset_hub_location.into())),
custom_xcm_on_dest: bx!(VersionedXcm::from(custom_xcm_on_dest)),
weight_limit: test.args.weight_limit,
})
}
/// We are able to dry-run and estimate the fees for a multi-hop XCM journey.
/// Scenario: Alice on PenpalA has some DOTs and wants to send them to PenpalB.
/// We want to know the fees using the `DryRunApi` and `XcmPaymentApi`.
#[test]
fn multi_hop_works() {
let destination = PenpalA::sibling_location_of(PenpalB::para_id());
let sender = PenpalASender::get();
let amount_to_send = 1_000_000_000_000;
let asset_owner = PenpalAssetOwner::get();
let assets: Assets = (Parent, amount_to_send).into();
let fee_asset_id: AssetId = Parent.into();
let relay_native_asset_location = Location::parent();
let sender_as_seen_by_ah = AssetHubPezkuwichain::sibling_location_of(PenpalA::para_id());
let sov_of_sender_on_ah =
AssetHubPezkuwichain::sovereign_account_id_of(sender_as_seen_by_ah.clone());
// fund Teyrchain's sender account
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner.clone()),
relay_native_asset_location.clone(),
sender.clone(),
amount_to_send * 2,
);
// fund the Teyrchain Origin's SA on AssetHub with the native tokens held in reserve.
AssetHubPezkuwichain::fund_accounts(vec![(sov_of_sender_on_ah.clone(), amount_to_send * 2)]);
// Init values for Teyrchain Destination
let beneficiary_id = PenpalBReceiver::get();
let test_args = TestContext {
sender: PenpalASender::get(), // Bob in PenpalB.
receiver: PenpalBReceiver::get(), // Alice.
args: TestArgs::new_para(
destination,
beneficiary_id.clone(),
amount_to_send,
assets,
None,
fee_asset_id,
),
};
let mut test = ParaToParaThroughAHTest::new(test_args);
// We get them from the PenpalA closure.
let mut delivery_fees_amount = 0;
let mut remote_message = VersionedXcm::from(Xcm(Vec::new()));
<PenpalA as TestExt>::execute_with(|| {
type Runtime = <PenpalA as Chain>::Runtime;
type OriginCaller = <PenpalA as Chain>::OriginCaller;
let call = transfer_assets_para_to_para_through_ah_call(test.clone());
let origin = OriginCaller::system(RawOrigin::Signed(sender.clone()));
let result = Runtime::dry_run_call(origin, call, xcm::prelude::XCM_VERSION).unwrap();
// We filter the result to get only the messages we are interested in.
let (destination_to_query, messages_to_query) = &result
.forwarded_xcms
.iter()
.find(|(destination, _)| {
*destination == VersionedLocation::from(Location::new(1, [Teyrchain(1000)]))
})
.unwrap();
assert_eq!(messages_to_query.len(), 1);
remote_message = messages_to_query[0].clone();
let asset_id_for_delivery_fees = VersionedAssetId::from(Location::parent());
let delivery_fees = Runtime::query_delivery_fees(
destination_to_query.clone(),
remote_message.clone(),
asset_id_for_delivery_fees,
)
.unwrap();
delivery_fees_amount = get_amount_from_versioned_assets(delivery_fees);
});
// These are set in the AssetHub closure.
let mut intermediate_execution_fees = 0;
let mut intermediate_delivery_fees_amount = 0;
let mut intermediate_remote_message = VersionedXcm::from(Xcm::<()>(Vec::new()));
<AssetHubPezkuwichain as TestExt>::execute_with(|| {
type Runtime = <AssetHubPezkuwichain as Chain>::Runtime;
type RuntimeCall = <AssetHubPezkuwichain as Chain>::RuntimeCall;
// First we get the execution fees.
let weight = Runtime::query_xcm_weight(remote_message.clone()).unwrap();
intermediate_execution_fees = Runtime::query_weight_to_asset_fee(
weight,
VersionedAssetId::from(AssetId(Location::new(1, []))),
)
.unwrap();
// We have to do this to turn `VersionedXcm<()>` into `VersionedXcm<RuntimeCall>`.
let xcm_program = VersionedXcm::from(Xcm::<RuntimeCall>::from(
remote_message.clone().try_into().unwrap(),
));
// Now we get the delivery fees to the final destination.
let result =
Runtime::dry_run_xcm(sender_as_seen_by_ah.clone().into(), xcm_program).unwrap();
let (destination_to_query, messages_to_query) = &result
.forwarded_xcms
.iter()
.find(|(destination, _)| {
*destination == VersionedLocation::from(Location::new(1, [Teyrchain(2001)]))
})
.unwrap();
// There's actually two messages here.
// One created when the message we sent from PenpalA arrived and was executed.
// The second one when we dry-run the xcm.
// We could've gotten the message from the queue without having to dry-run, but
// offchain applications would have to dry-run, so we do it here as well.
intermediate_remote_message = messages_to_query[0].clone();
let asset_id_for_delivery_fees = VersionedAssetId::from(Location::parent());
let delivery_fees = Runtime::query_delivery_fees(
destination_to_query.clone(),
intermediate_remote_message.clone(),
asset_id_for_delivery_fees,
)
.unwrap();
intermediate_delivery_fees_amount = get_amount_from_versioned_assets(delivery_fees);
});
// Get the final execution fees in the destination.
let mut final_execution_fees = 0;
<PenpalB as TestExt>::execute_with(|| {
type Runtime = <PenpalA as Chain>::Runtime;
let weight = Runtime::query_xcm_weight(intermediate_remote_message.clone()).unwrap();
final_execution_fees = Runtime::query_weight_to_asset_fee(
weight,
VersionedAssetId::from(AssetId(Location::parent())),
)
.unwrap();
});
// Dry-running is done.
PenpalA::reset_ext();
AssetHubPezkuwichain::reset_ext();
PenpalB::reset_ext();
// Fund accounts again.
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner),
relay_native_asset_location.clone(),
sender.clone(),
amount_to_send * 2,
);
AssetHubPezkuwichain::fund_accounts(vec![(sov_of_sender_on_ah, amount_to_send * 2)]);
// Actually run the extrinsic.
let sender_assets_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &sender)
});
let receiver_assets_before = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &beneficiary_id)
});
test.set_assertion::<PenpalA>(sender_assertions);
test.set_assertion::<AssetHubPezkuwichain>(hop_assertions);
test.set_assertion::<PenpalB>(receiver_assertions);
let call = transfer_assets_para_to_para_through_ah_call(test.clone());
test.set_call(call);
test.assert();
let sender_assets_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &sender)
});
let receiver_assets_after = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &beneficiary_id)
});
// We know the exact fees on every hop.
assert_eq!(
sender_assets_after,
sender_assets_before - amount_to_send - delivery_fees_amount /* This is charged directly
* from the sender's
* account. */
);
assert_eq!(
receiver_assets_after,
receiver_assets_before + amount_to_send -
intermediate_execution_fees -
intermediate_delivery_fees_amount -
final_execution_fees
);
}
#[test]
fn multi_hop_pay_fees_works() {
test_can_estimate_and_pay_exact_fees!(
PenpalA,
AssetHubPezkuwichain,
PenpalB,
(Parent, 1_000_000_000_000u128),
Penpal
);
}
@@ -0,0 +1,71 @@
[package]
name = "asset-hub-zagros-integration-tests"
version = "1.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
description = "Asset Hub Zagros runtime integration tests with xcm-emulator"
publish = false
[lints]
workspace = true
[dependencies]
assert_matches = { workspace = true }
codec = { workspace = true }
# Bizinikiwi
pezframe-support = { workspace = true }
pezframe-system = { workspace = true }
pezpallet-asset-conversion = { workspace = true }
pezpallet-asset-rewards = { workspace = true }
pezpallet-assets = { workspace = true }
pezpallet-balances = { workspace = true }
pezpallet-message-queue = { workspace = true }
pezpallet-treasury = { workspace = true }
pezsp-core = { workspace = true }
pezsp-runtime = { workspace = true }
# Pezkuwi
pezpallet-xcm = { workspace = true }
pezkuwi-runtime-common = { workspace = true, default-features = true }
xcm = { workspace = true }
xcm-builder = { workspace = true }
xcm-executor = { workspace = true }
xcm-runtime-apis = { workspace = true }
# Pezcumulus
asset-test-utils = { workspace = true, default-features = true }
pezcumulus-pezpallet-teyrchain-system = { workspace = true }
pezcumulus-pezpallet-xcmp-queue = { workspace = true }
emulated-integration-tests-common = { workspace = true }
teyrchains-common = { workspace = true, default-features = true }
zagros-system-emulated-network = { workspace = true }
[dev-dependencies]
pezsp-tracing = { workspace = true, default-features = true }
[features]
runtime-benchmarks = [
"asset-test-utils/runtime-benchmarks",
"pezcumulus-pezpallet-teyrchain-system/runtime-benchmarks",
"pezcumulus-pezpallet-xcmp-queue/runtime-benchmarks",
"emulated-integration-tests-common/runtime-benchmarks",
"pezframe-support/runtime-benchmarks",
"pezframe-system/runtime-benchmarks",
"pezpallet-asset-conversion/runtime-benchmarks",
"pezpallet-asset-rewards/runtime-benchmarks",
"pezpallet-assets/runtime-benchmarks",
"pezpallet-balances/runtime-benchmarks",
"pezpallet-message-queue/runtime-benchmarks",
"pezpallet-treasury/runtime-benchmarks",
"pezpallet-xcm/runtime-benchmarks",
"pezkuwi-runtime-common/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"teyrchains-common/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
"xcm-runtime-apis/runtime-benchmarks",
"xcm/runtime-benchmarks",
"zagros-system-emulated-network/runtime-benchmarks",
]
@@ -0,0 +1,126 @@
// 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 {
pub(crate) use codec::Encode;
// Bizinikiwi
pub(crate) use pezframe_support::{
assert_err, assert_ok,
pezpallet_prelude::Weight,
pezsp_runtime::{DispatchError, DispatchResult, ModuleError},
traits::fungibles::Inspect,
BoundedVec,
};
// Pezkuwi
pub(crate) use xcm::{
latest::{AssetTransferFilter, PEZKUWICHAIN_GENESIS_HASH, ZAGROS_GENESIS_HASH},
prelude::{AccountId32 as AccountId32Junction, *},
};
pub(crate) use xcm_executor::traits::TransferType;
// Pezcumulus
pub(crate) use asset_test_utils::xcm_helpers;
pub(crate) use emulated_integration_tests_common::{
accounts::DUMMY_EMPTY,
test_relay_is_trusted_teleporter, test_teyrchain_is_trusted_teleporter,
test_teyrchain_is_trusted_teleporter_for_relay,
test_xcm_fee_querying_apis_work_for_asset_hub,
xcm_emulator::{
assert_expected_events, bx, Chain, RelayChain as Relay, Test, TestArgs, TestContext,
TestExt, Teyrchain as Para,
},
xcm_helpers::{
fee_asset, find_mq_processed_id, find_xcm_sent_message_id,
get_amount_from_versioned_assets, non_fee_asset, xcm_transact_paid_execution,
},
xcm_simulator::helpers::TopicIdTracker,
PenpalATeleportableAssetLocation, ASSETS_PALLET_ID, RESERVABLE_ASSET_ID, USDT_ID, XCM_V3,
};
pub(crate) use teyrchains_common::{AccountId, Balance};
pub(crate) use zagros_system_emulated_network::{
asset_hub_zagros_emulated_chain::{
asset_hub_zagros_runtime::{
self,
governance::TreasuryAccount,
xcm_config::{
self as ahw_xcm_config, XcmConfig as AssetHubZagrosXcmConfig,
ZagrosLocation as RelayLocation,
},
AssetConversionOrigin as AssetHubZagrosAssetConversionOrigin,
ExistentialDeposit as AssetHubZagrosExistentialDeposit, ForeignAssetReserveData,
},
genesis::{AssetHubZagrosAssetOwner, ED as ASSET_HUB_ZAGROS_ED},
AssetHubZagrosParaPallet as AssetHubZagrosPallet,
},
bridge_hub_zagros_emulated_chain::{
bridge_hub_zagros_runtime::xcm_config::{self as bhw_xcm_config},
BridgeHubZagrosParaPallet as BridgeHubZagrosPallet,
},
collectives_zagros_emulated_chain::CollectivesZagrosParaPallet as CollectivesZagrosPallet,
coretime_zagros_emulated_chain::CoretimeZagrosParaPallet as CoretimeZagrosPallet,
penpal_emulated_chain::{
penpal_runtime::xcm_config::{
CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub,
LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub,
LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub,
UniversalLocation as PenpalUniversalLocation,
UsdtFromAssetHub as PenpalUsdtFromAssetHub,
},
PenpalAParaPallet as PenpalAPallet, PenpalAssetOwner,
PenpalBParaPallet as PenpalBPallet,
},
people_zagros_emulated_chain::PeopleZagrosParaPallet as PeopleZagrosPallet,
zagros_emulated_chain::{
genesis::ED as ZAGROS_ED,
zagros_runtime::{
governance::pezpallet_custom_origins::Origin::Treasurer,
xcm_config::{
UniversalLocation as ZagrosUniversalLocation, XcmConfig as ZagrosXcmConfig,
},
Dmp,
},
ZagrosRelayPallet as ZagrosPallet,
},
AssetHubZagrosPara as AssetHubZagros, AssetHubZagrosParaReceiver as AssetHubZagrosReceiver,
AssetHubZagrosParaSender as AssetHubZagrosSender, BridgeHubZagrosPara as BridgeHubZagros,
BridgeHubZagrosParaReceiver as BridgeHubZagrosReceiver,
CollectivesZagrosPara as CollectivesZagros, CoretimeZagrosPara as CoretimeZagros,
PenpalAPara as PenpalA, PenpalAParaReceiver as PenpalAReceiver,
PenpalAParaSender as PenpalASender, PenpalBPara as PenpalB,
PenpalBParaReceiver as PenpalBReceiver, PeopleZagrosPara as PeopleZagros,
ZagrosRelay as Zagros, ZagrosRelayReceiver as ZagrosReceiver,
ZagrosRelaySender as ZagrosSender,
};
pub(crate) const ASSET_ID: u32 = 3;
pub(crate) const ASSET_MIN_BALANCE: u128 = 1000;
pub(crate) type RelayToParaTest = Test<Zagros, PenpalA>;
pub(crate) type ParaToRelayTest = Test<PenpalA, Zagros>;
pub(crate) type RelayToSystemParaTest = Test<Zagros, AssetHubZagros>;
pub(crate) type SystemParaToRelayTest = Test<AssetHubZagros, Zagros>;
pub(crate) type SystemParaToParaTest = Test<AssetHubZagros, PenpalA>;
pub(crate) type ParaToSystemParaTest = Test<PenpalA, AssetHubZagros>;
pub(crate) type ParaToParaThroughRelayTest = Test<PenpalA, PenpalB, Zagros>;
pub(crate) type ParaToParaThroughAHTest = Test<PenpalA, PenpalB, AssetHubZagros>;
pub(crate) type RelayToParaThroughAHTest = Test<Zagros, PenpalA, AssetHubZagros>;
pub(crate) type PenpalToRelayThroughAHTest = Test<PenpalA, Zagros, AssetHubZagros>;
}
#[cfg(test)]
mod tests;
@@ -0,0 +1,277 @@
// 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 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 Asset Hub we don't want to support aliasing from other chains:
// - there is no real world demand for it, the direction is usually reversed, users already have
// accounts on AH and want to use them cross-chain on other chains,
// - without real world demand, it's better to keep AH permissions as tight as possible.
// Aliasing same account doesn't work on AH.
test_cross_chain_alias!(
vec![
// between BH and AH: denied
(BridgeHubZagros, AssetHubZagros, TELEPORT_FEES, DENIED),
// between Collectives and AH: denied
(CollectivesZagros, AssetHubZagros, TELEPORT_FEES, DENIED),
// between Coretime and AH: denied
(CoretimeZagros, AssetHubZagros, TELEPORT_FEES, DENIED),
// between People and AH: denied
(PeopleZagros, AssetHubZagros, TELEPORT_FEES, DENIED),
// between Penpal and AH: denied
(PenpalB, AssetHubZagros, 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 BH and AH: denied
(BridgeHubZagros, AssetHubZagros, TELEPORT_FEES, DENIED),
// between Collectives and AH: denied
(CollectivesZagros, AssetHubZagros, TELEPORT_FEES, DENIED),
// between Coretime and AH: denied
(CoretimeZagros, AssetHubZagros, TELEPORT_FEES, DENIED),
// between People and AH: denied
(PeopleZagros, AssetHubZagros, TELEPORT_FEES, DENIED),
// between Penpal and AH: denied
(PenpalB, AssetHubZagros, RESERVE_TRANSFER_FEES, DENIED)
],
origin,
target,
fees
);
}
#[test]
fn aliasing_child_locations() {
use AssetHubZagrosXcmConfig as XcmConfig;
AssetHubZagros::execute_with(|| {
// 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() {
use AssetHubZagrosXcmConfig as XcmConfig;
AssetHubZagros::execute_with(|| {
// Does not allow local/AH root to alias other (non-descendant) locations.
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 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(1, 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);
AssetHubZagros::fund_accounts(vec![(target.clone(), fees * 10)]);
// let's authorize `origin` on Penpal to alias `target` on AssetHub
AssetHubZagros::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!(<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::add_authorized_alias(
<AssetHubZagros 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 BH and AssetHub: denied
(BridgeHubZagros, AssetHubZagros, TELEPORT_FEES, DENIED),
// between Collectives and AssetHub: denied
(CollectivesZagros, AssetHubZagros, TELEPORT_FEES, DENIED),
// between People and AssetHub: denied
(PeopleZagros, AssetHubZagros, TELEPORT_FEES, DENIED),
// between Penpal and AssetHub: denied
(PenpalB, AssetHubZagros, 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 BH and AssetHub: denied
(BridgeHubZagros, AssetHubZagros, TELEPORT_FEES, DENIED),
// between Collectives and AssetHub: denied
(CollectivesZagros, AssetHubZagros, TELEPORT_FEES, DENIED),
// between People and AssetHub: denied
(PeopleZagros, AssetHubZagros, TELEPORT_FEES, DENIED),
// between Penpal and AssetHub: allowed
(PenpalB, AssetHubZagros, RESERVE_TRANSFER_FEES, ALLOWED)
],
origin,
target,
fees
);
// remove authorization for `origin` on Penpal to alias `target` on AssetHub
AssetHubZagros::execute_with(|| {
// `target` removes all authorized aliases
assert_ok!(
<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::remove_all_authorized_aliases(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(target.clone())
)
);
});
// Verify `penpal::origin` can no longer alias into `target` on AssetHub.
test_cross_chain_alias!(
vec![(PenpalB, AssetHubZagros, 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 = AssetHubZagrosExistentialDeposit::get();
let assets: Assets = (Parent, amount).into();
test_chain_can_claim_assets!(
AssetHubZagros,
RuntimeCall,
NetworkId::ByGenesis(ZAGROS_GENESIS_HASH),
assets,
amount
);
}
@@ -0,0 +1,327 @@
// 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::{
assets_balance_on, create_pool_with_wnd_on, foreign_balance_on,
imports::{
asset_hub_zagros_runtime::{ExistentialDeposit, Runtime},
*,
},
};
use asset_hub_zagros_runtime::{
xcm_config::ZagrosLocation, Balances, ForeignAssets, PezkuwiXcm, RuntimeOrigin,
};
use emulated_integration_tests_common::{accounts::ALICE, xcm_emulator::TestExt};
use pezframe_support::{
assert_err_ignore_postinfo, assert_ok,
traits::fungible::{Inspect, Mutate},
};
use pezsp_tracing::capture_test_logs;
use std::convert::Into;
use teyrchains_common::{AccountId, Balance};
use xcm::latest::{Assets, Error as XcmError, Location, Xcm};
const UNITS: Balance = 1_000_000_000;
#[test]
fn exchange_asset_success() {
test_exchange_asset(true, 500 * UNITS, 665 * UNITS, None);
}
#[test]
fn exchange_asset_insufficient_liquidity() {
let log_capture = capture_test_logs!({
test_exchange_asset(
true,
1_000 * UNITS,
2_000 * UNITS,
Some(InstructionError { index: 1, error: XcmError::NoDeal }),
);
});
assert!(log_capture.contains("NoDeal"));
}
#[test]
fn exchange_asset_insufficient_balance() {
let log_capture = capture_test_logs!({
test_exchange_asset(
true,
5_000 * UNITS,
1_665 * UNITS,
Some(InstructionError { index: 0, error: XcmError::FailedToTransactAsset("") }),
);
});
assert!(log_capture.contains("Funds are unavailable"));
}
#[test]
fn exchange_asset_pool_not_created() {
test_exchange_asset(
false,
500 * UNITS,
665 * UNITS,
Some(InstructionError { index: 1, error: XcmError::NoDeal }),
);
}
fn test_exchange_asset(
create_pool: bool,
give_amount: Balance,
want_amount: Balance,
expected_error: Option<InstructionError>,
) {
let alice: AccountId = Zagros::account_id_of(ALICE);
let native_asset_location = ZagrosLocation::get();
let native_asset_id = AssetId(native_asset_location.clone());
let origin = RuntimeOrigin::signed(alice.clone());
let asset_location = Location::new(1, [Teyrchain(2001)]);
let asset_id = AssetId(asset_location.clone());
// Setup initial state
AssetHubZagros::execute_with(|| {
assert_ok!(<Balances as Mutate<_>>::mint_into(
&alice,
ExistentialDeposit::get() + (1_000 * UNITS)
));
assert_ok!(ForeignAssets::force_create(
RuntimeOrigin::root(),
asset_location.clone().into(),
alice.clone().into(),
true,
1
));
});
if create_pool {
create_pool_with_wnd_on!(AssetHubZagros, asset_location.clone(), true, alice.clone());
}
// Execute and verify swap
AssetHubZagros::execute_with(|| {
let foreign_balance_before = ForeignAssets::balance(asset_location.clone(), &alice);
let wnd_balance_before = Balances::total_balance(&alice);
let give: Assets = (native_asset_id, give_amount).into();
let want: Assets = (asset_id, want_amount).into();
let xcm = Xcm(vec![
WithdrawAsset(give.clone().into()),
ExchangeAsset { give: give.into(), want: want.into(), maximal: true },
DepositAsset { assets: Wild(All), beneficiary: alice.clone().into() },
]);
let result = PezkuwiXcm::execute(origin, bx!(xcm::VersionedXcm::from(xcm)), Weight::MAX);
let foreign_balance_after = ForeignAssets::balance(asset_location, &alice);
let wnd_balance_after = Balances::total_balance(&alice);
if let Some(InstructionError { index, error }) = expected_error {
assert_err_ignore_postinfo!(
result,
pezpallet_xcm::Error::<Runtime>::LocalExecutionIncompleteWithError {
index,
error: error.into()
}
);
assert_eq!(
foreign_balance_after, foreign_balance_before,
"Foreign balance changed unexpectedly: got {foreign_balance_after}, expected {foreign_balance_before}"
);
assert_eq!(
wnd_balance_after, wnd_balance_before,
"ZGR balance changed unexpectedly: got {wnd_balance_after}, expected {wnd_balance_before}"
);
} else {
assert_ok!(result);
assert!(
foreign_balance_after >= foreign_balance_before + want_amount,
"Expected foreign balance to increase by at least {want_amount} units, got {foreign_balance_after} from {foreign_balance_before}"
);
assert_eq!(
wnd_balance_after, wnd_balance_before - give_amount,
"Expected ZGR balance to decrease by {give_amount} units, got {wnd_balance_after} from {wnd_balance_before}"
);
}
});
}
#[test]
fn exchange_asset_from_penpal_via_asset_hub_back_to_penpal() {
let sender = PenpalASender::get();
let sov_of_penpal_on_asset_hub = AssetHubZagros::sovereign_account_id_of(
AssetHubZagros::sibling_location_of(PenpalA::para_id()),
);
let wnd_from_teyrchain_pov: Location = RelayLocation::get();
let usdt_asset_hub_pov =
Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())]);
let usdt_penpal_pov = PenpalUsdtFromAssetHub::get();
let amount_of_wnd_to_transfer_to_ah = ZAGROS_ED * 1_000_000_000;
let amount_of_usdt_we_want_from_exchange = 1_000_000_000;
let mut topic_id_tracker = TopicIdTracker::new();
// SA-of-Penpal-on-AHW should contain ZGR amount equal at least the amount that will be
// transferred-in to AH Since AH is the reserve for ZGR
AssetHubZagros::fund_accounts(vec![(
sov_of_penpal_on_asset_hub.clone().into(),
ASSET_HUB_ZAGROS_ED + amount_of_wnd_to_transfer_to_ah,
)]);
// Give the sender enough ZGR
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
wnd_from_teyrchain_pov.clone(),
sender.clone(),
amount_of_wnd_to_transfer_to_ah,
);
// We create a pool between ZGR and USDT in AssetHub so we can do the exchange
create_pool_with_wnd_on!(
AssetHubZagros,
usdt_asset_hub_pov.clone(),
false,
AssetHubZagrosSender::get(),
1_000_000_000_000,
20_000_000_000
);
// Query initial balances
let sender_usdt_on_penpal_before =
foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender);
let sender_usdt_on_ah_before = assets_balance_on!(AssetHubZagros, USDT_ID, &sender);
let asset_hub_location_penpal_pov = PenpalA::sibling_location_of(AssetHubZagros::para_id());
let penpal_location_ah_pov = AssetHubZagros::sibling_location_of(PenpalA::para_id());
PenpalA::execute_with(|| {
let sender_signed_origin = <PenpalA as Chain>::RuntimeOrigin::signed(sender.clone());
let local_fees_amount = 80_000_000_000_000u128;
let remote_fees_amount = 200_000_000_000_000u128;
let penpal_local_fees: Asset = (wnd_from_teyrchain_pov.clone(), local_fees_amount).into();
let ah_remote_fees: Asset = (wnd_from_teyrchain_pov.clone(), remote_fees_amount).into();
let penpal_remote_fees: Asset = (wnd_from_teyrchain_pov.clone(), remote_fees_amount).into();
let wnd_to_withdraw: Asset =
(wnd_from_teyrchain_pov.clone(), amount_of_wnd_to_transfer_to_ah).into();
// xcm to be executed by penpal, sent by ah
let xcm_back_on_penpal = Xcm(vec![
RefundSurplus,
DepositAsset { assets: Wild(All), beneficiary: sender.clone().into() },
]);
// xcm to be executed by ah, sent by penpal
let xcm_on_ah = Xcm(vec![
ExchangeAsset {
give: Definite((wnd_from_teyrchain_pov.clone(), 100_000_000_000u128).into()),
want: (usdt_asset_hub_pov.clone(), amount_of_usdt_we_want_from_exchange).into(),
maximal: false,
},
InitiateTransfer {
destination: penpal_location_ah_pov,
remote_fees: Some(AssetTransferFilter::ReserveDeposit(
penpal_remote_fees.clone().into(),
)),
preserve_origin: false,
assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveDeposit(Wild(
All,
))]),
remote_xcm: xcm_back_on_penpal,
},
RefundSurplus,
DepositAsset { assets: Wild(All), beneficiary: sender.clone().into() },
]);
// xcm to be executed locally on penpal as starting point
let xcm = Xcm::<()>(vec![
WithdrawAsset(wnd_to_withdraw.into()),
PayFees { asset: penpal_local_fees },
InitiateTransfer {
destination: asset_hub_location_penpal_pov,
remote_fees: Some(AssetTransferFilter::ReserveWithdraw(
ah_remote_fees.clone().into(),
)),
preserve_origin: false,
assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveWithdraw(
Wild(All),
)]),
remote_xcm: xcm_on_ah,
},
RefundSurplus,
DepositAsset { assets: Wild(All), beneficiary: sender.clone().into() },
]);
// initiate transaction
<PenpalA as PenpalAPallet>::PezkuwiXcm::execute(
sender_signed_origin,
bx!(xcm::VersionedXcm::from(xcm.into())),
Weight::MAX,
)
.unwrap();
// verify expected events;
PenpalA::assert_xcm_pallet_attempted_complete(None);
let msg_sent_id = find_xcm_sent_message_id::<PenpalA>().expect("Missing Sent Event");
topic_id_tracker.insert("PenpalA_sent", msg_sent_id.into());
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
RuntimeEvent::AssetConversion(
pezpallet_asset_conversion::Event::SwapCreditExecuted { amount_out, ..}
) => { amount_out: *amount_out == amount_of_usdt_we_want_from_exchange, },
]
);
let mq_prc_id = find_mq_processed_id::<AssetHubZagros>().expect("Missing Processed Event");
topic_id_tracker.insert("AssetHubZagros_received", mq_prc_id);
let msg_sent_id = find_xcm_sent_message_id::<AssetHubZagros>().expect("Missing Sent Event");
topic_id_tracker.insert("AssetHubZagros_sent", msg_sent_id.into());
});
PenpalA::execute_with(|| {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
assert_expected_events!(
PenpalA,
vec![
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
let mq_prc_id = find_mq_processed_id::<PenpalA>().expect("Missing Processed Event");
topic_id_tracker.insert("PenpalA_received", mq_prc_id);
});
topic_id_tracker.assert_unique();
// Query final balances
let sender_usdt_on_ah_after = assets_balance_on!(AssetHubZagros, USDT_ID, &sender);
let sender_usdt_on_penpal_after =
foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender);
// Receiver's balance is increased by usdt amount we got from exchange
assert_eq!(
sender_usdt_on_penpal_after,
sender_usdt_on_penpal_before + amount_of_usdt_we_want_from_exchange
);
// Usdt amount on senders account AH side should stay the same i.e. all usdt came from exchange
// not free balance
assert_eq!(sender_usdt_on_ah_before, sender_usdt_on_ah_after);
}
@@ -0,0 +1,121 @@
// 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::{
accounts::{ALICE, BOB},
USDT_ID,
};
use pezframe_support::traits::fungibles::{Inspect, Mutate};
use pezkuwi_runtime_common::impls::VersionedLocatableAsset;
use xcm_executor::traits::ConvertLocation;
#[test]
fn create_and_claim_treasury_spend() {
const SPEND_AMOUNT: u128 = 1_000_000_000;
// treasury location from a sibling teyrchain.
let treasury_location: Location =
Location::new(1, [Teyrchain(CollectivesZagros::para_id().into()), PalletInstance(65)]);
// treasury account on a sibling teyrchain.
let treasury_account =
ahw_xcm_config::LocationToAccountId::convert_location(&treasury_location).unwrap();
let asset_hub_location = Location::new(1, [Teyrchain(AssetHubZagros::para_id().into())]);
let root = <CollectivesZagros as Chain>::RuntimeOrigin::root();
// asset kind to be spent from the treasury.
let asset_kind: VersionedLocatableAsset =
(asset_hub_location, AssetId((PalletInstance(50), GeneralIndex(USDT_ID.into())).into()))
.into();
// treasury spend beneficiary.
let alice: AccountId = Zagros::account_id_of(ALICE);
let bob: AccountId = CollectivesZagros::account_id_of(BOB);
let bob_signed = <CollectivesZagros as Chain>::RuntimeOrigin::signed(bob.clone());
AssetHubZagros::execute_with(|| {
type Assets = <AssetHubZagros as AssetHubZagrosPallet>::Assets;
// USDT created at genesis, mint some assets to the fellowship treasury account.
assert_ok!(<Assets as Mutate<_>>::mint_into(USDT_ID, &treasury_account, SPEND_AMOUNT * 4));
// beneficiary has zero balance.
assert_eq!(<Assets as Inspect<_>>::balance(USDT_ID, &alice,), 0u128,);
});
CollectivesZagros::execute_with(|| {
type RuntimeEvent = <CollectivesZagros as Chain>::RuntimeEvent;
type FellowshipTreasury =
<CollectivesZagros as CollectivesZagrosPallet>::FellowshipTreasury;
type AssetRate = <CollectivesZagros as CollectivesZagrosPallet>::AssetRate;
// create a conversion rate from `asset_kind` to the native currency.
assert_ok!(AssetRate::create(root.clone(), Box::new(asset_kind.clone()), 2.into()));
// create and approve a treasury spend.
assert_ok!(FellowshipTreasury::spend(
root,
Box::new(asset_kind),
SPEND_AMOUNT,
Box::new(Location::new(0, Into::<[u8; 32]>::into(alice.clone())).into()),
None,
));
// claim the spend.
assert_ok!(FellowshipTreasury::payout(bob_signed.clone(), 0));
assert_expected_events!(
CollectivesZagros,
vec![
RuntimeEvent::FellowshipTreasury(pezpallet_treasury::Event::Paid { .. }) => {},
]
);
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
type Assets = <AssetHubZagros as AssetHubZagrosPallet>::Assets;
// assert events triggered by xcm pay program
// 1. treasury asset transferred to spend beneficiary
// 2. response to the Fellowship treasury pallet instance sent back
// 3. XCM program completed
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::Assets(pezpallet_assets::Event::Transferred { asset_id: id, from, to, amount }) => {
id: id == &USDT_ID,
from: from == &treasury_account,
to: to == &alice,
amount: amount == &SPEND_AMOUNT,
},
RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed { success: true ,.. }) => {},
]
);
// beneficiary received the assets from the treasury.
assert_eq!(<Assets as Inspect<_>>::balance(USDT_ID, &alice,), SPEND_AMOUNT,);
});
CollectivesZagros::execute_with(|| {
type RuntimeEvent = <CollectivesZagros as Chain>::RuntimeEvent;
type FellowshipTreasury =
<CollectivesZagros as CollectivesZagrosPallet>::FellowshipTreasury;
// check the payment status to ensure the response from the AssetHub was received.
assert_ok!(FellowshipTreasury::check_status(bob_signed, 0));
assert_expected_events!(
CollectivesZagros,
vec![
RuntimeEvent::FellowshipTreasury(pezpallet_treasury::Event::SpendProcessed { .. }) => {},
]
);
});
}
@@ -0,0 +1,538 @@
// 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::{
assets_balance_on, create_pool_with_wnd_on, foreign_balance_on, imports::*,
tests::send::penpal_register_foreign_asset_on_asset_hub,
};
// Registers a new asset on Penpal, then registers it over XCM as foreign asset on Asset Hub.
// The foreign asset is set up either as teleportable between Penpal and AH, by making AH a reserve
// for it too. Or it keeps the asset's reserve solely on Penpal resulting in reserve-based transfers
// between Penpal and AH.
pub fn set_up_foreign_asset(
sender: pezsp_runtime::AccountId32,
asset_id_on_penpal: u32,
asset_amount_to_send: u128,
teleportable: bool,
) -> (Location, Location) {
let asset_owner = PenpalAssetOwner::get();
// Give the sender enough native
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner.clone()),
RelayLocation::get(),
sender.clone(),
asset_amount_to_send,
);
// Create the asset on Penpal
let to_fund = asset_amount_to_send * 2;
PenpalA::force_create_asset(
asset_id_on_penpal,
asset_owner.clone(),
true,
ASSET_MIN_BALANCE,
vec![(sender.clone(), to_fund)],
);
PenpalA::execute_with(|| {
type Assets = <PenpalA as PenpalAPallet>::Assets;
assert!(Assets::asset_exists(asset_id_on_penpal));
});
let asset_location_on_penpal = Location::new(
0,
[
Junction::PalletInstance(ASSETS_PALLET_ID),
Junction::GeneralIndex(asset_id_on_penpal.into()),
],
);
// Setup a pool on Penpal between native asset and newly created asset, so we can pay fees using
// new asset directly.
create_pool_with_wnd_on!(PenpalA, asset_location_on_penpal.clone(), false, asset_owner.clone());
// Register asset on Asset Hub using XCM
let penpal_sovereign_account = AssetHubZagros::sovereign_account_id_of(
AssetHubZagros::sibling_location_of(PenpalA::para_id()),
);
let penpal_location = Location::new(1, [Junction::Teyrchain(PenpalA::para_id().into())]);
let foreign_asset_at_asset_hub =
penpal_location.clone().appended_with(asset_location_on_penpal.clone()).unwrap();
// Do remote registration
penpal_register_foreign_asset_on_asset_hub(asset_location_on_penpal.clone());
// Setup a pool on Asset Hub between native asset and newly created asset, so we can pay fees
// using new asset directly.
create_pool_with_wnd_on!(
AssetHubZagros,
foreign_asset_at_asset_hub.clone(),
true,
penpal_sovereign_account.clone()
);
if teleportable {
// Configure Penpal to allow teleports of this asset to AH
PenpalA::execute_with(|| {
assert_ok!(<PenpalA as Chain>::System::set_storage(
<PenpalA as Chain>::RuntimeOrigin::root(),
vec![(
PenpalLocalTeleportableToAssetHub::key().to_vec(),
asset_location_on_penpal.encode(),
)],
));
});
}
let reserves_data = vec![(penpal_location, teleportable).into()];
AssetHubZagros::set_foreign_asset_reserves(
foreign_asset_at_asset_hub.clone(),
penpal_sovereign_account.clone(),
reserves_data,
);
(asset_location_on_penpal, foreign_asset_at_asset_hub)
}
// Helper for Penpal root to call ForeignAssets::set_reserves() on Asset Hub.
pub fn penpal_set_foreign_asset_reserves_on_asset_hub(
asset_id_on_ah: Location,
reserves: Vec<ForeignAssetReserveData>,
) {
// Encoded `set_reserves` call to be executed in AssetHub
let call = <AssetHubZagros as Chain>::RuntimeCall::ForeignAssets(pezpallet_assets::Call::<
<AssetHubZagros as Chain>::Runtime,
pezpallet_assets::Instance2,
>::set_reserves {
id: asset_id_on_ah.into(),
reserves,
})
.encode()
.into();
let penpal_sovereign = AssetHubZagros::sovereign_account_id_of(
AssetHubZagros::sibling_location_of(PenpalA::para_id()),
);
let origin_kind = OriginKind::Xcm;
let fee_amount = ASSET_HUB_ZAGROS_ED * 1000000;
let system_asset = (Parent, fee_amount).into();
let root_origin = <PenpalA as Chain>::RuntimeOrigin::root();
let asset_hub_location = PenpalA::sibling_location_of(AssetHubZagros::para_id()).into();
let xcm =
xcm_transact_paid_execution(call, origin_kind, system_asset, penpal_sovereign.clone());
PenpalA::execute_with(|| {
assert_ok!(<PenpalA as PenpalAPallet>::PezkuwiXcm::send(
root_origin,
bx!(asset_hub_location),
bx!(xcm),
));
PenpalA::assert_xcm_pallet_sent();
});
}
// ==============================================================================================
// ==== Bidirectional Transfer - Teleportable Foreign Asset - Penpal<->AssetHub ====
// ==============================================================================================
/// Transfers of teleportable foreign asset from Penpal to AssetHub and back.
/// Also verifies that reserve-transferring the asset fails both ways.
#[test]
fn bidirectional_teleport_foreign_asset_between_penpal_and_asset_hub() {
let sender = PenpalASender::get();
let receiver = AssetHubZagrosReceiver::get();
let new_asset_id = 42;
let asset_amount_to_send = ASSET_HUB_ZAGROS_ED * 10_000;
let (asset_location_on_penpal, foreign_asset_location_on_ah) =
set_up_foreign_asset(sender.clone(), new_asset_id, asset_amount_to_send, true);
////////////////////////////////
// Teleport it from Penpal to AH
////////////////////////////////
let penpal_sender_balance_before = assets_balance_on!(PenpalA, new_asset_id, &sender);
let ah_receiver_balance_before =
foreign_balance_on!(AssetHubZagros, foreign_asset_location_on_ah.clone(), &receiver);
let dest = PenpalA::sibling_location_of(AssetHubZagros::para_id());
let assets: Assets =
vec![(asset_location_on_penpal.clone(), asset_amount_to_send).into()].into();
// execute xcm from penpal to asset hub
PenpalA::execute_with(|| {
// xcm to be executed at dest
let xcm_on_dest = Xcm(vec![
// 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: receiver.clone().into() },
]);
let xcm = Xcm::<()>(vec![
WithdrawAsset(assets.clone().into()),
SetFeesMode { jit_withdraw: true },
InitiateTransfer {
destination: dest.clone(),
remote_fees: Some(AssetTransferFilter::Teleport(assets.clone().into())),
preserve_origin: false,
assets: BoundedVec::new(),
remote_xcm: xcm_on_dest.clone(),
},
]);
// teleporting the asset works
<PenpalA as PenpalAPallet>::PezkuwiXcm::execute(
<PenpalA as Chain>::RuntimeOrigin::signed(sender.clone()),
bx!(xcm::VersionedXcm::from(xcm.into())),
Weight::MAX,
)
.unwrap();
});
let penpal_sender_balance_after = assets_balance_on!(PenpalA, new_asset_id, &sender);
let ah_receiver_balance_after =
foreign_balance_on!(AssetHubZagros, foreign_asset_location_on_ah.clone(), &receiver);
assert!(penpal_sender_balance_after < penpal_sender_balance_before);
assert!(ah_receiver_balance_after > ah_receiver_balance_before);
// reserve-transferring the asset fails
PenpalA::execute_with(|| {
let xcm = Xcm::<()>(vec![
WithdrawAsset(assets.clone().into()),
SetFeesMode { jit_withdraw: true },
InitiateTransfer {
destination: dest,
remote_fees: Some(AssetTransferFilter::ReserveDeposit(assets.clone().into())),
preserve_origin: false,
assets: BoundedVec::new(),
remote_xcm: Default::default(),
},
]);
<PenpalA as PenpalAPallet>::PezkuwiXcm::execute(
<PenpalA as Chain>::RuntimeOrigin::signed(sender.clone()),
bx!(xcm::VersionedXcm::from(xcm.into())),
Weight::MAX,
)
.unwrap();
});
// AH is expected to reject the transfer with `UntrustedReserveLocation`
let expected_origin = AssetHubZagros::sibling_location_of(PenpalA::para_id());
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::PezkuwiXcm(
pezpallet_xcm::Event::ProcessXcmError { origin, error, .. }
) => {
origin: *origin == expected_origin,
error: *error == xcm::latest::Error::UntrustedReserveLocation,
},
]
);
});
/////////////////////////////////////
// Teleport it back from AH to Penpal
/////////////////////////////////////
let asset_amount_to_send = ah_receiver_balance_after;
let ah_sender_balance_before =
foreign_balance_on!(AssetHubZagros, foreign_asset_location_on_ah.clone(), &receiver);
let penpal_receiver_balance_before = assets_balance_on!(PenpalA, new_asset_id, &sender);
let dest = AssetHubZagros::sibling_location_of(PenpalA::para_id());
// execute xcm from asset hub to penpal
AssetHubZagros::execute_with(|| {
let assets: Assets =
vec![(foreign_asset_location_on_ah.clone(), asset_amount_to_send).into()].into();
// xcm to be executed at dest
let xcm_on_dest = Xcm(vec![
// 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: sender.clone().into() },
]);
// reserve-transferring the asset back to penpal fails
let xcm = Xcm::<()>(vec![
WithdrawAsset(assets.clone().into()),
SetFeesMode { jit_withdraw: true },
InitiateTransfer {
destination: dest.clone(),
remote_fees: Some(AssetTransferFilter::ReserveWithdraw(assets.clone().into())),
preserve_origin: false,
assets: BoundedVec::new(),
remote_xcm: Default::default(),
},
]);
assert!(matches!(
<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::execute(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(receiver.clone()),
bx!(xcm::VersionedXcm::from(xcm.into())),
Weight::MAX,
),
Err(pezsp_runtime::DispatchErrorWithPostInfo { .. }),
));
// teleporting it back works
let xcm = Xcm::<()>(vec![
WithdrawAsset(assets.clone().into()),
SetFeesMode { jit_withdraw: true },
InitiateTransfer {
destination: dest,
remote_fees: Some(AssetTransferFilter::Teleport(assets.into())),
preserve_origin: false,
assets: BoundedVec::new(),
remote_xcm: xcm_on_dest,
},
]);
<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::execute(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(receiver.clone()),
bx!(xcm::VersionedXcm::from(xcm.into())),
Weight::MAX,
)
.unwrap();
});
let ah_sender_balance_after =
foreign_balance_on!(AssetHubZagros, foreign_asset_location_on_ah, &receiver);
let penpal_receiver_balance_after = assets_balance_on!(PenpalA, new_asset_id, &sender);
assert!(ah_sender_balance_after < ah_sender_balance_before);
assert!(penpal_receiver_balance_after > penpal_receiver_balance_before);
}
// ==============================================================================================
// ==== Bidirectional Transfer - Reserve-based Foreign Asset - Penpal<->AssetHub ====
// ==============================================================================================
/// Transfers of foreign asset from Penpal to AssetHub and back. Foreign Asset is not registered
/// with Asset Hub as a trusted reserve, ergo teleports are not available and reserve-transfers are
/// to be used. Also verifies that teleporting the asset fails both ways.
#[test]
fn bidirectional_reserve_transfer_foreign_asset_between_penpal_and_asset_hub() {
let sender = PenpalASender::get();
let receiver = AssetHubZagrosReceiver::get();
let new_asset_id = 42;
let asset_amount_to_send = ASSET_HUB_ZAGROS_ED * 10_000;
let (asset_location_on_penpal, foreign_asset_location_on_ah) =
set_up_foreign_asset(sender.clone(), new_asset_id, asset_amount_to_send, false);
////////////////////////////////////////
// Reserve-transfer it from Penpal to AH
////////////////////////////////////////
let penpal_sender_balance_before = assets_balance_on!(PenpalA, new_asset_id, &sender);
let ah_receiver_balance_before =
foreign_balance_on!(AssetHubZagros, foreign_asset_location_on_ah.clone(), &receiver);
let dest = PenpalA::sibling_location_of(AssetHubZagros::para_id());
let assets: Assets =
vec![(asset_location_on_penpal.clone(), asset_amount_to_send).into()].into();
// execute xcm from penpal to asset hub
PenpalA::execute_with(|| {
// xcm to be executed at dest
let xcm_on_dest = Xcm(vec![
// 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: receiver.clone().into() },
]);
// teleporting the asset fails
let xcm = Xcm::<()>(vec![
WithdrawAsset(assets.clone().into()),
SetFeesMode { jit_withdraw: true },
InitiateTransfer {
destination: dest.clone(),
remote_fees: Some(AssetTransferFilter::Teleport(assets.clone().into())),
preserve_origin: false,
assets: BoundedVec::new(),
remote_xcm: xcm_on_dest.clone(),
},
]);
assert!(matches!(
<PenpalA as PenpalAPallet>::PezkuwiXcm::execute(
<PenpalA as Chain>::RuntimeOrigin::signed(sender.clone()),
bx!(xcm::VersionedXcm::from(xcm.into())),
Weight::MAX,
),
Err(pezsp_runtime::DispatchErrorWithPostInfo { .. }),
));
// reserve-transferring the asset works
let xcm = Xcm::<()>(vec![
WithdrawAsset(assets.clone().into()),
SetFeesMode { jit_withdraw: true },
InitiateTransfer {
destination: dest,
remote_fees: Some(AssetTransferFilter::ReserveDeposit(assets.into())),
preserve_origin: false,
assets: BoundedVec::new(),
remote_xcm: xcm_on_dest,
},
]);
assert_ok!(<PenpalA as PenpalAPallet>::PezkuwiXcm::execute(
<PenpalA as Chain>::RuntimeOrigin::signed(sender.clone()),
bx!(xcm::VersionedXcm::from(xcm.into())),
Weight::MAX,
));
});
let penpal_sender_balance_after = assets_balance_on!(PenpalA, new_asset_id, &sender);
let ah_receiver_balance_after =
foreign_balance_on!(AssetHubZagros, foreign_asset_location_on_ah.clone(), &receiver);
assert!(penpal_sender_balance_after < penpal_sender_balance_before);
assert!(ah_receiver_balance_after > ah_receiver_balance_before);
/////////////////////////////////////////////
// Reserve-transfer it back from AH to Penpal
/////////////////////////////////////////////
let asset_amount_to_send = ah_receiver_balance_after;
let ah_sender_balance_before =
foreign_balance_on!(AssetHubZagros, foreign_asset_location_on_ah.clone(), &receiver);
let penpal_receiver_balance_before = assets_balance_on!(PenpalA, new_asset_id, &sender);
let dest = AssetHubZagros::sibling_location_of(PenpalA::para_id());
// execute xcm from asset hub to penpal
AssetHubZagros::execute_with(|| {
let assets: Assets =
vec![(foreign_asset_location_on_ah.clone(), asset_amount_to_send).into()].into();
// xcm to be executed at dest
let xcm_on_dest = Xcm(vec![
// 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: sender.clone().into() },
]);
// teleporting the asset back to penpal fails
let xcm = Xcm::<()>(vec![
WithdrawAsset(assets.clone().into()),
SetFeesMode { jit_withdraw: true },
InitiateTransfer {
destination: dest.clone(),
remote_fees: Some(AssetTransferFilter::Teleport(assets.clone().into())),
preserve_origin: false,
assets: BoundedVec::new(),
remote_xcm: xcm_on_dest.clone(),
},
]);
assert!(matches!(
<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::execute(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(receiver.clone()),
bx!(xcm::VersionedXcm::from(xcm.into())),
Weight::MAX,
),
Err(pezsp_runtime::DispatchErrorWithPostInfo { .. }),
));
// but reserve-transferring it back works
let xcm = Xcm::<()>(vec![
WithdrawAsset(assets.clone().into()),
SetFeesMode { jit_withdraw: true },
InitiateTransfer {
destination: dest,
remote_fees: Some(AssetTransferFilter::ReserveWithdraw(assets.into())),
preserve_origin: false,
assets: BoundedVec::new(),
remote_xcm: xcm_on_dest,
},
]);
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::execute(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(receiver.clone()),
bx!(xcm::VersionedXcm::from(xcm.into())),
Weight::MAX,
));
});
let ah_sender_balance_after =
foreign_balance_on!(AssetHubZagros, foreign_asset_location_on_ah, &receiver);
let penpal_receiver_balance_after = assets_balance_on!(PenpalA, new_asset_id, &sender);
assert!(ah_sender_balance_after < ah_sender_balance_before);
assert!(penpal_receiver_balance_after > penpal_receiver_balance_before);
}
/// Verifies that foreign asset reserves can be only set by signed `Owner` account or through XCM
/// using remote `ManagerOrigin`.
#[test]
fn verify_foreign_asset_origin_checks() {
let sender = PenpalASender::get();
let new_asset_id = 42;
let asset_amount_to_send = ASSET_HUB_ZAGROS_ED * 10_000;
let (_, foreign_asset_location_on_ah) =
set_up_foreign_asset(sender.clone(), new_asset_id, asset_amount_to_send, false);
let penpal_sovereign = AssetHubZagros::sovereign_account_id_of(
AssetHubZagros::sibling_location_of(PenpalA::para_id()),
);
let reserves_data = ForeignAssetReserveData {
reserve: AssetHubZagros::sibling_location_of(PenpalA::para_id()),
teleportable: true,
};
// Set asset reserves using signed `owner` account.
let origin = <AssetHubZagros as Chain>::RuntimeOrigin::signed(penpal_sovereign);
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::set_reserves(
origin,
foreign_asset_location_on_ah.clone(),
vec![reserves_data.clone()],
)
.unwrap();
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::ReservesUpdated { asset_id, .. }) => {
asset_id: *asset_id == foreign_asset_location_on_ah,
},
]
);
});
// Now set asset reserves using some other signed account. It should fail.
let origin = <AssetHubZagros as Chain>::RuntimeOrigin::signed(sender.clone());
AssetHubZagros::execute_with(|| {
assert!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::set_reserves(
origin,
foreign_asset_location_on_ah.clone(),
vec![reserves_data],
)
.is_err());
});
// Now set asset reserves using remote XCM from correct origin chain.
// Use wrong `{origin, asset}` combination.
let asset_id_on_ah = emulated_integration_tests_common::PenpalBTeleportableAssetLocation::get();
penpal_set_foreign_asset_reserves_on_asset_hub(asset_id_on_ah, vec![]);
// Verify it failed.
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed { success: false, .. }) => {},
]
);
});
// Verify it works when using right `{origin, asset}` combination.
let asset_id_on_ah = foreign_asset_location_on_ah;
penpal_set_foreign_asset_reserves_on_asset_hub(asset_id_on_ah.clone(), vec![]);
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
AssetHubZagros::assert_xcmp_queue_success(None);
assert_expected_events!(
AssetHubZagros,
vec![
// Foreign Asset created
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::ReservesRemoved { asset_id }) => {
asset_id: *asset_id == asset_id_on_ah,
},
]
);
});
}
@@ -0,0 +1,133 @@
// 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.
mod aliases;
mod claim_assets;
mod exchange_asset;
mod fellowship_treasury;
mod foreign_assets;
mod hybrid_transfers;
mod reserve_transfer;
mod reward_pool;
mod send;
mod set_asset_claimer;
mod set_xcm_versions;
mod swap;
mod teleport;
mod transact;
mod transfer_assets_validation;
mod treasury;
mod xcm_fee_estimation;
#[macro_export]
macro_rules! foreign_balance_on {
( $chain:ident, $id:expr, $who:expr ) => {
emulated_integration_tests_common::impls::paste::paste! {
<$chain>::execute_with(|| {
type ForeignAssets = <$chain as [<$chain Pallet>]>::ForeignAssets;
<ForeignAssets as pezframe_support::traits::fungibles::Inspect<_>>::balance($id, $who)
})
}
};
}
#[macro_export]
macro_rules! assets_balance_on {
( $chain:ident, $id:expr, $who:expr ) => {
emulated_integration_tests_common::impls::paste::paste! {
<$chain>::execute_with(|| {
type Assets = <$chain as [<$chain Pallet>]>::Assets;
<Assets as pezframe_support::traits::fungibles::Inspect<_>>::balance($id, $who)
})
}
};
}
#[macro_export]
macro_rules! create_pool_with_wnd_on {
// default amounts
( $chain:ident, $asset_id:expr, $is_foreign:expr, $asset_owner:expr ) => {
$crate::create_pool_with_wnd_on!(
$chain,
$asset_id,
$is_foreign,
$asset_owner,
1_000_000_000_000,
2_000_000_000_000
);
};
// custom amounts
( $chain:ident, $asset_id:expr, $is_foreign:expr, $asset_owner:expr, $wnd_amount:expr, $asset_amount:expr ) => {
emulated_integration_tests_common::impls::paste::paste! {
<$chain>::execute_with(|| {
type RuntimeEvent = <$chain as Chain>::RuntimeEvent;
let owner = $asset_owner;
let signed_owner = <$chain as Chain>::RuntimeOrigin::signed(owner.clone());
let wnd_location: Location = Parent.into();
if $is_foreign {
assert_ok!(<$chain as [<$chain Pallet>]>::ForeignAssets::mint(
signed_owner.clone(),
$asset_id.clone().into(),
owner.clone().into(),
10_000_000_000_000, // For it to have more than enough.
));
} else {
let asset_id = match $asset_id.interior.last() {
Some(GeneralIndex(id)) => *id as u32,
_ => unreachable!(),
};
assert_ok!(<$chain as [<$chain Pallet>]>::Assets::mint(
signed_owner.clone(),
asset_id.into(),
owner.clone().into(),
10_000_000_000_000, // For it to have more than enough.
));
}
assert_ok!(<$chain as [<$chain Pallet>]>::AssetConversion::create_pool(
signed_owner.clone(),
Box::new(wnd_location.clone()),
Box::new($asset_id.clone()),
));
assert_expected_events!(
$chain,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::PoolCreated { .. }) => {},
]
);
assert_ok!(<$chain as [<$chain Pallet>]>::AssetConversion::add_liquidity(
signed_owner,
Box::new(wnd_location),
Box::new($asset_id),
$wnd_amount,
$asset_amount,
0,
0,
owner.into()
));
assert_expected_events!(
$chain,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::LiquidityAdded { .. }) => {},
]
);
});
}
};
}
@@ -0,0 +1,114 @@
// 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 codec::Encode;
use pezframe_support::{assert_ok, pezsp_runtime::traits::Dispatchable, traits::schedule::DispatchTime};
use xcm_executor::traits::ConvertLocation;
#[test]
fn treasury_creates_asset_reward_pool() {
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
type Balances = <AssetHubZagros as AssetHubZagrosPallet>::Balances;
let treasurer =
Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]);
let treasurer_account =
ahw_xcm_config::LocationToAccountId::convert_location(&treasurer).unwrap();
assert_ok!(Balances::force_set_balance(
<AssetHubZagros as Chain>::RuntimeOrigin::root(),
treasurer_account.clone().into(),
ASSET_HUB_ZAGROS_ED * 100_000,
));
let events = AssetHubZagros::events();
match events.iter().last() {
Some(RuntimeEvent::Balances(pezpallet_balances::Event::BalanceSet { who, .. })) => {
assert_eq!(*who, treasurer_account)
},
_ => panic!("Expected Balances::BalanceSet event"),
}
});
Zagros::execute_with(|| {
type AssetHubZagrosRuntimeCall = <AssetHubZagros as Chain>::RuntimeCall;
type AssetHubZagrosRuntime = <AssetHubZagros as Chain>::Runtime;
type ZagrosRuntimeCall = <Zagros as Chain>::RuntimeCall;
type ZagrosRuntime = <Zagros as Chain>::Runtime;
type ZagrosRuntimeEvent = <Zagros as Chain>::RuntimeEvent;
type ZagrosRuntimeOrigin = <Zagros as Chain>::RuntimeOrigin;
Dmp::make_teyrchain_reachable(AssetHubZagros::para_id());
let staked_asset_id = bx!(RelayLocation::get());
let reward_asset_id = bx!(RelayLocation::get());
let reward_rate_per_block = 1_000_000_000;
let lifetime = 1_000_000_000;
let admin = None;
let create_pool_call =
ZagrosRuntimeCall::XcmPallet(pezpallet_xcm::Call::<ZagrosRuntime>::send {
dest: bx!(VersionedLocation::V4(
xcm::v4::Junction::Teyrchain(AssetHubZagros::para_id().into()).into()
)),
message: bx!(VersionedXcm::V5(Xcm(vec![
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
Transact {
origin_kind: OriginKind::SovereignAccount,
fallback_max_weight: None,
call: AssetHubZagrosRuntimeCall::AssetRewards(
pezpallet_asset_rewards::Call::<AssetHubZagrosRuntime>::create_pool {
staked_asset_id,
reward_asset_id,
reward_rate_per_block,
expiry: DispatchTime::After(lifetime),
admin
}
)
.encode()
.into(),
}
]))),
});
let treasury_origin: ZagrosRuntimeOrigin = Treasurer.into();
assert_ok!(create_pool_call.dispatch(treasury_origin));
assert_expected_events!(
Zagros,
vec![
ZagrosRuntimeEvent::XcmPallet(pezpallet_xcm::Event::Sent { .. }) => {},
]
);
});
AssetHubZagros::execute_with(|| {
type Runtime = <AssetHubZagros as Chain>::Runtime;
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_eq!(1, pezpallet_asset_rewards::Pools::<Runtime>::iter().count());
let events = AssetHubZagros::events();
match events.iter().last() {
Some(RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed {
success: true,
..
})) => (),
_ => panic!("Expected MessageQueue::Processed event"),
}
});
}
@@ -0,0 +1,199 @@
// 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::{create_pool_with_wnd_on, imports::*};
/// Relay Chain should be able to execute `Transact` instructions in System Teyrchain
/// when `OriginKind::Superuser`.
#[test]
fn send_transact_as_superuser_from_relay_to_asset_hub_works() {
AssetHubZagros::force_create_asset_from_relay_as_root(
ASSET_ID,
ASSET_MIN_BALANCE,
true,
AssetHubZagrosSender::get().into(),
Some(Weight::from_parts(78_628_000, 3675)),
)
}
pub fn penpal_register_foreign_asset_on_asset_hub(asset_location_on_penpal: Location) {
let penpal_sovereign_account = AssetHubZagros::sovereign_account_id_of(
AssetHubZagros::sibling_location_of(PenpalA::para_id()),
);
let foreign_asset_at_asset_hub =
Location::new(1, [Junction::Teyrchain(PenpalA::para_id().into())])
.appended_with(asset_location_on_penpal)
.unwrap();
// Encoded `create_asset` call to be executed in AssetHub
let call = AssetHubZagros::create_foreign_asset_call(
foreign_asset_at_asset_hub.clone(),
ASSET_MIN_BALANCE,
penpal_sovereign_account.clone(),
);
let origin_kind = OriginKind::Xcm;
let fee_amount = ASSET_HUB_ZAGROS_ED * 1000000;
let system_asset = (Parent, fee_amount).into();
let root_origin = <PenpalA as Chain>::RuntimeOrigin::root();
let system_para_destination = PenpalA::sibling_location_of(AssetHubZagros::para_id()).into();
let xcm = xcm_transact_paid_execution(
call,
origin_kind,
system_asset,
penpal_sovereign_account.clone(),
);
// SA-of-Penpal-on-AHR needs to have balance to pay for fees and asset creation deposit
AssetHubZagros::fund_accounts(vec![(
penpal_sovereign_account.clone().into(),
ASSET_HUB_ZAGROS_ED * 10000000000,
)]);
PenpalA::execute_with(|| {
assert_ok!(<PenpalA as PenpalAPallet>::PezkuwiXcm::send(
root_origin,
bx!(system_para_destination),
bx!(xcm),
));
PenpalA::assert_xcm_pallet_sent();
});
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 == penpal_sovereign_account,
amount: *amount == fee_amount,
},
// Foreign Asset created
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Created { asset_id, creator, owner }) => {
asset_id: *asset_id == foreign_asset_at_asset_hub,
creator: *creator == penpal_sovereign_account.clone(),
owner: *owner == penpal_sovereign_account,
},
]
);
type ForeignAssets = <AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets;
assert!(ForeignAssets::asset_exists(foreign_asset_at_asset_hub));
});
}
/// We tests two things here:
/// - Teyrchain should be able to send XCM paying its fee at Asset Hub using system asset
/// - Teyrchain should be able to create a new Foreign Asset at Asset Hub
#[test]
fn send_xcm_from_para_to_asset_hub_paying_fee_with_system_asset() {
let asset_location_on_penpal = Location::new(
0,
[Junction::PalletInstance(ASSETS_PALLET_ID), Junction::GeneralIndex(ASSET_ID.into())],
);
penpal_register_foreign_asset_on_asset_hub(asset_location_on_penpal);
}
/// We tests two things here:
/// - Teyrchain should be able to send XCM paying its fee at Asset Hub using sufficient asset
/// - Teyrchain should be able to create a new Asset at Asset Hub
#[test]
fn send_xcm_from_para_to_asset_hub_paying_fee_with_sufficient_asset() {
let para_sovereign_account = AssetHubZagros::sovereign_account_id_of(
AssetHubZagros::sibling_location_of(PenpalA::para_id()),
);
// Force create and mint sufficient assets for Teyrchain's sovereign account
AssetHubZagros::force_create_and_mint_asset(
ASSET_ID,
ASSET_MIN_BALANCE,
true,
para_sovereign_account.clone(),
Some(Weight::from_parts(78_628_000, 3675)),
ASSET_MIN_BALANCE * 1000000000,
);
// Just a different `asset_id`` that does not exist yet
let new_asset_id = ASSET_ID + 1;
// Encoded `create_asset` call to be executed in AssetHub
let call = AssetHubZagros::create_asset_call(
new_asset_id,
ASSET_MIN_BALANCE,
para_sovereign_account.clone(),
);
let origin_kind = OriginKind::SovereignAccount;
let fee_amount = ASSET_MIN_BALANCE * 1000000;
let asset =
([PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())], fee_amount).into();
let asset_location =
Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())]);
let root_origin = <PenpalA as Chain>::RuntimeOrigin::root();
let system_para_destination = PenpalA::sibling_location_of(AssetHubZagros::para_id()).into();
let xcm = xcm_transact_paid_execution(call, origin_kind, asset, para_sovereign_account.clone());
// SA-of-Penpal-on-AHR needs to have balance to pay for asset creation deposit
AssetHubZagros::fund_accounts(vec![(
para_sovereign_account.clone().into(),
ASSET_HUB_ZAGROS_ED * 10000000000,
)]);
create_pool_with_wnd_on!(
AssetHubZagros,
asset_location,
false,
para_sovereign_account.clone(),
9_000_000_000_000_000,
9_000_000_000_000
);
PenpalA::execute_with(|| {
assert_ok!(<PenpalA as PenpalAPallet>::PezkuwiXcm::send(
root_origin,
bx!(system_para_destination),
bx!(xcm),
));
PenpalA::assert_xcm_pallet_sent();
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
AssetHubZagros::assert_xcmp_queue_success(None);
assert_expected_events!(
AssetHubZagros,
vec![
// Burned the fee
RuntimeEvent::Assets(pezpallet_assets::Event::Burned { asset_id, owner, balance }) => {
asset_id: *asset_id == ASSET_ID,
owner: *owner == para_sovereign_account,
balance: *balance == fee_amount,
},
// Asset created
RuntimeEvent::Assets(pezpallet_assets::Event::Created { asset_id, creator, owner }) => {
asset_id: *asset_id == new_asset_id,
creator: *creator == para_sovereign_account.clone(),
owner: *owner == para_sovereign_account,
},
]
);
});
}
@@ -0,0 +1,153 @@
// 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::{bhw_xcm_config::LocationToAccountId, *};
use emulated_integration_tests_common::{
accounts::{ALICE, BOB},
impls::AccountId32,
};
use pezframe_support::{assert_ok, pezsp_runtime::traits::Dispatchable};
use xcm_executor::traits::ConvertLocation;
use zagros_system_emulated_network::{
asset_hub_zagros_emulated_chain::asset_hub_zagros_runtime::RuntimeOrigin as AssetHubRuntimeOrigin,
bridge_hub_zagros_emulated_chain::bridge_hub_zagros_runtime::RuntimeOrigin as BridgeHubRuntimeOrigin,
};
#[test]
fn test_set_asset_claimer_within_a_chain() {
let (alice_account, _) = account_and_location(ALICE);
let (bob_account, bob_location) = account_and_location(BOB);
let trap_amount = 16_000_000_000_000;
let assets: Assets = (Parent, trap_amount).into();
let alice_balance_before =
<AssetHubZagros as Chain>::account_data_of(alice_account.clone()).free;
AssetHubZagros::fund_accounts(vec![(alice_account.clone(), trap_amount * 2)]);
let alice_balance_after =
<AssetHubZagros as Chain>::account_data_of(alice_account.clone()).free;
assert_eq!(alice_balance_after - alice_balance_before, trap_amount * 2);
type RuntimeCall = <AssetHubZagros as Chain>::RuntimeCall;
let asset_trap_xcm = Xcm::<RuntimeCall>::builder_unsafe()
.set_hints(vec![AssetClaimer { location: bob_location.clone() }])
.withdraw_asset(assets.clone())
.clear_origin()
.build();
AssetHubZagros::execute_with(|| {
assert_ok!(RuntimeCall::PezkuwiXcm(pezpallet_xcm::Call::execute {
message: bx!(VersionedXcm::from(asset_trap_xcm)),
max_weight: Weight::from_parts(4_000_000_000_000, 300_000),
})
.dispatch(AssetHubRuntimeOrigin::signed(alice_account.clone())));
});
let balance_after_trap = <AssetHubZagros as Chain>::account_data_of(alice_account.clone()).free;
assert_eq!(alice_balance_after - balance_after_trap, trap_amount);
let bob_balance_before = <AssetHubZagros as Chain>::account_data_of(bob_account.clone()).free;
let claim_xcm = Xcm::<RuntimeCall>::builder_unsafe()
.claim_asset(assets.clone(), Here)
.deposit_asset(AllCounted(assets.len() as u32), bob_location.clone())
.build();
AssetHubZagros::execute_with(|| {
assert_ok!(RuntimeCall::PezkuwiXcm(pezpallet_xcm::Call::execute {
message: bx!(VersionedXcm::from(claim_xcm)),
max_weight: Weight::from_parts(4_000_000_000_000, 300_000),
})
.dispatch(AssetHubRuntimeOrigin::signed(bob_account.clone())));
});
let bob_balance_after = <AssetHubZagros as Chain>::account_data_of(bob_account.clone()).free;
assert_eq!(bob_balance_after - bob_balance_before, trap_amount);
}
fn account_and_location(account: &str) -> (AccountId32, Location) {
let account_id = AssetHubZagros::account_id_of(account);
let account_clone = account_id.clone();
let location: Location = [Junction::AccountId32 {
network: Some(ByGenesis(ZAGROS_GENESIS_HASH)),
id: account_id.into(),
}]
.into();
(account_clone, location)
}
// The test:
// 1. Funds Bob account on BridgeHub, withdraws the funds, sets asset claimer to
// sibling-account-of(AssetHub/Alice) and traps the funds.
// 2. Alice on AssetHub sends an XCM to BridgeHub to claim assets, pay fees and deposit
// remaining to her sibling account on BridgeHub.
#[test]
fn test_set_asset_claimer_between_the_chains() {
let alice = AssetHubZagros::account_id_of(ALICE);
let alice_bh_sibling = Location::new(
1,
[
Teyrchain(AssetHubZagros::para_id().into()),
Junction::AccountId32 {
network: Some(ByGenesis(ZAGROS_GENESIS_HASH)),
id: alice.clone().into(),
},
],
);
let bob = BridgeHubZagros::account_id_of(BOB);
let trap_amount = 16_000_000_000_000u128;
BridgeHubZagros::fund_accounts(vec![(bob.clone(), trap_amount * 2)]);
let assets: Assets = (Parent, trap_amount).into();
type RuntimeCall = <BridgeHubZagros as Chain>::RuntimeCall;
let trap_xcm = Xcm::<RuntimeCall>::builder_unsafe()
.set_hints(vec![AssetClaimer { location: alice_bh_sibling.clone() }])
.withdraw_asset(assets.clone())
.clear_origin()
.build();
BridgeHubZagros::execute_with(|| {
assert_ok!(RuntimeCall::PezkuwiXcm(pezpallet_xcm::Call::execute {
message: bx!(VersionedXcm::from(trap_xcm)),
max_weight: Weight::from_parts(4_000_000_000_000, 700_000),
})
.dispatch(BridgeHubRuntimeOrigin::signed(bob.clone())));
});
let alice_bh_acc = LocationToAccountId::convert_location(&alice_bh_sibling).unwrap();
let balance = <BridgeHubZagros as Chain>::account_data_of(alice_bh_acc.clone()).free;
assert_eq!(balance, 0);
let pay_fees = 6_000_000_000_000u128;
let xcm_on_bh = Xcm::<()>::builder_unsafe()
.claim_asset(assets.clone(), Here)
.pay_fees((Parent, pay_fees))
.deposit_asset(All, alice_bh_sibling.clone())
.build();
let bh_on_ah = AssetHubZagros::sibling_location_of(BridgeHubZagros::para_id()).into();
AssetHubZagros::execute_with(|| {
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::send(
AssetHubRuntimeOrigin::signed(alice.clone()),
bx!(bh_on_ah),
bx!(VersionedXcm::from(xcm_on_bh)),
));
});
let alice_bh_acc = LocationToAccountId::convert_location(&alice_bh_sibling).unwrap();
let balance = <BridgeHubZagros as Chain>::account_data_of(alice_bh_acc).free;
assert_eq!(balance, trap_amount - pay_fees);
}
@@ -0,0 +1,82 @@
// 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 relay_sets_system_para_xcm_supported_version() {
// Init tests variables
let sudo_origin = <Zagros as Chain>::RuntimeOrigin::root();
let system_para_destination: Location = Zagros::child_location_of(AssetHubZagros::para_id());
// Relay Chain sets supported version for Asset Teyrchain
Zagros::execute_with(|| {
assert_ok!(<Zagros as ZagrosPallet>::XcmPallet::force_xcm_version(
sudo_origin,
bx!(system_para_destination.clone()),
XCM_V3
));
type RuntimeEvent = <Zagros as Chain>::RuntimeEvent;
assert_expected_events!(
Zagros,
vec![
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::SupportedVersionChanged {
location,
version: XCM_V3
}) => { location: *location == system_para_destination, },
]
);
});
}
#[test]
fn system_para_sets_relay_xcm_supported_version() {
// Init test variables
let parent_location = AssetHubZagros::parent_location();
let force_xcm_version_call =
<AssetHubZagros as Chain>::RuntimeCall::PezkuwiXcm(pezpallet_xcm::Call::<
<AssetHubZagros as Chain>::Runtime,
>::force_xcm_version {
location: bx!(parent_location.clone()),
version: XCM_V3,
})
.encode()
.into();
// System Teyrchain sets supported version for Relay Chain through it
Zagros::send_unpaid_transact_to_teyrchain_as_root(
AssetHubZagros::para_id(),
force_xcm_version_call,
);
// System Teyrchain receive the XCM message
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
AssetHubZagros::assert_dmp_queue_complete(Some(Weight::from_parts(47_887_000, 0)));
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::PezkuwiXcm(pezpallet_xcm::Event::SupportedVersionChanged {
location,
version: XCM_V3
}) => { location: *location == parent_location, },
]
);
});
}
@@ -0,0 +1,400 @@
// 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 swap_locally_on_chain_using_local_assets() {
let asset_native =
Box::new(Location::try_from(RelayLocation::get()).expect("conversion works"));
let asset_one = Box::new(Location {
parents: 0,
interior: [
Junction::PalletInstance(ASSETS_PALLET_ID),
Junction::GeneralIndex(ASSET_ID.into()),
]
.into(),
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::Assets::create(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosSender::get()),
ASSET_ID.into(),
AssetHubZagrosSender::get().into(),
1000,
));
assert!(<AssetHubZagros as AssetHubZagrosPallet>::Assets::asset_exists(ASSET_ID));
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::Assets::mint(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosSender::get()),
ASSET_ID.into(),
AssetHubZagrosSender::get().into(),
3_000_000_000_000,
));
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::AssetConversion::create_pool(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosSender::get()),
asset_native.clone(),
asset_one.clone(),
));
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::PoolCreated { .. }) => {},
]
);
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::AssetConversion::add_liquidity(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosSender::get()),
asset_native.clone(),
asset_one.clone(),
1_000_000_000_000,
2_000_000_000_000,
0,
0,
AssetHubZagrosSender::get().into()
));
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, },
]
);
let path = vec![asset_native.clone(), asset_one.clone()];
assert_ok!(
<AssetHubZagros as AssetHubZagrosPallet>::AssetConversion::swap_exact_tokens_for_tokens(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosSender::get()),
path,
100,
1,
AssetHubZagrosSender::get().into(),
true
)
);
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::SwapExecuted { amount_in, amount_out, .. }) => {
amount_in: *amount_in == 100,
amount_out: *amount_out == 199,
},
]
);
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::AssetConversion::remove_liquidity(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosSender::get()),
asset_native.clone(),
asset_one.clone(),
1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved.
0,
0,
AssetHubZagrosSender::get().into(),
));
});
}
#[test]
fn swap_locally_on_chain_using_foreign_assets() {
let asset_native = Box::new(Location::try_from(RelayLocation::get()).unwrap());
let asset_location_on_penpal = PenpalA::execute_with(|| {
Location::try_from(PenpalLocalTeleportableToAssetHub::get()).unwrap()
});
let foreign_asset_at_asset_hub_zagros =
Location::new(1, [Junction::Teyrchain(PenpalA::para_id().into())])
.appended_with(asset_location_on_penpal)
.unwrap();
let penpal_as_seen_by_ah = AssetHubZagros::sibling_location_of(PenpalA::para_id());
let sov_penpal_on_ahr = AssetHubZagros::sovereign_account_id_of(penpal_as_seen_by_ah);
AssetHubZagros::fund_accounts(vec![
// An account to swap dot for something else.
(AssetHubZagrosSender::get().into(), 5_000_000 * ASSET_HUB_ZAGROS_ED),
// Penpal's sovereign account in AH should have some balance
(sov_penpal_on_ahr.clone().into(), 100_000_000 * ASSET_HUB_ZAGROS_ED),
]);
AssetHubZagros::execute_with(|| {
// 0: No need to create foreign asset as it exists in genesis.
//
// 1: Mint foreign asset on asset_hub_zagros:
//
// (While it might be nice to use batch,
// currently that's disabled due to safe call filters.)
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
// 1. Mint foreign asset (in reality this should be a teleport or some such)
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets::mint(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahr.clone().into()),
foreign_asset_at_asset_hub_zagros.clone(),
sov_penpal_on_ahr.clone().into(),
ASSET_HUB_ZAGROS_ED * 3_000_000_000_000,
));
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Issued { .. }) => {},
]
);
// 2. Create pool:
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::AssetConversion::create_pool(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosSender::get()),
asset_native.clone(),
Box::new(foreign_asset_at_asset_hub_zagros.clone()),
));
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::PoolCreated { .. }) => {},
]
);
// 3. Add liquidity:
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::AssetConversion::add_liquidity(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()),
asset_native.clone(),
Box::new(foreign_asset_at_asset_hub_zagros.clone()),
1_000_000_000_000_000,
2_000_000_000_000_000,
0,
0,
sov_penpal_on_ahr.clone().into()
));
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => {
lp_token_minted: *lp_token_minted == 1414213562372995,
},
]
);
// 4. Swap!
let path = vec![asset_native.clone(), Box::new(foreign_asset_at_asset_hub_zagros.clone())];
assert_ok!(
<AssetHubZagros as AssetHubZagrosPallet>::AssetConversion::swap_exact_tokens_for_tokens(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosSender::get()),
path,
100000 * ASSET_HUB_ZAGROS_ED,
1000 * ASSET_HUB_ZAGROS_ED,
AssetHubZagrosSender::get().into(),
true
)
);
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::SwapExecuted { amount_in, amount_out, .. },) => {
amount_in: *amount_in == 100000000000000,
amount_out: *amount_out == 181322178776029,
},
]
);
// 5. Remove liquidity
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::AssetConversion::remove_liquidity(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()),
asset_native.clone(),
Box::new(foreign_asset_at_asset_hub_zagros),
1414213562372995 - ASSET_HUB_ZAGROS_ED * 2, // all but the 2 EDs can't be retrieved.
0,
0,
sov_penpal_on_ahr.clone().into(),
));
});
}
#[test]
fn cannot_create_pool_from_pool_assets() {
let asset_native = RelayLocation::get();
let mut asset_one = ahw_xcm_config::PoolAssetsPalletLocation::get();
asset_one.append_with(GeneralIndex(ASSET_ID.into())).expect("pool assets");
AssetHubZagros::execute_with(|| {
let pool_owner_account_id = AssetHubZagrosAssetConversionOrigin::get();
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::PoolAssets::create(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(pool_owner_account_id.clone()),
ASSET_ID.into(),
pool_owner_account_id.clone().into(),
1000,
));
assert!(<AssetHubZagros as AssetHubZagrosPallet>::PoolAssets::asset_exists(ASSET_ID));
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::PoolAssets::mint(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(pool_owner_account_id),
ASSET_ID.into(),
AssetHubZagrosSender::get().into(),
3_000_000_000_000,
));
assert_matches::assert_matches!(
<AssetHubZagros as AssetHubZagrosPallet>::AssetConversion::create_pool(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosSender::get()),
Box::new(Location::try_from(asset_native).expect("conversion works")),
Box::new(Location::try_from(asset_one).expect("conversion works")),
),
Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("Unknown"))
);
});
}
#[test]
fn pay_xcm_fee_with_some_asset_swapped_for_native() {
let asset_native = Location::try_from(RelayLocation::get()).expect("conversion works");
let asset_one = Location {
parents: 0,
interior: [
Junction::PalletInstance(ASSETS_PALLET_ID),
Junction::GeneralIndex(ASSET_ID.into()),
]
.into(),
};
let penpal = AssetHubZagros::sovereign_account_id_of(AssetHubZagros::sibling_location_of(
PenpalA::para_id(),
));
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
// set up pool with ASSET_ID <> NATIVE pair
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::Assets::create(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosSender::get()),
ASSET_ID.into(),
AssetHubZagrosSender::get().into(),
ASSET_MIN_BALANCE,
));
assert!(<AssetHubZagros as AssetHubZagrosPallet>::Assets::asset_exists(ASSET_ID));
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::Assets::mint(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosSender::get()),
ASSET_ID.into(),
AssetHubZagrosSender::get().into(),
3_000_000_000_000,
));
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::AssetConversion::create_pool(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosSender::get()),
Box::new(asset_native.clone()),
Box::new(asset_one.clone()),
));
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::PoolCreated { .. }) => {},
]
);
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::AssetConversion::add_liquidity(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosSender::get()),
Box::new(asset_native),
Box::new(asset_one),
1_000_000_000_000,
2_000_000_000_000,
0,
0,
AssetHubZagrosSender::get().into()
));
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, },
]
);
// ensure `penpal` sovereign account has no native tokens and mint some `ASSET_ID`
assert_eq!(
<AssetHubZagros as AssetHubZagrosPallet>::Balances::free_balance(penpal.clone()),
0
);
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::Assets::touch_other(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosSender::get()),
ASSET_ID.into(),
penpal.clone().into(),
));
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::Assets::mint(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosSender::get()),
ASSET_ID.into(),
penpal.clone().into(),
10_000_000_000_000,
));
});
PenpalA::execute_with(|| {
// send xcm transact from `penpal` account while paying with `ASSET_ID` tokens on
// `AssetHubZagros`
let call = <AssetHubZagros as Chain>::RuntimeCall::System(pezframe_system::Call::<
<AssetHubZagros as Chain>::Runtime,
>::remark {
remark: vec![],
})
.encode()
.into();
let penpal_root = <PenpalA as Chain>::RuntimeOrigin::root();
let fee_amount = 4_000_000_000_000u128;
let asset_one =
([PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())], fee_amount).into();
let asset_hub_location = PenpalA::sibling_location_of(AssetHubZagros::para_id()).into();
let xcm = xcm_transact_paid_execution(
call,
OriginKind::SovereignAccount,
asset_one,
penpal.clone(),
);
assert_ok!(<PenpalA as PenpalAPallet>::PezkuwiXcm::send(
penpal_root,
bx!(asset_hub_location),
bx!(xcm),
));
PenpalA::assert_xcm_pallet_sent();
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
AssetHubZagros::assert_xcmp_queue_success(None);
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::AssetConversion(pezpallet_asset_conversion::Event::SwapCreditExecuted { .. },) => {},
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed { success: true,.. }) => {},
]
);
});
}
#[test]
fn xcm_fee_querying_apis_work() {
test_xcm_fee_querying_apis_work_for_asset_hub!(AssetHubZagros);
}
@@ -0,0 +1,793 @@
// 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::{foreign_balance_on, imports::*};
fn relay_origin_assertions(t: RelayToSystemParaTest) {
type RuntimeEvent = <Zagros as Chain>::RuntimeEvent;
Zagros::assert_xcm_pallet_attempted_complete(None);
assert_expected_events!(
Zagros,
vec![
// Amount to teleport is withdrawn from Sender
RuntimeEvent::Balances(pezpallet_balances::Event::Burned { who, amount }) => {
who: *who == t.sender.account_id,
amount: *amount == t.args.amount,
},
]
);
}
fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
let system_para_native_asset_location = RelayLocation::get();
let expected_asset_id = t.args.asset_id.unwrap();
let (_, expected_asset_amount) = non_fee_asset(&t.args.assets, &t.args.fee_asset_id).unwrap();
PenpalA::assert_xcm_pallet_attempted_complete(None);
assert_expected_events!(
PenpalA,
vec![
RuntimeEvent::ForeignAssets(
pezpallet_assets::Event::Burned { asset_id, owner, .. }
) => {
asset_id: *asset_id == system_para_native_asset_location,
owner: *owner == t.sender.account_id,
},
RuntimeEvent::Assets(pezpallet_assets::Event::Burned { asset_id, owner, balance }) => {
asset_id: *asset_id == expected_asset_id,
owner: *owner == t.sender.account_id,
balance: *balance == expected_asset_amount,
},
]
);
}
fn penpal_to_ah_foreign_assets_receiver_assertions(t: ParaToSystemParaTest) {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
let sov_penpal_on_ahr = AssetHubZagros::sovereign_account_id_of(
AssetHubZagros::sibling_location_of(PenpalA::para_id()),
);
let (_, expected_foreign_asset_amount) =
non_fee_asset(&t.args.assets, &t.args.fee_asset_id).unwrap();
let (_, fee_asset_amount) = fee_asset(&t.args.assets, &t.args.fee_asset_id).unwrap();
AssetHubZagros::assert_xcmp_queue_success(None);
assert_expected_events!(
AssetHubZagros,
vec![
// native asset reserve transfer for paying fees, withdrawn from Penpal's sov account
RuntimeEvent::Balances(
pezpallet_balances::Event::Burned { who, amount }
) => {
who: *who == sov_penpal_on_ahr.clone().into(),
amount: *amount >= fee_asset_amount / 2,
},
RuntimeEvent::Balances(pezpallet_balances::Event::Minted { who, .. }) => {
who: *who == t.receiver.account_id,
},
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Issued { asset_id, owner, amount }) => {
asset_id: *asset_id == PenpalATeleportableAssetLocation::get(),
owner: *owner == t.receiver.account_id,
amount: *amount == expected_foreign_asset_amount,
},
RuntimeEvent::Balances(pezpallet_balances::Event::Deposit { .. }) => {},
]
);
}
fn ah_to_penpal_foreign_assets_sender_assertions(t: SystemParaToParaTest) {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
AssetHubZagros::assert_xcm_pallet_attempted_complete(None);
let (expected_foreign_asset_id, expected_foreign_asset_amount) =
non_fee_asset(&t.args.assets, &t.args.fee_asset_id).unwrap();
assert_expected_events!(
AssetHubZagros,
vec![
// foreign asset is burned locally as part of teleportation
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Burned { asset_id, owner, balance }) => {
asset_id: *asset_id == expected_foreign_asset_id,
owner: *owner == t.sender.account_id,
balance: *balance == expected_foreign_asset_amount,
},
]
);
}
fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
let expected_asset_id = t.args.asset_id.unwrap();
let (_, expected_asset_amount) = non_fee_asset(&t.args.assets, &t.args.fee_asset_id).unwrap();
let checking_account = <PenpalA as PenpalAPallet>::PezkuwiXcm::check_account();
let system_para_native_asset_location = RelayLocation::get();
PenpalA::assert_xcmp_queue_success(None);
assert_expected_events!(
PenpalA,
vec![
// checking account burns local asset as part of incoming teleport
RuntimeEvent::Assets(pezpallet_assets::Event::Burned { asset_id, owner, balance }) => {
asset_id: *asset_id == expected_asset_id,
owner: *owner == checking_account,
balance: *balance == expected_asset_amount,
},
// local asset is teleported into account of receiver
RuntimeEvent::Assets(pezpallet_assets::Event::Issued { asset_id, owner, amount }) => {
asset_id: *asset_id == expected_asset_id,
owner: *owner == t.receiver.account_id,
amount: *amount == expected_asset_amount,
},
// native asset for fee is deposited to receiver
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == system_para_native_asset_location,
owner: *owner == t.receiver.account_id,
},
]
);
}
fn relay_to_system_para_limited_teleport_assets(t: RelayToSystemParaTest) -> DispatchResult {
Dmp::make_teyrchain_reachable(AssetHubZagros::para_id());
<Zagros as ZagrosPallet>::XcmPallet::limited_teleport_assets(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.beneficiary.into()),
bx!(t.args.assets.into()),
bx!(t.args.fee_asset_id.into()),
t.args.weight_limit,
)
}
fn para_to_system_para_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult {
<PenpalA as PenpalAPallet>::PezkuwiXcm::transfer_assets_using_type_and_then(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.assets.into()),
bx!(TransferType::Teleport),
bx!(t.args.fee_asset_id.into()),
bx!(TransferType::DestinationReserve),
bx!(VersionedXcm::from(
Xcm::<()>::builder_unsafe()
.deposit_asset(AllCounted(2), t.args.beneficiary)
.build()
)),
t.args.weight_limit,
)
}
fn system_para_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult {
<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::transfer_assets_using_type_and_then(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.assets.into()),
bx!(TransferType::Teleport),
bx!(t.args.fee_asset_id.into()),
bx!(TransferType::LocalReserve),
bx!(VersionedXcm::from(
Xcm::<()>::builder_unsafe()
.deposit_asset(AllCounted(2), t.args.beneficiary)
.build()
)),
t.args.weight_limit,
)
}
#[test]
fn teleport_via_limited_teleport_assets_to_other_system_teyrchains_works() {
let amount = ASSET_HUB_ZAGROS_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
AssetHubZagros, // Origin
vec![BridgeHubZagros], // Destinations
(native_asset, amount),
fee_asset_id,
limited_teleport_assets
);
}
#[test]
fn teleport_via_transfer_assets_to_other_system_teyrchains_works() {
let amount = ASSET_HUB_ZAGROS_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
AssetHubZagros, // Origin
vec![BridgeHubZagros], // 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![AssetHubZagros],
amount,
limited_teleport_assets
);
test_teyrchain_is_trusted_teleporter_for_relay!(
AssetHubZagros,
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![AssetHubZagros], amount, transfer_assets);
test_teyrchain_is_trusted_teleporter_for_relay!(
AssetHubZagros,
Zagros,
amount,
transfer_assets
);
}
/// Limited Teleport of native asset from Relay Chain to Asset Hub
/// shouldn't work when there is not enough balance in Asset Hub's `CheckAccount`
#[test]
fn limited_teleport_native_assets_from_relay_to_asset_hub_checking_acc_fails() {
let check_account = AssetHubZagros::execute_with(|| {
<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::check_account()
});
let amount_to_send_larger_than_checking_acc: Balance =
AssetHubZagros::account_data_of(check_account).free + 1;
let destination = Zagros::child_location_of(AssetHubZagros::para_id());
let beneficiary_id = AssetHubZagrosReceiver::get().into();
let assets = (Here, amount_to_send_larger_than_checking_acc).into();
let fee_asset_id: AssetId = Here.into();
let test_args = TestContext {
sender: ZagrosSender::get(),
receiver: AssetHubZagrosReceiver::get(),
args: TestArgs::new_para(
destination,
beneficiary_id,
amount_to_send_larger_than_checking_acc,
assets,
None,
fee_asset_id,
),
};
let mut test = RelayToSystemParaTest::new(test_args);
let sender_balance_before = test.sender.balance;
let receiver_balance_before = test.receiver.balance;
fn para_dest_assertions_fails(_t: RelayToSystemParaTest) {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: false, .. }
) => {},
]
);
}
test.set_assertion::<AssetHubZagros>(para_dest_assertions_fails);
test.set_assertion::<Zagros>(relay_origin_assertions);
test.set_dispatchable::<Zagros>(relay_to_system_para_limited_teleport_assets);
test.assert();
let sender_balance_after = test.sender.balance;
let receiver_balance_after = test.receiver.balance;
let delivery_fees = Zagros::execute_with(|| {
xcm_helpers::teleport_assets_delivery_fees::<
<ZagrosXcmConfig as xcm_executor::Config>::XcmSender,
>(
test.args.assets.clone(),
test.args.fee_asset_id,
test.args.weight_limit,
test.args.beneficiary,
test.args.dest,
)
});
// Sender's balance is reduced
assert_eq!(
sender_balance_before - amount_to_send_larger_than_checking_acc - delivery_fees,
sender_balance_after
);
// Receiver's balance does not change
assert_eq!(receiver_balance_after, receiver_balance_before);
}
/// Checking account should correctly account for incoming teleports.
#[test]
fn limited_teleport_native_assets_from_relay_to_asset_hub_checking_acc_burn_works() {
// Init values for Relay Chain
let amount_to_send: Balance = ASSET_HUB_ZAGROS_ED * 1000;
let destination = Zagros::child_location_of(AssetHubZagros::para_id());
let beneficiary_id = AssetHubZagrosReceiver::get().into();
let assets = (Here, amount_to_send).into();
let fee_asset_id: AssetId = Here.into();
let test_args = TestContext {
sender: ZagrosSender::get(),
receiver: AssetHubZagrosReceiver::get(),
args: TestArgs::new_para(
destination,
beneficiary_id,
amount_to_send,
assets,
None,
fee_asset_id,
),
};
let mut test = RelayToSystemParaTest::new(test_args);
let sender_balance_before = test.sender.balance;
let receiver_balance_before = test.receiver.balance;
fn para_dest_assertions_works(t: RelayToSystemParaTest) {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
// Amount to teleport is burned from Asset Hub's `CheckAccount`
RuntimeEvent::Balances(pezpallet_balances::Event::Burned { who, amount }) => {
who: *who == <AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::check_account(),
amount: *amount == t.args.amount,
},
RuntimeEvent::Balances(pezpallet_balances::Event::Minted { who, .. }) => {
who: *who == t.receiver.account_id,
},
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
}
test.set_assertion::<AssetHubZagros>(para_dest_assertions_works);
test.set_assertion::<Zagros>(relay_origin_assertions);
test.set_dispatchable::<Zagros>(relay_to_system_para_limited_teleport_assets);
test.assert();
let sender_balance_after = test.sender.balance;
let receiver_balance_after = test.receiver.balance;
let delivery_fees = Zagros::execute_with(|| {
xcm_helpers::teleport_assets_delivery_fees::<
<ZagrosXcmConfig as xcm_executor::Config>::XcmSender,
>(
test.args.assets.clone(),
test.args.fee_asset_id,
test.args.weight_limit,
test.args.beneficiary,
test.args.dest,
)
});
// Sender's balance is reduced
assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after);
// Receiver's asset balance is increased
assert!(receiver_balance_after > receiver_balance_before);
// Receiver's asset balance increased by `amount_to_send - delivery_fees - bought_execution`;
// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
// should be non-zero
assert!(receiver_balance_after < receiver_balance_before + amount_to_send);
}
/// Checking account should correctly account for outgoing teleports.
#[test]
fn limited_teleport_native_assets_from_asset_hub_to_relay_checking_acc_mint_works() {
// Init values for Relay Chain
let amount_to_send: Balance = ASSET_HUB_ZAGROS_ED * 1000;
let destination = AssetHubZagros::parent_location().into();
let beneficiary_id = ZagrosReceiver::get().into();
let assets = (Parent, amount_to_send).into();
let fee_asset_id: AssetId = Parent.into();
let test_args = TestContext {
sender: AssetHubZagrosSender::get(),
receiver: ZagrosReceiver::get(),
args: TestArgs::new_para(
destination,
beneficiary_id,
amount_to_send,
assets,
None,
fee_asset_id,
),
};
let mut test = SystemParaToRelayTest::new(test_args);
let sender_balance_before = test.sender.balance;
let receiver_balance_before = test.receiver.balance;
fn para_origin_assertions(t: SystemParaToRelayTest) {
AssetHubZagros::assert_xcm_pallet_attempted_complete(None);
AssetHubZagros::assert_teyrchain_system_ump_sent();
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::Balances(
pezpallet_balances::Event::Burned { who, amount }
) => {
who: *who == t.sender.account_id,
amount: *amount == t.args.amount,
},
// Amount to teleport is burned from Asset Hub's `CheckAccount`
RuntimeEvent::Balances(pezpallet_balances::Event::Minted { who, amount }) => {
who: *who == <AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::check_account(),
amount: *amount == t.args.amount,
},
]
);
}
fn relay_dest_assertions(t: SystemParaToRelayTest) {
type RuntimeEvent = <Zagros as Chain>::RuntimeEvent;
assert_expected_events!(
Zagros,
vec![
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
RuntimeEvent::Balances(pezpallet_balances::Event::Minted { who, .. }) => {
who: *who == t.receiver.account_id,
},
]
);
}
fn system_para_limited_teleport_assets(t: SystemParaToRelayTest) -> DispatchResult {
<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::limited_teleport_assets(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.beneficiary.into()),
bx!(t.args.assets.into()),
bx!(t.args.fee_asset_id.into()),
t.args.weight_limit,
)
}
test.set_assertion::<AssetHubZagros>(para_origin_assertions);
test.set_assertion::<Zagros>(relay_dest_assertions);
test.set_dispatchable::<AssetHubZagros>(system_para_limited_teleport_assets);
test.assert();
let sender_balance_after = test.sender.balance;
let receiver_balance_after = test.receiver.balance;
let delivery_fees = AssetHubZagros::execute_with(|| {
xcm_helpers::teleport_assets_delivery_fees::<
<AssetHubZagrosXcmConfig as xcm_executor::Config>::XcmSender,
>(
test.args.assets.clone(),
test.args.fee_asset_id,
test.args.weight_limit,
test.args.beneficiary,
test.args.dest,
)
});
// Sender's balance is reduced
assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after);
// Receiver's asset balance is increased
assert!(receiver_balance_after > receiver_balance_before);
// Receiver's asset balance increased by `amount_to_send - delivery_fees - bought_execution`;
// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
// should be non-zero
assert!(receiver_balance_after < receiver_balance_before + amount_to_send);
}
/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets while paying
/// fees using (reserve transferred) native asset.
pub fn do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt(
para_to_ah_dispatchable: fn(ParaToSystemParaTest) -> DispatchResult,
ah_to_para_dispatchable: fn(SystemParaToParaTest) -> DispatchResult,
) {
// Init values for Teyrchain
let fee_amount_to_send: Balance = ASSET_HUB_ZAGROS_ED * 1000;
let asset_location_on_penpal =
PenpalA::execute_with(|| PenpalLocalTeleportableToAssetHub::get());
let asset_id_on_penpal = match asset_location_on_penpal.last() {
Some(Junction::GeneralIndex(id)) => *id as u32,
_ => unreachable!(),
};
let asset_amount_to_send = ASSET_HUB_ZAGROS_ED * 1000;
let asset_owner = PenpalAssetOwner::get();
let system_para_native_asset_location = RelayLocation::get();
let sender = PenpalASender::get();
let penpal_check_account = <PenpalA as PenpalAPallet>::PezkuwiXcm::check_account();
let ah_as_seen_by_penpal = PenpalA::sibling_location_of(AssetHubZagros::para_id());
let penpal_assets: Assets = vec![
(Parent, fee_amount_to_send).into(),
(asset_location_on_penpal.clone(), asset_amount_to_send).into(),
]
.into();
let fee_asset_id: AssetId = Parent.into();
// fund Teyrchain's sender account
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner.clone()),
system_para_native_asset_location.clone(),
sender.clone(),
fee_amount_to_send * 2,
);
// No need to create the asset (only mint) as it exists in genesis.
PenpalA::mint_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner.clone()),
asset_id_on_penpal,
sender.clone(),
asset_amount_to_send * 2,
);
// fund Teyrchain's check account to be able to teleport
PenpalA::fund_accounts(vec![(penpal_check_account.clone().into(), ASSET_HUB_ZAGROS_ED * 1000)]);
// prefund SA of Penpal on AssetHub with enough native tokens to pay for fees
let penpal_as_seen_by_ah = AssetHubZagros::sibling_location_of(PenpalA::para_id());
let sov_penpal_on_ah = AssetHubZagros::sovereign_account_id_of(penpal_as_seen_by_ah);
AssetHubZagros::fund_accounts(vec![(
sov_penpal_on_ah.clone().into(),
ASSET_HUB_ZAGROS_ED * 100_000_000_000,
)]);
// Init values for System Teyrchain
let foreign_asset_at_asset_hub =
Location::new(1, [Junction::Teyrchain(PenpalA::para_id().into())])
.appended_with(asset_location_on_penpal)
.unwrap();
let penpal_to_ah_beneficiary_id = AssetHubZagrosReceiver::get();
// Penpal to AH test args
let penpal_to_ah_test_args = TestContext {
sender: PenpalASender::get(),
receiver: AssetHubZagrosReceiver::get(),
args: TestArgs::new_para(
ah_as_seen_by_penpal,
penpal_to_ah_beneficiary_id,
asset_amount_to_send,
penpal_assets,
Some(asset_id_on_penpal),
fee_asset_id,
),
};
let mut penpal_to_ah = ParaToSystemParaTest::new(penpal_to_ah_test_args);
let penpal_sender_balance_before = foreign_balance_on!(
PenpalA,
system_para_native_asset_location.clone(),
&PenpalASender::get()
);
let ah_receiver_balance_before = penpal_to_ah.receiver.balance;
let penpal_sender_assets_before = PenpalA::execute_with(|| {
type Assets = <PenpalA as PenpalAPallet>::Assets;
<Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalASender::get())
});
let ah_receiver_assets_before = foreign_balance_on!(
AssetHubZagros,
foreign_asset_at_asset_hub.clone(),
&AssetHubZagrosReceiver::get()
);
penpal_to_ah.set_assertion::<PenpalA>(penpal_to_ah_foreign_assets_sender_assertions);
penpal_to_ah.set_assertion::<AssetHubZagros>(penpal_to_ah_foreign_assets_receiver_assertions);
penpal_to_ah.set_dispatchable::<PenpalA>(para_to_ah_dispatchable);
penpal_to_ah.assert();
let penpal_sender_balance_after = foreign_balance_on!(
PenpalA,
system_para_native_asset_location.clone(),
&PenpalASender::get()
);
let ah_receiver_balance_after = penpal_to_ah.receiver.balance;
let penpal_sender_assets_after = PenpalA::execute_with(|| {
type Assets = <PenpalA as PenpalAPallet>::Assets;
<Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalASender::get())
});
let ah_receiver_assets_after = foreign_balance_on!(
AssetHubZagros,
foreign_asset_at_asset_hub.clone(),
&AssetHubZagrosReceiver::get()
);
// Sender's balance is reduced
assert!(penpal_sender_balance_after < penpal_sender_balance_before);
// Receiver's balance is increased
assert!(ah_receiver_balance_after > ah_receiver_balance_before);
// Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`;
// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
// should be non-zero
assert!(ah_receiver_balance_after < ah_receiver_balance_before + fee_amount_to_send);
// Sender's balance is reduced by exact amount
assert_eq!(penpal_sender_assets_before - asset_amount_to_send, penpal_sender_assets_after);
// Receiver's balance is increased by exact amount
assert_eq!(ah_receiver_assets_after, ah_receiver_assets_before + asset_amount_to_send);
///////////////////////////////////////////////////////////////////////
// Now test transferring foreign assets back from AssetHub to Penpal //
///////////////////////////////////////////////////////////////////////
// Move funds on AH from AHReceiver to AHSender
AssetHubZagros::execute_with(|| {
type ForeignAssets = <AssetHubZagros as AssetHubZagrosPallet>::ForeignAssets;
assert_ok!(ForeignAssets::transfer(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosReceiver::get()),
foreign_asset_at_asset_hub.clone().try_into().unwrap(),
AssetHubZagrosSender::get().into(),
asset_amount_to_send,
));
});
// Only send back half the amount.
let asset_amount_to_send = asset_amount_to_send / 2;
let fee_amount_to_send = fee_amount_to_send / 2;
let ah_to_penpal_beneficiary_id = PenpalAReceiver::get();
let penpal_as_seen_by_ah = AssetHubZagros::sibling_location_of(PenpalA::para_id());
let ah_assets: Assets = vec![
(Parent, fee_amount_to_send).into(),
(foreign_asset_at_asset_hub.clone(), asset_amount_to_send).into(),
]
.into();
let fee_asset_id: AssetId = Parent.into();
// AH to Penpal test args
let ah_to_penpal_test_args = TestContext {
sender: AssetHubZagrosSender::get(),
receiver: PenpalAReceiver::get(),
args: TestArgs::new_para(
penpal_as_seen_by_ah,
ah_to_penpal_beneficiary_id,
asset_amount_to_send,
ah_assets,
Some(asset_id_on_penpal),
fee_asset_id,
),
};
let mut ah_to_penpal = SystemParaToParaTest::new(ah_to_penpal_test_args);
let ah_sender_balance_before = ah_to_penpal.sender.balance;
let penpal_receiver_balance_before = foreign_balance_on!(
PenpalA,
system_para_native_asset_location.clone(),
&PenpalAReceiver::get()
);
let ah_sender_assets_before = foreign_balance_on!(
AssetHubZagros,
foreign_asset_at_asset_hub.clone(),
&AssetHubZagrosSender::get()
);
let penpal_receiver_assets_before = PenpalA::execute_with(|| {
type Assets = <PenpalA as PenpalAPallet>::Assets;
<Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalAReceiver::get())
});
ah_to_penpal.set_assertion::<AssetHubZagros>(ah_to_penpal_foreign_assets_sender_assertions);
ah_to_penpal.set_assertion::<PenpalA>(ah_to_penpal_foreign_assets_receiver_assertions);
ah_to_penpal.set_dispatchable::<AssetHubZagros>(ah_to_para_dispatchable);
ah_to_penpal.assert();
let ah_sender_balance_after = ah_to_penpal.sender.balance;
let penpal_receiver_balance_after =
foreign_balance_on!(PenpalA, system_para_native_asset_location, &PenpalAReceiver::get());
let ah_sender_assets_after = foreign_balance_on!(
AssetHubZagros,
foreign_asset_at_asset_hub.clone(),
&AssetHubZagrosSender::get()
);
let penpal_receiver_assets_after = PenpalA::execute_with(|| {
type Assets = <PenpalA as PenpalAPallet>::Assets;
<Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalAReceiver::get())
});
// Sender's balance is reduced
assert!(ah_sender_balance_after < ah_sender_balance_before);
// Receiver's balance is increased
assert!(penpal_receiver_balance_after > penpal_receiver_balance_before);
// Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`;
// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
// should be non-zero
assert!(penpal_receiver_balance_after < penpal_receiver_balance_before + fee_amount_to_send);
// Sender's balance is reduced by exact amount
assert_eq!(ah_sender_assets_before - asset_amount_to_send, ah_sender_assets_after);
// Receiver's balance is increased by exact amount
assert_eq!(penpal_receiver_assets_after, penpal_receiver_assets_before + asset_amount_to_send);
}
/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets should work
/// (using native reserve-based transfer for fees)
#[test]
fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt(
para_to_system_para_transfer_assets,
system_para_to_para_transfer_assets,
);
}
/// Teleport Native Asset from AssetHub to Teyrchain fails.
#[test]
fn teleport_to_untrusted_chain_fails() {
// Init values for Teyrchain Origin
let destination = AssetHubZagros::sibling_location_of(PenpalA::para_id());
let signed_origin =
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosSender::get().into());
let roc_to_send: Balance = ZAGROS_ED * 10000;
let roc_location = RelayLocation::get();
// Assets to send
let assets: Vec<Asset> = vec![(roc_location.clone(), roc_to_send).into()];
let fee_id: AssetId = roc_location.into();
// this should fail
AssetHubZagros::execute_with(|| {
let result = <AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::transfer_assets_using_type_and_then(
signed_origin.clone(),
bx!(destination.clone().into()),
bx!(assets.clone().into()),
bx!(TransferType::Teleport),
bx!(fee_id.into()),
bx!(TransferType::Teleport),
bx!(VersionedXcm::from(Xcm::<()>::new())),
Unlimited,
);
assert_err!(
result,
DispatchError::Module(pezsp_runtime::ModuleError {
index: 31,
error: [2, 0, 0, 0],
message: Some("Filtered")
})
);
});
// this should also fail
AssetHubZagros::execute_with(|| {
let xcm: Xcm<asset_hub_zagros_runtime::RuntimeCall> = Xcm(vec![
WithdrawAsset(assets.into()),
InitiateTeleport { assets: Wild(All), dest: destination, xcm: Xcm::<()>::new() },
]);
let result = <AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::execute(
signed_origin,
bx!(xcm::VersionedXcm::from(xcm)),
Weight::MAX,
);
assert!(result.is_err());
});
}
@@ -0,0 +1,669 @@
// 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::{assets_balance_on, create_pool_with_wnd_on, foreign_balance_on, imports::*};
use pezframe_support::traits::tokens::fungibles::Mutate;
use xcm_builder::{DescribeAllTerminal, DescribeFamily, HashedDescription};
use xcm_executor::traits::ConvertLocation;
/// PenpalA transacts on PenpalB, paying fees using USDT. XCM has to go through Asset Hub as the
/// reserve location of USDT. The original origin `PenpalA/PenpalASender` is proxied by Asset Hub.
fn transfer_and_transact_in_same_xcm(
destination: Location,
usdt: Asset,
beneficiary: Location,
call: xcm::DoubleEncoded<()>,
) {
let signed_origin = <PenpalA as Chain>::RuntimeOrigin::signed(PenpalASender::get().into());
let context = PenpalUniversalLocation::get();
let asset_hub_location = PenpalA::sibling_location_of(AssetHubZagros::para_id());
let Fungible(total_usdt) = usdt.fun else { unreachable!() };
// TODO(https://github.com/pezkuwichain/pezkuwi-sdk/issues/147): dry-run to get local fees, for now use hardcoded value.
let local_fees_amount = 80_000_000_000; // current exact value 69_200_786_622
let ah_fees_amount = 90_000_000_000; // current exact value 79_948_099_299
let usdt_to_ah_then_onward_amount = total_usdt - local_fees_amount - ah_fees_amount;
let local_fees: Asset = (usdt.id.clone(), local_fees_amount).into();
let fees_for_ah: Asset = (usdt.id.clone(), ah_fees_amount).into();
let usdt_to_ah_then_onward: Asset = (usdt.id.clone(), usdt_to_ah_then_onward_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_on_ah = Xcm(vec![InitiateTransfer {
destination,
remote_fees: Some(AssetTransferFilter::ReserveDeposit(Wild(All))),
preserve_origin: true,
assets: BoundedVec::new(),
remote_xcm: xcm_on_dest,
}]);
let xcm = Xcm::<()>(vec![
WithdrawAsset(usdt.into()),
PayFees { asset: local_fees },
InitiateTransfer {
destination: asset_hub_location,
remote_fees: Some(AssetTransferFilter::ReserveWithdraw(fees_for_ah.into())),
preserve_origin: true,
assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveWithdraw(
usdt_to_ah_then_onward.into(),
)]),
remote_xcm: xcm_on_ah,
},
]);
<PenpalA as PenpalAPallet>::PezkuwiXcm::execute(
signed_origin,
bx!(xcm::VersionedXcm::from(xcm.into())),
Weight::MAX,
)
.unwrap();
}
/// PenpalA transacts on PenpalB, paying fees using USDT. XCM has to go through Asset Hub as the
/// reserve location of USDT. The original origin `PenpalA/PenpalASender` is proxied by Asset Hub.
#[test]
fn transact_from_para_to_para_through_asset_hub() {
let destination = PenpalA::sibling_location_of(PenpalB::para_id());
let sender = PenpalASender::get();
let fee_amount_to_send: Balance = ZAGROS_ED * 10000;
let sender_chain_as_seen_by_asset_hub = AssetHubZagros::sibling_location_of(PenpalA::para_id());
let sov_of_sender_on_asset_hub =
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::sovereign_account_id_of(receiver_as_seen_by_asset_hub);
// Create SA-of-Penpal-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),
]);
// Prefund USDT to sov account of sender.
AssetHubZagros::execute_with(|| {
type Assets = <AssetHubZagros as AssetHubZagrosPallet>::Assets;
assert_ok!(<Assets as Mutate<_>>::mint_into(
USDT_ID,
&sov_of_sender_on_asset_hub.clone().into(),
fee_amount_to_send,
));
});
// We create a pool between ZGR and USDT in AssetHub.
let usdt = Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())]);
create_pool_with_wnd_on!(
AssetHubZagros,
usdt,
false,
AssetHubZagrosSender::get(),
1_000_000_000_000,
20_000_000_000
);
// We also need a pool between ZGR and USDT on PenpalA.
create_pool_with_wnd_on!(PenpalA, PenpalUsdtFromAssetHub::get(), true, PenpalAssetOwner::get());
// We also need a pool between ZGR and USDT on PenpalB.
create_pool_with_wnd_on!(PenpalB, PenpalUsdtFromAssetHub::get(), true, PenpalAssetOwner::get());
let usdt_from_asset_hub = PenpalUsdtFromAssetHub::get();
PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
assert_ok!(<ForeignAssets as Mutate<_>>::mint_into(
usdt_from_asset_hub.clone(),
&sender,
fee_amount_to_send,
));
});
// Give the sender enough Relay tokens to pay for local delivery fees.
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
RelayLocation::get(),
sender.clone(),
10_000_000_000_000, // Large estimate to make sure it works.
);
// Init values for Teyrchain Destination
let receiver = PenpalBReceiver::get();
// Query initial balances
let sender_assets_before = foreign_balance_on!(PenpalA, usdt_from_asset_hub.clone(), &sender);
let receiver_assets_before =
foreign_balance_on!(PenpalB, usdt_from_asset_hub.clone(), &receiver);
// Now register a new asset on PenpalB from PenpalA/sender account while paying fees using USDT
// (going through Asset Hub)
let usdt_to_send: Asset = (usdt_from_asset_hub.clone(), fee_amount_to_send).into();
let asset_location_on_penpal_a =
Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())]);
let penpal_a_as_seen_by_penpal_b = PenpalB::sibling_location_of(PenpalA::para_id());
let sender_as_seen_by_penpal_b =
penpal_a_as_seen_by_penpal_b.clone().appended_with(sender.clone()).unwrap();
let foreign_asset_at_penpal_b =
penpal_a_as_seen_by_penpal_b.appended_with(asset_location_on_penpal_a).unwrap();
// 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(),
);
PenpalA::execute_with(|| {
// initiate transaction
transfer_and_transact_in_same_xcm(destination, usdt_to_send, receiver.clone().into(), call);
// verify expected events;
PenpalA::assert_xcm_pallet_attempted_complete(None);
});
AssetHubZagros::execute_with(|| {
let sov_penpal_a_on_ah = AssetHubZagros::sovereign_account_id_of(
AssetHubZagros::sibling_location_of(PenpalA::para_id()),
);
asset_hub_hop_assertions(sov_penpal_a_on_ah);
});
PenpalB::execute_with(|| {
let expected_creator =
HashedDescription::<AccountId, DescribeFamily<DescribeAllTerminal>>::convert_location(
&sender_as_seen_by_penpal_b,
)
.unwrap();
penpal_b_assertions(foreign_asset_at_penpal_b, expected_creator, receiver.clone());
});
// Query final balances
let sender_assets_after = foreign_balance_on!(PenpalA, usdt_from_asset_hub.clone(), &sender);
let receiver_assets_after = foreign_balance_on!(PenpalB, usdt_from_asset_hub, &receiver);
// Sender's balance is reduced by amount
assert_eq!(sender_assets_after, sender_assets_before - fee_amount_to_send);
// Receiver's balance is increased
assert!(receiver_assets_after > receiver_assets_before);
}
#[test]
fn transact_using_authorized_alias_from_para_to_asset_hub_and_back_to_para() {
let sender = PenpalASender::get();
let sov_of_penpal_on_asset_hub = AssetHubZagros::sovereign_account_id_of(
AssetHubZagros::sibling_location_of(PenpalA::para_id()),
);
let wnd_from_teyrchain_pov: Location = RelayLocation::get();
let usdt_asset_hub_pov =
Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())]);
let usdt_penpal_pov = PenpalUsdtFromAssetHub::get();
let amount_of_wnd_to_transfer_to_ah = ZAGROS_ED * 1_000_000_000u128;
let amount_of_usdt_we_want_from_exchange = 1_000_000_000u128;
let max_amount_of_wnd_we_allow_for_exchange = 1_000_000_000_000u128;
let sender_as_seen_from_ah = Location::new(
1,
[
Teyrchain(2000),
AccountId32 {
network: Some(NetworkId::ByGenesis(PEZKUWICHAIN_GENESIS_HASH)),
id: sender.clone().into(),
},
],
);
let mut topic_id_tracker = TopicIdTracker::new();
// SA-of-Penpal-on-AHW should contain ZGR amount equal at least the amount that will be
// transferred-in to AH Since AH is the reserve for ZGR
AssetHubZagros::fund_accounts(vec![(
sov_of_penpal_on_asset_hub.clone().into(),
ASSET_HUB_ZAGROS_ED + amount_of_wnd_to_transfer_to_ah,
)]);
// Give the sender enough ZGR
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
wnd_from_teyrchain_pov.clone(),
sender.clone(),
amount_of_wnd_to_transfer_to_ah,
);
// We create a pool between ZGR and USDT in AssetHub so we can do the exchange
create_pool_with_wnd_on!(
AssetHubZagros,
usdt_asset_hub_pov.clone(),
false,
AssetHubZagrosSender::get(),
1_000_000_000_000,
20_000_000_000
);
// We add auhtorized alias on AH so sender from Penpal can AliasOrigin into itself on AH
// (instead of aliasing into Sovereign Account of sender)
AssetHubZagros::execute_with(|| {
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::add_authorized_alias(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(sender.clone()),
Box::new(sender_as_seen_from_ah.into()),
None
));
});
// Query initial balances
let sender_usdt_on_penpal_before =
foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender);
let sender_usdt_on_ah_before = assets_balance_on!(AssetHubZagros, USDT_ID, &sender);
// Encoded `swap_tokens_for_exact_tokens` call to be executed in AH
let call = <AssetHubZagros as Chain>::RuntimeCall::AssetConversion(
pezpallet_asset_conversion::Call::swap_tokens_for_exact_tokens {
path: vec![
Box::new(wnd_from_teyrchain_pov.clone()),
Box::new(usdt_asset_hub_pov.clone()),
],
amount_out: amount_of_usdt_we_want_from_exchange,
amount_in_max: max_amount_of_wnd_we_allow_for_exchange,
send_to: sender.clone(),
keep_alive: true,
},
)
.encode()
.into();
let asset_hub_location_penpal_pov = PenpalA::sibling_location_of(AssetHubZagros::para_id());
let penpal_location_ah_pov = AssetHubZagros::sibling_location_of(PenpalA::para_id());
PenpalA::execute_with(|| {
let sender_signed_origin = <PenpalA as Chain>::RuntimeOrigin::signed(sender.clone());
let local_fees_amount = 80_000_000_000_000u128;
let remote_fees_amount = 90_000_000_000_000u128;
let penpal_local_fees: Asset = (wnd_from_teyrchain_pov.clone(), local_fees_amount).into();
let ah_remote_fees: Asset = (wnd_from_teyrchain_pov.clone(), remote_fees_amount).into();
let penpal_remote_fees: Asset = (wnd_from_teyrchain_pov.clone(), remote_fees_amount).into();
let wnd_to_withdraw: Asset =
(wnd_from_teyrchain_pov.clone(), amount_of_wnd_to_transfer_to_ah).into();
// xcm to be executed by penpal, sent by ah
let xcm_back_on_penpal = Xcm(vec![
RefundSurplus,
DepositAsset { assets: Wild(All), beneficiary: sender.clone().into() },
]);
// xcm to be executed by ah, sent by penpal
let xcm_on_ah = Xcm(vec![
// aliasing into sender itself, as opposed to sender's sovereign account
// its possible due to add_authorized_alias above
AliasOrigin(Location::new(
0,
[AccountId32 { network: None, id: sender.clone().into() }],
)),
DepositAsset {
assets: Definite(
(wnd_from_teyrchain_pov.clone(), max_amount_of_wnd_we_allow_for_exchange)
.into(),
),
beneficiary: sender.clone().into(),
},
Transact { origin_kind: OriginKind::SovereignAccount, call, fallback_max_weight: None },
ExpectTransactStatus(MaybeErrorCode::Success),
WithdrawAsset(
(usdt_asset_hub_pov.clone(), amount_of_usdt_we_want_from_exchange).into(),
),
InitiateTransfer {
destination: penpal_location_ah_pov,
remote_fees: Some(AssetTransferFilter::ReserveDeposit(
penpal_remote_fees.clone().into(),
)),
preserve_origin: false,
assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveDeposit(Wild(
All,
))]),
remote_xcm: xcm_back_on_penpal,
},
RefundSurplus,
DepositAsset { assets: Wild(All), beneficiary: sender.clone().into() },
]);
// xcm to be executed locally on penpal as starting point
let xcm = Xcm::<()>(vec![
WithdrawAsset(wnd_to_withdraw.into()),
PayFees { asset: penpal_local_fees },
InitiateTransfer {
destination: asset_hub_location_penpal_pov,
remote_fees: Some(AssetTransferFilter::ReserveWithdraw(
ah_remote_fees.clone().into(),
)),
preserve_origin: true,
assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveWithdraw(
Wild(All),
)]),
remote_xcm: xcm_on_ah,
},
RefundSurplus,
DepositAsset { assets: Wild(All), beneficiary: sender.clone().into() },
]);
// initiate transaction
<PenpalA as PenpalAPallet>::PezkuwiXcm::execute(
sender_signed_origin,
bx!(xcm::VersionedXcm::from(xcm.into())),
Weight::MAX,
)
.unwrap();
// verify expected events;
PenpalA::assert_xcm_pallet_attempted_complete(None);
let msg_sent_id = find_xcm_sent_message_id::<PenpalA>().expect("Missing Sent Event");
topic_id_tracker.insert("PenpalA_sent", msg_sent_id.into());
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
RuntimeEvent::AssetConversion(
pezpallet_asset_conversion::Event::SwapExecuted { amount_out, ..}
) => { amount_out: *amount_out == amount_of_usdt_we_want_from_exchange, },
]
);
let mq_prc_id = find_mq_processed_id::<AssetHubZagros>().expect("Missing Processed Event");
topic_id_tracker.insert("AssetHubZagros_received", mq_prc_id);
let msg_sent_id = find_xcm_sent_message_id::<AssetHubZagros>().expect("Missing Sent Event");
topic_id_tracker.insert("AssetHubZagros_sent", msg_sent_id.into());
});
PenpalA::execute_with(|| {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
assert_expected_events!(
PenpalA,
vec![
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
let mq_prc_id = find_mq_processed_id::<PenpalA>().expect("Missing Processed Event");
topic_id_tracker.insert("PenpalA_received", mq_prc_id);
});
topic_id_tracker.assert_unique();
// Query final balances
let sender_usdt_on_ah_after = assets_balance_on!(AssetHubZagros, USDT_ID, &sender);
let sender_usdt_on_penpal_after =
foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender);
// Receiver's balance is increased by usdt amount we got from exchange
assert_eq!(
sender_usdt_on_penpal_after,
sender_usdt_on_penpal_before + amount_of_usdt_we_want_from_exchange
);
// Usdt amount on senders account AH side should stay the same i.e. all usdt came from exchange
// not free balance
assert_eq!(sender_usdt_on_ah_before, sender_usdt_on_ah_after);
}
#[test]
fn transact_using_sov_account_from_para_to_asset_hub_and_back_to_para() {
let sender = PenpalASender::get();
let sov_of_penpal_on_asset_hub = AssetHubZagros::sovereign_account_id_of(
AssetHubZagros::sibling_location_of(PenpalA::para_id()),
);
let wnd_from_teyrchain_pov: Location = RelayLocation::get();
let usdt_asset_hub_pov =
Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())]);
let usdt_penpal_pov = PenpalUsdtFromAssetHub::get();
let amount_of_wnd_to_transfer_to_ah = ZAGROS_ED * 1_000_000_000u128;
let amount_of_usdt_we_want_from_exchange = 1_000_000_000u128;
let max_amount_of_wnd_we_allow_for_exchange = 1_000_000_000_000u128;
let sender_as_seen_from_ah = Location::new(
1,
[
Teyrchain(2000),
AccountId32 {
network: Some(NetworkId::ByGenesis(PEZKUWICHAIN_GENESIS_HASH)),
id: sender.clone().into(),
},
],
);
let sov_of_sender_on_asset_hub =
AssetHubZagros::sovereign_account_id_of(sender_as_seen_from_ah.clone());
let mut topic_id_tracker = TopicIdTracker::new();
// SA-of-Penpal-on-AHW should contain ZGR amount equal at least the amount that will be
// transferred-in to AH Since AH is the reserve for ZGR
AssetHubZagros::fund_accounts(vec![(
sov_of_penpal_on_asset_hub.clone().into(),
ASSET_HUB_ZAGROS_ED + amount_of_wnd_to_transfer_to_ah,
)]);
// Give the sender enough ZGR
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
wnd_from_teyrchain_pov.clone(),
sender.clone(),
amount_of_wnd_to_transfer_to_ah,
);
// We create a pool between ZGR and USDT in AssetHub so we can do the exchange
create_pool_with_wnd_on!(
AssetHubZagros,
usdt_asset_hub_pov.clone(),
false,
AssetHubZagrosSender::get(),
1_000_000_000_000,
20_000_000_000
);
// Query initial balances
let sender_usdt_on_penpal_before =
foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender);
let sender_usdt_on_ah_before =
assets_balance_on!(AssetHubZagros, USDT_ID, &sov_of_sender_on_asset_hub);
// Encoded `swap_tokens_for_exact_tokens` call to be executed in AH
let call = <AssetHubZagros as Chain>::RuntimeCall::AssetConversion(
pezpallet_asset_conversion::Call::swap_tokens_for_exact_tokens {
path: vec![
Box::new(wnd_from_teyrchain_pov.clone()),
Box::new(usdt_asset_hub_pov.clone()),
],
amount_out: amount_of_usdt_we_want_from_exchange,
amount_in_max: max_amount_of_wnd_we_allow_for_exchange,
send_to: sov_of_sender_on_asset_hub.clone(),
keep_alive: false,
},
)
.encode()
.into();
let asset_hub_location_penpal_pov = PenpalA::sibling_location_of(AssetHubZagros::para_id());
let penpal_location_ah_pov = AssetHubZagros::sibling_location_of(PenpalA::para_id());
PenpalA::execute_with(|| {
let sender_signed_origin = <PenpalA as Chain>::RuntimeOrigin::signed(sender.clone());
let local_fees_amount = 80_000_000_000_000u128;
let remote_fees_amount = 90_000_000_000_000u128;
let penpal_local_fees: Asset = (wnd_from_teyrchain_pov.clone(), local_fees_amount).into();
let ah_remote_fees: Asset = (wnd_from_teyrchain_pov.clone(), remote_fees_amount).into();
let penpal_remote_fees: Asset = (wnd_from_teyrchain_pov.clone(), remote_fees_amount).into();
let wnd_to_withdraw: Asset =
(wnd_from_teyrchain_pov.clone(), amount_of_wnd_to_transfer_to_ah).into();
// xcm to be executed by penpal, sent by ah
let xcm_back_on_penpal = Xcm(vec![
RefundSurplus,
DepositAsset { assets: Wild(All), beneficiary: sender.clone().into() },
]);
// xcm to be executed by ah, sent by penpal
let xcm_on_ah = Xcm(vec![
DepositAsset {
assets: Definite(
(wnd_from_teyrchain_pov.clone(), max_amount_of_wnd_we_allow_for_exchange)
.into(),
),
beneficiary: sov_of_sender_on_asset_hub.clone().into(),
},
Transact { origin_kind: OriginKind::SovereignAccount, call, fallback_max_weight: None },
ExpectTransactStatus(MaybeErrorCode::Success),
WithdrawAsset(
(usdt_asset_hub_pov.clone(), amount_of_usdt_we_want_from_exchange).into(),
),
InitiateTransfer {
destination: penpal_location_ah_pov,
remote_fees: Some(AssetTransferFilter::ReserveDeposit(
penpal_remote_fees.clone().into(),
)),
preserve_origin: false,
assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveDeposit(Wild(
All,
))]),
remote_xcm: xcm_back_on_penpal,
},
RefundSurplus,
DepositAsset {
assets: Wild(All),
beneficiary: sov_of_sender_on_asset_hub.clone().into(),
},
]);
// xcm to be executed locally on penpal as starting point
let xcm = Xcm::<()>(vec![
WithdrawAsset(wnd_to_withdraw.into()),
PayFees { asset: penpal_local_fees },
InitiateTransfer {
destination: asset_hub_location_penpal_pov,
remote_fees: Some(AssetTransferFilter::ReserveWithdraw(
ah_remote_fees.clone().into(),
)),
preserve_origin: true,
assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveWithdraw(
Wild(All),
)]),
remote_xcm: xcm_on_ah,
},
RefundSurplus,
DepositAsset { assets: Wild(All), beneficiary: sender.clone().into() },
]);
// initiate transaction
<PenpalA as PenpalAPallet>::PezkuwiXcm::execute(
sender_signed_origin,
bx!(xcm::VersionedXcm::from(xcm.into())),
Weight::MAX,
)
.unwrap();
// verify expected events;
PenpalA::assert_xcm_pallet_attempted_complete(None);
let msg_sent_id = find_xcm_sent_message_id::<PenpalA>().expect("Missing Sent Event");
topic_id_tracker.insert("PenpalA_sent", msg_sent_id.into());
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
RuntimeEvent::AssetConversion(
pezpallet_asset_conversion::Event::SwapExecuted { amount_out, ..}
) => { amount_out: *amount_out == amount_of_usdt_we_want_from_exchange, },
]
);
let mq_prc_id = find_mq_processed_id::<AssetHubZagros>().expect("Missing Processed Event");
topic_id_tracker.insert("AssetHubZagros_received", mq_prc_id);
let msg_sent_id = find_xcm_sent_message_id::<AssetHubZagros>().expect("Missing Sent Event");
topic_id_tracker.insert("AssetHubZagros_sent", msg_sent_id.into());
});
PenpalA::execute_with(|| {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
assert_expected_events!(
PenpalA,
vec![
RuntimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
let mq_prc_id = find_mq_processed_id::<PenpalA>().expect("Missing Processed Event");
topic_id_tracker.insert("PenpalA_received", mq_prc_id);
});
topic_id_tracker.assert_unique();
// Query final balances
let sender_usdt_on_ah_after =
assets_balance_on!(AssetHubZagros, USDT_ID, &sov_of_sender_on_asset_hub);
let sender_usdt_on_penpal_after =
foreign_balance_on!(PenpalA, usdt_penpal_pov.clone(), &sender);
// Receiver's balance is increased by usdt amount we got from exchange
assert_eq!(
sender_usdt_on_penpal_after,
sender_usdt_on_penpal_before + amount_of_usdt_we_want_from_exchange
);
// Usdt amount on senders account AH side should stay the same i.e. all usdt came from exchange
// not free balance
assert_eq!(sender_usdt_on_ah_before, sender_usdt_on_ah_after);
}
fn asset_hub_hop_assertions(sender_sa: AccountId) {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
// Withdrawn from sender teyrchain SA
RuntimeEvent::Assets(
pezpallet_assets::Event::Burned { owner, .. }
) => {
owner: *owner == sender_sa,
},
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,
},
]
);
}
@@ -0,0 +1,267 @@
// 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 for the validation of `pezpallet_xcm::Pallet::<T>::transfer_assets`.
//! See the `pezpallet_xcm::transfer_assets_validation` module for more information.
use crate::imports::*;
use pezframe_support::{assert_err, assert_ok};
use pezsp_runtime::DispatchError;
// ==================================================================================
// ============================== PenpalA <-> Zagros ===============================
// ==================================================================================
/// Test that `transfer_assets` fails when doing reserve transfer of ZGR from PenpalA to Zagros.
/// This fails because PenpalA's IsReserve config considers Zagros as the reserve for ZGR,
/// so transfer_assets automatically chooses reserve transfer, which we block.
#[test]
fn transfer_assets_wnd_reserve_transfer_para_to_relay_fails() {
let destination = PenpalA::parent_location();
let beneficiary: Location =
AccountId32Junction { network: None, id: ZagrosReceiver::get().into() }.into();
let amount_to_send: Balance = ZAGROS_ED * 1000;
let assets: Assets = (Parent, amount_to_send).into();
// Mint ZGR on PenpalA for testing.
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
RelayLocation::get(),
PenpalASender::get(),
amount_to_send * 2,
);
// Fund PenpalA's sovereign account on Zagros with the reserve ZGR.
let penpal_location_as_seen_by_relay = Zagros::child_location_of(PenpalA::para_id());
let sov_penpal_on_relay = Zagros::sovereign_account_id_of(penpal_location_as_seen_by_relay);
Zagros::fund_accounts(vec![(sov_penpal_on_relay.into(), amount_to_send * 2)]);
let fee_asset_id: AssetId = Parent.into();
PenpalA::execute_with(|| {
let result = <PenpalA as PenpalAPallet>::PezkuwiXcm::transfer_assets(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalASender::get()),
bx!(destination.into()),
bx!(beneficiary.into()),
bx!(assets.into()),
bx!(fee_asset_id.into()),
WeightLimit::Unlimited,
);
// This should fail because ZGR reserve transfer is blocked.
assert_err!(
result,
DispatchError::Module(pezsp_runtime::ModuleError {
index: 31,
error: [21, 0, 0, 0], // InvalidAssetUnknownReserve.
message: Some("InvalidAssetUnknownReserve")
})
);
});
}
/// Test that `transfer_assets` fails when doing reserve transfer of ZGR from Zagros to PenpalA
/// This fails because Zagros's configuration would make this a reserve transfer, which we block.
#[test]
fn transfer_assets_wnd_reserve_transfer_relay_to_para_fails() {
let destination = Zagros::child_location_of(PenpalA::para_id());
let beneficiary: Location =
AccountId32Junction { network: None, id: PenpalAReceiver::get().into() }.into();
let amount_to_send: Balance = ZAGROS_ED * 1000;
let assets: Assets = (Here, amount_to_send).into();
let fee_asset_id: AssetId = Here.into();
Zagros::execute_with(|| {
let result = <Zagros as ZagrosPallet>::XcmPallet::transfer_assets(
<Zagros as Chain>::RuntimeOrigin::signed(ZagrosSender::get()),
bx!(destination.into()),
bx!(beneficiary.into()),
bx!(assets.into()),
bx!(fee_asset_id.into()),
WeightLimit::Unlimited,
);
// This should fail because ZGR reserve transfer is blocked.
assert_err!(
result,
DispatchError::Module(pezsp_runtime::ModuleError {
index: 99,
error: [21, 0, 0, 0], // InvalidAssetUnknownReserve.
message: Some("InvalidAssetUnknownReserve")
})
);
});
}
// ==================================================================================
// ============================== PenpalA <-> PenpalB ===============================
// ==================================================================================
/// Test that `transfer_assets` fails when doing reserve transfer of ZGR from PenpalA to PenpalB
#[test]
fn transfer_assets_wnd_reserve_transfer_para_to_para_fails() {
let destination = PenpalA::sibling_location_of(PenpalB::para_id());
let beneficiary: Location =
AccountId32Junction { network: None, id: PenpalBReceiver::get().into() }.into();
let amount_to_send: Balance = ZAGROS_ED * 1000;
let assets: Assets = (Parent, amount_to_send).into();
// Mint ZGR on PenpalA for testing
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
RelayLocation::get(),
PenpalASender::get(),
amount_to_send * 2,
);
let fee_asset_id: AssetId = Parent.into();
PenpalA::execute_with(|| {
let result = <PenpalA as PenpalAPallet>::PezkuwiXcm::transfer_assets(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalASender::get()),
bx!(destination.into()),
bx!(beneficiary.into()),
bx!(assets.into()),
bx!(fee_asset_id.into()),
WeightLimit::Unlimited,
);
// This should fail because ZGR reserve transfer is blocked
assert_err!(
result,
DispatchError::Module(pezsp_runtime::ModuleError {
index: 31,
error: [21, 0, 0, 0], // InvalidAssetUnknownReserve
message: Some("InvalidAssetUnknownReserve")
})
);
});
}
// ==================================================================================
// ============================== Mixed Assets and Fees =============================
// ==================================================================================
/// Test that `transfer_assets` fails when ZGR is used as fee asset in reserve transfer
#[test]
fn transfer_assets_wnd_as_fee_in_reserve_transfer_fails() {
let destination = PenpalA::sibling_location_of(PenpalB::para_id());
let beneficiary: Location =
AccountId32Junction { network: None, id: PenpalBReceiver::get().into() }.into();
let asset_amount: Balance = 1_000_000_000_000; // A million USDT.
let fee_amount: Balance = ZAGROS_ED * 100;
// Create a foreign asset location (representing another asset).
let foreign_asset_location = Location::new(
1,
[
Teyrchain(AssetHubZagros::para_id().into()),
PalletInstance(ASSETS_PALLET_ID),
GeneralIndex(USDT_ID.into()), // USDT.
],
);
// Mint both assets on PenpalA for testing.
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
foreign_asset_location.clone(),
PenpalASender::get(),
asset_amount * 2,
);
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
RelayLocation::get(),
PenpalASender::get(),
fee_amount * 2,
);
// Transfer foreign asset, pay fees with ZGR.
let assets: Assets = vec![
(foreign_asset_location, asset_amount).into(),
(Parent, fee_amount).into(), // ZGR as fee.
]
.into();
let fee_asset_id: AssetId = Parent.into(); // ZGR is the fee asset.
PenpalA::execute_with(|| {
let result = <PenpalA as PenpalAPallet>::PezkuwiXcm::transfer_assets(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalASender::get()),
bx!(destination.into()),
bx!(beneficiary.into()),
bx!(assets.into()),
bx!(fee_asset_id.into()),
WeightLimit::Unlimited,
);
// This should fail because ZGR fee would be reserve transferred.
assert_err!(
result,
DispatchError::Module(pezsp_runtime::ModuleError {
index: 31,
error: [21, 0, 0, 0], // InvalidAssetUnknownReserve.
message: Some("InvalidAssetUnknownReserve")
})
);
});
}
/// Test that `transfer_assets` works when neither asset nor fee is ZGR.
#[test]
fn transfer_assets_non_native_assets_work() {
let destination = PenpalA::sibling_location_of(PenpalB::para_id());
let beneficiary: Location =
AccountId32Junction { network: None, id: PenpalBReceiver::get().into() }.into();
let amount: Balance = 1_000_000_000_000; // A million USDT.
// Create foreign asset locations (both non-native).
let asset_location = Location::new(
1,
[
Teyrchain(AssetHubZagros::para_id().into()),
PalletInstance(ASSETS_PALLET_ID),
GeneralIndex(USDT_ID.into()), // USDT.
],
);
// Mint both USDT and ZGR on PenpalA, one for sending, the other for paying delivery fees.
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
RelayLocation::get(),
PenpalASender::get(),
amount * 2,
);
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
asset_location.clone(),
PenpalASender::get(),
amount * 2,
);
// Transfer non-native assets.
let assets: Assets = (asset_location.clone(), amount).into();
let fee_asset_id: AssetId = (asset_location).into();
PenpalA::execute_with(|| {
let result = <PenpalA as PenpalAPallet>::PezkuwiXcm::transfer_assets(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalASender::get()),
bx!(destination.into()),
bx!(beneficiary.into()),
bx!(assets.into()),
bx!(fee_asset_id.into()),
WeightLimit::Unlimited,
);
// This should succeed because neither asset is ZGR.
assert_ok!(result);
});
}
@@ -0,0 +1,121 @@
// 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::{
accounts::{ALICE, BOB},
USDT_ID,
};
use pezframe_support::traits::fungibles::{Inspect, Mutate};
use pezkuwi_runtime_common::impls::VersionedLocatableAsset;
use xcm_executor::traits::ConvertLocation;
use zagros_system_emulated_network::zagros_emulated_chain::zagros_runtime::Dmp;
#[test]
fn create_and_claim_treasury_spend() {
const SPEND_AMOUNT: u128 = 1_000_000_000;
// treasury location from a sibling teyrchain.
let treasury_location: Location = Location::new(1, PalletInstance(37));
// treasury account on a sibling teyrchain.
let treasury_account =
ahw_xcm_config::LocationToAccountId::convert_location(&treasury_location).unwrap();
let asset_hub_location = Location::new(0, Teyrchain(AssetHubZagros::para_id().into()));
let root = <Zagros as Chain>::RuntimeOrigin::root();
// asset kind to be spent from the treasury.
let asset_kind: VersionedLocatableAsset =
(asset_hub_location, AssetId([PalletInstance(50), GeneralIndex(USDT_ID.into())].into()))
.into();
// treasury spend beneficiary.
let alice: AccountId = Zagros::account_id_of(ALICE);
let bob: AccountId = Zagros::account_id_of(BOB);
let bob_signed = <Zagros as Chain>::RuntimeOrigin::signed(bob.clone());
AssetHubZagros::execute_with(|| {
type Assets = <AssetHubZagros as AssetHubZagrosPallet>::Assets;
// USDT created at genesis, mint some assets to the treasury account.
assert_ok!(<Assets as Mutate<_>>::mint_into(USDT_ID, &treasury_account, SPEND_AMOUNT * 4));
// beneficiary has zero balance.
assert_eq!(<Assets as Inspect<_>>::balance(USDT_ID, &alice,), 0u128,);
});
Zagros::execute_with(|| {
type RuntimeEvent = <Zagros as Chain>::RuntimeEvent;
type Treasury = <Zagros as ZagrosPallet>::Treasury;
type AssetRate = <Zagros as ZagrosPallet>::AssetRate;
// create a conversion rate from `asset_kind` to the native currency.
assert_ok!(AssetRate::create(root.clone(), Box::new(asset_kind.clone()), 2.into()));
Dmp::make_teyrchain_reachable(1000);
// create and approve a treasury spend.
assert_ok!(Treasury::spend(
root,
Box::new(asset_kind),
SPEND_AMOUNT,
Box::new(Location::new(0, Into::<[u8; 32]>::into(alice.clone())).into()),
None,
));
// claim the spend.
assert_ok!(Treasury::payout(bob_signed.clone(), 0));
assert_expected_events!(
Zagros,
vec![
RuntimeEvent::Treasury(pezpallet_treasury::Event::Paid { .. }) => {},
]
);
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
type Assets = <AssetHubZagros as AssetHubZagrosPallet>::Assets;
// assert events triggered by xcm pay program
// 1. treasury asset transferred to spend beneficiary
// 2. response to Relay Chain treasury pallet instance sent back
// 3. XCM program completed
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::Assets(pezpallet_assets::Event::Transferred { asset_id: id, from, to, amount }) => {
id: id == &USDT_ID,
from: from == &treasury_account,
to: to == &alice,
amount: amount == &SPEND_AMOUNT,
},
RuntimeEvent::TeyrchainSystem(cumulus_pallet_teyrchain_system::Event::UpwardMessageSent { .. }) => {},
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed { success: true ,.. }) => {},
]
);
// beneficiary received the assets from the treasury.
assert_eq!(<Assets as Inspect<_>>::balance(USDT_ID, &alice,), SPEND_AMOUNT,);
});
Zagros::execute_with(|| {
type RuntimeEvent = <Zagros as Chain>::RuntimeEvent;
type Treasury = <Zagros as ZagrosPallet>::Treasury;
// check the payment status to ensure the response from the AssetHub was received.
assert_ok!(Treasury::check_status(bob_signed, 0));
assert_expected_events!(
Zagros,
vec![
RuntimeEvent::Treasury(pezpallet_treasury::Event::SpendProcessed { .. }) => {},
]
);
});
}
@@ -0,0 +1,474 @@
// 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 to ensure correct XCM fee estimation for cross-chain asset transfers.
use crate::{create_pool_with_wnd_on, imports::*};
use emulated_integration_tests_common::test_can_estimate_and_pay_exact_fees;
use pezframe_support::dispatch::RawOrigin;
use xcm_runtime_apis::{
dry_run::runtime_decl_for_dry_run_api::DryRunApiV2,
fees::runtime_decl_for_xcm_payment_api::XcmPaymentApiV2,
};
fn usdt_transfer_call(
destination: Location,
beneficiary: Location,
amount_to_send: u128,
usdt_location_on_penpal: Location,
usdt_location_on_ah: Location,
) -> <PenpalA as Chain>::RuntimeCall {
let asset_hub_location: Location = PenpalA::sibling_location_of(AssetHubZagros::para_id());
// Create the XCM to transfer USDT to PenpalB via Asset Hub using InitiateTransfer
let remote_xcm_on_penpal_b =
Xcm::<()>(vec![DepositAsset { assets: Wild(AllCounted(1)), beneficiary }]);
let xcm_on_asset_hub = Xcm::<()>(vec![InitiateTransfer {
destination,
remote_fees: Some(AssetTransferFilter::ReserveDeposit(
Definite((usdt_location_on_ah, 1_000_000u128).into()), // 1 USDT for fees
)),
preserve_origin: false,
assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveDeposit(Wild(All))]),
remote_xcm: remote_xcm_on_penpal_b,
}]);
let xcm = Xcm::<<PenpalA as Chain>::RuntimeCall>(vec![
WithdrawAsset((usdt_location_on_penpal.clone(), amount_to_send).into()),
PayFees {
asset: Asset {
id: AssetId(usdt_location_on_penpal.clone()),
fun: Fungible(1_000_000u128), // 1 USDT for local fees
},
},
InitiateTransfer {
destination: asset_hub_location,
remote_fees: Some(AssetTransferFilter::ReserveWithdraw(
Definite((usdt_location_on_penpal.clone(), 1_000_000u128).into()), /* 1 USDT for
* Asset Hub fees */
)),
preserve_origin: false,
assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveWithdraw(Wild(
All,
))]),
remote_xcm: xcm_on_asset_hub,
},
]);
<PenpalA as Chain>::RuntimeCall::PezkuwiXcm(pezpallet_xcm::Call::execute {
message: bx!(VersionedXcm::from(xcm)),
max_weight: Weight::MAX,
})
}
fn sender_assertions(test: ParaToParaThroughAHTest) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
PenpalA::assert_xcm_pallet_attempted_complete(None);
assert_expected_events!(
PenpalA,
vec![
RuntimeEvent::ForeignAssets(
pezpallet_assets::Event::Burned { asset_id, owner, balance }
) => {
asset_id: *asset_id == Location::new(1, []),
owner: *owner == test.sender.account_id,
balance: *balance == test.args.amount,
},
]
);
}
fn hop_assertions(test: ParaToParaThroughAHTest) {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
AssetHubZagros::assert_xcmp_queue_success(None);
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::Balances(
pezpallet_balances::Event::Burned { amount, .. }
) => {
amount: *amount >= test.args.amount * 90/100,
},
]
);
}
fn receiver_assertions(test: ParaToParaThroughAHTest) {
type RuntimeEvent = <PenpalB as Chain>::RuntimeEvent;
PenpalB::assert_xcmp_queue_success(None);
assert_expected_events!(
PenpalB,
vec![
RuntimeEvent::ForeignAssets(
pezpallet_assets::Event::Issued { asset_id, owner, .. }
) => {
asset_id: *asset_id == Location::new(1, []),
owner: *owner == test.receiver.account_id,
},
]
);
}
fn transfer_assets_para_to_para_through_ah_call(
test: ParaToParaThroughAHTest,
) -> <PenpalA as Chain>::RuntimeCall {
type RuntimeCall = <PenpalA as Chain>::RuntimeCall;
let asset_hub_location: Location = PenpalA::sibling_location_of(AssetHubZagros::para_id());
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
assets: Wild(AllCounted(test.args.assets.len() as u32)),
beneficiary: test.args.beneficiary,
}]);
RuntimeCall::PezkuwiXcm(pezpallet_xcm::Call::transfer_assets_using_type_and_then {
dest: bx!(test.args.dest.into()),
assets: bx!(test.args.assets.clone().into()),
assets_transfer_type: bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())),
remote_fees_id: bx!(VersionedAssetId::from(test.args.fee_asset_id)),
fees_transfer_type: bx!(TransferType::RemoteReserve(asset_hub_location.into())),
custom_xcm_on_dest: bx!(VersionedXcm::from(custom_xcm_on_dest)),
weight_limit: test.args.weight_limit,
})
}
/// We are able to dry-run and estimate the fees for a multi-hop XCM journey.
/// Scenario: Alice on PenpalA has some ZGR and wants to send them to PenpalB.
/// We want to know the fees using the `DryRunApi` and `XcmPaymentApi`.
#[test]
fn multi_hop_works() {
let destination = PenpalA::sibling_location_of(PenpalB::para_id());
let sender = PenpalASender::get();
let amount_to_send = 1_000_000_000_000;
let asset_owner = PenpalAssetOwner::get();
let assets: Assets = (Parent, amount_to_send).into();
let relay_native_asset_location = Location::parent();
let sender_as_seen_by_ah = AssetHubZagros::sibling_location_of(PenpalA::para_id());
let sov_of_sender_on_ah = AssetHubZagros::sovereign_account_id_of(sender_as_seen_by_ah.clone());
// fund Teyrchain's sender account
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner.clone()),
relay_native_asset_location.clone(),
sender.clone(),
amount_to_send * 2,
);
// fund the Teyrchain Origin's SA on AssetHub with the native tokens held in reserve.
AssetHubZagros::fund_accounts(vec![(sov_of_sender_on_ah.clone(), amount_to_send * 2)]);
// Init values for Teyrchain Destination
let beneficiary_id = PenpalBReceiver::get();
let fee_asset_id: AssetId = Parent.into();
let test_args = TestContext {
sender: PenpalASender::get(), // Bob in PenpalB.
receiver: PenpalBReceiver::get(), // Alice.
args: TestArgs::new_para(
destination,
beneficiary_id.clone(),
amount_to_send,
assets,
None,
fee_asset_id,
),
};
let mut test = ParaToParaThroughAHTest::new(test_args);
// We get them from the PenpalA closure.
let mut delivery_fees_amount = 0;
let mut remote_message = VersionedXcm::from(Xcm(Vec::new()));
<PenpalA as TestExt>::execute_with(|| {
type Runtime = <PenpalA as Chain>::Runtime;
type OriginCaller = <PenpalA as Chain>::OriginCaller;
let call = transfer_assets_para_to_para_through_ah_call(test.clone());
let origin = OriginCaller::system(RawOrigin::Signed(sender.clone()));
let result = Runtime::dry_run_call(origin, call, xcm::prelude::XCM_VERSION).unwrap();
// We filter the result to get only the messages we are interested in.
let (destination_to_query, messages_to_query) = &result
.forwarded_xcms
.iter()
.find(|(destination, _)| {
*destination == VersionedLocation::from(Location::new(1, [Teyrchain(1000)]))
})
.unwrap();
assert_eq!(messages_to_query.len(), 1);
remote_message = messages_to_query[0].clone();
let asset_id_for_delivery_fees = VersionedAssetId::from(Location::parent());
let delivery_fees = Runtime::query_delivery_fees(
destination_to_query.clone(),
remote_message.clone(),
asset_id_for_delivery_fees,
)
.unwrap();
delivery_fees_amount = get_amount_from_versioned_assets(delivery_fees);
});
// These are set in the AssetHub closure.
let mut intermediate_execution_fees = 0;
let mut intermediate_delivery_fees_amount = 0;
let mut intermediate_remote_message = VersionedXcm::from(Xcm::<()>(Vec::new()));
<AssetHubZagros as TestExt>::execute_with(|| {
type Runtime = <AssetHubZagros as Chain>::Runtime;
type RuntimeCall = <AssetHubZagros as Chain>::RuntimeCall;
// First we get the execution fees.
let weight = Runtime::query_xcm_weight(remote_message.clone()).unwrap();
intermediate_execution_fees = Runtime::query_weight_to_asset_fee(
weight,
VersionedAssetId::from(AssetId(Location::new(1, []))),
)
.unwrap();
// We have to do this to turn `VersionedXcm<()>` into `VersionedXcm<RuntimeCall>`.
let xcm_program = VersionedXcm::from(Xcm::<RuntimeCall>::from(
remote_message.clone().try_into().unwrap(),
));
// Now we get the delivery fees to the final destination.
let result =
Runtime::dry_run_xcm(sender_as_seen_by_ah.clone().into(), xcm_program).unwrap();
let (destination_to_query, messages_to_query) = &result
.forwarded_xcms
.iter()
.find(|(destination, _)| {
*destination == VersionedLocation::from(Location::new(1, [Teyrchain(2001)]))
})
.unwrap();
// There's actually two messages here.
// One created when the message we sent from PenpalA arrived and was executed.
// The second one when we dry-run the xcm.
// We could've gotten the message from the queue without having to dry-run, but
// offchain applications would have to dry-run, so we do it here as well.
intermediate_remote_message = messages_to_query[0].clone();
let asset_id_for_delivery_fees = VersionedAssetId::from(Location::parent());
let delivery_fees = Runtime::query_delivery_fees(
destination_to_query.clone(),
intermediate_remote_message.clone(),
asset_id_for_delivery_fees,
)
.unwrap();
intermediate_delivery_fees_amount = get_amount_from_versioned_assets(delivery_fees);
});
// Get the final execution fees in the destination.
let mut final_execution_fees = 0;
<PenpalB as TestExt>::execute_with(|| {
type Runtime = <PenpalA as Chain>::Runtime;
let weight = Runtime::query_xcm_weight(intermediate_remote_message.clone()).unwrap();
final_execution_fees =
Runtime::query_weight_to_asset_fee(weight, VersionedAssetId::from(Location::parent()))
.unwrap();
});
// Dry-running is done.
PenpalA::reset_ext();
AssetHubZagros::reset_ext();
PenpalB::reset_ext();
// Fund accounts again.
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner),
relay_native_asset_location.clone(),
sender.clone(),
amount_to_send * 2,
);
AssetHubZagros::fund_accounts(vec![(sov_of_sender_on_ah, amount_to_send * 2)]);
// Actually run the extrinsic.
let sender_assets_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &sender)
});
let receiver_assets_before = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &beneficiary_id)
});
test.set_assertion::<PenpalA>(sender_assertions);
test.set_assertion::<AssetHubZagros>(hop_assertions);
test.set_assertion::<PenpalB>(receiver_assertions);
let call = transfer_assets_para_to_para_through_ah_call(test.clone());
test.set_call(call);
test.assert();
let sender_assets_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &sender)
});
let receiver_assets_after = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &beneficiary_id)
});
// We know the exact fees on every hop.
assert_eq!(
sender_assets_after,
sender_assets_before - amount_to_send - delivery_fees_amount /* This is charged directly
* from the sender's
* account. */
);
assert_eq!(
receiver_assets_after,
receiver_assets_before + amount_to_send -
intermediate_execution_fees -
intermediate_delivery_fees_amount -
final_execution_fees
);
}
#[test]
fn multi_hop_pay_fees_works() {
test_can_estimate_and_pay_exact_fees!(
PenpalA,
AssetHubZagros,
PenpalB,
(Parent, 1_000_000_000_000u128),
Penpal
);
}
/// We are able to estimate delivery fees in USDT for a USDT transfer from PenpalA to PenpalB via
/// Asset Hub. Scenario: Alice on PenpalA has some USDT and wants to send them to PenpalB.
/// We want to estimate the delivery fees in USDT using the new `asset_id` parameter in
/// `query_delivery_fees`.
#[test]
fn usdt_fee_estimation_in_usdt_works() {
use emulated_integration_tests_common::USDT_ID;
let destination = PenpalA::sibling_location_of(PenpalB::para_id());
let sender = PenpalASender::get();
let amount_to_send = 10_000_000; // 10 USDT (6 decimals)
// USDT location from PenpalA's perspective
let usdt_location_on_penpal = PenpalUsdtFromAssetHub::get();
// USDT location from Asset Hub's perspective
let usdt_location_on_ah =
Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())]);
let penpal_as_seen_by_ah = AssetHubZagros::sibling_location_of(PenpalA::para_id());
let sov_of_penpal_on_ah = AssetHubZagros::sovereign_account_id_of(penpal_as_seen_by_ah.clone());
// fund PenpalA's sender account with USDT
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
usdt_location_on_penpal.clone(),
sender.clone(),
amount_to_send * 2,
);
// fund PenpalA's sovereign account on AssetHub with USDT
AssetHubZagros::mint_asset(
<AssetHubZagros as Chain>::RuntimeOrigin::signed(AssetHubZagrosAssetOwner::get()),
USDT_ID,
sov_of_penpal_on_ah.clone(),
amount_to_send * 2,
);
// Create a liquidity pool between ZGR (relay token) and USDT on AssetHub
// This is needed for the asset conversion in fee estimation
create_pool_with_wnd_on!(
AssetHubZagros,
usdt_location_on_ah.clone(),
false,
AssetHubZagrosSender::get(),
1_000_000_000_000, // 1 ZGR
2_000_000 // 2 USDT (1:2 ratio)
);
// Create a liquidity pool between ZGR and USDT on PenpalA as well
// This is needed for PenpalA to perform asset conversion for fee estimation
create_pool_with_wnd_on!(
PenpalA,
usdt_location_on_penpal.clone(),
true,
PenpalAssetOwner::get(),
1_000_000_000_000, // 1 ZGR
2_000_000 // 2 USDT (1:2 ratio)
);
let beneficiary_id = PenpalBReceiver::get();
// We get the delivery fees from the PenpalA closure.
let mut delivery_fees_amount = 0;
let mut remote_message = VersionedXcm::from(Xcm(Vec::new()));
<PenpalA as TestExt>::execute_with(|| {
type Runtime = <PenpalA as Chain>::Runtime;
type OriginCaller = <PenpalA as Chain>::OriginCaller;
let call = usdt_transfer_call(
destination.clone(),
beneficiary_id.clone().into(),
amount_to_send,
usdt_location_on_penpal.clone(),
usdt_location_on_ah.clone(),
);
let asset_hub_location: Location = PenpalA::sibling_location_of(AssetHubZagros::para_id());
let origin = OriginCaller::system(RawOrigin::Signed(sender.clone()));
let result = Runtime::dry_run_call(origin, call, xcm::prelude::XCM_VERSION).unwrap();
// Find the message sent to Asset Hub
let (destination_to_query, messages_to_query) = &result
.forwarded_xcms
.iter()
.find(|(destination, _)| {
*destination == VersionedLocation::from(asset_hub_location.clone())
})
.unwrap();
assert_eq!(messages_to_query.len(), 1);
remote_message = messages_to_query[0].clone();
// Query delivery fees in USDT using the new asset_id parameter
let usdt_asset_id = VersionedAssetId::from(AssetId(usdt_location_on_penpal.clone()));
let delivery_fees = Runtime::query_delivery_fees(
destination_to_query.clone(),
remote_message.clone(),
usdt_asset_id,
)
.unwrap();
delivery_fees_amount = get_amount_from_versioned_assets(delivery_fees.clone());
// Verify the fees are quoted in USDT (the delivery fees should be converted from native to
// USDT)
let fee_assets = match delivery_fees {
VersionedAssets::V5(assets) => assets,
_ => panic!("Expected V5 assets"),
};
// Should have one asset (USDT)
assert_eq!(fee_assets.len(), 1);
let fee_asset = fee_assets.get(0).unwrap();
// Verify it's USDT
assert_eq!(fee_asset.id.0, usdt_location_on_penpal);
// Verify we get a reasonable USDT amount (delivery fees should be > 0)
if let Fungible(amount) = fee_asset.fun {
assert!(amount > 0, "Delivery fees should be greater than 0");
} else {
panic!("Expected fungible delivery fees");
}
});
}
@@ -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,
},
]
);
}
@@ -0,0 +1,60 @@
[package]
name = "collectives-zagros-integration-tests"
version = "1.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
description = "Collectives Zagros runtime integration tests with xcm-emulator"
publish = false
[lints]
workspace = true
[dependencies]
codec = { workspace = true }
# Bizinikiwi
pezframe-support = { workspace = true }
pezpallet-assets = { workspace = true }
pezpallet-balances = { workspace = true }
pezpallet-message-queue = { workspace = true }
pezpallet-treasury = { workspace = true }
pezpallet-utility = { workspace = true }
pezpallet-whitelist = { workspace = true }
pezsp-runtime = { workspace = true }
# Pezkuwi
collectives-zagros-runtime = { workspace = true }
pezpallet-xcm = { workspace = true }
pezkuwi-runtime-common = { workspace = true, default-features = true }
xcm = { workspace = true }
xcm-executor = { workspace = true }
zagros-runtime-constants = { workspace = true, default-features = true }
# Pezcumulus
pezcumulus-pezpallet-teyrchain-system = { workspace = true }
pezcumulus-pezpallet-xcmp-queue = { workspace = true }
emulated-integration-tests-common = { workspace = true }
zagros-system-emulated-network = { workspace = true }
[features]
runtime-benchmarks = [
"collectives-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-assets/runtime-benchmarks",
"pezpallet-balances/runtime-benchmarks",
"pezpallet-message-queue/runtime-benchmarks",
"pezpallet-treasury/runtime-benchmarks",
"pezpallet-utility/runtime-benchmarks",
"pezpallet-whitelist/runtime-benchmarks",
"pezpallet-xcm/runtime-benchmarks",
"pezkuwi-runtime-common/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
"xcm/runtime-benchmarks",
"zagros-runtime-constants/runtime-benchmarks",
"zagros-system-emulated-network/runtime-benchmarks",
]
@@ -0,0 +1,61 @@
// 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 {
pub(crate) use xcm::{latest::ZAGROS_GENESIS_HASH, prelude::*};
pub(crate) use pezframe_support::assert_ok;
pub(crate) use emulated_integration_tests_common::{
accounts::ALICE,
test_teyrchain_is_trusted_teleporter,
xcm_emulator::{assert_expected_events, bx, Chain, TestExt, Teyrchain},
};
pub(crate) use zagros_system_emulated_network::{
asset_hub_zagros_emulated_chain::{
asset_hub_zagros_runtime::xcm_config::LocationToAccountId as AssetHubLocationToAccountId,
genesis::ED as ASSET_HUB_ZAGROS_ED, AssetHubZagrosParaPallet as AssetHubZagrosPallet,
},
bridge_hub_zagros_emulated_chain::BridgeHubZagrosParaPallet as BridgeHubZagrosPallet,
collectives_zagros_emulated_chain::{
collectives_zagros_runtime::{
fellowship as collectives_fellowship,
xcm_config::XcmConfig as CollectivesZagrosXcmConfig,
},
genesis::ED as COLLECTIVES_ZAGROS_ED,
CollectivesZagrosParaPallet as CollectivesZagrosPallet,
},
coretime_zagros_emulated_chain::CoretimeZagrosParaPallet as CoretimeZagrosPallet,
penpal_emulated_chain::{PenpalAssetOwner, PenpalBParaPallet as PenpalBPallet},
people_zagros_emulated_chain::PeopleZagrosParaPallet as PeopleZagrosPallet,
zagros_emulated_chain::{
genesis::ED as ZAGROS_ED,
zagros_runtime::{governance as zagros_governance, OriginCaller as ZagrosOriginCaller},
ZagrosRelayPallet as ZagrosPallet,
},
AssetHubZagrosPara as AssetHubZagros, AssetHubZagrosParaReceiver as AssetHubZagrosReceiver,
AssetHubZagrosParaSender as AssetHubZagrosSender, BridgeHubZagrosPara as BridgeHubZagros,
CollectivesZagrosPara as CollectivesZagros,
CollectivesZagrosParaReceiver as CollectivesZagrosReceiver,
CollectivesZagrosParaSender as CollectivesZagrosSender,
CoretimeZagrosPara as CoretimeZagros, PenpalBPara as PenpalB,
PeopleZagrosPara as PeopleZagros, ZagrosRelay as Zagros,
ZagrosRelayReceiver as ZagrosReceiver, ZagrosRelaySender as ZagrosSender,
};
}
#[cfg(test)]
mod tests;
@@ -0,0 +1,275 @@
// 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 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,
);
// Aliasing same account on different chains
test_cross_chain_alias!(
vec![
// between AH and Collectives: allowed
(AssetHubZagros, CollectivesZagros, TELEPORT_FEES, ALLOWED),
// between BH and Collectives: allowed
(BridgeHubZagros, CollectivesZagros, TELEPORT_FEES, ALLOWED),
// between Coretime and Collectives: allowed
(CoretimeZagros, CollectivesZagros, TELEPORT_FEES, ALLOWED),
// between People and Collectives: allowed
(PeopleZagros, CollectivesZagros, TELEPORT_FEES, ALLOWED),
// between Penpal and Collectives: denied
(PenpalB, CollectivesZagros, 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 Collectives: denied
(AssetHubZagros, CollectivesZagros, TELEPORT_FEES, DENIED),
// between BH and Collectives: denied
(BridgeHubZagros, CollectivesZagros, TELEPORT_FEES, DENIED),
// between Coretime and Collectives: denied
(CoretimeZagros, CollectivesZagros, TELEPORT_FEES, DENIED),
// between People and Collectives: denied
(PeopleZagros, CollectivesZagros, TELEPORT_FEES, DENIED),
// between Penpal and Collectives: denied
(PenpalB, CollectivesZagros, RESERVE_TRANSFER_FEES, DENIED)
],
origin,
target,
fees
);
}
#[test]
fn aliasing_child_locations() {
use CollectivesZagrosXcmConfig as XcmConfig;
CollectivesZagros::execute_with(|| {
// 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() {
use CollectivesZagrosXcmConfig as XcmConfig;
CollectivesZagros::execute_with(|| {
// Allows AH root to alias anything.
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);
CollectivesZagros::fund_accounts(vec![(target.clone(), fees * 10)]);
// let's authorize `origin` on Penpal to alias `target` on Collectives
CollectivesZagros::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!(
<CollectivesZagros as CollectivesZagrosPallet>::PezkuwiXcm::add_authorized_alias(
<CollectivesZagros 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 Collectives: denied
(AssetHubZagros, CollectivesZagros, TELEPORT_FEES, DENIED),
// between BH and Collectives: denied
(BridgeHubZagros, CollectivesZagros, TELEPORT_FEES, DENIED),
// between People and Collectives: denied
(PeopleZagros, CollectivesZagros, TELEPORT_FEES, DENIED),
// between Penpal and Collectives: denied
(PenpalB, CollectivesZagros, 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 Collectives: denied
(AssetHubZagros, CollectivesZagros, TELEPORT_FEES, DENIED),
// between BH and Collectives: denied
(BridgeHubZagros, CollectivesZagros, TELEPORT_FEES, DENIED),
// between People and Collectives: denied
(PeopleZagros, CollectivesZagros, TELEPORT_FEES, DENIED),
// between Penpal and Collectives: allowed
(PenpalB, CollectivesZagros, RESERVE_TRANSFER_FEES, ALLOWED)
],
origin,
target,
fees
);
// remove authorization for `origin` on Penpal to alias `target` on Collectives
CollectivesZagros::execute_with(|| {
// `target` removes all authorized aliases
assert_ok!(
<CollectivesZagros as CollectivesZagrosPallet>::PezkuwiXcm::remove_all_authorized_aliases(
<CollectivesZagros as Chain>::RuntimeOrigin::signed(target.clone())
)
);
});
// Verify `penpal::origin` can no longer alias into `target` on Collectives.
test_cross_chain_alias!(
vec![(PenpalB, CollectivesZagros, RESERVE_TRANSFER_FEES, DENIED)],
origin,
target,
fees
);
}
@@ -0,0 +1,111 @@
// 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 collectives_zagros_runtime::{
fellowship::FellowshipSalaryPaymaster, secretary::SecretarySalaryPaymaster,
};
use pezframe_support::{
assert_ok,
traits::{fungibles::Mutate, tokens::Pay},
};
use xcm_executor::traits::ConvertLocation;
const FELLOWSHIP_SALARY_PALLET_ID: u8 = 64;
const SECRETARY_SALARY_PALLET_ID: u8 = 91;
#[test]
fn pay_salary_technical_fellowship() {
let asset_id: u32 = 1984;
let fellowship_salary = (
Parent,
Teyrchain(CollectivesZagros::para_id().into()),
PalletInstance(FELLOWSHIP_SALARY_PALLET_ID),
);
let pay_from =
AssetHubLocationToAccountId::convert_location(&fellowship_salary.into()).unwrap();
let pay_to = Zagros::account_id_of(ALICE);
let pay_amount = 9_000_000_000;
AssetHubZagros::execute_with(|| {
type AssetHubAssets = <AssetHubZagros as AssetHubZagrosPallet>::Assets;
assert_ok!(<AssetHubAssets as Mutate<_>>::mint_into(asset_id, &pay_from, pay_amount * 2));
});
CollectivesZagros::execute_with(|| {
type RuntimeEvent = <CollectivesZagros as Chain>::RuntimeEvent;
assert_ok!(FellowshipSalaryPaymaster::pay(&pay_to, (), pay_amount));
assert_expected_events!(
CollectivesZagros,
vec![
RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},
]
);
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::Assets(pezpallet_assets::Event::Transferred { .. }) => {},
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed { success: true ,.. }) => {},
]
);
});
}
#[test]
fn pay_salary_secretary() {
const USDT_ID: u32 = 1984;
let secretary_salary = (
Parent,
Teyrchain(CollectivesZagros::para_id().into()),
PalletInstance(SECRETARY_SALARY_PALLET_ID),
);
let pay_from = AssetHubLocationToAccountId::convert_location(&secretary_salary.into()).unwrap();
let pay_to = Zagros::account_id_of(ALICE);
let pay_amount = 9_000_000_000;
AssetHubZagros::execute_with(|| {
type AssetHubAssets = <AssetHubZagros as AssetHubZagrosPallet>::Assets;
// USDT registered in genesis, now mint some into the payer's account
assert_ok!(<AssetHubAssets as Mutate<_>>::mint_into(USDT_ID, &pay_from, pay_amount * 2));
});
CollectivesZagros::execute_with(|| {
type RuntimeEvent = <CollectivesZagros as Chain>::RuntimeEvent;
assert_ok!(SecretarySalaryPaymaster::pay(&pay_to, (), pay_amount));
assert_expected_events!(
CollectivesZagros,
vec![
RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},
]
);
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::Assets(pezpallet_assets::Event::Transferred { .. }) => {},
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed { success: true ,.. }) => {},
]
);
});
}
@@ -0,0 +1,72 @@
// 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 codec::Encode;
use collectives_fellowship::pezpallet_fellowship_origins::Origin::Fellows as FellowsOrigin;
use pezframe_support::{assert_ok, pezsp_runtime::traits::Dispatchable};
#[test]
fn fellows_whitelist_call() {
CollectivesZagros::execute_with(|| {
type RuntimeEvent = <CollectivesZagros as Chain>::RuntimeEvent;
type RuntimeCall = <CollectivesZagros as Chain>::RuntimeCall;
type RuntimeOrigin = <CollectivesZagros as Chain>::RuntimeOrigin;
type Runtime = <CollectivesZagros as Chain>::Runtime;
type ZagrosCall = <Zagros as Chain>::RuntimeCall;
type ZagrosRuntime = <Zagros as Chain>::Runtime;
let call_hash = [1u8; 32].into();
let whitelist_call = RuntimeCall::PezkuwiXcm(pezpallet_xcm::Call::<Runtime>::send {
dest: bx!(VersionedLocation::from(Location::parent())),
message: bx!(VersionedXcm::from(Xcm(vec![
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
Transact {
origin_kind: OriginKind::Xcm,
call: ZagrosCall::Whitelist(
pezpallet_whitelist::Call::<ZagrosRuntime>::whitelist_call { call_hash }
)
.encode()
.into(),
fallback_max_weight: None
}
]))),
});
let fellows_origin: RuntimeOrigin = FellowsOrigin.into();
assert_ok!(whitelist_call.dispatch(fellows_origin));
assert_expected_events!(
CollectivesZagros,
vec![
RuntimeEvent::PezkuwiXcm(pezpallet_xcm::Event::Sent { .. }) => {},
]
);
});
Zagros::execute_with(|| {
type RuntimeEvent = <Zagros as Chain>::RuntimeEvent;
assert_expected_events!(
Zagros,
vec![
RuntimeEvent::Whitelist(pezpallet_whitelist::Event::CallWhitelisted { .. }) => {},
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed { success: true, .. }) => {},
]
);
});
}
@@ -0,0 +1,242 @@
// 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 pezframe_support::{
assert_ok, dispatch::RawOrigin, instances::Instance1, pezsp_runtime::traits::Dispatchable,
traits::fungible::Inspect,
};
use pezkuwi_runtime_common::impls::VersionedLocatableAsset;
use xcm_executor::traits::ConvertLocation;
use zagros_runtime_constants::currency::UNITS;
use zagros_system_emulated_network::zagros_emulated_chain::zagros_runtime::Dmp;
// Fund Fellowship Treasury from Zagros Treasury and spend from Fellowship Treasury.
#[test]
fn fellowship_treasury_spend() {
// initial treasury balance on Asset Hub in WNDs.
let treasury_balance = 20_000_000 * UNITS;
// target fellowship balance on Asset Hub in WNDs.
let fellowship_treasury_balance = 1_000_000 * UNITS;
// fellowship first spend balance in WNDs.
let fellowship_spend_balance = 10_000 * UNITS;
let init_alice_balance = AssetHubZagros::execute_with(|| {
<<AssetHubZagros as AssetHubZagrosPallet>::Balances as Inspect<_>>::balance(
&AssetHubZagros::account_id_of(ALICE),
)
});
let check_account = AssetHubZagros::execute_with(|| {
<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::check_account()
});
// prefund Asset Hub checking account so we accept teleport from relay
AssetHubZagros::fund_accounts(vec![(check_account, treasury_balance)]);
Zagros::execute_with(|| {
type RuntimeEvent = <Zagros as Chain>::RuntimeEvent;
type RuntimeCall = <Zagros as Chain>::RuntimeCall;
type Runtime = <Zagros as Chain>::Runtime;
type Balances = <Zagros as ZagrosPallet>::Balances;
type Treasury = <Zagros as ZagrosPallet>::Treasury;
// Fund Treasury account on Asset Hub with WNDs.
let root = <Zagros as Chain>::RuntimeOrigin::root();
let treasury_account = Treasury::account_id();
// Mist assets to Treasury account on Relay Chain.
assert_ok!(Balances::force_set_balance(
root.clone(),
treasury_account.clone().into(),
treasury_balance * 2,
));
Dmp::make_teyrchain_reachable(1000);
let native_asset = Location::here();
let asset_hub_location: Location = [Teyrchain(1000)].into();
let treasury_location: Location = (Parent, PalletInstance(37)).into();
let teleport_call = RuntimeCall::Utility(pezpallet_utility::Call::<Runtime>::dispatch_as {
as_origin: bx!(ZagrosOriginCaller::system(RawOrigin::Signed(treasury_account))),
call: bx!(RuntimeCall::XcmPallet(pezpallet_xcm::Call::<Runtime>::teleport_assets {
dest: bx!(VersionedLocation::from(asset_hub_location.clone())),
beneficiary: bx!(VersionedLocation::from(treasury_location)),
assets: bx!(VersionedAssets::from(Assets::from(Asset {
id: native_asset.clone().into(),
fun: treasury_balance.into()
}))),
fee_asset_id: bx!(native_asset.into()),
})),
});
// Dispatched from Root to `dispatch_as` `Signed(treasury_account)`.
assert_ok!(teleport_call.dispatch(root));
assert_expected_events!(
Zagros,
vec![
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::Sent { .. }) => {},
]
);
});
Zagros::execute_with(|| {
type RuntimeEvent = <Zagros as Chain>::RuntimeEvent;
type RuntimeCall = <Zagros as Chain>::RuntimeCall;
type RuntimeOrigin = <Zagros as Chain>::RuntimeOrigin;
type Runtime = <Zagros as Chain>::Runtime;
type Treasury = <Zagros as ZagrosPallet>::Treasury;
// Fund Fellowship Treasury from Zagros Treasury.
let treasury_origin: RuntimeOrigin =
zagros_governance::pezpallet_custom_origins::Origin::Treasurer.into();
let fellowship_treasury_location: Location =
Location::new(1, [Teyrchain(1001), PalletInstance(65)]);
let asset_hub_location: Location = [Teyrchain(1000)].into();
let native_asset = Location::parent();
let treasury_spend_call = RuntimeCall::Treasury(pezpallet_treasury::Call::<Runtime>::spend {
asset_kind: bx!(VersionedLocatableAsset::from((
asset_hub_location.clone(),
native_asset.into()
))),
amount: fellowship_treasury_balance,
beneficiary: bx!(VersionedLocation::from(fellowship_treasury_location)),
valid_from: None,
});
assert_ok!(treasury_spend_call.dispatch(treasury_origin));
// Claim the spend.
let alice_signed = RuntimeOrigin::signed(Zagros::account_id_of(ALICE));
assert_ok!(Treasury::payout(alice_signed.clone(), 0));
assert_expected_events!(
Zagros,
vec![
RuntimeEvent::Treasury(pezpallet_treasury::Event::AssetSpendApproved { .. }) => {},
RuntimeEvent::Treasury(pezpallet_treasury::Event::Paid { .. }) => {},
]
);
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
type Balances = <AssetHubZagros as AssetHubZagrosPallet>::Balances;
// Ensure that the funds deposited to the Fellowship Treasury account.
let fellowship_treasury_location: Location =
Location::new(1, [Teyrchain(1001), PalletInstance(65)]);
let fellowship_treasury_account =
AssetHubLocationToAccountId::convert_location(&fellowship_treasury_location).unwrap();
assert_eq!(
<Balances as Inspect<_>>::balance(&fellowship_treasury_account),
fellowship_treasury_balance
);
// Assert events triggered by xcm pay program:
// 1. treasury asset transferred to spend beneficiary;
// 2. response to Relay Chain Treasury pallet instance sent back;
// 3. XCM program completed;
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::Balances(pezpallet_balances::Event::Transfer { .. }) => {},
RuntimeEvent::TeyrchainSystem(cumulus_pallet_teyrchain_system::Event::UpwardMessageSent { .. }) => {},
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed { success: true ,.. }) => {},
]
);
});
CollectivesZagros::execute_with(|| {
type RuntimeEvent = <CollectivesZagros as Chain>::RuntimeEvent;
type RuntimeCall = <CollectivesZagros as Chain>::RuntimeCall;
type RuntimeOrigin = <CollectivesZagros as Chain>::RuntimeOrigin;
type Runtime = <CollectivesZagros as Chain>::Runtime;
type FellowshipTreasury =
<CollectivesZagros as CollectivesZagrosPallet>::FellowshipTreasury;
// Fund Alice account from Fellowship Treasury.
let fellows_origin: RuntimeOrigin =
collectives_fellowship::pezpallet_fellowship_origins::Origin::Fellows.into();
let asset_hub_location: Location = (Parent, Teyrchain(1000)).into();
let native_asset = Location::parent();
let alice_location: Location = [Junction::AccountId32 {
network: None,
id: CollectivesZagros::account_id_of(ALICE).into(),
}]
.into();
let fellowship_treasury_spend_call =
RuntimeCall::FellowshipTreasury(pezpallet_treasury::Call::<Runtime, Instance1>::spend {
asset_kind: bx!(VersionedLocatableAsset::from((
asset_hub_location,
native_asset.into()
))),
amount: fellowship_spend_balance,
beneficiary: bx!(VersionedLocation::from(alice_location)),
valid_from: None,
});
assert_ok!(fellowship_treasury_spend_call.dispatch(fellows_origin));
// Claim the spend.
let alice_signed = RuntimeOrigin::signed(CollectivesZagros::account_id_of(ALICE));
assert_ok!(FellowshipTreasury::payout(alice_signed.clone(), 0));
assert_expected_events!(
CollectivesZagros,
vec![
RuntimeEvent::FellowshipTreasury(pezpallet_treasury::Event::AssetSpendApproved { .. }) => {},
RuntimeEvent::FellowshipTreasury(pezpallet_treasury::Event::Paid { .. }) => {},
]
);
});
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
type Balances = <AssetHubZagros as AssetHubZagrosPallet>::Balances;
// Ensure that the funds deposited to Alice account.
let alice_account = AssetHubZagros::account_id_of(ALICE);
assert_eq!(
<Balances as Inspect<_>>::balance(&alice_account),
fellowship_spend_balance + init_alice_balance
);
// Assert events triggered by xcm pay program:
// 1. treasury asset transferred to spend beneficiary;
// 2. response to Relay Chain Treasury pallet instance sent back;
// 3. XCM program completed;
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::Balances(pezpallet_balances::Event::Transfer { .. }) => {},
RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed { success: true ,.. }) => {},
]
);
});
}
@@ -0,0 +1,20 @@
// 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.
mod aliases;
mod collectives_salary;
mod fellowship;
mod fellowship_treasury;
mod teleport;
@@ -0,0 +1,117 @@
// 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::{
test_relay_is_trusted_teleporter, test_teyrchain_is_trusted_teleporter_for_relay,
};
#[test]
fn teleport_via_limited_teleport_assets_from_and_to_relay() {
let amount = ZAGROS_ED * 10;
test_relay_is_trusted_teleporter!(
Zagros, // Origin
vec![CollectivesZagros], // Destinations
amount,
limited_teleport_assets
);
test_teyrchain_is_trusted_teleporter_for_relay!(
CollectivesZagros, // Origin
Zagros, // Destination
amount,
limited_teleport_assets
);
}
#[test]
fn teleport_via_transfer_assets_from_and_to_relay() {
let amount = ZAGROS_ED * 10;
test_relay_is_trusted_teleporter!(
Zagros, // Origin
vec![CollectivesZagros], // Destinations
amount,
transfer_assets
);
test_teyrchain_is_trusted_teleporter_for_relay!(
CollectivesZagros, // Origin
Zagros, // Destination
amount,
transfer_assets
);
}
#[test]
fn teleport_via_limited_teleport_assets_from_collectives_to_asset_hub() {
let amount = ASSET_HUB_ZAGROS_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
CollectivesZagros, // Origin
vec![AssetHubZagros], // Destinations
(native_asset, amount),
fee_asset_id,
limited_teleport_assets
);
}
#[test]
fn teleport_via_transfer_assets_from_collectives_to_asset_hub() {
let amount = ASSET_HUB_ZAGROS_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
CollectivesZagros, // Origin
vec![AssetHubZagros], // Destinations
(native_asset, amount),
fee_asset_id,
transfer_assets
);
}
#[test]
fn teleport_via_limited_teleport_assets_from_asset_hub_to_collectives() {
let amount = COLLECTIVES_ZAGROS_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
AssetHubZagros, // Origin
vec![CollectivesZagros], // Destinations
(native_asset, amount),
fee_asset_id,
limited_teleport_assets
);
}
#[test]
fn teleport_via_transfer_assets_from_asset_hub_to_collectives() {
let amount = COLLECTIVES_ZAGROS_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
AssetHubZagros, // Origin
vec![CollectivesZagros], // Destinations
(native_asset, amount),
fee_asset_id,
transfer_assets
);
}
@@ -0,0 +1,42 @@
[package]
name = "coretime-pezkuwichain-integration-tests"
version = "0.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
description = "Coretime Pezkuwichain runtime integration tests with xcm-emulator"
publish = false
[lints]
workspace = true
[dependencies]
# Bizinikiwi
pezframe-support = { workspace = true }
pezpallet-broker = { workspace = true, default-features = true }
pezpallet-message-queue = { workspace = true }
pezsp-runtime = { workspace = true }
# Pezkuwi
pezkuwi-runtime-teyrchains = { workspace = true, default-features = true }
pezkuwichain-runtime-constants = { workspace = true, default-features = true }
xcm = { workspace = true }
# Pezcumulus
pezcumulus-pezpallet-teyrchain-system = { workspace = true, default-features = true }
emulated-integration-tests-common = { workspace = true }
pezkuwichain-system-emulated-network = { workspace = true }
[features]
runtime-benchmarks = [
"pezcumulus-pezpallet-teyrchain-system/runtime-benchmarks",
"emulated-integration-tests-common/runtime-benchmarks",
"pezframe-support/runtime-benchmarks",
"pezpallet-broker/runtime-benchmarks",
"pezpallet-message-queue/runtime-benchmarks",
"pezkuwi-runtime-teyrchains/runtime-benchmarks",
"pezkuwichain-runtime-constants/runtime-benchmarks",
"pezkuwichain-system-emulated-network/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"xcm/runtime-benchmarks",
]
@@ -0,0 +1,51 @@
// 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 pezframe_support::assert_ok;
// Pezkuwi
pub(crate) use xcm::{latest::PEZKUWICHAIN_GENESIS_HASH, prelude::*};
// Pezcumulus
pub(crate) use emulated_integration_tests_common::xcm_emulator::{
assert_expected_events, Chain, TestExt, Teyrchain,
};
pub(crate) use pezkuwichain_system_emulated_network::{
asset_hub_pezkuwichain_emulated_chain::genesis::ED as ASSET_HUB_PEZKUWICHAIN_ED,
coretime_pezkuwichain_emulated_chain::{
coretime_pezkuwichain_runtime::ExistentialDeposit as CoretimePezkuwichainExistentialDeposit,
genesis::ED as CORETIME_PEZKUWICHAIN_ED,
CoretimePezkuwichainParaPallet as CoretimePezkuwichainPallet,
},
pezkuwichain_emulated_chain::{
genesis::ED as PEZKUWICHAIN_ED, PezkuwichainRelayPallet as PezkuwichainPallet,
},
AssetHubPezkuwichainPara as AssetHubPezkuwichain,
AssetHubPezkuwichainParaReceiver as AssetHubPezkuwichainReceiver,
AssetHubPezkuwichainParaSender as AssetHubPezkuwichainSender,
CoretimePezkuwichainPara as CoretimePezkuwichain,
CoretimePezkuwichainParaReceiver as CoretimePezkuwichainReceiver,
CoretimePezkuwichainParaSender as CoretimePezkuwichainSender,
PezkuwichainRelay as Pezkuwichain, PezkuwichainRelayReceiver as PezkuwichainReceiver,
PezkuwichainRelaySender as PezkuwichainSender,
};
}
#[cfg(test)]
mod tests;
@@ -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 = CoretimePezkuwichainExistentialDeposit::get();
let assets: Assets = (Parent, amount).into();
test_chain_can_claim_assets!(
CoretimePezkuwichain,
RuntimeCall,
NetworkId::ByGenesis(PEZKUWICHAIN_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 pezframe_support::traits::OnInitialize;
use pezpallet_broker::{ConfigRecord, Configuration, CoreAssignment, CoreMask, ScheduleItem};
use pezkuwichain_runtime_constants::system_teyrchain::coretime::TIMESLICE_PERIOD;
use pezkuwichain_system_emulated_network::pezkuwichain_emulated_chain::pezkuwichain_runtime::Dmp;
use pezsp_runtime::Perbill;
#[test]
fn transact_hardcoded_weights_are_sane() {
// There are three transacts with hardcoded weights sent from the Coretime Chain to the Relay
// Chain across the CoretimeInterface which are triggered at various points in the sales cycle.
// - Request core count - triggered directly by `start_sales` or `request_core_count`
// extrinsics.
// - Request revenue info - triggered when each timeslice is committed.
// - Assign core - triggered when an entry is encountered in the workplan for the next
// timeslice.
// RuntimeEvent aliases to avoid warning from usage of qualified paths in assertions due to
// <https://github.com/rust-lang/rust/issues/86935>
type CoretimeEvent = <CoretimePezkuwichain as Chain>::RuntimeEvent;
type RelayEvent = <Pezkuwichain as Chain>::RuntimeEvent;
Pezkuwichain::execute_with(|| {
Dmp::make_teyrchain_reachable(CoretimePezkuwichain::para_id());
});
// Reserve a workload, configure broker and start sales.
CoretimePezkuwichain::execute_with(|| {
// Hooks don't run in emulated tests - workaround as we need `on_initialize` to tick things
// along and have no concept of time passing otherwise.
<CoretimePezkuwichain as CoretimePezkuwichainPallet>::Broker::on_initialize(
<CoretimePezkuwichain as Chain>::System::block_number(),
);
let coretime_root_origin = <CoretimePezkuwichain as Chain>::RuntimeOrigin::root();
// Create and populate schedule with the worst case assignment on this core.
let mut schedule = Vec::new();
for i in 0..80 {
schedule.push(ScheduleItem {
mask: CoreMask::void().set(i),
assignment: CoreAssignment::Task(2000 + i),
})
}
assert_ok!(<CoretimePezkuwichain as CoretimePezkuwichainPallet>::Broker::reserve(
coretime_root_origin.clone(),
schedule.try_into().expect("Vector is within bounds."),
));
// Configure broker and start sales.
let config = ConfigRecord {
advance_notice: 1,
interlude_length: 1,
leadin_length: 2,
region_length: 1,
ideal_bulk_proportion: Perbill::from_percent(40),
limit_cores_offered: None,
renewal_bump: Perbill::from_percent(2),
contribution_timeout: 1,
};
assert_ok!(<CoretimePezkuwichain as CoretimePezkuwichainPallet>::Broker::configure(
coretime_root_origin.clone(),
config
));
assert_ok!(<CoretimePezkuwichain as CoretimePezkuwichainPallet>::Broker::start_sales(
coretime_root_origin,
100,
0
));
assert_eq!(
pezpallet_broker::Status::<<CoretimePezkuwichain as Chain>::Runtime>::get()
.unwrap()
.core_count,
1
);
assert_expected_events!(
CoretimePezkuwichain,
vec![
CoretimeEvent::Broker(
pezpallet_broker::Event::ReservationMade { .. }
) => {},
CoretimeEvent::Broker(
pezpallet_broker::Event::CoreCountRequested { core_count: 1 }
) => {},
CoretimeEvent::TeyrchainSystem(
cumulus_pallet_teyrchain_system::Event::UpwardMessageSent { .. }
) => {},
]
);
});
// Check that the request_core_count message was processed successfully. This will fail if the
// weights are misconfigured.
Pezkuwichain::execute_with(|| {
Pezkuwichain::assert_ump_queue_processed(true, Some(CoretimePezkuwichain::para_id()), None);
assert_expected_events!(
Pezkuwichain,
vec![
RelayEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
});
// Keep track of the relay chain block number so we can fast forward while still checking the
// right block.
let mut block_number_cursor =
Pezkuwichain::ext_wrapper(<Pezkuwichain as Chain>::System::block_number);
let config = CoretimePezkuwichain::ext_wrapper(|| {
Configuration::<<CoretimePezkuwichain as Chain>::Runtime>::get()
.expect("Pallet was configured earlier.")
});
// Now run up to the block before the sale is rotated.
while block_number_cursor < TIMESLICE_PERIOD - config.advance_notice - 1 {
CoretimePezkuwichain::execute_with(|| {
// Hooks don't run in emulated tests - workaround.
<CoretimePezkuwichain as CoretimePezkuwichainPallet>::Broker::on_initialize(
<CoretimePezkuwichain as Chain>::System::block_number(),
);
});
Pezkuwichain::ext_wrapper(|| {
block_number_cursor = <Pezkuwichain as Chain>::System::block_number();
});
}
// In this block we trigger assign core.
CoretimePezkuwichain::execute_with(|| {
// Hooks don't run in emulated tests - workaround.
<CoretimePezkuwichain as CoretimePezkuwichainPallet>::Broker::on_initialize(
<CoretimePezkuwichain as Chain>::System::block_number(),
);
assert_expected_events!(
CoretimePezkuwichain,
vec![
CoretimeEvent::Broker(
pezpallet_broker::Event::SaleInitialized { .. }
) => {},
CoretimeEvent::Broker(
pezpallet_broker::Event::CoreAssigned { .. }
) => {},
CoretimeEvent::TeyrchainSystem(
cumulus_pallet_teyrchain_system::Event::UpwardMessageSent { .. }
) => {},
]
);
});
// Check that the assign_core message was processed successfully.
// This will fail if the weights are misconfigured.
Pezkuwichain::execute_with(|| {
Pezkuwichain::assert_ump_queue_processed(true, Some(CoretimePezkuwichain::para_id()), None);
assert_expected_events!(
Pezkuwichain,
vec![
RelayEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
RelayEvent::Coretime(
pezkuwi_runtime_teyrchains::coretime::Event::CoreAssigned { .. }
) => {},
]
);
});
// In this block we trigger request revenue.
CoretimePezkuwichain::execute_with(|| {
// Hooks don't run in emulated tests - workaround.
<CoretimePezkuwichain as CoretimePezkuwichainPallet>::Broker::on_initialize(
<CoretimePezkuwichain as Chain>::System::block_number(),
);
assert_expected_events!(
CoretimePezkuwichain,
vec![
CoretimeEvent::TeyrchainSystem(
cumulus_pallet_teyrchain_system::Event::UpwardMessageSent { .. }
) => {},
]
);
});
// Check that the request_revenue_info_at message was processed successfully.
// This will fail if the weights are misconfigured.
Pezkuwichain::execute_with(|| {
Pezkuwichain::assert_ump_queue_processed(true, Some(CoretimePezkuwichain::para_id()), None);
assert_expected_events!(
Pezkuwichain,
vec![
RelayEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
});
// Here we receive and process the notify_revenue XCM with zero revenue.
CoretimePezkuwichain::execute_with(|| {
// Hooks don't run in emulated tests - workaround.
<CoretimePezkuwichain as CoretimePezkuwichainPallet>::Broker::on_initialize(
<CoretimePezkuwichain as Chain>::System::block_number(),
);
assert_expected_events!(
CoretimePezkuwichain,
vec![
CoretimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
// Zero revenue in first timeslice so history is immediately dropped.
CoretimeEvent::Broker(
pezpallet_broker::Event::HistoryDropped { when: 0, revenue: 0 }
) => {},
]
);
});
}
@@ -0,0 +1,18 @@
// 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.
mod claim_assets;
mod coretime_interface;
mod teleport;
@@ -0,0 +1,118 @@
// 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::{
test_relay_is_trusted_teleporter, test_teyrchain_is_trusted_teleporter,
test_teyrchain_is_trusted_teleporter_for_relay,
};
#[test]
fn teleport_via_limited_teleport_assets_from_and_to_relay() {
let amount = PEZKUWICHAIN_ED * 10;
test_relay_is_trusted_teleporter!(
Pezkuwichain, // Origin
vec![CoretimePezkuwichain], // Destinations
amount,
limited_teleport_assets
);
test_teyrchain_is_trusted_teleporter_for_relay!(
CoretimePezkuwichain, // Origin
Pezkuwichain, // Destination
amount,
limited_teleport_assets
);
}
#[test]
fn teleport_via_transfer_assets_from_and_to_relay() {
let amount = PEZKUWICHAIN_ED * 10;
test_relay_is_trusted_teleporter!(
Pezkuwichain, // Origin
vec![CoretimePezkuwichain], // Destinations
amount,
transfer_assets
);
test_teyrchain_is_trusted_teleporter_for_relay!(
CoretimePezkuwichain, // Origin
Pezkuwichain, // Destination
amount,
transfer_assets
);
}
#[test]
fn teleport_via_limited_teleport_assets_from_coretime_to_asset_hub() {
let amount = ASSET_HUB_PEZKUWICHAIN_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
CoretimePezkuwichain, // Origin
vec![AssetHubPezkuwichain], // Destinations
(native_asset, amount),
fee_asset_id,
limited_teleport_assets
);
}
#[test]
fn teleport_via_transfer_assets_from_coretime_to_asset_hub() {
let amount = ASSET_HUB_PEZKUWICHAIN_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
CoretimePezkuwichain, // Origin
vec![AssetHubPezkuwichain], // Destinations
(native_asset, amount),
fee_asset_id,
transfer_assets
);
}
#[test]
fn teleport_via_limited_teleport_assets_from_asset_hub_to_coretime() {
let amount = CORETIME_PEZKUWICHAIN_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
AssetHubPezkuwichain, // Origin
vec![CoretimePezkuwichain], // Destinations
(native_asset, amount),
fee_asset_id,
limited_teleport_assets
);
}
#[test]
fn teleport_via_transfer_assets_from_asset_hub_to_coretime() {
let amount = CORETIME_PEZKUWICHAIN_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
AssetHubPezkuwichain, // Origin
vec![CoretimePezkuwichain], // Destinations
(native_asset, amount),
fee_asset_id,
transfer_assets
);
}
@@ -0,0 +1,44 @@
[package]
name = "coretime-zagros-integration-tests"
version = "0.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
description = "Coretime Zagros runtime integration tests with xcm-emulator"
publish = false
[lints]
workspace = true
[dependencies]
# Bizinikiwi
pezframe-support = { workspace = true }
pezpallet-broker = { workspace = true, default-features = true }
pezpallet-message-queue = { workspace = true }
pezsp-runtime = { workspace = true }
# Pezkuwi
pezkuwi-runtime-teyrchains = { workspace = true, default-features = true }
xcm = { workspace = true }
xcm-executor = { workspace = true }
zagros-runtime-constants = { workspace = true, default-features = true }
# Pezcumulus
pezcumulus-pezpallet-teyrchain-system = { workspace = true, default-features = true }
emulated-integration-tests-common = { workspace = true }
zagros-system-emulated-network = { workspace = true }
[features]
runtime-benchmarks = [
"pezcumulus-pezpallet-teyrchain-system/runtime-benchmarks",
"emulated-integration-tests-common/runtime-benchmarks",
"pezframe-support/runtime-benchmarks",
"pezpallet-broker/runtime-benchmarks",
"pezpallet-message-queue/runtime-benchmarks",
"pezkuwi-runtime-teyrchains/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
"xcm/runtime-benchmarks",
"zagros-runtime-constants/runtime-benchmarks",
"zagros-system-emulated-network/runtime-benchmarks",
]
@@ -0,0 +1,53 @@
// 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 pezframe_support::assert_ok;
// Pezkuwi
pub(crate) use xcm::{latest::ZAGROS_GENESIS_HASH, prelude::*};
// Pezcumulus
pub(crate) use emulated_integration_tests_common::xcm_emulator::{
assert_expected_events, Chain, TestExt, Teyrchain,
};
pub(crate) use zagros_system_emulated_network::{
asset_hub_zagros_emulated_chain::{
genesis::ED as ASSET_HUB_ZAGROS_ED, AssetHubZagrosParaPallet as AssetHubZagrosPallet,
},
bridge_hub_zagros_emulated_chain::BridgeHubZagrosParaPallet as BridgeHubZagrosPallet,
collectives_zagros_emulated_chain::CollectivesZagrosParaPallet as CollectivesZagrosPallet,
coretime_zagros_emulated_chain::{
self, coretime_zagros_runtime::ExistentialDeposit as CoretimeZagrosExistentialDeposit,
genesis::ED as CORETIME_ZAGROS_ED, CoretimeZagrosParaPallet as CoretimeZagrosPallet,
},
penpal_emulated_chain::{PenpalAssetOwner, PenpalBParaPallet as PenpalBPallet},
people_zagros_emulated_chain::PeopleZagrosParaPallet as PeopleZagrosPallet,
zagros_emulated_chain::{genesis::ED as ZAGROS_ED, ZagrosRelayPallet as ZagrosPallet},
AssetHubZagrosPara as AssetHubZagros, AssetHubZagrosParaReceiver as AssetHubZagrosReceiver,
AssetHubZagrosParaSender as AssetHubZagrosSender, BridgeHubZagrosPara as BridgeHubZagros,
CollectivesZagrosPara as CollectivesZagros, CoretimeZagrosPara as CoretimeZagros,
CoretimeZagrosParaReceiver as CoretimeZagrosReceiver,
CoretimeZagrosParaSender as CoretimeZagrosSender, PenpalBPara as PenpalB,
PeopleZagrosPara as PeopleZagros, ZagrosRelay as Zagros,
ZagrosRelayReceiver as ZagrosReceiver, ZagrosRelaySender as ZagrosSender,
};
}
#[cfg(test)]
mod tests;
@@ -0,0 +1,272 @@
// 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 coretime_zagros_emulated_chain::coretime_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,
);
// Aliasing same account on different chains
test_cross_chain_alias!(
vec![
// between AH and Coretime: allowed
(AssetHubZagros, CoretimeZagros, TELEPORT_FEES, ALLOWED),
// between BH and Coretime: allowed
(BridgeHubZagros, CoretimeZagros, TELEPORT_FEES, ALLOWED),
// between Collectives and Coretime: allowed
(CollectivesZagros, CoretimeZagros, TELEPORT_FEES, ALLOWED),
// between People and Coretime: allowed
(PeopleZagros, CoretimeZagros, TELEPORT_FEES, ALLOWED),
// between Penpal and Coretime: denied
(PenpalB, CoretimeZagros, 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 Coretime: denied
(AssetHubZagros, CoretimeZagros, TELEPORT_FEES, DENIED),
// between BH and Coretime: denied
(BridgeHubZagros, CoretimeZagros, TELEPORT_FEES, DENIED),
// between Collectives and Coretime: denied
(CollectivesZagros, CoretimeZagros, TELEPORT_FEES, DENIED),
// between People and Coretime: denied
(PeopleZagros, CoretimeZagros, TELEPORT_FEES, DENIED),
// between Penpal and Coretime: denied
(PenpalB, CoretimeZagros, RESERVE_TRANSFER_FEES, DENIED)
],
origin,
target,
fees
);
}
#[test]
fn aliasing_child_locations() {
CoretimeZagros::execute_with(|| {
// 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() {
CoretimeZagros::execute_with(|| {
// Allows AH root to alias anything.
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);
CoretimeZagros::fund_accounts(vec![(target.clone(), fees * 10)]);
// let's authorize `origin` on Penpal to alias `target` on Coretime
CoretimeZagros::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!(<CoretimeZagros as CoretimeZagrosPallet>::PezkuwiXcm::add_authorized_alias(
<CoretimeZagros 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 Coretime: denied
(AssetHubZagros, CoretimeZagros, TELEPORT_FEES, DENIED),
// between BH and Coretime: denied
(BridgeHubZagros, CoretimeZagros, TELEPORT_FEES, DENIED),
// between People and Coretime: denied
(PeopleZagros, CoretimeZagros, TELEPORT_FEES, DENIED),
// between Penpal and Coretime: denied
(PenpalB, CoretimeZagros, 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 Coretime: denied
(AssetHubZagros, CoretimeZagros, TELEPORT_FEES, DENIED),
// between BH and Coretime: denied
(BridgeHubZagros, CoretimeZagros, TELEPORT_FEES, DENIED),
// between People and Coretime: denied
(PeopleZagros, CoretimeZagros, TELEPORT_FEES, DENIED),
// between Penpal and Coretime: allowed
(PenpalB, CoretimeZagros, RESERVE_TRANSFER_FEES, ALLOWED)
],
origin,
target,
fees
);
// remove authorization for `origin` on Penpal to alias `target` on Coretime
CoretimeZagros::execute_with(|| {
// `target` removes all authorized aliases
assert_ok!(
<CoretimeZagros as CoretimeZagrosPallet>::PezkuwiXcm::remove_all_authorized_aliases(
<CoretimeZagros as Chain>::RuntimeOrigin::signed(target.clone())
)
);
});
// Verify `penpal::origin` can no longer alias into `target` on Coretime.
test_cross_chain_alias!(
vec![(PenpalB, CoretimeZagros, 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 = CoretimeZagrosExistentialDeposit::get();
let assets: Assets = (Parent, amount).into();
test_chain_can_claim_assets!(
CoretimeZagros,
RuntimeCall,
NetworkId::ByGenesis(ZAGROS_GENESIS_HASH),
assets,
amount
);
}
@@ -0,0 +1,228 @@
// 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 pezframe_support::traits::OnInitialize;
use pezpallet_broker::{ConfigRecord, Configuration, CoreAssignment, CoreMask, ScheduleItem};
use pezsp_runtime::Perbill;
use zagros_runtime_constants::system_teyrchain::coretime::TIMESLICE_PERIOD;
use zagros_system_emulated_network::zagros_emulated_chain::zagros_runtime::Dmp;
#[test]
fn transact_hardcoded_weights_are_sane() {
// There are three transacts with hardcoded weights sent from the Coretime Chain to the Relay
// Chain across the CoretimeInterface which are triggered at various points in the sales cycle.
// - Request core count - triggered directly by `start_sales` or `request_core_count`
// extrinsics.
// - Request revenue info - triggered when each timeslice is committed.
// - Assign core - triggered when an entry is encountered in the workplan for the next
// timeslice.
// RuntimeEvent aliases to avoid warning from usage of qualified paths in assertions due to
// <https://github.com/rust-lang/rust/issues/86935>
type CoretimeEvent = <CoretimeZagros as Chain>::RuntimeEvent;
type RelayEvent = <Zagros as Chain>::RuntimeEvent;
Zagros::execute_with(|| {
Dmp::make_teyrchain_reachable(CoretimeZagros::para_id());
});
// Reserve a workload, configure broker and start sales.
CoretimeZagros::execute_with(|| {
// Hooks don't run in emulated tests - workaround as we need `on_initialize` to tick things
// along and have no concept of time passing otherwise.
<CoretimeZagros as CoretimeZagrosPallet>::Broker::on_initialize(
<CoretimeZagros as Chain>::System::block_number(),
);
let coretime_root_origin = <CoretimeZagros as Chain>::RuntimeOrigin::root();
// Create and populate schedule with the worst case assignment on this core.
let mut schedule = Vec::new();
for i in 0..80 {
schedule.push(ScheduleItem {
mask: CoreMask::void().set(i),
assignment: CoreAssignment::Task(2000 + i),
})
}
assert_ok!(<CoretimeZagros as CoretimeZagrosPallet>::Broker::reserve(
coretime_root_origin.clone(),
schedule.try_into().expect("Vector is within bounds."),
));
// Configure broker and start sales.
let config = ConfigRecord {
advance_notice: 1,
interlude_length: 1,
leadin_length: 2,
region_length: 1,
ideal_bulk_proportion: Perbill::from_percent(40),
limit_cores_offered: None,
renewal_bump: Perbill::from_percent(2),
contribution_timeout: 1,
};
assert_ok!(<CoretimeZagros as CoretimeZagrosPallet>::Broker::configure(
coretime_root_origin.clone(),
config
));
assert_ok!(<CoretimeZagros as CoretimeZagrosPallet>::Broker::start_sales(
coretime_root_origin,
100,
0
));
assert_eq!(
pezpallet_broker::Status::<<CoretimeZagros as Chain>::Runtime>::get()
.unwrap()
.core_count,
1
);
assert_expected_events!(
CoretimeZagros,
vec![
CoretimeEvent::Broker(
pezpallet_broker::Event::ReservationMade { .. }
) => {},
CoretimeEvent::Broker(
pezpallet_broker::Event::CoreCountRequested { core_count: 1 }
) => {},
CoretimeEvent::TeyrchainSystem(
cumulus_pallet_teyrchain_system::Event::UpwardMessageSent { .. }
) => {},
]
);
});
// Check that the request_core_count message was processed successfully. This will fail if the
// weights are misconfigured.
Zagros::execute_with(|| {
Zagros::assert_ump_queue_processed(true, Some(CoretimeZagros::para_id()), None);
assert_expected_events!(
Zagros,
vec![
RelayEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
});
// Keep track of the relay chain block number so we can fast forward while still checking the
// right block.
let mut block_number_cursor = Zagros::ext_wrapper(<Zagros as Chain>::System::block_number);
let config = CoretimeZagros::ext_wrapper(|| {
Configuration::<<CoretimeZagros as Chain>::Runtime>::get()
.expect("Pallet was configured earlier.")
});
// Now run up to the block before the sale is rotated.
while block_number_cursor < TIMESLICE_PERIOD - config.advance_notice - 1 {
CoretimeZagros::execute_with(|| {
// Hooks don't run in emulated tests - workaround.
<CoretimeZagros as CoretimeZagrosPallet>::Broker::on_initialize(
<CoretimeZagros as Chain>::System::block_number(),
);
});
Zagros::ext_wrapper(|| {
block_number_cursor = <Zagros as Chain>::System::block_number();
});
}
// In this block we trigger assign core.
CoretimeZagros::execute_with(|| {
// Hooks don't run in emulated tests - workaround.
<CoretimeZagros as CoretimeZagrosPallet>::Broker::on_initialize(
<CoretimeZagros as Chain>::System::block_number(),
);
assert_expected_events!(
CoretimeZagros,
vec![
CoretimeEvent::Broker(
pezpallet_broker::Event::SaleInitialized { .. }
) => {},
CoretimeEvent::Broker(
pezpallet_broker::Event::CoreAssigned { .. }
) => {},
CoretimeEvent::TeyrchainSystem(
cumulus_pallet_teyrchain_system::Event::UpwardMessageSent { .. }
) => {},
]
);
});
// In this block we trigger request revenue.
CoretimeZagros::execute_with(|| {
// Hooks don't run in emulated tests - workaround.
<CoretimeZagros as CoretimeZagrosPallet>::Broker::on_initialize(
<CoretimeZagros as Chain>::System::block_number(),
);
assert_expected_events!(
CoretimeZagros,
vec![
CoretimeEvent::TeyrchainSystem(
cumulus_pallet_teyrchain_system::Event::UpwardMessageSent { .. }
) => {},
]
);
});
// Check that the assign_core and request_revenue_info_at messages were processed successfully.
// This will fail if the weights are misconfigured.
Zagros::execute_with(|| {
Zagros::assert_ump_queue_processed(true, Some(CoretimeZagros::para_id()), None);
assert_expected_events!(
Zagros,
vec![
RelayEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
RelayEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
RelayEvent::Coretime(
pezkuwi_runtime_teyrchains::coretime::Event::CoreAssigned { .. }
) => {},
]
);
});
// Here we receive and process the notify_revenue XCM with zero revenue.
CoretimeZagros::execute_with(|| {
// Hooks don't run in emulated tests - workaround.
<CoretimeZagros as CoretimeZagrosPallet>::Broker::on_initialize(
<CoretimeZagros as Chain>::System::block_number(),
);
assert_expected_events!(
CoretimeZagros,
vec![
CoretimeEvent::MessageQueue(
pezpallet_message_queue::Event::Processed { success: true, .. }
) => {},
// Zero revenue in first timeslice so history is immediately dropped.
CoretimeEvent::Broker(
pezpallet_broker::Event::HistoryDropped { when: 0, revenue: 0 }
) => {},
]
);
});
}
@@ -0,0 +1,19 @@
// 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.
mod aliases;
mod claim_assets;
mod coretime_interface;
mod teleport;
@@ -0,0 +1,118 @@
// 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::{
test_relay_is_trusted_teleporter, test_teyrchain_is_trusted_teleporter,
test_teyrchain_is_trusted_teleporter_for_relay,
};
#[test]
fn teleport_via_limited_teleport_assets_from_and_to_relay() {
let amount = ZAGROS_ED * 10;
test_relay_is_trusted_teleporter!(
Zagros, // Origin
vec![CoretimeZagros], // Destinations
amount,
limited_teleport_assets
);
test_teyrchain_is_trusted_teleporter_for_relay!(
CoretimeZagros, // Origin
Zagros, // Destination
amount,
limited_teleport_assets
);
}
#[test]
fn teleport_via_transfer_assets_from_and_to_relay() {
let amount = ZAGROS_ED * 10;
test_relay_is_trusted_teleporter!(
Zagros, // Origin
vec![CoretimeZagros], // Destinations
amount,
transfer_assets
);
test_teyrchain_is_trusted_teleporter_for_relay!(
CoretimeZagros, // Origin
Zagros, // Destination
amount,
transfer_assets
);
}
#[test]
fn teleport_via_limited_teleport_assets_from_coretime_to_asset_hub() {
let amount = ASSET_HUB_ZAGROS_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
CoretimeZagros, // Origin
vec![AssetHubZagros], // Destinations
(native_asset, amount),
fee_asset_id,
limited_teleport_assets
);
}
#[test]
fn teleport_via_transfer_assets_from_coretime_to_asset_hub() {
let amount = ASSET_HUB_ZAGROS_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
CoretimeZagros, // Origin
vec![AssetHubZagros], // Destinations
(native_asset, amount),
fee_asset_id,
transfer_assets
);
}
#[test]
fn teleport_via_limited_teleport_assets_from_asset_hub_to_coretime() {
let amount = CORETIME_ZAGROS_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
AssetHubZagros, // Origin
vec![CoretimeZagros], // Destinations
(native_asset, amount),
fee_asset_id,
limited_teleport_assets
);
}
#[test]
fn teleport_via_transfer_assets_from_asset_hub_to_coretime() {
let amount = CORETIME_ZAGROS_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
AssetHubZagros, // Origin
vec![CoretimeZagros], // Destinations
(native_asset, amount),
fee_asset_id,
transfer_assets
);
}
@@ -0,0 +1,49 @@
[package]
name = "governance-zagros-integration-tests"
version = "0.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
description = "Zagros governance integration tests with xcm-emulator"
publish = false
[lints]
workspace = true
[dependencies]
codec = { workspace = true, default-features = true }
# Bizinikiwi
pezframe-support = { workspace = true, default-features = true }
pezframe-system = { workspace = true, default-features = true }
pezpallet-utility = { workspace = true, default-features = true }
pezpallet-whitelist = { workspace = true, default-features = true }
pezsp-core = { workspace = true, default-features = true }
pezsp-runtime = { workspace = true, default-features = true }
pezpallet-xcm = { workspace = true, default-features = true }
xcm = { workspace = true, default-features = true }
emulated-integration-tests-common = { workspace = true }
# Local
asset-hub-zagros-runtime = { workspace = true }
collectives-zagros-runtime = { workspace = true }
zagros-runtime = { workspace = true }
zagros-system-emulated-network = { workspace = true }
[features]
runtime-benchmarks = [
"asset-hub-zagros-runtime/runtime-benchmarks",
"collectives-zagros-runtime/runtime-benchmarks",
"emulated-integration-tests-common/runtime-benchmarks",
"pezframe-support/runtime-benchmarks",
"pezframe-system/runtime-benchmarks",
"pezpallet-utility/runtime-benchmarks",
"pezpallet-whitelist/runtime-benchmarks",
"pezpallet-xcm/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"xcm/runtime-benchmarks",
"zagros-runtime/runtime-benchmarks",
"zagros-system-emulated-network/runtime-benchmarks",
]
@@ -0,0 +1,54 @@
// Copyright (C) Parity Technologies and the various Pezkuwi contributors, see Contributions.md
// for a list of specific contributors.
// 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::*;
/// CollectivesZagros dispatches `pezpallet_xcm::send` with `OriginKind:Xcm` to the dest with encoded
/// whitelist call.
#[cfg(test)]
pub fn collectives_send_whitelist(
dest: Location,
encoded_whitelist_call: impl FnOnce() -> Vec<u8>,
) {
CollectivesZagros::execute_with(|| {
type RuntimeEvent = <CollectivesZagros as Chain>::RuntimeEvent;
type RuntimeCall = <CollectivesZagros as Chain>::RuntimeCall;
type RuntimeOrigin = <CollectivesZagros as Chain>::RuntimeOrigin;
type Runtime = <CollectivesZagros as Chain>::Runtime;
let send_whitelist_call = RuntimeCall::PezkuwiXcm(pezpallet_xcm::Call::<Runtime>::send {
dest: bx!(VersionedLocation::from(dest)),
message: bx!(VersionedXcm::from(Xcm(vec![
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
Transact {
origin_kind: OriginKind::Xcm,
fallback_max_weight: None,
call: encoded_whitelist_call().into(),
}
]))),
});
use collectives_zagros_runtime::fellowship::pezpallet_fellowship_origins::Origin::Fellows as FellowsOrigin;
let fellows_origin: RuntimeOrigin = FellowsOrigin.into();
assert_ok!(send_whitelist_call.dispatch(fellows_origin));
assert_expected_events!(
CollectivesZagros,
vec![
RuntimeEvent::PezkuwiXcm(pezpallet_xcm::Event::Sent { .. }) => {},
]
);
});
}
@@ -0,0 +1,47 @@
// Copyright (C) Parity Technologies and the various Pezkuwi contributors, see Contributions.md
// for a list of specific contributors.
// 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 {
pub(crate) use codec::Encode;
pub(crate) use emulated_integration_tests_common::{
impls::{assert_expected_events, bx, TestExt},
xcm_emulator::Chain,
xcm_helpers::{
build_xcm_send_authorize_upgrade_call, call_hash_of,
dispatch_whitelisted_call_with_preimage,
},
};
pub(crate) use pezframe_support::{assert_err, assert_ok};
pub(crate) use pezsp_runtime::{traits::Dispatchable, DispatchError};
pub(crate) use xcm::{latest::prelude::*, VersionedLocation, VersionedXcm};
pub(crate) use zagros_system_emulated_network::CollectivesZagrosPara as CollectivesZagros;
pub(crate) use zagros_system_emulated_network::{
AssetHubZagrosPara as AssetHubZagros, BridgeHubZagrosPara as BridgeHubZagros,
CoretimeZagrosPara as CoretimeZagros, PeopleZagrosPara as PeopleZagros,
ZagrosRelay as Zagros,
};
}
#[cfg(test)]
mod common;
#[cfg(test)]
mod open_gov_on_relay;
#[cfg(test)]
mod open_gov_on_asset_hub;
@@ -0,0 +1,287 @@
// 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::{common::*, imports::*};
use asset_hub_zagros_runtime::governance::pezpallet_custom_origins::Origin;
use emulated_integration_tests_common::impls::Teyrchain;
#[test]
fn assethub_can_authorize_upgrade_for_itself() {
let code_hash = [1u8; 32].into();
type AssetHubRuntime = <AssetHubZagros as Chain>::Runtime;
type AssetHubRuntimeCall = <AssetHubZagros as Chain>::RuntimeCall;
type AssetHubRuntimeOrigin = <AssetHubZagros as Chain>::RuntimeOrigin;
let authorize_upgrade =
AssetHubRuntimeCall::Utility(pezpallet_utility::Call::<AssetHubRuntime>::force_batch {
calls: vec![AssetHubRuntimeCall::System(pezframe_system::Call::authorize_upgrade {
code_hash,
})],
});
// bad origin
let invalid_origin: AssetHubRuntimeOrigin = Origin::StakingAdmin.into();
// ok origin
let ok_origin: AssetHubRuntimeOrigin = Origin::WhitelistedCaller.into();
// store preimage
let call_hash = call_hash_of::<AssetHubZagros>(&authorize_upgrade);
// Err - when dispatch non-whitelisted
assert_err!(
dispatch_whitelisted_call_with_preimage::<AssetHubZagros>(
authorize_upgrade.clone(),
ok_origin.clone()
),
DispatchError::Module(pezsp_runtime::ModuleError {
index: 93,
error: [3, 0, 0, 0],
message: Some("CallIsNotWhitelisted")
})
);
// whitelist
collectives_send_whitelist(
CollectivesZagros::sibling_location_of(<AssetHubZagros as Teyrchain>::para_id()),
|| {
AssetHubRuntimeCall::Whitelist(
pezpallet_whitelist::Call::<AssetHubRuntime>::whitelist_call { call_hash },
)
.encode()
},
);
// Err - when dispatch wrong origin
assert_err!(
dispatch_whitelisted_call_with_preimage::<AssetHubZagros>(
authorize_upgrade.clone(),
invalid_origin
),
DispatchError::BadOrigin
);
// check before
AssetHubZagros::execute_with(|| {
assert!(<AssetHubZagros as Chain>::System::authorized_upgrade().is_none())
});
// ok - authorized
assert_ok!(dispatch_whitelisted_call_with_preimage::<AssetHubZagros>(
authorize_upgrade,
ok_origin
));
// check after - authorized
AssetHubZagros::execute_with(|| {
assert_eq!(
<AssetHubZagros as Chain>::System::authorized_upgrade().unwrap().code_hash(),
&code_hash
)
});
}
#[test]
fn assethub_can_authorize_upgrade_for_relay_chain() {
let code_hash = [1u8; 32].into();
type AssetHubRuntime = <AssetHubZagros as Chain>::Runtime;
type AssetHubRuntimeCall = <AssetHubZagros as Chain>::RuntimeCall;
type AssetHubRuntimeOrigin = <AssetHubZagros as Chain>::RuntimeOrigin;
let authorize_upgrade =
AssetHubRuntimeCall::Utility(pezpallet_utility::Call::<AssetHubRuntime>::force_batch {
calls: vec![build_xcm_send_authorize_upgrade_call::<AssetHubZagros, Zagros>(
AssetHubZagros::parent_location(),
&code_hash,
None,
)],
});
// bad origin
let invalid_origin: AssetHubRuntimeOrigin = Origin::StakingAdmin.into();
// ok origin
let ok_origin: AssetHubRuntimeOrigin = Origin::WhitelistedCaller.into();
let call_hash = call_hash_of::<AssetHubZagros>(&authorize_upgrade);
// Err - when dispatch non-whitelisted
assert_err!(
dispatch_whitelisted_call_with_preimage::<AssetHubZagros>(
authorize_upgrade.clone(),
ok_origin.clone()
),
DispatchError::Module(pezsp_runtime::ModuleError {
index: 93,
error: [3, 0, 0, 0],
message: Some("CallIsNotWhitelisted")
})
);
// whitelist
collectives_send_whitelist(
CollectivesZagros::sibling_location_of(<AssetHubZagros as Teyrchain>::para_id()),
|| {
AssetHubRuntimeCall::Whitelist(
pezpallet_whitelist::Call::<AssetHubRuntime>::whitelist_call { call_hash },
)
.encode()
},
);
// Err - when dispatch wrong origin
assert_err!(
dispatch_whitelisted_call_with_preimage::<AssetHubZagros>(
authorize_upgrade.clone(),
invalid_origin
),
DispatchError::BadOrigin
);
// check before
Zagros::execute_with(|| assert!(<Zagros as Chain>::System::authorized_upgrade().is_none()));
// ok - authorized
assert_ok!(dispatch_whitelisted_call_with_preimage::<AssetHubZagros>(
authorize_upgrade,
ok_origin
));
// check after - authorized
Zagros::execute_with(|| {
assert_eq!(<Zagros as Chain>::System::authorized_upgrade().unwrap().code_hash(), &code_hash)
});
}
#[test]
fn assethub_can_authorize_upgrade_for_system_chains() {
type AssetHubRuntime = <AssetHubZagros as Chain>::Runtime;
type AssetHubRuntimeCall = <AssetHubZagros as Chain>::RuntimeCall;
type AssetHubRuntimeOrigin = <AssetHubZagros as Chain>::RuntimeOrigin;
let code_hash_bridge_hub = [2u8; 32].into();
let code_hash_collectives = [3u8; 32].into();
let code_hash_coretime = [4u8; 32].into();
let code_hash_people = [5u8; 32].into();
let authorize_upgrade =
AssetHubRuntimeCall::Utility(pezpallet_utility::Call::<AssetHubRuntime>::force_batch {
calls: vec![
build_xcm_send_authorize_upgrade_call::<AssetHubZagros, BridgeHubZagros>(
AssetHubZagros::sibling_location_of(BridgeHubZagros::para_id()),
&code_hash_bridge_hub,
None,
),
build_xcm_send_authorize_upgrade_call::<AssetHubZagros, CollectivesZagros>(
AssetHubZagros::sibling_location_of(CollectivesZagros::para_id()),
&code_hash_collectives,
None,
),
build_xcm_send_authorize_upgrade_call::<AssetHubZagros, CoretimeZagros>(
AssetHubZagros::sibling_location_of(CoretimeZagros::para_id()),
&code_hash_coretime,
None,
),
build_xcm_send_authorize_upgrade_call::<AssetHubZagros, PeopleZagros>(
AssetHubZagros::sibling_location_of(PeopleZagros::para_id()),
&code_hash_people,
None,
),
],
});
// bad origin
let invalid_origin: AssetHubRuntimeOrigin = Origin::StakingAdmin.into();
// ok origin
let ok_origin: AssetHubRuntimeOrigin = Origin::WhitelistedCaller.into();
let call_hash = call_hash_of::<AssetHubZagros>(&authorize_upgrade);
// Err - when dispatch non-whitelisted
assert_err!(
dispatch_whitelisted_call_with_preimage::<AssetHubZagros>(
authorize_upgrade.clone(),
ok_origin.clone()
),
DispatchError::Module(pezsp_runtime::ModuleError {
index: 93,
error: [3, 0, 0, 0],
message: Some("CallIsNotWhitelisted")
})
);
// whitelist
collectives_send_whitelist(
CollectivesZagros::sibling_location_of(<AssetHubZagros as Teyrchain>::para_id()),
|| {
AssetHubRuntimeCall::Whitelist(
pezpallet_whitelist::Call::<AssetHubRuntime>::whitelist_call { call_hash },
)
.encode()
},
);
// Err - when dispatch wrong origin
assert_err!(
dispatch_whitelisted_call_with_preimage::<AssetHubZagros>(
authorize_upgrade.clone(),
invalid_origin
),
DispatchError::BadOrigin
);
// check before
BridgeHubZagros::execute_with(|| {
assert!(<BridgeHubZagros as Chain>::System::authorized_upgrade().is_none())
});
CollectivesZagros::execute_with(|| {
assert!(<CollectivesZagros as Chain>::System::authorized_upgrade().is_none())
});
CoretimeZagros::execute_with(|| {
assert!(<CoretimeZagros as Chain>::System::authorized_upgrade().is_none())
});
PeopleZagros::execute_with(|| {
assert!(<PeopleZagros as Chain>::System::authorized_upgrade().is_none())
});
// ok - authorized
assert_ok!(dispatch_whitelisted_call_with_preimage::<AssetHubZagros>(
authorize_upgrade,
ok_origin
));
// check after - authorized
BridgeHubZagros::execute_with(|| {
assert_eq!(
<BridgeHubZagros as Chain>::System::authorized_upgrade().unwrap().code_hash(),
&code_hash_bridge_hub
)
});
CollectivesZagros::execute_with(|| {
assert_eq!(
<CollectivesZagros as Chain>::System::authorized_upgrade().unwrap().code_hash(),
&code_hash_collectives
)
});
CoretimeZagros::execute_with(|| {
assert_eq!(
<CoretimeZagros as Chain>::System::authorized_upgrade().unwrap().code_hash(),
&code_hash_coretime
)
});
PeopleZagros::execute_with(|| {
assert_eq!(
<PeopleZagros as Chain>::System::authorized_upgrade().unwrap().code_hash(),
&code_hash_people
)
});
}
@@ -0,0 +1,236 @@
// 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::{common::*, imports::*};
use emulated_integration_tests_common::{
assert_whitelisted,
impls::RelayChain,
xcm_emulator::{Chain, TestExt, Teyrchain},
};
use zagros_runtime::governance::pezpallet_custom_origins::Origin;
use zagros_system_emulated_network::{
AssetHubZagrosPara as AssetHubZagros, BridgeHubZagrosPara as BridgeHubZagros,
CoretimeZagrosPara as CoretimeZagros, PeopleZagrosPara as PeopleZagros, ZagrosRelay as Zagros,
};
use zagros_system_emulated_network::zagros_emulated_chain::zagros_runtime::Dmp;
#[test]
fn relaychain_can_authorize_upgrade_for_itself() {
let code_hash = [1u8; 32].into();
type ZagrosRuntime = <Zagros as Chain>::Runtime;
type ZagrosRuntimeCall = <Zagros as Chain>::RuntimeCall;
type ZagrosRuntimeOrigin = <Zagros as Chain>::RuntimeOrigin;
let authorize_upgrade =
ZagrosRuntimeCall::Utility(pezpallet_utility::Call::<ZagrosRuntime>::force_batch {
calls: vec![
// upgrade the relaychain
ZagrosRuntimeCall::System(pezframe_system::Call::authorize_upgrade { code_hash }),
],
});
// bad origin
let invalid_origin: ZagrosRuntimeOrigin = Origin::StakingAdmin.into();
// ok origin
let ok_origin: ZagrosRuntimeOrigin = Origin::WhitelistedCaller.into();
let call_hash = call_hash_of::<Zagros>(&authorize_upgrade);
// Err - when dispatch non-whitelisted
assert_err!(
dispatch_whitelisted_call_with_preimage::<Zagros>(
authorize_upgrade.clone(),
ok_origin.clone()
),
DispatchError::Module(pezsp_runtime::ModuleError {
index: 36,
error: [3, 0, 0, 0],
message: Some("CallIsNotWhitelisted")
})
);
// whitelist
collectives_send_whitelist(Location::parent(), || {
ZagrosRuntimeCall::Whitelist(pezpallet_whitelist::Call::<ZagrosRuntime>::whitelist_call {
call_hash,
})
.encode()
});
// Err - when dispatch wrong origin
assert_err!(
dispatch_whitelisted_call_with_preimage::<Zagros>(
authorize_upgrade.clone(),
invalid_origin
),
DispatchError::BadOrigin
);
// check before
Zagros::execute_with(|| assert!(<Zagros as Chain>::System::authorized_upgrade().is_none()));
// ok - authorized
assert_ok!(dispatch_whitelisted_call_with_preimage::<Zagros>(authorize_upgrade, ok_origin));
// check after - authorized
Zagros::execute_with(|| {
assert_eq!(<Zagros as Chain>::System::authorized_upgrade().unwrap().code_hash(), &code_hash)
});
}
#[test]
fn relaychain_can_authorize_upgrade_for_system_chains() {
type ZagrosRuntime = <Zagros as Chain>::Runtime;
type ZagrosRuntimeCall = <Zagros as Chain>::RuntimeCall;
type ZagrosRuntimeOrigin = <Zagros as Chain>::RuntimeOrigin;
Zagros::execute_with(|| {
Dmp::make_teyrchain_reachable(AssetHubZagros::para_id());
Dmp::make_teyrchain_reachable(BridgeHubZagros::para_id());
Dmp::make_teyrchain_reachable(CollectivesZagros::para_id());
Dmp::make_teyrchain_reachable(CoretimeZagros::para_id());
Dmp::make_teyrchain_reachable(PeopleZagros::para_id());
});
let code_hash_asset_hub = [1u8; 32].into();
let code_hash_bridge_hub = [2u8; 32].into();
let code_hash_collectives = [3u8; 32].into();
let code_hash_coretime = [4u8; 32].into();
let code_hash_people = [5u8; 32].into();
let authorize_upgrade =
ZagrosRuntimeCall::Utility(pezpallet_utility::Call::<ZagrosRuntime>::force_batch {
calls: vec![
build_xcm_send_authorize_upgrade_call::<Zagros, AssetHubZagros>(
Zagros::child_location_of(AssetHubZagros::para_id()),
&code_hash_asset_hub,
None,
),
build_xcm_send_authorize_upgrade_call::<Zagros, BridgeHubZagros>(
Zagros::child_location_of(BridgeHubZagros::para_id()),
&code_hash_bridge_hub,
None,
),
build_xcm_send_authorize_upgrade_call::<Zagros, CollectivesZagros>(
Zagros::child_location_of(CollectivesZagros::para_id()),
&code_hash_collectives,
None,
),
build_xcm_send_authorize_upgrade_call::<Zagros, CoretimeZagros>(
Zagros::child_location_of(CoretimeZagros::para_id()),
&code_hash_coretime,
None,
),
build_xcm_send_authorize_upgrade_call::<Zagros, PeopleZagros>(
Zagros::child_location_of(PeopleZagros::para_id()),
&code_hash_people,
None,
),
],
});
// bad origin
let invalid_origin: ZagrosRuntimeOrigin = Origin::StakingAdmin.into();
// ok origin
let ok_origin: ZagrosRuntimeOrigin = Origin::WhitelistedCaller.into();
let call_hash = call_hash_of::<Zagros>(&authorize_upgrade);
// Err - when dispatch non-whitelisted
assert_err!(
dispatch_whitelisted_call_with_preimage::<Zagros>(
authorize_upgrade.clone(),
ok_origin.clone()
),
DispatchError::Module(pezsp_runtime::ModuleError {
index: 36,
error: [3, 0, 0, 0],
message: Some("CallIsNotWhitelisted")
})
);
// whitelist
collectives_send_whitelist(Location::parent(), || {
ZagrosRuntimeCall::Whitelist(pezpallet_whitelist::Call::<ZagrosRuntime>::whitelist_call {
call_hash,
})
.encode()
});
Zagros::execute_with(|| {
assert_whitelisted!(Zagros, call_hash);
});
// Err - when dispatch wrong origin
assert_err!(
dispatch_whitelisted_call_with_preimage::<Zagros>(
authorize_upgrade.clone(),
invalid_origin
),
DispatchError::BadOrigin
);
// check before
AssetHubZagros::execute_with(|| {
assert!(<AssetHubZagros as Chain>::System::authorized_upgrade().is_none())
});
BridgeHubZagros::execute_with(|| {
assert!(<BridgeHubZagros as Chain>::System::authorized_upgrade().is_none())
});
CollectivesZagros::execute_with(|| {
assert!(<CollectivesZagros as Chain>::System::authorized_upgrade().is_none())
});
CoretimeZagros::execute_with(|| {
assert!(<CoretimeZagros as Chain>::System::authorized_upgrade().is_none())
});
PeopleZagros::execute_with(|| {
assert!(<PeopleZagros as Chain>::System::authorized_upgrade().is_none())
});
// ok - authorized
assert_ok!(dispatch_whitelisted_call_with_preimage::<Zagros>(authorize_upgrade, ok_origin));
AssetHubZagros::execute_with(|| {
assert_eq!(
<AssetHubZagros as Chain>::System::authorized_upgrade().unwrap().code_hash(),
&code_hash_asset_hub
)
});
// check after - authorized
BridgeHubZagros::execute_with(|| {
assert_eq!(
<BridgeHubZagros as Chain>::System::authorized_upgrade().unwrap().code_hash(),
&code_hash_bridge_hub
)
});
CollectivesZagros::execute_with(|| {
assert_eq!(
<CollectivesZagros as Chain>::System::authorized_upgrade().unwrap().code_hash(),
&code_hash_collectives
)
});
CoretimeZagros::execute_with(|| {
assert_eq!(
<CoretimeZagros as Chain>::System::authorized_upgrade().unwrap().code_hash(),
&code_hash_coretime
)
});
PeopleZagros::execute_with(|| {
assert_eq!(
<PeopleZagros as Chain>::System::authorized_upgrade().unwrap().code_hash(),
&code_hash_people
)
});
}
@@ -0,0 +1,40 @@
[package]
name = "people-pezkuwichain-integration-tests"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
description = "People Pezkuwichain runtime integration tests with xcm-emulator"
publish = false
[lints]
workspace = true
[dependencies]
# Bizinikiwi
pezframe-support = { workspace = true }
pezpallet-balances = { workspace = true }
pezsp-runtime = { workspace = true }
# Pezkuwi
xcm = { workspace = true }
xcm-executor = { workspace = true }
# Pezcumulus
asset-test-utils = { workspace = true, default-features = true }
emulated-integration-tests-common = { workspace = true }
pezkuwichain-system-emulated-network = { workspace = true }
teyrchains-common = { workspace = true, default-features = true }
[features]
runtime-benchmarks = [
"asset-test-utils/runtime-benchmarks",
"emulated-integration-tests-common/runtime-benchmarks",
"pezframe-support/runtime-benchmarks",
"pezpallet-balances/runtime-benchmarks",
"pezkuwichain-system-emulated-network/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"teyrchains-common/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
"xcm/runtime-benchmarks",
]
@@ -0,0 +1,54 @@
// 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 pezframe_support::pezsp_runtime::DispatchResult;
// Pezkuwi
pub(crate) use xcm::{latest::PEZKUWICHAIN_GENESIS_HASH, prelude::*};
// Pezcumulus
pub(crate) use asset_test_utils::xcm_helpers;
pub(crate) use emulated_integration_tests_common::xcm_emulator::{
assert_expected_events, bx, Chain, Test, TestArgs, TestContext, TestExt, Teyrchain as Para,
};
pub(crate) use pezkuwichain_system_emulated_network::{
people_pezkuwichain_emulated_chain::{
people_pezkuwichain_runtime::{
xcm_config::XcmConfig as PeoplePezkuwichainXcmConfig,
ExistentialDeposit as PeoplePezkuwichainExistentialDeposit,
},
PeoplePezkuwichainParaPallet as PeoplePezkuwichainPallet,
},
pezkuwichain_emulated_chain::{
genesis::ED as PEZKUWICHAIN_ED, PezkuwichainRelayPallet as PezkuwichainPallet,
},
AssetHubPezkuwichainPara as AssetHubPezkuwichain,
AssetHubPezkuwichainParaReceiver as AssetHubPezkuwichainReceiver,
PeoplePezkuwichainPara as PeoplePezkuwichain,
PeoplePezkuwichainParaReceiver as PeoplePezkuwichainReceiver,
PeoplePezkuwichainParaSender as PeoplePezkuwichainSender,
PezkuwichainRelay as Pezkuwichain, PezkuwichainRelayReceiver as PezkuwichainReceiver,
PezkuwichainRelaySender as PezkuwichainSender,
};
pub(crate) use teyrchains_common::Balance;
pub(crate) type SystemParaToRelayTest = Test<PeoplePezkuwichain, Pezkuwichain>;
}
#[cfg(test)]
mod tests;
@@ -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 = PeoplePezkuwichainExistentialDeposit::get();
let assets: Assets = (Parent, amount).into();
test_chain_can_claim_assets!(
PeoplePezkuwichain,
RuntimeCall,
NetworkId::ByGenesis(PEZKUWICHAIN_GENESIS_HASH),
assets,
amount
);
}
@@ -0,0 +1,17 @@
// 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.
mod claim_assets;
mod teleport;
@@ -0,0 +1,183 @@
// 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::{
test_relay_is_trusted_teleporter, test_teyrchain_is_trusted_teleporter,
test_teyrchain_is_trusted_teleporter_for_relay,
};
#[test]
fn teleport_via_limited_teleport_assets_from_and_to_relay() {
let amount = PEZKUWICHAIN_ED * 100;
test_relay_is_trusted_teleporter!(
Pezkuwichain,
vec![PeoplePezkuwichain],
amount,
limited_teleport_assets
);
test_teyrchain_is_trusted_teleporter_for_relay!(
PeoplePezkuwichain,
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![PeoplePezkuwichain],
amount,
transfer_assets
);
test_teyrchain_is_trusted_teleporter_for_relay!(
PeoplePezkuwichain,
Pezkuwichain,
amount,
transfer_assets
);
}
#[test]
fn teleport_via_limited_teleport_assets_to_other_system_teyrchains_works() {
let amount = PEZKUWICHAIN_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
PeoplePezkuwichain, // 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 = PEZKUWICHAIN_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
PeoplePezkuwichain, // Origin
vec![AssetHubPezkuwichain], // Destinations
(native_asset, amount),
fee_asset_id,
transfer_assets
);
}
fn relay_dest_assertions_fail(_t: SystemParaToRelayTest) {
Pezkuwichain::assert_ump_queue_processed(false, Some(PeoplePezkuwichain::para_id()), None);
}
fn para_origin_assertions(t: SystemParaToRelayTest) {
type RuntimeEvent = <PeoplePezkuwichain as Chain>::RuntimeEvent;
PeoplePezkuwichain::assert_xcm_pallet_attempted_complete(None);
PeoplePezkuwichain::assert_teyrchain_system_ump_sent();
assert_expected_events!(
PeoplePezkuwichain,
vec![
// Amount is withdrawn from Sender's account
RuntimeEvent::Balances(pezpallet_balances::Event::Burned { who, amount }) => {
who: *who == t.sender.account_id,
amount: *amount == t.args.amount,
},
]
);
}
fn system_para_limited_teleport_assets(t: SystemParaToRelayTest) -> DispatchResult {
<PeoplePezkuwichain as PeoplePezkuwichainPallet>::PezkuwiXcm::limited_teleport_assets(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.beneficiary.into()),
bx!(t.args.assets.into()),
bx!(t.args.fee_asset_id.into()),
t.args.weight_limit,
)
}
/// Limited Teleport of native asset from System Teyrchain to Relay Chain
/// shouldn't work when there is not enough balance in Relay Chain's `CheckAccount`
#[test]
fn limited_teleport_native_assets_from_system_para_to_relay_fails() {
// Init values for Relay Chain
let amount_to_send: Balance = PEZKUWICHAIN_ED * 1000;
let destination = PeoplePezkuwichain::parent_location();
let beneficiary_id = PezkuwichainReceiver::get();
let assets = (Parent, amount_to_send).into();
let fee_asset_id: AssetId = Parent.into();
// Fund a sender
PeoplePezkuwichain::fund_accounts(vec![(
PeoplePezkuwichainSender::get(),
PEZKUWICHAIN_ED * 2_000u128,
)]);
let test_args = TestContext {
sender: PeoplePezkuwichainSender::get(),
receiver: PezkuwichainReceiver::get(),
args: TestArgs::new_para(
destination,
beneficiary_id,
amount_to_send,
assets,
None,
fee_asset_id,
),
};
let mut test = SystemParaToRelayTest::new(test_args);
let sender_balance_before = test.sender.balance;
let receiver_balance_before = test.receiver.balance;
test.set_assertion::<PeoplePezkuwichain>(para_origin_assertions);
test.set_assertion::<Pezkuwichain>(relay_dest_assertions_fail);
test.set_dispatchable::<PeoplePezkuwichain>(system_para_limited_teleport_assets);
test.assert();
let sender_balance_after = test.sender.balance;
let receiver_balance_after = test.receiver.balance;
let delivery_fees = PeoplePezkuwichain::execute_with(|| {
xcm_helpers::teleport_assets_delivery_fees::<
<PeoplePezkuwichainXcmConfig as xcm_executor::Config>::XcmSender,
>(
test.args.assets.clone(),
test.args.fee_asset_id,
test.args.weight_limit,
test.args.beneficiary,
test.args.dest,
)
});
// Sender's balance is reduced
assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after);
// Receiver's balance does not change
assert_eq!(receiver_balance_after, receiver_balance_before);
}
@@ -0,0 +1,48 @@
[package]
name = "people-zagros-integration-tests"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
description = "People Zagros runtime integration tests with xcm-emulator"
publish = false
[lints]
workspace = true
[dependencies]
codec = { workspace = true }
# Bizinikiwi
pezframe-support = { workspace = true }
pezpallet-balances = { workspace = true }
pezpallet-identity = { workspace = true }
pezpallet-message-queue = { workspace = true }
pezpallet-xcm = { workspace = true }
pezsp-runtime = { workspace = true }
# Pezkuwi
xcm = { workspace = true }
xcm-executor = { workspace = true }
zagros-runtime = { workspace = true }
# Pezcumulus
emulated-integration-tests-common = { workspace = true }
teyrchains-common = { workspace = true, default-features = true }
zagros-system-emulated-network = { workspace = true }
[features]
runtime-benchmarks = [
"emulated-integration-tests-common/runtime-benchmarks",
"pezframe-support/runtime-benchmarks",
"pezpallet-balances/runtime-benchmarks",
"pezpallet-identity/runtime-benchmarks",
"pezpallet-message-queue/runtime-benchmarks",
"pezpallet-xcm/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"teyrchains-common/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
"xcm/runtime-benchmarks",
"zagros-runtime/runtime-benchmarks",
"zagros-system-emulated-network/runtime-benchmarks",
]
@@ -0,0 +1,53 @@
// 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 pezframe_support::assert_ok;
// Pezkuwi
pub(crate) use xcm::{latest::ZAGROS_GENESIS_HASH, prelude::*};
// Pezcumulus
pub(crate) use emulated_integration_tests_common::xcm_emulator::{
assert_expected_events, bx, Chain, TestExt, Teyrchain as Para,
};
pub(crate) use zagros_system_emulated_network::{
self,
asset_hub_zagros_emulated_chain::AssetHubZagrosParaPallet as AssetHubZagrosPallet,
bridge_hub_zagros_emulated_chain::BridgeHubZagrosParaPallet as BridgeHubZagrosPallet,
collectives_zagros_emulated_chain::CollectivesZagrosParaPallet as CollectivesZagrosPallet,
coretime_zagros_emulated_chain::CoretimeZagrosParaPallet as CoretimeZagrosPallet,
penpal_emulated_chain::{PenpalAssetOwner, PenpalBParaPallet as PenpalBPallet},
people_zagros_emulated_chain::{
people_zagros_runtime::{
self, xcm_config::XcmConfig as PeopleZagrosXcmConfig,
ExistentialDeposit as PeopleZagrosExistentialDeposit,
},
PeopleZagrosParaPallet as PeopleZagrosPallet,
},
zagros_emulated_chain::{genesis::ED as ZAGROS_ED, ZagrosRelayPallet as ZagrosPallet},
AssetHubZagrosPara as AssetHubZagros, AssetHubZagrosParaReceiver as AssetHubZagrosReceiver,
BridgeHubZagrosPara as BridgeHubZagros, CollectivesZagrosPara as CollectivesZagros,
CoretimeZagrosPara as CoretimeZagros, PenpalBPara as PenpalB,
PeopleZagrosPara as PeopleZagros, PeopleZagrosParaReceiver as PeopleZagrosReceiver,
PeopleZagrosParaSender as PeopleZagrosSender, ZagrosRelay as Zagros,
ZagrosRelayReceiver as ZagrosReceiver, ZagrosRelaySender as ZagrosSender,
};
}
#[cfg(test)]
mod tests;
@@ -0,0 +1,273 @@
// 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 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,
);
// Aliasing same account on different chains
test_cross_chain_alias!(
vec![
// between AH and People: allowed
(AssetHubZagros, PeopleZagros, TELEPORT_FEES, ALLOWED),
// between BH and People: allowed
(BridgeHubZagros, PeopleZagros, TELEPORT_FEES, ALLOWED),
// between Collectives and People: allowed
(CollectivesZagros, PeopleZagros, TELEPORT_FEES, ALLOWED),
// between Coretime and People: allowed
(CoretimeZagros, PeopleZagros, TELEPORT_FEES, ALLOWED),
// between Penpal and People: denied
(PenpalB, PeopleZagros, 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 People: denied
(AssetHubZagros, PeopleZagros, TELEPORT_FEES, DENIED),
// between BH and People: denied
(BridgeHubZagros, PeopleZagros, TELEPORT_FEES, DENIED),
// between Collectives and People: denied
(CollectivesZagros, PeopleZagros, TELEPORT_FEES, DENIED),
// between Coretime and People: denied
(CoretimeZagros, PeopleZagros, TELEPORT_FEES, DENIED),
// between Penpal and People: denied
(PenpalB, PeopleZagros, RESERVE_TRANSFER_FEES, DENIED)
],
origin,
target,
fees
);
}
#[test]
fn aliasing_child_locations() {
use PeopleZagrosXcmConfig as XcmConfig;
PeopleZagros::execute_with(|| {
// 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() {
use PeopleZagrosXcmConfig as XcmConfig;
PeopleZagros::execute_with(|| {
// Allows AH root to alias anything.
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);
PeopleZagros::fund_accounts(vec![(target.clone(), fees * 10)]);
// let's authorize `origin` on Penpal to alias `target` on People
PeopleZagros::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!(<PeopleZagros as PeopleZagrosPallet>::PezkuwiXcm::add_authorized_alias(
<PeopleZagros 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 People: denied
(AssetHubZagros, PeopleZagros, TELEPORT_FEES, DENIED),
// between BH and People: denied
(BridgeHubZagros, PeopleZagros, TELEPORT_FEES, DENIED),
// between Collectives and People: denied
(CollectivesZagros, PeopleZagros, TELEPORT_FEES, DENIED),
// between Penpal and People: denied
(PenpalB, PeopleZagros, 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 People: denied
(AssetHubZagros, PeopleZagros, TELEPORT_FEES, DENIED),
// between BH and People: denied
(BridgeHubZagros, PeopleZagros, TELEPORT_FEES, DENIED),
// between Collectives and People: denied
(CollectivesZagros, PeopleZagros, TELEPORT_FEES, DENIED),
// between Penpal and People: allowed
(PenpalB, PeopleZagros, RESERVE_TRANSFER_FEES, ALLOWED)
],
origin,
target,
fees
);
// remove authorization for `origin` on Penpal to alias `target` on People
PeopleZagros::execute_with(|| {
// `target` removes all authorized aliases
assert_ok!(
<PeopleZagros as PeopleZagrosPallet>::PezkuwiXcm::remove_all_authorized_aliases(
<PeopleZagros as Chain>::RuntimeOrigin::signed(target.clone())
)
);
});
// Verify `penpal::origin` can no longer alias into `target` on People.
test_cross_chain_alias!(
vec![(PenpalB, PeopleZagros, 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 = PeopleZagrosExistentialDeposit::get();
let assets: Assets = (Parent, amount).into();
test_chain_can_claim_assets!(
PeopleZagros,
RuntimeCall,
NetworkId::ByGenesis(ZAGROS_GENESIS_HASH),
assets,
amount
);
}
@@ -0,0 +1,547 @@
// 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 codec::Encode;
use pezframe_support::pezsp_runtime::traits::Dispatchable;
use people_zagros_runtime::people::IdentityInfo;
use teyrchains_common::AccountId;
use zagros_runtime::{
governance::pezpallet_custom_origins::Origin::GeneralAdmin as GeneralAdminOrigin, Dmp,
};
use zagros_system_emulated_network::people_zagros_emulated_chain::people_zagros_runtime;
use pezpallet_identity::Data;
use emulated_integration_tests_common::accounts::{ALICE, BOB};
#[test]
fn relay_commands_add_registrar() {
let (origin_kind, origin) = (OriginKind::Superuser, <Zagros as Chain>::RuntimeOrigin::root());
let registrar: AccountId = [1; 32].into();
Zagros::execute_with(|| {
type Runtime = <Zagros as Chain>::Runtime;
type RuntimeCall = <Zagros as Chain>::RuntimeCall;
type RuntimeEvent = <Zagros as Chain>::RuntimeEvent;
type PeopleCall = <PeopleZagros as Chain>::RuntimeCall;
type PeopleRuntime = <PeopleZagros as Chain>::Runtime;
Dmp::make_teyrchain_reachable(1004);
let add_registrar_call =
PeopleCall::Identity(pezpallet_identity::Call::<PeopleRuntime>::add_registrar {
account: registrar.into(),
});
let xcm_message = RuntimeCall::XcmPallet(pezpallet_xcm::Call::<Runtime>::send {
dest: bx!(VersionedLocation::from(Location::new(0, [Teyrchain(1004)]))),
message: bx!(VersionedXcm::from(Xcm(vec![
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
Transact {
origin_kind,
call: add_registrar_call.encode().into(),
fallback_max_weight: None
}
]))),
});
assert_ok!(xcm_message.dispatch(origin));
assert_expected_events!(
Zagros,
vec![
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::Sent { .. }) => {},
]
);
});
PeopleZagros::execute_with(|| {
type RuntimeEvent = <PeopleZagros as Chain>::RuntimeEvent;
assert_expected_events!(
PeopleZagros,
vec![
RuntimeEvent::Identity(pezpallet_identity::Event::RegistrarAdded { .. }) => {},
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed { success: true, .. }) => {},
]
);
});
}
#[test]
fn relay_commands_add_registrar_wrong_origin() {
let people_zagros_alice = PeopleZagros::account_id_of(ALICE);
let origins = vec![
(
OriginKind::SovereignAccount,
<Zagros as Chain>::RuntimeOrigin::signed(people_zagros_alice),
),
(OriginKind::Xcm, GeneralAdminOrigin.into()),
];
let mut signed_origin = true;
for (origin_kind, origin) in origins {
let registrar: AccountId = [1; 32].into();
Zagros::execute_with(|| {
type Runtime = <Zagros as Chain>::Runtime;
type RuntimeCall = <Zagros as Chain>::RuntimeCall;
type RuntimeEvent = <Zagros as Chain>::RuntimeEvent;
type PeopleCall = <PeopleZagros as Chain>::RuntimeCall;
type PeopleRuntime = <PeopleZagros as Chain>::Runtime;
Dmp::make_teyrchain_reachable(1004);
let add_registrar_call =
PeopleCall::Identity(pezpallet_identity::Call::<PeopleRuntime>::add_registrar {
account: registrar.into(),
});
let xcm_message = RuntimeCall::XcmPallet(pezpallet_xcm::Call::<Runtime>::send {
dest: bx!(VersionedLocation::from(Location::new(0, [Teyrchain(1004)]))),
message: bx!(VersionedXcm::from(Xcm(vec![
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
Transact {
origin_kind,
call: add_registrar_call.encode().into(),
fallback_max_weight: None
}
]))),
});
assert_ok!(xcm_message.dispatch(origin));
assert_expected_events!(
Zagros,
vec![
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::Sent { .. }) => {},
]
);
});
PeopleZagros::execute_with(|| {
type RuntimeEvent = <PeopleZagros as Chain>::RuntimeEvent;
if signed_origin {
assert_expected_events!(
PeopleZagros,
vec![
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed { success: false, .. }) => {},
]
);
} else {
assert_expected_events!(
PeopleZagros,
vec![
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed { success: true, .. }) => {},
]
);
}
});
signed_origin = false;
}
}
#[test]
fn relay_commands_kill_identity() {
// To kill an identity, first one must be set
PeopleZagros::execute_with(|| {
type PeopleRuntime = <PeopleZagros as Chain>::Runtime;
type PeopleRuntimeEvent = <PeopleZagros as Chain>::RuntimeEvent;
let people_zagros_alice =
<PeopleZagros as Chain>::RuntimeOrigin::signed(PeopleZagros::account_id_of(ALICE));
let identity_info = IdentityInfo {
email: Data::Raw(b"test@test.io".to_vec().try_into().unwrap()),
..Default::default()
};
let identity: Box<<PeopleRuntime as pezpallet_identity::Config>::IdentityInformation> =
Box::new(identity_info);
assert_ok!(<PeopleZagros as PeopleZagrosPallet>::Identity::set_identity(
people_zagros_alice,
identity
));
assert_expected_events!(
PeopleZagros,
vec![
PeopleRuntimeEvent::Identity(pezpallet_identity::Event::IdentitySet { .. }) => {},
]
);
});
let (origin_kind, origin) = (OriginKind::Superuser, <Zagros as Chain>::RuntimeOrigin::root());
Zagros::execute_with(|| {
type Runtime = <Zagros as Chain>::Runtime;
type RuntimeCall = <Zagros as Chain>::RuntimeCall;
type PeopleCall = <PeopleZagros as Chain>::RuntimeCall;
type RuntimeEvent = <Zagros as Chain>::RuntimeEvent;
type PeopleRuntime = <PeopleZagros as Chain>::Runtime;
Dmp::make_teyrchain_reachable(1004);
let kill_identity_call =
PeopleCall::Identity(pezpallet_identity::Call::<PeopleRuntime>::kill_identity {
target: people_zagros_runtime::MultiAddress::Id(PeopleZagros::account_id_of(ALICE)),
});
let xcm_message = RuntimeCall::XcmPallet(pezpallet_xcm::Call::<Runtime>::send {
dest: bx!(VersionedLocation::from(Location::new(0, [Teyrchain(1004)]))),
message: bx!(VersionedXcm::from(Xcm(vec![
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
Transact {
origin_kind,
call: kill_identity_call.encode().into(),
fallback_max_weight: None
}
]))),
});
assert_ok!(xcm_message.dispatch(origin));
assert_expected_events!(
Zagros,
vec![
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::Sent { .. }) => {},
]
);
});
PeopleZagros::execute_with(|| {
type RuntimeEvent = <PeopleZagros as Chain>::RuntimeEvent;
assert_expected_events!(
PeopleZagros,
vec![
RuntimeEvent::Identity(pezpallet_identity::Event::IdentityKilled { .. }) => {},
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed { success: true, .. }) => {},
]
);
});
}
#[test]
fn relay_commands_kill_identity_wrong_origin() {
let people_zagros_alice = PeopleZagros::account_id_of(BOB);
let origins = vec![
(
OriginKind::SovereignAccount,
<Zagros as Chain>::RuntimeOrigin::signed(people_zagros_alice),
),
(OriginKind::Xcm, GeneralAdminOrigin.into()),
];
for (origin_kind, origin) in origins {
Zagros::execute_with(|| {
type Runtime = <Zagros as Chain>::Runtime;
type RuntimeCall = <Zagros as Chain>::RuntimeCall;
type PeopleCall = <PeopleZagros as Chain>::RuntimeCall;
type RuntimeEvent = <Zagros as Chain>::RuntimeEvent;
type PeopleRuntime = <PeopleZagros as Chain>::Runtime;
Dmp::make_teyrchain_reachable(1004);
let kill_identity_call =
PeopleCall::Identity(pezpallet_identity::Call::<PeopleRuntime>::kill_identity {
target: people_zagros_runtime::MultiAddress::Id(PeopleZagros::account_id_of(
ALICE,
)),
});
let xcm_message = RuntimeCall::XcmPallet(pezpallet_xcm::Call::<Runtime>::send {
dest: bx!(VersionedLocation::from(Location::new(0, [Teyrchain(1004)]))),
message: bx!(VersionedXcm::from(Xcm(vec![
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
Transact {
origin_kind,
call: kill_identity_call.encode().into(),
fallback_max_weight: None
}
]))),
});
assert_ok!(xcm_message.dispatch(origin));
assert_expected_events!(
Zagros,
vec![
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::Sent { .. }) => {},
]
);
});
PeopleZagros::execute_with(|| {
assert_expected_events!(PeopleZagros, vec![]);
});
}
}
#[test]
fn relay_commands_add_remove_username_authority() {
let people_zagros_alice = PeopleZagros::account_id_of(ALICE);
let people_zagros_bob = PeopleZagros::account_id_of(BOB);
let (origin_kind, origin, usr) =
(OriginKind::Superuser, <Zagros as Chain>::RuntimeOrigin::root(), "rootusername");
// First, add a username authority.
Zagros::execute_with(|| {
type Runtime = <Zagros as Chain>::Runtime;
type RuntimeCall = <Zagros as Chain>::RuntimeCall;
type RuntimeEvent = <Zagros as Chain>::RuntimeEvent;
type PeopleCall = <PeopleZagros as Chain>::RuntimeCall;
type PeopleRuntime = <PeopleZagros as Chain>::Runtime;
Dmp::make_teyrchain_reachable(1004);
let add_username_authority =
PeopleCall::Identity(pezpallet_identity::Call::<PeopleRuntime>::add_username_authority {
authority: people_zagros_runtime::MultiAddress::Id(people_zagros_alice.clone()),
suffix: b"suffix1".into(),
allocation: 10,
});
let add_authority_xcm_msg = RuntimeCall::XcmPallet(pezpallet_xcm::Call::<Runtime>::send {
dest: bx!(VersionedLocation::from(Location::new(0, [Teyrchain(1004)]))),
message: bx!(VersionedXcm::from(Xcm(vec![
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
Transact {
origin_kind,
call: add_username_authority.encode().into(),
fallback_max_weight: None
}
]))),
});
assert_ok!(add_authority_xcm_msg.dispatch(origin.clone()));
assert_expected_events!(
Zagros,
vec![
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::Sent { .. }) => {},
]
);
});
// Check events system-teyrchain-side
PeopleZagros::execute_with(|| {
type RuntimeEvent = <PeopleZagros as Chain>::RuntimeEvent;
assert_expected_events!(
PeopleZagros,
vec![
RuntimeEvent::Identity(pezpallet_identity::Event::AuthorityAdded { .. }) => {},
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed { success: true, .. }) => {},
]
);
});
// Now, use the previously added username authority to concede a username to an account.
PeopleZagros::execute_with(|| {
type PeopleRuntimeEvent = <PeopleZagros as Chain>::RuntimeEvent;
let full_username = [usr.to_owned(), ".suffix1".to_owned()].concat().into_bytes();
assert_ok!(<PeopleZagros as PeopleZagrosPallet>::Identity::set_username_for(
<PeopleZagros as Chain>::RuntimeOrigin::signed(people_zagros_alice.clone()),
people_zagros_runtime::MultiAddress::Id(people_zagros_bob.clone()),
full_username,
None,
true
));
assert_expected_events!(
PeopleZagros,
vec![
PeopleRuntimeEvent::Identity(pezpallet_identity::Event::UsernameQueued { .. }) => {},
]
);
});
// Accept the given username
PeopleZagros::execute_with(|| {
type PeopleRuntimeEvent = <PeopleZagros as Chain>::RuntimeEvent;
let full_username = [usr.to_owned(), ".suffix1".to_owned()].concat().into_bytes();
assert_ok!(<PeopleZagros as PeopleZagrosPallet>::Identity::accept_username(
<PeopleZagros as Chain>::RuntimeOrigin::signed(people_zagros_bob.clone()),
full_username.try_into().unwrap(),
));
assert_expected_events!(
PeopleZagros,
vec![
PeopleRuntimeEvent::Identity(pezpallet_identity::Event::UsernameSet { .. }) => {},
]
);
});
// Now, remove the username authority with another privileged XCM call.
Zagros::execute_with(|| {
type Runtime = <Zagros as Chain>::Runtime;
type RuntimeCall = <Zagros as Chain>::RuntimeCall;
type RuntimeEvent = <Zagros as Chain>::RuntimeEvent;
type PeopleCall = <PeopleZagros as Chain>::RuntimeCall;
type PeopleRuntime = <PeopleZagros as Chain>::Runtime;
Dmp::make_teyrchain_reachable(1004);
let remove_username_authority = PeopleCall::Identity(pezpallet_identity::Call::<
PeopleRuntime,
>::remove_username_authority {
authority: people_zagros_runtime::MultiAddress::Id(people_zagros_alice.clone()),
suffix: b"suffix1".into(),
});
let remove_authority_xcm_msg = RuntimeCall::XcmPallet(pezpallet_xcm::Call::<Runtime>::send {
dest: bx!(VersionedLocation::from(Location::new(0, [Teyrchain(1004)]))),
message: bx!(VersionedXcm::from(Xcm(vec![
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
Transact {
origin_kind,
call: remove_username_authority.encode().into(),
fallback_max_weight: None
}
]))),
});
assert_ok!(remove_authority_xcm_msg.dispatch(origin));
assert_expected_events!(
Zagros,
vec![
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::Sent { .. }) => {},
]
);
});
// Final event check.
PeopleZagros::execute_with(|| {
type RuntimeEvent = <PeopleZagros as Chain>::RuntimeEvent;
assert_expected_events!(
PeopleZagros,
vec![
RuntimeEvent::Identity(pezpallet_identity::Event::AuthorityRemoved { .. }) => {},
RuntimeEvent::MessageQueue(pezpallet_message_queue::Event::Processed { success: true, .. }) => {},
]
);
});
}
#[test]
fn relay_commands_add_remove_username_authority_wrong_origin() {
let people_zagros_alice = PeopleZagros::account_id_of(ALICE);
let origins = vec![
(
OriginKind::SovereignAccount,
<Zagros as Chain>::RuntimeOrigin::signed(people_zagros_alice.clone()),
),
(OriginKind::Xcm, GeneralAdminOrigin.into()),
];
for (origin_kind, origin) in origins {
Zagros::execute_with(|| {
type Runtime = <Zagros as Chain>::Runtime;
type RuntimeCall = <Zagros as Chain>::RuntimeCall;
type RuntimeEvent = <Zagros as Chain>::RuntimeEvent;
type PeopleCall = <PeopleZagros as Chain>::RuntimeCall;
type PeopleRuntime = <PeopleZagros as Chain>::Runtime;
Dmp::make_teyrchain_reachable(1004);
let add_username_authority = PeopleCall::Identity(pezpallet_identity::Call::<
PeopleRuntime,
>::add_username_authority {
authority: people_zagros_runtime::MultiAddress::Id(people_zagros_alice.clone()),
suffix: b"suffix1".into(),
allocation: 10,
});
let add_authority_xcm_msg = RuntimeCall::XcmPallet(pezpallet_xcm::Call::<Runtime>::send {
dest: bx!(VersionedLocation::from(Location::new(0, [Teyrchain(1004)]))),
message: bx!(VersionedXcm::from(Xcm(vec![
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
Transact {
origin_kind,
call: add_username_authority.encode().into(),
fallback_max_weight: None
}
]))),
});
assert_ok!(add_authority_xcm_msg.dispatch(origin.clone()));
assert_expected_events!(
Zagros,
vec![
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::Sent { .. }) => {},
]
);
});
// Check events system-teyrchain-side
PeopleZagros::execute_with(|| {
assert_expected_events!(PeopleZagros, vec![]);
});
Zagros::execute_with(|| {
type Runtime = <Zagros as Chain>::Runtime;
type RuntimeCall = <Zagros as Chain>::RuntimeCall;
type RuntimeEvent = <Zagros as Chain>::RuntimeEvent;
type PeopleCall = <PeopleZagros as Chain>::RuntimeCall;
type PeopleRuntime = <PeopleZagros as Chain>::Runtime;
let remove_username_authority = PeopleCall::Identity(pezpallet_identity::Call::<
PeopleRuntime,
>::remove_username_authority {
authority: people_zagros_runtime::MultiAddress::Id(people_zagros_alice.clone()),
suffix: b"suffix1".into(),
});
Dmp::make_teyrchain_reachable(1004);
let remove_authority_xcm_msg =
RuntimeCall::XcmPallet(pezpallet_xcm::Call::<Runtime>::send {
dest: bx!(VersionedLocation::from(Location::new(0, [Teyrchain(1004)]))),
message: bx!(VersionedXcm::from(Xcm(vec![
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
Transact {
origin_kind: OriginKind::SovereignAccount,
call: remove_username_authority.encode().into(),
fallback_max_weight: None,
}
]))),
});
assert_ok!(remove_authority_xcm_msg.dispatch(origin));
assert_expected_events!(
Zagros,
vec![
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::Sent { .. }) => {},
]
);
});
PeopleZagros::execute_with(|| {
assert_expected_events!(PeopleZagros, vec![]);
});
}
}
@@ -0,0 +1,108 @@
// 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 cross-chain identity operations.
use crate::imports::*;
use codec::Encode;
use emulated_integration_tests_common::accounts::ALICE;
use pezframe_support::BoundedVec;
use pezpallet_identity::Data;
use people_zagros_runtime::people::{IdentityField, IdentityInfo};
use xcm::latest::AssetTransferFilter;
#[test]
fn set_identity_cross_chain() {
type Identity = <PeopleZagros as PeopleZagrosPallet>::Identity;
let asset_hub_zagros_alice = AssetHubZagros::account_id_of(ALICE);
let people_zagros_alice = PeopleZagros::account_id_of(ALICE);
AssetHubZagros::fund_accounts(vec![(asset_hub_zagros_alice.clone(), ZAGROS_ED * 10000)]);
PeopleZagros::fund_accounts(vec![(people_zagros_alice.clone(), ZAGROS_ED * 10000)]);
PeopleZagros::execute_with(|| {
// No identity for Alice
assert!(!Identity::has_identity(&people_zagros_alice, IdentityField::Email as u64));
});
let destination = AssetHubZagros::sibling_location_of(PeopleZagros::para_id());
let total_fees: Asset = (Location::parent(), ZAGROS_ED * 1000).into();
let fees: Asset = (Location::parent(), ZAGROS_ED * 500).into();
AssetHubZagros::execute_with(|| {
type RuntimeEvent = <AssetHubZagros as Chain>::RuntimeEvent;
let identity_info = IdentityInfo {
email: Data::Raw(b"test@test.io".to_vec().try_into().unwrap()),
..Default::default()
};
// Set Alice identity on People from Alice on AH
let set_identity_call =
<PeopleZagros as Chain>::RuntimeCall::Identity(pezpallet_identity::Call::<
<PeopleZagros as Chain>::Runtime,
>::set_identity {
info: bx!(identity_info),
});
let xcm_message = Xcm::<()>(vec![
WithdrawAsset(total_fees.into()),
PayFees { asset: fees.clone() },
InitiateTransfer {
destination,
remote_fees: Some(AssetTransferFilter::Teleport(fees.clone().into())),
preserve_origin: true,
assets: BoundedVec::new(),
remote_xcm: Xcm(vec![
// try to alias into `Alice` account local to People chain
AliasOrigin(people_zagros_alice.clone().into()),
// set identity for the local Alice account
Transact {
origin_kind: OriginKind::SovereignAccount,
call: set_identity_call.encode().into(),
fallback_max_weight: None,
},
RefundSurplus,
DepositAsset {
assets: Wild(AllCounted(1)),
beneficiary: people_zagros_alice.clone().into(),
},
]),
},
ExpectTransactStatus(MaybeErrorCode::Success),
RefundSurplus,
DepositAsset {
assets: Wild(AllCounted(1)),
beneficiary: asset_hub_zagros_alice.clone().into(),
},
]);
let signed_origin =
<AssetHubZagros as Chain>::RuntimeOrigin::signed(asset_hub_zagros_alice);
assert_ok!(<AssetHubZagros as AssetHubZagrosPallet>::PezkuwiXcm::execute(
signed_origin,
bx!(xcm::VersionedXcm::from(xcm_message.into())),
Weight::MAX
));
assert_expected_events!(
AssetHubZagros,
vec![
RuntimeEvent::PezkuwiXcm(pezpallet_xcm::Event::Sent { .. }) => {},
]
);
});
PeopleZagros::execute_with(|| {
// Verify Alice on People now has identity
assert!(Identity::has_identity(&people_zagros_alice, IdentityField::Email as u64));
});
}
@@ -0,0 +1,20 @@
// 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.
mod aliases;
mod claim_assets;
mod governance;
mod identity;
mod teleport;
@@ -0,0 +1,73 @@
// 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::{
test_relay_is_trusted_teleporter, test_teyrchain_is_trusted_teleporter,
test_teyrchain_is_trusted_teleporter_for_relay,
};
#[test]
fn teleport_via_limited_teleport_assets_from_and_to_relay() {
let amount = ZAGROS_ED * 100;
test_relay_is_trusted_teleporter!(Zagros, vec![PeopleZagros], amount, limited_teleport_assets);
test_teyrchain_is_trusted_teleporter_for_relay!(
PeopleZagros,
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![PeopleZagros], amount, transfer_assets);
test_teyrchain_is_trusted_teleporter_for_relay!(PeopleZagros, Zagros, amount, transfer_assets);
}
#[test]
fn teleport_via_limited_teleport_assets_to_other_system_teyrchains_works() {
let amount = ZAGROS_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
PeopleZagros, // 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 = ZAGROS_ED * 100;
let native_asset: Assets = (Parent, amount).into();
let fee_asset_id: AssetId = Parent.into();
test_teyrchain_is_trusted_teleporter!(
PeopleZagros, // Origin
vec![AssetHubZagros], // Destinations
(native_asset, amount),
fee_asset_id,
transfer_assets
);
}