XCM: Allow reclaim of assets dropped from holding (#3727)

* XCM: Introduce AssetTrap

* Revert reversions

* Remove attempts at weighing and add test

* Less storage use for asset trapping

* Add missing file

* Fixes

* Fixes

* Formatting

* Fixes

* Docs

* Filter types to allow runtimes to dictate which assets/origins should be trapped

* Formatting

* Tests

* Formatting

* Fixes

* Docs
This commit is contained in:
Gavin Wood
2021-08-28 02:09:36 +02:00
committed by GitHub
parent e56efb82d9
commit 636d0b030f
24 changed files with 519 additions and 49 deletions
+8 -8
View File
@@ -30,7 +30,7 @@ use xcm_executor::traits::{OnResponse, ShouldExecute};
pub struct TakeWeightCredit;
impl ShouldExecute for TakeWeightCredit {
fn should_execute<Call>(
_origin: &Option<MultiLocation>,
_origin: &MultiLocation,
_top_level: bool,
_message: &mut Xcm<Call>,
max_weight: Weight,
@@ -49,19 +49,21 @@ impl ShouldExecute for TakeWeightCredit {
pub struct AllowTopLevelPaidExecutionFrom<T>(PhantomData<T>);
impl<T: Contains<MultiLocation>> ShouldExecute for AllowTopLevelPaidExecutionFrom<T> {
fn should_execute<Call>(
origin: &Option<MultiLocation>,
origin: &MultiLocation,
top_level: bool,
message: &mut Xcm<Call>,
max_weight: Weight,
_weight_credit: &mut Weight,
) -> Result<(), ()> {
let origin = origin.as_ref().ok_or(())?;
ensure!(T::contains(origin), ());
ensure!(top_level, ());
let mut iter = message.0.iter_mut();
let i = iter.next().ok_or(())?;
match i {
ReceiveTeleportedAsset(..) | WithdrawAsset(..) | ReserveAssetDeposited(..) => (),
ReceiveTeleportedAsset(..) |
WithdrawAsset(..) |
ReserveAssetDeposited(..) |
ClaimAsset { .. } => (),
_ => return Err(()),
}
let mut i = iter.next().ok_or(())?;
@@ -87,13 +89,12 @@ impl<T: Contains<MultiLocation>> ShouldExecute for AllowTopLevelPaidExecutionFro
pub struct AllowUnpaidExecutionFrom<T>(PhantomData<T>);
impl<T: Contains<MultiLocation>> ShouldExecute for AllowUnpaidExecutionFrom<T> {
fn should_execute<Call>(
origin: &Option<MultiLocation>,
origin: &MultiLocation,
_top_level: bool,
_message: &mut Xcm<Call>,
_max_weight: Weight,
_weight_credit: &mut Weight,
) -> Result<(), ()> {
let origin = origin.as_ref().ok_or(())?;
ensure!(T::contains(origin), ());
Ok(())
}
@@ -115,13 +116,12 @@ impl<ParaId: IsSystem + From<u32>> Contains<MultiLocation> for IsChildSystemPara
pub struct AllowKnownQueryResponses<ResponseHandler>(PhantomData<ResponseHandler>);
impl<ResponseHandler: OnResponse> ShouldExecute for AllowKnownQueryResponses<ResponseHandler> {
fn should_execute<Call>(
origin: &Option<MultiLocation>,
origin: &MultiLocation,
_top_level: bool,
message: &mut Xcm<Call>,
_max_weight: Weight,
_weight_credit: &mut Weight,
) -> Result<(), ()> {
let origin = origin.as_ref().ok_or(())?;
match message.0.first() {
Some(QueryResponse { query_id, .. })
if ResponseHandler::expecting_response(origin, *query_id) =>
+34
View File
@@ -35,6 +35,7 @@ pub use sp_std::{
marker::PhantomData,
};
pub use xcm::latest::prelude::*;
use xcm_executor::traits::{ClaimAssets, DropAssets};
pub use xcm_executor::{
traits::{ConvertOrigin, FilterAssetLocation, InvertLocation, OnResponse, TransactAsset},
Assets, Config,
@@ -269,6 +270,37 @@ pub type TestBarrier = (
AllowUnpaidExecutionFrom<IsInVec<AllowUnpaidFrom>>,
);
parameter_types! {
pub static TrappedAssets: Vec<(MultiLocation, MultiAssets)> = vec![];
}
pub struct TestAssetTrap;
impl DropAssets for TestAssetTrap {
fn drop_assets(origin: &MultiLocation, assets: Assets) -> Weight {
let mut t: Vec<(MultiLocation, MultiAssets)> = TrappedAssets::get();
t.push((origin.clone(), assets.into()));
TrappedAssets::set(t);
5
}
}
impl ClaimAssets for TestAssetTrap {
fn claim_assets(origin: &MultiLocation, ticket: &MultiLocation, what: &MultiAssets) -> bool {
let mut t: Vec<(MultiLocation, MultiAssets)> = TrappedAssets::get();
if let (0, X1(GeneralIndex(i))) = (ticket.parents, &ticket.interior) {
if let Some((l, a)) = t.get(*i as usize) {
if l == origin && a == what {
t.swap_remove(*i as usize);
TrappedAssets::set(t);
return true
}
}
}
false
}
}
pub struct TestConfig;
impl Config for TestConfig {
type Call = TestCall;
@@ -282,4 +314,6 @@ impl Config for TestConfig {
type Weigher = FixedWeightBounds<UnitWeightCost, TestCall, MaxInstructions>;
type Trader = FixedRateOfFungible<WeightPrice, ()>;
type ResponseHandler = TestResponseHandler;
type AssetTrap = TestAssetTrap;
type AssetClaims = TestAssetTrap;
}
+121 -8
View File
@@ -58,7 +58,7 @@ fn take_weight_credit_barrier_should_work() {
Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]);
let mut weight_credit = 10;
let r = TakeWeightCredit::should_execute(
&Some(Parent.into()),
&Parent.into(),
true,
&mut message,
10,
@@ -68,7 +68,7 @@ fn take_weight_credit_barrier_should_work() {
assert_eq!(weight_credit, 0);
let r = TakeWeightCredit::should_execute(
&Some(Parent.into()),
&Parent.into(),
true,
&mut message,
10,
@@ -86,7 +86,7 @@ fn allow_unpaid_should_work() {
AllowUnpaidFrom::set(vec![Parent.into()]);
let r = AllowUnpaidExecutionFrom::<IsInVec<AllowUnpaidFrom>>::should_execute(
&Some(Parachain(1).into()),
&Parachain(1).into(),
true,
&mut message,
10,
@@ -95,7 +95,7 @@ fn allow_unpaid_should_work() {
assert_eq!(r, Err(()));
let r = AllowUnpaidExecutionFrom::<IsInVec<AllowUnpaidFrom>>::should_execute(
&Some(Parent.into()),
&Parent.into(),
true,
&mut message,
10,
@@ -112,7 +112,7 @@ fn allow_paid_should_work() {
Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]);
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
&Some(Parachain(1).into()),
&Parachain(1).into(),
true,
&mut message,
10,
@@ -128,7 +128,7 @@ fn allow_paid_should_work() {
]);
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
&Some(Parent.into()),
&Parent.into(),
true,
&mut underpaying_message,
30,
@@ -144,7 +144,7 @@ fn allow_paid_should_work() {
]);
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
&Some(Parachain(1).into()),
&Parachain(1).into(),
true,
&mut paying_message,
30,
@@ -153,7 +153,7 @@ fn allow_paid_should_work() {
assert_eq!(r, Err(()));
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
&Some(Parent.into()),
&Parent.into(),
true,
&mut paying_message,
30,
@@ -202,6 +202,119 @@ fn transfer_should_work() {
assert_eq!(sent_xcm(), vec![]);
}
#[test]
fn basic_asset_trap_should_work() {
// we'll let them have message execution for free.
AllowUnpaidFrom::set(vec![X1(Parachain(1)).into(), X1(Parachain(2)).into()]);
// Child parachain #1 owns 1000 tokens held by us in reserve.
add_asset(1001, (Here, 1000));
// They want to transfer 100 of them to their sibling parachain #2 but have a problem
let r = XcmExecutor::<TestConfig>::execute_xcm(
Parachain(1).into(),
Xcm(vec![
WithdrawAsset((Here, 100).into()),
DepositAsset {
assets: Wild(All),
max_assets: 0, //< Whoops!
beneficiary: AccountIndex64 { index: 3, network: Any }.into(),
},
]),
20,
);
assert_eq!(r, Outcome::Complete(25));
assert_eq!(assets(1001), vec![(Here, 900).into()]);
assert_eq!(assets(3), vec![]);
// Incorrect ticket doesn't work.
let old_trapped_assets = TrappedAssets::get();
let r = XcmExecutor::<TestConfig>::execute_xcm(
Parachain(1).into(),
Xcm(vec![
ClaimAsset { assets: (Here, 100).into(), ticket: GeneralIndex(1).into() },
DepositAsset {
assets: Wild(All),
max_assets: 1,
beneficiary: AccountIndex64 { index: 3, network: Any }.into(),
},
]),
20,
);
assert_eq!(r, Outcome::Incomplete(10, XcmError::UnknownClaim));
assert_eq!(assets(1001), vec![(Here, 900).into()]);
assert_eq!(assets(3), vec![]);
assert_eq!(old_trapped_assets, TrappedAssets::get());
// Incorrect origin doesn't work.
let old_trapped_assets = TrappedAssets::get();
let r = XcmExecutor::<TestConfig>::execute_xcm(
Parachain(2).into(),
Xcm(vec![
ClaimAsset { assets: (Here, 100).into(), ticket: GeneralIndex(0).into() },
DepositAsset {
assets: Wild(All),
max_assets: 1,
beneficiary: AccountIndex64 { index: 3, network: Any }.into(),
},
]),
20,
);
assert_eq!(r, Outcome::Incomplete(10, XcmError::UnknownClaim));
assert_eq!(assets(1001), vec![(Here, 900).into()]);
assert_eq!(assets(3), vec![]);
assert_eq!(old_trapped_assets, TrappedAssets::get());
// Incorrect assets doesn't work.
let old_trapped_assets = TrappedAssets::get();
let r = XcmExecutor::<TestConfig>::execute_xcm(
Parachain(1).into(),
Xcm(vec![
ClaimAsset { assets: (Here, 101).into(), ticket: GeneralIndex(0).into() },
DepositAsset {
assets: Wild(All),
max_assets: 1,
beneficiary: AccountIndex64 { index: 3, network: Any }.into(),
},
]),
20,
);
assert_eq!(r, Outcome::Incomplete(10, XcmError::UnknownClaim));
assert_eq!(assets(1001), vec![(Here, 900).into()]);
assert_eq!(assets(3), vec![]);
assert_eq!(old_trapped_assets, TrappedAssets::get());
let r = XcmExecutor::<TestConfig>::execute_xcm(
Parachain(1).into(),
Xcm(vec![
ClaimAsset { assets: (Here, 100).into(), ticket: GeneralIndex(0).into() },
DepositAsset {
assets: Wild(All),
max_assets: 1,
beneficiary: AccountIndex64 { index: 3, network: Any }.into(),
},
]),
20,
);
assert_eq!(r, Outcome::Complete(20));
assert_eq!(assets(1001), vec![(Here, 900).into()]);
assert_eq!(assets(3), vec![(Here, 100).into()]);
// Same again doesn't work :-)
let r = XcmExecutor::<TestConfig>::execute_xcm(
Parachain(1).into(),
Xcm(vec![
ClaimAsset { assets: (Here, 100).into(), ticket: GeneralIndex(0).into() },
DepositAsset {
assets: Wild(All),
max_assets: 1,
beneficiary: AccountIndex64 { index: 3, network: Any }.into(),
},
]),
20,
);
assert_eq!(r, Outcome::Incomplete(10, XcmError::UnknownClaim));
}
#[test]
fn errors_should_return_unused_weight() {
// we'll let them have message execution for free.
+3 -1
View File
@@ -166,7 +166,9 @@ impl xcm_executor::Config for XcmConfig {
type Barrier = Barrier;
type Weigher = FixedWeightBounds<BaseXcmWeight, Call, MaxInstructions>;
type Trader = FixedRateOfFungible<KsmPerSecond, ()>;
type ResponseHandler = ();
type ResponseHandler = XcmPallet;
type AssetTrap = XcmPallet;
type AssetClaims = XcmPallet;
}
pub type LocalOriginToLocation = SignedToAccountId32<Origin, AccountId, KusamaNetwork>;