mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-29 06:47:57 +00:00
Introduce XCM matcher for writing barriers (#6756)
* Introduce XCM matcher for writing barriers * Fix compilation * cargo fmt * Add more doc comments * Add mod doc comment * More doc comments * Add tests and fix logic * Remove redundant syntax * Add more doc comments * Add more doc comments * Add more doc comments
This commit is contained in:
@@ -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,89 @@ 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 {
|
||||
/// The concrete matcher type.
|
||||
type Matcher;
|
||||
|
||||
/// Method that creates and returns the matcher type from `Self`.
|
||||
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>;
|
||||
|
||||
/// 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. When there are no more instructions
|
||||
/// to be read, an error is returned.
|
||||
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 {}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
#[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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -21,13 +21,16 @@ use frame_support::{
|
||||
traits::{Contains, Get},
|
||||
};
|
||||
use polkadot_parachain::primitives::IsSystem;
|
||||
use sp_std::{marker::PhantomData, result::Result};
|
||||
use xcm::latest::{
|
||||
Instruction::{self, *},
|
||||
InteriorMultiLocation, Junction, Junctions,
|
||||
Junctions::X1,
|
||||
MultiLocation, Weight,
|
||||
WeightLimit::*,
|
||||
use sp_std::{cell::Cell, marker::PhantomData, ops::ControlFlow, result::Result};
|
||||
use xcm::{
|
||||
latest::{
|
||||
Instruction::{self, *},
|
||||
InteriorMultiLocation, Junction, Junctions,
|
||||
Junctions::X1,
|
||||
MultiLocation, Weight,
|
||||
WeightLimit::*,
|
||||
},
|
||||
CreateMatcher, MatchXcm,
|
||||
};
|
||||
use xcm_executor::traits::{OnResponse, ShouldExecute};
|
||||
|
||||
@@ -77,32 +80,31 @@ impl<T: Contains<MultiLocation>> ShouldExecute for AllowTopLevelPaidExecutionFro
|
||||
// We will read up to 5 instructions. This allows up to 3 `ClearOrigin` instructions. We
|
||||
// allow for more than one since anything beyond the first is a no-op and it's conceivable
|
||||
// that composition of operations might result in more than one being appended.
|
||||
let mut iter = instructions.iter_mut().take(5);
|
||||
let i = iter.next().ok_or(())?;
|
||||
match i {
|
||||
ReceiveTeleportedAsset(..) |
|
||||
WithdrawAsset(..) |
|
||||
ReserveAssetDeposited(..) |
|
||||
ClaimAsset { .. } => (),
|
||||
_ => return Err(()),
|
||||
}
|
||||
let mut i = iter.next().ok_or(())?;
|
||||
while let ClearOrigin = i {
|
||||
i = iter.next().ok_or(())?;
|
||||
}
|
||||
match i {
|
||||
BuyExecution { weight_limit: Limited(ref mut weight), .. }
|
||||
if weight.all_gte(max_weight) =>
|
||||
{
|
||||
*weight = max_weight;
|
||||
Ok(())
|
||||
},
|
||||
BuyExecution { ref mut weight_limit, .. } if weight_limit == &Unlimited => {
|
||||
*weight_limit = Limited(max_weight);
|
||||
Ok(())
|
||||
},
|
||||
_ => Err(()),
|
||||
}
|
||||
let end = instructions.len().min(5);
|
||||
instructions[..end]
|
||||
.matcher()
|
||||
.match_next_inst(|inst| match inst {
|
||||
ReceiveTeleportedAsset(..) |
|
||||
WithdrawAsset(..) |
|
||||
ReserveAssetDeposited(..) |
|
||||
ClaimAsset { .. } => Ok(()),
|
||||
_ => Err(()),
|
||||
})?
|
||||
.skip_inst_while(|inst| matches!(inst, ClearOrigin))?
|
||||
.match_next_inst(|inst| match inst {
|
||||
BuyExecution { weight_limit: Limited(ref mut weight), .. }
|
||||
if weight.all_gte(max_weight) =>
|
||||
{
|
||||
*weight = max_weight;
|
||||
Ok(())
|
||||
},
|
||||
BuyExecution { ref mut weight_limit, .. } if weight_limit == &Unlimited => {
|
||||
*weight_limit = Limited(max_weight);
|
||||
Ok(())
|
||||
},
|
||||
_ => Err(()),
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,29 +174,34 @@ impl<
|
||||
origin, instructions, max_weight, weight_credit,
|
||||
);
|
||||
let mut actual_origin = *origin;
|
||||
let mut skipped = 0;
|
||||
let skipped = Cell::new(0usize);
|
||||
// NOTE: We do not check the validity of `UniversalOrigin` here, meaning that a malicious
|
||||
// origin could place a `UniversalOrigin` in order to spoof some location which gets free
|
||||
// execution. This technical could get it past the barrier condition, but the execution
|
||||
// would instantly fail since the first instruction would cause an error with the
|
||||
// invalid UniversalOrigin.
|
||||
while skipped < MaxPrefixes::get() as usize {
|
||||
match instructions.get(skipped) {
|
||||
Some(UniversalOrigin(new_global)) => {
|
||||
// Note the origin is *relative to local consensus*! So we need to escape local
|
||||
// consensus with the `parents` before diving in into the `universal_location`.
|
||||
actual_origin = X1(*new_global).relative_to(&LocalUniversal::get());
|
||||
},
|
||||
Some(DescendOrigin(j)) => {
|
||||
actual_origin.append_with(*j).map_err(|_| ())?;
|
||||
},
|
||||
_ => break,
|
||||
}
|
||||
skipped += 1;
|
||||
}
|
||||
instructions.matcher().match_next_inst_while(
|
||||
|_| skipped.get() < MaxPrefixes::get() as usize,
|
||||
|inst| {
|
||||
match inst {
|
||||
UniversalOrigin(new_global) => {
|
||||
// Note the origin is *relative to local consensus*! So we need to escape
|
||||
// local consensus with the `parents` before diving in into the
|
||||
// `universal_location`.
|
||||
actual_origin = X1(*new_global).relative_to(&LocalUniversal::get());
|
||||
},
|
||||
DescendOrigin(j) => {
|
||||
let Ok(_) = actual_origin.append_with(*j) else { return Err(()) };
|
||||
},
|
||||
_ => return Ok(ControlFlow::Break(())),
|
||||
};
|
||||
skipped.set(skipped.get() + 1);
|
||||
Ok(ControlFlow::Continue(()))
|
||||
},
|
||||
)?;
|
||||
InnerBarrier::should_execute(
|
||||
&actual_origin,
|
||||
&mut instructions[skipped..],
|
||||
&mut instructions[skipped.get()..],
|
||||
max_weight,
|
||||
weight_credit,
|
||||
)
|
||||
@@ -241,12 +248,12 @@ impl<T: Contains<MultiLocation>> ShouldExecute for AllowExplicitUnpaidExecutionF
|
||||
origin, instructions, max_weight, _weight_credit,
|
||||
);
|
||||
ensure!(T::contains(origin), ());
|
||||
match instructions.first() {
|
||||
Some(UnpaidExecution { weight_limit: Limited(m), .. }) if m.all_gte(max_weight) =>
|
||||
Ok(()),
|
||||
Some(UnpaidExecution { weight_limit: Unlimited, .. }) => Ok(()),
|
||||
instructions.matcher().match_next_inst(|inst| match inst {
|
||||
UnpaidExecution { weight_limit: Limited(m), .. } if m.all_gte(max_weight) => Ok(()),
|
||||
UnpaidExecution { weight_limit: Unlimited, .. } => Ok(()),
|
||||
_ => Err(()),
|
||||
}
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,13 +283,16 @@ impl<ResponseHandler: OnResponse> ShouldExecute for AllowKnownQueryResponses<Res
|
||||
"AllowKnownQueryResponses origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}",
|
||||
origin, instructions, _max_weight, _weight_credit,
|
||||
);
|
||||
ensure!(instructions.len() == 1, ());
|
||||
match instructions.first() {
|
||||
Some(QueryResponse { query_id, querier, .. })
|
||||
if ResponseHandler::expecting_response(origin, *query_id, querier.as_ref()) =>
|
||||
Ok(()),
|
||||
_ => Err(()),
|
||||
}
|
||||
instructions
|
||||
.matcher()
|
||||
.assert_remaining_insts(1)?
|
||||
.match_next_inst(|inst| match inst {
|
||||
QueryResponse { query_id, querier, .. }
|
||||
if ResponseHandler::expecting_response(origin, *query_id, querier.as_ref()) =>
|
||||
Ok(()),
|
||||
_ => Err(()),
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,9 +312,13 @@ impl<T: Contains<MultiLocation>> ShouldExecute for AllowSubscriptionsFrom<T> {
|
||||
origin, instructions, _max_weight, _weight_credit,
|
||||
);
|
||||
ensure!(T::contains(origin), ());
|
||||
match instructions {
|
||||
&mut [SubscribeVersion { .. } | UnsubscribeVersion] => Ok(()),
|
||||
_ => Err(()),
|
||||
}
|
||||
instructions
|
||||
.matcher()
|
||||
.assert_remaining_insts(1)?
|
||||
.match_next_inst(|inst| match inst {
|
||||
SubscribeVersion { .. } | UnsubscribeVersion => Ok(()),
|
||||
_ => Err(()),
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user