lib.rs 30.2 KB
Newer Older
Gavin Wood's avatar
Gavin Wood committed
1
// Copyright 2020-2021 Parity Technologies (UK) Ltd.
2
// This file is part of Polkadot.
Gavin Wood's avatar
Gavin Wood committed
3

4
// Polkadot is free software: you can redistribute it and/or modify
Gavin Wood's avatar
Gavin Wood committed
5
6
7
8
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

9
// Polkadot is distributed in the hope that it will be useful,
Gavin Wood's avatar
Gavin Wood committed
10
11
12
13
14
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
15
// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
Gavin Wood's avatar
Gavin Wood committed
16
17
18
19
20

//! Pallet to handle XCM messages.

#![cfg_attr(not(feature = "std"), no_std)]

21
22
23
24
25
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;

Gavin Wood's avatar
Gavin Wood committed
26
use codec::{Decode, Encode};
27
use frame_support::traits::{Contains, EnsureOrigin, Get, OriginTrait};
Shawn Tabrizi's avatar
Shawn Tabrizi committed
28
use sp_runtime::{traits::BadOrigin, RuntimeDebug};
29
30
31
32
33
use sp_std::{
	boxed::Box,
	convert::{TryFrom, TryInto},
	marker::PhantomData,
	prelude::*,
34
	result::Result,
35
36
	vec,
};
37
38
39
40
use xcm::{
	latest::prelude::*, VersionedMultiAssets, VersionedMultiLocation, VersionedResponse,
	VersionedXcm,
};
41
use xcm_executor::traits::ConvertOrigin;
Gavin Wood's avatar
Gavin Wood committed
42

43
use frame_support::PalletId;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
44
pub use pallet::*;
Gavin Wood's avatar
Gavin Wood committed
45
46
47
48

#[frame_support::pallet]
pub mod pallet {
	use super::*;
49
50
51
52
53
	use frame_support::{
		dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo},
		pallet_prelude::*,
	};
	use frame_system::{pallet_prelude::*, Config as SysConfig};
54
55
56
57
58
59
	use sp_core::H256;
	use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, BlockNumberProvider, Hash};
	use xcm_executor::{
		traits::{ClaimAssets, DropAssets, InvertLocation, OnResponse, WeightBounds},
		Assets,
	};
Gavin Wood's avatar
Gavin Wood committed
60
61
62
63
64
65
66
67
68
69
70
71
72

	#[pallet::pallet]
	#[pallet::generate_store(pub(super) trait Store)]
	pub struct Pallet<T>(_);

	#[pallet::config]
	/// The module configuration trait.
	pub trait Config: frame_system::Config {
		/// The overarching event type.
		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;

		/// Required origin for sending XCM messages. If successful, the it resolves to `MultiLocation`
		/// which exists as an interior location within this chain's XCM context.
73
		type SendXcmOrigin: EnsureOrigin<<Self as SysConfig>::Origin, Success = MultiLocation>;
Gavin Wood's avatar
Gavin Wood committed
74
75
76
77

		/// The type used to actually dispatch an XCM to its destination.
		type XcmRouter: SendXcm;

Shaun Wang's avatar
Shaun Wang committed
78
79
		/// Required origin for executing XCM messages, including the teleport functionality. If successful,
		/// then it resolves to `MultiLocation` which exists as an interior location within this chain's XCM
80
		/// context.
81
		type ExecuteXcmOrigin: EnsureOrigin<<Self as SysConfig>::Origin, Success = MultiLocation>;
Gavin Wood's avatar
Gavin Wood committed
82

83
		/// Our XCM filter which messages to be executed using `XcmExecutor` must pass.
84
		type XcmExecuteFilter: Contains<(MultiLocation, Xcm<<Self as SysConfig>::Call>)>;
85

Gavin Wood's avatar
Gavin Wood committed
86
		/// Something to execute an XCM message.
87
		type XcmExecutor: ExecuteXcm<<Self as SysConfig>::Call>;
88

89
		/// Our XCM filter which messages to be teleported using the dedicated extrinsic must pass.
90
91
		type XcmTeleportFilter: Contains<(MultiLocation, Vec<MultiAsset>)>;

92
93
94
		/// Our XCM filter which messages to be reserve-transferred using the dedicated extrinsic must pass.
		type XcmReserveTransferFilter: Contains<(MultiLocation, Vec<MultiAsset>)>;

95
		/// Means of measuring the weight consumed by an XCM message locally.
96
		type Weigher: WeightBounds<<Self as SysConfig>::Call>;
97
98
99

		/// Means of inverting a location.
		type LocationInverter: InvertLocation;
100
101
102
103
104
105
106
107
108

		/// The outer `Origin` type.
		type Origin: From<Origin> + From<<Self as SysConfig>::Origin>;

		/// The outer `Call` type.
		type Call: Parameter
			+ GetDispatchInfo
			+ IsType<<Self as frame_system::Config>::Call>
			+ Dispatchable<Origin = <Self as Config>::Origin, PostInfo = PostDispatchInfo>;
Gavin Wood's avatar
Gavin Wood committed
109
110
	}

