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}