(
) -> frame_support::weights::Weight {
let on_chain_storage_version = ::on_chain_storage_version();
log::info!(
target: "runtime::xcm",
"Running migration storage v1 for xcm with storage version {:?}",
on_chain_storage_version,
);
if on_chain_storage_version < 1 {
let mut count = 0;
Queries::::translate::, _>(|_key, value| {
count += 1;
Some(value.into())
});
StorageVersion::new(1).put::();
log::info!(
target: "runtime::xcm",
"Running migration storage v1 for xcm with storage version {:?} was complete",
on_chain_storage_version,
);
// calculate and return migration weights
T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1)
} else {
log::warn!(
target: "runtime::xcm",
"Attempted to apply migration to v1 but failed because storage version is {:?}",
on_chain_storage_version,
);
T::DbWeight::get().reads(1)
}
}
}
#[pallet::call]
impl Pallet {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::send())]
pub fn send(
origin: OriginFor,
dest: Box,
message: Box>,
) -> DispatchResult {
let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
let interior: Junctions =
origin_location.try_into().map_err(|_| Error::::InvalidOrigin)?;
let dest = MultiLocation::try_from(*dest).map_err(|()| Error::::BadVersion)?;
let message: Xcm<()> = (*message).try_into().map_err(|()| Error::::BadVersion)?;
let message_id =
Self::send_xcm(interior, dest, message.clone()).map_err(Error::::from)?;
let e = Event::Sent { origin: origin_location, destination: dest, message, message_id };
Self::deposit_event(e);
Ok(())
}
/// Teleport some assets from the local chain to some destination chain.
///
/// Fee payment on the destination side is made from the asset in the `assets` vector of
/// index `fee_asset_item`. The weight limit for fees is not provided and thus is unlimited,
/// with all fees taken as needed from the asset.
///
/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
/// - `dest`: Destination context for the assets. Will typically be `X2(Parent, Parachain(..))` to send
/// from parachain to parachain, or `X1(Parachain(..))` to send from relay to parachain.
/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will generally be
/// an `AccountId32` value.
/// - `assets`: The assets to be withdrawn. The first item should be the currency used to to pay the fee on the
/// `dest` side. May not be empty.
/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
/// fees.
#[pallet::call_index(1)]
#[pallet::weight({
let maybe_assets: Result = (*assets.clone()).try_into();
let maybe_dest: Result = (*dest.clone()).try_into();
match (maybe_assets, maybe_dest) {
(Ok(assets), Ok(dest)) => {
use sp_std::vec;
let count = assets.len() as u32;
let mut message = Xcm(vec![
WithdrawAsset(assets),
SetFeesMode { jit_withdraw: true },
InitiateTeleport {
assets: Wild(AllCounted(count)),
dest,
xcm: Xcm(vec![]),
},
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::teleport_assets().saturating_add(w))
}
_ => Weight::MAX,
}
})]
pub fn teleport_assets(
origin: OriginFor,
dest: Box,
beneficiary: Box,
assets: Box,
fee_asset_item: u32,
) -> DispatchResult {
Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_item, None)
}
/// Transfer some assets from the local chain to the sovereign account of a destination
/// chain and forward a notification XCM.
///
/// Fee payment on the destination side is made from the asset in the `assets` vector of
/// index `fee_asset_item`. The weight limit for fees is not provided and thus is unlimited,
/// with all fees taken as needed from the asset.
///
/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
/// - `dest`: Destination context for the assets. Will typically be `X2(Parent, Parachain(..))` to send
/// from parachain to parachain, or `X1(Parachain(..))` to send from relay to parachain.
/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will generally be
/// an `AccountId32` value.
/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the fee on the
/// `dest` side.
/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
/// fees.
#[pallet::call_index(2)]
#[pallet::weight({
let maybe_assets: Result = (*assets.clone()).try_into();
let maybe_dest: Result = (*dest.clone()).try_into();
match (maybe_assets, maybe_dest) {
(Ok(assets), Ok(dest)) => {
use sp_std::vec;
let mut message = Xcm(vec![
SetFeesMode { jit_withdraw: true },
TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) }
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::reserve_transfer_assets().saturating_add(w))
}
_ => Weight::MAX,
}
})]
pub fn reserve_transfer_assets(
origin: OriginFor,
dest: Box,
beneficiary: Box,
assets: Box,
fee_asset_item: u32,
) -> DispatchResult {
Self::do_reserve_transfer_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
None,
)
}
/// Execute an XCM message from a local, signed, origin.
///
/// An event is deposited indicating whether `msg` could be executed completely or only
/// partially.
///
/// No more than `max_weight` will be used in its attempted execution. If this is less than the
/// maximum amount of weight that the message could take to be executed, then no execution
/// attempt will be made.
///
/// NOTE: A successful return to this does *not* imply that the `msg` was executed successfully
/// to completion; only that *some* of it was executed.
#[pallet::call_index(3)]
#[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))]
pub fn execute(
origin: OriginFor,
message: Box::RuntimeCall>>,
max_weight: Weight,
) -> DispatchResultWithPostInfo {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let hash = message.using_encoded(sp_io::hashing::blake2_256);
let message = (*message).try_into().map_err(|()| Error::::BadVersion)?;
let value = (origin_location, message);
ensure!(T::XcmExecuteFilter::contains(&value), Error::::Filtered);
let (origin_location, message) = value;
let outcome = T::XcmExecutor::execute_xcm_in_credit(
origin_location,
message,
hash,
max_weight,
max_weight,
);
let result =
Ok(Some(outcome.weight_used().saturating_add(T::WeightInfo::execute())).into());
Self::deposit_event(Event::Attempted { outcome });
result
}
/// Extoll that a particular destination can be communicated with through a particular
/// version of XCM.
///
/// - `origin`: Must be an origin specified by AdminOrigin.
/// - `location`: The destination that is being described.
/// - `xcm_version`: The latest version of XCM that `location` supports.
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::force_xcm_version())]
pub fn force_xcm_version(
origin: OriginFor,
location: Box,
version: XcmVersion,
) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
let location = *location;
SupportedVersion::::insert(
XCM_VERSION,
LatestVersionedMultiLocation(&location),
version,
);
Self::deposit_event(Event::SupportedVersionChanged { location, version });
Ok(())
}
/// Set a safe XCM version (the version that XCM should be encoded with if the most recent
/// version a destination can accept is unknown).
///
/// - `origin`: Must be an origin specified by AdminOrigin.
/// - `maybe_xcm_version`: The default XCM encoding version, or `None` to disable.
#[pallet::call_index(5)]
#[pallet::weight(T::WeightInfo::force_default_xcm_version())]
pub fn force_default_xcm_version(
origin: OriginFor,
maybe_xcm_version: Option,
) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
SafeXcmVersion::::set(maybe_xcm_version);
Ok(())
}
/// Ask a location to notify us regarding their XCM version and any changes to it.
///
/// - `origin`: Must be an origin specified by AdminOrigin.
/// - `location`: The location to which we should subscribe for XCM version notifications.
#[pallet::call_index(6)]
#[pallet::weight(T::WeightInfo::force_subscribe_version_notify())]
pub fn force_subscribe_version_notify(
origin: OriginFor,
location: Box,
) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
let location: MultiLocation =
(*location).try_into().map_err(|()| Error::::BadLocation)?;
Self::request_version_notify(location).map_err(|e| {
match e {
XcmError::InvalidLocation => Error::::AlreadySubscribed,
_ => Error::::InvalidOrigin,
}
.into()
})
}
/// Require that a particular destination should no longer notify us regarding any XCM
/// version changes.
///
/// - `origin`: Must be an origin specified by AdminOrigin.
/// - `location`: The location to which we are currently subscribed for XCM version
/// notifications which we no longer desire.
#[pallet::call_index(7)]
#[pallet::weight(T::WeightInfo::force_unsubscribe_version_notify())]
pub fn force_unsubscribe_version_notify(
origin: OriginFor,
location: Box,
) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
let location: MultiLocation =
(*location).try_into().map_err(|()| Error::::BadLocation)?;
Self::unrequest_version_notify(location).map_err(|e| {
match e {
XcmError::InvalidLocation => Error::::NoSubscription,
_ => Error::::InvalidOrigin,
}
.into()
})
}
/// Transfer some assets from the local chain to the sovereign account of a destination
/// chain and forward a notification XCM.
///
/// Fee payment on the destination side is made from the asset in the `assets` vector of
/// index `fee_asset_item`, up to enough to pay for `weight_limit` of weight. If more weight
/// is needed than `weight_limit`, then the operation will fail and the assets send may be
/// at risk.
///
/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
/// - `dest`: Destination context for the assets. Will typically be `X2(Parent, Parachain(..))` to send
/// from parachain to parachain, or `X1(Parachain(..))` to send from relay to parachain.
/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will generally be
/// an `AccountId32` value.
/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the fee on the
/// `dest` side.
/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
/// fees.
/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
#[pallet::call_index(8)]
#[pallet::weight({
let maybe_assets: Result = (*assets.clone()).try_into();
let maybe_dest: Result = (*dest.clone()).try_into();
match (maybe_assets, maybe_dest) {
(Ok(assets), Ok(dest)) => {
use sp_std::vec;
let mut message = Xcm(vec![
SetFeesMode { jit_withdraw: true },
TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) }
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::reserve_transfer_assets().saturating_add(w))
}
_ => Weight::MAX,
}
})]
pub fn limited_reserve_transfer_assets(
origin: OriginFor,
dest: Box,
beneficiary: Box,
assets: Box,
fee_asset_item: u32,
weight_limit: WeightLimit,
) -> DispatchResult {
Self::do_reserve_transfer_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
Some(weight_limit),
)
}
/// Teleport some assets from the local chain to some destination chain.
///
/// Fee payment on the destination side is made from the asset in the `assets` vector of
/// index `fee_asset_item`, up to enough to pay for `weight_limit` of weight. If more weight
/// is needed than `weight_limit`, then the operation will fail and the assets send may be
/// at risk.
///
/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
/// - `dest`: Destination context for the assets. Will typically be `X2(Parent, Parachain(..))` to send
/// from parachain to parachain, or `X1(Parachain(..))` to send from relay to parachain.
/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will generally be
/// an `AccountId32` value.
/// - `assets`: The assets to be withdrawn. The first item should be the currency used to to pay the fee on the
/// `dest` side. May not be empty.
/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
/// fees.
/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
#[pallet::call_index(9)]
#[pallet::weight({
let maybe_assets: Result = (*assets.clone()).try_into();
let maybe_dest: Result = (*dest.clone()).try_into();
match (maybe_assets, maybe_dest) {
(Ok(assets), Ok(dest)) => {
use sp_std::vec;
let mut message = Xcm(vec![
WithdrawAsset(assets),
SetFeesMode { jit_withdraw: true },
InitiateTeleport { assets: Wild(All), dest, xcm: Xcm(vec![]) },
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::teleport_assets().saturating_add(w))
}
_ => Weight::MAX,
}
})]
pub fn limited_teleport_assets(
origin: OriginFor,
dest: Box,
beneficiary: Box,
assets: Box,
fee_asset_item: u32,
weight_limit: WeightLimit,
) -> DispatchResult {
Self::do_teleport_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
Some(weight_limit),
)
}
/// Set or unset the global suspension state of the XCM executor.
///
/// - `origin`: Must be an origin specified by AdminOrigin.
/// - `suspended`: `true` to suspend, `false` to resume.
#[pallet::call_index(10)]
#[pallet::weight(T::WeightInfo::force_suspension())]
pub fn force_suspension(origin: OriginFor, suspended: bool) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
XcmExecutionSuspended::::set(suspended);
Ok(())
}
}
}
/// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic.
const MAX_ASSETS_FOR_TRANSFER: usize = 2;
impl QueryHandler for Pallet {
type QueryId = u64;
type BlockNumber = T::BlockNumber;
type Error = XcmError;
type UniversalLocation = T::UniversalLocation;
/// Attempt to create a new query ID and register it as a query that is yet to respond.
fn new_query(
responder: impl Into,
timeout: T::BlockNumber,
match_querier: impl Into,
) -> Self::QueryId {
Self::do_new_query(responder, None, timeout, match_querier).into()
}
/// To check the status of the query, use `fn query()` passing the resultant `QueryId`
/// value.
fn report_outcome(
message: &mut Xcm<()>,
responder: impl Into,
timeout: Self::BlockNumber,
) -> Result {
let responder = responder.into();
let destination = Self::UniversalLocation::get()
.invert_target(&responder)
.map_err(|()| XcmError::LocationNotInvertible)?;
let query_id = Self::new_query(responder, timeout, Here);
let response_info = QueryResponseInfo { destination, query_id, max_weight: Weight::zero() };
let report_error = Xcm(vec![ReportError(response_info)]);
message.0.insert(0, SetAppendix(report_error));
Ok(query_id)
}
/// Removes response when ready and emits [Event::ResponseTaken] event.
fn take_response(query_id: Self::QueryId) -> QueryResponseStatus {
match Queries::::get(query_id) {
Some(QueryStatus::Ready { response, at }) => match response.try_into() {
Ok(response) => {
Queries::::remove(query_id);
Self::deposit_event(Event::ResponseTaken { query_id });
QueryResponseStatus::Ready { response, at }
},
Err(_) => QueryResponseStatus::UnexpectedVersion,
},
Some(QueryStatus::Pending { timeout, .. }) => QueryResponseStatus::Pending { timeout },
Some(_) => QueryResponseStatus::UnexpectedVersion,
None => QueryResponseStatus::NotFound,
}
}
#[cfg(feature = "runtime-benchmarks")]
fn expect_response(id: Self::QueryId, response: Response) {
let response = response.into();
Queries::::insert(
id,
QueryStatus::Ready { response, at: frame_system::Pallet::::block_number() },
);
}
}
impl Pallet {
fn do_reserve_transfer_assets(
origin: OriginFor,
dest: Box,
beneficiary: Box,
assets: Box,
fee_asset_item: u32,
maybe_weight_limit: Option,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest = (*dest).try_into().map_err(|()| Error::::BadVersion)?;
let beneficiary: MultiLocation =
(*beneficiary).try_into().map_err(|()| Error::::BadVersion)?;
let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::::BadVersion)?;
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets);
let value = (origin_location, assets.into_inner());
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered);
let (origin_location, assets) = value;
let context = T::UniversalLocation::get();
let fees = assets
.get(fee_asset_item as usize)
.ok_or(Error::::Empty)?
.clone()
.reanchored(&dest, context)
.map_err(|_| Error::::CannotReanchor)?;
let max_assets = assets.len() as u32;
let assets: MultiAssets = assets.into();
let weight_limit = match maybe_weight_limit {
Some(weight_limit) => weight_limit,
None => {
let fees = fees.clone();
let mut remote_message = Xcm(vec![
ReserveAssetDeposited(assets.clone()),
ClearOrigin,
BuyExecution { fees, weight_limit: Limited(Weight::zero()) },
DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary },
]);
// use local weight for remote message and hope for the best.
let remote_weight = T::Weigher::weight(&mut remote_message)
.map_err(|()| Error::::UnweighableMessage)?;
Limited(remote_weight)
},
};
let xcm = Xcm(vec![
BuyExecution { fees, weight_limit },
DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary },
]);
let mut message = Xcm(vec![
SetFeesMode { jit_withdraw: true },
TransferReserveAsset { assets, dest, xcm },
]);
let weight =
T::Weigher::weight(&mut message).map_err(|()| Error::::UnweighableMessage)?;
let hash = message.using_encoded(sp_io::hashing::blake2_256);
let outcome =
T::XcmExecutor::execute_xcm_in_credit(origin_location, message, hash, weight, weight);
Self::deposit_event(Event::Attempted { outcome });
Ok(())
}
fn do_teleport_assets(
origin: OriginFor,
dest: Box,
beneficiary: Box,
assets: Box,
fee_asset_item: u32,
maybe_weight_limit: Option,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest = (*dest).try_into().map_err(|()| Error::::BadVersion)?;
let beneficiary: MultiLocation =
(*beneficiary).try_into().map_err(|()| Error::::BadVersion)?;
let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::::BadVersion)?;
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets);
let value = (origin_location, assets.into_inner());
ensure!(T::XcmTeleportFilter::contains(&value), Error::::Filtered);
let (origin_location, assets) = value;
let context = T::UniversalLocation::get();
let fees = assets
.get(fee_asset_item as usize)
.ok_or(Error::::Empty)?
.clone()
.reanchored(&dest, context)
.map_err(|_| Error::::CannotReanchor)?;
let max_assets = assets.len() as u32;
let assets: MultiAssets = assets.into();
let weight_limit = match maybe_weight_limit {
Some(weight_limit) => weight_limit,
None => {
let fees = fees.clone();
let mut remote_message = Xcm(vec![
ReceiveTeleportedAsset(assets.clone()),
ClearOrigin,
BuyExecution { fees, weight_limit: Limited(Weight::zero()) },
DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary },
]);
// use local weight for remote message and hope for the best.
let remote_weight = T::Weigher::weight(&mut remote_message)
.map_err(|()| Error::::UnweighableMessage)?;
Limited(remote_weight)
},
};
let xcm = Xcm(vec![
BuyExecution { fees, weight_limit },
DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary },
]);
let mut message = Xcm(vec![
WithdrawAsset(assets),
SetFeesMode { jit_withdraw: true },
InitiateTeleport { assets: Wild(AllCounted(max_assets)), dest, xcm },
]);
let weight =
T::Weigher::weight(&mut message).map_err(|()| Error::::UnweighableMessage)?;
let hash = message.using_encoded(sp_io::hashing::blake2_256);
let outcome =
T::XcmExecutor::execute_xcm_in_credit(origin_location, message, hash, weight, weight);
Self::deposit_event(Event::Attempted { outcome });
Ok(())
}
/// Will always make progress, and will do its best not to use much more than `weight_cutoff`
/// in doing so.
pub(crate) fn check_xcm_version_change(
mut stage: VersionMigrationStage,
weight_cutoff: Weight,
) -> (Weight, Option) {
let mut weight_used = Weight::zero();
let sv_migrate_weight = T::WeightInfo::migrate_supported_version();
let vn_migrate_weight = T::WeightInfo::migrate_version_notifiers();
let vnt_already_notified_weight = T::WeightInfo::already_notified_target();
let vnt_notify_weight = T::WeightInfo::notify_current_targets();
let vnt_migrate_weight = T::WeightInfo::migrate_version_notify_targets();
let vnt_migrate_fail_weight = T::WeightInfo::notify_target_migration_fail();
let vnt_notify_migrate_weight = T::WeightInfo::migrate_and_notify_old_targets();
use VersionMigrationStage::*;
if stage == MigrateSupportedVersion {
// We assume that supported XCM version only ever increases, so just cycle through lower
// XCM versioned from the current.
for v in 0..XCM_VERSION {
for (old_key, value) in SupportedVersion::::drain_prefix(v) {
if let Ok(new_key) = old_key.into_latest() {
SupportedVersion::::insert(XCM_VERSION, new_key, value);
}
weight_used.saturating_accrue(sv_migrate_weight);
if weight_used.any_gte(weight_cutoff) {
return (weight_used, Some(stage))
}
}
}
stage = MigrateVersionNotifiers;
}
if stage == MigrateVersionNotifiers {
for v in 0..XCM_VERSION {
for (old_key, value) in VersionNotifiers::::drain_prefix(v) {
if let Ok(new_key) = old_key.into_latest() {
VersionNotifiers::::insert(XCM_VERSION, new_key, value);
}
weight_used.saturating_accrue(vn_migrate_weight);
if weight_used.any_gte(weight_cutoff) {
return (weight_used, Some(stage))
}
}
}
stage = NotifyCurrentTargets(None);
}
let xcm_version = T::AdvertisedXcmVersion::get();
if let NotifyCurrentTargets(maybe_last_raw_key) = stage {
let mut iter = match maybe_last_raw_key {
Some(k) => VersionNotifyTargets::::iter_prefix_from(XCM_VERSION, k),
None => VersionNotifyTargets::::iter_prefix(XCM_VERSION),
};
while let Some((key, value)) = iter.next() {
let (query_id, max_weight, target_xcm_version) = value;
let new_key: MultiLocation = match key.clone().try_into() {
Ok(k) if target_xcm_version != xcm_version => k,
_ => {
// We don't early return here since we need to be certain that we
// make some progress.
weight_used.saturating_accrue(vnt_already_notified_weight);
continue
},
};
let response = Response::Version(xcm_version);
let message =
Xcm(vec![QueryResponse { query_id, response, max_weight, querier: None }]);
let event = match send_xcm::(new_key, message) {
Ok((message_id, cost)) => {
let value = (query_id, max_weight, xcm_version);
VersionNotifyTargets::::insert(XCM_VERSION, key, value);
Event::VersionChangeNotified {
destination: new_key,
result: xcm_version,
cost,
message_id,
}
},
Err(e) => {
VersionNotifyTargets::::remove(XCM_VERSION, key);
Event::NotifyTargetSendFail { location: new_key, query_id, error: e.into() }
},
};
Self::deposit_event(event);
weight_used.saturating_accrue(vnt_notify_weight);
if weight_used.any_gte(weight_cutoff) {
let last = Some(iter.last_raw_key().into());
return (weight_used, Some(NotifyCurrentTargets(last)))
}
}
stage = MigrateAndNotifyOldTargets;
}
if stage == MigrateAndNotifyOldTargets {
for v in 0..XCM_VERSION {
for (old_key, value) in VersionNotifyTargets::::drain_prefix(v) {
let (query_id, max_weight, target_xcm_version) = value;
let new_key = match MultiLocation::try_from(old_key.clone()) {
Ok(k) => k,
Err(()) => {
Self::deposit_event(Event::NotifyTargetMigrationFail {
location: old_key,
query_id: value.0,
});
weight_used.saturating_accrue(vnt_migrate_fail_weight);
if weight_used.any_gte(weight_cutoff) {
return (weight_used, Some(stage))
}
continue
},
};
let versioned_key = LatestVersionedMultiLocation(&new_key);
if target_xcm_version == xcm_version {
VersionNotifyTargets::::insert(XCM_VERSION, versioned_key, value);
weight_used.saturating_accrue(vnt_migrate_weight);
} else {
// Need to notify target.
let response = Response::Version(xcm_version);
let message = Xcm(vec![QueryResponse {
query_id,
response,
max_weight,
querier: None,
}]);
let event = match send_xcm::(new_key, message) {
Ok((message_id, cost)) => {
VersionNotifyTargets::::insert(
XCM_VERSION,
versioned_key,
(query_id, max_weight, xcm_version),
);
Event::VersionChangeNotified {
destination: new_key,
result: xcm_version,
cost,
message_id,
}
},
Err(e) => Event::NotifyTargetSendFail {
location: new_key,
query_id,
error: e.into(),
},
};
Self::deposit_event(event);
weight_used.saturating_accrue(vnt_notify_migrate_weight);
}
if weight_used.any_gte(weight_cutoff) {
return (weight_used, Some(stage))
}
}
}
}
(weight_used, None)
}
/// Request that `dest` informs us of its version.
pub fn request_version_notify(dest: impl Into) -> XcmResult {
let dest = dest.into();
let versioned_dest = VersionedMultiLocation::from(dest);
let already = VersionNotifiers::::contains_key(XCM_VERSION, &versioned_dest);
ensure!(!already, XcmError::InvalidLocation);
let query_id = QueryCounter::::mutate(|q| {
let r = *q;
q.saturating_inc();
r
});
// TODO #3735: Correct weight.
let instruction = SubscribeVersion { query_id, max_response_weight: Weight::zero() };
let (message_id, cost) = send_xcm::(dest, Xcm(vec![instruction]))?;
Self::deposit_event(Event::VersionNotifyRequested { destination: dest, cost, message_id });
VersionNotifiers::::insert(XCM_VERSION, &versioned_dest, query_id);
let query_status =
QueryStatus::VersionNotifier { origin: versioned_dest, is_active: false };
Queries::::insert(query_id, query_status);
Ok(())
}
/// Request that `dest` ceases informing us of its version.
pub fn unrequest_version_notify(dest: impl Into) -> XcmResult {
let dest = dest.into();
let versioned_dest = LatestVersionedMultiLocation(&dest);
let query_id = VersionNotifiers::::take(XCM_VERSION, versioned_dest)
.ok_or(XcmError::InvalidLocation)?;
let (message_id, cost) = send_xcm::(dest, Xcm(vec![UnsubscribeVersion]))?;
Self::deposit_event(Event::VersionNotifyUnrequested {
destination: dest,
cost,
message_id,
});
Queries::::remove(query_id);
Ok(())
}
/// Relay an XCM `message` from a given `interior` location in this context to a given `dest`
/// location. The `fee_payer` is charged for the delivery unless `None` in which case fees
/// are not charged (and instead borne by the chain).
pub fn send_xcm(
interior: impl Into,
dest: impl Into,
mut message: Xcm<()>,
) -> Result {
let interior = interior.into();
let dest = dest.into();
let maybe_fee_payer = if interior != Junctions::Here {
message.0.insert(0, DescendOrigin(interior));
Some(interior.into())
} else {
None
};
log::debug!(target: "xcm::send_xcm", "dest: {:?}, message: {:?}", &dest, &message);
let (ticket, price) = validate_send::(dest, message)?;
if let Some(fee_payer) = maybe_fee_payer {
Self::charge_fees(fee_payer, price).map_err(|_| SendError::Fees)?;
}
T::XcmRouter::deliver(ticket)
}
pub fn check_account() -> T::AccountId {
const ID: PalletId = PalletId(*b"py/xcmch");
AccountIdConversion::::into_account_truncating(&ID)
}
/// Create a new expectation of a query response with the querier being here.
fn do_new_query(
responder: impl Into,
maybe_notify: Option<(u8, u8)>,
timeout: T::BlockNumber,
match_querier: impl Into,
) -> u64 {
QueryCounter::::mutate(|q| {
let r = *q;
q.saturating_inc();
Queries::::insert(
r,
QueryStatus::Pending {
responder: responder.into().into(),
maybe_match_querier: Some(match_querier.into().into()),
maybe_notify,
timeout,
},
);
r
})
}
/// 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.
///
/// `report_outcome_notify` may return an error if the `responder` is not invertible.
///
/// It is assumed that the querier of the response will be `Here`.
///
/// 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: impl Into,
notify: impl Into<::RuntimeCall>,
timeout: T::BlockNumber,
) -> Result<(), XcmError> {
let responder = responder.into();
let destination = T::UniversalLocation::get()
.invert_target(&responder)
.map_err(|()| XcmError::LocationNotInvertible)?;
let notify: ::RuntimeCall = notify.into();
let max_weight = notify.get_dispatch_info().weight;
let query_id = Self::new_notify_query(responder, notify, timeout, Here);
let response_info = QueryResponseInfo { destination, query_id, max_weight };
let report_error = Xcm(vec![ReportError(response_info)]);
message.0.insert(0, SetAppendix(report_error));
Ok(())
}
/// 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: impl Into,
notify: impl Into<::RuntimeCall>,
timeout: T::BlockNumber,
match_querier: impl Into,
) -> 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, match_querier)
}
/// Note that a particular destination to whom we would like to send a message is unknown
/// and queue it for version discovery.
fn note_unknown_version(dest: &MultiLocation) {
log::trace!(
target: "xcm::pallet_xcm::note_unknown_version",
"XCM version is unknown for destination: {:?}",
dest,
);
let versioned_dest = VersionedMultiLocation::from(*dest);
VersionDiscoveryQueue::::mutate(|q| {
if let Some(index) = q.iter().position(|i| &i.0 == &versioned_dest) {
// exists - just bump the count.
q[index].1.saturating_inc();
} else {
let _ = q.try_push((versioned_dest, 1));
}
});
}
/// Withdraw given `assets` from the given `location` and pay as XCM fees.
///
/// Fails if:
/// - the `assets` are not known on this chain;
/// - the `assets` cannot be withdrawn with that location as the Origin.
fn charge_fees(location: MultiLocation, assets: MultiAssets) -> DispatchResult {
T::XcmExecutor::charge_fees(location, assets.clone())
.map_err(|_| Error::::FeesNotMet)?;
Self::deposit_event(Event::FeesPaid { paying: location, fees: assets });
Ok(())
}
}
pub struct LockTicket {
sovereign_account: T::AccountId,
amount: BalanceOf,
unlocker: MultiLocation,
item_index: Option,
}
impl xcm_executor::traits::Enact for LockTicket {
fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
use xcm_executor::traits::LockError::UnexpectedState;
let mut locks = LockedFungibles::::get(&self.sovereign_account).unwrap_or_default();
match self.item_index {
Some(index) => {
ensure!(locks.len() > index, UnexpectedState);
ensure!(locks[index].1.try_as::<_>() == Ok(&self.unlocker), UnexpectedState);
locks[index].0 = locks[index].0.max(self.amount);
},
None => {
locks
.try_push((self.amount, self.unlocker.into()))
.map_err(|(_balance, _location)| UnexpectedState)?;
},
}
LockedFungibles::::insert(&self.sovereign_account, locks);
T::Currency::extend_lock(
*b"py/xcmlk",
&self.sovereign_account,
self.amount,
WithdrawReasons::all(),
);
Ok(())
}
}
pub struct UnlockTicket {
sovereign_account: T::AccountId,
amount: BalanceOf,
unlocker: MultiLocation,
}
impl xcm_executor::traits::Enact for UnlockTicket {
fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
use xcm_executor::traits::LockError::UnexpectedState;
let mut locks =
LockedFungibles::::get(&self.sovereign_account).ok_or(UnexpectedState)?;
let mut maybe_remove_index = None;
let mut locked = BalanceOf::::zero();
let mut found = false;
// We could just as well do with with an into_iter, filter_map and collect, however this way
// avoids making an allocation.
for (i, x) in locks.iter_mut().enumerate() {
if x.1.try_as::<_>().defensive() == Ok(&self.unlocker) {
x.0 = x.0.saturating_sub(self.amount);
if x.0.is_zero() {
maybe_remove_index = Some(i);
}
found = true;
}
locked = locked.max(x.0);
}
ensure!(found, UnexpectedState);
if let Some(remove_index) = maybe_remove_index {
locks.swap_remove(remove_index);
}
LockedFungibles::::insert(&self.sovereign_account, locks);
let reasons = WithdrawReasons::all();
T::Currency::set_lock(*b"py/xcmlk", &self.sovereign_account, locked, reasons);
Ok(())
}
}
pub struct ReduceTicket {
key: (u32, T::AccountId, VersionedAssetId),
amount: u128,
locker: VersionedMultiLocation,
owner: VersionedMultiLocation,
}
impl xcm_executor::traits::Enact for ReduceTicket {
fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
use xcm_executor::traits::LockError::UnexpectedState;
let mut record = RemoteLockedFungibles::::get(&self.key).ok_or(UnexpectedState)?;
ensure!(self.locker == record.locker && self.owner == record.owner, UnexpectedState);
let new_amount = record.amount.checked_sub(self.amount).ok_or(UnexpectedState)?;
ensure!(record.amount_held().map_or(true, |h| new_amount >= h), UnexpectedState);
if new_amount == 0 {
RemoteLockedFungibles::::remove(&self.key);
} else {
record.amount = new_amount;
RemoteLockedFungibles::::insert(&self.key, &record);
}
Ok(())
}
}
impl xcm_executor::traits::AssetLock for Pallet {
type LockTicket = LockTicket