111
112
113
	/// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic.
	const MAX_ASSETS_FOR_TRANSFER: usize = 2;

Gavin Wood's avatar
Gavin Wood committed
114
115
116
	#[pallet::event]
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
	pub enum Event<T: Config> {
117
118
119
		/// Execution of an XCM message was attempted.
		///
		/// \[ outcome \]
Gavin Wood's avatar
Gavin Wood committed
120
		Attempted(xcm::latest::Outcome),
121
122
123
		/// A XCM message was sent.
		///
		/// \[ origin, destination, message \]
124
		Sent(MultiLocation, MultiLocation, Xcm<()>),
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
		/// Query response received which does not match a registered query. This may be because a
		/// matching query was never registered, it may be because it is a duplicate response, or
		/// because the query timed out.
		///
		/// \[ origin location, id \]
		UnexpectedResponse(MultiLocation, QueryId),
		/// Query response has been received and is ready for taking with `take_response`. There is
		/// no registered notification call.
		///
		/// \[ id, response \]
		ResponseReady(QueryId, Response),
		/// Query response has been received and query is removed. The registered notification has
		/// been dispatched and executed successfully.
		///
		/// \[ id, pallet index, call index \]
		Notified(QueryId, u8, u8),
		/// Query response has been received and query is removed. The registered notification could
		/// not be dispatched because the dispatch weight is greater than the maximum weight
		/// originally budgeted by this runtime for the query result.
		///
		/// \[ id, pallet index, call index, actual weight, max budgeted weight \]
		NotifyOverweight(QueryId, u8, u8, Weight, Weight),
		/// Query response has been received and query is removed. There was a general error with
		/// dispatching the notification call.
		///
		/// \[ id, pallet index, call index \]
		NotifyDispatchError(QueryId, u8, u8),
		/// Query response has been received and query is removed. The dispatch was unable to be
		/// decoded into a `Call`; this might be due to dispatch function having a signature which
		/// is not `(origin, QueryId, Response)`.
		///
		/// \[ id, pallet index, call index \]
		NotifyDecodeFailed(QueryId, u8, u8),
		/// Expected query response has been received but the origin location of the response does
		/// not match that expected. The query remains registered for a later, valid, response to
		/// be received and acted upon.
		///
		/// \[ origin location, id, expected location \]
		InvalidResponder(MultiLocation, QueryId, MultiLocation),
		/// Expected query response has been received but the expected origin location placed in
		/// storate by this runtime previously cannot be decoded. The query remains registered.
		///
		/// This is unexpected (since a location placed in storage in a previously executing
		/// runtime should be readable prior to query timeout) and dangerous since the possibly
		/// valid response will be dropped. Manual governance intervention is probably going to be
		/// needed.
		///
		/// \[ origin location, id \]
		InvalidResponderVersion(MultiLocation, QueryId),
		/// Received query response has been read and removed.
		///
		/// \[ id \]
		ResponseTaken(QueryId),
178
179
180
181
		/// Some assets have been placed in an asset trap.
		///
		/// \[ hash, origin, assets \]
		AssetsTrapped(H256, MultiLocation, VersionedMultiAssets),
182
183
184
185
186
187
188
189
190
191
192
193
194
195
	}

	#[pallet::origin]
	#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)]
	pub enum Origin {
		/// It comes from somewhere in the XCM space wanting to transact.
		Xcm(MultiLocation),
		/// It comes as an expected response from an XCM location.
		Response(MultiLocation),
	}
	impl From<MultiLocation> for Origin {
		fn from(location: MultiLocation) -> Origin {
			Origin::Xcm(location)
		}
Gavin Wood's avatar
Gavin Wood committed
196
197
198
199
	}

	#[pallet::error]
	pub enum Error<T> {
200
201
		/// The desired destination was unreachable, generally because there is a no way of routing
		/// to it.
Gavin Wood's avatar
Gavin Wood committed
202
		Unreachable,
203
204
		/// There was some other issue (i.e. not to do with routing) in sending the message. Perhaps
		/// a lack of space for buffering the message.
Gavin Wood's avatar
Gavin Wood committed
205
		SendFailure,
206
207
		/// The message execution fails the filter.
		Filtered,
208
209
		/// The message's weight could not be determined.
		UnweighableMessage,
210
211
		/// The destination `MultiLocation` provided cannot be inverted.
		DestinationNotInvertible,
212
213
		/// The assets to be sent are empty.
		Empty,
214
		/// Could not re-anchor the assets to declare the fees for the destination chain.
215
		CannotReanchor,
216
217
		/// Too many assets have been attempted for transfer.
		TooManyAssets,
Gavin Wood's avatar
Gavin Wood committed
218
219
		/// Origin is invalid for sending.
		InvalidOrigin,
220
221
		/// The version of the `Versioned` value used is not able to be interpreted.
		BadVersion,
Gavin Wood's avatar
Gavin Wood committed
222
223
	}

