Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Introduce XCM matcher for writing barriers #6756

Merged
merged 12 commits into from
Mar 4, 2023
80 changes: 80 additions & 0 deletions xcm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
ggwpez marked this conversation as resolved.
Show resolved Hide resolved
/// 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>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this whole API exclusively tailored to mutable Instruction slices?
That seems to limit the environments that it can be used in.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 AllowUnpaidExecutionFrom barrier works.

Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition, the Matcher struct already mutably borrows the entire slice, so the mutable borrow of the instruction here isn't really doing anything worse than that; instead, it's narrowing the scope of the mutable borrow to a single instruction.


/// 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 {}
Expand Down
92 changes: 92 additions & 0 deletions xcm/src/v3/matcher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// 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 cond(&self.xcm[self.current_idx]) && self.current_idx < self.total_inst {
KiChjang marked this conversation as resolved.
Show resolved Hide resolved
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
6 changes: 4 additions & 2 deletions xcm/src/v3/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// 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
Expand All @@ -12,7 +12,7 @@
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Cumulus. If not, see <http://www.gnu.org/licenses/>.
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.

//! Version 3 of the Cross-Consensus Message format data structures.

Expand All @@ -34,12 +34,14 @@ use scale_info::TypeInfo;

mod junction;
pub(crate) mod junctions;
mod matcher;
mod multiasset;
mod multilocation;
mod traits;

pub use junction::{BodyId, BodyPart, Junction, NetworkId};
pub use junctions::Junctions;
pub use matcher::Matcher;
pub use multiasset::{
AssetId, AssetInstance, Fungibility, MultiAsset, MultiAssetFilter, MultiAssets,
WildFungibility, WildMultiAsset,
Expand Down
4 changes: 2 additions & 2 deletions xcm/src/v3/traits.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// 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
Expand All @@ -12,7 +12,7 @@
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Cumulus. If not, see <http://www.gnu.org/licenses/>.
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.

//! Cross-Consensus Message format data structures.

Expand Down
Loading