referrerpolicy=no-referrer-when-downgrade

xcm_executor_integration_tests/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17#![cfg(test)]
18
19use codec::Encode;
20use frame_support::weights::Weight;
21use polkadot_test_client::{
22	BlockBuilderExt, ClientBlockImportExt, DefaultTestClientBuilderExt, InitPolkadotBlockBuilder,
23	TestClientBuilder, TestClientBuilderExt,
24};
25use polkadot_test_runtime::{pallet_test_notifier, xcm_config::XcmConfig};
26use polkadot_test_service::construct_extrinsic;
27use sp_runtime::traits::Block;
28use sp_state_machine::InspectState;
29use xcm::{latest::prelude::*, VersionedResponse, VersionedXcm};
30use xcm_executor::traits::WeightBounds;
31
32#[test]
33fn basic_buy_fees_message_executes() {
34	sp_tracing::try_init_simple();
35	let client = TestClientBuilder::new().build();
36
37	let msg = Xcm(vec![
38		WithdrawAsset((Parent, 100).into()),
39		BuyExecution { fees: (Parent, 1).into(), weight_limit: Unlimited },
40		DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Parent.into() },
41	]);
42
43	let mut block_builder = client.init_polkadot_block_builder();
44
45	let execute = construct_extrinsic(
46		&client,
47		polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute {
48			message: Box::new(VersionedXcm::from(msg)),
49			max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024),
50		}),
51		sp_keyring::Sr25519Keyring::Alice,
52		0,
53	);
54
55	block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic");
56
57	let block = block_builder.build().expect("Finalizes the block").block;
58	let block_hash = block.hash();
59
60	futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block))
61		.expect("imports the block");
62
63	client.state_at(block_hash).expect("state should exist").inspect_state(|| {
64		assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!(
65			r.event,
66			polkadot_test_runtime::RuntimeEvent::Xcm(pallet_xcm::Event::Attempted {
67				outcome: Outcome::Complete { .. }
68			}),
69		)));
70	});
71}
72
73#[test]
74fn transact_recursion_limit_works() {
75	sp_tracing::try_init_simple();
76	let client = TestClientBuilder::new().build();
77
78	let base_xcm = |call: polkadot_test_runtime::RuntimeCall| {
79		Xcm(vec![
80			WithdrawAsset((Here, 1_000).into()),
81			BuyExecution { fees: (Here, 1).into(), weight_limit: Unlimited },
82			Transact {
83				origin_kind: OriginKind::Native,
84				call: call.encode().into(),
85				fallback_max_weight: None,
86			},
87		])
88	};
89	let mut call: Option<polkadot_test_runtime::RuntimeCall> = None;
90	// set up transacts with recursive depth of 11
91	for depth in (1..12).rev() {
92		let mut msg;
93		match depth {
94			// this one should fail with `XcmError::ExceedsStackLimit`
95			11 => {
96				msg = Xcm(vec![ClearOrigin]);
97			},
98			// this one checks that the inner one (depth 11) fails as expected,
99			// itself should not fail => should have outcome == Complete
100			10 => {
101				let inner_call = call.take().unwrap();
102				let expected_transact_status =
103					sp_runtime::DispatchError::Module(sp_runtime::ModuleError {
104						index: 27,
105						error: [28, 0, 40, 0], // ExceedsStackLimit
106						message: Some("LocalExecutionIncompleteWithError"),
107					})
108					.encode()
109					.into();
110				msg = base_xcm(inner_call);
111				msg.inner_mut().push(ExpectTransactStatus(expected_transact_status));
112			},
113			// these are the outer 9 calls that expect `ExpectTransactStatus(Success)`
114			d if d >= 1 && d <= 9 => {
115				let inner_call = call.take().unwrap();
116				msg = base_xcm(inner_call);
117				msg.inner_mut().push(ExpectTransactStatus(MaybeErrorCode::Success));
118			},
119			_ => unreachable!(),
120		}
121		let max_weight =
122			<XcmConfig as xcm_executor::Config>::Weigher::weight(&mut msg, Weight::MAX).unwrap();
123		call = Some(polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute {
124			message: Box::new(VersionedXcm::from(msg.clone())),
125			max_weight,
126		}));
127	}
128
129	let mut block_builder = client.init_polkadot_block_builder();
130
131	let execute = construct_extrinsic(&client, call.unwrap(), sp_keyring::Sr25519Keyring::Alice, 0);
132
133	block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic");
134
135	let block = block_builder.build().expect("Finalizes the block").block;
136	let block_hash = block.hash();
137
138	futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block))
139		.expect("imports the block");
140
141	client.state_at(block_hash).expect("state should exist").inspect_state(|| {
142		let events = polkadot_test_runtime::System::events();
143		// verify 10 pallet_xcm calls were successful
144		assert_eq!(
145			polkadot_test_runtime::System::events()
146				.iter()
147				.filter(|r| matches!(
148					r.event,
149					polkadot_test_runtime::RuntimeEvent::Xcm(pallet_xcm::Event::Attempted {
150						outcome: Outcome::Complete { .. }
151					}),
152				))
153				.count(),
154			10
155		);
156		// verify transaction fees have been paid
157		assert!(events.iter().any(|r| matches!(
158			&r.event,
159			polkadot_test_runtime::RuntimeEvent::TransactionPayment(
160				pallet_transaction_payment::Event::TransactionFeePaid {
161					who: payer,
162					..
163				}
164			) if *payer == sp_keyring::Sr25519Keyring::Alice.into(),
165		)));
166	});
167}
168
169#[test]
170fn query_response_fires() {
171	use pallet_test_notifier::Event::*;
172	use pallet_xcm::QueryStatus;
173	use polkadot_test_runtime::RuntimeEvent::TestNotifier;
174
175	sp_tracing::try_init_simple();
176	let client = TestClientBuilder::new().build();
177
178	let mut block_builder = client.init_polkadot_block_builder();
179
180	let execute = construct_extrinsic(
181		&client,
182		polkadot_test_runtime::RuntimeCall::TestNotifier(
183			pallet_test_notifier::Call::prepare_new_query {},
184		),
185		sp_keyring::Sr25519Keyring::Alice,
186		0,
187	);
188
189	block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic");
190
191	let block = block_builder.build().expect("Finalizes the block").block;
192	let block_hash = block.hash();
193
194	futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block))
195		.expect("imports the block");
196
197	let mut query_id = None;
198	client.state_at(block_hash).expect("state should exist").inspect_state(|| {
199		for r in polkadot_test_runtime::System::events().iter() {
200			match r.event {
201				TestNotifier(QueryPrepared(q)) => query_id = Some(q),
202				_ => (),
203			}
204		}
205	});
206	let query_id = query_id.unwrap();
207
208	let mut block_builder = client.init_polkadot_block_builder();
209
210	let response = Response::ExecutionResult(None);
211	let max_weight = Weight::from_parts(1_000_000, 1024 * 1024);
212	let querier = Some(Here.into());
213	let msg = Xcm(vec![QueryResponse { query_id, response, max_weight, querier }]);
214	let msg = Box::new(VersionedXcm::from(msg));
215
216	let execute = construct_extrinsic(
217		&client,
218		polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute {
219			message: msg,
220			max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024),
221		}),
222		sp_keyring::Sr25519Keyring::Alice,
223		1,
224	);
225
226	block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic");
227
228	let block = block_builder.build().expect("Finalizes the block").block;
229	let block_hash = block.hash();
230
231	futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block))
232		.expect("imports the block");
233
234	client.state_at(block_hash).expect("state should exist").inspect_state(|| {
235		assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!(
236			r.event,
237			polkadot_test_runtime::RuntimeEvent::Xcm(pallet_xcm::Event::ResponseReady {
238				query_id: q,
239				response: Response::ExecutionResult(None),
240			}) if q == query_id,
241		)));
242		assert_eq!(
243			polkadot_test_runtime::Xcm::query(&query_id),
244			Some(QueryStatus::Ready {
245				response: VersionedResponse::from(Response::ExecutionResult(None)),
246				at: 2u32.into()
247			}),
248		)
249	});
250}
251
252#[test]
253fn query_response_elicits_handler() {
254	use pallet_test_notifier::Event::*;
255	use polkadot_test_runtime::RuntimeEvent::TestNotifier;
256
257	sp_tracing::try_init_simple();
258	let client = TestClientBuilder::new().build();
259
260	let mut block_builder = client.init_polkadot_block_builder();
261
262	let execute = construct_extrinsic(
263		&client,
264		polkadot_test_runtime::RuntimeCall::TestNotifier(
265			pallet_test_notifier::Call::prepare_new_notify_query {},
266		),
267		sp_keyring::Sr25519Keyring::Alice,
268		0,
269	);
270
271	block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic");
272
273	let block = block_builder.build().expect("Finalizes the block").block;
274	let block_hash = block.hash();
275
276	futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block))
277		.expect("imports the block");
278
279	let mut query_id = None;
280	client.state_at(block_hash).expect("state should exist").inspect_state(|| {
281		for r in polkadot_test_runtime::System::events().iter() {
282			match r.event {
283				TestNotifier(NotifyQueryPrepared(q)) => query_id = Some(q),
284				_ => (),
285			}
286		}
287	});
288	let query_id = query_id.unwrap();
289
290	let mut block_builder = client.init_polkadot_block_builder();
291
292	let response = Response::ExecutionResult(None);
293	let max_weight = Weight::from_parts(1_000_000, 1024 * 1024);
294	let querier = Some(Here.into());
295	let msg = Xcm(vec![QueryResponse { query_id, response, max_weight, querier }]);
296
297	let execute = construct_extrinsic(
298		&client,
299		polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute {
300			message: Box::new(VersionedXcm::from(msg)),
301			max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024),
302		}),
303		sp_keyring::Sr25519Keyring::Alice,
304		1,
305	);
306
307	block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic");
308
309	let block = block_builder.build().expect("Finalizes the block").block;
310	let block_hash = block.hash();
311
312	futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block))
313		.expect("imports the block");
314
315	client.state_at(block_hash).expect("state should exist").inspect_state(|| {
316		assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!(
317			&r.event,
318			TestNotifier(ResponseReceived(
319				location,
320				q,
321				Response::ExecutionResult(None),
322			)) if *q == query_id && matches!(location.unpack(), (0, [Junction::AccountId32 { .. }])),
323		)));
324	});
325}
326
327/// Simulates a cross-chain message from Parachain to Parachain through Relay Chain
328/// that deposits assets into the reserve of the destination.
329/// Regression test for `DepositReserveAsset` changes in
330/// <https://github.com/paritytech/polkadot-sdk/pull/3340>
331#[test]
332fn deposit_reserve_asset_works_for_any_xcm_sender() {
333	sp_tracing::try_init_simple();
334	let client = TestClientBuilder::new().build();
335
336	// Init values for the simulated origin Parachain
337	let amount_to_send: u128 = 1_000_000_000_000;
338	let assets: Assets = (Parent, amount_to_send).into();
339	let fee_asset_item = 0;
340	let max_assets = assets.len() as u32;
341	let fees = assets.get(fee_asset_item as usize).unwrap().clone();
342	let weight_limit = Unlimited;
343	let reserve = Location::parent();
344	let dest = Location::new(1, [Parachain(2000)]);
345	let beneficiary_id = sp_keyring::Sr25519Keyring::Alice.to_account_id();
346	let beneficiary = Location::new(0, [AccountId32 { network: None, id: beneficiary_id.into() }]);
347
348	// spends up to half of fees for execution on reserve and other half for execution on
349	// destination
350	let fee1 = amount_to_send.saturating_div(2);
351	let fee2 = amount_to_send.saturating_sub(fee1);
352	let fees_half_1 = Asset::from((fees.id.clone(), Fungible(fee1)));
353	let fees_half_2 = Asset::from((fees.id.clone(), Fungible(fee2)));
354
355	let reserve_context = <XcmConfig as xcm_executor::Config>::UniversalLocation::get();
356	// identifies fee item as seen by `reserve` - to be used at reserve chain
357	let reserve_fees = fees_half_1.reanchored(&reserve, &reserve_context).unwrap();
358	// identifies fee item as seen by `dest` - to be used at destination chain
359	let dest_fees = fees_half_2.reanchored(&dest, &reserve_context).unwrap();
360	// identifies assets as seen by `reserve` - to be used at reserve chain
361	let assets_reanchored = assets.reanchored(&reserve, &reserve_context).unwrap();
362	// identifies `dest` as seen by `reserve`
363	let dest = dest.reanchored(&reserve, &reserve_context).unwrap();
364	// xcm to be executed at dest
365	let xcm_on_dest = Xcm(vec![
366		BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() },
367		DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary },
368	]);
369	// xcm to be executed at reserve
370	let msg = Xcm(vec![
371		WithdrawAsset(assets_reanchored),
372		ClearOrigin,
373		BuyExecution { fees: reserve_fees, weight_limit },
374		DepositReserveAsset { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest },
375	]);
376
377	let mut block_builder = client.init_polkadot_block_builder();
378
379	// Make the para available, so that `DMP` doesn't reject the XCM because the para is unknown.
380	let make_para_available =
381		construct_extrinsic(
382			&client,
383			polkadot_test_runtime::RuntimeCall::Sudo(pallet_sudo::Call::sudo {
384				call: Box::new(polkadot_test_runtime::RuntimeCall::System(
385					frame_system::Call::set_storage {
386						items: vec![(
387							polkadot_runtime_parachains::paras::Heads::<
388								polkadot_test_runtime::Runtime,
389							>::hashed_key_for(2000u32),
390							vec![1, 2, 3],
391						)],
392					},
393				)),
394			}),
395			sp_keyring::Sr25519Keyring::Alice,
396			0,
397		);
398
399	// Simulate execution of an incoming XCM message at the reserve chain
400	let execute = construct_extrinsic(
401		&client,
402		polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute {
403			message: Box::new(VersionedXcm::from(msg)),
404			max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024),
405		}),
406		sp_keyring::Sr25519Keyring::Alice,
407		1,
408	);
409
410	block_builder
411		.push_polkadot_extrinsic(make_para_available)
412		.expect("pushes extrinsic");
413	block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic");
414
415	let block = block_builder.build().expect("Finalizes the block").block;
416	let block_hash = block.hash();
417
418	futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block))
419		.expect("imports the block");
420
421	client.state_at(block_hash).expect("state should exist").inspect_state(|| {
422		assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!(
423			r.event,
424			polkadot_test_runtime::RuntimeEvent::Xcm(pallet_xcm::Event::Attempted {
425				outcome: Outcome::Complete { .. }
426			}),
427		)));
428	});
429}