Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AllowTransferAndSwap Filter for pallet_xcm #2789

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 81 additions & 1 deletion polkadot/xcm/pallet-xcm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,14 @@ use frame_system::pallet_prelude::{BlockNumberFor, *};
pub use pallet::*;
use scale_info::TypeInfo;
use sp_runtime::{
Either,
traits::{
AccountIdConversion, BadOrigin, BlakeTwo256, BlockNumberProvider, Dispatchable, Hash,
Saturating, Zero,
},
RuntimeDebug,
};
use sp_std::{boxed::Box, marker::PhantomData, prelude::*, result::Result, vec};
use sp_std::{boxed::Box, cell::Cell, marker::PhantomData, prelude::*, result::Result, vec};
use xcm::{latest::QueryResponseInfo, prelude::*};
use xcm_builder::{
ExecuteController, ExecuteControllerWeightInfo, QueryController, QueryControllerWeightInfo,
Expand Down Expand Up @@ -3013,3 +3014,82 @@ impl<RuntimeOrigin: From<crate::Origin>> ConvertOrigin<RuntimeOrigin>
}
}
}

/// Meant to serve as an `XcmExecuteFilter` by allowing XCM instructions related to transferring and exchanging assets
/// while disallowing e.g. `Transact`.
///
/// For the list of allowed instructions see [`allowed_or_recurse`].
pub struct AllowTransferAndSwap<MaxXcmDepth, MaxInstructions, RuntimeCall>(
PhantomData<(MaxXcmDepth, MaxInstructions, RuntimeCall)>,
);

impl<MaxXcmDepth, MaxInstructions, RuntimeCall> Contains<(MultiLocation, Xcm<RuntimeCall>)>
for AllowTransferAndSwap<MaxXcmDepth, MaxInstructions, RuntimeCall>
where
MaxXcmDepth: Get<u16>,
MaxInstructions: Get<u16>,
{
fn contains((loc, xcm): &(MultiLocation, Xcm<RuntimeCall>)) -> bool {
// allow root to execute XCM
if loc == &MultiLocation::here() {
return true;
}

let instructions_count = Cell::new(0u16);
check_instructions_recursively::<MaxXcmDepth, MaxInstructions, RuntimeCall>(xcm, 0, &instructions_count)
}
}

/// Recurses depth-first through the instructions of an XCM and checks whether they are allowed, limiting both recursion
/// depth (via `MaxXcmDepth`) and instructions (`MaxInstructions`).
///
/// See [`allowed_or_recurse`] for the filter list.
fn check_instructions_recursively<MaxXcmDepth, MaxInstructions, RuntimeCall>(
xcm: &Xcm<RuntimeCall>,
depth: u16,
instructions: &Cell<u16>,
) -> bool
where
MaxXcmDepth: Get<u16>,
MaxInstructions: Get<u16>,
{
if depth > MaxXcmDepth::get() {
return false;
}
for inst in xcm.inner().iter() {
instructions.set(instructions.get() + 1);
if instructions.get() > MaxInstructions::get() {
return false;
}

match allowed_or_recurse(inst) {
Either::Left(true) => continue,
Either::Left(false) => return false,
Either::Right(xcm) => {
if !check_instructions_recursively::<MaxXcmDepth, MaxInstructions, ()>(xcm, depth + 1, instructions) {
return false;
}
}
}
}
true
}

