fix: Complete snowbridge pezpallet rebrand and critical bug fixes
- snowbridge-pezpallet-* → pezsnowbridge-pezpallet-* (201 refs) - pallet/ directories → pezpallet/ (4 locations) - Fixed pezpallet.rs self-include recursion bug - Fixed sc-chain-spec hardcoded crate name in derive macro - Reverted .pezpallet_by_name() to .pallet_by_name() (subxt API) - Added BizinikiwiConfig type alias for zombienet tests - Deleted obsolete session state files Verified: pezsnowbridge-pezpallet-*, pezpallet-staking, pezpallet-staking-async, pezframe-benchmarking-cli all pass cargo check
This commit is contained in:
+89
@@ -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_pez_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,
|
||||
},
|
||||
pezbridge_hub_pezkuwichain_emulated_chain::{
|
||||
genesis::ED as BRIDGE_HUB_PEZKUWICHAIN_ED, BridgeHubPezkuwichainExistentialDeposit,
|
||||
BridgeHubPezkuwichainParaPallet as BridgeHubPezkuwichainPallet,
|
||||
},
|
||||
pez_penpal_emulated_chain::{
|
||||
pez_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;
|
||||
+617
@@ -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(
|
||||
pezcumulus_pezpallet_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()
|
||||
);
|
||||
}
|
||||
+34
@@ -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
|
||||
);
|
||||
}
|
||||
+303
@@ -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(
|
||||
pezcumulus_pezpallet_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
|
||||
}
|
||||
+109
@@ -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 pezpallet.
|
||||
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));
|
||||
});
|
||||
}
|
||||
+155
@@ -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, .. }
|
||||
) => {},
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
+84
@@ -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
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user