// 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. //! Module contains predefined test-case scenarios for `Runtime` with various assets. use super::xcm_helpers; use crate::{assert_matches_reserve_asset_deposited_instructions, get_fungible_delivery_fees}; use assets_common::local_and_foreign_assets::ForeignAssetReserveData; use codec::Encode; use core::ops::Mul; use pezcumulus_primitives_core::{UpwardMessageSender, XcmpMessageSource}; use pezframe_support::{ assert_noop, assert_ok, traits::{ fungible::Mutate, fungibles::InspectEnumerable, Currency, Get, OnFinalize, OnInitialize, OriginTrait, }, weights::Weight, }; use pezframe_system::pezpallet_prelude::BlockNumberFor; use pezsp_runtime::{ traits::{Block as BlockT, MaybeEquivalence, StaticLookup, Zero}, DispatchError, SaturatedConversion, Saturating, }; use teyrchains_common::{AccountId, Balance}; use teyrchains_runtimes_test_utils::{ assert_metadata, assert_total, mock_open_hrmp_channel, AccountIdOf, BalanceOf, CollatorSessionKeys, ExtBuilder, SlotDurations, ValidatorIdOf, XcmReceivedFrom, }; use xcm::{latest::prelude::*, VersionedAssetId, VersionedAssets, VersionedXcm}; use xcm_executor::{ traits::{ConvertLocation, TransferType}, XcmExecutor, }; use xcm_runtime_apis::fees::{ runtime_decl_for_xcm_payment_api::XcmPaymentApiV2, Error as XcmPaymentApiError, }; type RuntimeHelper = teyrchains_runtimes_test_utils::RuntimeHelper; // Re-export test_case from `teyrchains-runtimes-test-utils` pub use teyrchains_runtimes_test_utils::test_cases::change_storage_constant_by_governance_works; /// Test-case makes sure that `Runtime` can receive native asset from relay chain and can teleport /// it back pub fn teleports_for_native_asset_works< Runtime, AllPalletsWithoutSystem, XcmConfig, CheckingAccount, WeightToFee, HrmpChannelOpener, >( collator_session_keys: CollatorSessionKeys, slot_durations: SlotDurations, existential_deposit: BalanceOf, target_account: AccountIdOf, unwrap_pallet_xcm_event: Box) -> Option>>, runtime_para_id: u32, ) where Runtime: pezframe_system::Config + pezpallet_balances::Config + pezpallet_session::Config + pezpallet_xcm::Config + teyrchain_info::Config + pezpallet_collator_selection::Config + pezcumulus_pallet_teyrchain_system::Config + pezcumulus_pallet_xcmp_queue::Config + pezpallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]>, ValidatorIdOf: From>, BalanceOf: From + Into, WeightToFee: pezframe_support::weights::WeightToFee, ::Balance: From + Into, ::AccountId: Into<<::RuntimeOrigin as OriginTrait>::AccountId>, <::Lookup as StaticLookup>::Source: From<::AccountId>, ::AccountId: From, XcmConfig: xcm_executor::Config, CheckingAccount: Get>>, HrmpChannelOpener: pezframe_support::inherent::ProvideInherent< Call = pezcumulus_pallet_teyrchain_system::Call, >, { let buy_execution_fee_amount_eta = WeightToFee::weight_to_fee(&Weight::from_parts(90_000_000_000, 1024)); let native_asset_amount_unit = existential_deposit; let native_asset_amount_received = native_asset_amount_unit * 10.into() + buy_execution_fee_amount_eta.into(); let checking_account = if let Some(checking_account) = CheckingAccount::get() { Some((checking_account, native_asset_amount_received)) } else { None }; let mut builder = ExtBuilder::::default() .with_collators(collator_session_keys.collators()) .with_session_keys(collator_session_keys.session_keys()) .with_safe_xcm_version(XCM_VERSION) .with_para_id(runtime_para_id.into()) .with_tracing(); if let Some((checking_account, initial_checking_account)) = checking_account.as_ref() { builder = builder.with_balances(vec![(checking_account.clone(), *initial_checking_account)]); }; builder .build() .execute_with(|| { let mut alice = [0u8; 32]; alice[0] = 1; let included_head = RuntimeHelper::::run_to_block( 2, AccountId::from(alice).into(), ); // check Balances before assert_eq!(>::free_balance(&target_account), 0.into()); if let Some((checking_account, initial_checking_account)) = checking_account.as_ref() { assert_eq!( >::free_balance(checking_account), *initial_checking_account ); }; let native_asset_id = Location::parent(); // 1. process received teleported assets from relaychain let xcm = Xcm(vec![ ReceiveTeleportedAsset(Assets::from(vec![Asset { id: AssetId(native_asset_id.clone()), fun: Fungible(native_asset_amount_received.into()), }])), ClearOrigin, BuyExecution { fees: Asset { id: AssetId(native_asset_id.clone()), fun: Fungible(buy_execution_fee_amount_eta), }, weight_limit: Limited(Weight::from_parts(3035310000, 65536)), }, DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Location { parents: 0, interior: [AccountId32 { network: None, id: target_account.clone().into(), }] .into(), }, }, ExpectTransactStatus(MaybeErrorCode::Success), ]); let mut hash = xcm.using_encoded(pezsp_io::hashing::blake2_256); let outcome = XcmExecutor::::prepare_and_execute( Parent, xcm, &mut hash, RuntimeHelper::::xcm_max_weight(XcmReceivedFrom::Parent), Weight::zero(), ); assert_ok!(outcome.ensure_complete()); // check Balances after assert_ne!(>::free_balance(&target_account), 0.into()); if let Some((checking_account, initial_checking_account)) = checking_account.as_ref() { assert_eq!( >::free_balance(checking_account), *initial_checking_account - native_asset_amount_received ); } let native_asset_to_teleport_away = native_asset_amount_unit * 3.into(); // 2. try to teleport asset back to the relaychain { as UpwardMessageSender>::ensure_successful_delivery(); let dest = Location::parent(); let mut dest_beneficiary = Location::parent() .appended_with(AccountId32 { network: None, id: pezsp_runtime::AccountId32::new([3; 32]).into(), }) .unwrap(); dest_beneficiary.reanchor(&dest, &XcmConfig::UniversalLocation::get()).unwrap(); let target_account_balance_before_teleport = >::free_balance(&target_account); assert!( native_asset_to_teleport_away < target_account_balance_before_teleport - existential_deposit ); // Mint funds into account to ensure it has enough balance to pay delivery fees let delivery_fees = xcm_helpers::teleport_assets_delivery_fees::( (native_asset_id.clone(), native_asset_to_teleport_away.into()).into(), native_asset_id.clone().into(), Unlimited, dest_beneficiary.clone(), dest.clone(), ); >::mint_into( &target_account, delivery_fees.into(), ) .unwrap(); assert_ok!(RuntimeHelper::::do_teleport_assets::( RuntimeHelper::::origin_of(target_account.clone()), dest, dest_beneficiary, (native_asset_id.clone(), native_asset_to_teleport_away.into()), None, included_head.clone(), &alice, &slot_durations, )); // check balances assert_eq!( >::free_balance(&target_account), target_account_balance_before_teleport - native_asset_to_teleport_away ); if let Some((checking_account, initial_checking_account)) = checking_account.as_ref() { assert_eq!( >::free_balance(checking_account), *initial_checking_account - native_asset_amount_received + native_asset_to_teleport_away ); } // check events RuntimeHelper::::assert_pallet_xcm_event_outcome( &unwrap_pallet_xcm_event, |outcome| { assert_ok!(outcome.ensure_complete()); }, ); } // 3. try to teleport assets away to other teyrchain (2345): should not work as we don't // trust `IsTeleporter` for `(relay-native-asset, para(2345))` pair { let other_para_id = 2345; let dest = Location::new(1, [Teyrchain(other_para_id)]); let mut dest_beneficiary = Location::new(1, [Teyrchain(other_para_id)]) .appended_with(AccountId32 { network: None, id: pezsp_runtime::AccountId32::new([3; 32]).into(), }) .unwrap(); dest_beneficiary.reanchor(&dest, &XcmConfig::UniversalLocation::get()).unwrap(); let target_account_balance_before_teleport = >::free_balance(&target_account); let native_asset_to_teleport_away = native_asset_amount_unit * 3.into(); assert!( native_asset_to_teleport_away < target_account_balance_before_teleport - existential_deposit ); assert_eq!( RuntimeHelper::::do_teleport_assets::( RuntimeHelper::::origin_of(target_account.clone()), dest, dest_beneficiary, (native_asset_id, native_asset_to_teleport_away.into()), Some((runtime_para_id, other_para_id)), included_head, &alice, &slot_durations, ), Err(DispatchError::Module(pezsp_runtime::ModuleError { index: 31, error: [2, 0, 0, 0,], message: Some("Filtered",), },),) ); // check balances assert_eq!( >::free_balance(&target_account), target_account_balance_before_teleport ); if let Some((checking_account, initial_checking_account)) = checking_account.as_ref() { assert_eq!( >::free_balance(checking_account), *initial_checking_account - native_asset_amount_received + native_asset_to_teleport_away ); } } }) } #[macro_export] macro_rules! include_teleports_for_native_asset_works( ( $runtime:path, $all_pallets_without_system:path, $xcm_config:path, $checking_account:ty, $weight_to_fee:path, $hrmp_channel_opener:path, $collator_session_key:expr, $slot_durations:expr, $existential_deposit:expr, $unwrap_pallet_xcm_event:expr, $runtime_para_id:expr ) => { #[test] fn teleports_for_native_asset_works() { const BOB: [u8; 32] = [2u8; 32]; let target_account = teyrchains_common::AccountId::from(BOB); $crate::test_cases::teleports_for_native_asset_works::< $runtime, $all_pallets_without_system, $xcm_config, $checking_account, $weight_to_fee, $hrmp_channel_opener >( $collator_session_key, $slot_durations, $existential_deposit, target_account, $unwrap_pallet_xcm_event, $runtime_para_id ) } } ); /// Test-case makes sure that `Runtime` can receive teleported assets from sibling teyrchain, and /// can teleport it back pub fn teleports_for_foreign_assets_works< Runtime, AllPalletsWithoutSystem, XcmConfig, CheckingAccount, WeightToFee, HrmpChannelOpener, SovereignAccountOf, ForeignAssetsPalletInstance, >( collator_session_keys: CollatorSessionKeys, slot_durations: SlotDurations, target_account: AccountIdOf, existential_deposit: BalanceOf, asset_owner: AccountIdOf, unwrap_pallet_xcm_event: Box) -> Option>>, unwrap_xcmp_queue_event: Box< dyn Fn(Vec) -> Option>, >, ) where Runtime: pezframe_system::Config + pezpallet_balances::Config + pezpallet_session::Config + pezpallet_xcm::Config + teyrchain_info::Config + pezpallet_collator_selection::Config + pezcumulus_pallet_teyrchain_system::Config + pezcumulus_pallet_xcmp_queue::Config + pezpallet_assets::Config + pezpallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]>, ValidatorIdOf: From>, BalanceOf: From, XcmConfig: xcm_executor::Config, CheckingAccount: Get>, HrmpChannelOpener: pezframe_support::inherent::ProvideInherent< Call = pezcumulus_pallet_teyrchain_system::Call, >, WeightToFee: pezframe_support::weights::WeightToFee, ::Balance: From + Into, SovereignAccountOf: ConvertLocation>, >::AssetId: From + Into, >::AssetIdParameter: From + Into, >::Balance: From + Into, ::AccountId: Into<<::RuntimeOrigin as OriginTrait>::AccountId>, <::Lookup as StaticLookup>::Source: From<::AccountId>, ::AccountId: From, ForeignAssetsPalletInstance: 'static, { // foreign teyrchain with the same consensus currency as asset let foreign_para_id = 2222; let foreign_asset_id_location = xcm::v5::Location { parents: 1, interior: [ xcm::v5::Junction::Teyrchain(foreign_para_id), xcm::v5::Junction::GeneralIndex(1234567), ] .into(), }; let foreign_asset_reserve_data = ForeignAssetReserveData { reserve: xcm::v5::Location { parents: 1, interior: [xcm::v5::Junction::Teyrchain(foreign_para_id)].into(), }, teleportable: true, }; // foreign creator, which can be sibling teyrchain to match ForeignCreators let foreign_creator = Location { parents: 1, interior: [Teyrchain(foreign_para_id)].into() }; let foreign_creator_as_account_id = SovereignAccountOf::convert_location(&foreign_creator).expect(""); // we want to buy execution with local relay chain currency let buy_execution_fee_amount = WeightToFee::weight_to_fee(&Weight::from_parts(90_000_000_000, 0)); let buy_execution_fee = Asset { id: AssetId(Location::parent()), fun: Fungible(buy_execution_fee_amount) }; let teleported_foreign_asset_amount = 10_000_000_000_000; let runtime_para_id = 1000; ExtBuilder::::default() .with_collators(collator_session_keys.collators()) .with_session_keys(collator_session_keys.session_keys()) .with_balances(vec![ ( foreign_creator_as_account_id, existential_deposit + (buy_execution_fee_amount * 2).into(), ), (target_account.clone(), existential_deposit), (CheckingAccount::get(), existential_deposit), ]) .with_safe_xcm_version(XCM_VERSION) .with_para_id(runtime_para_id.into()) .with_tracing() .build() .execute_with(|| { let mut alice = [0u8; 32]; alice[0] = 1; let included_head = RuntimeHelper::::run_to_block( 2, AccountId::from(alice).into(), ); // checks target_account before assert_eq!( >::free_balance(&target_account), existential_deposit ); // check `CheckingAccount` before assert_eq!( >::free_balance(&CheckingAccount::get()), existential_deposit ); assert_eq!( >::balance( foreign_asset_id_location.clone().into(), &target_account ), 0.into() ); assert_eq!( >::balance( foreign_asset_id_location.clone().into(), &CheckingAccount::get() ), 0.into() ); // check totals before assert_total::< pezpallet_assets::Pallet, AccountIdOf, >(foreign_asset_id_location.clone(), 0, 0); // create foreign asset (0 total issuance) let asset_minimum_asset_balance = 3333333_u128; assert_ok!( >::force_create( RuntimeHelper::::root_origin(), foreign_asset_id_location.clone().into(), asset_owner.clone().into(), false, asset_minimum_asset_balance.into() ) ); assert_total::< pezpallet_assets::Pallet, AccountIdOf, >(foreign_asset_id_location.clone(), 0, 0); assert!(teleported_foreign_asset_amount > asset_minimum_asset_balance); // mark the foreign asset as teleportable assert_ok!( >::set_reserves( RuntimeHelper::::origin_of(asset_owner.into()), foreign_asset_id_location.clone().into(), vec![foreign_asset_reserve_data], ) ); // 1. process received teleported assets from sibling teyrchain (foreign_para_id) let xcm = Xcm(vec![ // BuyExecution with relaychain native token WithdrawAsset(buy_execution_fee.clone().into()), BuyExecution { fees: Asset { id: AssetId(Location::parent()), fun: Fungible(buy_execution_fee_amount), }, weight_limit: Unlimited, }, // Process teleported asset ReceiveTeleportedAsset(Assets::from(vec![Asset { id: AssetId(foreign_asset_id_location.clone()), fun: Fungible(teleported_foreign_asset_amount), }])), DepositAsset { assets: Wild(AllOf { id: AssetId(foreign_asset_id_location.clone()), fun: WildFungibility::Fungible, }), beneficiary: Location { parents: 0, interior: [AccountId32 { network: None, id: target_account.clone().into(), }] .into(), }, }, ExpectTransactStatus(MaybeErrorCode::Success), ]); let mut hash = xcm.using_encoded(pezsp_io::hashing::blake2_256); let outcome = XcmExecutor::::prepare_and_execute( foreign_creator, xcm, &mut hash, RuntimeHelper::::xcm_max_weight(XcmReceivedFrom::Sibling), Weight::zero(), ); assert_ok!(outcome.ensure_complete()); // checks target_account after assert_eq!( >::free_balance(&target_account), existential_deposit ); assert_eq!( >::balance( foreign_asset_id_location.clone().into(), &target_account ), teleported_foreign_asset_amount.into() ); // checks `CheckingAccount` after assert_eq!( >::free_balance(&CheckingAccount::get()), existential_deposit ); assert_eq!( >::balance( foreign_asset_id_location.clone().into(), &CheckingAccount::get() ), 0.into() ); // check total after (twice: target_account + CheckingAccount) assert_total::< pezpallet_assets::Pallet, AccountIdOf, >( foreign_asset_id_location.clone(), teleported_foreign_asset_amount, teleported_foreign_asset_amount, ); // 2. try to teleport asset back to source teyrchain (foreign_para_id) { let dest = Location::new(1, [Teyrchain(foreign_para_id)]); let mut dest_beneficiary = Location::new(1, [Teyrchain(foreign_para_id)]) .appended_with(AccountId32 { network: None, id: pezsp_runtime::AccountId32::new([3; 32]).into(), }) .unwrap(); dest_beneficiary.reanchor(&dest, &XcmConfig::UniversalLocation::get()).unwrap(); let target_account_balance_before_teleport = >::balance( foreign_asset_id_location.clone().into(), &target_account, ); let asset_to_teleport_away = asset_minimum_asset_balance * 3; assert!( asset_to_teleport_away < (target_account_balance_before_teleport - asset_minimum_asset_balance.into()) .into() ); // Make sure the target account has enough native asset to pay for delivery fees let delivery_fees = xcm_helpers::teleport_assets_delivery_fees::( (foreign_asset_id_location.clone(), asset_to_teleport_away).into(), foreign_asset_id_location.clone().into(), Unlimited, dest_beneficiary.clone(), dest.clone(), ); >::mint_into( &target_account, delivery_fees.into(), ) .unwrap(); assert_ok!(RuntimeHelper::::do_teleport_assets::( RuntimeHelper::::origin_of(target_account.clone()), dest, dest_beneficiary, (foreign_asset_id_location.clone(), asset_to_teleport_away), Some((runtime_para_id, foreign_para_id)), included_head, &alice, &slot_durations, )); // check balances assert_eq!( >::balance( foreign_asset_id_location.clone().into(), &target_account ), (target_account_balance_before_teleport - asset_to_teleport_away.into()) ); assert_eq!( >::balance( foreign_asset_id_location.clone().into(), &CheckingAccount::get() ), 0.into() ); // check total after (twice: target_account + CheckingAccount) assert_total::< pezpallet_assets::Pallet, AccountIdOf, >( foreign_asset_id_location.clone(), teleported_foreign_asset_amount - asset_to_teleport_away, teleported_foreign_asset_amount - asset_to_teleport_away, ); // check events RuntimeHelper::::assert_pallet_xcm_event_outcome( &unwrap_pallet_xcm_event, |outcome| { assert_ok!(outcome.ensure_complete()); }, ); assert!(RuntimeHelper::::xcmp_queue_message_sent(unwrap_xcmp_queue_event) .is_some()); } }) } #[macro_export] macro_rules! include_teleports_for_foreign_assets_works( ( $runtime:path, $all_pallets_without_system:path, $xcm_config:path, $checking_account:path, $weight_to_fee:path, $hrmp_channel_opener:path, $sovereign_account_of:path, $assets_pallet_instance:path, $collator_session_key:expr, $slot_durations:expr, $existential_deposit:expr, $unwrap_pallet_xcm_event:expr, $unwrap_xcmp_queue_event:expr ) => { #[test] fn teleports_for_foreign_assets_works() { const BOB: [u8; 32] = [2u8; 32]; let target_account = teyrchains_common::AccountId::from(BOB); const SOME_ASSET_OWNER: [u8; 32] = [5u8; 32]; let asset_owner = teyrchains_common::AccountId::from(SOME_ASSET_OWNER); $crate::test_cases::teleports_for_foreign_assets_works::< $runtime, $all_pallets_without_system, $xcm_config, $checking_account, $weight_to_fee, $hrmp_channel_opener, $sovereign_account_of, $assets_pallet_instance >( $collator_session_key, $slot_durations, target_account, $existential_deposit, asset_owner, $unwrap_pallet_xcm_event, $unwrap_xcmp_queue_event ) } } ); /// Test-case makes sure that `Runtime`'s `xcm::AssetTransactor` can handle native relay chain /// currency pub fn asset_transactor_transfer_with_local_consensus_currency_works( collator_session_keys: CollatorSessionKeys, source_account: AccountIdOf, target_account: AccountIdOf, existential_deposit: BalanceOf, additional_checks_before: Box, additional_checks_after: Box, ) where Runtime: pezframe_system::Config + pezpallet_balances::Config + pezpallet_session::Config + pezpallet_xcm::Config + teyrchain_info::Config + pezpallet_collator_selection::Config + pezcumulus_pallet_teyrchain_system::Config + pezpallet_timestamp::Config, AccountIdOf: Into<[u8; 32]>, ValidatorIdOf: From>, BalanceOf: From, XcmConfig: xcm_executor::Config, ::Balance: From + Into, ::AccountId: Into<<::RuntimeOrigin as OriginTrait>::AccountId>, <::Lookup as StaticLookup>::Source: From<::AccountId>, { let unit = existential_deposit; ExtBuilder::::default() .with_collators(collator_session_keys.collators()) .with_session_keys(collator_session_keys.session_keys()) .with_balances(vec![(source_account.clone(), (BalanceOf::::from(10_u128) * unit))]) .with_tracing() .build() .execute_with(|| { // check Balances before assert_eq!( >::free_balance(&source_account), (BalanceOf::::from(10_u128) * unit) ); assert_eq!( >::free_balance(&target_account), (BalanceOf::::zero() * unit) ); // additional check before additional_checks_before(); // transfer_asset (deposit/withdraw) ALICE -> BOB let _ = RuntimeHelper::::do_transfer( Location { parents: 0, interior: [AccountId32 { network: None, id: source_account.clone().into() }] .into(), }, Location { parents: 0, interior: [AccountId32 { network: None, id: target_account.clone().into() }] .into(), }, // local_consensus_currency_asset, e.g.: relaychain token (KSM, HEZ, ...) ( Location { parents: 1, interior: Here }, (BalanceOf::::from(1_u128) * unit).into(), ), ) .expect("no error"); // check Balances after assert_eq!( >::free_balance(source_account), (BalanceOf::::from(9_u128) * unit) ); assert_eq!( >::free_balance(target_account), (BalanceOf::::from(1_u128) * unit) ); additional_checks_after(); }) } #[macro_export] macro_rules! include_asset_transactor_transfer_with_local_consensus_currency_works( ( $runtime:path, $xcm_config:path, $collator_session_key:expr, $existential_deposit:expr, $additional_checks_before:expr, $additional_checks_after:expr ) => { #[test] fn asset_transactor_transfer_with_local_consensus_currency_works() { const ALICE: [u8; 32] = [1u8; 32]; let source_account = teyrchains_common::AccountId::from(ALICE); const BOB: [u8; 32] = [2u8; 32]; let target_account = teyrchains_common::AccountId::from(BOB); $crate::test_cases::asset_transactor_transfer_with_local_consensus_currency_works::< $runtime, $xcm_config >( $collator_session_key, source_account, target_account, $existential_deposit, $additional_checks_before, $additional_checks_after ) } } ); /// Test-case makes sure that `Runtime`'s `xcm::AssetTransactor` can handle native relay chain /// currency pub fn asset_transactor_transfer_with_pallet_assets_instance_works< Runtime, XcmConfig, AssetsPalletInstance, AssetId, AssetIdConverter, >( collator_session_keys: CollatorSessionKeys, existential_deposit: BalanceOf, asset_id: AssetId, asset_owner: AccountIdOf, alice_account: AccountIdOf, bob_account: AccountIdOf, charlie_account: AccountIdOf, additional_checks_before: Box, additional_checks_after: Box, ) where Runtime: pezframe_system::Config + pezpallet_balances::Config + pezpallet_session::Config + pezpallet_xcm::Config + teyrchain_info::Config + pezpallet_collator_selection::Config + pezcumulus_pallet_teyrchain_system::Config + pezpallet_assets::Config + pezpallet_timestamp::Config, AccountIdOf: Into<[u8; 32]>, ValidatorIdOf: From>, BalanceOf: From, XcmConfig: xcm_executor::Config, >::AssetId: From + Into, >::AssetIdParameter: From + Into, >::Balance: From + Into, ::AccountId: Into<<::RuntimeOrigin as OriginTrait>::AccountId>, <::Lookup as StaticLookup>::Source: From<::AccountId>, AssetsPalletInstance: 'static, AssetId: Clone, AssetIdConverter: MaybeEquivalence, { ExtBuilder::::default() .with_collators(collator_session_keys.collators()) .with_session_keys(collator_session_keys.session_keys()) .with_balances(vec![ (asset_owner.clone(), existential_deposit), (alice_account.clone(), existential_deposit), (bob_account.clone(), existential_deposit), ]) .with_tracing() .build() .execute_with(|| { // create some asset class let asset_minimum_asset_balance = 3333333_u128; let asset_id_as_location = AssetIdConverter::convert_back(&asset_id).unwrap(); assert_ok!(>::force_create( RuntimeHelper::::root_origin(), asset_id.clone().into(), asset_owner.clone().into(), false, asset_minimum_asset_balance.into() )); // We first mint enough asset for the account to exist for assets assert_ok!(>::mint( RuntimeHelper::::origin_of(asset_owner.clone()), asset_id.clone().into(), alice_account.clone().into(), (6 * asset_minimum_asset_balance).into() )); // check Assets before assert_eq!( >::balance( asset_id.clone().into(), &alice_account ), (6 * asset_minimum_asset_balance).into() ); assert_eq!( >::balance( asset_id.clone().into(), &bob_account ), 0.into() ); assert_eq!( >::balance( asset_id.clone().into(), &charlie_account ), 0.into() ); assert_eq!( >::balance( asset_id.clone().into(), &asset_owner ), 0.into() ); assert_eq!( >::free_balance(&alice_account), existential_deposit ); assert_eq!( >::free_balance(&bob_account), existential_deposit ); assert_eq!( >::free_balance(&charlie_account), 0.into() ); assert_eq!( >::free_balance(&asset_owner), existential_deposit ); additional_checks_before(); // transfer_asset (deposit/withdraw) ALICE -> CHARLIE (not ok - Charlie does not have // ExistentialDeposit) assert_noop!( RuntimeHelper::::do_transfer( Location { parents: 0, interior: [AccountId32 { network: None, id: alice_account.clone().into() }] .into(), }, Location { parents: 0, interior: [AccountId32 { network: None, id: charlie_account.clone().into() }] .into(), }, (asset_id_as_location.clone(), asset_minimum_asset_balance), ), XcmError::FailedToTransactAsset(Into::<&str>::into( pezsp_runtime::TokenError::CannotCreate )) ); // transfer_asset (deposit/withdraw) ALICE -> BOB (ok - has ExistentialDeposit) assert!(matches!( RuntimeHelper::::do_transfer( Location { parents: 0, interior: [AccountId32 { network: None, id: alice_account.clone().into() }] .into(), }, Location { parents: 0, interior: [AccountId32 { network: None, id: bob_account.clone().into() }] .into(), }, (asset_id_as_location, asset_minimum_asset_balance), ), Ok(_) )); // check Assets after assert_eq!( >::balance( asset_id.clone().into(), &alice_account ), (5 * asset_minimum_asset_balance).into() ); assert_eq!( >::balance( asset_id.clone().into(), &bob_account ), asset_minimum_asset_balance.into() ); assert_eq!( >::balance( asset_id.clone().into(), &charlie_account ), 0.into() ); assert_eq!( >::balance( asset_id.into(), &asset_owner ), 0.into() ); assert_eq!( >::free_balance(&alice_account), existential_deposit ); assert_eq!( >::free_balance(&bob_account), existential_deposit ); assert_eq!( >::free_balance(&charlie_account), 0.into() ); assert_eq!( >::free_balance(&asset_owner), existential_deposit ); additional_checks_after(); }) } #[macro_export] macro_rules! include_asset_transactor_transfer_with_pallet_assets_instance_works( ( $test_name:tt, $runtime:path, $xcm_config:path, $assets_pallet_instance:path, $asset_id:path, $asset_id_converter:path, $collator_session_key:expr, $existential_deposit:expr, $tested_asset_id:expr, $additional_checks_before:expr, $additional_checks_after:expr ) => { #[test] fn $test_name() { const SOME_ASSET_OWNER: [u8; 32] = [5u8; 32]; let asset_owner = teyrchains_common::AccountId::from(SOME_ASSET_OWNER); const ALICE: [u8; 32] = [1u8; 32]; let alice_account = teyrchains_common::AccountId::from(ALICE); const BOB: [u8; 32] = [2u8; 32]; let bob_account = teyrchains_common::AccountId::from(BOB); const CHARLIE: [u8; 32] = [3u8; 32]; let charlie_account = teyrchains_common::AccountId::from(CHARLIE); $crate::test_cases::asset_transactor_transfer_with_pallet_assets_instance_works::< $runtime, $xcm_config, $assets_pallet_instance, $asset_id, $asset_id_converter >( $collator_session_key, $existential_deposit, $tested_asset_id, asset_owner, alice_account, bob_account, charlie_account, $additional_checks_before, $additional_checks_after ) } } ); /// Test-case makes sure that `Runtime` can create and manage `ForeignAssets` pub fn create_and_manage_foreign_assets_for_local_consensus_teyrchain_assets_works< Runtime, XcmConfig, WeightToFee, SovereignAccountOf, ForeignAssetsPalletInstance, AssetId, AssetIdConverter, >( collator_session_keys: CollatorSessionKeys, existential_deposit: BalanceOf, asset_deposit: BalanceOf, metadata_deposit_base: BalanceOf, metadata_deposit_per_byte: BalanceOf, alice_account: AccountIdOf, bob_account: AccountIdOf, runtime_call_encode: Box< dyn Fn(pezpallet_assets::Call) -> Vec, >, unwrap_pallet_assets_event: Box< dyn Fn(Vec) -> Option>, >, additional_checks_before: Box, additional_checks_after: Box, ) where Runtime: pezframe_system::Config + pezpallet_balances::Config + pezpallet_session::Config + pezpallet_xcm::Config + teyrchain_info::Config + pezpallet_collator_selection::Config + pezcumulus_pallet_teyrchain_system::Config + pezpallet_assets::Config + pezpallet_timestamp::Config, AccountIdOf: Into<[u8; 32]>, ValidatorIdOf: From>, BalanceOf: From, XcmConfig: xcm_executor::Config, WeightToFee: pezframe_support::weights::WeightToFee, ::Balance: From + Into, SovereignAccountOf: ConvertLocation>, >::AssetId: From + Into, >::AssetIdParameter: From + Into, >::Balance: From + Into, ::AccountId: Into<<::RuntimeOrigin as OriginTrait>::AccountId>, <::Lookup as StaticLookup>::Source: From<::AccountId>, ForeignAssetsPalletInstance: 'static, AssetId: Clone, AssetIdConverter: MaybeEquivalence, { // foreign teyrchain with the same consensus currency as asset let foreign_asset_id_location = Location::new(1, [Teyrchain(2222), GeneralIndex(1234567)]); let asset_id = AssetIdConverter::convert(&foreign_asset_id_location).unwrap(); // foreign creator, which can be sibling teyrchain to match ForeignCreators let foreign_creator = Location { parents: 1, interior: [Teyrchain(2222)].into() }; let foreign_creator_as_account_id = SovereignAccountOf::convert_location(&foreign_creator).expect(""); // we want to buy execution with local relay chain currency let buy_execution_fee_amount = WeightToFee::weight_to_fee(&Weight::from_parts(90_000_000_000, 0)); let buy_execution_fee = Asset { id: AssetId(Location::parent()), fun: Fungible(buy_execution_fee_amount) }; const ASSET_NAME: &str = "My super coin"; const ASSET_SYMBOL: &str = "MY_S_COIN"; let metadata_deposit_per_byte_eta = metadata_deposit_per_byte .saturating_mul(((ASSET_NAME.len() + ASSET_SYMBOL.len()) as u128).into()); ExtBuilder::::default() .with_collators(collator_session_keys.collators()) .with_session_keys(collator_session_keys.session_keys()) .with_balances(vec![( foreign_creator_as_account_id.clone(), existential_deposit + asset_deposit + metadata_deposit_base + metadata_deposit_per_byte_eta + buy_execution_fee_amount.into() + buy_execution_fee_amount.into(), )]) .with_tracing() .build() .execute_with(|| { assert!(>::asset_ids() .collect::>() .is_empty()); assert_eq!( >::free_balance(&foreign_creator_as_account_id), existential_deposit + asset_deposit + metadata_deposit_base + metadata_deposit_per_byte_eta + buy_execution_fee_amount.into() + buy_execution_fee_amount.into() ); additional_checks_before(); // execute XCM with Transacts to create/manage foreign assets by foreign governance // prepare data for xcm::Transact(create) let foreign_asset_create = runtime_call_encode(pezpallet_assets::Call::< Runtime, ForeignAssetsPalletInstance, >::create { id: asset_id.clone().into(), // admin as sovereign_account admin: foreign_creator_as_account_id.clone().into(), min_balance: 1.into(), }); // prepare data for xcm::Transact(set_metadata) let foreign_asset_set_metadata = runtime_call_encode(pezpallet_assets::Call::< Runtime, ForeignAssetsPalletInstance, >::set_metadata { id: asset_id.clone().into(), name: Vec::from(ASSET_NAME), symbol: Vec::from(ASSET_SYMBOL), decimals: 12, }); // prepare data for xcm::Transact(set_team - change just freezer to Bob) let foreign_asset_set_team = runtime_call_encode(pezpallet_assets::Call::< Runtime, ForeignAssetsPalletInstance, >::set_team { id: asset_id.clone().into(), issuer: foreign_creator_as_account_id.clone().into(), admin: foreign_creator_as_account_id.clone().into(), freezer: bob_account.clone().into(), }); // lets simulate this was triggered by relay chain from local consensus sibling // teyrchain let xcm = Xcm(vec![ WithdrawAsset(buy_execution_fee.clone().into()), BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited }, Transact { origin_kind: OriginKind::Xcm, call: foreign_asset_create.into(), fallback_max_weight: None, }, Transact { origin_kind: OriginKind::SovereignAccount, call: foreign_asset_set_metadata.into(), fallback_max_weight: None, }, Transact { origin_kind: OriginKind::SovereignAccount, call: foreign_asset_set_team.into(), fallback_max_weight: None, }, ExpectTransactStatus(MaybeErrorCode::Success), ]); // messages with different consensus should go through the local bridge-hub let mut hash = xcm.using_encoded(pezsp_io::hashing::blake2_256); // execute xcm as XcmpQueue would do let outcome = XcmExecutor::::prepare_and_execute( foreign_creator.clone(), xcm, &mut hash, RuntimeHelper::::xcm_max_weight(XcmReceivedFrom::Sibling), Weight::zero(), ); assert_ok!(outcome.ensure_complete()); // check events let mut events = >::events() .into_iter() .filter_map(|e| unwrap_pallet_assets_event(e.event.encode())); assert!(events.any(|e| matches!(e, pezpallet_assets::Event::Created { .. }))); assert!(events.any(|e| matches!(e, pezpallet_assets::Event::MetadataSet { .. }))); assert!(events.any(|e| matches!(e, pezpallet_assets::Event::TeamChanged { .. }))); // check assets after assert!(!>::asset_ids() .collect::>() .is_empty()); // check update metadata use pezframe_support::traits::fungibles::roles::Inspect as InspectRoles; assert_eq!( >::owner( asset_id.clone().into() ), Some(foreign_creator_as_account_id.clone()) ); assert_eq!( >::admin( asset_id.clone().into() ), Some(foreign_creator_as_account_id.clone()) ); assert_eq!( >::issuer( asset_id.clone().into() ), Some(foreign_creator_as_account_id.clone()) ); assert_eq!( >::freezer( asset_id.clone().into() ), Some(bob_account.clone()) ); assert!( >::free_balance(&foreign_creator_as_account_id) >= existential_deposit + buy_execution_fee_amount.into(), "Free balance: {:?} should be ge {:?}", >::free_balance(&foreign_creator_as_account_id), existential_deposit + buy_execution_fee_amount.into() ); assert_metadata::< pezpallet_assets::Pallet, AccountIdOf, >(asset_id.clone(), ASSET_NAME, ASSET_SYMBOL, 12); // check if changed freezer, can freeze assert_noop!( >::freeze( RuntimeHelper::::origin_of(bob_account), asset_id.clone().into(), alice_account.clone().into() ), pezpallet_assets::Error::::NoAccount ); assert_noop!( >::freeze( RuntimeHelper::::origin_of(foreign_creator_as_account_id.clone()), asset_id.into(), alice_account.into() ), pezpallet_assets::Error::::NoPermission ); // lets try create asset for different teyrchain(3333) (foreign_creator(2222) can create // just his assets) let foreign_asset_id_location = Location { parents: 1, interior: [Teyrchain(3333), GeneralIndex(1234567)].into() }; let asset_id = AssetIdConverter::convert(&foreign_asset_id_location).unwrap(); // prepare data for xcm::Transact(create) let foreign_asset_create = runtime_call_encode(pezpallet_assets::Call::< Runtime, ForeignAssetsPalletInstance, >::create { id: asset_id.into(), // admin as sovereign_account admin: foreign_creator_as_account_id.clone().into(), min_balance: 1.into(), }); let xcm = Xcm(vec![ WithdrawAsset(buy_execution_fee.clone().into()), BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited }, Transact { origin_kind: OriginKind::Xcm, call: foreign_asset_create.into(), fallback_max_weight: None, }, ExpectTransactStatus(MaybeErrorCode::from(DispatchError::BadOrigin.encode())), ]); // messages with different consensus should go through the local bridge-hub let mut hash = xcm.using_encoded(pezsp_io::hashing::blake2_256); // execute xcm as XcmpQueue would do let outcome = XcmExecutor::::prepare_and_execute( foreign_creator, xcm, &mut hash, RuntimeHelper::::xcm_max_weight(XcmReceivedFrom::Sibling), Weight::zero(), ); assert_ok!(outcome.ensure_complete()); additional_checks_after(); }) } #[macro_export] macro_rules! include_create_and_manage_foreign_assets_for_local_consensus_teyrchain_assets_works( ( $runtime:path, $xcm_config:path, $weight_to_fee:path, $sovereign_account_of:path, $assets_pallet_instance:path, $asset_id:path, $asset_id_converter:path, $collator_session_key:expr, $existential_deposit:expr, $asset_deposit:expr, $metadata_deposit_base:expr, $metadata_deposit_per_byte:expr, $runtime_call_encode:expr, $unwrap_pallet_assets_event:expr, $additional_checks_before:expr, $additional_checks_after:expr ) => { #[test] fn create_and_manage_foreign_assets_for_local_consensus_teyrchain_assets_works() { const ALICE: [u8; 32] = [1u8; 32]; let alice_account = teyrchains_common::AccountId::from(ALICE); const BOB: [u8; 32] = [2u8; 32]; let bob_account = teyrchains_common::AccountId::from(BOB); $crate::test_cases::create_and_manage_foreign_assets_for_local_consensus_teyrchain_assets_works::< $runtime, $xcm_config, $weight_to_fee, $sovereign_account_of, $assets_pallet_instance, $asset_id, $asset_id_converter >( $collator_session_key, $existential_deposit, $asset_deposit, $metadata_deposit_base, $metadata_deposit_per_byte, alice_account, bob_account, $runtime_call_encode, $unwrap_pallet_assets_event, $additional_checks_before, $additional_checks_after ) } } ); /// Test-case makes sure that `Runtime` can reserve-transfer asset to other teyrchains (where /// teleport is not trusted) pub fn reserve_transfer_native_asset_to_non_teleport_para_works< Runtime, AllPalletsWithoutSystem, XcmConfig, HrmpChannelOpener, HrmpChannelSource, LocationToAccountId, >( collator_session_keys: CollatorSessionKeys, slot_durations: SlotDurations, existential_deposit: BalanceOf, alice_account: AccountIdOf, unwrap_pallet_xcm_event: Box) -> Option>>, unwrap_xcmp_queue_event: Box< dyn Fn(Vec) -> Option>, >, weight_limit: WeightLimit, ) where Runtime: pezframe_system::Config + pezpallet_balances::Config + pezpallet_session::Config + pezpallet_xcm::Config + teyrchain_info::Config + pezpallet_collator_selection::Config + pezcumulus_pallet_teyrchain_system::Config + pezcumulus_pallet_xcmp_queue::Config + pezpallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]>, ValidatorIdOf: From>, BalanceOf: From, ::Balance: From + Into, XcmConfig: xcm_executor::Config, LocationToAccountId: ConvertLocation>, ::AccountId: Into<<::RuntimeOrigin as OriginTrait>::AccountId>, <::Lookup as StaticLookup>::Source: From<::AccountId>, ::AccountId: From, HrmpChannelOpener: pezframe_support::inherent::ProvideInherent< Call = pezcumulus_pallet_teyrchain_system::Call, >, HrmpChannelSource: XcmpMessageSource, { let runtime_para_id = 1000; ExtBuilder::::default() .with_collators(collator_session_keys.collators()) .with_session_keys(collator_session_keys.session_keys()) .with_tracing() .with_safe_xcm_version(3) .with_para_id(runtime_para_id.into()) .build() .execute_with(|| { let mut alice = [0u8; 32]; alice[0] = 1; let included_head = RuntimeHelper::::run_to_block( 2, AccountId::from(alice).into(), ); // reserve-transfer native asset with local reserve to remote teyrchain (2345) let other_para_id = 2345; let native_asset = Location::parent(); let dest = Location::new(1, [Teyrchain(other_para_id)]); let mut dest_beneficiary = Location::new(1, [Teyrchain(other_para_id)]) .appended_with(AccountId32 { network: None, id: pezsp_runtime::AccountId32::new([3; 32]).into(), }) .unwrap(); dest_beneficiary.reanchor(&dest, &XcmConfig::UniversalLocation::get()).unwrap(); let reserve_account = LocationToAccountId::convert_location(&dest) .expect("Sovereign account for reserves"); let balance_to_transfer = 1_000_000_000_000_u128; // open HRMP to other teyrchain mock_open_hrmp_channel::( runtime_para_id.into(), other_para_id.into(), included_head, &alice, &slot_durations, ); // we calculate exact delivery fees _after_ sending the message by weighing the sent // xcm, and this delivery fee varies for different runtimes, so just add enough buffer, // then verify the arithmetics check out on final balance. let delivery_fees_buffer = 40_000_000_000u128; // drip 2xED + transfer_amount + delivery_fees_buffer to Alice account let alice_account_init_balance = existential_deposit.saturating_mul(2.into()) + balance_to_transfer.into() + delivery_fees_buffer.into(); let _ = >::deposit_creating( &alice_account, alice_account_init_balance, ); // SA of target location needs to have at least ED, otherwise making reserve fails let _ = >::deposit_creating( &reserve_account, existential_deposit, ); // we just check here, that user retains enough balance after withdrawal // and also we check if `balance_to_transfer` is more than `existential_deposit`, assert!( (>::free_balance(&alice_account) - balance_to_transfer.into()) >= existential_deposit ); // SA has just ED assert_eq!( >::free_balance(&reserve_account), existential_deposit ); // local native asset (pezpallet_balances) let asset_to_transfer = Asset { fun: Fungible(balance_to_transfer.into()), id: AssetId(native_asset) }; // pezpallet_xcm call reserve transfer assert_ok!(>::transfer_assets_using_type_and_then( RuntimeHelper::::origin_of(alice_account.clone()), Box::new(dest.clone().into_versioned()), Box::new(VersionedAssets::from(Assets::from(asset_to_transfer))), Box::new(TransferType::LocalReserve), Box::new(VersionedAssetId::from(AssetId(Location::parent()))), Box::new(TransferType::LocalReserve), Box::new(VersionedXcm::from( Xcm::<()>::builder_unsafe() .deposit_asset(AllCounted(1), dest_beneficiary.clone()) .build() )), weight_limit, )); // check events // check pezpallet_xcm attempted RuntimeHelper::::assert_pallet_xcm_event_outcome( &unwrap_pallet_xcm_event, |outcome| { assert_ok!(outcome.ensure_complete()); }, ); // check that xcm was sent let xcm_sent_message_hash = >::events() .into_iter() .filter_map(|e| unwrap_xcmp_queue_event(e.event.encode())) .find_map(|e| match e { pezcumulus_pallet_xcmp_queue::Event::XcmpMessageSent { message_hash } => Some(message_hash), _ => None, }); // read xcm let xcm_sent = RuntimeHelper::::take_xcm( other_para_id.into(), ) .unwrap(); let delivery_fees = get_fungible_delivery_fees::< ::XcmSender, >(dest.clone(), Xcm::try_from(xcm_sent.clone()).unwrap()); assert_eq!( xcm_sent_message_hash, Some(xcm_sent.using_encoded(pezsp_io::hashing::blake2_256)) ); let mut xcm_sent: Xcm<()> = xcm_sent.try_into().expect("versioned xcm"); // check sent XCM Program to other teyrchain println!("reserve_transfer_native_asset_works sent xcm: {:?}", xcm_sent); let reserve_assets_deposited = Assets::from(vec![Asset { id: AssetId(Location { parents: 1, interior: Here }), fun: Fungible(1000000000000), }]); assert_matches_reserve_asset_deposited_instructions( &mut xcm_sent, &reserve_assets_deposited, &dest_beneficiary, ); // check alice account decreased by balance_to_transfer ( + delivery_fees) assert_eq!( >::free_balance(&alice_account), alice_account_init_balance - balance_to_transfer.into() - delivery_fees.into() ); // check reserve account // check reserve account increased by balance_to_transfer assert_eq!( >::free_balance(&reserve_account), existential_deposit + balance_to_transfer.into() ); }) } pub fn xcm_payment_api_with_pools_works() where Runtime: XcmPaymentApiV2 + pezframe_system::Config + pezpallet_balances::Config + pezpallet_session::Config + pezpallet_xcm::Config + teyrchain_info::Config + pezpallet_collator_selection::Config + pezcumulus_pallet_teyrchain_system::Config + pezcumulus_pallet_xcmp_queue::Config + pezpallet_timestamp::Config + pezpallet_assets::Config< pezpallet_assets::Instance1, AssetId = u32, Balance = ::Balance, > + pezpallet_asset_conversion::Config< AssetKind = xcm::v5::Location, Balance = ::Balance, >, ValidatorIdOf: From>, RuntimeOrigin: OriginTrait::AccountId>, <::Lookup as StaticLookup>::Source: From<::AccountId>, Block: BlockT, WeightToFee: pezframe_support::weights::WeightToFee, { use xcm::prelude::*; ExtBuilder::::default().build().execute_with(|| { let test_account = AccountId::from([0u8; 32]); let transfer_amount = 100u128; let xcm_to_weigh = Xcm::::builder_unsafe() .withdraw_asset((Here, transfer_amount)) .buy_execution((Here, transfer_amount), Unlimited) .deposit_asset(AllCounted(1), [1u8; 32]) .build(); let versioned_xcm_to_weigh = VersionedXcm::from(xcm_to_weigh.clone().into()); let xcm_weight = Runtime::query_xcm_weight(versioned_xcm_to_weigh).expect("xcm weight must be computed"); let expected_weight_native_fee: u128 = WeightToFee::weight_to_fee(&xcm_weight).saturated_into(); let native_token: Location = Parent.into(); let native_token_versioned = VersionedAssetId::from(AssetId(native_token.clone())); let execution_fees = Runtime::query_weight_to_asset_fee(xcm_weight, native_token_versioned) .expect("weight must be converted to native fee"); assert_eq!(execution_fees, expected_weight_native_fee); // We need some balance to create an asset. assert_ok!( pezpallet_balances::Pallet::::mint_into(&test_account, 3_000_000_000_000,) ); // Now we try to use an asset that's not in a pool. let asset_id = 1984u32; // USDT. let usdt_token: Location = (PalletInstance(50), GeneralIndex(asset_id.into())).into(); assert_ok!(pezpallet_assets::Pallet::::create( RuntimeOrigin::signed(test_account.clone()), asset_id.into(), test_account.clone().into(), 1000 )); let execution_fees = Runtime::query_weight_to_asset_fee(xcm_weight, usdt_token.clone().into()); assert_eq!(execution_fees, Err(XcmPaymentApiError::AssetNotFound)); // We add it to a pool with native. assert_ok!(pezpallet_asset_conversion::Pallet::::create_pool( RuntimeOrigin::signed(test_account.clone()), native_token.clone().try_into().unwrap(), usdt_token.clone().try_into().unwrap() )); let execution_fees = Runtime::query_weight_to_asset_fee(xcm_weight, usdt_token.clone().into()); // Still not enough because it doesn't have any liquidity. assert_eq!(execution_fees, Err(XcmPaymentApiError::AssetNotFound)); // We mint some of the asset... assert_ok!(pezpallet_assets::Pallet::::mint( RuntimeOrigin::signed(test_account.clone()), asset_id.into(), test_account.clone().into(), 3_000_000_000_000, )); // ...so we can add liquidity to the pool. assert_ok!(pezpallet_asset_conversion::Pallet::::add_liquidity( RuntimeOrigin::signed(test_account.clone()), native_token.clone().try_into().unwrap(), usdt_token.clone().try_into().unwrap(), 1_000_000_000_000, 2_000_000_000_000, 0, 0, test_account )); let expected_weight_usdt_fee: u128 = pezpallet_asset_conversion::Pallet::::quote_price_tokens_for_exact_tokens( usdt_token.clone(), native_token, expected_weight_native_fee, true, ) .expect("the quote price must work") .saturated_into(); assert_ne!(expected_weight_usdt_fee, expected_weight_native_fee); let execution_fees = Runtime::query_weight_to_asset_fee(xcm_weight, usdt_token.into()) .expect("weight must be converted to native fee"); assert_eq!(execution_fees, expected_weight_usdt_fee); }); } pub fn setup_pool_for_paying_fees_with_foreign_assets( existential_deposit: Balance, (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance): ( AccountId, Location, Balance, ), ) where Runtime: pezframe_system::Config + pezpallet_balances::Config + pezpallet_assets::Config< pezpallet_assets::Instance2, AssetId = xcm::v5::Location, Balance = ::Balance, > + pezpallet_asset_conversion::Config< AssetKind = xcm::v5::Location, Balance = ::Balance, >, RuntimeOrigin: OriginTrait::AccountId>, <::Lookup as StaticLookup>::Source: From<::AccountId>, { // setup a pool to pay fees with `foreign_asset_id_location` tokens let pool_owner: AccountId = [14u8; 32].into(); let native_asset = Location::parent(); let pool_liquidity: Balance = existential_deposit.max(foreign_asset_id_minimum_balance).mul(100_000); let _ = pezpallet_balances::Pallet::::force_set_balance( RuntimeOrigin::root(), pool_owner.clone().into(), (existential_deposit + pool_liquidity).mul(2).into(), ); assert_ok!(pezpallet_assets::Pallet::::mint( RuntimeOrigin::signed(foreign_asset_owner), foreign_asset_id_location.clone().into(), pool_owner.clone().into(), (foreign_asset_id_minimum_balance + pool_liquidity).mul(2).into(), )); assert_ok!(pezpallet_asset_conversion::Pallet::::create_pool( RuntimeOrigin::signed(pool_owner.clone()), Box::new(native_asset.clone().into()), Box::new(foreign_asset_id_location.clone().into()) )); assert_ok!(pezpallet_asset_conversion::Pallet::::add_liquidity( RuntimeOrigin::signed(pool_owner.clone()), Box::new(native_asset.into()), Box::new(foreign_asset_id_location.into()), pool_liquidity, pool_liquidity, 1, 1, pool_owner, )); } pub fn xcm_payment_api_foreign_asset_pool_works< Runtime, RuntimeCall, RuntimeOrigin, LocationToAccountId, Block, WeightToFee, >( existential_deposit: Balance, another_network_genesis_hash: [u8; 32], ) where Runtime: XcmPaymentApiV2 + pezframe_system::Config + pezpallet_balances::Config + pezpallet_session::Config + pezpallet_xcm::Config + teyrchain_info::Config + pezpallet_collator_selection::Config + pezcumulus_pallet_teyrchain_system::Config + pezcumulus_pallet_xcmp_queue::Config + pezpallet_timestamp::Config + pezpallet_assets::Config< pezpallet_assets::Instance2, AssetId = xcm::v5::Location, Balance = ::Balance, > + pezpallet_asset_conversion::Config< AssetKind = xcm::v5::Location, Balance = ::Balance, >, RuntimeOrigin: OriginTrait::AccountId>, ValidatorIdOf: From>, <::Lookup as StaticLookup>::Source: From<::AccountId>, LocationToAccountId: ConvertLocation, Block: BlockT, WeightToFee: pezframe_support::weights::WeightToFee, { use xcm::prelude::*; ExtBuilder::::default().build().execute_with(|| { let foreign_asset_owner = LocationToAccountId::convert_location(&Location::parent()).unwrap(); let foreign_asset_id_location = Location::new( 2, [Junction::GlobalConsensus(NetworkId::ByGenesis(another_network_genesis_hash))], ); let native_asset_location = Location::parent(); let foreign_asset_id_minimum_balance = 1_000_000_000; pezpallet_assets::Pallet::::force_create( RuntimeHelper::::root_origin(), foreign_asset_id_location.clone().into(), foreign_asset_owner.clone().into(), true, // is_sufficient=true foreign_asset_id_minimum_balance.into(), ) .unwrap(); setup_pool_for_paying_fees_with_foreign_assets::( existential_deposit, ( foreign_asset_owner, foreign_asset_id_location.clone(), foreign_asset_id_minimum_balance, ), ); let transfer_amount = 100u128; let xcm_to_weigh = Xcm::::builder_unsafe() .withdraw_asset((Here, transfer_amount)) .buy_execution((Here, transfer_amount), Unlimited) .deposit_asset(AllCounted(1), [1u8; 32]) .build(); let versioned_xcm_to_weigh = VersionedXcm::from(xcm_to_weigh.into()); let xcm_weight = Runtime::query_xcm_weight(versioned_xcm_to_weigh).expect("xcm weight must be computed"); let weight_native_fee: u128 = WeightToFee::weight_to_fee(&xcm_weight).saturated_into(); let expected_weight_foreign_asset_fee: u128 = pezpallet_asset_conversion::Pallet::::quote_price_tokens_for_exact_tokens( foreign_asset_id_location.clone(), native_asset_location, weight_native_fee, true, ) .expect("the quote price must work") .saturated_into(); assert_ne!(expected_weight_foreign_asset_fee, weight_native_fee); let execution_fees = Runtime::query_weight_to_asset_fee( xcm_weight, foreign_asset_id_location.clone().into(), ) .expect("weight must be converted to foreign asset fee"); assert_eq!(execution_fees, expected_weight_foreign_asset_fee); }); }