referrerpolicy=no-referrer-when-downgrade

staging_xcm_builder/
matcher.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//! XCM matcher API, used primarily for writing barrier conditions.
18
19use core::ops::ControlFlow;
20use frame_support::traits::ProcessMessageError;
21use xcm::latest::{Instruction, Location};
22
23/// Creates an instruction matcher from an XCM. Since XCM versions differ, we need to make a trait
24/// here to unify the interfaces among them.
25pub trait CreateMatcher {
26	/// The concrete matcher type.
27	type Matcher;
28
29	/// Method that creates and returns the matcher type from `Self`.
30	fn matcher(self) -> Self::Matcher;
31}
32
33impl<'a, Call> CreateMatcher for &'a mut [Instruction<Call>] {
34	type Matcher = Matcher<'a, Call>;
35
36	fn matcher(self) -> Self::Matcher {
37		let total_inst = self.len();
38
39		Matcher { xcm: self, current_idx: 0, total_inst }
40	}
41}
42
43/// API that allows to pattern-match against anything that is contained within an XCM.
44///
45/// The intended usage of the matcher API is to enable the ability to chain successive methods of
46/// this trait together, along with the ? operator for the purpose of facilitating the writing,
47/// maintenance and auditability of XCM barriers.
48///
49/// Example:
50/// ```rust
51/// use frame_support::traits::ProcessMessageError;
52/// use xcm::latest::Instruction;
53/// use staging_xcm_builder::{CreateMatcher, MatchXcm};
54///
55/// let mut msg = [Instruction::<()>::ClearOrigin];
56/// let res = msg
57/// 	.matcher()
58/// 	.assert_remaining_insts(1)?
59/// 	.match_next_inst(|inst| match inst {
60/// 		Instruction::<()>::ClearOrigin => Ok(()),
61/// 		_ => Err(ProcessMessageError::BadFormat),
62/// 	});
63/// assert!(res.is_ok());
64///
65/// Ok::<(), ProcessMessageError>(())
66/// ```
67pub trait MatchXcm {
68	/// The concrete instruction type. Necessary to specify as it changes between XCM versions.
69	type Inst;
70	/// The `Location` type. Necessary to specify as it changes between XCM versions.
71	type Loc;
72	/// The error type to throw when errors happen during matching.
73	type Error;
74
75	/// Returns success if the number of instructions that still have not been iterated over
76	/// equals `n`, otherwise returns an error.
77	fn assert_remaining_insts(self, n: usize) -> Result<Self, Self::Error>
78	where
79		Self: Sized;
80
81	/// Accepts a closure `f` that contains an argument signifying the next instruction to be
82	/// iterated over. The closure can then be used to check whether the instruction matches a
83	/// given condition, and can also be used to mutate the fields of an instruction.
84	///
85	/// The closure `f` returns success when the instruction passes the condition, otherwise it
86	/// returns an error, which will ultimately be returned by this function.
87	fn match_next_inst<F>(self, f: F) -> Result<Self, Self::Error>
88	where
89		Self: Sized,
90		F: FnMut(&mut Self::Inst) -> Result<(), Self::Error>;
91
92	/// Attempts to continuously iterate through the instructions while applying `f` to each of
93	/// them, until either the last instruction or `cond` returns false.
94	///
95	/// If `f` returns an error, then iteration halts and the function returns that error.
96	/// Otherwise, `f` returns a `ControlFlow` which signifies whether the iteration breaks or
97	/// continues.
98	fn match_next_inst_while<C, F>(self, cond: C, f: F) -> Result<Self, Self::Error>
99	where
100		Self: Sized,
101		C: Fn(&Self::Inst) -> bool,
102		F: FnMut(&mut Self::Inst) -> Result<ControlFlow<()>, Self::Error>;
103
104	/// Iterate instructions forward until `cond` returns false. When there are no more instructions
105	/// to be read, an error is returned.
106	fn skip_inst_while<C>(self, cond: C) -> Result<Self, Self::Error>
107	where
108		Self: Sized,
109		C: Fn(&Self::Inst) -> bool,
110	{
111		Self::match_next_inst_while(self, cond, |_| Ok(ControlFlow::Continue(())))
112	}
113}
114
115/// Struct created from calling `fn matcher()` on a mutable slice of `Instruction`s.
116///
117/// Implements `MatchXcm` to allow an iterator-like API to match against each `Instruction`
118/// contained within the slice, which facilitates the building of XCM barriers.
119pub struct Matcher<'a, Call> {
120	pub(crate) xcm: &'a mut [Instruction<Call>],
121	pub(crate) current_idx: usize,
122	pub(crate) total_inst: usize,
123}
124
125impl<'a, Call> MatchXcm for Matcher<'a, Call> {
126	type Error = ProcessMessageError;
127	type Inst = Instruction<Call>;
128	type Loc = Location;
129
130	fn assert_remaining_insts(self, n: usize) -> Result<Self, Self::Error>
131	where
132		Self: Sized,
133	{
134		if self.total_inst - self.current_idx != n {
135			return Err(ProcessMessageError::BadFormat)
136		}
137
138		Ok(self)
139	}
140
141	fn match_next_inst<F>(mut self, mut f: F) -> Result<Self, Self::Error>
142	where
143		Self: Sized,
144		F: FnMut(&mut Self::Inst) -> Result<(), Self::Error>,
145	{
146		if self.current_idx < self.total_inst {
147			f(&mut self.xcm[self.current_idx])?;
148			self.current_idx += 1;
149			Ok(self)
150		} else {
151			Err(ProcessMessageError::BadFormat)
152		}
153	}
154
155	fn match_next_inst_while<C, F>(mut self, cond: C, mut f: F) -> Result<Self, Self::Error>
156	where
157		Self: Sized,
158		C: Fn(&Self::Inst) -> bool,
159		F: FnMut(&mut Self::Inst) -> Result<ControlFlow<()>, Self::Error>,
160	{
161		if self.current_idx >= self.total_inst {
162			return Err(ProcessMessageError::BadFormat)
163		}
164
165		while self.current_idx < self.total_inst && cond(&self.xcm[self.current_idx]) {
166			if let ControlFlow::Break(()) = f(&mut self.xcm[self.current_idx])? {
167				break
168			}
169			self.current_idx += 1;
170		}
171
172		Ok(self)
173	}
174}
175
176#[cfg(test)]
177mod tests {
178	use super::*;
179	use std::{vec, vec::Vec};
180	use xcm::latest::prelude::*;
181
182	#[test]
183	fn match_next_inst_works() {
184		let test_cases: Vec<(Vec<Instruction<()>>, bool)> =
185			vec![(vec![ClearOrigin], true), (vec![Trap(0)], false)];
186
187		for (mut xcm, expected) in test_cases.into_iter() {
188			let result = xcm.matcher().match_next_inst(|inst| match inst {
189				ClearOrigin => Ok(()),
190				_ => Err(ProcessMessageError::Unsupported),
191			});
192			assert_eq!(result.is_ok(), expected);
193		}
194	}
195
196	#[test]
197	fn match_next_inst_while_works() {
198		let mut xcm: Vec<Instruction<()>> = vec![ClearOrigin];
199
200		let _ = xcm
201			.matcher()
202			.match_next_inst_while(|_| true, |_| Ok(ControlFlow::Continue(())))
203			.unwrap();
204	}
205}