/// Check if an XCM instruction is allowed (returning `Left(true)`), disallowed (`Left(false)`) or needs recursion to
/// determine whether it is allowed (`Right(xcm)`).
fn allowed_or_recurse<RuntimeCall>(inst: &Instruction<RuntimeCall>) -> Either<bool, &Xcm<()>> {
match inst {
ClearOrigin
| ExchangeAsset { .. }
| WithdrawAsset(..)
| TransferAsset { .. }
| DepositAsset { .. }
| ExpectAsset(..)
| SetFeesMode { .. }
| BuyExecution { .. } => Either::Left(true),
InitiateReserveWithdraw { xcm, .. } | DepositReserveAsset { xcm, .. } | TransferReserveAsset { xcm, .. } => {
Either::Right(xcm)
}
_ => Either::Left(false),
}
}
277 changes: 277 additions & 0 deletions polkadot/xcm/pallet-xcm/src/tests/execute_filter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
use crate::mock::*;
use crate::AllowTransferAndSwap;
use codec::Encode;
use frame_support::pallet_prelude::Weight;
use frame_support::traits::Contains;
use xcm::prelude::*;
use xcm::latest::Instruction;
use sp_runtime::traits::ConstU16;

#[test]
fn xcm_execute_filter_should_not_allow_transact() {
let call = RuntimeCall::System(frame_system::Call::remark { remark: Vec::new() }).encode();
let xcm = Xcm(vec![Transact {
origin_kind: OriginKind::Native,
require_weight_at_most: Weight::from_parts(1, 1),
call: call.into(),
}]);
let loc = MultiLocation::new(
0,
AccountId32 {
network: None,
id: [1; 32],
},
);
assert!(xcm_execute_filter_does_not_allow(&(loc, xcm)));
}

#[test]
fn xcm_execute_filter_should_allow_a_transfer_and_swap() {
//Arrange
let fees = MultiAsset::from((MultiLocation::here(), 10));
let weight_limit = WeightLimit::Unlimited;
let give: MultiAssetFilter = fees.clone().into();
let want: MultiAssets = fees.clone().into();
let assets: MultiAssets = fees.clone().into();

let max_assets = 2;
let beneficiary = Junction::AccountId32 {
id: [3; 32],
network: None,
}
.into();
let dest = MultiLocation::new(1, Parachain(2047));

let xcm = Xcm(vec![
BuyExecution { fees, weight_limit },
ExchangeAsset {
give,
want,
maximal: true,
},
DepositAsset {
assets: Wild(AllCounted(max_assets)),
beneficiary,
},
]);

let message = Xcm(vec![
SetFeesMode { jit_withdraw: true },
TransferReserveAsset { assets, dest, xcm },
]);

let loc = MultiLocation::new(
0,
AccountId32 {
network: None,
id: [1; 32],
},
);

//Act and assert
assert!(xcm_execute_filter_allows(&(loc, message)));
}

#[test]
fn xcm_execute_filter_should_filter_too_deep_xcm() {
//Arrange
let fees = MultiAsset::from((MultiLocation::here(), 10));
let assets: MultiAssets = fees.into();

let max_assets = 2;
let beneficiary = Junction::AccountId32 {
id: [3; 32],
network: None,
}
.into();
let dest = MultiLocation::new(1, Parachain(2047));

let deposit = Xcm(vec![DepositAsset {
assets: Wild(AllCounted(max_assets)),
beneficiary,
}]);

let mut message = Xcm(vec![TransferReserveAsset {
assets: assets.clone(),
dest,
xcm: deposit,
}]);

for _ in 0..5 {
let xcm = message.clone();
message = Xcm(vec![TransferReserveAsset {
assets: assets.clone(),
dest,
xcm,
}]);
}

let loc = MultiLocation::new(
0,
AccountId32 {
network: None,
id: [1; 32],
},
);

//Act and assert
assert!(xcm_execute_filter_does_not_allow(&(loc, message)));
}

