-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Introduce XCM matcher for writing barriers #6756
Changes from 8 commits
83ba763
3920975
ba143e8
0f43e74
a3c86eb
9f9967e
23d0edf
5dc0fda
ddbdb88
2a0819b
c9c8fe3
5e3f859
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ | |
| #![no_std] | ||
| extern crate alloc; | ||
|
|
||
| use core::ops::ControlFlow; | ||
| use derivative::Derivative; | ||
| use parity_scale_codec::{Decode, Encode, Error as CodecError, Input, MaxEncodedLen}; | ||
| use scale_info::TypeInfo; | ||
|
|
@@ -47,6 +48,85 @@ pub const MAX_XCM_DECODE_DEPTH: u32 = 8; | |
| /// A version of XCM. | ||
| pub type Version = u32; | ||
|
|
||
| /// Creates an instruction matcher from an XCM. Since XCM versions differ, we need to make a trait | ||
| /// here to unify the interfaces among them. | ||
| pub trait CreateMatcher { | ||
| type Matcher; | ||
| fn matcher(self) -> Self::Matcher; | ||
| } | ||
|
|
||
| /// API that allows to pattern-match against anything that is contained within an XCM. | ||
| /// | ||
| /// The intended usage of the matcher API is to enable the ability to chain successive methods of | ||
| /// this trait together, along with the ? operator for the purpose of facilitating the writing, | ||
| /// maintenance and auditability of XCM barriers. | ||
| /// | ||
| /// Example: | ||
| /// ```rust | ||
| /// use xcm::{ | ||
| /// v3::{Instruction, Matcher}, | ||
| /// CreateMatcher, MatchXcm, | ||
| /// }; | ||
| /// | ||
| /// let mut msg = [Instruction::<()>::ClearOrigin]; | ||
| /// let res = msg | ||
| /// .matcher() | ||
| /// .assert_remaining_insts(1)? | ||
| /// .match_next_inst(|inst| match inst { | ||
| /// Instruction::<()>::ClearOrigin => Ok(()), | ||
| /// _ => Err(()), | ||
| /// }); | ||
| /// assert!(res.is_ok()); | ||
| /// | ||
| /// Ok::<(), ()>(()) | ||
| /// ``` | ||
| pub trait MatchXcm { | ||
| /// The concrete instruction type. Necessary to specify as it changes between XCM versions. | ||
| type Inst; | ||
| /// The `MultiLocation` type. Necessary to specify as it changes between XCM versions. | ||
| type Loc; | ||
| /// The error type to throw when errors happen during matching. | ||
| type Error; | ||
|
|
||
| /// Returns success if the number of instructions that still have not been iterated over | ||
| /// equals `n`, otherwise returns an error. | ||
| fn assert_remaining_insts(self, n: usize) -> Result<Self, Self::Error> | ||
| where | ||
| Self: Sized; | ||
|
|
||
| /// Accepts a closure `f` that contains an argument signifying the next instruction to be | ||
| /// iterated over. The closure can then be used to check whether the instruction matches a | ||
| /// given condition, and can also be used to mutate the fields of an instruction. | ||
| /// | ||
| /// The closure `f` returns success when the instruction passes the condition, otherwise it | ||
| /// returns an error, which will ultimately be returned by this function. | ||
| fn match_next_inst<F>(self, f: F) -> Result<Self, Self::Error> | ||
| where | ||
| Self: Sized, | ||
| F: FnMut(&mut Self::Inst) -> Result<(), Self::Error>; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this whole API exclusively tailored to mutable Instruction slices?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because you may want to mutate the instruction whilst iterating through it -- this is how the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But can we then not have two flavours? One for mut and one normal? Or do you think its not worth it?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not entirely sure how it is limited tbh, we mutably borrow instructions one at a time, so unless there is another (mutable) borrow of the same instruction somewhere else, it shouldn't restrict the places in which you can use this API.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In addition, the |
||
|
|
||
| /// Attempts to continuously iterate through the instructions while applying `f` to each of | ||
| /// them, until either the last instruction or `cond` returns false. | ||
| /// | ||
| /// If `f` returns an error, then iteration halts and the function returns that error. | ||
| /// Otherwise, `f` returns a `ControlFlow` which signifies whether the iteration breaks or | ||
| /// continues. | ||
| fn match_next_inst_while<C, F>(self, cond: C, f: F) -> Result<Self, Self::Error> | ||
| where | ||
| Self: Sized, | ||
| C: Fn(&Self::Inst) -> bool, | ||
| F: FnMut(&mut Self::Inst) -> Result<ControlFlow<()>, Self::Error>; | ||
|
|
||
| /// Iterate instructions forward until `cond` returns false. | ||
| fn skip_inst_while<C>(self, cond: C) -> Result<Self, Self::Error> | ||
| where | ||
| Self: Sized, | ||
| C: Fn(&Self::Inst) -> bool, | ||
| { | ||
| Self::match_next_inst_while(self, cond, |_| Ok(ControlFlow::Continue(()))) | ||
| } | ||
| } | ||
|
|
||
| #[derive(Clone, Eq, PartialEq, Debug)] | ||
| pub enum Unsupported {} | ||
| impl Encode for Unsupported {} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| // Copyright 2023 Parity Technologies (UK) Ltd. | ||
| // This file is part of Polkadot. | ||
|
|
||
| // Substrate is free software: you can redistribute it and/or modify | ||
| // it under the terms of the GNU General Public License as published by | ||
| // the Free Software Foundation, either version 3 of the License, or | ||
| // (at your option) any later version. | ||
|
|
||
| // Substrate is distributed in the hope that it will be useful, | ||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| // GNU General Public License for more details. | ||
|
|
||
| // You should have received a copy of the GNU General Public License | ||
| // along with Polkadot. If not, see <http://www.gnu.org/licenses/>. | ||
|
|
||
| //! XCM matcher API, used primarily for writing barrier conditions. | ||
|
|
||
| use super::{Instruction, MultiLocation}; | ||
| use crate::{CreateMatcher, MatchXcm}; | ||
| use core::ops::ControlFlow; | ||
|
|
||
| impl<'a, Call> CreateMatcher for &'a mut [Instruction<Call>] { | ||
| type Matcher = Matcher<'a, Call>; | ||
|
|
||
| fn matcher(self) -> Self::Matcher { | ||
| let total_inst = self.len(); | ||
|
|
||
| Matcher { xcm: self, current_idx: 0, total_inst } | ||
| } | ||
| } | ||
|
|
||
| /// Struct created from calling `fn matcher()` on a mutable slice of `Instruction`s. | ||
| /// | ||
| /// Implements `MatchXcm` to allow an iterator-like API to match against each `Instruction` | ||
| /// contained within the slice, which facilitates the building of XCM barriers. | ||
| pub struct Matcher<'a, Call> { | ||
| pub(crate) xcm: &'a mut [Instruction<Call>], | ||
| pub(crate) current_idx: usize, | ||
| pub(crate) total_inst: usize, | ||
| } | ||
|
|
||
| impl<'a, Call> MatchXcm for Matcher<'a, Call> { | ||
| type Error = (); | ||
| type Inst = Instruction<Call>; | ||
| type Loc = MultiLocation; | ||
|
|
||
| fn assert_remaining_insts(self, n: usize) -> Result<Self, Self::Error> | ||
| where | ||
| Self: Sized, | ||
| { | ||
| if self.total_inst - self.current_idx != n { | ||
| return Err(()) | ||
| } | ||
|
|
||
| Ok(self) | ||
| } | ||
|
|
||
| fn match_next_inst<F>(mut self, mut f: F) -> Result<Self, Self::Error> | ||
| where | ||
| Self: Sized, | ||
| F: FnMut(&mut Self::Inst) -> Result<(), Self::Error>, | ||
| { | ||
| if self.current_idx < self.total_inst { | ||
| f(&mut self.xcm[self.current_idx])?; | ||
| self.current_idx += 1; | ||
| Ok(self) | ||
| } else { | ||
| Err(()) | ||
| } | ||
| } | ||
|
|
||
| fn match_next_inst_while<C, F>(mut self, cond: C, mut f: F) -> Result<Self, Self::Error> | ||
| where | ||
| Self: Sized, | ||
| C: Fn(&Self::Inst) -> bool, | ||
| F: FnMut(&mut Self::Inst) -> Result<ControlFlow<()>, Self::Error>, | ||
| { | ||
| if self.current_idx >= self.total_inst { | ||
| return Err(()) | ||
| } | ||
|
|
||
| while self.current_idx < self.total_inst && cond(&self.xcm[self.current_idx]) { | ||
| if let ControlFlow::Break(()) = f(&mut self.xcm[self.current_idx])? { | ||
| break | ||
| } | ||
| self.current_idx += 1; | ||
| } | ||
|
|
||
| Ok(self) | ||
| } | ||
| } | ||
KiChjang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use crate::v3::prelude::*; | ||
| use alloc::{vec, vec::Vec}; | ||
|
|
||
| #[test] | ||
| fn match_next_inst_while_works() { | ||
| let mut xcm: Vec<Instruction<()>> = vec![ClearOrigin]; | ||
|
|
||
| let _ = xcm[..] | ||
| .matcher() | ||
| .match_next_inst_while(|_| true, |_| Ok(ControlFlow::Continue(()))) | ||
| .unwrap(); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.