XCM v2: Scripting, Query responses, Exception handling and Error reporting (#3629)

* Intoduce XCM v2

Also some minor fix for v0/v1

* Minor version cleanup

* Minor version cleanup

* Introduce SendError for XcmSend trait to avoid cycles with having Outcome in Xcm

* comment

* Corrent type

* Docs

* Fix build

* Fixes

* Introduce the basic impl

* Docs

* Add function

* Basic implementation

* Weighed responses and on_report

* Make XCM more script-like

* Remove BuyExecution::orders

* Fixes

* Fixes

* Fixes

* Formatting

* Initial draft and make pallet-xcm build

* fix XCM tests

* Formatting

* Fixes

* Formatting

* spelling

* Fixes

* Fixes

* spelling

* tests for translation

* extra fields to XCM pallet

* Formatting

* Fixes

* spelling

* first integration test

* Another integration test

* Formatting

* fix tests

* all tests

* Fixes

* Fixes

* Formatting

* Fixes

* Fixes

* Formatting

* Bump

* Remove unneeded structuring

* add instruction

* Fixes

* spelling

* Fixes

* Fixes

* Formatting

* Fixes

* Fixes

* Formatting

* Introduce and use VersionedResponse

* Introduce versioning to dispatchables' params

* Fixes

* Formatting

* Rest of merge

* more work

* Formatting

* Basic logic

* Fixes

* Fixes

* Add test

* Fixes

* Formatting

* Fixes

* Fixes

* Fixes

* Nits

* Simplify

* Spelling

* Formatting

* Return weight of unexecuted instructions in case of error as surplus

* Formatting

* Fixes

* Test for instruction count limiting

* Formatting

* Docs
This commit is contained in:
Gavin Wood
2021-08-26 12:41:16 +02:00
committed by GitHub
parent 4193f05fa3
commit 8b80b283d4
37 changed files with 3070 additions and 966 deletions
+43 -30
View File
@@ -19,10 +19,10 @@
use frame_support::{ensure, traits::Contains, weights::Weight};
use polkadot_parachain::primitives::IsSystem;
use sp_std::{marker::PhantomData, result::Result};
use xcm::latest::{Junction, Junctions, MultiLocation, Order, Xcm};
use xcm::latest::{Instruction::*, Junction, Junctions, MultiLocation, WeightLimit::*, Xcm};
use xcm_executor::traits::{OnResponse, ShouldExecute};
/// Execution barrier that just takes `shallow_weight` from `weight_credit`.
/// Execution barrier that just takes `max_weight` from `weight_credit`.
///
/// Useful to allow XCM execution by local chain users via extrinsics.
/// E.g. `pallet_xcm::reserve_asset_transfer` to transfer a reserve asset
@@ -30,42 +30,53 @@ use xcm_executor::traits::{OnResponse, ShouldExecute};
pub struct TakeWeightCredit;
impl ShouldExecute for TakeWeightCredit {
fn should_execute<Call>(
_origin: &MultiLocation,
_origin: &Option<MultiLocation>,
_top_level: bool,
_message: &Xcm<Call>,
shallow_weight: Weight,
_message: &mut Xcm<Call>,
max_weight: Weight,
weight_credit: &mut Weight,
) -> Result<(), ()> {
*weight_credit = weight_credit.checked_sub(shallow_weight).ok_or(())?;
*weight_credit = weight_credit.checked_sub(max_weight).ok_or(())?;
Ok(())
}
}
/// Allows execution from `origin` if it is contained in `T` (i.e. `T::Contains(origin)`) taking payments into
/// account.
/// Allows execution from `origin` if it is contained in `T` (i.e. `T::Contains(origin)`) taking
/// payments into account.
///
/// Only allows for `TeleportAsset`, `WithdrawAsset` and `ReserveAssetDeposit` XCMs because they are the only ones
/// that place assets in the Holding Register to pay for execution.
/// Only allows for `TeleportAsset`, `WithdrawAsset` and `ReserveAssetDeposit` XCMs because they are
/// the only ones that place assets in the Holding Register to pay for execution.
pub struct AllowTopLevelPaidExecutionFrom<T>(PhantomData<T>);
impl<T: Contains<MultiLocation>> ShouldExecute for AllowTopLevelPaidExecutionFrom<T> {
fn should_execute<Call>(
origin: &MultiLocation,
origin: &Option<MultiLocation>,
top_level: bool,
message: &Xcm<Call>,
shallow_weight: Weight,
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, ());
match message {
Xcm::ReceiveTeleportedAsset { effects, .. } |
Xcm::WithdrawAsset { effects, .. } |
Xcm::ReserveAssetDeposited { effects, .. }
if matches!(
effects.first(),
Some(Order::BuyExecution { debt, ..}) if *debt >= shallow_weight
) =>
Ok(()),
let mut iter = message.0.iter_mut();
let i = iter.next().ok_or(())?;
match i {
ReceiveTeleportedAsset(..) | WithdrawAsset(..) | ReserveAssetDeposited(..) => (),
_ => 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 >= max_weight => {
*weight = max_weight;
Ok(())
},
BuyExecution { ref mut weight_limit, .. } if weight_limit == &Unlimited => {
*weight_limit = Limited(max_weight);
Ok(())
},
_ => Err(()),
}
}
@@ -76,12 +87,13 @@ 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: &MultiLocation,
origin: &Option<MultiLocation>,
_top_level: bool,
_message: &Xcm<Call>,
_shallow_weight: Weight,
_message: &mut Xcm<Call>,
_max_weight: Weight,
_weight_credit: &mut Weight,
) -> Result<(), ()> {
let origin = origin.as_ref().ok_or(())?;
ensure!(T::contains(origin), ());
Ok(())
}
@@ -103,14 +115,15 @@ 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: &MultiLocation,
origin: &Option<MultiLocation>,
_top_level: bool,
message: &Xcm<Call>,
_shallow_weight: Weight,
message: &mut Xcm<Call>,
_max_weight: Weight,
_weight_credit: &mut Weight,
) -> Result<(), ()> {
match message {
Xcm::QueryResponse { query_id, .. }
let origin = origin.as_ref().ok_or(())?;
match message.0.first() {
Some(QueryResponse { query_id, .. })
if ResponseHandler::expecting_response(origin, *query_id) =>
Ok(()),
_ => Err(()),
+5 -3
View File
@@ -107,7 +107,7 @@ pub fn sent_xcm() -> Vec<(MultiLocation, opaque::Xcm)> {
}
pub struct TestSendXcm;
impl SendXcm for TestSendXcm {
fn send_xcm(dest: MultiLocation, msg: opaque::Xcm) -> XcmResult {
fn send_xcm(dest: MultiLocation, msg: opaque::Xcm) -> SendResult {
SENT_XCM.with(|q| q.borrow_mut().push((dest, msg)));
Ok(())
}
@@ -222,9 +222,10 @@ impl OnResponse for TestResponseHandler {
})
}
fn on_response(
_origin: MultiLocation,
_origin: &MultiLocation,
query_id: u64,
response: xcm::latest::Response,
_max_weight: Weight,
) -> Weight {
QUERIES.with(|q| {
q.borrow_mut().entry(query_id).and_modify(|v| {
@@ -258,6 +259,7 @@ parameter_types! {
pub static AllowPaidFrom: Vec<MultiLocation> = vec![];
// 1_000_000_000_000 => 1 unit of asset for 1 unit of Weight.
pub static WeightPrice: (AssetId, u128) = (From::from(Here), 1_000_000_000_000);
pub static MaxInstructions: u32 = 100;
}
pub type TestBarrier = (
@@ -277,7 +279,7 @@ impl Config for TestConfig {
type IsTeleporter = TestIsTeleporter;
type LocationInverter = LocationInverter<TestAncestry>;
type Barrier = TestBarrier;
type Weigher = FixedWeightBounds<UnitWeightCost, TestCall>;
type Weigher = FixedWeightBounds<UnitWeightCost, TestCall, MaxInstructions>;
type Trader = FixedRateOfFungible<WeightPrice, ()>;
type ResponseHandler = TestResponseHandler;
}
+196 -117
View File
@@ -44,31 +44,21 @@ fn basic_setup_works() {
#[test]
fn weigher_should_work() {
let mut message = opaque::Xcm::ReserveAssetDeposited {
assets: (Parent, 100).into(),
effects: vec![
Order::BuyExecution {
fees: (Parent, 1).into(),
weight: 0,
debt: 30,
halt_on_error: true,
instructions: vec![],
},
Order::DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() },
],
}
.into();
assert_eq!(<TestConfig as Config>::Weigher::shallow(&mut message), Ok(30));
let mut message = Xcm(vec![
ReserveAssetDeposited((Parent, 100).into()),
BuyExecution { fees: (Parent, 1).into(), weight_limit: Limited(30) },
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() },
]);
assert_eq!(<TestConfig as Config>::Weigher::weight(&mut message), Ok(30));
}
#[test]
fn take_weight_credit_barrier_should_work() {
let mut message =
opaque::Xcm::TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() };
Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]);
let mut weight_credit = 10;
let r = TakeWeightCredit::should_execute(
&Parent.into(),
&Some(Parent.into()),
true,
&mut message,
10,
@@ -78,7 +68,7 @@ fn take_weight_credit_barrier_should_work() {
assert_eq!(weight_credit, 0);
let r = TakeWeightCredit::should_execute(
&Parent.into(),
&Some(Parent.into()),
true,
&mut message,
10,
@@ -91,12 +81,12 @@ fn take_weight_credit_barrier_should_work() {
#[test]
fn allow_unpaid_should_work() {
let mut message =
opaque::Xcm::TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() };
Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]);
AllowUnpaidFrom::set(vec![Parent.into()]);
let r = AllowUnpaidExecutionFrom::<IsInVec<AllowUnpaidFrom>>::should_execute(
&Parachain(1).into(),
&Some(Parachain(1).into()),
true,
&mut message,
10,
@@ -105,7 +95,7 @@ fn allow_unpaid_should_work() {
assert_eq!(r, Err(()));
let r = AllowUnpaidExecutionFrom::<IsInVec<AllowUnpaidFrom>>::should_execute(
&Parent.into(),
&Some(Parent.into()),
true,
&mut message,
10,
@@ -119,10 +109,10 @@ fn allow_paid_should_work() {
AllowPaidFrom::set(vec![Parent.into()]);
let mut message =
opaque::Xcm::TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() };
Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]);
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
&Parachain(1).into(),
&Some(Parachain(1).into()),
true,
&mut message,
10,
@@ -131,22 +121,14 @@ fn allow_paid_should_work() {
assert_eq!(r, Err(()));
let fees = (Parent, 1).into();
let mut underpaying_message = opaque::Xcm::ReserveAssetDeposited {
assets: (Parent, 100).into(),
effects: vec![
Order::BuyExecution {
fees,
weight: 0,
debt: 20,
halt_on_error: true,
instructions: vec![],
},
Order::DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() },
],
};
let mut underpaying_message = Xcm::<()>(vec![
ReserveAssetDeposited((Parent, 100).into()),
BuyExecution { fees, weight_limit: Limited(20) },
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() },
]);
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
&Parent.into(),
&Some(Parent.into()),
true,
&mut underpaying_message,
30,
@@ -155,22 +137,14 @@ fn allow_paid_should_work() {
assert_eq!(r, Err(()));
let fees = (Parent, 1).into();
let mut paying_message = opaque::Xcm::ReserveAssetDeposited {
assets: (Parent, 100).into(),
effects: vec![
Order::BuyExecution {
fees,
weight: 0,
debt: 30,
halt_on_error: true,
instructions: vec![],
},
Order::DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() },
],
};
let mut paying_message = Xcm::<()>(vec![
ReserveAssetDeposited((Parent, 100).into()),
BuyExecution { fees, weight_limit: Limited(30) },
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() },
]);
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
&Parachain(1).into(),
&Some(Parachain(1).into()),
true,
&mut paying_message,
30,
@@ -179,7 +153,7 @@ fn allow_paid_should_work() {
assert_eq!(r, Err(()));
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
&Parent.into(),
&Some(Parent.into()),
true,
&mut paying_message,
30,
@@ -196,23 +170,11 @@ fn paying_reserve_deposit_should_work() {
let origin = Parent.into();
let fees = (Parent, 30).into();
let message = Xcm::<TestCall>::ReserveAssetDeposited {
assets: (Parent, 100).into(),
effects: vec![
Order::<TestCall>::BuyExecution {
fees,
weight: 0,
debt: 30,
halt_on_error: true,
instructions: vec![],
},
Order::<TestCall>::DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: Here.into(),
},
],
};
let message = Xcm(vec![
ReserveAssetDeposited((Parent, 100).into()),
BuyExecution { fees, weight_limit: Limited(30) },
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() },
]);
let weight_limit = 50;
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message, weight_limit);
assert_eq!(r, Outcome::Complete(30));
@@ -228,10 +190,10 @@ fn transfer_should_work() {
// They want to transfer 100 of them to their sibling parachain #2
let r = XcmExecutor::<TestConfig>::execute_xcm(
Parachain(1).into(),
Xcm::TransferAsset {
Xcm(vec![TransferAsset {
assets: (Here, 100).into(),
beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(),
},
}]),
50,
);
assert_eq!(r, Outcome::Complete(10));
@@ -240,6 +202,132 @@ fn transfer_should_work() {
assert_eq!(sent_xcm(), vec![]);
}
#[test]
fn errors_should_return_unused_weight() {
// we'll let them have message execution for free.
AllowUnpaidFrom::set(vec![Here.into()]);
// We own 1000 of our tokens.
add_asset(3000, (Here, 11));
let mut message = Xcm(vec![
// First xfer results in an error on the last message only
TransferAsset {
assets: (Here, 1).into(),
beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(),
},
// Second xfer results in error third message and after
TransferAsset {
assets: (Here, 2).into(),
beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(),
},
// Third xfer results in error second message and after
TransferAsset {
assets: (Here, 4).into(),
beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(),
},
]);
// Weight limit of 70 is needed.
let limit = <TestConfig as Config>::Weigher::weight(&mut message).unwrap();
assert_eq!(limit, 30);
let r = XcmExecutor::<TestConfig>::execute_xcm(Here.into(), message.clone(), limit);
assert_eq!(r, Outcome::Complete(30));
assert_eq!(assets(3), vec![(Here, 7).into()]);
assert_eq!(assets(3000), vec![(Here, 4).into()]);
assert_eq!(sent_xcm(), vec![]);
let r = XcmExecutor::<TestConfig>::execute_xcm(Here.into(), message.clone(), limit);
assert_eq!(r, Outcome::Incomplete(30, XcmError::NotWithdrawable));
assert_eq!(assets(3), vec![(Here, 10).into()]);
assert_eq!(assets(3000), vec![(Here, 1).into()]);
assert_eq!(sent_xcm(), vec![]);
let r = XcmExecutor::<TestConfig>::execute_xcm(Here.into(), message.clone(), limit);
assert_eq!(r, Outcome::Incomplete(20, XcmError::NotWithdrawable));
assert_eq!(assets(3), vec![(Here, 11).into()]);
assert_eq!(assets(3000), vec![]);
assert_eq!(sent_xcm(), vec![]);
let r = XcmExecutor::<TestConfig>::execute_xcm(Here.into(), message, limit);
assert_eq!(r, Outcome::Incomplete(10, XcmError::NotWithdrawable));
assert_eq!(assets(3), vec![(Here, 11).into()]);
assert_eq!(assets(3000), vec![]);
assert_eq!(sent_xcm(), vec![]);
}
#[test]
fn weight_bounds_should_respect_instructions_limit() {
MaxInstructions::set(3);
let mut message = Xcm(vec![ClearOrigin; 4]);
// 4 instructions are too many.
assert_eq!(<TestConfig as Config>::Weigher::weight(&mut message), Err(()));
let mut message =
Xcm(vec![SetErrorHandler(Xcm(vec![ClearOrigin])), SetAppendix(Xcm(vec![ClearOrigin]))]);
// 4 instructions are too many, even when hidden within 2.
assert_eq!(<TestConfig as Config>::Weigher::weight(&mut message), Err(()));
let mut message =
Xcm(vec![SetErrorHandler(Xcm(vec![SetErrorHandler(Xcm(vec![SetErrorHandler(Xcm(
vec![ClearOrigin],
))]))]))]);
// 4 instructions are too many, even when it's just one that's 3 levels deep.
assert_eq!(<TestConfig as Config>::Weigher::weight(&mut message), Err(()));
let mut message =
Xcm(vec![SetErrorHandler(Xcm(vec![SetErrorHandler(Xcm(vec![ClearOrigin]))]))]);
// 3 instructions are OK.
assert_eq!(<TestConfig as Config>::Weigher::weight(&mut message), Ok(30));
}
#[test]
fn code_registers_should_work() {
// we'll let them have message execution for free.
AllowUnpaidFrom::set(vec![Here.into()]);
// We own 1000 of our tokens.
add_asset(3000, (Here, 21));
let mut message = Xcm(vec![
// Set our error handler - this will fire only on the second message, when there's an error
SetErrorHandler(Xcm(vec![
TransferAsset {
assets: (Here, 2).into(),
beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(),
},
// It was handled fine.
ClearError,
])),
// Set the appendix - this will always fire.
SetAppendix(Xcm(vec![TransferAsset {
assets: (Here, 4).into(),
beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(),
}])),
// First xfer always works ok
TransferAsset {
assets: (Here, 1).into(),
beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(),
},
// Second xfer results in error on the second message - our error handler will fire.
TransferAsset {
assets: (Here, 8).into(),
beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(),
},
]);
// Weight limit of 70 is needed.
let limit = <TestConfig as Config>::Weigher::weight(&mut message).unwrap();
assert_eq!(limit, 70);
let r = XcmExecutor::<TestConfig>::execute_xcm(Here.into(), message.clone(), limit);
assert_eq!(r, Outcome::Complete(50)); // We don't pay the 20 weight for the error handler.
assert_eq!(assets(3), vec![(Here, 13).into()]);
assert_eq!(assets(3000), vec![(Here, 8).into()]);
assert_eq!(sent_xcm(), vec![]);
let r = XcmExecutor::<TestConfig>::execute_xcm(Here.into(), message, limit);
assert_eq!(r, Outcome::Complete(70)); // We pay the full weight here.
assert_eq!(assets(3), vec![(Here, 20).into()]);
assert_eq!(assets(3000), vec![(Here, 1).into()]);
assert_eq!(sent_xcm(), vec![]);
}
#[test]
fn reserve_transfer_should_work() {
AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]);
@@ -252,15 +340,15 @@ fn reserve_transfer_should_work() {
// and let them know to hand it to account #3.
let r = XcmExecutor::<TestConfig>::execute_xcm(
Parachain(1).into(),
Xcm::TransferReserveAsset {
Xcm(vec![TransferReserveAsset {
assets: (Here, 100).into(),
dest: Parachain(2).into(),
effects: vec![Order::DepositAsset {
xcm: Xcm::<()>(vec![DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: three.clone(),
}],
},
}]),
}]),
50,
);
assert_eq!(r, Outcome::Complete(10));
@@ -270,14 +358,11 @@ fn reserve_transfer_should_work() {
sent_xcm(),
vec![(
Parachain(2).into(),
Xcm::ReserveAssetDeposited {
assets: (Parent, 100).into(),
effects: vec![Order::DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: three
}],
}
Xcm::<()>(vec![
ReserveAssetDeposited((Parent, 100).into()),
ClearOrigin,
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: three },
]),
)]
);
}
@@ -287,11 +372,11 @@ fn transacting_should_work() {
AllowUnpaidFrom::set(vec![Parent.into()]);
let origin = Parent.into();
let message = Xcm::<TestCall>::Transact {
let message = Xcm::<TestCall>(vec![Transact {
origin_type: OriginKind::Native,
require_weight_at_most: 50,
call: TestCall::Any(50, None).encode().into(),
};
}]);
let weight_limit = 60;
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message, weight_limit);
assert_eq!(r, Outcome::Complete(60));
@@ -302,14 +387,14 @@ fn transacting_should_respect_max_weight_requirement() {
AllowUnpaidFrom::set(vec![Parent.into()]);
let origin = Parent.into();
let message = Xcm::<TestCall>::Transact {
let message = Xcm::<TestCall>(vec![Transact {
origin_type: OriginKind::Native,
require_weight_at_most: 40,
call: TestCall::Any(50, None).encode().into(),
};
}]);
let weight_limit = 60;
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message, weight_limit);
assert_eq!(r, Outcome::Incomplete(60, XcmError::TooMuchWeightRequired));
assert_eq!(r, Outcome::Incomplete(50, XcmError::TooMuchWeightRequired));
}
#[test]
@@ -317,11 +402,11 @@ fn transacting_should_refund_weight() {
AllowUnpaidFrom::set(vec![Parent.into()]);
let origin = Parent.into();
let message = Xcm::<TestCall>::Transact {
let message = Xcm::<TestCall>(vec![Transact {
origin_type: OriginKind::Native,
require_weight_at_most: 50,
call: TestCall::Any(50, Some(30)).encode().into(),
};
}]);
let weight_limit = 60;
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message, weight_limit);
assert_eq!(r, Outcome::Complete(40));
@@ -336,32 +421,22 @@ fn paid_transacting_should_refund_payment_for_unused_weight() {
let origin = one.clone();
let fees = (Parent, 100).into();
let message = Xcm::<TestCall>::WithdrawAsset {
assets: (Parent, 100).into(), // enough for 100 units of weight.
effects: vec![
Order::<TestCall>::BuyExecution {
fees,
weight: 70,
debt: 30,
halt_on_error: true,
instructions: vec![Xcm::<TestCall>::Transact {
origin_type: OriginKind::Native,
require_weight_at_most: 60,
// call estimated at 70 but only takes 10.
call: TestCall::Any(60, Some(10)).encode().into(),
}],
},
Order::<TestCall>::DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: one.clone(),
},
],
};
let message = Xcm::<TestCall>(vec![
WithdrawAsset((Parent, 100).into()), // enough for 100 units of weight.
BuyExecution { fees, weight_limit: Limited(100) },
Transact {
origin_type: OriginKind::Native,
require_weight_at_most: 50,
// call estimated at 50 but only takes 10.
call: TestCall::Any(50, Some(10)).encode().into(),
},
RefundSurplus,
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: one.clone() },
]);
let weight_limit = 100;
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message, weight_limit);
assert_eq!(r, Outcome::Complete(50));
assert_eq!(assets(1), vec![(Parent, 50).into()]);
assert_eq!(r, Outcome::Complete(60));
assert_eq!(assets(1), vec![(Parent, 40).into()]);
}
#[test]
@@ -372,7 +447,11 @@ fn prepaid_result_of_query_should_get_free_execution() {
expect_response(query_id, origin.clone());
let the_response = Response::Assets((Parent, 100).into());
let message = Xcm::<TestCall>::QueryResponse { query_id, response: the_response.clone() };
let message = Xcm::<TestCall>(vec![QueryResponse {
query_id,
response: the_response.clone(),
max_weight: 10,
}]);
let weight_limit = 10;
// First time the response gets through since we're expecting it...
@@ -382,7 +461,7 @@ fn prepaid_result_of_query_should_get_free_execution() {
// Second time it doesn't, since we're not.
let r = XcmExecutor::<TestConfig>::execute_xcm(origin.clone(), message.clone(), weight_limit);
assert_eq!(r, Outcome::Incomplete(10, XcmError::Barrier));
assert_eq!(r, Outcome::Error(XcmError::Barrier));
}
fn fungible_multi_asset(location: MultiLocation, amount: u128) -> MultiAsset {
+35 -66
View File
@@ -21,76 +21,43 @@ use frame_support::{
use parity_scale_codec::Decode;
use sp_runtime::traits::{SaturatedConversion, Saturating, Zero};
use sp_std::{convert::TryInto, marker::PhantomData, result::Result};
use xcm::latest::{AssetId, AssetId::Concrete, Error, MultiAsset, MultiLocation, Order, Xcm};
use xcm::latest::prelude::*;
use xcm_executor::{
traits::{WeightBounds, WeightTrader},
Assets,
};
pub struct FixedWeightBounds<T, C>(PhantomData<(T, C)>);
impl<T: Get<Weight>, C: Decode + GetDispatchInfo> WeightBounds<C> for FixedWeightBounds<T, C> {
fn shallow(message: &mut Xcm<C>) -> Result<Weight, ()> {
Ok(match message {
Xcm::Transact { call, .. } =>
call.ensure_decoded()?.get_dispatch_info().weight.saturating_add(T::get()),
Xcm::RelayedFrom { ref mut message, .. } =>
T::get().saturating_add(Self::shallow(message.as_mut())?),
Xcm::WithdrawAsset { effects, .. } |
Xcm::ReserveAssetDeposited { effects, .. } |
Xcm::ReceiveTeleportedAsset { effects, .. } => {
let mut extra = T::get();
for order in effects.iter_mut() {
extra.saturating_accrue(Self::shallow_order(order)?);
}
extra
},
_ => T::get(),
})
pub struct FixedWeightBounds<T, C, M>(PhantomData<(T, C, M)>);
impl<T: Get<Weight>, C: Decode + GetDispatchInfo, M: Get<u32>> WeightBounds<C>
for FixedWeightBounds<T, C, M>
{
fn weight(message: &mut Xcm<C>) -> Result<Weight, ()> {
let mut instructions_left = M::get();
Self::weight_with_limit(message, &mut instructions_left)
}
fn deep(message: &mut Xcm<C>) -> Result<Weight, ()> {
Ok(match message {
Xcm::RelayedFrom { ref mut message, .. } => Self::deep(message.as_mut())?,
Xcm::WithdrawAsset { effects, .. } |
Xcm::ReserveAssetDeposited { effects, .. } |
Xcm::ReceiveTeleportedAsset { effects, .. } => {
let mut extra = 0;
for order in effects.iter_mut() {
extra.saturating_accrue(Self::deep_order(order)?);
}
extra
},
_ => 0,
})
fn instr_weight(message: &Instruction<C>) -> Result<Weight, ()> {
Self::instr_weight_with_limit(message, &mut u32::max_value())
}
}
impl<T: Get<Weight>, C: Decode + GetDispatchInfo> FixedWeightBounds<T, C> {
fn shallow_order(order: &mut Order<C>) -> Result<Weight, ()> {
Ok(match order {
Order::BuyExecution { .. } => {
// On success, execution of this will result in more weight being consumed but
// we don't count it here since this is only the *shallow*, non-negotiable weight
// spend and doesn't count weight placed behind a `BuyExecution` since it will not
// be definitely consumed from any existing weight credit if execution of the message
// is attempted.
T::get()
},
_ => T::get(),
})
impl<T: Get<Weight>, C: Decode + GetDispatchInfo, M> FixedWeightBounds<T, C, M> {
fn weight_with_limit(message: &Xcm<C>, instrs_limit: &mut u32) -> Result<Weight, ()> {
let mut r = 0;
*instrs_limit = instrs_limit.checked_sub(message.0.len() as u32).ok_or(())?;
for m in message.0.iter() {
r += Self::instr_weight_with_limit(m, instrs_limit)?;
}
Ok(r)
}
fn deep_order(order: &mut Order<C>) -> Result<Weight, ()> {
Ok(match order {
Order::BuyExecution { instructions, .. } => {
let mut extra = 0;
for instruction in instructions.iter_mut() {
extra.saturating_accrue(
Self::shallow(instruction)?.saturating_add(Self::deep(instruction)?),
);
}
extra
},
fn instr_weight_with_limit(
message: &Instruction<C>,
instrs_limit: &mut u32,
) -> Result<Weight, ()> {
Ok(T::get().saturating_add(match message {
Transact { require_weight_at_most, .. } => *require_weight_at_most,
SetErrorHandler(xcm) | SetAppendix(xcm) => Self::weight_with_limit(xcm, instrs_limit)?,
_ => 0,
})
}))
}
}
@@ -124,10 +91,11 @@ impl<T: Get<(MultiLocation, u128)>, R: TakeRevenue> WeightTrader
Self(0, 0, PhantomData)
}
fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, Error> {
fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError> {
let (id, units_per_second) = T::get();
let amount = units_per_second * (weight as u128) / (WEIGHT_PER_SECOND as u128);
let unused = payment.checked_sub((id, amount).into()).map_err(|_| Error::TooExpensive)?;
let unused =
payment.checked_sub((id, amount).into()).map_err(|_| XcmError::TooExpensive)?;
self.0 = self.0.saturating_add(weight);
self.1 = self.1.saturating_add(amount);
Ok(unused)
@@ -169,13 +137,14 @@ impl<T: Get<(AssetId, u128)>, R: TakeRevenue> WeightTrader for FixedRateOfFungib
Self(0, 0, PhantomData)
}
fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, Error> {
fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError> {
let (id, units_per_second) = T::get();
let amount = units_per_second * (weight as u128) / (WEIGHT_PER_SECOND as u128);
if amount == 0 {
return Ok(payment)
}
let unused = payment.checked_sub((id, amount).into()).map_err(|_| Error::TooExpensive)?;
let unused =
payment.checked_sub((id, amount).into()).map_err(|_| XcmError::TooExpensive)?;
self.0 = self.0.saturating_add(weight);
self.1 = self.1.saturating_add(amount);
Ok(unused)
@@ -228,11 +197,11 @@ impl<
Self(0, Zero::zero(), PhantomData)
}
fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, Error> {
fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError> {
let amount = WeightToFee::calc(&weight);
let u128_amount: u128 = amount.try_into().map_err(|_| Error::Overflow)?;
let u128_amount: u128 = amount.try_into().map_err(|_| XcmError::Overflow)?;
let required = (Concrete(AssetId::get()), u128_amount).into();
let unused = payment.checked_sub(required).map_err(|_| Error::TooExpensive)?;
let unused = payment.checked_sub(required).map_err(|_| XcmError::TooExpensive)?;
self.0 = self.0.saturating_add(weight);
self.1 = self.1.saturating_add(amount);
Ok(unused)
+7 -4
View File
@@ -47,7 +47,7 @@ pub fn sent_xcm() -> Vec<(MultiLocation, opaque::Xcm)> {
}
pub struct TestSendXcm;
impl SendXcm for TestSendXcm {
fn send_xcm(dest: MultiLocation, msg: opaque::Xcm) -> XcmResult {
fn send_xcm(dest: MultiLocation, msg: opaque::Xcm) -> SendResult {
SENT_XCM.with(|q| q.borrow_mut().push((dest, msg)));
Ok(())
}
@@ -150,6 +150,7 @@ pub type Barrier = (
parameter_types! {
pub const KusamaForStatemint: (MultiAssetFilter, MultiLocation) =
(MultiAssetFilter::Wild(WildMultiAsset::AllOf { id: Concrete(MultiLocation::here()), fun: WildFungible }), X1(Parachain(1000)).into());
pub const MaxInstructions: u32 = 100;
}
pub type TrustedTeleporters = (xcm_builder::Case<KusamaForStatemint>,);
@@ -163,7 +164,7 @@ impl xcm_executor::Config for XcmConfig {
type IsTeleporter = TrustedTeleporters;
type LocationInverter = LocationInverter<Ancestry>;
type Barrier = Barrier;
type Weigher = FixedWeightBounds<BaseXcmWeight, Call>;
type Weigher = FixedWeightBounds<BaseXcmWeight, Call, MaxInstructions>;
type Trader = FixedRateOfFungible<KsmPerSecond, ()>;
type ResponseHandler = ();
}
@@ -181,7 +182,9 @@ impl pallet_xcm::Config for Runtime {
type XcmExecutor = XcmExecutor<XcmConfig>;
type XcmTeleportFilter = Everything;
type XcmReserveTransferFilter = Everything;
type Weigher = FixedWeightBounds<BaseXcmWeight, Call>;
type Weigher = FixedWeightBounds<BaseXcmWeight, Call, MaxInstructions>;
type Call = Call;
type Origin = Origin;
}
impl origin::Config for Runtime {}
@@ -198,7 +201,7 @@ construct_runtime!(
System: frame_system::{Pallet, Call, Storage, Config, Event<T>},
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
ParasOrigin: origin::{Pallet, Origin},
XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event<T>},
XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event<T>, Origin},
}
);
+98 -111
View File
@@ -16,7 +16,6 @@
mod mock;
use frame_support::weights::Weight;
use mock::{
kusama_like_with_balances, AccountId, Balance, Balances, BaseXcmWeight, XcmConfig, CENTS,
};
@@ -31,15 +30,8 @@ pub const INITIAL_BALANCE: u128 = 100_000_000_000;
pub const REGISTER_AMOUNT: Balance = 10 * CENTS;
// Construct a `BuyExecution` order.
fn buy_execution<C>(debt: Weight) -> Order<C> {
use xcm::latest::prelude::*;
Order::BuyExecution {
fees: (Here, REGISTER_AMOUNT).into(),
weight: 0,
debt,
halt_on_error: false,
instructions: vec![],
}
fn buy_execution<C>() -> Instruction<C> {
BuyExecution { fees: (Here, REGISTER_AMOUNT).into(), weight_limit: Unlimited }
}
/// Scenario:
@@ -56,17 +48,15 @@ fn withdraw_and_deposit_works() {
let weight = 3 * BaseXcmWeight::get();
let r = XcmExecutor::<XcmConfig>::execute_xcm(
Parachain(PARA_ID).into(),
Xcm::WithdrawAsset {
assets: vec![(Here, amount).into()].into(),
effects: vec![
buy_execution(weight),
Order::DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: Parachain(other_para_id).into(),
},
],
},
Xcm(vec![
WithdrawAsset((Here, amount).into()),
buy_execution(),
DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: Parachain(other_para_id).into(),
},
]),
weight,
);
assert_eq!(r, Outcome::Complete(weight));
@@ -94,31 +84,31 @@ fn query_holding_works() {
let amount = REGISTER_AMOUNT;
let query_id = 1234;
let weight = 4 * BaseXcmWeight::get();
let max_response_weight = 1_000_000_000;
let r = XcmExecutor::<XcmConfig>::execute_xcm(
Parachain(PARA_ID).into(),
Xcm::WithdrawAsset {
assets: vec![(Here, amount).into()].into(),
effects: vec![
buy_execution(weight),
Order::DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: OnlyChild.into(), // invalid destination
},
// is not triggered becasue the deposit fails
Order::QueryHolding {
query_id,
dest: Parachain(PARA_ID).into(),
assets: All.into(),
},
],
},
Xcm(vec![
WithdrawAsset((Here, amount).into()),
buy_execution(),
DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: OnlyChild.into(), // invalid destination
},
// is not triggered becasue the deposit fails
QueryHolding {
query_id,
dest: Parachain(PARA_ID).into(),
assets: All.into(),
max_response_weight,
},
]),
weight,
);
assert_eq!(
r,
Outcome::Incomplete(
weight,
weight - BaseXcmWeight::get(),
XcmError::FailedToTransactAsset("AccountIdConversionFailed")
)
);
@@ -129,23 +119,22 @@ fn query_holding_works() {
// now do a successful transfer
let r = XcmExecutor::<XcmConfig>::execute_xcm(
Parachain(PARA_ID).into(),
Xcm::WithdrawAsset {
assets: vec![(Here, amount).into()].into(),
effects: vec![
buy_execution(weight),
Order::DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: Parachain(other_para_id).into(),
},
// used to get a notification in case of success
Order::QueryHolding {
query_id,
dest: Parachain(PARA_ID).into(),
assets: All.into(),
},
],
},
Xcm(vec![
WithdrawAsset((Here, amount).into()),
buy_execution(),
DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: Parachain(other_para_id).into(),
},
// used to get a notification in case of success
QueryHolding {
query_id,
dest: Parachain(PARA_ID).into(),
assets: All.into(),
max_response_weight: 1_000_000_000,
},
]),
weight,
);
assert_eq!(r, Outcome::Complete(weight));
@@ -156,7 +145,11 @@ fn query_holding_works() {
mock::sent_xcm(),
vec![(
Parachain(PARA_ID).into(),
Xcm::QueryResponse { query_id, response: Response::Assets(vec![].into()) }
Xcm(vec![QueryResponse {
query_id,
response: Response::Assets(vec![].into()),
max_weight: 1_000_000_000,
}]),
)]
);
});
@@ -180,8 +173,8 @@ fn teleport_to_statemine_works() {
let other_para_id = 3000;
let amount = REGISTER_AMOUNT;
let teleport_effects = vec![
buy_execution(5), // unchecked mock value
Order::DepositAsset {
buy_execution(), // unchecked mock value
DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: (1, Parachain(PARA_ID)).into(),
@@ -192,17 +185,15 @@ fn teleport_to_statemine_works() {
// teleports are allowed to community chains, even in the absence of trust from their side.
let r = XcmExecutor::<XcmConfig>::execute_xcm(
Parachain(PARA_ID).into(),
Xcm::WithdrawAsset {
assets: vec![(Here, amount).into()].into(),
effects: vec![
buy_execution(weight),
Order::InitiateTeleport {
assets: All.into(),
dest: Parachain(other_para_id).into(),
effects: teleport_effects.clone(),
},
],
},
Xcm(vec![
WithdrawAsset((Here, amount).into()),
buy_execution(),
InitiateTeleport {
assets: All.into(),
dest: Parachain(other_para_id).into(),
xcm: Xcm(teleport_effects.clone()),
},
]),
weight,
);
assert_eq!(r, Outcome::Complete(weight));
@@ -210,27 +201,25 @@ fn teleport_to_statemine_works() {
mock::sent_xcm(),
vec![(
Parachain(other_para_id).into(),
Xcm::ReceiveTeleportedAsset {
assets: vec![(Parent, amount).into()].into(),
effects: teleport_effects.clone(),
}
Xcm(vec![ReceiveTeleportedAsset((Parent, amount).into()), ClearOrigin,]
.into_iter()
.chain(teleport_effects.clone().into_iter())
.collect())
)]
);
// teleports are allowed from statemine to kusama.
let r = XcmExecutor::<XcmConfig>::execute_xcm(
Parachain(PARA_ID).into(),
Xcm::WithdrawAsset {
assets: vec![(Here, amount).into()].into(),
effects: vec![
buy_execution(weight),
Order::InitiateTeleport {
assets: All.into(),
dest: Parachain(statemine_id).into(),
effects: teleport_effects.clone(),
},
],
},
Xcm(vec![
WithdrawAsset((Here, amount).into()),
buy_execution(),
InitiateTeleport {
assets: All.into(),
dest: Parachain(statemine_id).into(),
xcm: Xcm(teleport_effects.clone()),
},
]),
weight,
);
assert_eq!(r, Outcome::Complete(weight));
@@ -241,17 +230,17 @@ fn teleport_to_statemine_works() {
vec![
(
Parachain(other_para_id).into(),
Xcm::ReceiveTeleportedAsset {
assets: vec![(Parent, amount).into()].into(),
effects: teleport_effects.clone(),
}
Xcm(vec![ReceiveTeleportedAsset((Parent, amount).into()), ClearOrigin,]
.into_iter()
.chain(teleport_effects.clone().into_iter())
.collect()),
),
(
Parachain(statemine_id).into(),
Xcm::ReceiveTeleportedAsset {
assets: vec![(Parent, amount).into()].into(),
effects: teleport_effects,
}
Xcm(vec![ReceiveTeleportedAsset((Parent, amount).into()), ClearOrigin,]
.into_iter()
.chain(teleport_effects.clone().into_iter())
.collect()),
)
]
);
@@ -273,8 +262,8 @@ fn reserve_based_transfer_works() {
let other_para_id = 3000;
let amount = REGISTER_AMOUNT;
let transfer_effects = vec![
buy_execution(5), // unchecked mock value
Order::DepositAsset {
buy_execution(), // unchecked mock value
DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: (1, Parachain(PARA_ID)).into(),
@@ -283,18 +272,16 @@ fn reserve_based_transfer_works() {
let weight = 3 * BaseXcmWeight::get();
let r = XcmExecutor::<XcmConfig>::execute_xcm(
Parachain(PARA_ID).into(),
Xcm::WithdrawAsset {
assets: vec![(Here, amount).into()].into(),
effects: vec![
buy_execution(weight),
Order::DepositReserveAsset {
assets: All.into(),
max_assets: 1,
dest: Parachain(other_para_id).into(),
effects: transfer_effects.clone(),
},
],
},
Xcm(vec![
WithdrawAsset((Here, amount).into()),
buy_execution(),
DepositReserveAsset {
assets: All.into(),
max_assets: 1,
dest: Parachain(other_para_id).into(),
xcm: Xcm(transfer_effects.clone()),
},
]),
weight,
);
assert_eq!(r, Outcome::Complete(weight));
@@ -303,10 +290,10 @@ fn reserve_based_transfer_works() {
mock::sent_xcm(),
vec![(
Parachain(other_para_id).into(),
Xcm::ReserveAssetDeposited {
assets: vec![(Parent, amount).into()].into(),
effects: transfer_effects,
}
Xcm(vec![ReserveAssetDeposited((Parent, amount).into()), ClearOrigin,]
.into_iter()
.chain(transfer_effects.into_iter())
.collect())
)]
);
});