Newer
Older
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
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 >= 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(&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 }]);
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
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
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
}
}
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
/// 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) {
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(())
.and_then(|v| xcm.into().into_version(v.min(XCM_VERSION)))
}
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
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(())
}
}
impl<T: Config> DropAssets for Pallet<T> {
fn drop_assets(origin: &MultiLocation, assets: Assets) -> Weight {
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.
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
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: Weight,
) -> Weight {
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
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
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;
if weight > 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
}
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
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 successful_origin() -> O {
O::from(Origin::Xcm(Here.into()))
Gavin Wood
committed
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
/// `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>);
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),