// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot 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. // Polkadot 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 Polkadot. If not, see . #![cfg_attr(not(feature = "std"), no_std)] #![cfg(test)] use codec::Encode; use frame_support::{dispatch::GetDispatchInfo, weights::Weight}; use polkadot_test_client::{ BlockBuilderExt, ClientBlockImportExt, DefaultTestClientBuilderExt, InitPolkadotBlockBuilder, TestClientBuilder, TestClientBuilderExt, }; use polkadot_test_runtime::{pallet_test_notifier, xcm_config::XcmConfig}; use polkadot_test_service::construct_extrinsic; use sp_runtime::traits::Block; use sp_state_machine::InspectState; use xcm::{latest::prelude::*, VersionedResponse, VersionedXcm}; use xcm_executor::traits::WeightBounds; #[test] fn basic_buy_fees_message_executes() { sp_tracing::try_init_simple(); let mut 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_polkadot_block_builder(); let execute = construct_extrinsic( &client, polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute { message: Box::new(VersionedXcm::from(msg)), max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), }), sp_keyring::Sr25519Keyring::Alice, 0, ); block_builder.push_polkadot_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(sp_consensus::BlockOrigin::Own, block)) .expect("imports the block"); client.state_at(block_hash).expect("state should exist").inspect_state(|| { assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!( r.event, polkadot_test_runtime::RuntimeEvent::Xcm(pallet_xcm::Event::Attempted { outcome: Outcome::Complete(_) }), ))); }); } #[test] fn transact_recursion_limit_works() { sp_tracing::try_init_simple(); let mut client = TestClientBuilder::new().build(); let base_xcm = |call: polkadot_test_runtime::RuntimeCall| { Xcm(vec![ WithdrawAsset((Here, 1_000).into()), BuyExecution { fees: (Here, 1).into(), weight_limit: Unlimited }, Transact { origin_kind: OriginKind::Native, require_weight_at_most: call.get_dispatch_info().weight, call: call.encode().into(), }, ]) }; 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 = sp_runtime::DispatchError::Module(sp_runtime::ModuleError { index: 27, error: [24, 0, 0, 0], message: Some("LocalExecutionIncomplete"), }) .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).unwrap(); call = Some(polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute { message: Box::new(VersionedXcm::from(msg.clone())), max_weight, })); } let mut block_builder = client.init_polkadot_block_builder(); let execute = construct_extrinsic(&client, call.unwrap(), sp_keyring::Sr25519Keyring::Alice, 0); block_builder.push_polkadot_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(sp_consensus::BlockOrigin::Own, block)) .expect("imports the block"); client.state_at(block_hash).expect("state should exist").inspect_state(|| { let events = polkadot_test_runtime::System::events(); // verify 10 pallet_xcm calls were successful assert_eq!( polkadot_test_runtime::System::events() .iter() .filter(|r| matches!( r.event, polkadot_test_runtime::RuntimeEvent::Xcm(pallet_xcm::Event::Attempted { outcome: Outcome::Complete(_) }), )) .count(), 10 ); // verify transaction fees have been paid assert!(events.iter().any(|r| matches!( &r.event, polkadot_test_runtime::RuntimeEvent::TransactionPayment( pallet_transaction_payment::Event::TransactionFeePaid { who: payer, .. } ) if *payer == sp_keyring::Sr25519Keyring::Alice.into(), ))); }); } #[test] fn query_response_fires() { use pallet_test_notifier::Event::*; use pallet_xcm::QueryStatus; use polkadot_test_runtime::RuntimeEvent::TestNotifier; sp_tracing::try_init_simple(); let mut client = TestClientBuilder::new().build(); let mut block_builder = client.init_polkadot_block_builder(); let execute = construct_extrinsic( &client, polkadot_test_runtime::RuntimeCall::TestNotifier( pallet_test_notifier::Call::prepare_new_query {}, ), sp_keyring::Sr25519Keyring::Alice, 0, ); block_builder.push_polkadot_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(sp_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 polkadot_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_polkadot_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, polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute { message: msg, max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), }), sp_keyring::Sr25519Keyring::Alice, 1, ); block_builder.push_polkadot_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(sp_consensus::BlockOrigin::Own, block)) .expect("imports the block"); client.state_at(block_hash).expect("state should exist").inspect_state(|| { assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!( r.event, polkadot_test_runtime::RuntimeEvent::Xcm(pallet_xcm::Event::ResponseReady { query_id: q, response: Response::ExecutionResult(None), }) if q == query_id, ))); assert_eq!( polkadot_test_runtime::Xcm::query(query_id), Some(QueryStatus::Ready { response: VersionedResponse::V3(Response::ExecutionResult(None)), at: 2u32.into() }), ) }); } #[test] fn query_response_elicits_handler() { use pallet_test_notifier::Event::*; use polkadot_test_runtime::RuntimeEvent::TestNotifier; sp_tracing::try_init_simple(); let mut client = TestClientBuilder::new().build(); let mut block_builder = client.init_polkadot_block_builder(); let execute = construct_extrinsic( &client, polkadot_test_runtime::RuntimeCall::TestNotifier( pallet_test_notifier::Call::prepare_new_notify_query {}, ), sp_keyring::Sr25519Keyring::Alice, 0, ); block_builder.push_polkadot_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(sp_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 polkadot_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_polkadot_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, polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute { message: Box::new(VersionedXcm::from(msg)), max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), }), sp_keyring::Sr25519Keyring::Alice, 1, ); block_builder.push_polkadot_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(sp_consensus::BlockOrigin::Own, block)) .expect("imports the block"); client.state_at(block_hash).expect("state should exist").inspect_state(|| { assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!( r.event, TestNotifier(ResponseReceived( MultiLocation { parents: 0, interior: X1(Junction::AccountId32 { .. }) }, q, Response::ExecutionResult(None), )) if q == query_id, ))); }); }