// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute // This file is part of Pezkuwi. // Pezkuwi is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Pezkuwi is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Pezkuwi. If not, see . #![cfg(test)] use codec::Encode; use pezframe_support::weights::Weight; use pezkuwi_test_client::{ BlockBuilderExt, ClientBlockImportExt, DefaultTestClientBuilderExt, InitPezkuwiBlockBuilder, TestClientBuilder, TestClientBuilderExt, }; use pezkuwi_test_runtime::{pezpallet_test_notifier, xcm_config::XcmConfig}; use pezkuwi_test_service::construct_extrinsic; use pezsp_runtime::traits::Block; use pezsp_state_machine::InspectState; use xcm::{latest::prelude::*, VersionedResponse, VersionedXcm}; use xcm_executor::traits::WeightBounds; #[test] fn basic_buy_fees_message_executes() { pezsp_tracing::try_init_simple(); let client = TestClientBuilder::new().build(); let msg = Xcm(vec![ WithdrawAsset((Parent, 100).into()), BuyExecution { fees: (Parent, 1).into(), weight_limit: Unlimited }, DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Parent.into() }, ]); let mut block_builder = client.init_pezkuwi_block_builder(); let execute = construct_extrinsic( &client, pezkuwi_test_runtime::RuntimeCall::Xcm(pezpallet_xcm::Call::execute { message: Box::new(VersionedXcm::from(msg)), max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), }), pezsp_keyring::Sr25519Keyring::Alice, 0, ); block_builder.push_pezkuwi_extrinsic(execute).expect("pushes extrinsic"); let block = block_builder.build().expect("Finalizes the block").block; let block_hash = block.hash(); futures::executor::block_on(client.import(pezsp_consensus::BlockOrigin::Own, block)) .expect("imports the block"); client.state_at(block_hash).expect("state should exist").inspect_state(|| { assert!(pezkuwi_test_runtime::System::events().iter().any(|r| matches!( r.event, pezkuwi_test_runtime::RuntimeEvent::Xcm(pezpallet_xcm::Event::Attempted { outcome: Outcome::Complete { .. } }), ))); }); } #[test] fn transact_recursion_limit_works() { pezsp_tracing::try_init_simple(); let client = TestClientBuilder::new().build(); let base_xcm = |call: pezkuwi_test_runtime::RuntimeCall| { Xcm(vec![ WithdrawAsset((Here, 1_000).into()), BuyExecution { fees: (Here, 1).into(), weight_limit: Unlimited }, Transact { origin_kind: OriginKind::Native, call: call.encode().into(), fallback_max_weight: None, }, ]) }; let mut call: Option = None; // set up transacts with recursive depth of 11 for depth in (1..12).rev() { let mut msg; match depth { // this one should fail with `XcmError::ExceedsStackLimit` 11 => { msg = Xcm(vec![ClearOrigin]); }, // this one checks that the inner one (depth 11) fails as expected, // itself should not fail => should have outcome == Complete 10 => { let inner_call = call.take().unwrap(); let expected_transact_status = pezsp_runtime::DispatchError::Module(pezsp_runtime::ModuleError { index: 27, error: [28, 0, 40, 0], // ExceedsStackLimit message: Some("LocalExecutionIncompleteWithError"), }) .encode() .into(); msg = base_xcm(inner_call); msg.inner_mut().push(ExpectTransactStatus(expected_transact_status)); }, // these are the outer 9 calls that expect `ExpectTransactStatus(Success)` d if d >= 1 && d <= 9 => { let inner_call = call.take().unwrap(); msg = base_xcm(inner_call); msg.inner_mut().push(ExpectTransactStatus(MaybeErrorCode::Success)); }, _ => unreachable!(), } let max_weight = ::Weigher::weight(&mut msg, Weight::MAX).unwrap(); call = Some(pezkuwi_test_runtime::RuntimeCall::Xcm(pezpallet_xcm::Call::execute { message: Box::new(VersionedXcm::from(msg.clone())), max_weight, })); } let mut block_builder = client.init_pezkuwi_block_builder(); let execute = construct_extrinsic(&client, call.unwrap(), pezsp_keyring::Sr25519Keyring::Alice, 0); block_builder.push_pezkuwi_extrinsic(execute).expect("pushes extrinsic"); let block = block_builder.build().expect("Finalizes the block").block; let block_hash = block.hash(); futures::executor::block_on(client.import(pezsp_consensus::BlockOrigin::Own, block)) .expect("imports the block"); client.state_at(block_hash).expect("state should exist").inspect_state(|| { let events = pezkuwi_test_runtime::System::events(); // verify 10 pezpallet_xcm calls were successful assert_eq!( pezkuwi_test_runtime::System::events() .iter() .filter(|r| matches!( r.event, pezkuwi_test_runtime::RuntimeEvent::Xcm(pezpallet_xcm::Event::Attempted { outcome: Outcome::Complete { .. } }), )) .count(), 10 ); // verify transaction fees have been paid assert!(events.iter().any(|r| matches!( &r.event, pezkuwi_test_runtime::RuntimeEvent::TransactionPayment( pezpallet_transaction_payment::Event::TransactionFeePaid { who: payer, .. } ) if *payer == pezsp_keyring::Sr25519Keyring::Alice.into(), ))); }); } #[test] fn query_response_fires() { use pezkuwi_test_runtime::RuntimeEvent::TestNotifier; use pezpallet_test_notifier::Event::*; use pezpallet_xcm::QueryStatus; pezsp_tracing::try_init_simple(); let client = TestClientBuilder::new().build(); let mut block_builder = client.init_pezkuwi_block_builder(); let execute = construct_extrinsic( &client, pezkuwi_test_runtime::RuntimeCall::TestNotifier( pezpallet_test_notifier::Call::prepare_new_query {}, ), pezsp_keyring::Sr25519Keyring::Alice, 0, ); block_builder.push_pezkuwi_extrinsic(execute).expect("pushes extrinsic"); let block = block_builder.build().expect("Finalizes the block").block; let block_hash = block.hash(); futures::executor::block_on(client.import(pezsp_consensus::BlockOrigin::Own, block)) .expect("imports the block"); let mut query_id = None; client.state_at(block_hash).expect("state should exist").inspect_state(|| { for r in pezkuwi_test_runtime::System::events().iter() { match r.event { TestNotifier(QueryPrepared(q)) => query_id = Some(q), _ => (), } } }); let query_id = query_id.unwrap(); let mut block_builder = client.init_pezkuwi_block_builder(); let response = Response::ExecutionResult(None); let max_weight = Weight::from_parts(1_000_000, 1024 * 1024); let querier = Some(Here.into()); let msg = Xcm(vec![QueryResponse { query_id, response, max_weight, querier }]); let msg = Box::new(VersionedXcm::from(msg)); let execute = construct_extrinsic( &client, pezkuwi_test_runtime::RuntimeCall::Xcm(pezpallet_xcm::Call::execute { message: msg, max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), }), pezsp_keyring::Sr25519Keyring::Alice, 1, ); block_builder.push_pezkuwi_extrinsic(execute).expect("pushes extrinsic"); let block = block_builder.build().expect("Finalizes the block").block; let block_hash = block.hash(); futures::executor::block_on(client.import(pezsp_consensus::BlockOrigin::Own, block)) .expect("imports the block"); client.state_at(block_hash).expect("state should exist").inspect_state(|| { assert!(pezkuwi_test_runtime::System::events().iter().any(|r| matches!( r.event, pezkuwi_test_runtime::RuntimeEvent::Xcm(pezpallet_xcm::Event::ResponseReady { query_id: q, response: Response::ExecutionResult(None), }) if q == query_id, ))); assert_eq!( pezkuwi_test_runtime::Xcm::query(&query_id), Some(QueryStatus::Ready { response: VersionedResponse::from(Response::ExecutionResult(None)), at: 2u32.into() }), ) }); } #[test] fn query_response_elicits_handler() { use pezkuwi_test_runtime::RuntimeEvent::TestNotifier; use pezpallet_test_notifier::Event::*; pezsp_tracing::try_init_simple(); let client = TestClientBuilder::new().build(); let mut block_builder = client.init_pezkuwi_block_builder(); let execute = construct_extrinsic( &client, pezkuwi_test_runtime::RuntimeCall::TestNotifier( pezpallet_test_notifier::Call::prepare_new_notify_query {}, ), pezsp_keyring::Sr25519Keyring::Alice, 0, ); block_builder.push_pezkuwi_extrinsic(execute).expect("pushes extrinsic"); let block = block_builder.build().expect("Finalizes the block").block; let block_hash = block.hash(); futures::executor::block_on(client.import(pezsp_consensus::BlockOrigin::Own, block)) .expect("imports the block"); let mut query_id = None; client.state_at(block_hash).expect("state should exist").inspect_state(|| { for r in pezkuwi_test_runtime::System::events().iter() { match r.event { TestNotifier(NotifyQueryPrepared(q)) => query_id = Some(q), _ => (), } } }); let query_id = query_id.unwrap(); let mut block_builder = client.init_pezkuwi_block_builder(); let response = Response::ExecutionResult(None); let max_weight = Weight::from_parts(1_000_000, 1024 * 1024); let querier = Some(Here.into()); let msg = Xcm(vec![QueryResponse { query_id, response, max_weight, querier }]); let execute = construct_extrinsic( &client, pezkuwi_test_runtime::RuntimeCall::Xcm(pezpallet_xcm::Call::execute { message: Box::new(VersionedXcm::from(msg)), max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), }), pezsp_keyring::Sr25519Keyring::Alice, 1, ); block_builder.push_pezkuwi_extrinsic(execute).expect("pushes extrinsic"); let block = block_builder.build().expect("Finalizes the block").block; let block_hash = block.hash(); futures::executor::block_on(client.import(pezsp_consensus::BlockOrigin::Own, block)) .expect("imports the block"); client.state_at(block_hash).expect("state should exist").inspect_state(|| { assert!(pezkuwi_test_runtime::System::events().iter().any(|r| matches!( &r.event, TestNotifier(ResponseReceived( location, q, Response::ExecutionResult(None), )) if *q == query_id && matches!(location.unpack(), (0, [Junction::AccountId32 { .. }])), ))); }); } /// Simulates a cross-chain message from Teyrchain to Teyrchain through Relay Chain /// that deposits assets into the reserve of the destination. /// Regression test for `DepositReserveAsset` changes in /// #[test] fn deposit_reserve_asset_works_for_any_xcm_sender() { pezsp_tracing::try_init_simple(); let client = TestClientBuilder::new().build(); // Init values for the simulated origin Teyrchain let amount_to_send: u128 = 1_000_000_000_000; let assets: Assets = (Parent, amount_to_send).into(); let fee_asset_id: AssetId = Parent.into(); let max_assets = assets.len() as u32; let fees = assets.inner().iter().find(|a| a.id == fee_asset_id).unwrap().clone(); let weight_limit = Unlimited; let reserve = Location::parent(); let dest = Location::new(1, [Teyrchain(2000)]); let beneficiary_id = pezsp_keyring::Sr25519Keyring::Alice.to_account_id(); let beneficiary = Location::new(0, [AccountId32 { network: None, id: beneficiary_id.into() }]); // spends up to half of fees for execution on reserve and other half for execution on // destination let fee1 = amount_to_send.saturating_div(2); let fee2 = amount_to_send.saturating_sub(fee1); let fees_half_1 = Asset::from((fees.id.clone(), Fungible(fee1))); let fees_half_2 = Asset::from((fees.id.clone(), Fungible(fee2))); let reserve_context = ::UniversalLocation::get(); // identifies fee item as seen by `reserve` - to be used at reserve chain let reserve_fees = fees_half_1.reanchored(&reserve, &reserve_context).unwrap(); // identifies fee item as seen by `dest` - to be used at destination chain let dest_fees = fees_half_2.reanchored(&dest, &reserve_context).unwrap(); // identifies assets as seen by `reserve` - to be used at reserve chain let assets_reanchored = assets.reanchored(&reserve, &reserve_context).unwrap(); // identifies `dest` as seen by `reserve` let dest = dest.reanchored(&reserve, &reserve_context).unwrap(); // xcm to be executed at dest let xcm_on_dest = Xcm(vec![ BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }, DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }, ]); // xcm to be executed at reserve let msg = Xcm(vec![ WithdrawAsset(assets_reanchored), ClearOrigin, BuyExecution { fees: reserve_fees, weight_limit }, DepositReserveAsset { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest }, ]); let mut block_builder = client.init_pezkuwi_block_builder(); // Make the para available, so that `DMP` doesn't reject the XCM because the para is unknown. let make_para_available = construct_extrinsic( &client, pezkuwi_test_runtime::RuntimeCall::Sudo(pezpallet_sudo::Call::sudo { call: Box::new(pezkuwi_test_runtime::RuntimeCall::System( pezframe_system::Call::set_storage { items: vec![( pezkuwi_runtime_teyrchains::paras::Heads::< pezkuwi_test_runtime::Runtime, >::hashed_key_for(2000u32), vec![1, 2, 3], )], }, )), }), pezsp_keyring::Sr25519Keyring::Alice, 0, ); // Simulate execution of an incoming XCM message at the reserve chain let execute = construct_extrinsic( &client, pezkuwi_test_runtime::RuntimeCall::Xcm(pezpallet_xcm::Call::execute { message: Box::new(VersionedXcm::from(msg)), max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), }), pezsp_keyring::Sr25519Keyring::Alice, 1, ); block_builder .push_pezkuwi_extrinsic(make_para_available) .expect("pushes extrinsic"); block_builder.push_pezkuwi_extrinsic(execute).expect("pushes extrinsic"); let block = block_builder.build().expect("Finalizes the block").block; let block_hash = block.hash(); futures::executor::block_on(client.import(pezsp_consensus::BlockOrigin::Own, block)) .expect("imports the block"); client.state_at(block_hash).expect("state should exist").inspect_state(|| { assert!(pezkuwi_test_runtime::System::events().iter().any(|r| matches!( r.event, pezkuwi_test_runtime::RuntimeEvent::Xcm(pezpallet_xcm::Event::Attempted { outcome: Outcome::Complete { .. } }), ))); }); }