224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
	/// The status of a query.
	#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)]
	pub enum QueryStatus<BlockNumber> {
		/// The query was sent but no response has yet been received.
		Pending {
			responder: VersionedMultiLocation,
			maybe_notify: Option<(u8, u8)>,
			timeout: BlockNumber,
		},
		/// A response has been received.
		Ready { response: VersionedResponse, at: BlockNumber },
	}

	/// Value of a query, must be unique for each query.
	pub type QueryId = u64;

	/// The latest available query index.
	#[pallet::storage]
	pub(super) type QueryCount<T: Config> = StorageValue<_, QueryId, ValueQuery>;

	/// The ongoing queries.
	#[pallet::storage]
	#[pallet::getter(fn query)]
	pub(super) type Queries<T: Config> =
		StorageMap<_, Blake2_128Concat, QueryId, QueryStatus<T::BlockNumber>, OptionQuery>;

250
251
252
253
254
255
256
257
	/// The existing asset traps.
	///
	/// Key is the blake2 256 hash of (origin, versioned `MultiAssets`) pair. Value is the number of
	/// times this pair has been trapped (usually just 1 if it exists at all).
	#[pallet::storage]
	#[pallet::getter(fn asset_trap)]
	pub(super) type AssetTraps<T: Config> = StorageMap<_, Identity, H256, u32, ValueQuery>;

