mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 18:41:03 +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>);
|
||||
|
||||
Reference in New Issue
Block a user