Files
pezkuwi-subxt/polkadot/xcm/xcm-builder/src/tests.rs
T
Gavin Wood 8b80b283d4 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
2021-08-26 12:41:16 +02:00

515 lines
16 KiB
Rust

// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot 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.
// Polkadot 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/>.
use super::{mock::*, *};
use frame_support::{assert_err, weights::constants::WEIGHT_PER_SECOND};
use xcm::latest::prelude::*;
use xcm_executor::{traits::*, Config, XcmExecutor};
#[test]
fn basic_setup_works() {
add_reserve(Parent.into(), Wild((Parent, WildFungible).into()));
assert!(<TestConfig as Config>::IsReserve::filter_asset_location(
&(Parent, 100).into(),
&Parent.into(),
));
assert_eq!(to_account(X1(Parachain(1)).into()), Ok(1001));
assert_eq!(to_account(X1(Parachain(50)).into()), Ok(1050));
assert_eq!(to_account(MultiLocation::new(1, X1(Parachain(1)))), Ok(2001));
assert_eq!(to_account(MultiLocation::new(1, X1(Parachain(50)))), Ok(2050));
assert_eq!(
to_account(MultiLocation::new(0, X1(AccountIndex64 { index: 1, network: Any }))),
Ok(1),
);
assert_eq!(
to_account(MultiLocation::new(0, X1(AccountIndex64 { index: 42, network: Any }))),
Ok(42),
);
assert_eq!(to_account(Here.into()), Ok(3000));
}
#[test]
fn weigher_should_work() {
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 =
Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]);
let mut weight_credit = 10;
let r = TakeWeightCredit::should_execute(
&Some(Parent.into()),
true,
&mut message,
10,
&mut weight_credit,
);
assert_eq!(r, Ok(()));
assert_eq!(weight_credit, 0);
let r = TakeWeightCredit::should_execute(
&Some(Parent.into()),
true,
&mut message,
10,
&mut weight_credit,
);
assert_eq!(r, Err(()));
assert_eq!(weight_credit, 0);
}
#[test]
fn allow_unpaid_should_work() {
let mut message =
Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]);
AllowUnpaidFrom::set(vec![Parent.into()]);
let r = AllowUnpaidExecutionFrom::<IsInVec<AllowUnpaidFrom>>::should_execute(
&Some(Parachain(1).into()),
true,
&mut message,
10,
&mut 0,
);
assert_eq!(r, Err(()));
let r = AllowUnpaidExecutionFrom::<IsInVec<AllowUnpaidFrom>>::should_execute(
&Some(Parent.into()),
true,
&mut message,
10,
&mut 0,
);
assert_eq!(r, Ok(()));
}
#[test]
fn allow_paid_should_work() {
AllowPaidFrom::set(vec![Parent.into()]);
let mut message =
Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]);
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
&Some(Parachain(1).into()),
true,
&mut message,
10,
&mut 0,
);
assert_eq!(r, Err(()));
let fees = (Parent, 1).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(
&Some(Parent.into()),
true,
&mut underpaying_message,
30,
&mut 0,
);
assert_eq!(r, Err(()));
let fees = (Parent, 1).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(
&Some(Parachain(1).into()),
true,
&mut paying_message,
30,
&mut 0,
);
assert_eq!(r, Err(()));
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
&Some(Parent.into()),
true,
&mut paying_message,
30,
&mut 0,
);
assert_eq!(r, Ok(()));
}
#[test]
fn paying_reserve_deposit_should_work() {
AllowPaidFrom::set(vec![Parent.into()]);
add_reserve(Parent.into(), (Parent, WildFungible).into());
WeightPrice::set((Parent.into(), 1_000_000_000_000));
let origin = Parent.into();
let fees = (Parent, 30).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));
assert_eq!(assets(3000), vec![(Parent, 70).into()]);
}
#[test]
fn transfer_should_work() {
// we'll let them have message execution for free.
AllowUnpaidFrom::set(vec![X1(Parachain(1)).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
let r = XcmExecutor::<TestConfig>::execute_xcm(
Parachain(1).into(),
Xcm(vec![TransferAsset {
assets: (Here, 100).into(),
beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(),
}]),
50,
);
assert_eq!(r, Outcome::Complete(10));
assert_eq!(assets(3), vec![(Here, 100).into()]);
assert_eq!(assets(1001), vec![(Here, 900).into()]);
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()]);
// Child parachain #1 owns 1000 tokens held by us in reserve.
add_asset(1001, (Here, 1000));
// The remote account owned by gav.
let three: MultiLocation = X1(AccountIndex64 { index: 3, network: Any }).into();
// They want to transfer 100 of our native asset from sovereign account of parachain #1 into #2
// and let them know to hand it to account #3.
let r = XcmExecutor::<TestConfig>::execute_xcm(
Parachain(1).into(),
Xcm(vec![TransferReserveAsset {
assets: (Here, 100).into(),
dest: Parachain(2).into(),
xcm: Xcm::<()>(vec![DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: three.clone(),
}]),
}]),
50,
);
assert_eq!(r, Outcome::Complete(10));
assert_eq!(assets(1002), vec![(Here, 100).into()]);
assert_eq!(
sent_xcm(),
vec![(
Parachain(2).into(),
Xcm::<()>(vec![
ReserveAssetDeposited((Parent, 100).into()),
ClearOrigin,
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: three },
]),
)]
);
}
#[test]
fn transacting_should_work() {
AllowUnpaidFrom::set(vec![Parent.into()]);
let origin = Parent.into();
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));
}
#[test]
fn transacting_should_respect_max_weight_requirement() {
AllowUnpaidFrom::set(vec![Parent.into()]);
let origin = Parent.into();
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(50, XcmError::TooMuchWeightRequired));
}
#[test]
fn transacting_should_refund_weight() {
AllowUnpaidFrom::set(vec![Parent.into()]);
let origin = Parent.into();
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));
}
#[test]
fn paid_transacting_should_refund_payment_for_unused_weight() {
let one: MultiLocation = X1(AccountIndex64 { index: 1, network: Any }).into();
AllowPaidFrom::set(vec![one.clone()]);
add_asset(1, (Parent, 100));
WeightPrice::set((Parent.into(), 1_000_000_000_000));
let origin = one.clone();
let fees = (Parent, 100).into();
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(60));
assert_eq!(assets(1), vec![(Parent, 40).into()]);
}
#[test]
fn prepaid_result_of_query_should_get_free_execution() {
let query_id = 33;
let origin: MultiLocation = Parent.into();
// We put this in manually here, but normally this would be done at the point of crafting the message.
expect_response(query_id, origin.clone());
let the_response = Response::Assets((Parent, 100).into());
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...
let r = XcmExecutor::<TestConfig>::execute_xcm(origin.clone(), message.clone(), weight_limit);
assert_eq!(r, Outcome::Complete(10));
assert_eq!(response(query_id).unwrap(), the_response);
// 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::Error(XcmError::Barrier));
}
fn fungible_multi_asset(location: MultiLocation, amount: u128) -> MultiAsset {
(AssetId::from(location), Fungibility::Fungible(amount)).into()
}
#[test]
fn weight_trader_tuple_should_work() {
pub const PARA_1: MultiLocation = X1(Parachain(1)).into();
pub const PARA_2: MultiLocation = X1(Parachain(2)).into();
parameter_types! {
pub static HereWeightPrice: (AssetId, u128) = (Here.into().into(), WEIGHT_PER_SECOND.into());
pub static PARA1WeightPrice: (AssetId, u128) = (PARA_1.into(), WEIGHT_PER_SECOND.into());
}
type Traders = (
// trader one
FixedRateOfFungible<HereWeightPrice, ()>,
// trader two
FixedRateOfFungible<PARA1WeightPrice, ()>,
);
let mut traders = Traders::new();
// trader one buys weight
assert_eq!(
traders.buy_weight(5, fungible_multi_asset(Here.into(), 10).into()),
Ok(fungible_multi_asset(Here.into(), 5).into()),
);
// trader one refunds
assert_eq!(traders.refund_weight(2), Some(fungible_multi_asset(Here.into(), 2)));
let mut traders = Traders::new();
// trader one failed; trader two buys weight
assert_eq!(
traders.buy_weight(5, fungible_multi_asset(PARA_1, 10).into()),
Ok(fungible_multi_asset(PARA_1, 5).into()),
);
// trader two refunds
assert_eq!(traders.refund_weight(2), Some(fungible_multi_asset(PARA_1, 2)));
let mut traders = Traders::new();
// all traders fails
assert_err!(
traders.buy_weight(5, fungible_multi_asset(PARA_2, 10).into()),
XcmError::TooExpensive,
);
// and no refund
assert_eq!(traders.refund_weight(2), None);
}