Gavin Wood's avatar
Gavin Wood committed
258
259
260
261
262
	#[pallet::hooks]
	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}

	#[pallet::call]
	impl<T: Config> Pallet<T> {
263
		#[pallet::weight(100_000_000)]
264
265
		pub fn send(
			origin: OriginFor<T>,
266
267
			dest: Box<VersionedMultiLocation>,
			message: Box<VersionedXcm<()>>,
268
		) -> DispatchResult {
Gavin Wood's avatar
Gavin Wood committed
269
			let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
Gavin Wood's avatar
Gavin Wood committed
270
271
			let interior =
				origin_location.clone().try_into().map_err(|_| Error::<T>::InvalidOrigin)?;
272
273
274
275
			let dest = MultiLocation::try_from(*dest).map_err(|()| Error::<T>::BadVersion)?;
			let message: Xcm<()> = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;

			Self::send_xcm(interior, dest.clone(), message.clone()).map_err(|e| match e {
276
				SendError::CannotReachDestination(..) => Error::<T>::Unreachable,
Gavin Wood's avatar
Gavin Wood committed
277
278
				_ => Error::<T>::SendFailure,
			})?;
279
			Self::deposit_event(Event::Sent(origin_location, dest, message));
Gavin Wood's avatar
Gavin Wood committed
280
281
282
			Ok(())
		}

283
284
		/// Teleport some assets from the local chain to some destination chain.
		///
285
286
		/// Fee payment on the destination side is made from the first asset listed in the `assets` vector.
		///
287
288
289
290
291
		/// - `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.
Gavin Wood's avatar
Gavin Wood committed
292
293
		/// - `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.
294
295
296
		/// - `dest_weight`: Equal to the total weight on `dest` of the XCM message
		///   `Teleport { assets, effects: [ BuyExecution{..}, DepositAsset{..} ] }`.
		#[pallet::weight({
297
298
299
300
			let maybe_assets: Result<MultiAssets, ()> = (*assets.clone()).try_into();
			let maybe_dest: Result<MultiLocation, ()> = (*dest.clone()).try_into();
			match (maybe_assets, maybe_dest) {
				(Ok(assets), Ok(dest)) => {
301
302
303
304
305
					use sp_std::vec;
					let mut message = Xcm(vec![
						WithdrawAsset(assets),
						InitiateTeleport { assets: Wild(All), dest, xcm: Xcm(vec![]) },
					]);
306
307
308
309
					T::Weigher::weight(&mut message).map_or(Weight::max_value(), |w| 100_000_000 + w)
				},
				_ => Weight::max_value(),
			}
310
		})]
311
		pub fn teleport_assets(
312
			origin: OriginFor<T>,
313
314
315
			dest: Box<VersionedMultiLocation>,
			beneficiary: Box<VersionedMultiLocation>,
			assets: Box<VersionedMultiAssets>,
Gavin Wood's avatar
Gavin Wood committed
316
			fee_asset_item: u32,
317
318
		) -> DispatchResult {
			let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
319
320
321
322
323
			let dest = MultiLocation::try_from(*dest).map_err(|()| Error::<T>::BadVersion)?;
			let beneficiary =
				MultiLocation::try_from(*beneficiary).map_err(|()| Error::<T>::BadVersion)?;
			let assets = MultiAssets::try_from(*assets).map_err(|()| Error::<T>::BadVersion)?;

324
			ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
Gavin Wood's avatar
Gavin Wood committed
325
			let value = (origin_location, assets.drain());
326
327
			ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
			let (origin_location, assets) = value;
328
329
			let inv_dest = T::LocationInverter::invert_location(&dest)
				.map_err(|()| Error::<T>::DestinationNotInvertible)?;
Gavin Wood's avatar
Gavin Wood committed
330
331
332
333
334
335
336
337
			let fees = assets
				.get(fee_asset_item as usize)
				.ok_or(Error::<T>::Empty)?
				.clone()
				.reanchored(&inv_dest)
				.map_err(|_| Error::<T>::CannotReanchor)?;
			let max_assets = assets.len() as u32;
			let assets = assets.into();
338
339
340
			let mut message = Xcm(vec![
				WithdrawAsset(assets),
				InitiateTeleport {
Gavin Wood's avatar
Gavin Wood committed
341
					assets: Wild(All),
342
					dest,
343
344
					xcm: Xcm(vec![
						BuyExecution { fees, weight_limit: Unlimited },
345
						DepositAsset { assets: Wild(All), max_assets, beneficiary },
346
347
348
					]),
				},
			]);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
349
350
351
352
			let weight =
				T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
			let outcome =
				T::XcmExecutor::execute_xcm_in_credit(origin_location, message, weight, weight);
353
354
355
356
			Self::deposit_event(Event::Attempted(outcome));
			Ok(())
		}

