pezstaging_xcm_builder/
barriers.rs

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