Newer
Older
Gavin Wood
committed
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
/// which will call a dispatchable when a response happens.
pub fn new_notify_query(
responder: MultiLocation,
notify: impl Into<<T as Config>::Call>,
timeout: T::BlockNumber,
) -> u64 {
let notify =
notify.into().using_encoded(|mut bytes| Decode::decode(&mut bytes)).expect(
"decode input is output of Call encode; Call guaranteed to have two enums; qed",
);
Self::do_new_query(responder, Some(notify), timeout)
}
/// Attempt to remove and return the response of query with ID `query_id`.
///
/// Returns `None` if the response is not (yet) available.
pub fn take_response(query_id: QueryId) -> Option<(Response, T::BlockNumber)> {
if let Some(QueryStatus::Ready { response, at }) = Queries::<T>::get(query_id) {
let response = response.try_into().ok()?;
Queries::<T>::remove(query_id);
Self::deposit_event(Event::ResponseTaken(query_id));
Some((response, at))
} else {
None
}
}
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
/// 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)))
}
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
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.
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
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 {
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
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
1220
1221
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
}
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
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
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
/// `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> {
fn convert_origin(origin: MultiLocation, kind: OriginKind) -> Result<Origin, MultiLocation> {
match kind {
OriginKind::Xcm => Ok(crate::Origin::Xcm(origin).into()),
_ => Err(origin),