357
358
359
		/// Transfer some assets from the local chain to the sovereign account of a destination chain and forward
		/// a notification XCM.
		///
360
361
		/// Fee payment on the destination side is made from the first asset listed in the `assets` vector.
		///
362
363
364
365
366
367
368
		/// - `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.
369
370
		/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
		///   fees.
371
		#[pallet::weight({
372
373
			match ((*assets.clone()).try_into(), (*dest.clone()).try_into()) {
				(Ok(assets), Ok(dest)) => {
374
375
376
377
					use sp_std::vec;
					let mut message = Xcm(vec![
						TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) }
					]);
378
379
380
381
					T::Weigher::weight(&mut message).map_or(Weight::max_value(), |w| 100_000_000 + w)
				},
				_ => Weight::max_value(),
			}
382
		})]
383
		pub fn reserve_transfer_assets(
384
			origin: OriginFor<T>,
385
386
387
			dest: Box<VersionedMultiLocation>,
			beneficiary: Box<VersionedMultiLocation>,
			assets: Box<VersionedMultiAssets>,
Gavin Wood's avatar
Gavin Wood committed
388
			fee_asset_item: u32,
389
390
		) -> DispatchResult {
			let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
391
392
393
394
			let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let beneficiary = (*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;

395
			ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
Gavin Wood's avatar
Gavin Wood committed
396
			let value = (origin_location, assets.drain());
397
398
			ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
			let (origin_location, assets) = value;
399
400
			let inv_dest = T::LocationInverter::invert_location(&dest)
				.map_err(|()| Error::<T>::DestinationNotInvertible)?;
Gavin Wood's avatar
Gavin Wood committed
401
402
403
404
405
406
407
408
			let fees = assets
				.get(fee_asset_item as usize)
				.ok_or(Error::<T>::Empty)?
				.clone()
				.reanchored(&inv_dest)
				.map_err(|_| Error::<T>::CannotReanchor)?;
			let max_assets = assets.len() as u32;
			let assets = assets.into();
409
			let mut message = Xcm(vec![TransferReserveAsset {
410
				assets,
411
				dest,
412
413
				xcm: Xcm(vec![
					BuyExecution { fees, weight_limit: Unlimited },
414
					DepositAsset { assets: Wild(All), max_assets, beneficiary },
415
416
				]),
			}]);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
417
418
419
420
			let weight =
				T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
			let outcome =
				T::XcmExecutor::execute_xcm_in_credit(origin_location, message, weight, weight);
421
422
423
424
			Self::deposit_event(Event::Attempted(outcome));
			Ok(())
		}

Gavin Wood's avatar
Gavin Wood committed
425
426
427
428
429
430
431
432
433
434
435
		/// 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.
436
		#[pallet::weight(max_weight.saturating_add(100_000_000u64))]
Shawn Tabrizi's avatar
Shawn Tabrizi committed
437
438
		pub fn execute(
			origin: OriginFor<T>,
439
			message: Box<VersionedXcm<<T as SysConfig>::Call>>,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
440
441
			max_weight: Weight,
		) -> DispatchResult {
Gavin Wood's avatar
Gavin Wood committed
442
			let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
443
444
			let message = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let value = (origin_location, message);
445
446
447
			ensure!(T::XcmExecuteFilter::contains(&value), Error::<T>::Filtered);
			let (origin_location, message) = value;
			let outcome = T::XcmExecutor::execute_xcm(origin_location, message, max_weight);
Gavin Wood's avatar
Gavin Wood committed
448
449
450
451
452
453
454
455
			Self::deposit_event(Event::Attempted(outcome));
			Ok(())
		}
	}

	impl<T: Config> Pallet<T> {
		/// Relay an XCM `message` from a given `interior` location in this context to a given `dest`
		/// location. A null `dest` is not handled.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
456
		pub fn send_xcm(
Gavin Wood's avatar
Gavin Wood committed
457
			interior: Junctions,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
458
			dest: MultiLocation,
459
460
461
462
			mut message: Xcm<()>,
		) -> Result<(), SendError> {
			if interior != Junctions::Here {
				message.0.insert(0, DescendOrigin(interior))
Gavin Wood's avatar
Gavin Wood committed
463
			};
Alexander Popiak's avatar
Alexander Popiak committed
464
			log::trace!(target: "xcm::send_xcm", "dest: {:?}, message: {:?}", &dest, &message);
Gavin Wood's avatar
Gavin Wood committed
465
466
			T::XcmRouter::send_xcm(dest, message)
		}
467
468
469
470
471

		pub fn check_account() -> T::AccountId {
			const ID: PalletId = PalletId(*b"py/xcmch");
			AccountIdConversion::<T::AccountId>::into_account(&ID)
		}
Gavin Wood's avatar
Gavin Wood committed
472

473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
		fn do_new_query(
			responder: MultiLocation,
			maybe_notify: Option<(u8, u8)>,
			timeout: T::BlockNumber,
		) -> u64 {
			QueryCount::<T>::mutate(|q| {
				let r = *q;
				*q += 1;
				Queries::<T>::insert(
					r,
					QueryStatus::Pending { responder: responder.into(), maybe_notify, timeout },
				);
				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.
		///
497
498
		/// `report_outcome` may return an error if the `responder` is not invertible.
		///
499
500
501
502
503
504
		/// To check the status of the query, use `fn query()` passing the resultant `QueryId`
		/// value.
		pub fn report_outcome(
			message: &mut Xcm<()>,
			responder: MultiLocation,
			timeout: T::BlockNumber,
505
506
507
		) -> Result<QueryId, XcmError> {
			let dest = T::LocationInverter::invert_location(&responder)
				.map_err(|()| XcmError::MultiLocationNotInvertible)?;
508
509
510
			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));
511
			Ok(query_id)
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
		}

		/// 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.
		///
527
528
		/// `report_outcome_notify` may return an error if the `responder` is not invertible.
		///
529
530
531
532
533
534
535
536
537
538
		/// 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: MultiLocation,
			notify: impl Into<<T as Config>::Call>,
			timeout: T::BlockNumber,
539
540
541
		) -> Result<(), XcmError> {
			let dest = T::LocationInverter::invert_location(&responder)
				.map_err(|()| XcmError::MultiLocationNotInvertible)?;
542
543
544
545
546
			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));
