Newer
Older
Ok(()) => {
VersionNotifyTargets::<T>::insert(
XCM_VERSION,
versioned_key,
(query_id, max_weight, xcm_version),
);
Event::VersionChangeNotified(new_key, xcm_version)
},
Err(e) => Event::NotifyTargetSendFail(new_key, query_id, e.into()),
};
Self::deposit_event(event);
weight_used.saturating_accrue(todo_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.
Keith Yeung
committed
pub fn request_version_notify(dest: impl Into<MultiLocation>) -> XcmResult {
let dest = dest.into();
let versioned_dest = VersionedMultiLocation::from(dest.clone());
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: 0 };
T::XcmRouter::send_xcm(dest, Xcm(vec![instruction]))?;
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.
Keith Yeung
committed
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)?;
T::XcmRouter::send_xcm(dest.clone(), Xcm(vec![UnsubscribeVersion]))?;
Queries::<T>::remove(query_id);
Ok(())
}
/// Relay an XCM `message` from a given `interior` location in this context to a given `dest`
/// location. A null `dest` is not handled.
Keith Yeung
committed
interior: impl Into<Junctions>,
dest: impl Into<MultiLocation>,
Gavin Wood
committed
mut message: Xcm<()>,
) -> Result<(), SendError> {
Keith Yeung
committed
let interior = interior.into();
let dest = dest.into();
Gavin Wood
committed
if interior != Junctions::Here {
message.0.insert(0, DescendOrigin(interior))
log::trace!(target: "xcm::send_xcm", "dest: {:?}, message: {:?}", &dest, &message);
pub fn check_account() -> T::AccountId {
const ID: PalletId = PalletId(*b"py/xcmch");
AccountIdConversion::<T::AccountId>::into_account_truncating(&ID)
Gavin Wood
committed
fn do_new_query(
Keith Yeung
committed
responder: impl Into<MultiLocation>,
Gavin Wood
committed
maybe_notify: Option<(u8, u8)>,
timeout: T::BlockNumber,
) -> u64 {
QueryCounter::<T>::mutate(|q| {
Gavin Wood
committed
let r = *q;
Gavin Wood
committed
Queries::<T>::insert(
r,
Keith Yeung
committed
QueryStatus::Pending {
responder: responder.into().into(),
maybe_notify,
timeout,
},
Gavin Wood
committed
);
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.
///
/// `report_outcome` may return an error if the `responder` is not invertible.
///
Gavin Wood
committed
/// To check the status of the query, use `fn query()` passing the resultant `QueryId`
/// value.
pub fn report_outcome(
message: &mut Xcm<()>,
Keith Yeung
committed
responder: impl Into<MultiLocation>,
Gavin Wood
committed
timeout: T::BlockNumber,
) -> Result<QueryId, XcmError> {
Keith Yeung
committed
let responder = responder.into();
let dest = T::LocationInverter::invert_location(&responder)
.map_err(|()| XcmError::MultiLocationNotInvertible)?;
Gavin Wood
committed
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));
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.
///
Gavin Wood
committed
/// 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<()>,
Keith Yeung
committed
responder: impl Into<MultiLocation>,
Gavin Wood
committed
notify: impl Into<<T as Config>::Call>,
timeout: T::BlockNumber,
Keith Yeung
committed
let responder = responder.into();
let dest = T::LocationInverter::invert_location(&responder)
.map_err(|()| XcmError::MultiLocationNotInvertible)?;
Gavin Wood
committed
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: max_response_weight.ref_time(),
}]);
Gavin Wood
committed
message.0.insert(0, SetAppendix(report_error));
Gavin Wood
committed
}
/// Attempt to create a new query ID and register it as a query that is yet to respond.
Keith Yeung
committed
pub fn new_query(responder: impl Into<MultiLocation>, timeout: T::BlockNumber) -> u64 {
Gavin Wood
committed
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(
Keith Yeung
committed
responder: impl Into<MultiLocation>,
Gavin Wood
committed
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
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
}
}
/// 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,
);
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
let versioned_dest = VersionedMultiLocation::from(dest.clone());
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();
} else {
let _ = q.try_push((versioned_dest, 1));
}
});
}
}
impl<T: Config> WrapVersion for Pallet<T> {
fn wrap_version<Call>(
dest: &MultiLocation,
xcm: impl Into<VersionedXcm<Call>>,
) -> Result<VersionedXcm<Call>, ()> {
SupportedVersion::<T>::get(XCM_VERSION, LatestVersionedMultiLocation(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)))
}
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
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 `Repsonse::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: u64) -> 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 };
T::XcmRouter::send_xcm(dest.clone(), Xcm(vec![instruction]))?;
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) -> 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) -> u64 {
if assets.is_empty() {
return 0
}
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.clone(), versioned));
// TODO #3735: Put the real weight in there.
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
0
}
}
impl<T: Config> ClaimAssets for Pallet<T> {
fn claim_assets(
origin: &MultiLocation,
ticket: &MultiLocation,
assets: &MultiAssets,
) -> 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));
match AssetTraps::<T>::get(hash) {
0 => return false,
1 => AssetTraps::<T>::remove(hash),
n => AssetTraps::<T>::insert(hash, n - 1),
}
return true
}
}
Gavin Wood
committed
impl<T: Config> OnResponse for Pallet<T> {
fn expecting_response(origin: &MultiLocation, query_id: QueryId) -> bool {
match Queries::<T>::get(query_id) {
Some(QueryStatus::Pending { responder, .. }) =>
MultiLocation::try_from(responder).map_or(false, |r| origin == &r),
Some(QueryStatus::VersionNotifier { origin: r, .. }) =>
MultiLocation::try_from(r).map_or(false, |r| origin == &r),
_ => false,
Gavin Wood
committed
}
}
fn on_response(
origin: &MultiLocation,
query_id: QueryId,
response: Response,
max_weight: u64,
) -> u64 {
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
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,
Ok(o) => {
Self::deposit_event(Event::InvalidResponder(
origin.clone(),
query_id,
Some(o),
));
return 0
},
_ => {
Self::deposit_event(Event::InvalidResponder(
origin.clone(),
query_id,
None,
));
// TODO #3735: Correct weight for this.
return 0
},
};
// TODO #3735: Check max_weight is correct.
if !is_active {
Queries::<T>::insert(
query_id,
QueryStatus::VersionNotifier {
origin: origin.clone().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(origin, v));
0
},
(response, Some(QueryStatus::Pending { responder, maybe_notify, .. })) => {
let responder = match MultiLocation::try_from(responder) {
Ok(r) => r,
Err(_) => {
Self::deposit_event(Event::InvalidResponderVersion(
origin.clone(),
query_id,
));
return 0
},
};
if origin != &responder {
Self::deposit_event(Event::InvalidResponder(
origin.clone(),
query_id,
Some(responder),
));
return 0
}
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;
let max_weight = Weight::from_ref_time(max_weight);
if weight.any_gt(max_weight) {
let e = Event::NotifyOverweight(
Gavin Wood
committed
query_id,
pallet_index,
call_index,
Gavin Wood
committed
);
Self::deposit_event(e);
Gavin Wood
committed
}
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);
Gavin Wood
committed
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
},
Gavin Wood
committed
}
},
_ => {
Self::deposit_event(Event::UnexpectedResponse(origin.clone(), query_id));
return 0
},
Gavin Wood
committed
}
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())
/// `EnsureOrigin` implementation succeeding with a `MultiLocation` value to recognize and filter the
/// `Origin::Xcm` item.
pub struct EnsureXcm<F>(PhantomData<F>);
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.
pub struct XcmPassthrough<Origin>(PhantomData<Origin>);
impl<Origin: From<crate::Origin>> ConvertOrigin<Origin> for XcmPassthrough<Origin> {
Keith Yeung
committed
fn convert_origin(
origin: impl Into<MultiLocation>,
kind: OriginKind,
) -> Result<Origin, MultiLocation> {
let origin = origin.into();
match kind {
OriginKind::Xcm => Ok(crate::Origin::Xcm(origin).into()),
_ => Err(origin),