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
+402 -77
View File
@@ -33,7 +33,10 @@ use sp_std::{
prelude::*,
vec,
};
use xcm::{latest::prelude::*, VersionedMultiAssets, VersionedMultiLocation, VersionedXcm};
use xcm::{
latest::prelude::*, VersionedMultiAssets, VersionedMultiLocation, VersionedResponse,
VersionedXcm,
};
use xcm_executor::traits::ConvertOrigin;
use frame_support::PalletId;
@@ -42,10 +45,13 @@ pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use sp_runtime::traits::AccountIdConversion;
use xcm_executor::traits::{InvertLocation, WeightBounds};
use frame_support::{
dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo},
pallet_prelude::*,
};
use frame_system::{pallet_prelude::*, Config as SysConfig};
use sp_runtime::traits::{AccountIdConversion, BlockNumberProvider};
use xcm_executor::traits::{InvertLocation, OnResponse, WeightBounds};
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
@@ -59,7 +65,7 @@ pub mod pallet {
/// Required origin for sending XCM messages. If successful, the it resolves to `MultiLocation`
/// which exists as an interior location within this chain's XCM context.
type SendXcmOrigin: EnsureOrigin<Self::Origin, Success = MultiLocation>;
type SendXcmOrigin: EnsureOrigin<<Self as SysConfig>::Origin, Success = MultiLocation>;
/// The type used to actually dispatch an XCM to its destination.
type XcmRouter: SendXcm;
@@ -67,13 +73,13 @@ pub mod pallet {
/// Required origin for executing XCM messages, including the teleport functionality. If successful,
/// then it resolves to `MultiLocation` which exists as an interior location within this chain's XCM
/// context.
type ExecuteXcmOrigin: EnsureOrigin<Self::Origin, Success = MultiLocation>;
type ExecuteXcmOrigin: EnsureOrigin<<Self as SysConfig>::Origin, Success = MultiLocation>;
/// Our XCM filter which messages to be executed using `XcmExecutor` must pass.
type XcmExecuteFilter: Contains<(MultiLocation, Xcm<Self::Call>)>;
type XcmExecuteFilter: Contains<(MultiLocation, Xcm<<Self as SysConfig>::Call>)>;
/// Something to execute an XCM message.
type XcmExecutor: ExecuteXcm<Self::Call>;
type XcmExecutor: ExecuteXcm<<Self as SysConfig>::Call>;
/// Our XCM filter which messages to be teleported using the dedicated extrinsic must pass.
type XcmTeleportFilter: Contains<(MultiLocation, Vec<MultiAsset>)>;
@@ -82,10 +88,19 @@ pub mod pallet {
type XcmReserveTransferFilter: Contains<(MultiLocation, Vec<MultiAsset>)>;
/// Means of measuring the weight consumed by an XCM message locally.
type Weigher: WeightBounds<Self::Call>;
type Weigher: WeightBounds<<Self as SysConfig>::Call>;
/// Means of inverting a location.
type LocationInverter: InvertLocation;
/// The outer `Origin` type.
type Origin: From<Origin> + From<<Self as SysConfig>::Origin>;
/// The outer `Call` type.
type Call: Parameter
+ GetDispatchInfo
+ IsType<<Self as frame_system::Config>::Call>
+ Dispatchable<Origin = <Self as Config>::Origin, PostInfo = PostDispatchInfo>;
}
/// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic.
@@ -94,13 +109,90 @@ pub mod pallet {
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Execution of an XCM message was attempted.
///
/// \[ outcome \]
Attempted(xcm::latest::Outcome),
/// A XCM message was sent.
///
/// \[ origin, destination, message \]
Sent(MultiLocation, MultiLocation, Xcm<()>),
/// Query response received which does not match a registered query. This may be because a
/// matching query was never registered, it may be because it is a duplicate response, or
/// because the query timed out.
///
/// \[ origin location, id \]
UnexpectedResponse(MultiLocation, QueryId),
/// Query response has been received and is ready for taking with `take_response`. There is
/// no registered notification call.
///
/// \[ id, response \]
ResponseReady(QueryId, Response),
/// Query response has been received and query is removed. The registered notification has
/// been dispatched and executed successfully.
///
/// \[ id, pallet index, call index \]
Notified(QueryId, u8, u8),
/// Query response has been received and query is removed. The registered notification could
/// not be dispatched because the dispatch weight is greater than the maximum weight
/// originally budgeted by this runtime for the query result.
///
/// \[ id, pallet index, call index, actual weight, max budgeted weight \]
NotifyOverweight(QueryId, u8, u8, Weight, Weight),
/// Query response has been received and query is removed. There was a general error with
/// dispatching the notification call.
///
/// \[ id, pallet index, call index \]
NotifyDispatchError(QueryId, u8, u8),
/// Query response has been received and query is removed. The dispatch was unable to be
/// decoded into a `Call`; this might be due to dispatch function having a signature which
/// is not `(origin, QueryId, Response)`.
///
/// \[ id, pallet index, call index \]
NotifyDecodeFailed(QueryId, u8, u8),
/// Expected query response has been received but the origin location of the response does
/// not match that expected. The query remains registered for a later, valid, response to
/// be received and acted upon.
///
/// \[ origin location, id, expected location \]
InvalidResponder(MultiLocation, QueryId, MultiLocation),
/// Expected query response has been received but the expected origin location placed in
/// storate by this runtime previously cannot be decoded. The query remains registered.
///
/// This is unexpected (since a location placed in storage in a previously executing
/// runtime should be readable prior to query timeout) and dangerous since the possibly
/// valid response will be dropped. Manual governance intervention is probably going to be
/// needed.
///
/// \[ origin location, id \]
InvalidResponderVersion(MultiLocation, QueryId),
/// Received query response has been read and removed.
///
/// \[ id \]
ResponseTaken(QueryId),
}
#[pallet::origin]
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)]
pub enum Origin {
/// It comes from somewhere in the XCM space wanting to transact.
Xcm(MultiLocation),
/// It comes as an expected response from an XCM location.
Response(MultiLocation),
}
impl From<MultiLocation> for Origin {
fn from(location: MultiLocation) -> Origin {
Origin::Xcm(location)
}
}
#[pallet::error]
pub enum Error<T> {
/// The desired destination was unreachable, generally because there is a no way of routing
/// to it.
Unreachable,
/// There was some other issue (i.e. not to do with routing) in sending the message. Perhaps
/// a lack of space for buffering the message.
SendFailure,
/// The message execution fails the filter.
Filtered,
@@ -118,6 +210,32 @@ pub mod pallet {
BadVersion,
}
/// The status of a query.
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)]
pub enum QueryStatus<BlockNumber> {
/// The query was sent but no response has yet been received.
Pending {
responder: VersionedMultiLocation,
maybe_notify: Option<(u8, u8)>,
timeout: BlockNumber,
},
/// A response has been received.
Ready { response: VersionedResponse, at: BlockNumber },
}
/// Value of a query, must be unique for each query.
pub type QueryId = u64;
/// The latest available query index.
#[pallet::storage]
pub(super) type QueryCount<T: Config> = StorageValue<_, QueryId, ValueQuery>;
/// The ongoing queries.
#[pallet::storage]
#[pallet::getter(fn query)]
pub(super) type Queries<T: Config> =
StorageMap<_, Blake2_128Concat, QueryId, QueryStatus<T::BlockNumber>, OptionQuery>;
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
@@ -136,7 +254,7 @@ pub mod pallet {
let message: Xcm<()> = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
Self::send_xcm(interior, dest.clone(), message.clone()).map_err(|e| match e {
XcmError::CannotReachDestination(..) => Error::<T>::Unreachable,
SendError::CannotReachDestination(..) => Error::<T>::Unreachable,
_ => Error::<T>::SendFailure,
})?;
Self::deposit_event(Event::Sent(origin_location, dest, message));
@@ -161,14 +279,11 @@ pub mod pallet {
let maybe_dest: Result<MultiLocation, ()> = (*dest.clone()).try_into();
match (maybe_assets, maybe_dest) {
(Ok(assets), Ok(dest)) => {
let mut message = Xcm::WithdrawAsset {
assets,
effects: sp_std::vec![ InitiateTeleport {
assets: Wild(All),
dest,
effects: sp_std::vec![],
} ]
};
use sp_std::vec;
let mut message = Xcm(vec![
WithdrawAsset(assets),
InitiateTeleport { assets: Wild(All), dest, xcm: Xcm(vec![]) },
]);
T::Weigher::weight(&mut message).map_or(Weight::max_value(), |w| 100_000_000 + w)
},
_ => Weight::max_value(),
@@ -180,7 +295,6 @@ pub mod pallet {
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
fee_asset_item: u32,
dest_weight: Weight,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest = MultiLocation::try_from(*dest).map_err(|()| Error::<T>::BadVersion)?;
@@ -201,24 +315,17 @@ pub mod pallet {
.map_err(|_| Error::<T>::CannotReanchor)?;
let max_assets = assets.len() as u32;
let assets = assets.into();
let mut message = Xcm::WithdrawAsset {
assets,
effects: vec![InitiateTeleport {
let mut message = Xcm(vec![
WithdrawAsset(assets),
InitiateTeleport {
assets: Wild(All),
dest,
effects: vec![
BuyExecution {
fees,
// Zero weight for additional XCM (since there are none to execute)
weight: 0,
debt: dest_weight,
halt_on_error: false,
instructions: vec![],
},
xcm: Xcm(vec![
BuyExecution { fees, weight_limit: Unlimited },
DepositAsset { assets: Wild(All), max_assets, beneficiary },
],
}],
};
]),
},
]);
let weight =
T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
let outcome =
@@ -239,13 +346,15 @@ pub mod pallet {
/// an `AccountId32` value.
/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the fee on the
/// `dest` side.
/// - `dest_weight`: Equal to the total weight on `dest` of the XCM message
/// `ReserveAssetDeposited { assets, effects: [ BuyExecution{..}, DepositAsset{..} ] }`.
/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
/// fees.
#[pallet::weight({
match ((*assets.clone()).try_into(), (*dest.clone()).try_into()) {
(Ok(assets), Ok(dest)) => {
let effects = sp_std::vec![];
let mut message = Xcm::TransferReserveAsset { assets, dest, effects };
use sp_std::vec;
let mut message = Xcm(vec![
TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) }
]);
T::Weigher::weight(&mut message).map_or(Weight::max_value(), |w| 100_000_000 + w)
},
_ => Weight::max_value(),
@@ -257,7 +366,6 @@ pub mod pallet {
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
fee_asset_item: u32,
dest_weight: Weight,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
@@ -277,21 +385,14 @@ pub mod pallet {
.map_err(|_| Error::<T>::CannotReanchor)?;
let max_assets = assets.len() as u32;
let assets = assets.into();
let mut message = Xcm::TransferReserveAsset {
let mut message = Xcm(vec![TransferReserveAsset {
assets,
dest,
effects: vec![
BuyExecution {
fees,
// Zero weight for additional instructions/orders (since there are none to execute)
weight: 0,
debt: dest_weight, // covers this, `TransferReserveAsset` xcm, and `DepositAsset` order.
halt_on_error: false,
instructions: vec![],
},
xcm: Xcm(vec![
BuyExecution { fees, weight_limit: Unlimited },
DepositAsset { assets: Wild(All), max_assets, beneficiary },
],
};
]),
}]);
let weight =
T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
let outcome =
@@ -314,7 +415,7 @@ pub mod pallet {
#[pallet::weight(max_weight.saturating_add(100_000_000u64))]
pub fn execute(
origin: OriginFor<T>,
message: Box<VersionedXcm<T::Call>>,
message: Box<VersionedXcm<<T as SysConfig>::Call>>,
max_weight: Weight,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
@@ -334,12 +435,10 @@ pub mod pallet {
pub fn send_xcm(
interior: Junctions,
dest: MultiLocation,
message: Xcm<()>,
) -> Result<(), XcmError> {
let message = if let Junctions::Here = interior {
message
} else {
Xcm::<()>::RelayedFrom { who: interior, message: Box::new(message) }
mut message: Xcm<()>,
) -> Result<(), SendError> {
if interior != Junctions::Here {
message.0.insert(0, DescendOrigin(interior))
};
log::trace!(target: "xcm::send_xcm", "dest: {:?}, message: {:?}", &dest, &message);
T::XcmRouter::send_xcm(dest, message)
@@ -349,25 +448,215 @@ pub mod pallet {
const ID: PalletId = PalletId(*b"py/xcmch");
AccountIdConversion::<T::AccountId>::into_account(&ID)
}
fn do_new_query(
responder: MultiLocation,
maybe_notify: Option<(u8, u8)>,
timeout: T::BlockNumber,
) -> u64 {
QueryCount::<T>::mutate(|q| {
let r = *q;
*q += 1;
Queries::<T>::insert(
r,
QueryStatus::Pending { responder: responder.into(), maybe_notify, timeout },
);
r
})
}
/// Consume `message` and return another which is equivalent to it except that it reports
/// back the outcome.
///
/// - `message`: The message whose outcome should be reported.
/// - `responder`: The origin from which a response should be expected.
/// - `timeout`: The block number after which it is permissible for `notify` not to be
/// called even if a response is received.
///
/// To check the status of the query, use `fn query()` passing the resultant `QueryId`
/// value.
pub fn report_outcome(
message: &mut Xcm<()>,
responder: MultiLocation,
timeout: T::BlockNumber,
) -> QueryId {
let dest = T::LocationInverter::invert_location(&responder);
let query_id = Self::new_query(responder, timeout);
let report_error = Xcm(vec![ReportError { dest, query_id, max_response_weight: 0 }]);
message.0.insert(0, SetAppendix(report_error));
query_id
}
/// Consume `message` and return another which is equivalent to it except that it reports
/// back the outcome and dispatches `notify` on this chain.
///
/// - `message`: The message whose outcome should be reported.
/// - `responder`: The origin from which a response should be expected.
/// - `notify`: A dispatchable function which will be called once the outcome of `message`
/// is known. It may be a dispatchable in any pallet of the local chain, but other than
/// the usual origin, it must accept exactly two arguments: `query_id: QueryId` and
/// `outcome: Response`, and in that order. It should expect that the origin is
/// `Origin::Response` and will contain the responder's location.
/// - `timeout`: The block number after which it is permissible for `notify` not to be
/// called even if a response is received.
///
/// NOTE: `notify` gets called as part of handling an incoming message, so it should be
/// lightweight. Its weight is estimated during this function and stored ready for
/// weighing `ReportOutcome` on the way back. If it turns out to be heavier once it returns
/// then reporting the outcome will fail. Futhermore if the estimate is too high, then it
/// may be put in the overweight queue and need to be manually executed.
pub fn report_outcome_notify(
message: &mut Xcm<()>,
responder: MultiLocation,
notify: impl Into<<T as Config>::Call>,
timeout: T::BlockNumber,
) {
let dest = T::LocationInverter::invert_location(&responder);
let notify: <T as Config>::Call = notify.into();
let max_response_weight = notify.get_dispatch_info().weight;
let query_id = Self::new_notify_query(responder, notify, timeout);
let report_error = Xcm(vec![ReportError { dest, query_id, max_response_weight }]);
message.0.insert(0, SetAppendix(report_error));
}
/// Attempt to create a new query ID and register it as a query that is yet to respond.
pub fn new_query(responder: MultiLocation, timeout: T::BlockNumber) -> u64 {
Self::do_new_query(responder, None, timeout)
}
/// Attempt to create a new query ID and register it as a query that is yet to respond, and
/// which will call a dispatchable when a response happens.
pub fn new_notify_query(
responder: MultiLocation,
notify: impl Into<<T as Config>::Call>,
timeout: T::BlockNumber,
) -> u64 {
let notify =
notify.into().using_encoded(|mut bytes| Decode::decode(&mut bytes)).expect(
"decode input is output of Call encode; Call guaranteed to have two enums; qed",
);
Self::do_new_query(responder, Some(notify), timeout)
}
/// Attempt to remove and return the response of query with ID `query_id`.
///
/// Returns `None` if the response is not (yet) available.
pub fn take_response(query_id: QueryId) -> Option<(Response, T::BlockNumber)> {
if let Some(QueryStatus::Ready { response, at }) = Queries::<T>::get(query_id) {
let response = response.try_into().ok()?;
Queries::<T>::remove(query_id);
Self::deposit_event(Event::ResponseTaken(query_id));
Some((response, at))
} else {
None
}
}
}
/// Origin for the parachains module.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)]
#[pallet::origin]
pub enum Origin {
/// It comes from somewhere in the XCM space.
Xcm(MultiLocation),
}
impl<T: Config> OnResponse for Pallet<T> {
/// Returns `true` if we are expecting a response from `origin` for query `query_id`.
fn expecting_response(origin: &MultiLocation, query_id: QueryId) -> bool {
if let Some(QueryStatus::Pending { responder, .. }) = Queries::<T>::get(query_id) {
return MultiLocation::try_from(responder).map_or(false, |r| origin == &r)
}
false
}
impl From<MultiLocation> for Origin {
fn from(location: MultiLocation) -> Origin {
Origin::Xcm(location)
/// Handler for receiving a `response` from `origin` relating to `query_id`.
fn on_response(
origin: &MultiLocation,
query_id: QueryId,
response: Response,
max_weight: Weight,
) -> Weight {
if let Some(QueryStatus::Pending { responder, maybe_notify, .. }) =
Queries::<T>::get(query_id)
{
if let Ok(responder) = MultiLocation::try_from(responder) {
if origin == &responder {
return match maybe_notify {
Some((pallet_index, call_index)) => {
// This is a bit horrible, but we happen to know that the `Call` will
// be built by `(pallet_index: u8, call_index: u8, QueryId, Response)`.
// So we just encode that and then re-encode to a real Call.
let bare = (pallet_index, call_index, query_id, response);
if let Ok(call) = bare.using_encoded(|mut bytes| {
<T as Config>::Call::decode(&mut bytes)
}) {
Queries::<T>::remove(query_id);
let weight = call.get_dispatch_info().weight;
if weight > max_weight {
let e = Event::NotifyOverweight(
query_id,
pallet_index,
call_index,
weight,
max_weight,
);
Self::deposit_event(e);
return 0
}
let dispatch_origin = Origin::Response(origin.clone()).into();
match call.dispatch(dispatch_origin) {
Ok(post_info) => {
let e =
Event::Notified(query_id, pallet_index, call_index);
Self::deposit_event(e);
post_info.actual_weight
},
Err(error_and_info) => {
let e = Event::NotifyDispatchError(
query_id,
pallet_index,
call_index,
);
Self::deposit_event(e);
// Not much to do with the result as it is. It's up to the parachain to ensure that the
// message makes sense.
error_and_info.post_info.actual_weight
},
}
.unwrap_or(weight)
} else {
let e = Event::NotifyDecodeFailed(
query_id,
pallet_index,
call_index,
);
Self::deposit_event(e);
0
}
},
None => {
let e = Event::ResponseReady(query_id, response.clone());
Self::deposit_event(e);
let at = frame_system::Pallet::<T>::current_block_number();
let response = response.into();
Queries::<T>::insert(query_id, QueryStatus::Ready { response, at });
0
},
}
} else {
Self::deposit_event(Event::InvalidResponder(
origin.clone(),
query_id,
responder,
));
}
} else {
Self::deposit_event(Event::InvalidResponderVersion(origin.clone(), query_id));
}
} else {
Self::deposit_event(Event::UnexpectedResponse(origin.clone(), query_id));
}
0
}
}
}
/// Ensure that the origin `o` represents a sibling parachain.
/// Returns `Ok` with the parachain ID of the sibling or an `Err` otherwise.
/// Ensure that the origin `o` represents an XCM (`Transact`) origin.
///
/// Returns `Ok` with the location of the XCM sender or an `Err` otherwise.
pub fn ensure_xcm<OuterOrigin>(o: OuterOrigin) -> Result<MultiLocation, BadOrigin>
where
OuterOrigin: Into<Result<Origin, OuterOrigin>>,
@@ -378,6 +667,19 @@ where
}
}
/// Ensure that the origin `o` represents an XCM response origin.
///
/// Returns `Ok` with the location of the responder or an `Err` otherwise.
pub fn ensure_response<OuterOrigin>(o: OuterOrigin) -> Result<MultiLocation, BadOrigin>
where
OuterOrigin: Into<Result<Origin, OuterOrigin>>,
{
match o.into() {
Ok(Origin::Response(location)) => Ok(location),
_ => Err(BadOrigin),
}
}
/// Filter for `MultiLocation` to find those which represent a strict majority approval of an identified
/// plurality.
///
@@ -403,12 +705,10 @@ where
fn try_origin(outer: O) -> Result<Self::Success, O> {
outer.try_with_caller(|caller| {
caller.try_into().and_then(|Origin::Xcm(location)| {
if F::contains(&location) {
Ok(location)
} else {
Err(Origin::Xcm(location).into())
}
caller.try_into().and_then(|o| match o {
Origin::Xcm(location) if F::contains(&location) => Ok(location),
Origin::Xcm(location) => Err(Origin::Xcm(location).into()),
o => Err(o.into()),
})
})
}
@@ -419,6 +719,31 @@ where
}
}
/// `EnsureOrigin` implementation succeeding with a `MultiLocation` value to recognize and filter
/// the `Origin::Response` item.
pub struct EnsureResponse<F>(PhantomData<F>);
impl<O: OriginTrait + From<Origin>, F: Contains<MultiLocation>> EnsureOrigin<O>
for EnsureResponse<F>
where
O::PalletsOrigin: From<Origin> + TryInto<Origin, Error = O::PalletsOrigin>,
{
type Success = MultiLocation;
fn try_origin(outer: O) -> Result<Self::Success, O> {
outer.try_with_caller(|caller| {
caller.try_into().and_then(|o| match o {
Origin::Response(responder) => Ok(responder),
o => Err(o.into()),
})
})
}
#[cfg(feature = "runtime-benchmarks")]
fn successful_origin() -> O {
O::from(Origin::Response(Here.into()))
}
}
/// A simple passthrough where we reuse the `MultiLocation`-typed XCM origin as the inner value of
/// this crate's `Origin::Xcm` value.
pub struct XcmPassthrough<Origin>(PhantomData<Origin>);
+113 -25
View File
@@ -20,13 +20,10 @@ use polkadot_runtime_parachains::origin;
use sp_core::H256;
use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32};
pub use sp_std::{cell::RefCell, fmt::Debug, marker::PhantomData};
use xcm::{
latest::prelude::*,
opaque::latest::{Error as XcmError, MultiAsset, Result as XcmResult, SendXcm, Xcm},
};
use xcm::latest::prelude::*;
use xcm_builder::{
AccountId32Aliases, AllowTopLevelPaidExecutionFrom, Case, ChildParachainAsNative,
ChildParachainConvertsVia, ChildSystemParachainAsSuperuser,
AccountId32Aliases, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, Case,
ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser,
CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete,
LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation,
TakeWeightCredit,
@@ -40,6 +37,85 @@ pub type Balance = u128;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
#[frame_support::pallet]
pub mod pallet_test_notifier {
use crate::{ensure_response, QueryId};
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use sp_runtime::DispatchResult;
use xcm::latest::prelude::*;
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config + crate::Config {
type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;
type Origin: IsType<<Self as frame_system::Config>::Origin>
+ Into<Result<crate::Origin, <Self as Config>::Origin>>;
type Call: IsType<<Self as crate::Config>::Call> + From<Call<Self>>;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
QueryPrepared(QueryId),
NotifyQueryPrepared(QueryId),
ResponseReceived(MultiLocation, QueryId, Response),
}
#[pallet::error]
pub enum Error<T> {
UnexpectedId,
BadAccountFormat,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(1_000_000)]
pub fn prepare_new_query(origin: OriginFor<T>) -> DispatchResult {
let who = ensure_signed(origin)?;
let id = who
.using_encoded(|mut d| <[u8; 32]>::decode(&mut d))
.map_err(|_| Error::<T>::BadAccountFormat)?;
let qid = crate::Pallet::<T>::new_query(
Junction::AccountId32 { network: Any, id }.into(),
100u32.into(),
);
Self::deposit_event(Event::<T>::QueryPrepared(qid));
Ok(())
}
#[pallet::weight(1_000_000)]
pub fn prepare_new_notify_query(origin: OriginFor<T>) -> DispatchResult {
let who = ensure_signed(origin)?;
let id = who
.using_encoded(|mut d| <[u8; 32]>::decode(&mut d))
.map_err(|_| Error::<T>::BadAccountFormat)?;
let call = Call::<T>::notification_received(0, Default::default());
let qid = crate::Pallet::<T>::new_notify_query(
Junction::AccountId32 { network: Any, id }.into(),
<T as Config>::Call::from(call),
100u32.into(),
);
Self::deposit_event(Event::<T>::NotifyQueryPrepared(qid));
Ok(())
}
#[pallet::weight(1_000_000)]
pub fn notification_received(
origin: OriginFor<T>,
query_id: QueryId,
response: Response,
) -> DispatchResult {
let responder = ensure_response(<T as Config>::Origin::from(origin))?;
Self::deposit_event(Event::<T>::ResponseReceived(responder, query_id, response));
Ok(())
}
}
}
construct_runtime!(
pub enum Test where
Block = Block,
@@ -49,20 +125,21 @@ 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},
TestNotifier: pallet_test_notifier::{Pallet, Call, Event<T>},
}
);
thread_local! {
pub static SENT_XCM: RefCell<Vec<(MultiLocation, Xcm)>> = RefCell::new(Vec::new());
pub static SENT_XCM: RefCell<Vec<(MultiLocation, Xcm<()>)>> = RefCell::new(Vec::new());
}
pub fn sent_xcm() -> Vec<(MultiLocation, Xcm)> {
pub fn sent_xcm() -> Vec<(MultiLocation, Xcm<()>)> {
SENT_XCM.with(|q| (*q.borrow()).clone())
}
/// Sender that never returns error, always sends
pub struct TestSendXcm;
impl SendXcm for TestSendXcm {
fn send_xcm(dest: MultiLocation, msg: Xcm) -> XcmResult {
fn send_xcm(dest: MultiLocation, msg: Xcm<()>) -> SendResult {
SENT_XCM.with(|q| q.borrow_mut().push((dest, msg)));
Ok(())
}
@@ -70,9 +147,9 @@ impl SendXcm for TestSendXcm {
/// Sender that returns error if `X8` junction and stops routing
pub struct TestSendXcmErrX8;
impl SendXcm for TestSendXcmErrX8 {
fn send_xcm(dest: MultiLocation, msg: Xcm) -> XcmResult {
fn send_xcm(dest: MultiLocation, msg: Xcm<()>) -> SendResult {
if dest.len() == 8 {
Err(XcmError::Undefined)
Err(SendError::Transport("Destination location full"))
} else {
SENT_XCM.with(|q| q.borrow_mut().push((dest, msg)));
Ok(())
@@ -152,9 +229,14 @@ parameter_types! {
pub const BaseXcmWeight: Weight = 1_000;
pub CurrencyPerSecond: (AssetId, u128) = (Concrete(RelayLocation::get()), 1);
pub TrustedAssets: (MultiAssetFilter, MultiLocation) = (All.into(), Here.into());
pub const MaxInstructions: u32 = 100;
}
pub type Barrier = (TakeWeightCredit, AllowTopLevelPaidExecutionFrom<Everything>);
pub type Barrier = (
TakeWeightCredit,
AllowTopLevelPaidExecutionFrom<Everything>,
AllowKnownQueryResponses<XcmPallet>,
);
pub struct XcmConfig;
impl xcm_executor::Config for XcmConfig {
@@ -166,9 +248,9 @@ impl xcm_executor::Config for XcmConfig {
type IsTeleporter = Case<TrustedAssets>;
type LocationInverter = LocationInverter<Ancestry>;
type Barrier = Barrier;
type Weigher = FixedWeightBounds<BaseXcmWeight, Call>;
type Weigher = FixedWeightBounds<BaseXcmWeight, Call, MaxInstructions>;
type Trader = FixedRateOfFungible<CurrencyPerSecond, ()>;
type ResponseHandler = ();
type ResponseHandler = XcmPallet;
}
pub type LocalOriginToLocation = SignedToAccountId32<Origin, AccountId, AnyNetwork>;
@@ -182,25 +264,31 @@ impl pallet_xcm::Config for Test {
type XcmExecutor = XcmExecutor<XcmConfig>;
type XcmTeleportFilter = Everything;
type XcmReserveTransferFilter = Everything;
type Weigher = FixedWeightBounds<BaseXcmWeight, Call>;
type Weigher = FixedWeightBounds<BaseXcmWeight, Call, MaxInstructions>;
type LocationInverter = LocationInverter<Ancestry>;
type Origin = Origin;
type Call = Call;
}
impl origin::Config for Test {}
impl pallet_test_notifier::Config for Test {
type Event = Event;
type Origin = Origin;
type Call = Call;
}
pub(crate) fn last_event() -> Event {
System::events().pop().expect("Event expected").event
}
pub(crate) fn buy_execution<C>(fees: impl Into<MultiAsset>, debt: Weight) -> Order<C> {
use xcm::opaque::latest::prelude::*;
Order::BuyExecution {
fees: fees.into(),
weight: 0,
debt,
halt_on_error: false,
instructions: vec![],
}
pub(crate) fn last_events(n: usize) -> Vec<Event> {
System::events().into_iter().map(|e| e.event).rev().take(n).rev().collect()
}
pub(crate) fn buy_execution<C>(fees: impl Into<MultiAsset>) -> Instruction<C> {
use xcm::latest::prelude::*;
BuyExecution { fees: fees.into(), weight_limit: Unlimited }
}
pub(crate) fn new_test_ext_with_balances(
+138 -44
View File
@@ -14,11 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use crate::mock::*;
use crate::{mock::*, QueryStatus};
use frame_support::{assert_noop, assert_ok, traits::Currency};
use polkadot_parachain::primitives::{AccountIdConversion, Id as ParaId};
use std::convert::TryInto;
use xcm::{v1::prelude::*, VersionedXcm};
use xcm::{latest::prelude::*, VersionedXcm};
use xcm_executor::XcmExecutor;
const ALICE: AccountId = AccountId::new([0u8; 32]);
const BOB: AccountId = AccountId::new([1u8; 32]);
@@ -26,6 +27,111 @@ const PARA_ID: u32 = 2000;
const INITIAL_BALANCE: u128 = 100;
const SEND_AMOUNT: u128 = 10;
#[test]
fn report_outcome_notify_works() {
let balances =
vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)];
let sender = AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into();
let mut message = Xcm(vec![TransferAsset {
assets: (Here, SEND_AMOUNT).into(),
beneficiary: sender.clone(),
}]);
let call = pallet_test_notifier::Call::notification_received(0, Default::default());
let notify = Call::TestNotifier(call);
new_test_ext_with_balances(balances).execute_with(|| {
XcmPallet::report_outcome_notify(&mut message, Parachain(PARA_ID).into(), notify, 100);
assert_eq!(
message,
Xcm(vec![
SetAppendix(Xcm(vec![ReportError {
query_id: 0,
dest: Parent.into(),
max_response_weight: 1_000_000
},])),
TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() },
])
);
let status = QueryStatus::Pending {
responder: MultiLocation::from(Parachain(PARA_ID)).into(),
maybe_notify: Some((4, 2)),
timeout: 100,
};
assert_eq!(crate::Queries::<Test>::iter().collect::<Vec<_>>(), vec![(0, status)]);
let r = XcmExecutor::<XcmConfig>::execute_xcm(
Parachain(PARA_ID).into(),
Xcm(vec![QueryResponse {
query_id: 0,
response: Response::ExecutionResult(Ok(())),
max_weight: 1_000_000,
}]),
1_000_000_000,
);
assert_eq!(r, Outcome::Complete(1_000));
assert_eq!(
last_events(2),
vec![
Event::TestNotifier(pallet_test_notifier::Event::ResponseReceived(
Parachain(PARA_ID).into(),
0,
Response::ExecutionResult(Ok(())),
)),
Event::XcmPallet(crate::Event::Notified(0, 4, 2)),
]
);
assert_eq!(crate::Queries::<Test>::iter().collect::<Vec<_>>(), vec![]);
});
}
#[test]
fn report_outcome_works() {
let balances =
vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)];
let sender = AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into();
let mut message = Xcm(vec![TransferAsset {
assets: (Here, SEND_AMOUNT).into(),
beneficiary: sender.clone(),
}]);
new_test_ext_with_balances(balances).execute_with(|| {
XcmPallet::report_outcome(&mut message, Parachain(PARA_ID).into(), 100);
assert_eq!(
message,
Xcm(vec![
SetAppendix(Xcm(vec![ReportError {
query_id: 0,
dest: Parent.into(),
max_response_weight: 0
},])),
TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() },
])
);
let status = QueryStatus::Pending {
responder: MultiLocation::from(Parachain(PARA_ID)).into(),
maybe_notify: None,
timeout: 100,
};
assert_eq!(crate::Queries::<Test>::iter().collect::<Vec<_>>(), vec![(0, status)]);
let r = XcmExecutor::<XcmConfig>::execute_xcm(
Parachain(PARA_ID).into(),
Xcm(vec![QueryResponse {
query_id: 0,
response: Response::ExecutionResult(Ok(())),
max_weight: 0,
}]),
1_000_000_000,
);
assert_eq!(r, Outcome::Complete(1_000));
assert_eq!(
last_event(),
Event::XcmPallet(crate::Event::ResponseReady(0, Response::ExecutionResult(Ok(())),))
);
let response = Some((Response::ExecutionResult(Ok(())), 1));
assert_eq!(XcmPallet::take_response(0), response);
});
}
/// Test sending an `XCM` message (`XCM::ReserveAssetDeposit`)
///
/// Asserts that the expected message is sent and the event is emitted
@@ -34,30 +140,26 @@ fn send_works() {
let balances =
vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)];
new_test_ext_with_balances(balances).execute_with(|| {
let weight = 2 * BaseXcmWeight::get();
let sender: MultiLocation =
AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into();
let message = Xcm::ReserveAssetDeposited {
assets: (Parent, SEND_AMOUNT).into(),
effects: vec![
buy_execution((Parent, SEND_AMOUNT), weight),
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() },
],
};
assert_ok!(XcmPallet::send(
Origin::signed(ALICE),
Box::new(RelayLocation::get().into()),
Box::new(VersionedXcm::from(message.clone()))
));
let message = Xcm(vec![
ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
ClearOrigin,
buy_execution((Parent, SEND_AMOUNT)),
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() },
]);
let versioned_dest = Box::new(RelayLocation::get().into());
let versioned_message = Box::new(VersionedXcm::from(message.clone()));
assert_ok!(XcmPallet::send(Origin::signed(ALICE), versioned_dest, versioned_message));
assert_eq!(
sent_xcm(),
vec![(
Here.into(),
RelayedFrom {
who: sender.clone().try_into().unwrap(),
message: Box::new(message.clone()),
}
)]
Xcm(Some(DescendOrigin(sender.clone().try_into().unwrap()))
.into_iter()
.chain(message.0.clone().into_iter())
.collect())
)],
);
assert_eq!(
last_event(),
@@ -75,16 +177,13 @@ fn send_fails_when_xcm_router_blocks() {
let balances =
vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)];
new_test_ext_with_balances(balances).execute_with(|| {
let weight = 2 * BaseXcmWeight::get();
let sender: MultiLocation =
Junction::AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into();
let message = Xcm::ReserveAssetDeposited {
assets: (Parent, SEND_AMOUNT).into(),
effects: vec![
buy_execution((Parent, SEND_AMOUNT), weight),
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() },
],
};
let message = Xcm(vec![
ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
buy_execution((Parent, SEND_AMOUNT)),
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() },
]);
assert_noop!(
XcmPallet::send(
Origin::signed(ALICE),
@@ -113,7 +212,6 @@ fn teleport_assets_works() {
Box::new(AccountId32 { network: Any, id: BOB.into() }.into().into()),
Box::new((Here, SEND_AMOUNT).into()),
0,
weight,
));
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT);
assert_eq!(
@@ -142,7 +240,6 @@ fn reserve_transfer_assets_works() {
Box::new(dest.clone().into()),
Box::new((Here, SEND_AMOUNT).into()),
0,
weight
));
// Alice spent amount
assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE - SEND_AMOUNT);
@@ -153,13 +250,12 @@ fn reserve_transfer_assets_works() {
sent_xcm(),
vec![(
Parachain(PARA_ID).into(),
Xcm::ReserveAssetDeposited {
assets: (Parent, SEND_AMOUNT).into(),
effects: vec![
buy_execution((Parent, SEND_AMOUNT), weight),
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest },
]
}
Xcm(vec![
ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
ClearOrigin,
buy_execution((Parent, SEND_AMOUNT)),
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest },
]),
)]
);
assert_eq!(
@@ -184,13 +280,11 @@ fn execute_withdraw_to_deposit_works() {
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE);
assert_ok!(XcmPallet::execute(
Origin::signed(ALICE),
Box::new(VersionedXcm::from(Xcm::WithdrawAsset {
assets: (Here, SEND_AMOUNT).into(),
effects: vec![
buy_execution((Here, SEND_AMOUNT), weight),
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }
],
})),
Box::new(VersionedXcm::from(Xcm(vec![
WithdrawAsset((Here, SEND_AMOUNT).into()),
buy_execution((Here, SEND_AMOUNT)),
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest },
]))),
weight
));
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT);
+112 -8
View File
@@ -33,9 +33,10 @@ use parity_scale_codec::{Decode, Encode, Error as CodecError, Input};
pub mod v0;
pub mod v1;
pub mod v2;
pub mod latest {
pub use super::v1::*;
pub use super::v2::*;
}
mod double_encoded;
@@ -97,6 +98,71 @@ impl TryFrom<VersionedMultiLocation> for v1::MultiLocation {
}
}
/// A single `Response` value, together with its version code.
#[derive(Derivative, Encode, Decode)]
#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))]
#[codec(encode_bound())]
#[codec(decode_bound())]
pub enum VersionedResponse {
V0(v0::Response),
V1(v1::Response),
V2(v2::Response),
}
impl From<v0::Response> for VersionedResponse {
fn from(x: v0::Response) -> Self {
VersionedResponse::V0(x)
}
}
impl From<v1::Response> for VersionedResponse {
fn from(x: v1::Response) -> Self {
VersionedResponse::V1(x)
}
}
impl<T: Into<v2::Response>> From<T> for VersionedResponse {
fn from(x: T) -> Self {
VersionedResponse::V2(x.into())
}
}
impl TryFrom<VersionedResponse> for v0::Response {
type Error = ();
fn try_from(x: VersionedResponse) -> Result<Self, ()> {
use VersionedResponse::*;
match x {
V0(x) => Ok(x),
V1(x) => x.try_into(),
V2(x) => VersionedResponse::V1(x.try_into()?).try_into(),
}
}
}
impl TryFrom<VersionedResponse> for v1::Response {
type Error = ();
fn try_from(x: VersionedResponse) -> Result<Self, ()> {
use VersionedResponse::*;
match x {
V0(x) => x.try_into(),
V1(x) => Ok(x),
V2(x) => x.try_into(),
}
}
}
impl TryFrom<VersionedResponse> for v2::Response {
type Error = ();
fn try_from(x: VersionedResponse) -> Result<Self, ()> {
use VersionedResponse::*;
match x {
V0(x) => VersionedResponse::V1(x.try_into()?).try_into(),
V1(x) => x.try_into(),
V2(x) => Ok(x),
}
}
}
/// A single `MultiAsset` value, together with its version code.
#[derive(Derivative, Encode, Decode)]
#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))]
@@ -142,8 +208,6 @@ impl TryFrom<VersionedMultiAsset> for v1::MultiAsset {
}
/// A single `MultiAssets` value, together with its version code.
///
/// NOTE: For XCM v0, this was `Vec<MultiAsset>`.
#[derive(Derivative, Encode, Decode)]
#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))]
#[codec(encode_bound())]
@@ -195,6 +259,7 @@ impl TryFrom<VersionedMultiAssets> for v1::MultiAssets {
pub enum VersionedXcm<Call> {
V0(v0::Xcm<Call>),
V1(v1::Xcm<Call>),
V2(v2::Xcm<Call>),
}
impl<Call> From<v0::Xcm<Call>> for VersionedXcm<Call> {
@@ -209,12 +274,20 @@ impl<Call> From<v1::Xcm<Call>> for VersionedXcm<Call> {
}
}
impl<Call> From<v2::Xcm<Call>> for VersionedXcm<Call> {
fn from(x: v2::Xcm<Call>) -> Self {
VersionedXcm::V2(x)
}
}
impl<Call> TryFrom<VersionedXcm<Call>> for v0::Xcm<Call> {
type Error = ();
fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
use VersionedXcm::*;
match x {
VersionedXcm::V0(x) => Ok(x),
VersionedXcm::V1(x) => x.try_into(),
V0(x) => Ok(x),
V1(x) => x.try_into(),
V2(x) => V1(x.try_into()?).try_into(),
}
}
}
@@ -222,9 +295,23 @@ impl<Call> TryFrom<VersionedXcm<Call>> for v0::Xcm<Call> {
impl<Call> TryFrom<VersionedXcm<Call>> for v1::Xcm<Call> {
type Error = ();
fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
use VersionedXcm::*;
match x {
VersionedXcm::V0(x) => x.try_into(),
VersionedXcm::V1(x) => Ok(x),
V0(x) => x.try_into(),
V1(x) => Ok(x),
V2(x) => x.try_into(),
}
}
}
impl<Call> TryFrom<VersionedXcm<Call>> for v2::Xcm<Call> {
type Error = ();
fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
use VersionedXcm::*;
match x {
V0(x) => V1(x.try_into()?).try_into(),
V1(x) => x.try_into(),
V2(x) => Ok(x),
}
}
}
@@ -269,6 +356,17 @@ impl WrapVersion for AlwaysV1 {
}
}
/// `WrapVersion` implementation which attempts to always convert the XCM to version 1 before wrapping it.
pub struct AlwaysV2;
impl WrapVersion for AlwaysV2 {
fn wrap_version<Call>(
_: &latest::MultiLocation,
xcm: impl Into<VersionedXcm<Call>>,
) -> Result<VersionedXcm<Call>, ()> {
Ok(VersionedXcm::<Call>::V2(xcm.into().try_into()?))
}
}
/// `WrapVersion` implementation which attempts to always convert the XCM to the latest version before wrapping it.
pub type AlwaysLatest = AlwaysV1;
@@ -288,9 +386,15 @@ pub mod opaque {
// Then override with the opaque types in v1
pub use crate::v1::opaque::{Order, Xcm};
}
pub mod v2 {
// Everything from v1
pub use crate::v2::*;
// Then override with the opaque types in v2
pub use crate::v2::opaque::{Instruction, Xcm};
}
pub mod latest {
pub use super::v1::*;
pub use super::v2::*;
}
/// The basic `VersionedXcm` type which just uses the `Vec<u8>` as an encoded call.
+3 -3
View File
@@ -295,7 +295,7 @@ impl<Call> Xcm<Call> {
},
TeleportAsset { assets, effects } =>
TeleportAsset { assets, effects: effects.into_iter().map(Order::into).collect() },
QueryResponse { query_id: u64, response } => QueryResponse { query_id: u64, response },
QueryResponse { query_id, response } => QueryResponse { query_id, response },
TransferAsset { assets, dest } => TransferAsset { assets, dest },
TransferReserveAsset { assets, dest, effects } =>
TransferReserveAsset { assets, dest, effects },
@@ -356,8 +356,8 @@ impl<Call> TryFrom<Xcm1<Call>> for Xcm<Call> {
.map(Order::try_from)
.collect::<result::Result<_, _>>()?,
},
Xcm1::QueryResponse { query_id: u64, response } =>
QueryResponse { query_id: u64, response: response.try_into()? },
Xcm1::QueryResponse { query_id, response } =>
QueryResponse { query_id, response: response.try_into()? },
Xcm1::TransferAsset { assets, beneficiary } =>
TransferAsset { assets: assets.try_into()?, dest: beneficiary.try_into()? },
Xcm1::TransferReserveAsset { assets, dest, effects } => TransferReserveAsset {
+107 -43
View File
@@ -16,7 +16,10 @@
//! Version 1 of the Cross-Consensus Message format data structures.
use super::v0::{Response as Response0, Xcm as Xcm0};
use super::{
v0::{Response as OldResponse, Xcm as OldXcm},
v2::{Instruction, Response as NewResponse, Xcm as NewXcm},
};
use crate::DoubleEncoded;
use alloc::vec::Vec;
use core::{
@@ -28,7 +31,7 @@ use derivative::Derivative;
use parity_scale_codec::{self, Decode, Encode};
mod junction;
pub mod multiasset;
mod multiasset;
mod multilocation;
mod order;
mod traits; // the new multiasset.
@@ -38,7 +41,9 @@ pub use multiasset::{
AssetId, AssetInstance, Fungibility, MultiAsset, MultiAssetFilter, MultiAssets,
WildFungibility, WildMultiAsset,
};
pub use multilocation::{Ancestor, AncestorThen, Junctions, MultiLocation, Parent, ParentThen};
pub use multilocation::{
Ancestor, AncestorThen, InteriorMultiLocation, Junctions, MultiLocation, Parent, ParentThen,
};
pub use order::Order;
pub use traits::{Error, ExecuteXcm, Outcome, Result, SendXcm};
@@ -48,30 +53,23 @@ pub use super::v0::{BodyId, BodyPart, NetworkId, OriginKind};
/// A prelude for importing all types typically used when interacting with XCM messages.
pub mod prelude {
pub use super::{
super::v0::{
BodyId, BodyPart,
NetworkId::{self, *},
},
junction::Junction::{self, *},
multiasset::{
AssetId::{self, *},
AssetInstance::{self, *},
Fungibility::{self, *},
MultiAsset,
MultiAssetFilter::{self, *},
MultiAssets,
WildFungibility::{self, Fungible as WildFungible, NonFungible as WildNonFungible},
WildMultiAsset::{self, *},
},
multilocation::{
Ancestor, AncestorThen,
Junctions::{self, *},
MultiLocation, Parent, ParentThen,
},
opaque,
order::Order::{self, *},
traits::{Error as XcmError, ExecuteXcm, Outcome, Result as XcmResult, SendXcm},
OriginKind, Response,
Ancestor, AncestorThen,
AssetId::{self, *},
AssetInstance::{self, *},
BodyId, BodyPart, Error as XcmError, ExecuteXcm,
Fungibility::{self, *},
InteriorMultiLocation,
Junctions::{self, *},
MultiAsset,
MultiAssetFilter::{self, *},
MultiAssets, MultiLocation,
NetworkId::{self, *},
OriginKind, Outcome, Parent, ParentThen, Response, Result as XcmResult, SendXcm,
WildFungibility::{self, Fungible as WildFungible, NonFungible as WildNonFungible},
WildMultiAsset::{self, *},
Xcm::{self, *},
};
}
@@ -271,7 +269,7 @@ pub enum Xcm<Call> {
///
/// Errors:
#[codec(index = 10)]
RelayedFrom { who: Junctions, message: alloc::boxed::Box<Xcm<Call>> },
RelayedFrom { who: InteriorMultiLocation, message: alloc::boxed::Box<Xcm<Call>> },
}
impl<Call> Xcm<Call> {
@@ -291,7 +289,7 @@ impl<Call> Xcm<Call> {
assets,
effects: effects.into_iter().map(Order::into).collect(),
},
QueryResponse { query_id: u64, response } => QueryResponse { query_id: u64, response },
QueryResponse { query_id, response } => QueryResponse { query_id, response },
TransferAsset { assets, beneficiary } => TransferAsset { assets, beneficiary },
TransferReserveAsset { assets, dest, effects } =>
TransferReserveAsset { assets, dest, effects },
@@ -317,46 +315,46 @@ pub mod opaque {
}
// Convert from a v0 response to a v1 response
impl TryFrom<Response0> for Response {
impl TryFrom<OldResponse> for Response {
type Error = ();
fn try_from(old_response: Response0) -> result::Result<Self, ()> {
fn try_from(old_response: OldResponse) -> result::Result<Self, ()> {
match old_response {
Response0::Assets(assets) => Ok(Self::Assets(assets.try_into()?)),
OldResponse::Assets(assets) => Ok(Self::Assets(assets.try_into()?)),
}
}
}
impl<Call> TryFrom<Xcm0<Call>> for Xcm<Call> {
impl<Call> TryFrom<OldXcm<Call>> for Xcm<Call> {
type Error = ();
fn try_from(old: Xcm0<Call>) -> result::Result<Xcm<Call>, ()> {
fn try_from(old: OldXcm<Call>) -> result::Result<Xcm<Call>, ()> {
use Xcm::*;
Ok(match old {
Xcm0::WithdrawAsset { assets, effects } => WithdrawAsset {
OldXcm::WithdrawAsset { assets, effects } => WithdrawAsset {
assets: assets.try_into()?,
effects: effects
.into_iter()
.map(Order::try_from)
.collect::<result::Result<_, _>>()?,
},
Xcm0::ReserveAssetDeposit { assets, effects } => ReserveAssetDeposited {
OldXcm::ReserveAssetDeposit { assets, effects } => ReserveAssetDeposited {
assets: assets.try_into()?,
effects: effects
.into_iter()
.map(Order::try_from)
.collect::<result::Result<_, _>>()?,
},
Xcm0::TeleportAsset { assets, effects } => ReceiveTeleportedAsset {
OldXcm::TeleportAsset { assets, effects } => ReceiveTeleportedAsset {
assets: assets.try_into()?,
effects: effects
.into_iter()
.map(Order::try_from)
.collect::<result::Result<_, _>>()?,
},
Xcm0::QueryResponse { query_id: u64, response } =>
QueryResponse { query_id: u64, response: response.try_into()? },
Xcm0::TransferAsset { assets, dest } =>
OldXcm::QueryResponse { query_id, response } =>
QueryResponse { query_id, response: response.try_into()? },
OldXcm::TransferAsset { assets, dest } =>
TransferAsset { assets: assets.try_into()?, beneficiary: dest.try_into()? },
Xcm0::TransferReserveAsset { assets, dest, effects } => TransferReserveAsset {
OldXcm::TransferReserveAsset { assets, dest, effects } => TransferReserveAsset {
assets: assets.try_into()?,
dest: dest.try_into()?,
effects: effects
@@ -364,17 +362,83 @@ impl<Call> TryFrom<Xcm0<Call>> for Xcm<Call> {
.map(Order::try_from)
.collect::<result::Result<_, _>>()?,
},
Xcm0::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } =>
OldXcm::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } =>
HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity },
Xcm0::HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient },
Xcm0::HrmpChannelClosing { initiator, sender, recipient } =>
OldXcm::HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient },
OldXcm::HrmpChannelClosing { initiator, sender, recipient } =>
HrmpChannelClosing { initiator, sender, recipient },
Xcm0::Transact { origin_type, require_weight_at_most, call } =>
OldXcm::Transact { origin_type, require_weight_at_most, call } =>
Transact { origin_type, require_weight_at_most, call: call.into() },
Xcm0::RelayedFrom { who, message } => RelayedFrom {
OldXcm::RelayedFrom { who, message } => RelayedFrom {
who: MultiLocation::try_from(who)?.try_into()?,
message: alloc::boxed::Box::new((*message).try_into()?),
},
})
}
}
impl<Call> TryFrom<NewXcm<Call>> for Xcm<Call> {
type Error = ();
fn try_from(old: NewXcm<Call>) -> result::Result<Xcm<Call>, ()> {
use Xcm::*;
let mut iter = old.0.into_iter();
let instruction = iter.next().ok_or(())?;
Ok(match instruction {
Instruction::WithdrawAsset(assets) => {
let effects = iter.map(Order::try_from).collect::<result::Result<_, _>>()?;
WithdrawAsset { assets, effects }
},
Instruction::ReserveAssetDeposited(assets) => {
if !matches!(iter.next(), Some(Instruction::ClearOrigin)) {
return Err(())
}
let effects = iter.map(Order::try_from).collect::<result::Result<_, _>>()?;
ReserveAssetDeposited { assets, effects }
},
Instruction::ReceiveTeleportedAsset(assets) => {
if !matches!(iter.next(), Some(Instruction::ClearOrigin)) {
return Err(())
}
let effects = iter.map(Order::try_from).collect::<result::Result<_, _>>()?;
ReceiveTeleportedAsset { assets, effects }
},
Instruction::QueryResponse { query_id, response, max_weight } => {
// Cannot handle special response weights.
if max_weight > 0 {
return Err(())
}
QueryResponse { query_id, response: response.try_into()? }
},
Instruction::TransferAsset { assets, beneficiary } =>
TransferAsset { assets, beneficiary },
Instruction::TransferReserveAsset { assets, dest, xcm } => TransferReserveAsset {
assets,
dest,
effects: xcm
.0
.into_iter()
.map(Order::try_from)
.collect::<result::Result<_, _>>()?,
},
Instruction::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } =>
HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity },
Instruction::HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient },
Instruction::HrmpChannelClosing { initiator, sender, recipient } =>
HrmpChannelClosing { initiator, sender, recipient },
Instruction::Transact { origin_type, require_weight_at_most, call } =>
Transact { origin_type, require_weight_at_most, call },
_ => return Err(()),
})
}
}
// Convert from a v1 response to a v2 response
impl TryFrom<NewResponse> for Response {
type Error = ();
fn try_from(response: NewResponse) -> result::Result<Self, ()> {
match response {
NewResponse::Assets(assets) => Ok(Self::Assets(assets)),
_ => Err(()),
}
}
}
+22 -13
View File
@@ -22,26 +22,30 @@ use parity_scale_codec::{Decode, Encode};
/// A relative path between state-bearing consensus systems.
///
/// A location in a consensus system is defined as an *isolatable state machine* held within global consensus. The
/// location in question need not have a sophisticated consensus algorithm of its own; a single account within
/// Ethereum, for example, could be considered a location.
/// A location in a consensus system is defined as an *isolatable state machine* held within global
/// consensus. The location in question need not have a sophisticated consensus algorithm of its
/// own; a single account within Ethereum, for example, could be considered a location.
///
/// A very-much non-exhaustive list of types of location include:
/// - A (normal, layer-1) block chain, e.g. the Bitcoin mainnet or a parachain.
/// - A layer-0 super-chain, e.g. the Polkadot Relay chain.
/// - A layer-2 smart contract, e.g. an ERC-20 on Ethereum.
/// - A logical functional component of a chain, e.g. a single instance of a pallet on a Frame-based Substrate chain.
/// - A logical functional component of a chain, e.g. a single instance of a pallet on a Frame-based
/// Substrate chain.
/// - An account.
///
/// A `MultiLocation` is a *relative identifier*, meaning that it can only be used to define the relative path
/// between two locations, and cannot generally be used to refer to a location universally. It is comprised of a
/// number of *junctions*, each morphing the previous location, either diving down into one of its internal locations,
/// called a *sub-consensus*, or going up into its parent location.
/// A `MultiLocation` is a *relative identifier*, meaning that it can only be used to define the
/// relative path between two locations, and cannot generally be used to refer to a location
/// universally. It is comprised of an integer number of parents specifying the number of times to
/// "escape" upwards into the containing consensus system and then a number of *junctions*, each
/// diving down and specifying some interior portion of state (which may be considered a
/// "sub-consensus" system).
///
/// The `parents` field of this struct indicates the number of parent junctions that exist at the
/// beginning of this `MultiLocation`. A corollary of such a property is that no parent junctions
/// can be added in the middle or at the end of a `MultiLocation`, thus ensuring well-formedness
/// of each and every `MultiLocation` that can be constructed.
/// This specific `MultiLocation` implementation uses a `Junctions` datatype which is a Rust `enum`
/// in order to make pattern matching easier. There are occasions where it is important to ensure
/// that a value is strictly an interior location, in those cases, `Junctions` may be used.
///
/// The `MultiLocation` value of `Null` simply refers to the interpreting consensus system.
#[derive(Clone, Decode, Encode, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct MultiLocation {
/// The number of parent junctions at the beginning of this `MultiLocation`.
@@ -50,6 +54,11 @@ pub struct MultiLocation {
pub interior: Junctions,
}
/// A relative location which is constrained to be an interior location of the context.
///
/// See also `MultiLocation`.
pub type InteriorMultiLocation = Junctions;
impl Default for MultiLocation {
fn default() -> Self {
Self::here()
@@ -724,7 +733,7 @@ impl Junctions {
///
/// # Example
/// ```rust
/// # use xcm::latest::{Junctions::*, Junction::*};
/// # use xcm::v1::{Junctions::*, Junction::*};
/// # fn main() {
/// let mut m = X3(Parachain(2), PalletInstance(3), OnlyChild);
/// assert_eq!(m.match_and_split(&X2(Parachain(2), PalletInstance(3))), Some(&OnlyChild));
+70 -14
View File
@@ -16,10 +16,9 @@
//! Version 1 of the Cross-Consensus Message format data structures.
use super::{
super::v0::Order as Order0, MultiAsset, MultiAssetFilter, MultiAssets, MultiLocation, Xcm,
};
use alloc::vec::Vec;
use super::{MultiAsset, MultiAssetFilter, MultiAssets, MultiLocation, Xcm};
use crate::{v0::Order as OldOrder, v2::Instruction};
use alloc::{vec, vec::Vec};
use core::{
convert::{TryFrom, TryInto},
result,
@@ -184,18 +183,18 @@ impl<Call> Order<Call> {
}
}
impl<Call> TryFrom<Order0<Call>> for Order<Call> {
impl<Call> TryFrom<OldOrder<Call>> for Order<Call> {
type Error = ();
fn try_from(old: Order0<Call>) -> result::Result<Order<Call>, ()> {
fn try_from(old: OldOrder<Call>) -> result::Result<Order<Call>, ()> {
use Order::*;
Ok(match old {
Order0::Null => Noop,
Order0::DepositAsset { assets, dest } => DepositAsset {
OldOrder::Null => Noop,
OldOrder::DepositAsset { assets, dest } => DepositAsset {
assets: assets.try_into()?,
max_assets: 1,
beneficiary: dest.try_into()?,
},
Order0::DepositReserveAsset { assets, dest, effects } => DepositReserveAsset {
OldOrder::DepositReserveAsset { assets, dest, effects } => DepositReserveAsset {
assets: assets.try_into()?,
max_assets: 1,
dest: dest.try_into()?,
@@ -204,9 +203,9 @@ impl<Call> TryFrom<Order0<Call>> for Order<Call> {
.map(Order::<()>::try_from)
.collect::<result::Result<_, _>>()?,
},
Order0::ExchangeAsset { give, receive } =>
OldOrder::ExchangeAsset { give, receive } =>
ExchangeAsset { give: give.try_into()?, receive: receive.try_into()? },
Order0::InitiateReserveWithdraw { assets, reserve, effects } =>
OldOrder::InitiateReserveWithdraw { assets, reserve, effects } =>
InitiateReserveWithdraw {
assets: assets.try_into()?,
reserve: reserve.try_into()?,
@@ -215,7 +214,7 @@ impl<Call> TryFrom<Order0<Call>> for Order<Call> {
.map(Order::<()>::try_from)
.collect::<result::Result<_, _>>()?,
},
Order0::InitiateTeleport { assets, dest, effects } => InitiateTeleport {
OldOrder::InitiateTeleport { assets, dest, effects } => InitiateTeleport {
assets: assets.try_into()?,
dest: dest.try_into()?,
effects: effects
@@ -223,9 +222,9 @@ impl<Call> TryFrom<Order0<Call>> for Order<Call> {
.map(Order::<()>::try_from)
.collect::<result::Result<_, _>>()?,
},
Order0::QueryHolding { query_id, dest, assets } =>
OldOrder::QueryHolding { query_id, dest, assets } =>
QueryHolding { query_id, dest: dest.try_into()?, assets: assets.try_into()? },
Order0::BuyExecution { fees, weight, debt, halt_on_error, xcm } => {
OldOrder::BuyExecution { fees, weight, debt, halt_on_error, xcm } => {
let instructions =
xcm.into_iter().map(Xcm::<Call>::try_from).collect::<result::Result<_, _>>()?;
BuyExecution { fees: fees.try_into()?, weight, debt, halt_on_error, instructions }
@@ -233,3 +232,60 @@ impl<Call> TryFrom<Order0<Call>> for Order<Call> {
})
}
}
impl<Call> TryFrom<Instruction<Call>> for Order<Call> {
type Error = ();
fn try_from(old: Instruction<Call>) -> result::Result<Order<Call>, ()> {
use Order::*;
Ok(match old {
Instruction::DepositAsset { assets, max_assets, beneficiary } =>
DepositAsset { assets, max_assets, beneficiary },
Instruction::DepositReserveAsset { assets, max_assets, dest, xcm } =>
DepositReserveAsset {
assets,
max_assets,
dest,
effects: xcm
.0
.into_iter()
.map(Order::<()>::try_from)
.collect::<result::Result<_, _>>()?,
},
Instruction::ExchangeAsset { give, receive } => ExchangeAsset { give, receive },
Instruction::InitiateReserveWithdraw { assets, reserve, xcm } =>
InitiateReserveWithdraw {
assets,
reserve,
effects: xcm
.0
.into_iter()
.map(Order::<()>::try_from)
.collect::<result::Result<_, _>>()?,
},
Instruction::InitiateTeleport { assets, dest, xcm } => InitiateTeleport {
assets,
dest,
effects: xcm
.0
.into_iter()
.map(Order::<()>::try_from)
.collect::<result::Result<_, _>>()?,
},
Instruction::QueryHolding { query_id, dest, assets, max_response_weight } => {
// Cannot handle special response weights.
if max_response_weight > 0 {
return Err(())
}
QueryHolding { query_id, dest, assets }
},
Instruction::BuyExecution { fees, weight_limit } => {
let instructions = vec![];
let halt_on_error = true;
let weight = 0;
let debt = Option::<u64>::from(weight_limit).ok_or(())?;
BuyExecution { fees, weight, debt, halt_on_error, instructions }
},
_ => return Err(()),
})
}
}
+761
View File
@@ -0,0 +1,761 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// Substrate 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.
// Substrate 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 Cumulus. If not, see <http://www.gnu.org/licenses/>.
//! Version 1 of the Cross-Consensus Message format data structures.
use super::v1::{Order as OldOrder, Response as OldResponse, Xcm as OldXcm};
use crate::DoubleEncoded;
use alloc::{vec, vec::Vec};
use core::{
convert::{TryFrom, TryInto},
fmt::Debug,
result,
};
use derivative::Derivative;
use parity_scale_codec::{self, Decode, Encode};
mod traits;
pub use traits::{Error, ExecuteXcm, Outcome, Result, SendError, SendResult, SendXcm};
// These parts of XCM v1 have been unchanged in XCM v2, and are re-imported here.
pub use super::v1::{
Ancestor, AncestorThen, AssetId, AssetInstance, BodyId, BodyPart, Fungibility,
InteriorMultiLocation, Junction, Junctions, MultiAsset, MultiAssetFilter, MultiAssets,
MultiLocation, NetworkId, OriginKind, Parent, ParentThen, WildFungibility, WildMultiAsset,
};
#[derive(Derivative, Default, Encode, Decode)]
#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))]
#[codec(encode_bound())]
#[codec(decode_bound())]
pub struct Xcm<Call>(pub Vec<Instruction<Call>>);
impl<Call> Xcm<Call> {
/// Create an empty instance.
pub fn new() -> Self {
Self(vec![])
}
/// Return `true` if no instructions are held in `self`.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Return the number of instructions held in `self`.
pub fn len(&self) -> usize {
self.0.len()
}
/// Consume and either return `self` if it contains some instructions, or if it's empty, then
/// instead return the result of `f`.
pub fn or_else(self, f: impl FnOnce() -> Self) -> Self {
if self.0.is_empty() {
f()
} else {
self
}
}
/// Return the first instruction, if any.
pub fn first(&self) -> Option<&Instruction<Call>> {
self.0.first()
}
/// Return the last instruction, if any.
pub fn last(&self) -> Option<&Instruction<Call>> {
self.0.last()
}
/// Return the only instruction, contained in `Self`, iff only one exists (`None` otherwise).
pub fn only(&self) -> Option<&Instruction<Call>> {
if self.0.len() == 1 {
self.0.first()
} else {
None
}
}
/// Return the only instruction, contained in `Self`, iff only one exists (returns `self`
/// otherwise).
pub fn into_only(mut self) -> core::result::Result<Instruction<Call>, Self> {
if self.0.len() == 1 {
self.0.pop().ok_or(self)
} else {
Err(self)
}
}
}
/// A prelude for importing all types typically used when interacting with XCM messages.
pub mod prelude {
mod contents {
pub use super::super::{
Ancestor, AncestorThen,
AssetId::{self, *},
AssetInstance::{self, *},
BodyId, BodyPart, Error as XcmError, ExecuteXcm,
Fungibility::{self, *},
Instruction::*,
InteriorMultiLocation,
Junction::{self, *},
Junctions::{self, *},
MultiAsset,
MultiAssetFilter::{self, *},
MultiAssets, MultiLocation,
NetworkId::{self, *},
OriginKind, Outcome, Parent, ParentThen, Response, Result as XcmResult, SendError,
SendResult, SendXcm,
WeightLimit::{self, *},
WildFungibility::{self, Fungible as WildFungible, NonFungible as WildNonFungible},
WildMultiAsset::{self, *},
};
}
pub use super::{Instruction, Xcm};
pub use contents::*;
pub mod opaque {
pub use super::{
super::opaque::{Instruction, Xcm},
contents::*,
};
}
}
/// Response data to a query.
#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)]
pub enum Response {
/// No response. Serves as a neutral default.
Null,
/// Some assets.
Assets(MultiAssets),
/// The outcome of an XCM instruction.
ExecutionResult(result::Result<(), (u32, Error)>),
}
impl Default for Response {
fn default() -> Self {
Self::Null
}
}
/// An optional weight limit.
#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)]
pub enum WeightLimit {
/// No weight limit imposed.
Unlimited,
/// Weight limit imposed of the inner value.
Limited(#[codec(compact)] u64),
}
impl From<Option<u64>> for WeightLimit {
fn from(x: Option<u64>) -> Self {
match x {
Some(w) => WeightLimit::Limited(w),
None => WeightLimit::Unlimited,
}
}
}
impl From<WeightLimit> for Option<u64> {
fn from(x: WeightLimit) -> Self {
match x {
WeightLimit::Limited(w) => Some(w),
WeightLimit::Unlimited => None,
}
}
}
/// Cross-Consensus Message: A message from one consensus system to another.
///
/// Consensus systems that may send and receive messages include blockchains and smart contracts.
///
/// All messages are delivered from a known *origin*, expressed as a `MultiLocation`.
///
/// This is the inner XCM format and is version-sensitive. Messages are typically passed using the outer
/// XCM format, known as `VersionedXcm`.
#[derive(Derivative, Encode, Decode)]
#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))]
#[codec(encode_bound())]
#[codec(decode_bound())]
pub enum Instruction<Call> {
/// Withdraw asset(s) (`assets`) from the ownership of `origin` and place them into the Holding
/// Register.
///
/// - `assets`: The asset(s) to be withdrawn into holding.
///
/// Kind: *Instruction*.
///
/// Errors:
WithdrawAsset(MultiAssets),
/// Asset(s) (`assets`) have been received into the ownership of this system on the `origin`
/// system and equivalent derivatives should be placed into the Holding Register.
///
/// - `assets`: The asset(s) that are minted into holding.
///
/// Safety: `origin` must be trusted to have received and be storing `assets` such that they
/// may later be withdrawn should this system send a corresponding message.
///
/// Kind: *Trusted Indication*.
///
/// Errors:
ReserveAssetDeposited(MultiAssets),
/// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets should
/// be created and placed into the Holding Register.
///
/// - `assets`: The asset(s) that are minted into the Holding Register.
///
/// Safety: `origin` must be trusted to have irrevocably destroyed the corresponding `assets`
/// prior as a consequence of sending this message.
///
/// Kind: *Trusted Indication*.
///
/// Errors:
ReceiveTeleportedAsset(MultiAssets),
/// Respond with information that the local system is expecting.
///
/// - `query_id`: The identifier of the query that resulted in this message being sent.
/// - `response`: The message content.
/// - `max_weight`: The maximum weight that handling this response should take.
///
/// Safety: No concerns.
///
/// Kind: *Information*.
///
/// Errors:
QueryResponse {
#[codec(compact)]
query_id: u64,
response: Response,
#[codec(compact)]
max_weight: u64,
},
/// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets
/// under the ownership of `beneficiary`.
///
/// - `assets`: The asset(s) to be withdrawn.
/// - `beneficiary`: The new owner for the assets.
///
/// Safety: No concerns.
///
/// Kind: *Instruction*.
///
/// Errors:
TransferAsset { assets: MultiAssets, beneficiary: MultiLocation },
/// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets
/// under the ownership of `dest` within this consensus system (i.e. its sovereign account).
///
/// Send an onward XCM message to `dest` of `ReserveAssetDeposited` with the given
/// `xcm`.
///
/// - `assets`: The asset(s) to be withdrawn.
/// - `dest`: The location whose sovereign account will own the assets and thus the effective
/// beneficiary for the assets and the notification target for the reserve asset deposit
/// message.
/// - `xcm`: The instructions that should follow the `ReserveAssetDeposited`
/// instruction, which is sent onwards to `dest`.
///
/// Safety: No concerns.
///
/// Kind: *Instruction*.
///
/// Errors:
TransferReserveAsset { assets: MultiAssets, dest: MultiLocation, xcm: Xcm<()> },
/// Apply the encoded transaction `call`, whose dispatch-origin should be `origin` as expressed
/// by the kind of origin `origin_type`.
///
/// - `origin_type`: The means of expressing the message origin as a dispatch origin.
/// - `max_weight`: The weight of `call`; this should be at least the chain's calculated weight
/// and will be used in the weight determination arithmetic.
/// - `call`: The encoded transaction to be applied.
///
/// Safety: No concerns.
///
/// Kind: *Instruction*.
///
/// Errors:
Transact { origin_type: OriginKind, require_weight_at_most: u64, call: DoubleEncoded<Call> },
/// A message to notify about a new incoming HRMP channel. This message is meant to be sent by the
/// relay-chain to a para.
///
/// - `sender`: The sender in the to-be opened channel. Also, the initiator of the channel opening.
/// - `max_message_size`: The maximum size of a message proposed by the sender.
/// - `max_capacity`: The maximum number of messages that can be queued in the channel.
///
/// Safety: The message should originate directly from the relay-chain.
///
/// Kind: *System Notification*
HrmpNewChannelOpenRequest {
#[codec(compact)]
sender: u32,
#[codec(compact)]
max_message_size: u32,
#[codec(compact)]
max_capacity: u32,
},
/// A message to notify about that a previously sent open channel request has been accepted by
/// the recipient. That means that the channel will be opened during the next relay-chain session
/// change. This message is meant to be sent by the relay-chain to a para.
///
/// Safety: The message should originate directly from the relay-chain.
///
/// Kind: *System Notification*
///
/// Errors:
HrmpChannelAccepted {
// NOTE: We keep this as a structured item to a) keep it consistent with the other Hrmp
// items; and b) because the field's meaning is not obvious/mentioned from the item name.
#[codec(compact)]
recipient: u32,
},
/// A message to notify that the other party in an open channel decided to close it. In particular,
/// `initiator` is going to close the channel opened from `sender` to the `recipient`. The close
/// will be enacted at the next relay-chain session change. This message is meant to be sent by
/// the relay-chain to a para.
///
/// Safety: The message should originate directly from the relay-chain.
///
/// Kind: *System Notification*
///
/// Errors:
HrmpChannelClosing {
#[codec(compact)]
initiator: u32,
#[codec(compact)]
sender: u32,
#[codec(compact)]
recipient: u32,
},
/// Clear the origin.
///
/// This may be used by the XCM author to ensure that later instructions cannot command the
/// authority of the origin (e.g. if they are being relayed from an untrusted source, as often
/// the case with `ReserveAssetDeposited`).
///
/// Safety: No concerns.
///
/// Kind: *Instruction*.
///
/// Errors:
ClearOrigin,
/// Mutate the origin to some interior location.
///
/// Kind: *Instruction*
///
/// Errors:
DescendOrigin(InteriorMultiLocation),
/// Immediately report the contents of the Error Register to the given destination via XCM.
///
/// A `QueryResponse` message of type `ExecutionOutcome` is sent to `dest` with the given
/// `query_id` and the outcome of the XCM.
///
/// Kind: *Instruction*
///
/// Errors:
ReportError {
#[codec(compact)]
query_id: u64,
dest: MultiLocation,
#[codec(compact)]
max_response_weight: u64,
},
/// Remove the asset(s) (`assets`) from the Holding Register and place equivalent assets under
/// the ownership of `beneficiary` within this consensus system.
///
/// - `assets`: The asset(s) to remove from holding.
/// - `max_assets`: The maximum number of unique assets/asset instances to remove from holding.
/// Only the first `max_assets` assets/instances of those matched by `assets` will be removed,
/// prioritized under standard asset ordering. Any others will remain in holding.
/// - `beneficiary`: The new owner for the assets.
///
/// Errors:
DepositAsset { assets: MultiAssetFilter, max_assets: u32, beneficiary: MultiLocation },
/// Remove the asset(s) (`assets`) from the Holding Register and place equivalent assets under
/// the ownership of `dest` within this consensus system (i.e. deposit them into its sovereign
/// account).
///
/// Send an onward XCM message to `dest` of `ReserveAssetDeposited` with the given `effects`.
///
/// - `assets`: The asset(s) to remove from holding.
/// - `max_assets`: The maximum number of unique assets/asset instances to remove from holding.
/// Only the first `max_assets` assets/instances of those matched by `assets` will be removed,
/// prioritized under standard asset ordering. Any others will remain in holding.
/// - `dest`: The location whose sovereign account will own the assets and thus the effective
/// beneficiary for the assets and the notification target for the reserve asset deposit
/// message.
/// - `xcm`: The orders that should follow the `ReserveAssetDeposited` instruction
/// which is sent onwards to `dest`.
///
/// Errors:
DepositReserveAsset {
assets: MultiAssetFilter,
max_assets: u32,
dest: MultiLocation,
xcm: Xcm<()>,
},
/// Remove the asset(s) (`give`) from the Holding Register and replace them with alternative
/// assets.
///
/// The minimum amount of assets to be received into the Holding Register for the order not to
/// fail may be stated.
///
/// - `give`: The asset(s) to remove from holding.
/// - `receive`: The minimum amount of assets(s) which `give` should be exchanged for.
///
/// Errors:
ExchangeAsset { give: MultiAssetFilter, receive: MultiAssets },
/// Remove the asset(s) (`assets`) from holding and send a `WithdrawAsset` XCM message to a
/// reserve location.
///
/// - `assets`: The asset(s) to remove from holding.
/// - `reserve`: A valid location that acts as a reserve for all asset(s) in `assets`. The
/// sovereign account of this consensus system *on the reserve location* will have appropriate
/// assets withdrawn and `effects` will be executed on them. There will typically be only one
/// valid location on any given asset/chain combination.
/// - `xcm`: The instructions to execute on the assets once withdrawn *on the reserve
/// location*.
///
/// Errors:
InitiateReserveWithdraw { assets: MultiAssetFilter, reserve: MultiLocation, xcm: Xcm<()> },
/// Remove the asset(s) (`assets`) from holding and send a `ReceiveTeleportedAsset` XCM message
/// to a `dest` location.
///
/// - `assets`: The asset(s) to remove from holding.
/// - `dest`: A valid location that respects teleports coming from this location.
/// - `xcm`: The instructions to execute on the assets once arrived *on the destination
/// location*.
///
/// NOTE: The `dest` location *MUST* respect this origin as a valid teleportation origin for all
/// `assets`. If it does not, then the assets may be lost.
///
/// Errors:
InitiateTeleport { assets: MultiAssetFilter, dest: MultiLocation, xcm: Xcm<()> },
/// Send a `Balances` XCM message with the `assets` value equal to the holding contents, or a
/// portion thereof.
///
/// - `query_id`: An identifier that will be replicated into the returned XCM message.
/// - `dest`: A valid destination for the returned XCM message. This may be limited to the
/// current origin.
/// - `assets`: A filter for the assets that should be reported back. The assets reported back
/// will be, asset-wise, *the lesser of this value and the holding register*. No wildcards
/// will be used when reporting assets back.
/// - `max_response_weight`: The maximum amount of weight that the `QueryResponse` item which
/// is sent as a reply may take to execute. NOTE: If this is unexpectedly large then the
/// response may not execute at all.
///
/// Errors:
QueryHolding {
#[codec(compact)]
query_id: u64,
dest: MultiLocation,
assets: MultiAssetFilter,
#[codec(compact)]
max_response_weight: u64,
},
/// Pay for the execution of some XCM `xcm` and `orders` with up to `weight`
/// picoseconds of execution time, paying for this with up to `fees` from the Holding Register.
///
/// - `fees`: The asset(s) to remove from the Holding Register to pay for fees.
/// - `weight_limit`: The maximum amount of weight to purchase; this must be at least the
/// expected maximum weight of the total XCM to be executed for the
/// `AllowTopLevelPaidExecutionFrom` barrier to allow the XCM be executed.
///
/// Errors:
BuyExecution { fees: MultiAsset, weight_limit: WeightLimit },
/// Refund any surplus weight previously bought with `BuyExecution`.
RefundSurplus,
/// Set code that should be called in the case of an error happening.
///
/// An error occurring within execution of this code will _NOT_ result in the error register
/// being set, nor will an error handler be called due to it. The error handler and appendix
/// may each still be set.
///
/// The apparent weight of this instruction is inclusive of the inner `Xcm`; the executing
/// weight however includes only the difference between the previous handler and the new
/// handler, which can reasonably be negative, which would result in a surplus.
SetErrorHandler(Xcm<Call>),
/// Set code that should be called after code execution (including the error handler if any)
/// is finished. This will be called regardless of whether an error occurred.
///
/// Any error occurring due to execution of this code will result in the error register being
/// set, and the error handler (if set) firing.
///
/// The apparent weight of this instruction is inclusive of the inner `Xcm`; the executing
/// weight however includes only the difference between the previous appendix and the new
/// appendix, which can reasonably be negative, which would result in a surplus.
SetAppendix(Xcm<Call>),
/// Clear the error register.
ClearError,
}
impl<Call> Xcm<Call> {
pub fn into<C>(self) -> Xcm<C> {
Xcm::from(self)
}
pub fn from<C>(xcm: Xcm<C>) -> Self {
Self(xcm.0.into_iter().map(Instruction::<Call>::from).collect())
}
}
impl<Call> Instruction<Call> {
pub fn into<C>(self) -> Instruction<C> {
Instruction::from(self)
}
pub fn from<C>(xcm: Instruction<C>) -> Self {
use Instruction::*;
match xcm {
WithdrawAsset(assets) => WithdrawAsset(assets),
ReserveAssetDeposited(assets) => ReserveAssetDeposited(assets),
ReceiveTeleportedAsset(assets) => ReceiveTeleportedAsset(assets),
QueryResponse { query_id, response, max_weight } =>
QueryResponse { query_id, response, max_weight },
TransferAsset { assets, beneficiary } => TransferAsset { assets, beneficiary },
TransferReserveAsset { assets, dest, xcm } =>
TransferReserveAsset { assets, dest, xcm },
HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } =>
HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity },
HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient },
HrmpChannelClosing { initiator, sender, recipient } =>
HrmpChannelClosing { initiator, sender, recipient },
Transact { origin_type, require_weight_at_most, call } =>
Transact { origin_type, require_weight_at_most, call: call.into() },
ReportError { query_id, dest, max_response_weight } =>
ReportError { query_id, dest, max_response_weight },
DepositAsset { assets, max_assets, beneficiary } =>
DepositAsset { assets, max_assets, beneficiary },
DepositReserveAsset { assets, max_assets, dest, xcm } =>
DepositReserveAsset { assets, max_assets, dest, xcm },
ExchangeAsset { give, receive } => ExchangeAsset { give, receive },
InitiateReserveWithdraw { assets, reserve, xcm } =>
InitiateReserveWithdraw { assets, reserve, xcm },
InitiateTeleport { assets, dest, xcm } => InitiateTeleport { assets, dest, xcm },
QueryHolding { query_id, dest, assets, max_response_weight } =>
QueryHolding { query_id, dest, assets, max_response_weight },
BuyExecution { fees, weight_limit } => BuyExecution { fees, weight_limit },
ClearOrigin => ClearOrigin,
DescendOrigin(who) => DescendOrigin(who),
RefundSurplus => RefundSurplus,
SetErrorHandler(xcm) => SetErrorHandler(xcm.into()),
SetAppendix(xcm) => SetAppendix(xcm.into()),
ClearError => ClearError,
}
}
}
pub mod opaque {
/// The basic concrete type of `Xcm`, which doesn't make any assumptions about the
/// format of a call other than it is pre-encoded.
pub type Xcm = super::Xcm<()>;
/// The basic concrete type of `Instruction`, which doesn't make any assumptions about the
/// format of a call other than it is pre-encoded.
pub type Instruction = super::Instruction<()>;
}
// Convert from a v1 response to a v2 response
impl TryFrom<OldResponse> for Response {
type Error = ();
fn try_from(old_response: OldResponse) -> result::Result<Self, ()> {
match old_response {
OldResponse::Assets(assets) => Ok(Self::Assets(assets)),
}
}
}
impl<Call> TryFrom<OldXcm<Call>> for Xcm<Call> {
type Error = ();
fn try_from(old: OldXcm<Call>) -> result::Result<Xcm<Call>, ()> {
use Instruction::*;
Ok(Xcm(match old {
OldXcm::WithdrawAsset { assets, effects } => Some(Ok(WithdrawAsset(assets)))
.into_iter()
.chain(effects.into_iter().map(Instruction::try_from))
.collect::<result::Result<Vec<_>, _>>()?,
OldXcm::ReserveAssetDeposited { assets, effects } =>
Some(Ok(ReserveAssetDeposited(assets)))
.into_iter()
.chain(Some(Ok(ClearOrigin)).into_iter())
.chain(effects.into_iter().map(Instruction::try_from))
.collect::<result::Result<Vec<_>, _>>()?,
OldXcm::ReceiveTeleportedAsset { assets, effects } =>
Some(Ok(ReceiveTeleportedAsset(assets)))
.into_iter()
.chain(Some(Ok(ClearOrigin)).into_iter())
.chain(effects.into_iter().map(Instruction::try_from))
.collect::<result::Result<Vec<_>, _>>()?,
OldXcm::QueryResponse { query_id, response } => vec![QueryResponse {
query_id,
response: response.try_into()?,
max_weight: 50_000_000,
}],
OldXcm::TransferAsset { assets, beneficiary } =>
vec![TransferAsset { assets, beneficiary }],
OldXcm::TransferReserveAsset { assets, dest, effects } => vec![TransferReserveAsset {
assets,
dest,
xcm: Xcm(effects
.into_iter()
.map(Instruction::<()>::try_from)
.collect::<result::Result<_, _>>()?),
}],
OldXcm::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } =>
vec![HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }],
OldXcm::HrmpChannelAccepted { recipient } => vec![HrmpChannelAccepted { recipient }],
OldXcm::HrmpChannelClosing { initiator, sender, recipient } =>
vec![HrmpChannelClosing { initiator, sender, recipient }],
OldXcm::Transact { origin_type, require_weight_at_most, call } =>
vec![Transact { origin_type, require_weight_at_most, call }],
// We don't handle this one at all due to nested XCM.
OldXcm::RelayedFrom { .. } => return Err(()),
}))
}
}
impl<Call> TryFrom<OldOrder<Call>> for Instruction<Call> {
type Error = ();
fn try_from(old: OldOrder<Call>) -> result::Result<Instruction<Call>, ()> {
use Instruction::*;
Ok(match old {
OldOrder::Noop => return Err(()),
OldOrder::DepositAsset { assets, max_assets, beneficiary } =>
DepositAsset { assets, max_assets, beneficiary },
OldOrder::DepositReserveAsset { assets, max_assets, dest, effects } =>
DepositReserveAsset {
assets,
max_assets,
dest,
xcm: Xcm(effects
.into_iter()
.map(Instruction::<()>::try_from)
.collect::<result::Result<_, _>>()?),
},
OldOrder::ExchangeAsset { give, receive } => ExchangeAsset { give, receive },
OldOrder::InitiateReserveWithdraw { assets, reserve, effects } =>
InitiateReserveWithdraw {
assets,
reserve,
xcm: Xcm(effects
.into_iter()
.map(Instruction::<()>::try_from)
.collect::<result::Result<_, _>>()?),
},
OldOrder::InitiateTeleport { assets, dest, effects } => InitiateTeleport {
assets,
dest,
xcm: Xcm(effects
.into_iter()
.map(Instruction::<()>::try_from)
.collect::<result::Result<_, _>>()?),
},
OldOrder::QueryHolding { query_id, dest, assets } =>
QueryHolding { query_id, dest, assets, max_response_weight: 0 },
OldOrder::BuyExecution { fees, debt, instructions, .. } => {
// We don't handle nested XCM.
if !instructions.is_empty() {
return Err(())
}
BuyExecution { fees, weight_limit: WeightLimit::Limited(debt) }
},
})
}
}
#[cfg(test)]
mod tests {
use super::{prelude::*, *};
#[test]
fn basic_roundtrip_works() {
let xcm =
Xcm::<()>(vec![TransferAsset { assets: (Here, 1).into(), beneficiary: Here.into() }]);
let old_xcm =
OldXcm::<()>::TransferAsset { assets: (Here, 1).into(), beneficiary: Here.into() };
assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap());
let new_xcm: Xcm<()> = old_xcm.try_into().unwrap();
assert_eq!(new_xcm, xcm);
}
#[test]
fn teleport_roundtrip_works() {
let xcm = Xcm::<()>(vec![
ReceiveTeleportedAsset((Here, 1).into()),
ClearOrigin,
DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Here.into() },
]);
let old_xcm: OldXcm<()> = OldXcm::<()>::ReceiveTeleportedAsset {
assets: (Here, 1).into(),
effects: vec![OldOrder::DepositAsset {
assets: Wild(All),
max_assets: 1,
beneficiary: Here.into(),
}],
};
assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap());
let new_xcm: Xcm<()> = old_xcm.try_into().unwrap();
assert_eq!(new_xcm, xcm);
}
#[test]
fn reserve_deposit_roundtrip_works() {
let xcm = Xcm::<()>(vec![
ReserveAssetDeposited((Here, 1).into()),
ClearOrigin,
BuyExecution { fees: (Here, 1).into(), weight_limit: Some(1).into() },
DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Here.into() },
]);
let old_xcm: OldXcm<()> = OldXcm::<()>::ReserveAssetDeposited {
assets: (Here, 1).into(),
effects: vec![
OldOrder::BuyExecution {
fees: (Here, 1).into(),
debt: 1,
weight: 0,
instructions: vec![],
halt_on_error: true,
},
OldOrder::DepositAsset {
assets: Wild(All),
max_assets: 1,
beneficiary: Here.into(),
},
],
};
assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap());
let new_xcm: Xcm<()> = old_xcm.try_into().unwrap();
assert_eq!(new_xcm, xcm);
}
}
+308
View File
@@ -0,0 +1,308 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// Substrate 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.
// Substrate 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 Cumulus. If not, see <http://www.gnu.org/licenses/>.
//! Cross-Consensus Message format data structures.
use core::result;
use parity_scale_codec::{Decode, Encode};
use super::{MultiLocation, Xcm};
#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug)]
pub enum Error {
Undefined,
/// An arithmetic overflow happened.
Overflow,
/// The operation is intentionally unsupported.
Unimplemented,
UnhandledXcmVersion,
/// The implementation does not handle a given XCM.
UnhandledXcmMessage,
/// The implementation does not handle an effect present in an XCM.
UnhandledEffect,
EscalationOfPrivilege,
UntrustedReserveLocation,
UntrustedTeleportLocation,
DestinationBufferOverflow,
MultiLocationFull,
FailedToDecode,
BadOrigin,
ExceedsMaxMessageSize,
/// An asset transaction (like withdraw or deposit) failed.
/// See implementers of the `TransactAsset` trait for sources.
/// Causes can include type conversion failures between id or balance types.
FailedToTransactAsset(#[codec(skip)] &'static str),
/// Execution of the XCM would potentially result in a greater weight used than the pre-specified
/// weight limit. The amount that is potentially required is the parameter.
WeightLimitReached(Weight),
/// An asset wildcard was passed where it was not expected (e.g. as the asset to withdraw in a
/// `WithdrawAsset` XCM).
Wildcard,
/// The case where an XCM message has specified a optional weight limit and the weight required for
/// processing is too great.
///
/// Used by:
/// - `Transact`
TooMuchWeightRequired,
/// The fees specified by the XCM message were not found in the holding register.
///
/// Used by:
/// - `BuyExecution`
NotHoldingFees,
/// The weight of an XCM message is not computable ahead of execution. This generally means at least part
/// of the message is invalid, which could be due to it containing overly nested structures or an invalid
/// nested data segment (e.g. for the call in `Transact`).
WeightNotComputable,
/// The XCM did not pass the barrier condition for execution. The barrier condition differs on different
/// chains and in different circumstances, but generally it means that the conditions surrounding the message
/// were not such that the chain considers the message worth spending time executing. Since most chains
/// lift the barrier to execution on appropriate payment, presentation of an NFT voucher, or based on the
/// message origin, it means that none of those were the case.
Barrier,
/// Indicates that it is not possible for a location to have an asset be withdrawn or transferred from its
/// ownership. This probably means it doesn't own (enough of) it, but may also indicate that it is under a
/// lock, hold, freeze or is otherwise unavailable.
NotWithdrawable,
/// Indicates that the consensus system cannot deposit an asset under the ownership of a particular location.
LocationCannotHold,
/// The assets given to purchase weight is are insufficient for the weight desired.
TooExpensive,
/// The given asset is not handled.
AssetNotFound,
/// The given message cannot be translated into a format that the destination can be expected to interpret.
DestinationUnsupported,
/// `execute_xcm` has been called too many times recursively.
RecursionLimitReached,
/// Destination is routable, but there is some issue with the transport mechanism.
///
/// A human-readable explanation of the specific issue is provided.
Transport(#[codec(skip)] &'static str),
/// Destination is known to be unroutable.
Unroutable,
/// The weight required was not specified when it should have been.
UnknownWeightRequired,
}
impl From<()> for Error {
fn from(_: ()) -> Self {
Self::Undefined
}
}
impl From<SendError> for Error {
fn from(e: SendError) -> Self {
match e {
SendError::CannotReachDestination(..) | SendError::Unroutable => Error::Unroutable,
SendError::Transport(s) => Error::Transport(s),
SendError::DestinationUnsupported => Error::DestinationUnsupported,
SendError::ExceedsMaxMessageSize => Error::ExceedsMaxMessageSize,
}
}
}
pub type Result = result::Result<(), Error>;
/// Local weight type; execution time in picoseconds.
pub type Weight = u64;
/// Outcome of an XCM execution.
#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug)]
pub enum Outcome {
/// Execution completed successfully; given weight was used.
Complete(Weight),
/// Execution started, but did not complete successfully due to the given error; given weight was used.
Incomplete(Weight, Error),
/// Execution did not start due to the given error.
Error(Error),
}
impl Outcome {
pub fn ensure_complete(self) -> Result {
match self {
Outcome::Complete(_) => Ok(()),
Outcome::Incomplete(_, e) => Err(e),
Outcome::Error(e) => Err(e),
}
}
pub fn ensure_execution(self) -> result::Result<Weight, Error> {
match self {
Outcome::Complete(w) => Ok(w),
Outcome::Incomplete(w, _) => Ok(w),
Outcome::Error(e) => Err(e),
}
}
/// How much weight was used by the XCM execution attempt.
pub fn weight_used(&self) -> Weight {
match self {
Outcome::Complete(w) => *w,
Outcome::Incomplete(w, _) => *w,
Outcome::Error(_) => 0,
}
}
}
/// Type of XCM message executor.
pub trait ExecuteXcm<Call> {
/// Execute some XCM `message` from `origin` using no more than `weight_limit` weight. The weight limit is
/// a basic hard-limit and the implementation may place further restrictions or requirements on weight and
/// other aspects.
fn execute_xcm(origin: MultiLocation, message: Xcm<Call>, weight_limit: Weight) -> Outcome {
log::debug!(
target: "xcm::execute_xcm",
"origin: {:?}, message: {:?}, weight_limit: {:?}",
origin,
message,
weight_limit,
);
Self::execute_xcm_in_credit(origin, message, weight_limit, 0)
}
/// Execute some XCM `message` from `origin` using no more than `weight_limit` weight.
///
/// Some amount of `weight_credit` may be provided which, depending on the implementation, may allow
/// execution without associated payment.
fn execute_xcm_in_credit(
origin: MultiLocation,
message: Xcm<Call>,
weight_limit: Weight,
weight_credit: Weight,
) -> Outcome;
}
impl<C> ExecuteXcm<C> for () {
fn execute_xcm_in_credit(
_origin: MultiLocation,
_message: Xcm<C>,
_weight_limit: Weight,
_weight_credit: Weight,
) -> Outcome {
Outcome::Error(Error::Unimplemented)
}
}
/// Error result value when attempting to send an XCM message.
#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug)]
pub enum SendError {
/// The message and destination combination was not recognized as being reachable.
///
/// This is not considered fatal: if there are alternative transport routes available, then
/// they may be attempted. For this reason, the destination and message are contained.
CannotReachDestination(MultiLocation, Xcm<()>),
/// Destination is routable, but there is some issue with the transport mechanism. This is
/// considered fatal.
/// A human-readable explanation of the specific issue is provided.
Transport(#[codec(skip)] &'static str),
/// Destination is known to be unroutable. This is considered fatal.
Unroutable,
/// The given message cannot be translated into a format that the destination can be expected
/// to interpret.
DestinationUnsupported,
/// Message could not be sent due to its size exceeding the maximum allowed by the transport
/// layer.
ExceedsMaxMessageSize,
}
/// Result value when attempting to send an XCM message.
pub type SendResult = result::Result<(), SendError>;
/// Utility for sending an XCM message.
///
/// These can be amalgamated in tuples to form sophisticated routing systems. In tuple format, each router might return
/// `CannotReachDestination` to pass the execution to the next sender item. Note that each `CannotReachDestination`
/// might alter the destination and the XCM message for to the next router.
///
///
/// # Example
/// ```rust
/// # use xcm::v2::prelude::*;
/// # use parity_scale_codec::Encode;
///
/// /// A sender that only passes the message through and does nothing.
/// struct Sender1;
/// impl SendXcm for Sender1 {
/// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> SendResult {
/// return Err(SendError::CannotReachDestination(destination, message))
/// }
/// }
///
/// /// A sender that accepts a message that has an X2 junction, otherwise stops the routing.
/// struct Sender2;
/// impl SendXcm for Sender2 {
/// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> SendResult {
/// if let MultiLocation { parents: 0, interior: X2(j1, j2) } = destination {
/// Ok(())
/// } else {
/// Err(SendError::Unroutable)
/// }
/// }
/// }
///
/// /// A sender that accepts a message from a parent, passing through otherwise.
/// struct Sender3;
/// impl SendXcm for Sender3 {
/// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> SendResult {
/// match destination {
/// MultiLocation { parents: 1, interior: Here } => Ok(()),
/// _ => Err(SendError::CannotReachDestination(destination, message)),
/// }
/// }
/// }
///
/// // A call to send via XCM. We don't really care about this.
/// # fn main() {
/// let call: Vec<u8> = ().encode();
/// let message = Xcm(vec![Instruction::Transact {
/// origin_type: OriginKind::Superuser,
/// require_weight_at_most: 0,
/// call: call.into(),
/// }]);
/// let destination = MultiLocation::parent();
///
/// assert!(
/// // Sender2 will block this.
/// <(Sender1, Sender2, Sender3) as SendXcm>::send_xcm(destination.clone(), message.clone())
/// .is_err()
/// );
///
/// assert!(
/// // Sender3 will catch this.
/// <(Sender1, Sender3) as SendXcm>::send_xcm(destination.clone(), message.clone())
/// .is_ok()
/// );
/// # }
/// ```
pub trait SendXcm {
/// Send an XCM `message` to a given `destination`.
///
/// If it is not a destination which can be reached with this type but possibly could by others, then it *MUST*
/// return `CannotReachDestination`. Any other error will cause the tuple implementation to exit early without
/// trying other type fields.
fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> SendResult;
}
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl SendXcm for Tuple {
fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> SendResult {
for_tuples!( #(
// we shadow `destination` and `message` in each expansion for the next one.
let (destination, message) = match Tuple::send_xcm(destination, message) {
Err(SendError::CannotReachDestination(d, m)) => (d, m),
o @ _ => return o,
};
)* );
Err(SendError::CannotReachDestination(destination, message))
}
}
+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())
)]
);
});
@@ -21,46 +21,35 @@ use polkadot_test_client::{
BlockBuilderExt, ClientBlockImportExt, DefaultTestClientBuilderExt, ExecutionStrategy,
InitPolkadotBlockBuilder, TestClientBuilder, TestClientBuilderExt,
};
use polkadot_test_runtime::pallet_test_notifier;
use polkadot_test_service::construct_extrinsic;
use sp_runtime::{generic::BlockId, traits::Block};
use sp_state_machine::InspectState;
use xcm::{latest::prelude::*, VersionedXcm};
use xcm_executor::MAX_RECURSION_LIMIT;
// This is the inflection point where the test should either fail or pass.
const MAX_RECURSION_CHECK: u32 = MAX_RECURSION_LIMIT / 2;
use xcm::{latest::prelude::*, VersionedResponse, VersionedXcm};
#[test]
fn execute_within_recursion_limit() {
fn basic_buy_fees_message_executes() {
sp_tracing::try_init_simple();
let mut client = TestClientBuilder::new()
.set_execution_strategy(ExecutionStrategy::AlwaysWasm)
.build();
let mut msg = WithdrawAsset { assets: (Parent, 100).into(), effects: vec![] };
for _ in 0..MAX_RECURSION_CHECK {
msg = WithdrawAsset {
assets: (Parent, 100).into(),
effects: vec![Order::BuyExecution {
fees: (Parent, 1).into(),
weight: 0,
debt: 0,
halt_on_error: true,
// nest `msg` into itself on each iteration.
instructions: vec![msg],
}],
};
}
let msg = Xcm(vec![
WithdrawAsset((Parent, 100).into()),
BuyExecution { fees: (Parent, 1).into(), weight_limit: Unlimited },
DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Parent.into() },
]);
let mut block_builder = client.init_polkadot_block_builder();
let execute = construct_extrinsic(
&client,
polkadot_test_runtime::Call::Xcm(pallet_xcm::Call::execute(
Box::new(VersionedXcm::from(msg.clone())),
Box::new(VersionedXcm::from(msg)),
1_000_000_000,
)),
sp_keyring::Sr25519Keyring::Alice,
0,
);
block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic");
@@ -79,42 +68,65 @@ fn execute_within_recursion_limit() {
r.event,
polkadot_test_runtime::Event::Xcm(pallet_xcm::Event::Attempted(Outcome::Complete(
_
)),),
))),
)));
});
}
#[test]
fn exceed_recursion_limit() {
fn query_response_fires() {
use pallet_test_notifier::Event::*;
use pallet_xcm::QueryStatus;
use polkadot_test_runtime::Event::TestNotifier;
sp_tracing::try_init_simple();
let mut client = TestClientBuilder::new()
.set_execution_strategy(ExecutionStrategy::AlwaysWasm)
.build();
let mut msg = WithdrawAsset { assets: (Parent, 100).into(), effects: vec![] };
for _ in 0..(MAX_RECURSION_CHECK + 1) {
msg = WithdrawAsset {
assets: (Parent, 100).into(),
effects: vec![Order::BuyExecution {
fees: (Parent, 1).into(),
weight: 0,
debt: 0,
halt_on_error: true,
// nest `msg` into itself on each iteration.
instructions: vec![msg],
}],
};
}
let mut block_builder = client.init_polkadot_block_builder();
let execute = construct_extrinsic(
&client,
polkadot_test_runtime::Call::Xcm(pallet_xcm::Call::execute(
Box::new(VersionedXcm::from(msg.clone())),
1_000_000_000,
)),
polkadot_test_runtime::Call::TestNotifier(pallet_test_notifier::Call::prepare_new_query()),
sp_keyring::Sr25519Keyring::Alice,
0,
);
block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic");
let block = block_builder.build().expect("Finalizes the block").block;
let block_hash = block.hash();
futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block))
.expect("imports the block");
let mut query_id = None;
client
.state_at(&BlockId::Hash(block_hash))
.expect("state should exist")
.inspect_state(|| {
for r in polkadot_test_runtime::System::events().iter() {
match r.event {
TestNotifier(QueryPrepared(q)) => query_id = Some(q),
_ => (),
}
}
});
let query_id = query_id.unwrap();
let mut block_builder = client.init_polkadot_block_builder();
let response = Response::ExecutionResult(Ok(()));
let max_weight = 1_000_000;
let msg = Xcm(vec![QueryResponse { query_id, response, max_weight }]);
let msg = Box::new(VersionedXcm::from(msg));
let execute = construct_extrinsic(
&client,
polkadot_test_runtime::Call::Xcm(pallet_xcm::Call::execute(msg, 1_000_000_000)),
sp_keyring::Sr25519Keyring::Alice,
1,
);
block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic");
@@ -131,9 +143,99 @@ fn exceed_recursion_limit() {
.inspect_state(|| {
assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!(
r.event,
polkadot_test_runtime::Event::Xcm(pallet_xcm::Event::Attempted(
Outcome::Incomplete(_, XcmError::RecursionLimitReached),
)),
polkadot_test_runtime::Event::Xcm(pallet_xcm::Event::ResponseReady(
q,
Response::ExecutionResult(Ok(())),
)) if q == query_id,
)));
assert_eq!(
polkadot_test_runtime::Xcm::query(query_id),
Some(QueryStatus::Ready {
response: VersionedResponse::V2(Response::ExecutionResult(Ok(()))),
at: 2u32.into()
}),
)
});
}
#[test]
fn query_response_elicits_handler() {
use pallet_test_notifier::Event::*;
use polkadot_test_runtime::Event::TestNotifier;
sp_tracing::try_init_simple();
let mut client = TestClientBuilder::new()
.set_execution_strategy(ExecutionStrategy::AlwaysWasm)
.build();
let mut block_builder = client.init_polkadot_block_builder();
let execute = construct_extrinsic(
&client,
polkadot_test_runtime::Call::TestNotifier(
pallet_test_notifier::Call::prepare_new_notify_query(),
),
sp_keyring::Sr25519Keyring::Alice,
0,
);
block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic");
let block = block_builder.build().expect("Finalizes the block").block;
let block_hash = block.hash();
futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block))
.expect("imports the block");
let mut query_id = None;
client
.state_at(&BlockId::Hash(block_hash))
.expect("state should exist")
.inspect_state(|| {
for r in polkadot_test_runtime::System::events().iter() {
match r.event {
TestNotifier(NotifyQueryPrepared(q)) => query_id = Some(q),
_ => (),
}
}
});
let query_id = query_id.unwrap();
let mut block_builder = client.init_polkadot_block_builder();
let response = Response::ExecutionResult(Ok(()));
let max_weight = 1_000_000;
let msg = Xcm(vec![QueryResponse { query_id, response, max_weight }]);
let execute = construct_extrinsic(
&client,
polkadot_test_runtime::Call::Xcm(pallet_xcm::Call::execute(
Box::new(VersionedXcm::from(msg)),
1_000_000_000,
)),
sp_keyring::Sr25519Keyring::Alice,
1,
);
block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic");
let block = block_builder.build().expect("Finalizes the block").block;
let block_hash = block.hash();
futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block))
.expect("imports the block");
client
.state_at(&BlockId::Hash(block_hash))
.expect("state should exist")
.inspect_state(|| {
assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!(
r.event,
TestNotifier(ResponseReceived(
MultiLocation { parents: 0, interior: X1(Junction::AccountId32 { .. }) },
q,
Response::ExecutionResult(Ok(())),
)) if q == query_id,
)));
});
}
+2 -2
View File
@@ -38,10 +38,10 @@ pub trait Config {
/// How to get a call origin from a `OriginKind` value.
type OriginConverter: ConvertOrigin<<Self::Call as Dispatchable>::Origin>;
/// Combinations of (Location, Asset) pairs which we unilateral trust as reserves.
/// Combinations of (Location, Asset) pairs which we trust as reserves.
type IsReserve: FilterAssetLocation;
/// Combinations of (Location, Asset) pairs which we bilateral trust as teleporters.
/// Combinations of (Location, Asset) pairs which we trust as teleporters.
type IsTeleporter: FilterAssetLocation;
/// Means of inverting a location.
+269 -216
View File
@@ -21,10 +21,12 @@ use frame_support::{
ensure,
weights::GetDispatchInfo,
};
use sp_runtime::traits::Saturating;
use sp_std::{marker::PhantomData, prelude::*};
use xcm::latest::{
Error as XcmError, ExecuteXcm, MultiAssets, MultiLocation, Order, Outcome, Response, SendXcm,
Xcm,
Error as XcmError, ExecuteXcm,
Instruction::{self, *},
MultiAssets, MultiLocation, Outcome, Response, SendXcm, Xcm,
};
pub mod traits;
@@ -39,7 +41,25 @@ mod config;
pub use config::Config;
/// The XCM executor.
pub struct XcmExecutor<Config>(PhantomData<Config>);
pub struct XcmExecutor<Config: config::Config> {
holding: Assets,
origin: Option<MultiLocation>,
trader: Config::Trader,
/// The most recent error result and instruction index into the fragment in which it occured,
/// if any.
error: Option<(u32, XcmError)>,
/// The surplus weight, defined as the amount by which `max_weight` is
/// an over-estimate of the actual weight consumed. We do it this way to avoid needing the
/// execution engine to keep track of all instructions' weights (it only needs to care about
/// the weight of dynamically determined instructions such as `Transact`).
total_surplus: u64,
total_refunded: u64,
error_handler: Xcm<Config::Call>,
error_handler_weight: u64,
appendix: Xcm<Config::Call>,
appendix_weight: u64,
_config: PhantomData<Config>,
}
/// The maximum recursion limit for `execute_xcm` and `execute_effects`.
pub const MAX_RECURSION_LIMIT: u32 = 8;
@@ -47,7 +67,7 @@ pub const MAX_RECURSION_LIMIT: u32 = 8;
impl<Config: config::Config> ExecuteXcm<Config::Call> for XcmExecutor<Config> {
fn execute_xcm_in_credit(
origin: MultiLocation,
message: Xcm<Config::Call>,
mut message: Xcm<Config::Call>,
weight_limit: Weight,
mut weight_credit: Weight,
) -> Outcome {
@@ -59,143 +79,186 @@ impl<Config: config::Config> ExecuteXcm<Config::Call> for XcmExecutor<Config> {
weight_limit,
weight_credit,
);
let mut message = Xcm::<Config::Call>::from(message);
let shallow_weight = match Config::Weigher::shallow(&mut message) {
let xcm_weight = match Config::Weigher::weight(&mut message) {
Ok(x) => x,
Err(()) => return Outcome::Error(XcmError::WeightNotComputable),
};
let deep_weight = match Config::Weigher::deep(&mut message) {
Ok(x) => x,
Err(()) => return Outcome::Error(XcmError::WeightNotComputable),
};
let maximum_weight = match shallow_weight.checked_add(deep_weight) {
Some(x) => x,
None => return Outcome::Error(XcmError::Overflow),
};
if maximum_weight > weight_limit {
return Outcome::Error(XcmError::WeightLimitReached(maximum_weight))
if xcm_weight > weight_limit {
return Outcome::Error(XcmError::WeightLimitReached(xcm_weight))
}
let mut trader = Config::Trader::new();
let result = Self::do_execute_xcm(
origin,
let origin = Some(origin);
if let Err(_) = Config::Barrier::should_execute(
&origin,
true,
message,
&mut message,
xcm_weight,
&mut weight_credit,
Some(shallow_weight),
&mut trader,
0,
);
drop(trader);
log::trace!(target: "xcm::execute_xcm", "result: {:?}", &result);
match result {
Ok(surplus) => Outcome::Complete(maximum_weight.saturating_sub(surplus)),
// TODO: #2841 #REALWEIGHT We can do better than returning `maximum_weight` here, and we should otherwise
// we'll needlessly be disregarding block execution time.
Err(e) => Outcome::Incomplete(maximum_weight, e),
) {
return Outcome::Error(XcmError::Barrier)
}
let mut vm = Self::new(origin);
while !message.0.is_empty() {
let result = vm.execute(message);
log::trace!(target: "xcm::execute_xcm_in_credit", "result: {:?}", result);
message = if let Err((i, e, w)) = result {
vm.total_surplus.saturating_accrue(w);
vm.error = Some((i, e));
vm.take_error_handler().or_else(|| vm.take_appendix())
} else {
vm.drop_error_handler();
vm.take_appendix()
}
}
vm.refund_surplus();
drop(vm.trader);
// TODO #2841: Do something with holding? (Fail-safe AssetTrap?)
let weight_used = xcm_weight.saturating_sub(vm.total_surplus);
match vm.error {
None => Outcome::Complete(weight_used),
// TODO: #2841 #REALWEIGHT We should deduct the cost of any instructions following
// the error which didn't end up being executed.
Some((_, e)) => Outcome::Incomplete(weight_used, e),
}
}
}
impl<Config: config::Config> XcmExecutor<Config> {
fn reanchored(mut assets: Assets, dest: &MultiLocation) -> MultiAssets {
let inv_dest = Config::LocationInverter::invert_location(&dest);
assets.prepend_location(&inv_dest);
assets.into_assets_iter().collect::<Vec<_>>().into()
fn new(origin: Option<MultiLocation>) -> Self {
Self {
holding: Assets::new(),
origin,
trader: Config::Trader::new(),
error: None,
total_surplus: 0,
total_refunded: 0,
error_handler: Xcm(vec![]),
error_handler_weight: 0,
appendix: Xcm(vec![]),
appendix_weight: 0,
_config: PhantomData,
}
}
/// Execute the XCM and return the portion of weight of `shallow_weight + deep_weight` that `message` did not use.
///
/// NOTE: The amount returned must be less than `shallow_weight + deep_weight` of `message`.
fn do_execute_xcm(
origin: MultiLocation,
top_level: bool,
mut message: Xcm<Config::Call>,
weight_credit: &mut Weight,
maybe_shallow_weight: Option<Weight>,
trader: &mut Config::Trader,
num_recursions: u32,
) -> Result<Weight, XcmError> {
/// Execute the XCM program fragment and report back the error and which instruction caused it,
/// or `Ok` if there was no error.
fn execute(&mut self, xcm: Xcm<Config::Call>) -> Result<(), (u32, XcmError, u64)> {
log::trace!(
target: "xcm::do_execute_xcm",
"origin: {:?}, top_level: {:?}, message: {:?}, weight_credit: {:?}, maybe_shallow_weight: {:?}, recursion: {:?}",
origin,
top_level,
message,
weight_credit,
maybe_shallow_weight,
num_recursions,
target: "xcm::execute",
"origin: {:?}, total_surplus/refunded: {:?}/{:?}, error_handler_weight: {:?}",
self.origin,
self.total_surplus,
self.total_refunded,
self.error_handler_weight,
);
if num_recursions > MAX_RECURSION_LIMIT {
return Err(XcmError::RecursionLimitReached)
let mut result = Ok(());
for (i, instr) in xcm.0.into_iter().enumerate() {
match &mut result {
r @ Ok(()) =>
if let Err(e) = self.process_instruction(instr) {
*r = Err((i as u32, e, 0));
},
Err((_, _, ref mut w)) =>
if let Ok(x) = Config::Weigher::instr_weight(&instr) {
w.saturating_accrue(x)
},
}
}
result
}
// This is the weight of everything that cannot be paid for. This basically means all computation
// except any XCM which is behind an Order::BuyExecution.
let shallow_weight = maybe_shallow_weight
.or_else(|| Config::Weigher::shallow(&mut message).ok())
.ok_or(XcmError::WeightNotComputable)?;
/// Remove the registered error handler and return it. Do not refund its weight.
fn take_error_handler(&mut self) -> Xcm<Config::Call> {
let mut r = Xcm::<Config::Call>(vec![]);
sp_std::mem::swap(&mut self.error_handler, &mut r);
self.error_handler_weight = 0;
r
}
Config::Barrier::should_execute(
&origin,
top_level,
&message,
shallow_weight,
weight_credit,
)
.map_err(|()| XcmError::Barrier)?;
/// Drop the registered error handler and refund its weight.
fn drop_error_handler(&mut self) {
self.error_handler = Xcm::<Config::Call>(vec![]);
self.total_surplus = self.total_surplus.saturating_add(self.error_handler_weight);
self.error_handler_weight = 0;
}
// The surplus weight, defined as the amount by which `shallow_weight` plus all nested
// `shallow_weight` values (ensuring no double-counting and also known as `deep_weight`) is an
// over-estimate of the actual weight consumed.
let mut total_surplus: Weight = 0;
/// Remove the registered appendix and return it.
fn take_appendix(&mut self) -> Xcm<Config::Call> {
let mut r = Xcm::<Config::Call>(vec![]);
sp_std::mem::swap(&mut self.appendix, &mut r);
self.appendix_weight = 0;
r
}
let maybe_holding_effects = match (origin.clone(), message) {
(origin, Xcm::WithdrawAsset { assets, effects }) => {
/// Refund any unused weight.
fn refund_surplus(&mut self) {
let current_surplus = self.total_surplus.saturating_sub(self.total_refunded);
if current_surplus > 0 {
self.total_refunded = self.total_refunded.saturating_add(current_surplus);
if let Some(w) = self.trader.refund_weight(current_surplus) {
self.holding.subsume(w);
}
}
}
/// Process a single XCM instruction, mutating the state of the XCM virtual machine.
fn process_instruction(&mut self, instr: Instruction<Config::Call>) -> Result<(), XcmError> {
match instr {
WithdrawAsset(assets) => {
// Take `assets` from the origin account (on-chain) and place in holding.
let mut holding = Assets::default();
for asset in assets.inner() {
let withdrawn = Config::AssetTransactor::withdraw_asset(&asset, &origin)?;
holding.subsume_assets(withdrawn);
let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?;
for asset in assets.drain().into_iter() {
Config::AssetTransactor::withdraw_asset(&asset, origin)?;
self.holding.subsume(asset);
}
Some((holding, effects))
Ok(())
},
(origin, Xcm::ReserveAssetDeposited { assets, effects }) => {
ReserveAssetDeposited(assets) => {
// check whether we trust origin to be our reserve location for this asset.
for asset in assets.inner() {
// We only trust the origin to send us assets that they identify as their
// sovereign assets.
let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?;
for asset in assets.drain().into_iter() {
// Must ensure that we recognise the asset as being managed by the origin.
ensure!(
Config::IsReserve::filter_asset_location(asset, &origin),
Config::IsReserve::filter_asset_location(&asset, origin),
XcmError::UntrustedReserveLocation
);
self.holding.subsume(asset);
}
Some((assets.into(), effects))
Ok(())
},
(origin, Xcm::TransferAsset { assets, beneficiary }) => {
TransferAsset { assets, beneficiary } => {
// Take `assets` from the origin account (on-chain) and place into dest account.
let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?;
for asset in assets.inner() {
Config::AssetTransactor::beam_asset(&asset, &origin, &beneficiary)?;
Config::AssetTransactor::beam_asset(&asset, origin, &beneficiary)?;
}
None
Ok(())
},
(origin, Xcm::TransferReserveAsset { mut assets, dest, effects }) => {
TransferReserveAsset { mut assets, dest, xcm } => {
let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?;
// Take `assets` from the origin account (on-chain) and place into dest account.
let inv_dest = Config::LocationInverter::invert_location(&dest);
for asset in assets.inner() {
Config::AssetTransactor::beam_asset(asset, &origin, &dest)?;
Config::AssetTransactor::beam_asset(asset, origin, &dest)?;
}
assets.reanchor(&inv_dest)?;
Config::XcmSender::send_xcm(dest, Xcm::ReserveAssetDeposited { assets, effects })?;
None
let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin];
message.extend(xcm.0.into_iter());
Config::XcmSender::send_xcm(dest, Xcm(message)).map_err(Into::into)
},
(origin, Xcm::ReceiveTeleportedAsset { assets, effects }) => {
ReceiveTeleportedAsset(assets) => {
let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?;
// check whether we trust origin to teleport this asset to us via config trait.
for asset in assets.inner() {
// We only trust the origin to send us assets that they identify as their
// sovereign assets.
ensure!(
Config::IsTeleporter::filter_asset_location(asset, &origin),
Config::IsTeleporter::filter_asset_location(asset, origin),
XcmError::UntrustedTeleportLocation
);
// We should check that the asset can actually be teleported in (for this to be in error, there
@@ -203,13 +266,15 @@ impl<Config: config::Config> XcmExecutor<Config> {
// don't want to punish a possibly innocent chain/user).
Config::AssetTransactor::can_check_in(&origin, asset)?;
}
for asset in assets.inner() {
Config::AssetTransactor::check_in(&origin, asset);
for asset in assets.drain().into_iter() {
Config::AssetTransactor::check_in(origin, &asset);
self.holding.subsume(asset);
}
Some((Assets::from(assets), effects))
Ok(())
},
(origin, Xcm::Transact { origin_type, require_weight_at_most, mut call }) => {
Transact { origin_type, require_weight_at_most, mut call } => {
// We assume that the Relay-chain is allowed to use transact on this parachain.
let origin = self.origin.clone().ok_or(XcmError::BadOrigin)?;
// TODO: #2841 #TRANSACTFILTER allow the trait to issue filters for the relay-chain
let message_call = call.take_decoded().map_err(|_| XcmError::FailedToDecode)?;
@@ -227,142 +292,130 @@ impl<Config: config::Config> XcmExecutor<Config> {
}
.unwrap_or(weight);
let surplus = weight.saturating_sub(actual_weight);
// Credit any surplus weight that we bought. This should be safe since it's work we
// didn't realise that we didn't have to do.
// It works because we assume that the `Config::Weigher` will always count the `call`'s
// `get_dispatch_info` weight into its `shallow` estimate.
*weight_credit = weight_credit.saturating_add(surplus);
// Do the same for the total surplus, which is reported to the caller and eventually makes its way
// back up the stack to be subtracted from the deep-weight.
total_surplus = total_surplus.saturating_add(surplus);
// Return the overestimated amount so we can adjust our expectations on how much this entire
// execution has taken.
None
// We assume that the `Config::Weigher` will counts the `require_weight_at_most`
// for the estimate of how much weight this instruction will take. Now that we know
// that it's less, we credit it.
//
// We make the adjustment for the total surplus, which is used eventually
// reported back to the caller and this ensures that they account for the total
// weight consumed correctly (potentially allowing them to do more operations in a
// block than they otherwise would).
self.total_surplus = self.total_surplus.saturating_add(surplus);
Ok(())
},
(origin, Xcm::QueryResponse { query_id, response }) => {
Config::ResponseHandler::on_response(origin, query_id, response);
None
QueryResponse { query_id, response, max_weight } => {
let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?;
Config::ResponseHandler::on_response(origin, query_id, response, max_weight);
Ok(())
},
(origin, Xcm::RelayedFrom { who, message }) => {
let mut origin = origin;
origin.append_with(who).map_err(|_| XcmError::MultiLocationFull)?;
let surplus = Self::do_execute_xcm(
origin,
top_level,
*message,
weight_credit,
None,
trader,
num_recursions + 1,
)?;
total_surplus = total_surplus.saturating_add(surplus);
None
DescendOrigin(who) => self
.origin
.as_mut()
.ok_or(XcmError::BadOrigin)?
.append_with(who)
.map_err(|_| XcmError::MultiLocationFull),
ClearOrigin => {
self.origin = None;
Ok(())
},
_ => Err(XcmError::UnhandledXcmMessage)?, // Unhandled XCM message.
};
if let Some((mut holding, effects)) = maybe_holding_effects {
for effect in effects.into_iter() {
total_surplus += Self::execute_orders(
&origin,
&mut holding,
effect,
trader,
num_recursions + 1,
)?;
}
}
Ok(total_surplus)
}
fn execute_orders(
origin: &MultiLocation,
holding: &mut Assets,
order: Order<Config::Call>,
trader: &mut Config::Trader,
num_recursions: u32,
) -> Result<Weight, XcmError> {
log::trace!(
target: "xcm::execute_orders",
"origin: {:?}, holding: {:?}, order: {:?}, recursion: {:?}",
origin,
holding,
order,
num_recursions,
);
if num_recursions > MAX_RECURSION_LIMIT {
return Err(XcmError::RecursionLimitReached)
}
let mut total_surplus = 0;
match order {
Order::DepositAsset { assets, max_assets, beneficiary } => {
let deposited = holding.limited_saturating_take(assets, max_assets as usize);
ReportError { query_id, dest, max_response_weight: max_weight } => {
// Report the given result by sending a QueryResponse XCM to a previously given outcome
// destination if one was registered.
let response = Response::ExecutionResult(match self.error {
None => Ok(()),
Some(e) => Err(e),
});
let message = QueryResponse { query_id, response, max_weight };
Config::XcmSender::send_xcm(dest, Xcm(vec![message]))?;
Ok(())
},
DepositAsset { assets, max_assets, beneficiary } => {
let deposited = self.holding.limited_saturating_take(assets, max_assets as usize);
for asset in deposited.into_assets_iter() {
Config::AssetTransactor::deposit_asset(&asset, &beneficiary)?;
}
Ok(())
},
Order::DepositReserveAsset { assets, max_assets, dest, effects } => {
let deposited = holding.limited_saturating_take(assets, max_assets as usize);
DepositReserveAsset { assets, max_assets, dest, xcm } => {
let deposited = self.holding.limited_saturating_take(assets, max_assets as usize);
for asset in deposited.assets_iter() {
Config::AssetTransactor::deposit_asset(&asset, &dest)?;
}
let assets = Self::reanchored(deposited, &dest);
Config::XcmSender::send_xcm(dest, Xcm::ReserveAssetDeposited { assets, effects })?;
let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin];
message.extend(xcm.0.into_iter());
Config::XcmSender::send_xcm(dest, Xcm(message)).map_err(Into::into)
},
Order::InitiateReserveWithdraw { assets, reserve, effects } => {
let assets = Self::reanchored(holding.saturating_take(assets), &reserve);
Config::XcmSender::send_xcm(reserve, Xcm::WithdrawAsset { assets, effects })?;
InitiateReserveWithdraw { assets, reserve, xcm } => {
let assets = Self::reanchored(self.holding.saturating_take(assets), &reserve);
let mut message = vec![WithdrawAsset(assets), ClearOrigin];
message.extend(xcm.0.into_iter());
Config::XcmSender::send_xcm(reserve, Xcm(message)).map_err(Into::into)
},
Order::InitiateTeleport { assets, dest, effects } => {
InitiateTeleport { assets, dest, xcm } => {
// We must do this first in order to resolve wildcards.
let assets = holding.saturating_take(assets);
let assets = self.holding.saturating_take(assets);
for asset in assets.assets_iter() {
Config::AssetTransactor::check_out(&origin, &asset);
Config::AssetTransactor::check_out(&dest, &asset);
}
let assets = Self::reanchored(assets, &dest);
Config::XcmSender::send_xcm(dest, Xcm::ReceiveTeleportedAsset { assets, effects })?;
let mut message = vec![ReceiveTeleportedAsset(assets), ClearOrigin];
message.extend(xcm.0.into_iter());
Config::XcmSender::send_xcm(dest, Xcm(message)).map_err(Into::into)
},
Order::QueryHolding { query_id, dest, assets } => {
let assets = Self::reanchored(holding.min(&assets), &dest);
Config::XcmSender::send_xcm(
dest,
Xcm::QueryResponse { query_id, response: Response::Assets(assets) },
)?;
QueryHolding { query_id, dest, assets, max_response_weight } => {
let assets = Self::reanchored(self.holding.min(&assets), &dest);
let max_weight = max_response_weight;
let response = Response::Assets(assets);
let instruction = QueryResponse { query_id, response, max_weight };
Config::XcmSender::send_xcm(dest, Xcm(vec![instruction])).map_err(Into::into)
},
Order::BuyExecution { fees, weight, debt, halt_on_error, instructions } => {
// pay for `weight` using up to `fees` of the holding register.
let purchasing_weight =
Weight::from(weight.checked_add(debt).ok_or(XcmError::Overflow)?);
let max_fee =
holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?;
let unspent = trader.buy_weight(purchasing_weight, max_fee)?;
holding.subsume_assets(unspent);
let mut remaining_weight = weight;
for instruction in instructions.into_iter() {
match Self::do_execute_xcm(
origin.clone(),
false,
instruction,
&mut remaining_weight,
None,
trader,
num_recursions + 1,
) {
Err(e) if halt_on_error => return Err(e),
Err(_) => {},
Ok(surplus) => total_surplus += surplus,
}
}
if let Some(w) = trader.refund_weight(remaining_weight) {
holding.subsume(w);
BuyExecution { fees, weight_limit } => {
// There is no need to buy any weight is `weight_limit` is `Unlimited` since it
// would indicate that `AllowTopLevelPaidExecutionFrom` was unused for execution
// and thus there is some other reason why it has been determined that this XCM
// should be executed.
if let Some(weight) = Option::<u64>::from(weight_limit) {
// pay for `weight` using up to `fees` of the holding register.
let max_fee =
self.holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?;
let unspent = self.trader.buy_weight(weight, max_fee)?;
self.holding.subsume_assets(unspent);
}
Ok(())
},
_ => return Err(XcmError::UnhandledEffect)?,
RefundSurplus => {
self.refund_surplus();
Ok(())
},
SetErrorHandler(mut handler) => {
let handler_weight = Config::Weigher::weight(&mut handler)?;
self.total_surplus = self.total_surplus.saturating_add(self.error_handler_weight);
self.error_handler = handler;
self.error_handler_weight = handler_weight;
Ok(())
},
SetAppendix(mut appendix) => {
let appendix_weight = Config::Weigher::weight(&mut appendix)?;
self.total_surplus = self.total_surplus.saturating_add(self.appendix_weight);
self.appendix = appendix;
self.appendix_weight = appendix_weight;
Ok(())
},
ClearError => {
self.error = None;
Ok(())
},
ExchangeAsset { .. } => Err(XcmError::Unimplemented),
HrmpNewChannelOpenRequest { .. } => Err(XcmError::Unimplemented),
HrmpChannelAccepted { .. } => Err(XcmError::Unimplemented),
HrmpChannelClosing { .. } => Err(XcmError::Unimplemented),
}
Ok(total_surplus)
}
fn reanchored(mut assets: Assets, dest: &MultiLocation) -> MultiAssets {
let inv_dest = Config::LocationInverter::invert_location(&dest);
assets.prepend_location(&inv_dest);
assets.into_assets_iter().collect::<Vec<_>>().into()
}
}
@@ -22,13 +22,23 @@ pub trait OnResponse {
/// Returns `true` if we are expecting a response from `origin` for query `query_id`.
fn expecting_response(origin: &MultiLocation, query_id: u64) -> bool;
/// Handler for receiving a `response` from `origin` relating to `query_id`.
fn on_response(origin: MultiLocation, query_id: u64, response: Response) -> Weight;
fn on_response(
origin: &MultiLocation,
query_id: u64,
response: Response,
max_weight: Weight,
) -> Weight;
}
impl OnResponse for () {
fn expecting_response(_origin: &MultiLocation, _query_id: u64) -> bool {
false
}
fn on_response(_origin: MultiLocation, _query_id: u64, _response: Response) -> Weight {
fn on_response(
_origin: &MultiLocation,
_query_id: u64,
_response: Response,
_max_weight: Weight,
) -> Weight {
0
}
}
@@ -26,19 +26,18 @@ pub trait ShouldExecute {
/// Returns `true` if the given `message` may be executed.
///
/// - `origin`: The origin (sender) of the message.
/// - `top_level`: `true` indicates the initial XCM coming from the `origin`, `false` indicates an embedded XCM
/// executed internally as part of another message or an `Order`.
/// - `top_level`: `true` indicates the initial XCM coming from the `origin`, `false` indicates
/// an embedded XCM executed internally as part of another message or an `Order`.
/// - `message`: The message itself.
/// - `shallow_weight`: The weight of the non-negotiable execution of the message. This does not include any
/// embedded XCMs sat behind mechanisms like `BuyExecution` which would need to answer for their own weight.
/// - `weight_credit`: The pre-established amount of weight that the system has determined this message may utilize
/// in its execution. Typically non-zero only because of prior fee payment, but could in principle be due to other
/// factors.
/// - `max_weight`: The (possibly over-) estimation of the weight of execution of the message.
/// - `weight_credit`: The pre-established amount of weight that the system has determined this
/// message may utilize in its execution. Typically non-zero only because of prior fee
/// payment, but could in principle be due to other factors.
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<(), ()>;
}
@@ -46,25 +45,25 @@ pub trait ShouldExecute {
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl ShouldExecute for Tuple {
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<(), ()> {
for_tuples!( #(
match Tuple::should_execute(origin, top_level, message, shallow_weight, weight_credit) {
match Tuple::should_execute(origin, top_level, message, max_weight, weight_credit) {
Ok(()) => return Ok(()),
_ => (),
}
)* );
log::trace!(
target: "xcm::should_execute",
"did not pass barrier: origin: {:?}, top_level: {:?}, message: {:?}, shallow_weight: {:?}, weight_credit: {:?}",
"did not pass barrier: origin: {:?}, top_level: {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}",
origin,
top_level,
message,
shallow_weight,
max_weight,
weight_credit,
);
Err(())
@@ -58,7 +58,7 @@ pub trait TransactAsset {
///
/// When composed as a tuple, all type-items are called. It is up to the implementer that there exists no
/// value for `_what` which can cause side-effects for more than one of the type-items.
fn check_out(_origin: &MultiLocation, _what: &MultiAsset) {}
fn check_out(_dest: &MultiLocation, _what: &MultiAsset) {}
/// Deposit the `what` asset into the account of `who`.
///
@@ -67,8 +67,8 @@ pub trait TransactAsset {
Err(XcmError::Unimplemented)
}
/// Withdraw the given asset from the consensus system. Return the actual asset(s) withdrawn. In
/// the case of `what` being a wildcard, this may be something more specific.
/// Withdraw the given asset from the consensus system. Return the actual asset(s) withdrawn,
/// which should always be equal to `_what`.
///
/// Implementations should return `XcmError::FailedToTransactAsset` if withdraw failed.
fn withdraw_asset(_what: &MultiAsset, _who: &MultiLocation) -> Result<Assets, XcmError> {
+9 -29
View File
@@ -17,37 +17,17 @@
use crate::Assets;
use frame_support::weights::Weight;
use sp_std::result::Result;
use xcm::latest::{Error, MultiAsset, MultiLocation, Xcm};
use xcm::latest::prelude::*;
/// Determine the weight of an XCM message.
pub trait WeightBounds<Call> {
/// Return the minimum amount of weight that an attempted execution of this message would definitely
/// Return the maximum amount of weight that an attempted execution of this message could
/// consume.
///
/// This is useful to gauge how many fees should be paid up front to begin execution of the message.
/// It is not useful for determining whether execution should begin lest it result in surpassing weight
/// limits - in that case `deep` is the function to use.
fn shallow(message: &mut Xcm<Call>) -> Result<Weight, ()>;
fn weight(message: &mut Xcm<Call>) -> Result<Weight, ()>;
/// Return the deep amount of weight, over `shallow` that complete, successful and worst-case execution of
/// `message` would incur.
///
/// This is perhaps overly pessimistic for determining how many fees should be paid for up-front since
/// fee payment (or any other way of offsetting the execution costs such as an voucher-style NFT) may
/// happen in stages throughout execution of the XCM.
///
/// A reminder: if it is possible that `message` may have alternative means of successful completion
/// (perhaps a conditional path), then the *worst case* weight must be reported.
///
/// This is guaranteed equal to the eventual sum of all `shallow` XCM messages that get executed through
/// any internal effects. Inner XCM messages may be executed by:
/// - `Order::BuyExecution`
fn deep(message: &mut Xcm<Call>) -> Result<Weight, ()>;
/// Return the total weight for executing `message`.
fn weight(message: &mut Xcm<Call>) -> Result<Weight, ()> {
Self::shallow(message)?.checked_add(Self::deep(message)?).ok_or(())
}
/// Return the maximum amount of weight that an attempted execution of this instruction could
/// consume.
fn instr_weight(instruction: &Instruction<Call>) -> Result<Weight, ()>;
}
/// A means of getting approximate weight consumption for a given destination message executor and a
@@ -70,7 +50,7 @@ pub trait WeightTrader: Sized {
/// Purchase execution weight credit in return for up to a given `fee`. If less of the fee is required
/// then the surplus is returned. If the `fee` cannot be used to pay for the `weight`, then an error is
/// returned.
fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, Error>;
fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError>;
/// Attempt a refund of `weight` into some asset. The caller does not guarantee that the weight was
/// purchased using `buy_weight`.
@@ -87,7 +67,7 @@ impl WeightTrader for Tuple {
for_tuples!( ( #( Tuple::new() ),* ) )
}
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 mut last_error = None;
for_tuples!( #(
match Tuple.buy_weight(weight, payment.clone()) {
@@ -95,7 +75,7 @@ impl WeightTrader for Tuple {
Err(e) => { last_error = Some(e) }
}
)* );
let last_error = last_error.unwrap_or(Error::TooExpensive);
let last_error = last_error.unwrap_or(XcmError::TooExpensive);
log::trace!(target: "xcm::buy_weight", "last_error: {:?}", last_error);
Err(last_error)
}
+37 -49
View File
@@ -105,19 +105,13 @@ mod tests {
use super::*;
use codec::Encode;
use frame_support::{assert_ok, weights::Weight};
use frame_support::assert_ok;
use xcm::latest::prelude::*;
use xcm_simulator::TestExt;
// Helper function for forming buy execution message
fn buy_execution<C>(fees: impl Into<MultiAsset>, debt: Weight) -> Order<C> {
Order::BuyExecution {
fees: fees.into(),
weight: 0,
debt,
halt_on_error: false,
instructions: vec![],
}
fn buy_execution<C>(fees: impl Into<MultiAsset>) -> Instruction<C> {
BuyExecution { fees: fees.into(), weight_limit: Unlimited }
}
#[test]
@@ -131,11 +125,11 @@ mod tests {
assert_ok!(RelayChainPalletXcm::send_xcm(
Here,
Parachain(1).into(),
Transact {
Xcm(vec![Transact {
origin_type: OriginKind::SovereignAccount,
require_weight_at_most: INITIAL_BALANCE as u64,
call: remark.encode().into(),
},
}]),
));
});
@@ -158,11 +152,11 @@ mod tests {
assert_ok!(ParachainPalletXcm::send_xcm(
Here,
Parent.into(),
Transact {
Xcm(vec![Transact {
origin_type: OriginKind::SovereignAccount,
require_weight_at_most: INITIAL_BALANCE as u64,
call: remark.encode().into(),
},
}]),
));
});
@@ -185,11 +179,11 @@ mod tests {
assert_ok!(ParachainPalletXcm::send_xcm(
Here,
MultiLocation::new(1, X1(Parachain(2))),
Transact {
Xcm(vec![Transact {
origin_type: OriginKind::SovereignAccount,
require_weight_at_most: INITIAL_BALANCE as u64,
call: remark.encode().into(),
},
}]),
));
});
@@ -206,7 +200,6 @@ mod tests {
MockNet::reset();
let withdraw_amount = 123;
let max_weight_for_execution = 3;
Relay::execute_with(|| {
assert_ok!(RelayChainPalletXcm::reserve_transfer_assets(
@@ -215,7 +208,6 @@ mod tests {
Box::new(X1(AccountId32 { network: Any, id: ALICE.into() }).into().into()),
Box::new((Here, withdraw_amount).into()),
0,
max_weight_for_execution,
));
assert_eq!(
parachain::Balances::free_balance(&para_account_id(1)),
@@ -241,20 +233,17 @@ mod tests {
MockNet::reset();
let send_amount = 10;
let weight_for_execution = 3 * relay_chain::BaseXcmWeight::get();
ParaA::execute_with(|| {
let message = WithdrawAsset {
assets: (Here, send_amount).into(),
effects: vec![
buy_execution((Here, send_amount), weight_for_execution),
Order::DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: Parachain(2).into(),
},
],
};
let message = Xcm(vec![
WithdrawAsset((Here, send_amount).into()),
buy_execution((Here, send_amount)),
DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: Parachain(2).into(),
},
]);
// Send withdraw and deposit
assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent.into(), message.clone()));
});
@@ -278,27 +267,25 @@ mod tests {
MockNet::reset();
let send_amount = 10;
let weight_for_execution = 3 * relay_chain::BaseXcmWeight::get();
let query_id_set = 1234;
// Send a message which fully succeeds on the relay chain
ParaA::execute_with(|| {
let message = WithdrawAsset {
assets: (Here, send_amount).into(),
effects: vec![
buy_execution((Here, send_amount), weight_for_execution),
Order::DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: Parachain(2).into(),
},
Order::QueryHolding {
query_id: query_id_set,
dest: Parachain(1).into(),
assets: All.into(),
},
],
};
let message = Xcm(vec![
WithdrawAsset((Here, send_amount).into()),
buy_execution((Here, send_amount)),
DepositAsset {
assets: All.into(),
max_assets: 1,
beneficiary: Parachain(2).into(),
},
QueryHolding {
query_id: query_id_set,
dest: Parachain(1).into(),
assets: All.into(),
max_response_weight: 1_000_000_000,
},
]);
// Send withdraw and deposit with query holding
assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent.into(), message.clone(),));
});
@@ -318,10 +305,11 @@ mod tests {
ParaA::execute_with(|| {
assert_eq!(
parachain::MsgQueue::received_dmp(),
vec![QueryResponse {
vec![Xcm(vec![QueryResponse {
query_id: query_id_set,
response: Response::Assets(MultiAssets::new())
}]
response: Response::Assets(MultiAssets::new()),
max_weight: 1_000_000_000,
}])],
);
});
}
@@ -121,6 +121,7 @@ pub type XcmOriginToCallOrigin = (
parameter_types! {
pub const UnitWeightCost: Weight = 1;
pub KsmPerSecond: (AssetId, u128) = (Concrete(Parent.into()), 1);
pub const MaxInstructions: u32 = 100;
}
pub type LocalAssetTransactor =
@@ -139,7 +140,7 @@ impl Config for XcmConfig {
type IsTeleporter = ();
type LocationInverter = LocationInverter<Ancestry>;
type Barrier = Barrier;
type Weigher = FixedWeightBounds<UnitWeightCost, Call>;
type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
type Trader = FixedRateOfFungible<KsmPerSecond, ()>;
type ResponseHandler = ();
}
@@ -298,8 +299,10 @@ impl pallet_xcm::Config for Runtime {
type XcmExecutor = XcmExecutor<XcmConfig>;
type XcmTeleportFilter = Nothing;
type XcmReserveTransferFilter = Everything;
type Weigher = FixedWeightBounds<UnitWeightCost, Call>;
type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
type LocationInverter = LocationInverter<Ancestry>;
type Origin = Origin;
type Call = Call;
}
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Runtime>;
@@ -114,6 +114,7 @@ type LocalOriginConverter = (
parameter_types! {
pub const BaseXcmWeight: Weight = 1_000;
pub KsmPerSecond: (AssetId, u128) = (Concrete(KsmLocation::get()), 1);
pub const MaxInstructions: u32 = 100;
}
pub type XcmRouter = super::RelayChainXcmRouter;
@@ -129,7 +130,7 @@ impl Config for XcmConfig {
type IsTeleporter = ();
type LocationInverter = LocationInverter<Ancestry>;
type Barrier = Barrier;
type Weigher = FixedWeightBounds<BaseXcmWeight, Call>;
type Weigher = FixedWeightBounds<BaseXcmWeight, Call, MaxInstructions>;
type Trader = FixedRateOfFungible<KsmPerSecond, ()>;
type ResponseHandler = ();
}
@@ -146,8 +147,10 @@ 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 LocationInverter = LocationInverter<Ancestry>;
type Origin = Origin;
type Call = Call;
}
parameter_types! {
@@ -175,6 +178,6 @@ construct_runtime!(
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
ParasOrigin: origin::{Pallet, Origin},
ParasUmp: ump::{Pallet, Call, Storage, Event},
XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event<T>},
XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event<T>, Origin},
}
);
+6 -6
View File
@@ -260,7 +260,7 @@ macro_rules! decl_test_network {
},
)*
_ => {
return Err($crate::XcmError::CannotReachDestination(destination, message));
return Err($crate::XcmError::Unroutable);
}
}
}
@@ -285,7 +285,7 @@ macro_rules! decl_test_network {
);
},
)*
_ => return Err($crate::XcmError::SendFailed("Only sends to children parachain.")),
_ => return Err($crate::XcmError::Transport("Only sends to children parachain.")),
}
}
@@ -296,7 +296,7 @@ macro_rules! decl_test_network {
pub struct ParachainXcmRouter<T>($crate::PhantomData<T>);
impl<T: $crate::Get<$crate::ParaId>> $crate::SendXcm for ParachainXcmRouter<T> {
fn send_xcm(destination: $crate::MultiLocation, message: $crate::Xcm<()>) -> $crate::XcmResult {
fn send_xcm(destination: $crate::MultiLocation, message: $crate::Xcm<()>) -> $crate::SendResult {
use $crate::{UmpSink, XcmpMessageHandlerT};
match destination.interior() {
@@ -312,7 +312,7 @@ macro_rules! decl_test_network {
Ok(())
},
)*
_ => Err($crate::XcmError::CannotReachDestination(destination, message)),
_ => Err($crate::SendError::CannotReachDestination(destination, message)),
}
}
}
@@ -320,7 +320,7 @@ macro_rules! decl_test_network {
/// XCM router for relay chain.
pub struct RelayChainXcmRouter;
impl $crate::SendXcm for RelayChainXcmRouter {
fn send_xcm(destination: $crate::MultiLocation, message: $crate::Xcm<()>) -> $crate::XcmResult {
fn send_xcm(destination: $crate::MultiLocation, message: $crate::Xcm<()>) -> $crate::SendResult {
use $crate::DmpMessageHandlerT;
match destination.interior() {
@@ -331,7 +331,7 @@ macro_rules! decl_test_network {
Ok(())
},
)*
_ => Err($crate::XcmError::SendFailed("Only sends to children parachain.")),
_ => Err($crate::SendError::Unroutable),
}
}
}