547
			Ok(())
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
		}

		/// Attempt to create a new query ID and register it as a query that is yet to respond.
		pub fn new_query(responder: MultiLocation, timeout: T::BlockNumber) -> u64 {
			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(
			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
			}
		}
Shawn Tabrizi's avatar
Shawn Tabrizi committed
582
	}
Gavin Wood's avatar
Gavin Wood committed
583

584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
	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: Put the real weight in there.
			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
		}
	}

624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
	impl<T: Config> OnResponse for Pallet<T> {
		fn expecting_response(origin: &MultiLocation, query_id: QueryId) -> bool {
			if let Some(QueryStatus::Pending { responder, .. }) = Queries::<T>::get(query_id) {
				return MultiLocation::try_from(responder).map_or(false, |r| origin == &r)
			}
			false
		}

		fn on_response(
			origin: &MultiLocation,
			query_id: QueryId,
			response: Response,
			max_weight: Weight,
		) -> Weight {
			if let Some(QueryStatus::Pending { responder, maybe_notify, .. }) =
				Queries::<T>::get(query_id)
			{
				if let Ok(responder) = MultiLocation::try_from(responder) {
					if origin == &responder {
						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(
											query_id,
											pallet_index,
											call_index,
											weight,
											max_weight,
										);
										Self::deposit_event(e);
										return 0
									}
									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,
									);
									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
							},
						}
					} else {
						Self::deposit_event(Event::InvalidResponder(
							origin.clone(),
							query_id,
							responder,
						));
					}
				} else {
					Self::deposit_event(Event::InvalidResponderVersion(origin.clone(), query_id));
				}
			} else {
				Self::deposit_event(Event::UnexpectedResponse(origin.clone(), query_id));
			}
			0
