mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 14:01:02 +00:00
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:
@@ -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>);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
@@ -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,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));
|
||||
|
||||
@@ -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(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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(()),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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},
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -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,
|
||||
)));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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(¶_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},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user