Newer
Older
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
// XcmContext irrelevant in teleports checks
let dummy_context =
XcmContext { origin: None, message_id: Default::default(), topic: None };
for asset in assets.inner() {
// We should check that the asset can actually be teleported out (for this to
// be in error, there would need to be an accounting violation by ourselves,
// so it's unlikely, but we don't want to allow that kind of bug to leak into
// a trusted chain.
<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
&dest,
asset,
&dummy_context,
)
.map_err(|_| Error::<T>::CannotCheckOutTeleport)?;
}
for asset in assets.inner() {
// safe to do this here, we're in a transactional call that will be reverted on any
// errors down the line
<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
&dest,
asset,
&dummy_context,
);
}
// XCM instructions to be executed on local chain
let mut local_execute_xcm = Xcm(vec![
// withdraw assets to be teleported
WithdrawAsset(assets.clone()),
// burn assets on local chain
BurnAsset(assets),
// XCM instructions to be executed on destination chain
let mut xcm_on_dest = Xcm(vec![
// teleport `assets` in from origin chain
ReceiveTeleportedAsset(reanchored_assets),
// following instructions are not exec'ed on behalf of origin chain anymore
ClearOrigin,
]);
// handle fees
Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
// deposit all remaining assets in holding to `beneficiary` location
xcm_on_dest
.inner_mut()
.push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary });
Ok((local_execute_xcm, xcm_on_dest))
Adrian Catangiu
committed
}
/// Halve `fees` fungible amount.
pub(crate) fn halve_fees(fees: MultiAsset) -> Result<(MultiAsset, MultiAsset), Error<T>> {
match fees.fun {
Fungible(amount) => {
let fee1 = amount.saturating_div(2);
let fee2 = amount.saturating_sub(fee1);
ensure!(fee1 > 0, Error::<T>::FeesNotMet);
ensure!(fee2 > 0, Error::<T>::FeesNotMet);
Ok((MultiAsset::from((fees.id, fee1)), MultiAsset::from((fees.id, fee2))))
},
NonFungible(_) => Err(Error::<T>::FeesNotMet),
}
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
/// 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<VersionMigrationStage>) {
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::<T>::drain_prefix(v) {
if let Ok(new_key) = old_key.into_latest() {
SupportedVersion::<T>::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::<T>::drain_prefix(v) {
if let Ok(new_key) = old_key.into_latest() {
VersionNotifiers::<T>::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))
if let NotifyCurrentTargets(maybe_last_raw_key) = stage {
let mut iter = match maybe_last_raw_key {
Some(k) => VersionNotifyTargets::<T>::iter_prefix_from(XCM_VERSION, k),
None => VersionNotifyTargets::<T>::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::<T::XcmRouter>(new_key, message) {
Ok((message_id, cost)) => {
let value = (query_id, max_weight, xcm_version);
VersionNotifyTargets::<T>::insert(XCM_VERSION, key, value);
Event::VersionChangeNotified {
destination: new_key,
result: xcm_version,
cost,
message_id,
}
},
Err(e) => {
VersionNotifyTargets::<T>::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::<T>::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))
}
let versioned_key = LatestVersionedMultiLocation(&new_key);
if target_xcm_version == xcm_version {
VersionNotifyTargets::<T>::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::<T::XcmRouter>(new_key, message) {
Ok((message_id, cost)) => {
VersionNotifyTargets::<T>::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))
/// Request that `dest` informs us of its version.
pub fn request_version_notify(dest: impl Into<MultiLocation>) -> XcmResult {
let dest = dest.into();
let versioned_dest = VersionedMultiLocation::from(dest);
let already = VersionNotifiers::<T>::contains_key(XCM_VERSION, &versioned_dest);
ensure!(!already, XcmError::InvalidLocation);
let query_id = QueryCounter::<T>::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::<T::XcmRouter>(dest, Xcm(vec![instruction]))?;
Self::deposit_event(Event::VersionNotifyRequested { destination: dest, cost, message_id });
VersionNotifiers::<T>::insert(XCM_VERSION, &versioned_dest, query_id);
let query_status =
QueryStatus::VersionNotifier { origin: versioned_dest, is_active: false };
Queries::<T>::insert(query_id, query_status);
Ok(())
}
/// Request that `dest` ceases informing us of its version.
pub fn unrequest_version_notify(dest: impl Into<MultiLocation>) -> XcmResult {
let dest = dest.into();
let versioned_dest = LatestVersionedMultiLocation(&dest);
let query_id = VersionNotifiers::<T>::take(XCM_VERSION, versioned_dest)
.ok_or(XcmError::InvalidLocation)?;
let (message_id, cost) = send_xcm::<T::XcmRouter>(dest, Xcm(vec![UnsubscribeVersion]))?;
Self::deposit_event(Event::VersionNotifyUnrequested {
destination: dest,
cost,
message_id,
});
/// 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<Junctions>,
dest: impl Into<MultiLocation>,
mut message: Xcm<()>,
) -> Result<XcmHash, SendError> {
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::<T::XcmRouter>(dest, message)?;
if let Some(fee_payer) = maybe_fee_payer {
Self::charge_fees(fee_payer, price).map_err(|_| SendError::Fees)?;
pub fn check_account() -> T::AccountId {
const ID: PalletId = PalletId(*b"py/xcmch");
AccountIdConversion::<T::AccountId>::into_account_truncating(&ID)
}
/// Create a new expectation of a query response with the querier being here.
fn do_new_query(
responder: impl Into<MultiLocation>,
maybe_notify: Option<(u8, u8)>,
timeout: BlockNumberFor<T>,
match_querier: impl Into<MultiLocation>,
) -> u64 {
QueryCounter::<T>::mutate(|q| {
let r = *q;
q.saturating_inc();
Queries::<T>::insert(
r,
QueryStatus::Pending {
responder: responder.into().into(),
maybe_match_querier: Some(match_querier.into().into()),
maybe_notify,
timeout,
},
);
r
})
}
Gavin Wood
committed
/// 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<MultiLocation>,
notify: impl Into<<T as Config>::RuntimeCall>,
timeout: BlockNumberFor<T>,
) -> Result<(), XcmError> {
let responder = responder.into();
let destination = T::UniversalLocation::get()
.invert_target(&responder)
.map_err(|()| XcmError::LocationNotInvertible)?;
let notify: <T as Config>::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(())
}
Gavin Wood
committed
/// 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<MultiLocation>,
notify: impl Into<<T as Config>::RuntimeCall>,
timeout: BlockNumberFor<T>,
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
match_querier: impl Into<MultiLocation>,
) -> 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::<T>::mutate(|q| {
if let Some(index) = q.iter().position(|i| &i.0 == &versioned_dest) {
// exists - just bump the count.
q[index].1.saturating_inc();
Gavin Wood
committed
} else {
Gavin Wood
committed
}
});
}
/// 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::<T>::FeesNotMet)?;
Self::deposit_event(Event::FeesPaid { paying: location, fees: assets });
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
Ok(())
}
}
pub struct LockTicket<T: Config> {
sovereign_account: T::AccountId,
amount: BalanceOf<T>,
unlocker: MultiLocation,
item_index: Option<usize>,
}
impl<T: Config> xcm_executor::traits::Enact for LockTicket<T> {
fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
use xcm_executor::traits::LockError::UnexpectedState;
let mut locks = LockedFungibles::<T>::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)?;
},
Gavin Wood
committed
}
LockedFungibles::<T>::insert(&self.sovereign_account, locks);
T::Currency::extend_lock(
*b"py/xcmlk",
&self.sovereign_account,
self.amount,
WithdrawReasons::all(),
);
Ok(())
}
}
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
pub struct UnlockTicket<T: Config> {
sovereign_account: T::AccountId,
amount: BalanceOf<T>,
unlocker: MultiLocation,
}
impl<T: Config> xcm_executor::traits::Enact for UnlockTicket<T> {
fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
use xcm_executor::traits::LockError::UnexpectedState;
let mut locks =
LockedFungibles::<T>::get(&self.sovereign_account).ok_or(UnexpectedState)?;
let mut maybe_remove_index = None;
let mut locked = BalanceOf::<T>::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);
ensure!(found, UnexpectedState);
if let Some(remove_index) = maybe_remove_index {
locks.swap_remove(remove_index);
}
LockedFungibles::<T>::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<T: Config> {
key: (u32, T::AccountId, VersionedAssetId),
amount: u128,
locker: VersionedMultiLocation,
owner: VersionedMultiLocation,
}
impl<T: Config> xcm_executor::traits::Enact for ReduceTicket<T> {
fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
use xcm_executor::traits::LockError::UnexpectedState;
let mut record = RemoteLockedFungibles::<T>::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::<T>::remove(&self.key);
} else {
impl<T: Config> xcm_executor::traits::AssetLock for Pallet<T> {
type LockTicket = LockTicket<T>;
type UnlockTicket = UnlockTicket<T>;
type ReduceTicket = ReduceTicket<T>;
fn prepare_lock(
unlocker: MultiLocation,
asset: MultiAsset,
owner: MultiLocation,
) -> Result<LockTicket<T>, xcm_executor::traits::LockError> {
use xcm_executor::traits::LockError::*;
let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned);
let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
let item_index = locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker));
ensure!(item_index.is_some() || locks.len() < T::MaxLockers::get() as usize, NoResources);
Ok(LockTicket { sovereign_account, amount, unlocker, item_index })
}
fn prepare_unlock(
unlocker: MultiLocation,
asset: MultiAsset,
owner: MultiLocation,
) -> Result<UnlockTicket<T>, xcm_executor::traits::LockError> {
use xcm_executor::traits::LockError::*;
let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned);
let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
let item_index =
locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker)).ok_or(NotLocked)?;
ensure!(locks[item_index].0 >= amount, NotLocked);
Ok(UnlockTicket { sovereign_account, amount, unlocker })
}
fn note_unlockable(
locker: MultiLocation,
asset: MultiAsset,
mut owner: MultiLocation,
) -> Result<(), xcm_executor::traits::LockError> {
use xcm_executor::traits::LockError::*;
ensure!(T::TrustedLockers::contains(&locker, &asset), NotTrusted);
let amount = match asset.fun {
Fungible(a) => a,
NonFungible(_) => return Err(Unimplemented),
};
owner.remove_network_id();
let account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
let locker = locker.into();
let owner = owner.into();
let id: VersionedAssetId = asset.id.into();
let key = (XCM_VERSION, account, id);
let mut record =
RemoteLockedFungibleRecord { amount, owner, locker, consumers: BoundedVec::default() };
if let Some(old) = RemoteLockedFungibles::<T>::get(&key) {
// Make sure that the new record wouldn't clobber any old data.
ensure!(old.locker == record.locker && old.owner == record.owner, WouldClobber);
RemoteLockedFungibles::<T>::insert(&key, record);
Ok(())
fn prepare_reduce_unlockable(
locker: MultiLocation,
asset: MultiAsset,
mut owner: MultiLocation,
) -> Result<Self::ReduceTicket, xcm_executor::traits::LockError> {
use xcm_executor::traits::LockError::*;
let amount = match asset.fun {
Fungible(a) => a,
NonFungible(_) => return Err(Unimplemented),
};
owner.remove_network_id();
let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
let locker = locker.into();
let owner = owner.into();
let id: VersionedAssetId = asset.id.into();
let key = (XCM_VERSION, sovereign_account, id);
let record = RemoteLockedFungibles::<T>::get(&key).ok_or(NotLocked)?;
// Make sure that the record contains what we expect and there's enough to unlock.
ensure!(locker == record.locker && owner == record.owner, WouldClobber);
ensure!(record.amount >= amount, NotEnoughLocked);
ensure!(
record.amount_held().map_or(true, |h| record.amount.saturating_sub(amount) >= h),
InUse
);
Ok(ReduceTicket { key, amount, locker, owner })
}
}
impl<T: Config> WrapVersion for Pallet<T> {
fn wrap_version<RuntimeCall>(
dest: &MultiLocation,
xcm: impl Into<VersionedXcm<RuntimeCall>>,
) -> Result<VersionedXcm<RuntimeCall>, ()> {
Self::get_version_for(dest)
.or_else(|| {
Self::note_unknown_version(dest);
SafeXcmVersion::<T>::get()
})
.ok_or_else(|| {
log::trace!(
target: "xcm::pallet_xcm::wrap_version",
"Could not determine a version to wrap XCM for destination: {:?}",
dest,
);
()
})
.and_then(|v| xcm.into().into_version(v.min(XCM_VERSION)))
}
}
impl<T: Config> GetVersion for Pallet<T> {
fn get_version_for(dest: &MultiLocation) -> Option<XcmVersion> {
SupportedVersion::<T>::get(XCM_VERSION, LatestVersionedMultiLocation(dest))
}
}
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
impl<T: Config> VersionChangeNotifier for Pallet<T> {
/// Start notifying `location` should the XCM version of this chain change.
///
/// When it does, this type should ensure a `QueryResponse` message is sent with the given
/// `query_id` & `max_weight` and with a `response` of `Response::Version`. This should happen
/// until/unless `stop` is called with the correct `query_id`.
///
/// If the `location` has an ongoing notification and when this function is called, then an
/// error should be returned.
fn start(
dest: &MultiLocation,
query_id: QueryId,
max_weight: Weight,
_context: &XcmContext,
) -> XcmResult {
let versioned_dest = LatestVersionedMultiLocation(dest);
let already = VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest);
ensure!(!already, XcmError::InvalidLocation);
let xcm_version = T::AdvertisedXcmVersion::get();
let response = Response::Version(xcm_version);
let instruction = QueryResponse { query_id, response, max_weight, querier: None };
let (message_id, cost) = send_xcm::<T::XcmRouter>(*dest, Xcm(vec![instruction]))?;
Self::deposit_event(Event::<T>::VersionNotifyStarted {
destination: *dest,
cost,
message_id,
});
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
let value = (query_id, max_weight, xcm_version);
VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_dest, value);
Ok(())
}
/// Stop notifying `location` should the XCM change. This is a no-op if there was never a
/// subscription.
fn stop(dest: &MultiLocation, _context: &XcmContext) -> XcmResult {
VersionNotifyTargets::<T>::remove(XCM_VERSION, LatestVersionedMultiLocation(dest));
Ok(())
}
/// Return true if a location is subscribed to XCM version changes.
fn is_subscribed(dest: &MultiLocation) -> bool {
let versioned_dest = LatestVersionedMultiLocation(dest);
VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest)
}
}
impl<T: Config> DropAssets for Pallet<T> {
fn drop_assets(origin: &MultiLocation, assets: Assets, _context: &XcmContext) -> Weight {
if assets.is_empty() {
return Weight::zero()
let versioned = VersionedMultiAssets::from(MultiAssets::from(assets));
let hash = BlakeTwo256::hash_of(&(&origin, &versioned));
AssetTraps::<T>::mutate(hash, |n| *n += 1);
Self::deposit_event(Event::AssetsTrapped { hash, origin: *origin, assets: versioned });
// TODO #3735: Put the real weight in there.
Weight::zero()
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
impl<T: Config> ClaimAssets for Pallet<T> {
fn claim_assets(
origin: &MultiLocation,
ticket: &MultiLocation,
assets: &MultiAssets,
_context: &XcmContext,
) -> bool {
let mut versioned = VersionedMultiAssets::from(assets.clone());
match (ticket.parents, &ticket.interior) {
(0, X1(GeneralIndex(i))) =>
versioned = match versioned.into_version(*i as u32) {
Ok(v) => v,
Err(()) => return false,
},
(0, Here) => (),
_ => return false,
};
let hash = BlakeTwo256::hash_of(&(origin, versioned.clone()));
match AssetTraps::<T>::get(hash) {
0 => return false,
1 => AssetTraps::<T>::remove(hash),
n => AssetTraps::<T>::insert(hash, n - 1),
Self::deposit_event(Event::AssetsClaimed { hash, origin: *origin, assets: versioned });
impl<T: Config> OnResponse for Pallet<T> {
fn expecting_response(
origin: &MultiLocation,
query_id: QueryId,
querier: Option<&MultiLocation>,
) -> bool {
match Queries::<T>::get(query_id) {
Some(QueryStatus::Pending { responder, maybe_match_querier, .. }) =>
MultiLocation::try_from(responder).map_or(false, |r| origin == &r) &&
maybe_match_querier.map_or(true, |match_querier| {
MultiLocation::try_from(match_querier).map_or(false, |match_querier| {
querier.map_or(false, |q| q == &match_querier)
})
}),
Some(QueryStatus::VersionNotifier { origin: r, .. }) =>
MultiLocation::try_from(r).map_or(false, |r| origin == &r),
_ => false,
Gavin Wood
committed
}
Gavin Wood
committed
fn on_response(
origin: &MultiLocation,
query_id: QueryId,
querier: Option<&MultiLocation>,
response: Response,
max_weight: Weight,
_context: &XcmContext,
) -> Weight {
let origin = *origin;
match (response, Queries::<T>::get(query_id)) {
(
Response::Version(v),
Some(QueryStatus::VersionNotifier { origin: expected_origin, is_active }),
) => {
let origin: MultiLocation = match expected_origin.try_into() {
Ok(o) if o == origin => o,
Self::deposit_event(Event::InvalidResponder {
origin,
query_id,
expected_location: Some(o),
});
Self::deposit_event(Event::InvalidResponder {
origin,
query_id,
expected_location: None,
});
// TODO #3735: Correct weight for this.
return Weight::zero()
},
};
// TODO #3735: Check max_weight is correct.
if !is_active {
Queries::<T>::insert(
query_id,
QueryStatus::VersionNotifier { origin: origin.into(), is_active: true },
}
// We're being notified of a version change.
SupportedVersion::<T>::insert(
XCM_VERSION,
LatestVersionedMultiLocation(&origin),
v,
);
Self::deposit_event(Event::SupportedVersionChanged {
location: origin,
version: v,
});
Weight::zero()
},
(
response,
Some(QueryStatus::Pending { responder, maybe_notify, maybe_match_querier, .. }),
) => {
if let Some(match_querier) = maybe_match_querier {
let match_querier = match MultiLocation::try_from(match_querier) {
Ok(mq) => mq,
Self::deposit_event(Event::InvalidQuerierVersion { origin, query_id });
Self::deposit_event(Event::InvalidQuerier {
origin,
expected_querier: match_querier,
maybe_actual_querier: querier.cloned(),
});
}
let responder = match MultiLocation::try_from(responder) {
Ok(r) => r,
Err(_) => {
Self::deposit_event(Event::InvalidResponderVersion { origin, query_id });
if origin != responder {
Self::deposit_event(Event::InvalidResponder {
origin,
expected_location: Some(responder),
});
return Weight::zero()
}
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>::RuntimeCall::decode(&mut bytes)
}) {
Queries::<T>::remove(query_id);
let weight = call.get_dispatch_info().weight;
if weight.any_gt(max_weight) {
let e = Event::NotifyOverweight {
actual_weight: weight,
max_budgeted_weight: max_weight,
};
let dispatch_origin = Origin::Response(origin).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 {
Gavin Wood
committed
query_id,
pallet_index,
call_index,
Gavin Wood
committed
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.
let e =
Event::NotifyDecodeFailed { query_id, pallet_index, call_index };
let e = Event::ResponseReady { query_id, response: 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 });
Weight::zero()
},
}
},
_ => {
let e = Event::UnexpectedResponse { origin, query_id };
Self::deposit_event(e);
impl<T: Config> CheckSuspension for Pallet<T> {
fn is_suspended<Call>(
_origin: &MultiLocation,
_instructions: &mut [Instruction<Call>],
_max_weight: Weight,
_properties: &mut Properties,
) -> bool {
XcmExecutionSuspended::<T>::get()
}
}
Gavin Wood
committed
/// 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>>,
{
match o.into() {
Ok(Origin::Xcm(location)) => Ok(location),
_ => Err(BadOrigin),
}
}
Gavin Wood
committed
/// 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.
///
/// May reasonably be used with `EnsureXcm`.
pub struct IsMajorityOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
impl<Prefix: Get<MultiLocation>, Body: Get<BodyId>> Contains<MultiLocation>
fn contains(l: &MultiLocation) -> bool {
let maybe_suffix = l.match_and_split(&Prefix::get());
matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part.is_majority())
/// Filter for `MultiLocation` to find those which represent a voice of an identified plurality.
///
/// May reasonably be used with `EnsureXcm`.
pub struct IsVoiceOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
impl<Prefix: Get<MultiLocation>, Body: Get<BodyId>> Contains<MultiLocation>
for IsVoiceOfBody<Prefix, Body>
{
fn contains(l: &MultiLocation) -> bool {
let maybe_suffix = l.match_and_split(&Prefix::get());
matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part == &BodyPart::Voice)
}
}
/// `EnsureOrigin` implementation succeeding with a `MultiLocation` value to recognize and filter
/// the `Origin::Xcm` item.
impl<O: OriginTrait + From<Origin>, F: Contains<MultiLocation>> EnsureOrigin<O> for EnsureXcm<F>
where
O::PalletsOrigin: From<Origin> + TryInto<Origin, Error = O::PalletsOrigin>,
{
type Success = MultiLocation;
fn try_origin(outer: O) -> Result<Self::Success, O> {
Gavin Wood
committed
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()),
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<O, ()> {
Ok(O::from(Origin::Xcm(Here.into())))
Gavin Wood
committed
/// `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 try_successful_origin() -> Result<O, ()> {
Ok(O::from(Origin::Response(Here.into())))
Gavin Wood
committed
}
}
/// A simple passthrough where we reuse the `MultiLocation`-typed XCM origin as the inner value of
/// this crate's `Origin::Xcm` value.