Shawn Tabrizi's avatar
Shawn Tabrizi committed
719
		}
Gavin Wood's avatar
Gavin Wood committed
720
721
722
	}
}

723
724
725
/// Ensure that the origin `o` represents an XCM (`Transact`) origin.
///
/// Returns `Ok` with the location of the XCM sender or an `Err` otherwise.
Gavin Wood's avatar
Gavin Wood committed
726
pub fn ensure_xcm<OuterOrigin>(o: OuterOrigin) -> Result<MultiLocation, BadOrigin>
Shawn Tabrizi's avatar
Shawn Tabrizi committed
727
728
where
	OuterOrigin: Into<Result<Origin, OuterOrigin>>,
Gavin Wood's avatar
Gavin Wood committed
729
730
731
732
733
734
735
{
	match o.into() {
		Ok(Origin::Xcm(location)) => Ok(location),
		_ => Err(BadOrigin),
	}
}

736
737
738
739
740
741
742
743
744
745
746
747
748
/// 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),
	}
}

Gavin Wood's avatar
Gavin Wood committed
749
750
751
752
/// Filter for `MultiLocation` to find those which represent a strict majority approval of an identified
/// plurality.
///
/// May reasonably be used with `EnsureXcm`.
753
pub struct IsMajorityOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
754
impl<Prefix: Get<MultiLocation>, Body: Get<BodyId>> Contains<MultiLocation>
Shawn Tabrizi's avatar
Shawn Tabrizi committed
755
756
	for IsMajorityOfBody<Prefix, Body>
{
757
	fn contains(l: &MultiLocation) -> bool {
758
759
		let maybe_suffix = l.match_and_split(&Prefix::get());
		matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part.is_majority())
Gavin Wood's avatar
Gavin Wood committed
760
761
762
	}
}

Shaun Wang's avatar
Shaun Wang committed
763
/// `EnsureOrigin` implementation succeeding with a `MultiLocation` value to recognize and filter the
Gavin Wood's avatar
Gavin Wood committed
764
765
/// `Origin::Xcm` item.
pub struct EnsureXcm<F>(PhantomData<F>);
766
impl<O: OriginTrait + From<Origin>, F: Contains<MultiLocation>> EnsureOrigin<O> for EnsureXcm<F>
Shawn Tabrizi's avatar
Shawn Tabrizi committed
767
768
where
	O::PalletsOrigin: From<Origin> + TryInto<Origin, Error = O::PalletsOrigin>,
Gavin Wood's avatar
Gavin Wood committed
769
770
771
772
{
	type Success = MultiLocation;

	fn try_origin(outer: O) -> Result<Self::Success, O> {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
773
		outer.try_with_caller(|caller| {
774
775
776
777
			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()),
Shawn Tabrizi's avatar
Shawn Tabrizi committed
778
779
			})
		})
Gavin Wood's avatar
Gavin Wood committed
780
781
782
783
	}

	#[cfg(feature = "runtime-benchmarks")]
	fn successful_origin() -> O {
784
		O::from(Origin::Xcm(Here.into()))
Gavin Wood's avatar
Gavin Wood committed
785
786
	}
}
787

788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
/// `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()))
	}
}

813
814
815
/// 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>);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
816
impl<Origin: From<crate::Origin>> ConvertOrigin<Origin> for XcmPassthrough<Origin> {
817
	fn convert_origin(origin: MultiLocation, kind: OriginKind) -> Result<Origin, MultiLocation> {
818
819
820
		match kind {
			OriginKind::Xcm => Ok(crate::Origin::Xcm(origin).into()),
			_ => Err(origin),
821
822
823
		}
	}
}