referrerpolicy=no-referrer-when-downgrade

staging_xcm_builder/
barriers.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//! Various implementations for `ShouldExecute`.
18
19use crate::{CreateMatcher, MatchXcm};
20use core::{cell::Cell, marker::PhantomData, ops::ControlFlow, result::Result};
21use frame_support::{
22	ensure,
23	traits::{Contains, ContainsPair, Get, Nothing, ProcessMessageError},
24};
25use polkadot_parachain_primitives::primitives::IsSystem;
26use xcm::prelude::*;
27use xcm_executor::traits::{CheckSuspension, DenyExecution, OnResponse, Properties, ShouldExecute};
28
29/// Execution barrier that just takes `max_weight` from `properties.weight_credit`.
30///
31/// Useful to allow XCM execution by local chain users via extrinsics.
32/// E.g. `pallet_xcm::reserve_asset_transfer` to transfer a reserve asset
33/// out of the local chain to another one.
34pub struct TakeWeightCredit;
35impl ShouldExecute for TakeWeightCredit {
36	fn should_execute<RuntimeCall>(
37		origin: &Location,
38		instructions: &mut [Instruction<RuntimeCall>],
39		max_weight: Weight,
40		properties: &mut Properties,
41	) -> Result<(), ProcessMessageError> {
42		tracing::trace!(
43			target: "xcm::barriers",
44			?origin,
45			?instructions,
46			?max_weight,
47			?properties,
48			"TakeWeightCredit"
49		);
50		properties.weight_credit = properties
51			.weight_credit
52			.checked_sub(&max_weight)
53			.ok_or(ProcessMessageError::Overweight(max_weight))?;
54		Ok(())
55	}
56}
57
58const MAX_ASSETS_FOR_BUY_EXECUTION: usize = 2;
59
60/// Allows execution from `origin` if it is contained in `T` (i.e. `T::Contains(origin)`) taking
61/// payments into account.
62///
63/// Only allows for `WithdrawAsset`, `ReceiveTeleportedAsset`, `ReserveAssetDeposited` and
64/// `ClaimAsset` XCMs because they are the only ones that place assets in the Holding Register to
65/// pay for execution.
66pub struct AllowTopLevelPaidExecutionFrom<T>(PhantomData<T>);
67impl<T: Contains<Location>> ShouldExecute for AllowTopLevelPaidExecutionFrom<T> {
68	fn should_execute<RuntimeCall>(
69		origin: &Location,
70		instructions: &mut [Instruction<RuntimeCall>],
71		max_weight: Weight,
72		properties: &mut Properties,
73	) -> Result<(), ProcessMessageError> {
74		tracing::trace!(
75			target: "xcm::barriers",
76			?origin,
77			?instructions,
78			?max_weight,
79			?properties,
80			"AllowTopLevelPaidExecutionFrom",
81		);
82
83		ensure!(T::contains(origin), ProcessMessageError::Unsupported);
84		// We will read up to 5 instructions. This allows up to 3 `ClearOrigin` instructions. We
85		// allow for more than one since anything beyond the first is a no-op and it's conceivable
86		// that composition of operations might result in more than one being appended.
87		let end = instructions.len().min(5);
88		instructions[..end]
89			.matcher()
90			.match_next_inst(|inst| match inst {
91				WithdrawAsset(ref assets) |
92				ReceiveTeleportedAsset(ref assets) |
93				ReserveAssetDeposited(ref assets) |
94				ClaimAsset { ref assets, .. } =>
95					if assets.len() <= MAX_ASSETS_FOR_BUY_EXECUTION {
96						Ok(())
97					} else {
98						Err(ProcessMessageError::BadFormat)
99					},
100				_ => Err(ProcessMessageError::BadFormat),
101			})?
102			.skip_inst_while(|inst| {
103				matches!(inst, ClearOrigin | AliasOrigin(..)) ||
104					matches!(inst, DescendOrigin(child) if child != &Here) ||
105					matches!(inst, SetHints { .. })
106			})?
107			.match_next_inst(|inst| match inst {
108				BuyExecution { weight_limit: Limited(ref mut weight), .. }
109					if weight.all_gte(max_weight) =>
110				{
111					*weight = max_weight;
112					Ok(())
113				},
114				BuyExecution { ref mut weight_limit, .. } if weight_limit == &Unlimited => {
115					*weight_limit = Limited(max_weight);
116					Ok(())
117				},
118				PayFees { .. } => Ok(()),
119				_ => Err(ProcessMessageError::Overweight(max_weight)),
120			})?;
121		Ok(())
122	}
123}
124
125/// A derivative barrier, which scans the first `MaxPrefixes` instructions for origin-alterers and
126/// then evaluates `should_execute` of the `InnerBarrier` based on the remaining instructions and
127/// the newly computed origin.
128///
129/// This effectively allows for the possibility of distinguishing an origin which is acting as a
130/// router for its derivative locations (or as a bridge for a remote location) and an origin which
131/// is actually trying to send a message for itself. In the former case, the message will be
132/// prefixed with origin-mutating instructions.
133///
134/// Any barriers which should be interpreted based on the computed origin rather than the original
135/// message origin should be subject to this. This is the case for most barriers since the
136/// effective origin is generally more important than the routing origin. Any other barriers, and
137/// especially those which should be interpreted only the routing origin should not be subject to
138/// this.
139///
140/// E.g.
141/// ```nocompile
142/// type MyBarrier = (
143/// 	TakeWeightCredit,
144/// 	AllowTopLevelPaidExecutionFrom<DirectCustomerLocations>,
145/// 	WithComputedOrigin<(
146/// 		AllowTopLevelPaidExecutionFrom<DerivativeCustomerLocations>,
147/// 		AllowUnpaidExecutionFrom<ParentLocation>,
148/// 		AllowSubscriptionsFrom<AllowedSubscribers>,
149/// 		AllowKnownQueryResponses<TheResponseHandler>,
150/// 	)>,
151/// );
152/// ```
153///
154/// In the above example, `AllowUnpaidExecutionFrom` appears once underneath
155/// `WithComputedOrigin`. This is in order to distinguish between messages which are notionally
156/// from a derivative location of `ParentLocation` but that just happened to be sent via
157/// `ParentLocation` rather than messages that were sent by the parent.
158///
159/// Similarly `AllowTopLevelPaidExecutionFrom` appears twice: once inside of `WithComputedOrigin`
160/// where we provide the list of origins which are derivative origins, and then secondly outside
161/// of `WithComputedOrigin` where we provide the list of locations which are direct origins. It's
162/// reasonable for these lists to be merged into one and that used both inside and out.
163///
164/// Finally, we see `AllowSubscriptionsFrom` and `AllowKnownQueryResponses` are both inside of
165/// `WithComputedOrigin`. This means that if a message begins with origin-mutating instructions,
166/// then it must be the finally computed origin which we accept subscriptions or expect a query
167/// response from. For example, even if an origin appeared in the `AllowedSubscribers` list, we
168/// would ignore this rule if it began with origin mutators and they changed the origin to something
169/// which was not on the list.
170pub struct WithComputedOrigin<InnerBarrier, LocalUniversal, MaxPrefixes>(
171	PhantomData<(InnerBarrier, LocalUniversal, MaxPrefixes)>,
172);
173impl<InnerBarrier: ShouldExecute, LocalUniversal: Get<InteriorLocation>, MaxPrefixes: Get<u32>>
174	ShouldExecute for WithComputedOrigin<InnerBarrier, LocalUniversal, MaxPrefixes>
175{
176	fn should_execute<Call>(
177		origin: &Location,
178		instructions: &mut [Instruction<Call>],
179		max_weight: Weight,
180		properties: &mut Properties,
181	) -> Result<(), ProcessMessageError> {
182		tracing::trace!(
183			target: "xcm::barriers",
184			?origin,
185			?instructions,
186			?max_weight,
187			?properties,
188			"WithComputedOrigin"
189		);
190		let mut actual_origin = origin.clone();
191		let skipped = Cell::new(0usize);
192		// NOTE: We do not check the validity of `UniversalOrigin` here, meaning that a malicious
193		// origin could place a `UniversalOrigin` in order to spoof some location which gets free
194		// execution. This technical could get it past the barrier condition, but the execution
195		// would instantly fail since the first instruction would cause an error with the
196		// invalid UniversalOrigin.
197		instructions.matcher().match_next_inst_while(
198			|_| skipped.get() < MaxPrefixes::get() as usize,
199			|inst| {
200				match inst {
201					UniversalOrigin(new_global) => {
202						// Note the origin is *relative to local consensus*! So we need to escape
203						// local consensus with the `parents` before diving in into the
204						// `universal_location`.
205						actual_origin =
206							Junctions::from([*new_global]).relative_to(&LocalUniversal::get());
207					},
208					DescendOrigin(j) => {
209						let Ok(_) = actual_origin.append_with(j.clone()) else {
210							return Err(ProcessMessageError::Unsupported)
211						};
212					},
213					_ => return Ok(ControlFlow::Break(())),
214				};
215				skipped.set(skipped.get() + 1);
216				Ok(ControlFlow::Continue(()))
217			},
218		)?;
219		InnerBarrier::should_execute(
220			&actual_origin,
221			&mut instructions[skipped.get()..],
222			max_weight,
223			properties,
224		)
225	}
226}
227
228/// Sets the message ID to `t` using a `SetTopic(t)` in the last position if present.
229///
230/// Note that the message ID does not necessarily have to be unique; it is the
231/// sender's responsibility to ensure uniqueness.
232///
233/// Requires some inner barrier to pass on the rest of the message.
234pub struct TrailingSetTopicAsId<InnerBarrier>(PhantomData<InnerBarrier>);
235impl<InnerBarrier: ShouldExecute> ShouldExecute for TrailingSetTopicAsId<InnerBarrier> {
236	fn should_execute<Call>(
237		origin: &Location,
238		instructions: &mut [Instruction<Call>],
239		max_weight: Weight,
240		properties: &mut Properties,
241	) -> Result<(), ProcessMessageError> {
242		tracing::trace!(
243			target: "xcm::barriers",
244			?origin,
245			?instructions,
246			?max_weight,
247			?properties,
248			"TrailingSetTopicAsId"
249		);
250		let until = if let Some(SetTopic(t)) = instructions.last() {
251			properties.message_id = Some(*t);
252			instructions.len() - 1
253		} else {
254			instructions.len()
255		};
256		InnerBarrier::should_execute(&origin, &mut instructions[..until], max_weight, properties)
257	}
258}
259
260/// Barrier condition that allows for a `SuspensionChecker` that controls whether or not the XCM
261/// executor will be suspended from executing the given XCM.
262pub struct RespectSuspension<Inner, SuspensionChecker>(PhantomData<(Inner, SuspensionChecker)>);
263impl<Inner, SuspensionChecker> ShouldExecute for RespectSuspension<Inner, SuspensionChecker>
264where
265	Inner: ShouldExecute,
266	SuspensionChecker: CheckSuspension,
267{
268	fn should_execute<Call>(
269		origin: &Location,
270		instructions: &mut [Instruction<Call>],
271		max_weight: Weight,
272		properties: &mut Properties,
273	) -> Result<(), ProcessMessageError> {
274		if SuspensionChecker::is_suspended(origin, instructions, max_weight, properties) {
275			Err(ProcessMessageError::Yield)
276		} else {
277			Inner::should_execute(origin, instructions, max_weight, properties)
278		}
279	}
280}
281
282/// Allows execution from any origin that is contained in `T` (i.e. `T::Contains(origin)`).
283///
284/// Use only for executions from completely trusted origins, from which no permissionless messages
285/// can be sent.
286pub struct AllowUnpaidExecutionFrom<T>(PhantomData<T>);
287impl<T: Contains<Location>> ShouldExecute for AllowUnpaidExecutionFrom<T> {
288	fn should_execute<RuntimeCall>(
289		origin: &Location,
290		instructions: &mut [Instruction<RuntimeCall>],
291		max_weight: Weight,
292		properties: &mut Properties,
293	) -> Result<(), ProcessMessageError> {
294		tracing::trace!(
295			target: "xcm::barriers",
296			?origin, ?instructions, ?max_weight, ?properties,
297			"AllowUnpaidExecutionFrom"
298		);
299		ensure!(T::contains(origin), ProcessMessageError::Unsupported);
300		Ok(())
301	}
302}
303
304/// Allows execution from any origin that is contained in `T` (i.e. `T::Contains(origin)`) if the
305/// message explicitly includes the `UnpaidExecution` instruction.
306///
307/// Use only for executions from trusted origin groups.
308///
309/// Allows for the message to receive teleports or reserve asset transfers and altering
310/// the origin before indicating `UnpaidExecution`.
311///
312/// Origin altering instructions are executed so the barrier can more accurately reject messages
313/// whose effective origin at the time of calling `UnpaidExecution` is not allowed.
314/// This means `T` will be checked against the actual origin _after_ being modified by prior
315/// instructions.
316///
317/// In order to execute the `AliasOrigin` instruction, the `Aliasers` type should be set to the same
318/// `Aliasers` item in the XCM configuration. If it isn't, then all messages with an `AliasOrigin`
319/// instruction will be rejected.
320pub struct AllowExplicitUnpaidExecutionFrom<T, Aliasers = Nothing>(PhantomData<(T, Aliasers)>);
321impl<T: Contains<Location>, Aliasers: ContainsPair<Location, Location>> ShouldExecute
322	for AllowExplicitUnpaidExecutionFrom<T, Aliasers>
323{
324	fn should_execute<Call>(
325		origin: &Location,
326		instructions: &mut [Instruction<Call>],
327		max_weight: Weight,
328		properties: &mut Properties,
329	) -> Result<(), ProcessMessageError> {
330		tracing::trace!(
331			target: "xcm::barriers",
332			?origin, ?instructions, ?max_weight, ?properties,
333			"AllowExplicitUnpaidExecutionFrom",
334		);
335		// We will read up to 5 instructions before `UnpaidExecution`.
336		// This allows up to 3 asset transfer instructions, thus covering all possible transfer
337		// types, followed by a potential origin altering instruction, and a potential `SetHints`.
338		let mut actual_origin = origin.clone();
339		let processed = Cell::new(0usize);
340		let instructions_to_process = 5;
341		instructions
342			.matcher()
343			// We skip set hints and all types of asset transfer instructions.
344			.match_next_inst_while(
345				|inst| {
346					processed.get() < instructions_to_process &&
347						matches!(
348							inst,
349							ReceiveTeleportedAsset(_) |
350								ReserveAssetDeposited(_) | WithdrawAsset(_) |
351								SetHints { .. }
352						)
353				},
354				|_| {
355					processed.set(processed.get() + 1);
356					Ok(ControlFlow::Continue(()))
357				},
358			)?
359			// Then we go through all origin altering instructions and we
360			// alter the original origin.
361			.match_next_inst_while(
362				|_| processed.get() < instructions_to_process,
363				|inst| {
364					match inst {
365						ClearOrigin => {
366							// We don't support the `ClearOrigin` instruction since we always need
367							// to know the origin to know if it's allowed unpaid execution.
368							return Err(ProcessMessageError::Unsupported);
369						},
370						AliasOrigin(target) =>
371							if Aliasers::contains(&actual_origin, &target) {
372								actual_origin = target.clone();
373							} else {
374								return Err(ProcessMessageError::Unsupported);
375							},
376						DescendOrigin(child) if child != &Here => {
377							let Ok(_) = actual_origin.append_with(child.clone()) else {
378								return Err(ProcessMessageError::Unsupported);
379							};
380						},
381						_ => return Ok(ControlFlow::Break(())),
382					};
383					processed.set(processed.get() + 1);
384					Ok(ControlFlow::Continue(()))
385				},
386			)?
387			// We finally match on the required `UnpaidExecution` instruction.
388			.match_next_inst(|inst| match inst {
389				UnpaidExecution { weight_limit: Limited(m), .. } if m.all_gte(max_weight) => Ok(()),
390				UnpaidExecution { weight_limit: Unlimited, .. } => Ok(()),
391				_ => Err(ProcessMessageError::Overweight(max_weight)),
392			})?;
393
394		// After processing all the instructions, `actual_origin` was modified and we
395		// check if it's allowed to have unpaid execution.
396		ensure!(T::contains(&actual_origin), ProcessMessageError::Unsupported);
397
398		Ok(())
399	}
400}
401
402/// Allows a message only if it is from a system-level child parachain.
403pub struct IsChildSystemParachain<ParaId>(PhantomData<ParaId>);
404impl<ParaId: IsSystem + From<u32>> Contains<Location> for IsChildSystemParachain<ParaId> {
405	fn contains(l: &Location) -> bool {
406		matches!(
407			l.interior().as_slice(),
408			[Junction::Parachain(id)]
409				if ParaId::from(*id).is_system() && l.parent_count() == 0,
410		)
411	}
412}
413
414/// Matches if the given location is a system-level sibling parachain.
415pub struct IsSiblingSystemParachain<ParaId, SelfParaId>(PhantomData<(ParaId, SelfParaId)>);
416impl<ParaId: IsSystem + From<u32> + Eq, SelfParaId: Get<ParaId>> Contains<Location>
417	for IsSiblingSystemParachain<ParaId, SelfParaId>
418{
419	fn contains(l: &Location) -> bool {
420		matches!(
421			l.unpack(),
422			(1, [Junction::Parachain(id)])
423				if SelfParaId::get() != ParaId::from(*id) && ParaId::from(*id).is_system(),
424		)
425	}
426}
427
428/// Matches if the given location contains only the specified amount of parents and no interior
429/// junctions.
430pub struct IsParentsOnly<Count>(PhantomData<Count>);
431impl<Count: Get<u8>> Contains<Location> for IsParentsOnly<Count> {
432	fn contains(t: &Location) -> bool {
433		t.contains_parents_only(Count::get())
434	}
435}
436
437/// Allows only messages if the generic `ResponseHandler` expects them via `expecting_response`.
438pub struct AllowKnownQueryResponses<ResponseHandler>(PhantomData<ResponseHandler>);
439impl<ResponseHandler: OnResponse> ShouldExecute for AllowKnownQueryResponses<ResponseHandler> {
440	fn should_execute<RuntimeCall>(
441		origin: &Location,
442		instructions: &mut [Instruction<RuntimeCall>],
443		max_weight: Weight,
444		properties: &mut Properties,
445	) -> Result<(), ProcessMessageError> {
446		tracing::trace!(
447			target: "xcm::barriers",
448			?origin, ?instructions, ?max_weight, ?properties,
449			"AllowKnownQueryResponses"
450		);
451		instructions
452			.matcher()
453			.assert_remaining_insts(1)?
454			.match_next_inst(|inst| match inst {
455				QueryResponse { query_id, querier, .. }
456					if ResponseHandler::expecting_response(origin, *query_id, querier.as_ref()) =>
457					Ok(()),
458				_ => Err(ProcessMessageError::BadFormat),
459			})?;
460		Ok(())
461	}
462}
463
464/// Allows execution from `origin` if it is just a straight `SubscribeVersion` or
465/// `UnsubscribeVersion` instruction.
466pub struct AllowSubscriptionsFrom<T>(PhantomData<T>);
467impl<T: Contains<Location>> ShouldExecute for AllowSubscriptionsFrom<T> {
468	fn should_execute<RuntimeCall>(
469		origin: &Location,
470		instructions: &mut [Instruction<RuntimeCall>],
471		max_weight: Weight,
472		properties: &mut Properties,
473	) -> Result<(), ProcessMessageError> {
474		tracing::trace!(
475			target: "xcm::barriers",
476			?origin, ?instructions, ?max_weight, ?properties,
477			"AllowSubscriptionsFrom",
478		);
479		ensure!(T::contains(origin), ProcessMessageError::Unsupported);
480		instructions
481			.matcher()
482			.assert_remaining_insts(1)?
483			.match_next_inst(|inst| match inst {
484				SubscribeVersion { .. } | UnsubscribeVersion => Ok(()),
485				_ => Err(ProcessMessageError::BadFormat),
486			})?;
487		Ok(())
488	}
489}
490
491/// Allows execution for the Relay Chain origin (represented as `Location::parent()`) if it is just
492/// a straight `HrmpNewChannelOpenRequest`, `HrmpChannelAccepted`, or `HrmpChannelClosing`
493/// instruction.
494///
495/// Note: This barrier fulfills safety recommendations for the mentioned instructions - see their
496/// documentation.
497pub struct AllowHrmpNotificationsFromRelayChain;
498impl ShouldExecute for AllowHrmpNotificationsFromRelayChain {
499	fn should_execute<RuntimeCall>(
500		origin: &Location,
501		instructions: &mut [Instruction<RuntimeCall>],
502		max_weight: Weight,
503		properties: &mut Properties,
504	) -> Result<(), ProcessMessageError> {
505		tracing::trace!(
506			target: "xcm::barriers",
507			?origin, ?instructions, ?max_weight, ?properties,
508			"AllowHrmpNotificationsFromRelayChain"
509		);
510		// accept only the Relay Chain
511		ensure!(matches!(origin.unpack(), (1, [])), ProcessMessageError::Unsupported);
512		// accept only HRMP notifications and nothing else
513		instructions
514			.matcher()
515			.assert_remaining_insts(1)?
516			.match_next_inst(|inst| match inst {
517				HrmpNewChannelOpenRequest { .. } |
518				HrmpChannelAccepted { .. } |
519				HrmpChannelClosing { .. } => Ok(()),
520				_ => Err(ProcessMessageError::BadFormat),
521			})?;
522		Ok(())
523	}
524}
525
526/// Deny executing the XCM if it matches any of the Deny filter regardless of anything else.
527/// If it passes the Deny, and matches one of the Allow cases then it is let through.
528pub struct DenyThenTry<Deny, Allow>(PhantomData<Deny>, PhantomData<Allow>)
529where
530	Deny: DenyExecution,
531	Allow: ShouldExecute;
532
533impl<Deny, Allow> ShouldExecute for DenyThenTry<Deny, Allow>
534where
535	Deny: DenyExecution,
536	Allow: ShouldExecute,
537{
538	fn should_execute<RuntimeCall>(
539		origin: &Location,
540		message: &mut [Instruction<RuntimeCall>],
541		max_weight: Weight,
542		properties: &mut Properties,
543	) -> Result<(), ProcessMessageError> {
544		Deny::deny_execution(origin, message, max_weight, properties)?;
545		Allow::should_execute(origin, message, max_weight, properties)
546	}
547}
548
549// See issue <https://github.com/paritytech/polkadot/issues/5233>
550pub struct DenyReserveTransferToRelayChain;
551impl DenyExecution for DenyReserveTransferToRelayChain {
552	fn deny_execution<RuntimeCall>(
553		origin: &Location,
554		message: &mut [Instruction<RuntimeCall>],
555		_max_weight: Weight,
556		_properties: &mut Properties,
557	) -> Result<(), ProcessMessageError> {
558		message.matcher().match_next_inst_while(
559			|_| true,
560			|inst| match inst {
561				InitiateReserveWithdraw {
562					reserve: Location { parents: 1, interior: Here },
563					..
564				} |
565				DepositReserveAsset { dest: Location { parents: 1, interior: Here }, .. } |
566				TransferReserveAsset { dest: Location { parents: 1, interior: Here }, .. } => {
567					Err(ProcessMessageError::Unsupported) // Deny
568				},
569
570				// An unexpected reserve transfer has arrived from the Relay Chain. Generally,
571				// `IsReserve` should not allow this, but we just log it here.
572				ReserveAssetDeposited { .. }
573					if matches!(origin, Location { parents: 1, interior: Here }) =>
574				{
575					tracing::debug!(
576						target: "xcm::barriers",
577						"Unexpected ReserveAssetDeposited from the Relay Chain",
578					);
579					Ok(ControlFlow::Continue(()))
580				},
581
582				_ => Ok(ControlFlow::Continue(())),
583			},
584		)?;
585		Ok(())
586	}
587}
588
589environmental::environmental!(recursion_count: u8);
590
591/// Denies execution if the XCM contains instructions not meant to run on this chain,
592/// first checking at the top-level and then **recursively**.
593///
594/// This barrier only applies to **locally executed** XCM instructions (`SetAppendix`,
595/// `SetErrorHandler`, and `ExecuteWithOrigin`). Remote parts of the XCM are expected to be
596/// validated by the receiving chain's barrier.
597///
598/// Note: Ensures that restricted instructions do not execute on the local chain, enforcing stricter
599/// execution policies while allowing remote chains to enforce their own rules.
600pub struct DenyRecursively<Inner>(PhantomData<Inner>);
601
602impl<Inner: DenyExecution> DenyRecursively<Inner> {
603	/// Recursively applies the deny filter to a nested XCM.
604	///
605	/// Ensures that restricted instructions are blocked at any depth within the XCM.
606	/// Uses a **recursion counter** to prevent stack overflows from deep nesting.
607	fn deny_recursively<RuntimeCall>(
608		origin: &Location,
609		xcm: &mut Xcm<RuntimeCall>,
610		max_weight: Weight,
611		properties: &mut Properties,
612	) -> Result<ControlFlow<()>, ProcessMessageError> {
613		// Initialise recursion counter for this execution context.
614		recursion_count::using_once(&mut 1, || {
615			// Prevent stack overflow by enforcing a recursion depth limit.
616			recursion_count::with(|count| {
617				if *count > xcm_executor::RECURSION_LIMIT {
618					tracing::debug!(
619                    	target: "xcm::barriers",
620                    	"Recursion limit exceeded (count: {count}), origin: {:?}, xcm: {:?}, max_weight: {:?}, properties: {:?}",
621                    	origin, xcm, max_weight, properties
622                	);
623					return None;
624				}
625				*count = count.saturating_add(1);
626				Some(())
627			}).flatten().ok_or(ProcessMessageError::StackLimitReached)?;
628
629			// Ensure the counter is decremented even if an early return occurs.
630			sp_core::defer! {
631				recursion_count::with(|count| {
632					*count = count.saturating_sub(1);
633				});
634			}
635
636			// Recursively check the nested XCM instructions.
637			Self::deny_execution(origin, xcm.inner_mut(), max_weight, properties)
638		})?;
639
640		Ok(ControlFlow::Continue(()))
641	}
642}
643
644impl<Inner: DenyExecution> DenyExecution for DenyRecursively<Inner> {
645	/// Denies execution of restricted local nested XCM instructions.
646	///
647	/// This checks for `SetAppendix`, `SetErrorHandler`, and `ExecuteWithOrigin` instruction
648	/// applying the deny filter **recursively** to any nested XCMs found.
649	fn deny_execution<RuntimeCall>(
650		origin: &Location,
651		instructions: &mut [Instruction<RuntimeCall>],
652		max_weight: Weight,
653		properties: &mut Properties,
654	) -> Result<(), ProcessMessageError> {
655		// First, check if the top-level message should be denied.
656		Inner::deny_execution(origin, instructions, max_weight, properties).inspect_err(|e| {
657			tracing::debug!(
658				target: "xcm::barriers",
659				"DenyRecursively::Inner denied execution, origin: {:?}, instructions: {:?}, max_weight: {:?}, properties: {:?}, error: {:?}",
660				origin, instructions, max_weight, properties, e
661			);
662		})?;
663
664		// If the top-level check passes, check nested instructions recursively.
665		instructions.matcher().match_next_inst_while(
666			|_| true,
667			|inst| match inst {
668				SetAppendix(nested_xcm) |
669				SetErrorHandler(nested_xcm) |
670				ExecuteWithOrigin { xcm: nested_xcm, .. } => Self::deny_recursively::<RuntimeCall>(
671					origin, nested_xcm, max_weight, properties,
672				),
673				_ => Ok(ControlFlow::Continue(())),
674			},
675		)?;
676
677		// Permit everything else
678		Ok(())
679	}
680}