#[test]
fn xcm_execute_filter_should_not_filter_message_with_max_deep() {
//Arrange
let fees = MultiAsset::from((MultiLocation::here(), 10));
let assets: MultiAssets = fees.into();

let max_assets = 2;
let beneficiary = Junction::AccountId32 {
id: [3; 32],
network: None,
}
.into();
let dest = MultiLocation::new(1, Parachain(2047));

let deposit = Xcm(vec![DepositAsset {
assets: Wild(AllCounted(max_assets)),
beneficiary,
}]);

let mut message = Xcm(vec![TransferReserveAsset {
assets: assets.clone(),
dest,
xcm: deposit,
}]);

for _ in 0..4 {
let xcm = message.clone();
message = Xcm(vec![TransferReserveAsset {
assets: assets.clone(),
dest,
xcm,
}]);
}

let loc = MultiLocation::new(
0,
AccountId32 {
network: None,
id: [1; 32],
},
);

//Act and assert
assert!(AllowTransferAndSwap::<ConstU16<5>, ConstU16<100>, ()>::contains(&(
loc, message
)));
}

#[test]
fn xcm_execute_filter_should_filter_messages_with_one_more_instruction_than_allowed_in_depth() {
//Arrange
let fees = MultiAsset::from((MultiLocation::here(), 10));
let assets: MultiAssets = fees.into();

let max_assets = 2;
let beneficiary = Junction::AccountId32 {
id: [3; 32],
network: None,
}
.into();
let dest = MultiLocation::new(1, Parachain(2047));

let deposit = Xcm(vec![DepositAsset {
assets: Wild(AllCounted(max_assets)),
beneficiary,
}]);

let mut message = Xcm(vec![TransferReserveAsset {
assets: assets.clone(),
dest,
xcm: deposit,
}]);

for _ in 0..2 {
let xcm = message.clone();
message = Xcm(vec![TransferReserveAsset {
assets: assets.clone(),
dest,
xcm: xcm.clone(),
}]);
}

//It has 5 instruction
let mut instructions_with_inner_xcms: Vec<Instruction<()>> = vec![TransferReserveAsset {
assets: assets.clone(),
dest,
xcm: message.clone(),
}];

let mut rest: Vec<Instruction<()>> = vec![
DepositAsset {
assets: Wild(AllCounted(max_assets)),
beneficiary,
};
95
];

instructions_with_inner_xcms.append(&mut rest);

message = Xcm(vec![TransferReserveAsset {
assets,
dest,
xcm: Xcm(instructions_with_inner_xcms.clone()),
}]);

let loc = MultiLocation::new(
0,
AccountId32 {
network: None,
id: [1; 32],
},
);

//Act and assert
assert!(xcm_execute_filter_does_not_allow(&(loc, message)));
}

#[test]
fn xcm_execute_filter_should_filter_messages_with_one_more_instruction_than_allowed_in_one_level() {
//Arrange
let max_assets = 2;
let beneficiary = Junction::AccountId32 {
id: [3; 32],
network: None,
}
.into();

let message_with_more_instructions_than_allowed = Xcm(vec![
DepositAsset {
assets: Wild(AllCounted(max_assets)),
beneficiary,
};
101
]);

let loc = MultiLocation::new(
0,
AccountId32 {
network: None,
id: [1; 32],
},
);

//Act and assert
assert!(xcm_execute_filter_does_not_allow(&(
loc,
message_with_more_instructions_than_allowed
)));
}

fn xcm_execute_filter_allows(loc_and_message: &(MultiLocation, Xcm<RuntimeCall>)) -> bool {
AllowTransferAndSwap::<ConstU16<5>, ConstU16<100>, RuntimeCall>::contains(loc_and_message)
}

fn xcm_execute_filter_does_not_allow(loc_and_message: &(MultiLocation, Xcm<()>)) -> bool {
!AllowTransferAndSwap::<ConstU16<5>, ConstU16<100>, ()>::contains(loc_and_message)
}
2 changes: 2 additions & 0 deletions polkadot/xcm/pallet-xcm/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ const INITIAL_BALANCE: u128 = 100;
const SEND_AMOUNT: u128 = 10;
const FEE_AMOUNT: u128 = 2;

mod execute_filter;

#[test]
fn report_outcome_notify_works() {
let balances = vec![
Expand Down
Loading