mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 21:01:02 +00:00
575b8f8d15
## Summary This pull request proposes a solution for improved control of the versioned XCM flow over the bridge (across different consensus chains) and resolves the situation where the sending chain/consensus has already migrated to a higher XCM version than the receiving chain/consensus. ## Problem/Motivation The current flow over the bridge involves a transfer from AssetHubRococo (AHR) to BridgeHubRococo (BHR) to BridgeHubWestend (BHW) and finally to AssetHubWestend (AHW), beginning with a reserve-backed transfer on AHR. In this process: 1. AHR sends XCM `ExportMessage` through `XcmpQueue`, incorporating XCM version checks using the `WrapVersion` feature, influenced by `pallet_xcm::SupportedVersion` (managed by `pallet_xcm::force_xcm_version` or version discovery). 2. BHR handles the `ExportMessage` instruction, utilizing the latest XCM version. The `HaulBlobExporter` converts the inner XCM to [`VersionedXcm::from`](https://github.com/paritytech/polkadot-sdk/blob/63ac2471aa0210f0ac9903bdd7d8f9351f9a635f/polkadot/xcm/xcm-builder/src/universal_exports.rs#L465-L467), also using the latest XCM version. However, challenges arise: - Incompatibility when BHW uses a different version than BHR. For instance, if BHR migrates to **XCMv4** while BHW remains on **XCMv3**, BHR's `VersionedXcm::from` uses `VersionedXcm::V4` variant, causing encoding issues for BHW. ``` /// Just a simulation of possible error, which could happen on BHW /// (this code is based on actual master without XCMv4) let encoded = hex_literal::hex!("0400"); println!("{:?}", VersionedXcm::<()>::decode(&mut &encoded[..])); Err(Error { cause: None, desc: "Could not decode `VersionedXcm`, variant doesn't exist" }) ``` - Similar compatibility issues exist between AHR and AHW. ## Solution This pull request introduces the following solutions: 1. **New trait `CheckVersion`** - added to the `xcm` module and exposing `pallet_xcm::SupportedVersion`. This enhancement allows checking the actual XCM version for desired destinations outside of the `pallet_xcm` module. 2. **Version Check in `HaulBlobExporter`** uses `CheckVersion` to check known/configured destination versions, ensuring compatibility. For example, in the scenario mentioned, BHR can store the version `3` for BHW. If BHR is on XCMv4, it will attempt to downgrade the message to version `3` instead of using the latest version `4`. 3. **Version Check in `pallet-xcm-bridge-hub-router`** - this check ensures compatibility with the real destination's XCM version, preventing the unnecessary sending of messages to the local bridge hub if versions are incompatible. These additions aim to improve the control and compatibility of XCM flows over the bridge and addressing issues related to version mismatches. ## Possible alternative solution _(More investigation is needed, and at the very least, it should extend to XCMv4/5. If this proves to be a viable option, I can open an RFC for XCM.)._ Add the `XcmVersion` attribute to the `ExportMessage` so that the sending chain can determine, based on what is stored in `pallet_xcm::SupportedVersion`, the version the destination is using. This way, we may not need to handle the version in `HaulBlobExporter`. ``` ExportMessage { network: NetworkId, destination: InteriorMultiLocation, xcm: Xcm<()> destination_xcm_version: Version, // <- new attritbute }, ``` ``` pub trait ExportXcm { fn validate( network: NetworkId, channel: u32, universal_source: &mut Option<InteriorMultiLocation>, destination: &mut Option<InteriorMultiLocation>, message: &mut Option<Xcm<()>>, destination_xcm_version: Version, , // <- new attritbute ) -> SendResult<Self::Ticket>; ``` ## Future Directions This PR does not fix version discovery over bridge, further investigation will be conducted here: https://github.com/paritytech/polkadot-sdk/issues/2417. ## TODO - [x] `pallet_xcm` mock for tests uses hard-coded XCM version `2` - change to 3 or lastest? - [x] fix `pallet-xcm-bridge-hub-router` - [x] fix HaulBlobExporter with version determination [here](https://github.com/paritytech/polkadot-sdk/blob/2183669d05f9b510f979a0cc3c7847707bacba2e/polkadot/xcm/xcm-builder/src/universal_exports.rs#L465) - [x] add unit-tests to the runtimes - [x] run benchmarks for `ExportMessage` - [x] extend local run scripts about `force_xcm_version(dest, version)` - [ ] when merged, prepare governance calls for Rococo/Westend - [ ] add PRDoc Part of: https://github.com/paritytech/parity-bridges-common/issues/2719 --------- Co-authored-by: command-bot <>
847 lines
26 KiB
Rust
847 lines
26 KiB
Rust
// This file is part of Cumulus.
|
|
|
|
// 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 Westmint (Westend Assets Hub) chain.
|
|
|
|
use asset_hub_westend_runtime::{
|
|
xcm_config::{
|
|
self, bridging, AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount,
|
|
ForeignCreatorsSovereignAccountOf, LocationToAccountId, TrustBackedAssetsPalletLocation,
|
|
WestendLocation, XcmConfig,
|
|
},
|
|
AllPalletsWithoutSystem, AssetDeposit, Assets, Balances, ExistentialDeposit, ForeignAssets,
|
|
ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem,
|
|
PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys,
|
|
ToRococoXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue,
|
|
};
|
|
use asset_test_utils::{
|
|
test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys, ExtBuilder,
|
|
};
|
|
use codec::{Decode, Encode};
|
|
use cumulus_primitives_utility::ChargeWeightInFungibles;
|
|
use frame_support::{
|
|
assert_noop, assert_ok,
|
|
traits::fungibles::InspectEnumerable,
|
|
weights::{Weight, WeightToFee as WeightToFeeT},
|
|
};
|
|
use parachains_common::{
|
|
westend::fee::WeightToFee, AccountId, AssetIdForTrustBackedAssets, AuraId, Balance,
|
|
};
|
|
use sp_runtime::traits::MaybeEquivalence;
|
|
use std::convert::Into;
|
|
use xcm::latest::prelude::*;
|
|
use xcm_executor::traits::{Identity, JustTry, WeightTrader};
|
|
|
|
const ALICE: [u8; 32] = [1u8; 32];
|
|
const SOME_ASSET_ADMIN: [u8; 32] = [5u8; 32];
|
|
|
|
type AssetIdForTrustBackedAssetsConvert =
|
|
assets_common::AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocation>;
|
|
|
|
type RuntimeHelper = asset_test_utils::RuntimeHelper<Runtime, AllPalletsWithoutSystem>;
|
|
|
|
fn collator_session_key(account: [u8; 32]) -> CollatorSessionKey<Runtime> {
|
|
CollatorSessionKey::new(
|
|
AccountId::from(account),
|
|
AccountId::from(account),
|
|
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(account)) },
|
|
)
|
|
}
|
|
|
|
fn collator_session_keys() -> CollatorSessionKeys<Runtime> {
|
|
CollatorSessionKeys::default().add(collator_session_key(ALICE))
|
|
}
|
|
|
|
#[test]
|
|
fn test_asset_xcm_trader() {
|
|
ExtBuilder::<Runtime>::default()
|
|
.with_collators(vec![AccountId::from(ALICE)])
|
|
.with_session_keys(vec![(
|
|
AccountId::from(ALICE),
|
|
AccountId::from(ALICE),
|
|
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) },
|
|
)])
|
|
.build()
|
|
.execute_with(|| {
|
|
// We need root origin to create a sufficient asset
|
|
let minimum_asset_balance = 3333333_u128;
|
|
let local_asset_id = 1;
|
|
assert_ok!(Assets::force_create(
|
|
RuntimeHelper::root_origin(),
|
|
local_asset_id.into(),
|
|
AccountId::from(ALICE).into(),
|
|
true,
|
|
minimum_asset_balance
|
|
));
|
|
|
|
// We first mint enough asset for the account to exist for assets
|
|
assert_ok!(Assets::mint(
|
|
RuntimeHelper::origin_of(AccountId::from(ALICE)),
|
|
local_asset_id.into(),
|
|
AccountId::from(ALICE).into(),
|
|
minimum_asset_balance
|
|
));
|
|
|
|
// get asset id as multilocation
|
|
let asset_multilocation =
|
|
AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap();
|
|
|
|
// Set Alice as block author, who will receive fees
|
|
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
|
|
|
// We are going to buy 4e9 weight
|
|
let bought = Weight::from_parts(4_000_000_000u64, 0);
|
|
|
|
// Lets calculate amount needed
|
|
let asset_amount_needed =
|
|
AssetFeeAsExistentialDepositMultiplierFeeCharger::charge_weight_in_fungibles(
|
|
local_asset_id,
|
|
bought,
|
|
)
|
|
.expect("failed to compute");
|
|
|
|
// Lets pay with: asset_amount_needed + asset_amount_extra
|
|
let asset_amount_extra = 100_u128;
|
|
let asset: MultiAsset =
|
|
(asset_multilocation, asset_amount_needed + asset_amount_extra).into();
|
|
|
|
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
|
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
|
|
|
// Lets buy_weight and make sure buy_weight does not return an error
|
|
let unused_assets = trader.buy_weight(bought, asset.into(), &ctx).expect("Expected Ok");
|
|
// Check whether a correct amount of unused assets is returned
|
|
assert_ok!(
|
|
unused_assets.ensure_contains(&(asset_multilocation, asset_amount_extra).into())
|
|
);
|
|
|
|
// Drop trader
|
|
drop(trader);
|
|
|
|
// Make sure author(Alice) has received the amount
|
|
assert_eq!(
|
|
Assets::balance(local_asset_id, AccountId::from(ALICE)),
|
|
minimum_asset_balance + asset_amount_needed
|
|
);
|
|
|
|
// We also need to ensure the total supply increased
|
|
assert_eq!(
|
|
Assets::total_supply(local_asset_id),
|
|
minimum_asset_balance + asset_amount_needed
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_asset_xcm_trader_with_refund() {
|
|
ExtBuilder::<Runtime>::default()
|
|
.with_collators(vec![AccountId::from(ALICE)])
|
|
.with_session_keys(vec![(
|
|
AccountId::from(ALICE),
|
|
AccountId::from(ALICE),
|
|
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) },
|
|
)])
|
|
.build()
|
|
.execute_with(|| {
|
|
// We need root origin to create a sufficient asset
|
|
// We set existential deposit to be identical to the one for Balances first
|
|
assert_ok!(Assets::force_create(
|
|
RuntimeHelper::root_origin(),
|
|
1.into(),
|
|
AccountId::from(ALICE).into(),
|
|
true,
|
|
ExistentialDeposit::get()
|
|
));
|
|
|
|
// We first mint enough asset for the account to exist for assets
|
|
assert_ok!(Assets::mint(
|
|
RuntimeHelper::origin_of(AccountId::from(ALICE)),
|
|
1.into(),
|
|
AccountId::from(ALICE).into(),
|
|
ExistentialDeposit::get()
|
|
));
|
|
|
|
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
|
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
|
|
|
// Set Alice as block author, who will receive fees
|
|
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
|
|
|
// We are going to buy 4e9 weight
|
|
let bought = Weight::from_parts(4_000_000_000u64, 0);
|
|
let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap();
|
|
|
|
// lets calculate amount needed
|
|
let amount_bought = WeightToFee::weight_to_fee(&bought);
|
|
|
|
let asset: MultiAsset = (asset_multilocation, amount_bought).into();
|
|
|
|
// Make sure buy_weight does not return an error
|
|
assert_ok!(trader.buy_weight(bought, asset.clone().into(), &ctx));
|
|
|
|
// Make sure again buy_weight does return an error
|
|
// This assert relies on the fact, that we use `TakeFirstAssetTrader` in `WeightTrader`
|
|
// tuple chain, which cannot be called twice
|
|
assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive);
|
|
|
|
// We actually use half of the weight
|
|
let weight_used = bought / 2;
|
|
|
|
// Make sure refurnd works.
|
|
let amount_refunded = WeightToFee::weight_to_fee(&(bought - weight_used));
|
|
|
|
assert_eq!(
|
|
trader.refund_weight(bought - weight_used, &ctx),
|
|
Some((asset_multilocation, amount_refunded).into())
|
|
);
|
|
|
|
// Drop trader
|
|
drop(trader);
|
|
|
|
// We only should have paid for half of the bought weight
|
|
let fees_paid = WeightToFee::weight_to_fee(&weight_used);
|
|
|
|
assert_eq!(
|
|
Assets::balance(1, AccountId::from(ALICE)),
|
|
ExistentialDeposit::get() + fees_paid
|
|
);
|
|
|
|
// We also need to ensure the total supply increased
|
|
assert_eq!(Assets::total_supply(1), ExistentialDeposit::get() + fees_paid);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_asset_xcm_trader_refund_not_possible_since_amount_less_than_ed() {
|
|
ExtBuilder::<Runtime>::default()
|
|
.with_collators(vec![AccountId::from(ALICE)])
|
|
.with_session_keys(vec![(
|
|
AccountId::from(ALICE),
|
|
AccountId::from(ALICE),
|
|
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) },
|
|
)])
|
|
.build()
|
|
.execute_with(|| {
|
|
// We need root origin to create a sufficient asset
|
|
// We set existential deposit to be identical to the one for Balances first
|
|
assert_ok!(Assets::force_create(
|
|
RuntimeHelper::root_origin(),
|
|
1.into(),
|
|
AccountId::from(ALICE).into(),
|
|
true,
|
|
ExistentialDeposit::get()
|
|
));
|
|
|
|
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
|
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
|
|
|
// Set Alice as block author, who will receive fees
|
|
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
|
|
|
// We are going to buy small amount
|
|
let bought = Weight::from_parts(500_000_000u64, 0);
|
|
|
|
let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap();
|
|
|
|
let amount_bought = WeightToFee::weight_to_fee(&bought);
|
|
|
|
assert!(
|
|
amount_bought < ExistentialDeposit::get(),
|
|
"we are testing what happens when the amount does not exceed ED"
|
|
);
|
|
|
|
let asset: MultiAsset = (asset_multilocation, amount_bought).into();
|
|
|
|
// Buy weight should return an error
|
|
assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive);
|
|
|
|
// not credited since the ED is higher than this value
|
|
assert_eq!(Assets::balance(1, AccountId::from(ALICE)), 0);
|
|
|
|
// We also need to ensure the total supply did not increase
|
|
assert_eq!(Assets::total_supply(1), 0);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_that_buying_ed_refund_does_not_refund() {
|
|
ExtBuilder::<Runtime>::default()
|
|
.with_collators(vec![AccountId::from(ALICE)])
|
|
.with_session_keys(vec![(
|
|
AccountId::from(ALICE),
|
|
AccountId::from(ALICE),
|
|
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) },
|
|
)])
|
|
.build()
|
|
.execute_with(|| {
|
|
// We need root origin to create a sufficient asset
|
|
// We set existential deposit to be identical to the one for Balances first
|
|
assert_ok!(Assets::force_create(
|
|
RuntimeHelper::root_origin(),
|
|
1.into(),
|
|
AccountId::from(ALICE).into(),
|
|
true,
|
|
ExistentialDeposit::get()
|
|
));
|
|
|
|
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
|
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
|
|
|
// Set Alice as block author, who will receive fees
|
|
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
|
|
|
let bought = Weight::from_parts(500_000_000u64, 0);
|
|
|
|
let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap();
|
|
|
|
let amount_bought = WeightToFee::weight_to_fee(&bought);
|
|
|
|
assert!(
|
|
amount_bought < ExistentialDeposit::get(),
|
|
"we are testing what happens when the amount does not exceed ED"
|
|
);
|
|
|
|
// We know we will have to buy at least ED, so lets make sure first it will
|
|
// fail with a payment of less than ED
|
|
let asset: MultiAsset = (asset_multilocation, amount_bought).into();
|
|
assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive);
|
|
|
|
// Now lets buy ED at least
|
|
let asset: MultiAsset = (asset_multilocation, ExistentialDeposit::get()).into();
|
|
|
|
// Buy weight should work
|
|
assert_ok!(trader.buy_weight(bought, asset.into(), &ctx));
|
|
|
|
// Should return None. We have a specific check making sure we dont go below ED for
|
|
// drop payment
|
|
assert_eq!(trader.refund_weight(bought, &ctx), None);
|
|
|
|
// Drop trader
|
|
drop(trader);
|
|
|
|
// Make sure author(Alice) has received the amount
|
|
assert_eq!(Assets::balance(1, AccountId::from(ALICE)), ExistentialDeposit::get());
|
|
|
|
// We also need to ensure the total supply increased
|
|
assert_eq!(Assets::total_supply(1), ExistentialDeposit::get());
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_asset_xcm_trader_not_possible_for_non_sufficient_assets() {
|
|
ExtBuilder::<Runtime>::default()
|
|
.with_collators(vec![AccountId::from(ALICE)])
|
|
.with_session_keys(vec![(
|
|
AccountId::from(ALICE),
|
|
AccountId::from(ALICE),
|
|
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) },
|
|
)])
|
|
.build()
|
|
.execute_with(|| {
|
|
// Create a non-sufficient asset with specific existential deposit
|
|
let minimum_asset_balance = 1_000_000_u128;
|
|
assert_ok!(Assets::force_create(
|
|
RuntimeHelper::root_origin(),
|
|
1.into(),
|
|
AccountId::from(ALICE).into(),
|
|
false,
|
|
minimum_asset_balance
|
|
));
|
|
|
|
// We first mint enough asset for the account to exist for assets
|
|
assert_ok!(Assets::mint(
|
|
RuntimeHelper::origin_of(AccountId::from(ALICE)),
|
|
1.into(),
|
|
AccountId::from(ALICE).into(),
|
|
minimum_asset_balance
|
|
));
|
|
|
|
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
|
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
|
|
|
// Set Alice as block author, who will receive fees
|
|
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
|
|
|
// We are going to buy 4e9 weight
|
|
let bought = Weight::from_parts(4_000_000_000u64, 0);
|
|
|
|
// lets calculate amount needed
|
|
let asset_amount_needed = WeightToFee::weight_to_fee(&bought);
|
|
|
|
let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap();
|
|
|
|
let asset: MultiAsset = (asset_multilocation, asset_amount_needed).into();
|
|
|
|
// Make sure again buy_weight does return an error
|
|
assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive);
|
|
|
|
// Drop trader
|
|
drop(trader);
|
|
|
|
// Make sure author(Alice) has NOT received the amount
|
|
assert_eq!(Assets::balance(1, AccountId::from(ALICE)), minimum_asset_balance);
|
|
|
|
// We also need to ensure the total supply NOT increased
|
|
assert_eq!(Assets::total_supply(1), minimum_asset_balance);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_assets_balances_api_works() {
|
|
use assets_common::runtime_api::runtime_decl_for_fungibles_api::FungiblesApi;
|
|
|
|
ExtBuilder::<Runtime>::default()
|
|
.with_collators(vec![AccountId::from(ALICE)])
|
|
.with_session_keys(vec![(
|
|
AccountId::from(ALICE),
|
|
AccountId::from(ALICE),
|
|
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) },
|
|
)])
|
|
.build()
|
|
.execute_with(|| {
|
|
let local_asset_id = 1;
|
|
let foreign_asset_id_multilocation =
|
|
MultiLocation { parents: 1, interior: X2(Parachain(1234), GeneralIndex(12345)) };
|
|
|
|
// check before
|
|
assert_eq!(Assets::balance(local_asset_id, AccountId::from(ALICE)), 0);
|
|
assert_eq!(
|
|
ForeignAssets::balance(foreign_asset_id_multilocation, AccountId::from(ALICE)),
|
|
0
|
|
);
|
|
assert_eq!(Balances::free_balance(AccountId::from(ALICE)), 0);
|
|
assert!(Runtime::query_account_balances(AccountId::from(ALICE))
|
|
.unwrap()
|
|
.try_as::<MultiAssets>()
|
|
.unwrap()
|
|
.is_none());
|
|
|
|
// Drip some balance
|
|
use frame_support::traits::fungible::Mutate;
|
|
let some_currency = ExistentialDeposit::get();
|
|
Balances::mint_into(&AccountId::from(ALICE), some_currency).unwrap();
|
|
|
|
// We need root origin to create a sufficient asset
|
|
let minimum_asset_balance = 3333333_u128;
|
|
assert_ok!(Assets::force_create(
|
|
RuntimeHelper::root_origin(),
|
|
local_asset_id.into(),
|
|
AccountId::from(ALICE).into(),
|
|
true,
|
|
minimum_asset_balance
|
|
));
|
|
|
|
// We first mint enough asset for the account to exist for assets
|
|
assert_ok!(Assets::mint(
|
|
RuntimeHelper::origin_of(AccountId::from(ALICE)),
|
|
local_asset_id.into(),
|
|
AccountId::from(ALICE).into(),
|
|
minimum_asset_balance
|
|
));
|
|
|
|
// create foreign asset
|
|
let foreign_asset_minimum_asset_balance = 3333333_u128;
|
|
assert_ok!(ForeignAssets::force_create(
|
|
RuntimeHelper::root_origin(),
|
|
foreign_asset_id_multilocation,
|
|
AccountId::from(SOME_ASSET_ADMIN).into(),
|
|
false,
|
|
foreign_asset_minimum_asset_balance
|
|
));
|
|
|
|
// We first mint enough asset for the account to exist for assets
|
|
assert_ok!(ForeignAssets::mint(
|
|
RuntimeHelper::origin_of(AccountId::from(SOME_ASSET_ADMIN)),
|
|
foreign_asset_id_multilocation,
|
|
AccountId::from(ALICE).into(),
|
|
6 * foreign_asset_minimum_asset_balance
|
|
));
|
|
|
|
// check after
|
|
assert_eq!(
|
|
Assets::balance(local_asset_id, AccountId::from(ALICE)),
|
|
minimum_asset_balance
|
|
);
|
|
assert_eq!(
|
|
ForeignAssets::balance(foreign_asset_id_multilocation, AccountId::from(ALICE)),
|
|
6 * minimum_asset_balance
|
|
);
|
|
assert_eq!(Balances::free_balance(AccountId::from(ALICE)), some_currency);
|
|
|
|
let result: MultiAssets = Runtime::query_account_balances(AccountId::from(ALICE))
|
|
.unwrap()
|
|
.try_into()
|
|
.unwrap();
|
|
assert_eq!(result.len(), 3);
|
|
|
|
// check currency
|
|
assert!(result.inner().iter().any(|asset| asset.eq(
|
|
&assets_common::fungible_conversion::convert_balance::<WestendLocation, Balance>(
|
|
some_currency
|
|
)
|
|
.unwrap()
|
|
)));
|
|
// check trusted asset
|
|
assert!(result.inner().iter().any(|asset| asset.eq(&(
|
|
AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap(),
|
|
minimum_asset_balance
|
|
)
|
|
.into())));
|
|
// check foreign asset
|
|
assert!(result.inner().iter().any(|asset| asset.eq(&(
|
|
Identity::convert_back(&foreign_asset_id_multilocation).unwrap(),
|
|
6 * foreign_asset_minimum_asset_balance
|
|
)
|
|
.into())));
|
|
});
|
|
}
|
|
|
|
asset_test_utils::include_teleports_for_native_asset_works!(
|
|
Runtime,
|
|
AllPalletsWithoutSystem,
|
|
XcmConfig,
|
|
CheckingAccount,
|
|
WeightToFee,
|
|
ParachainSystem,
|
|
collator_session_keys(),
|
|
ExistentialDeposit::get(),
|
|
Box::new(|runtime_event_encoded: Vec<u8>| {
|
|
match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) {
|
|
Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event),
|
|
_ => None,
|
|
}
|
|
}),
|
|
1000
|
|
);
|
|
|
|
asset_test_utils::include_teleports_for_foreign_assets_works!(
|
|
Runtime,
|
|
AllPalletsWithoutSystem,
|
|
XcmConfig,
|
|
CheckingAccount,
|
|
WeightToFee,
|
|
ParachainSystem,
|
|
ForeignCreatorsSovereignAccountOf,
|
|
ForeignAssetsInstance,
|
|
collator_session_keys(),
|
|
ExistentialDeposit::get(),
|
|
Box::new(|runtime_event_encoded: Vec<u8>| {
|
|
match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) {
|
|
Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event),
|
|
_ => None,
|
|
}
|
|
}),
|
|
Box::new(|runtime_event_encoded: Vec<u8>| {
|
|
match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) {
|
|
Ok(RuntimeEvent::XcmpQueue(event)) => Some(event),
|
|
_ => None,
|
|
}
|
|
})
|
|
);
|
|
|
|
asset_test_utils::include_asset_transactor_transfer_with_local_consensus_currency_works!(
|
|
Runtime,
|
|
XcmConfig,
|
|
collator_session_keys(),
|
|
ExistentialDeposit::get(),
|
|
Box::new(|| {
|
|
assert!(Assets::asset_ids().collect::<Vec<_>>().is_empty());
|
|
assert!(ForeignAssets::asset_ids().collect::<Vec<_>>().is_empty());
|
|
}),
|
|
Box::new(|| {
|
|
assert!(Assets::asset_ids().collect::<Vec<_>>().is_empty());
|
|
assert!(ForeignAssets::asset_ids().collect::<Vec<_>>().is_empty());
|
|
})
|
|
);
|
|
|
|
asset_test_utils::include_asset_transactor_transfer_with_pallet_assets_instance_works!(
|
|
asset_transactor_transfer_with_trust_backed_assets_works,
|
|
Runtime,
|
|
XcmConfig,
|
|
TrustBackedAssetsInstance,
|
|
AssetIdForTrustBackedAssets,
|
|
AssetIdForTrustBackedAssetsConvert,
|
|
collator_session_keys(),
|
|
ExistentialDeposit::get(),
|
|
12345,
|
|
Box::new(|| {
|
|
assert!(ForeignAssets::asset_ids().collect::<Vec<_>>().is_empty());
|
|
}),
|
|
Box::new(|| {
|
|
assert!(ForeignAssets::asset_ids().collect::<Vec<_>>().is_empty());
|
|
})
|
|
);
|
|
|
|
asset_test_utils::include_asset_transactor_transfer_with_pallet_assets_instance_works!(
|
|
asset_transactor_transfer_with_foreign_assets_works,
|
|
Runtime,
|
|
XcmConfig,
|
|
ForeignAssetsInstance,
|
|
MultiLocation,
|
|
JustTry,
|
|
collator_session_keys(),
|
|
ExistentialDeposit::get(),
|
|
MultiLocation { parents: 1, interior: X2(Parachain(1313), GeneralIndex(12345)) },
|
|
Box::new(|| {
|
|
assert!(Assets::asset_ids().collect::<Vec<_>>().is_empty());
|
|
}),
|
|
Box::new(|| {
|
|
assert!(Assets::asset_ids().collect::<Vec<_>>().is_empty());
|
|
})
|
|
);
|
|
|
|
asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_parachain_assets_works!(
|
|
Runtime,
|
|
XcmConfig,
|
|
WeightToFee,
|
|
ForeignCreatorsSovereignAccountOf,
|
|
ForeignAssetsInstance,
|
|
MultiLocation,
|
|
JustTry,
|
|
collator_session_keys(),
|
|
ExistentialDeposit::get(),
|
|
AssetDeposit::get(),
|
|
MetadataDepositBase::get(),
|
|
MetadataDepositPerByte::get(),
|
|
Box::new(|pallet_asset_call| RuntimeCall::ForeignAssets(pallet_asset_call).encode()),
|
|
Box::new(|runtime_event_encoded: Vec<u8>| {
|
|
match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) {
|
|
Ok(RuntimeEvent::ForeignAssets(pallet_asset_event)) => Some(pallet_asset_event),
|
|
_ => None,
|
|
}
|
|
}),
|
|
Box::new(|| {
|
|
assert!(Assets::asset_ids().collect::<Vec<_>>().is_empty());
|
|
assert!(ForeignAssets::asset_ids().collect::<Vec<_>>().is_empty());
|
|
}),
|
|
Box::new(|| {
|
|
assert!(Assets::asset_ids().collect::<Vec<_>>().is_empty());
|
|
assert_eq!(ForeignAssets::asset_ids().collect::<Vec<_>>().len(), 1);
|
|
})
|
|
);
|
|
|
|
fn bridging_to_asset_hub_rococo() -> TestBridgingConfig {
|
|
let _ = PolkadotXcm::force_xcm_version(
|
|
RuntimeOrigin::root(),
|
|
Box::new(bridging::to_rococo::AssetHubRococo::get()),
|
|
XCM_VERSION,
|
|
)
|
|
.expect("version saved!");
|
|
TestBridgingConfig {
|
|
bridged_network: bridging::to_rococo::RococoNetwork::get(),
|
|
local_bridge_hub_para_id: bridging::SiblingBridgeHubParaId::get(),
|
|
local_bridge_hub_location: bridging::SiblingBridgeHub::get(),
|
|
bridged_target_location: bridging::to_rococo::AssetHubRococo::get(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn limited_reserve_transfer_assets_for_native_asset_to_asset_hub_rococo_works() {
|
|
asset_test_utils::test_cases_over_bridge::limited_reserve_transfer_assets_for_native_asset_works::<
|
|
Runtime,
|
|
AllPalletsWithoutSystem,
|
|
XcmConfig,
|
|
ParachainSystem,
|
|
XcmpQueue,
|
|
LocationToAccountId,
|
|
>(
|
|
collator_session_keys(),
|
|
ExistentialDeposit::get(),
|
|
AccountId::from(ALICE),
|
|
Box::new(|runtime_event_encoded: Vec<u8>| {
|
|
match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) {
|
|
Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event),
|
|
_ => None,
|
|
}
|
|
}),
|
|
Box::new(|runtime_event_encoded: Vec<u8>| {
|
|
match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) {
|
|
Ok(RuntimeEvent::XcmpQueue(event)) => Some(event),
|
|
_ => None,
|
|
}
|
|
}),
|
|
bridging_to_asset_hub_rococo,
|
|
WeightLimit::Unlimited,
|
|
Some(xcm_config::bridging::XcmBridgeHubRouterFeeAssetId::get()),
|
|
Some(xcm_config::TreasuryAccount::get()),
|
|
)
|
|
}
|
|
#[test]
|
|
fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_works() {
|
|
const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32];
|
|
asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::<
|
|
Runtime,
|
|
AllPalletsWithoutSystem,
|
|
XcmConfig,
|
|
LocationToAccountId,
|
|
ForeignAssetsInstance,
|
|
>(
|
|
collator_session_keys().add(collator_session_key(BLOCK_AUTHOR_ACCOUNT)),
|
|
ExistentialDeposit::get(),
|
|
AccountId::from([73; 32]),
|
|
AccountId::from(BLOCK_AUTHOR_ACCOUNT),
|
|
// receiving ROCs
|
|
(MultiLocation { parents: 2, interior: X1(GlobalConsensus(Rococo)) }, 1000000000000, 1_000_000_000),
|
|
bridging_to_asset_hub_rococo,
|
|
(
|
|
X1(PalletInstance(bp_bridge_hub_westend::WITH_BRIDGE_WESTEND_TO_ROCOCO_MESSAGES_PALLET_INDEX)),
|
|
GlobalConsensus(Rococo),
|
|
X1(Parachain(1000))
|
|
)
|
|
)
|
|
}
|
|
#[test]
|
|
fn report_bridge_status_from_xcm_bridge_router_for_rococo_works() {
|
|
asset_test_utils::test_cases_over_bridge::report_bridge_status_from_xcm_bridge_router_works::<
|
|
Runtime,
|
|
AllPalletsWithoutSystem,
|
|
XcmConfig,
|
|
LocationToAccountId,
|
|
ToRococoXcmRouterInstance,
|
|
>(
|
|
collator_session_keys(),
|
|
bridging_to_asset_hub_rococo,
|
|
|| {
|
|
sp_std::vec![
|
|
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
|
|
Transact {
|
|
origin_kind: OriginKind::Xcm,
|
|
require_weight_at_most:
|
|
bp_asset_hub_westend::XcmBridgeHubRouterTransactCallMaxWeight::get(),
|
|
call: bp_asset_hub_westend::Call::ToRococoXcmRouter(
|
|
bp_asset_hub_westend::XcmBridgeHubRouterCall::report_bridge_status {
|
|
bridge_id: Default::default(),
|
|
is_congested: true,
|
|
}
|
|
)
|
|
.encode()
|
|
.into(),
|
|
}
|
|
]
|
|
.into()
|
|
},
|
|
|| {
|
|
sp_std::vec![
|
|
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
|
|
Transact {
|
|
origin_kind: OriginKind::Xcm,
|
|
require_weight_at_most:
|
|
bp_asset_hub_westend::XcmBridgeHubRouterTransactCallMaxWeight::get(),
|
|
call: bp_asset_hub_westend::Call::ToRococoXcmRouter(
|
|
bp_asset_hub_westend::XcmBridgeHubRouterCall::report_bridge_status {
|
|
bridge_id: Default::default(),
|
|
is_congested: false,
|
|
}
|
|
)
|
|
.encode()
|
|
.into(),
|
|
}
|
|
]
|
|
.into()
|
|
},
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_report_bridge_status_call_compatibility() {
|
|
// if this test fails, make sure `bp_asset_hub_rococo` has valid encoding
|
|
assert_eq!(
|
|
RuntimeCall::ToRococoXcmRouter(pallet_xcm_bridge_hub_router::Call::report_bridge_status {
|
|
bridge_id: Default::default(),
|
|
is_congested: true,
|
|
})
|
|
.encode(),
|
|
bp_asset_hub_westend::Call::ToRococoXcmRouter(
|
|
bp_asset_hub_westend::XcmBridgeHubRouterCall::report_bridge_status {
|
|
bridge_id: Default::default(),
|
|
is_congested: true,
|
|
}
|
|
)
|
|
.encode()
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn check_sane_weight_report_bridge_status() {
|
|
use pallet_xcm_bridge_hub_router::WeightInfo;
|
|
let actual = <Runtime as pallet_xcm_bridge_hub_router::Config<
|
|
ToRococoXcmRouterInstance,
|
|
>>::WeightInfo::report_bridge_status();
|
|
let max_weight = bp_asset_hub_westend::XcmBridgeHubRouterTransactCallMaxWeight::get();
|
|
assert!(
|
|
actual.all_lte(max_weight),
|
|
"max_weight: {:?} should be adjusted to actual {:?}",
|
|
max_weight,
|
|
actual
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn change_xcm_bridge_hub_router_byte_fee_by_governance_works() {
|
|
asset_test_utils::test_cases::change_storage_constant_by_governance_works::<
|
|
Runtime,
|
|
bridging::XcmBridgeHubRouterByteFee,
|
|
Balance,
|
|
>(
|
|
collator_session_keys(),
|
|
1000,
|
|
Box::new(|call| RuntimeCall::System(call).encode()),
|
|
|| {
|
|
(
|
|
bridging::XcmBridgeHubRouterByteFee::key().to_vec(),
|
|
bridging::XcmBridgeHubRouterByteFee::get(),
|
|
)
|
|
},
|
|
|old_value| {
|
|
if let Some(new_value) = old_value.checked_add(1) {
|
|
new_value
|
|
} else {
|
|
old_value.checked_sub(1).unwrap()
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn reserve_transfer_native_asset_to_non_teleport_para_works() {
|
|
asset_test_utils::test_cases::reserve_transfer_native_asset_to_non_teleport_para_works::<
|
|
Runtime,
|
|
AllPalletsWithoutSystem,
|
|
XcmConfig,
|
|
ParachainSystem,
|
|
XcmpQueue,
|
|
LocationToAccountId,
|
|
>(
|
|
collator_session_keys(),
|
|
ExistentialDeposit::get(),
|
|
AccountId::from(ALICE),
|
|
Box::new(|runtime_event_encoded: Vec<u8>| {
|
|
match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) {
|
|
Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event),
|
|
_ => None,
|
|
}
|
|
}),
|
|
Box::new(|runtime_event_encoded: Vec<u8>| {
|
|
match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) {
|
|
Ok(RuntimeEvent::XcmpQueue(event)) => Some(event),
|
|
_ => None,
|
|
}
|
|
}),
|
|
WeightLimit::Unlimited,
|
|
);
|
|
}
|