use crate::{CreateMatcher, MatchXcm};
use core::{cell::Cell, marker::PhantomData, ops::ControlFlow, result::Result};
use pezframe_support::{
ensure,
traits::{Contains, ContainsPair, Get, Nothing, ProcessMessageError},
};
use pezkuwi_teyrchain_primitives::primitives::IsSystem;
use xcm::prelude::*;
use xcm_executor::traits::{CheckSuspension, DenyExecution, OnResponse, Properties, ShouldExecute};
pub struct TakeWeightCredit;
impl ShouldExecute for TakeWeightCredit {
fn should_execute<RuntimeCall>(
origin: &Location,
instructions: &mut [Instruction<RuntimeCall>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
tracing::trace!(
target: "xcm::barriers",
?origin,
?instructions,
?max_weight,
?properties,
"TakeWeightCredit"
);
properties.weight_credit = properties
.weight_credit
.checked_sub(&max_weight)
.ok_or(ProcessMessageError::Overweight(max_weight))?;
Ok(())
}
}
const MAX_ASSETS_FOR_BUY_EXECUTION: usize = 2;
pub struct AllowTopLevelPaidExecutionFrom<T>(PhantomData<T>);
impl<T: Contains<Location>> ShouldExecute for AllowTopLevelPaidExecutionFrom<T> {
fn should_execute<RuntimeCall>(
origin: &Location,
instructions: &mut [Instruction<RuntimeCall>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
tracing::trace!(
target: "xcm::barriers",
?origin,
?instructions,
?max_weight,
?properties,
"AllowTopLevelPaidExecutionFrom",
);
ensure!(T::contains(origin), ProcessMessageError::Unsupported);
let end = instructions.len().min(5);
instructions[..end]
.matcher()
.match_next_inst(|inst| match inst {
WithdrawAsset(ref assets)
| ReceiveTeleportedAsset(ref assets)
| ReserveAssetDeposited(ref assets)
| ClaimAsset { ref assets, .. } => {
if assets.len() <= MAX_ASSETS_FOR_BUY_EXECUTION {
Ok(())
} else {
Err(ProcessMessageError::BadFormat)
}
},
_ => Err(ProcessMessageError::BadFormat),
})?
.skip_inst_while(|inst| {
matches!(inst, ClearOrigin | AliasOrigin(..))
|| matches!(inst, DescendOrigin(child) if child != &Here)
|| matches!(inst, SetHints { .. })
})?
.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(())
},
PayFees { .. } => Ok(()),
_ => Err(ProcessMessageError::Overweight(max_weight)),
})?;
Ok(())
}
}
pub struct WithComputedOrigin<InnerBarrier, LocalUniversal, MaxPrefixes>(
PhantomData<(InnerBarrier, LocalUniversal, MaxPrefixes)>,
);
impl<InnerBarrier: ShouldExecute, LocalUniversal: Get<InteriorLocation>, MaxPrefixes: Get<u32>>
ShouldExecute for WithComputedOrigin<InnerBarrier, LocalUniversal, MaxPrefixes>
{
fn should_execute<Call>(
origin: &Location,
instructions: &mut [Instruction<Call>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
tracing::trace!(
target: "xcm::barriers",
?origin,
?instructions,
?max_weight,
?properties,
"WithComputedOrigin"
);
let mut actual_origin = origin.clone();
let skipped = Cell::new(0usize);
instructions.matcher().match_next_inst_while(
|_| skipped.get() < MaxPrefixes::get() as usize,
|inst| {
match inst {
UniversalOrigin(new_global) => {
actual_origin =
Junctions::from([*new_global]).relative_to(&LocalUniversal::get());
},
DescendOrigin(j) => {
let Ok(_) = actual_origin.append_with(j.clone()) else {
return Err(ProcessMessageError::Unsupported);
};
},
_ => return Ok(ControlFlow::Break(())),
};
skipped.set(skipped.get() + 1);
Ok(ControlFlow::Continue(()))
},
)?;
InnerBarrier::should_execute(
&actual_origin,
&mut instructions[skipped.get()..],
max_weight,
properties,
)
}
}
pub struct TrailingSetTopicAsId<InnerBarrier>(PhantomData<InnerBarrier>);
impl<InnerBarrier: ShouldExecute> ShouldExecute for TrailingSetTopicAsId<InnerBarrier> {
fn should_execute<Call>(
origin: &Location,
instructions: &mut [Instruction<Call>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
tracing::trace!(
target: "xcm::barriers",
?origin,
?instructions,
?max_weight,
?properties,
"TrailingSetTopicAsId"
);
let until = if let Some(SetTopic(t)) = instructions.last() {
properties.message_id = Some(*t);
instructions.len() - 1
} else {
instructions.len()
};
InnerBarrier::should_execute(&origin, &mut instructions[..until], max_weight, properties)
}
}
pub struct RespectSuspension<Inner, SuspensionChecker>(PhantomData<(Inner, SuspensionChecker)>);
impl<Inner, SuspensionChecker> ShouldExecute for RespectSuspension<Inner, SuspensionChecker>
where
Inner: ShouldExecute,
SuspensionChecker: CheckSuspension,
{
fn should_execute<Call>(
origin: &Location,
instructions: &mut [Instruction<Call>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
if SuspensionChecker::is_suspended(origin, instructions, max_weight, properties) {
Err(ProcessMessageError::Yield)
} else {
Inner::should_execute(origin, instructions, max_weight, properties)
}
}
}
pub struct AllowUnpaidExecutionFrom<T>(PhantomData<T>);
impl<T: Contains<Location>> ShouldExecute for AllowUnpaidExecutionFrom<T> {
fn should_execute<RuntimeCall>(
origin: &Location,
instructions: &mut [Instruction<RuntimeCall>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
tracing::trace!(
target: "xcm::barriers",
?origin, ?instructions, ?max_weight, ?properties,
"AllowUnpaidExecutionFrom"
);
ensure!(T::contains(origin), ProcessMessageError::Unsupported);
Ok(())
}
}
pub struct AllowExplicitUnpaidExecutionFrom<T, Aliasers = Nothing>(PhantomData<(T, Aliasers)>);
impl<T: Contains<Location>, Aliasers: ContainsPair<Location, Location>> ShouldExecute
for AllowExplicitUnpaidExecutionFrom<T, Aliasers>
{
fn should_execute<Call>(
origin: &Location,
instructions: &mut [Instruction<Call>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
tracing::trace!(
target: "xcm::barriers",
?origin, ?instructions, ?max_weight, ?properties,
"AllowExplicitUnpaidExecutionFrom",
);
let mut actual_origin = origin.clone();
let processed = Cell::new(0usize);
let instructions_to_process = 5;
instructions
.matcher()
.match_next_inst_while(
|inst| {
processed.get() < instructions_to_process
&& matches!(
inst,
ReceiveTeleportedAsset(_)
| ReserveAssetDeposited(_)
| WithdrawAsset(_) | SetHints { .. }
)
},
|_| {
processed.set(processed.get() + 1);
Ok(ControlFlow::Continue(()))
},
)?
.match_next_inst_while(
|_| processed.get() < instructions_to_process,
|inst| {
match inst {
ClearOrigin => {
return Err(ProcessMessageError::Unsupported);
},
AliasOrigin(target) => {
if Aliasers::contains(&actual_origin, &target) {
actual_origin = target.clone();
} else {
return Err(ProcessMessageError::Unsupported);
}
},
DescendOrigin(child) if child != &Here => {
let Ok(_) = actual_origin.append_with(child.clone()) else {
return Err(ProcessMessageError::Unsupported);
};
},
_ => return Ok(ControlFlow::Break(())),
};
processed.set(processed.get() + 1);
Ok(ControlFlow::Continue(()))
},
)?
.match_next_inst(|inst| match inst {
UnpaidExecution { weight_limit: Limited(m), .. } if m.all_gte(max_weight) => Ok(()),
UnpaidExecution { weight_limit: Unlimited, .. } => Ok(()),
_ => Err(ProcessMessageError::Overweight(max_weight)),
})?;
ensure!(T::contains(&actual_origin), ProcessMessageError::Unsupported);
Ok(())
}
}
pub struct IsChildSystemTeyrchain<ParaId>(PhantomData<ParaId>);
impl<ParaId: IsSystem + From<u32>> Contains<Location> for IsChildSystemTeyrchain<ParaId> {
fn contains(l: &Location) -> bool {
matches!(
l.interior().as_slice(),
[Junction::Teyrchain(id)]
if ParaId::from(*id).is_system() && l.parent_count() == 0,
)
}
}
pub struct IsSiblingSystemTeyrchain<ParaId, SelfParaId>(PhantomData<(ParaId, SelfParaId)>);
impl<ParaId: IsSystem + From<u32> + Eq, SelfParaId: Get<ParaId>> Contains<Location>
for IsSiblingSystemTeyrchain<ParaId, SelfParaId>
{
fn contains(l: &Location) -> bool {
matches!(
l.unpack(),
(1, [Junction::Teyrchain(id)])
if SelfParaId::get() != ParaId::from(*id) && ParaId::from(*id).is_system(),
)
}
}
pub struct IsParentsOnly<Count>(PhantomData<Count>);
impl<Count: Get<u8>> Contains<Location> for IsParentsOnly<Count> {
fn contains(t: &Location) -> bool {
t.contains_parents_only(Count::get())
}
}
pub struct AllowKnownQueryResponses<ResponseHandler>(PhantomData<ResponseHandler>);
impl<ResponseHandler: OnResponse> ShouldExecute for AllowKnownQueryResponses<ResponseHandler> {
fn should_execute<RuntimeCall>(
origin: &Location,
instructions: &mut [Instruction<RuntimeCall>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
tracing::trace!(
target: "xcm::barriers",
?origin, ?instructions, ?max_weight, ?properties,
"AllowKnownQueryResponses"
);
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(ProcessMessageError::BadFormat),
})?;
Ok(())
}
}
pub struct AllowSubscriptionsFrom<T>(PhantomData<T>);
impl<T: Contains<Location>> ShouldExecute for AllowSubscriptionsFrom<T> {
fn should_execute<RuntimeCall>(
origin: &Location,
instructions: &mut [Instruction<RuntimeCall>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
tracing::trace!(
target: "xcm::barriers",
?origin, ?instructions, ?max_weight, ?properties,
"AllowSubscriptionsFrom",
);
ensure!(T::contains(origin), ProcessMessageError::Unsupported);
instructions
.matcher()
.assert_remaining_insts(1)?
.match_next_inst(|inst| match inst {
SubscribeVersion { .. } | UnsubscribeVersion => Ok(()),
_ => Err(ProcessMessageError::BadFormat),
})?;
Ok(())
}
}
pub struct AllowHrmpNotificationsFromRelayChain;
impl ShouldExecute for AllowHrmpNotificationsFromRelayChain {
fn should_execute<RuntimeCall>(
origin: &Location,
instructions: &mut [Instruction<RuntimeCall>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
tracing::trace!(
target: "xcm::barriers",
?origin, ?instructions, ?max_weight, ?properties,
"AllowHrmpNotificationsFromRelayChain"
);
ensure!(matches!(origin.unpack(), (1, [])), ProcessMessageError::Unsupported);
instructions
.matcher()
.assert_remaining_insts(1)?
.match_next_inst(|inst| match inst {
HrmpNewChannelOpenRequest { .. }
| HrmpChannelAccepted { .. }
| HrmpChannelClosing { .. } => Ok(()),
_ => Err(ProcessMessageError::BadFormat),
})?;
Ok(())
}
}
pub struct DenyThenTry<Deny, Allow>(PhantomData<Deny>, PhantomData<Allow>)
where
Deny: DenyExecution,
Allow: ShouldExecute;
impl<Deny, Allow> ShouldExecute for DenyThenTry<Deny, Allow>
where
Deny: DenyExecution,
Allow: ShouldExecute,
{
fn should_execute<RuntimeCall>(
origin: &Location,
message: &mut [Instruction<RuntimeCall>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
Deny::deny_execution(origin, message, max_weight, properties)?;
Allow::should_execute(origin, message, max_weight, properties)
}
}
pub struct DenyReserveTransferToRelayChain;
impl DenyExecution for DenyReserveTransferToRelayChain {
fn deny_execution<RuntimeCall>(
origin: &Location,
message: &mut [Instruction<RuntimeCall>],
_max_weight: Weight,
_properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
message.matcher().match_next_inst_while(
|_| true,
|inst| match inst {
InitiateReserveWithdraw {
reserve: Location { parents: 1, interior: Here },
..
}
| DepositReserveAsset { dest: Location { parents: 1, interior: Here }, .. }
| TransferReserveAsset { dest: Location { parents: 1, interior: Here }, .. } => {
Err(ProcessMessageError::Unsupported) },
ReserveAssetDeposited { .. }
if matches!(origin, Location { parents: 1, interior: Here }) =>
{
tracing::debug!(
target: "xcm::barriers",
"Unexpected ReserveAssetDeposited from the Relay Chain",
);
Ok(ControlFlow::Continue(()))
},
_ => Ok(ControlFlow::Continue(())),
},
)?;
Ok(())
}
}
environmental::environmental!(recursion_count: u8);
pub struct DenyRecursively<Inner>(PhantomData<Inner>);
impl<Inner: DenyExecution> DenyRecursively<Inner> {
fn deny_recursively<RuntimeCall>(
origin: &Location,
xcm: &mut Xcm<RuntimeCall>,
max_weight: Weight,
properties: &mut Properties,
) -> Result<ControlFlow<()>, ProcessMessageError> {
recursion_count::using_once(&mut 1, || {
recursion_count::with(|count| {
if *count > xcm_executor::RECURSION_LIMIT {
tracing::debug!(
target: "xcm::barriers",
"Recursion limit exceeded (count: {count}), origin: {:?}, xcm: {:?}, max_weight: {:?}, properties: {:?}",
origin, xcm, max_weight, properties
);
return None;
}
*count = count.saturating_add(1);
Some(())
}).flatten().ok_or(ProcessMessageError::StackLimitReached)?;
pezsp_core::defer! {
recursion_count::with(|count| {
*count = count.saturating_sub(1);
});
}
Self::deny_execution(origin, xcm.inner_mut(), max_weight, properties)
})?;
Ok(ControlFlow::Continue(()))
}
}
impl<Inner: DenyExecution> DenyExecution for DenyRecursively<Inner> {
fn deny_execution<RuntimeCall>(
origin: &Location,
instructions: &mut [Instruction<RuntimeCall>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
Inner::deny_execution(origin, instructions, max_weight, properties).inspect_err(|e| {
tracing::debug!(
target: "xcm::barriers",
"DenyRecursively::Inner denied execution, origin: {:?}, instructions: {:?}, max_weight: {:?}, properties: {:?}, error: {:?}",
origin, instructions, max_weight, properties, e
);
})?;
instructions.matcher().match_next_inst_while(
|_| true,
|inst| match inst {
SetAppendix(nested_xcm)
| SetErrorHandler(nested_xcm)
| ExecuteWithOrigin { xcm: nested_xcm, .. } => Self::deny_recursively::<RuntimeCall>(
origin, nested_xcm, max_weight, properties,
),
_ => Ok(ControlFlow::Continue(())),
},
)?;
Ok(())
}
}