lib.rs 14.3 KB
Newer Older
Gavin Wood's avatar
Gavin Wood committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Copyright 2020-2021 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.

// Cumulus is free software: you can redistribute it and/or modify
// 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.

// Cumulus is distributed in the hope that it will be useful,
// 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
// along with Cumulus.  If not, see <http://www.gnu.org/licenses/>.

//! 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
29
use sp_runtime::{traits::BadOrigin, RuntimeDebug};
use sp_std::{boxed::Box, convert::TryInto, marker::PhantomData, prelude::*, vec};
Gavin Wood's avatar
Gavin Wood committed
30
use xcm::latest::prelude::*;
31
use xcm_executor::traits::ConvertOrigin;
Gavin Wood's avatar
Gavin Wood committed
32

33
use frame_support::PalletId;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
34
pub use pallet::*;
Gavin Wood's avatar
Gavin Wood committed
35
36
37
38
39
40

#[frame_support::pallet]
pub mod pallet {
	use super::*;
	use frame_support::pallet_prelude::*;
	use frame_system::pallet_prelude::*;
41
	use sp_runtime::traits::AccountIdConversion;
42
	use xcm_executor::traits::{InvertLocation, WeightBounds};
Gavin Wood's avatar
Gavin Wood committed
43
44
45
46
47
48
49
50
51
52
53
54
55

	#[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.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
56
		type SendXcmOrigin: EnsureOrigin<Self::Origin, Success = MultiLocation>;
Gavin Wood's avatar
Gavin Wood committed
57
58
59
60

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

Shaun Wang's avatar
Shaun Wang committed
61
62
		/// 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
63
		/// context.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
64
		type ExecuteXcmOrigin: EnsureOrigin<Self::Origin, Success = MultiLocation>;
Gavin Wood's avatar
Gavin Wood committed
65

66
67
68
		/// Our XCM filter which messages to be executed using `XcmExecutor` must pass.
		type XcmExecuteFilter: Contains<(MultiLocation, Xcm<Self::Call>)>;

Gavin Wood's avatar
Gavin Wood committed
69
70
		/// Something to execute an XCM message.
		type XcmExecutor: ExecuteXcm<Self::Call>;
71

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

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

78
79
		/// Means of measuring the weight consumed by an XCM message locally.
		type Weigher: WeightBounds<Self::Call>;
80
81
82

		/// Means of inverting a location.
		type LocationInverter: InvertLocation;
Gavin Wood's avatar
Gavin Wood committed
83
84
	}

85
86
87
	/// 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
88
89
90
	#[pallet::event]
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
	pub enum Event<T: Config> {
Gavin Wood's avatar
Gavin Wood committed
91
		Attempted(xcm::latest::Outcome),
92
		Sent(MultiLocation, MultiLocation, Xcm<()>),
Gavin Wood's avatar
Gavin Wood committed
93
94
95
96
97
98
	}

	#[pallet::error]
	pub enum Error<T> {
		Unreachable,
		SendFailure,
99
100
		/// The message execution fails the filter.
		Filtered,
101
102
		/// The message's weight could not be determined.
		UnweighableMessage,
103
104
105
106
		/// The assets to be sent are empty.
		Empty,
		/// Could not reanchor the assets to declare the fees for the destination chain.
		CannotReanchor,
107
108
		/// Too many assets have been attempted for transfer.
		TooManyAssets,
Gavin Wood's avatar
Gavin Wood committed
109
110
111
112
113
114
115
	}

	#[pallet::hooks]
	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}

	#[pallet::call]
	impl<T: Config> Pallet<T> {
116
		#[pallet::weight(100_000_000)]
117
118
119
120
121
		pub fn send(
			origin: OriginFor<T>,
			dest: Box<MultiLocation>,
			message: Box<Xcm<()>>,
		) -> DispatchResult {
Gavin Wood's avatar
Gavin Wood committed
122
			let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
123
			Self::send_xcm(origin_location.clone(), *dest.clone(), *message.clone()).map_err(
Shawn Tabrizi's avatar
Shawn Tabrizi committed
124
				|e| match e {
Gavin Wood's avatar
Gavin Wood committed
125
126
					XcmError::CannotReachDestination(..) => Error::<T>::Unreachable,
					_ => Error::<T>::SendFailure,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
127
128
				},
			)?;
129
			Self::deposit_event(Event::Sent(origin_location, *dest, *message));
Gavin Wood's avatar
Gavin Wood committed
130
131
132
			Ok(())
		}

133
134
		/// Teleport some assets from the local chain to some destination chain.
		///
135
136
		/// Fee payment on the destination side is made from the first asset listed in the `assets` vector.
		///
137
138
139
140
141
		/// - `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
142
143
		/// - `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.
144
145
146
147
148
149
		/// - `dest_weight`: Equal to the total weight on `dest` of the XCM message
		///   `Teleport { assets, effects: [ BuyExecution{..}, DepositAsset{..} ] }`.
		#[pallet::weight({
			let mut message = Xcm::WithdrawAsset {
				assets: assets.clone(),
				effects: sp_std::vec![ InitiateTeleport {
Gavin Wood's avatar
Gavin Wood committed
150
					assets: Wild(All),
151
					dest: *dest.clone(),
152
153
154
155
156
					effects: sp_std::vec![],
				} ]
			};
			T::Weigher::weight(&mut message).map_or(Weight::max_value(), |w| 100_000_000 + w)
		})]
157
		pub fn teleport_assets(
158
			origin: OriginFor<T>,
159
160
			dest: Box<MultiLocation>,
			beneficiary: Box<MultiLocation>,
Gavin Wood's avatar
Gavin Wood committed
161
162
			assets: MultiAssets,
			fee_asset_item: u32,
163
164
165
			dest_weight: Weight,
		) -> DispatchResult {
			let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
166
			ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
Gavin Wood's avatar
Gavin Wood committed
167
			let value = (origin_location, assets.drain());
168
169
			ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
			let (origin_location, assets) = value;
170
			let inv_dest = T::LocationInverter::invert_location(&dest);
Gavin Wood's avatar
Gavin Wood committed
171
172
173
174
175
176
177
178
			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();
179
180
			let mut message = Xcm::WithdrawAsset {
				assets,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
181
				effects: vec![InitiateTeleport {
Gavin Wood's avatar
Gavin Wood committed
182
					assets: Wild(All),
183
					dest: *dest,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
184
185
					effects: vec![
						BuyExecution {
186
							fees,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
187
188
189
190
							// Zero weight for additional XCM (since there are none to execute)
							weight: 0,
							debt: dest_weight,
							halt_on_error: false,
Gavin Wood's avatar
Gavin Wood committed
191
192
							orders: vec![],
							instructions: vec![],
Shawn Tabrizi's avatar
Shawn Tabrizi committed
193
						},
194
						DepositAsset { assets: Wild(All), max_assets, beneficiary: *beneficiary },
Shawn Tabrizi's avatar
Shawn Tabrizi committed
195
196
					],
				}],
197
			};
Shawn Tabrizi's avatar
Shawn Tabrizi committed
198
199
200
201
			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);
202
203
204
205
			Self::deposit_event(Event::Attempted(outcome));
			Ok(())
		}

206
207
208
		/// Transfer some assets from the local chain to the sovereign account of a destination chain and forward
		/// a notification XCM.
		///
209
210
		/// Fee payment on the destination side is made from the first asset listed in the `assets` vector.
		///
211
212
213
214
215
216
217
218
		/// - `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.
		/// - `dest_weight`: Equal to the total weight on `dest` of the XCM message
Gavin Wood's avatar
Gavin Wood committed
219
		///   `ReserveAssetDeposited { assets, effects: [ BuyExecution{..}, DepositAsset{..} ] }`.
220
221
222
		#[pallet::weight({
			let mut message = Xcm::TransferReserveAsset {
				assets: assets.clone(),
223
				dest: *dest.clone(),
224
225
226
227
				effects: sp_std::vec![],
			};
			T::Weigher::weight(&mut message).map_or(Weight::max_value(), |w| 100_000_000 + w)
		})]
228
		pub fn reserve_transfer_assets(
229
			origin: OriginFor<T>,
230
231
			dest: Box<MultiLocation>,
			beneficiary: Box<MultiLocation>,
Gavin Wood's avatar
Gavin Wood committed
232
233
			assets: MultiAssets,
			fee_asset_item: u32,
234
235
236
			dest_weight: Weight,
		) -> DispatchResult {
			let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
237
			ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
Gavin Wood's avatar
Gavin Wood committed
238
			let value = (origin_location, assets.drain());
239
240
			ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
			let (origin_location, assets) = value;
241
			let inv_dest = T::LocationInverter::invert_location(&dest);
Gavin Wood's avatar
Gavin Wood committed
242
243
244
245
246
247
248
249
			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();
250
251
			let mut message = Xcm::TransferReserveAsset {
				assets,
252
				dest: *dest,
253
254
				effects: vec![
					BuyExecution {
255
						fees,
Gavin Wood's avatar
Gavin Wood committed
256
						// Zero weight for additional instructions/orders (since there are none to execute)
257
						weight: 0,
Gavin Wood's avatar
Gavin Wood committed
258
						debt: dest_weight, // covers this, `TransferReserveAsset` xcm, and `DepositAsset` order.
259
						halt_on_error: false,
Gavin Wood's avatar
Gavin Wood committed
260
261
						orders: vec![],
						instructions: vec![],
262
					},
263
					DepositAsset { assets: Wild(All), max_assets, beneficiary: *beneficiary },
264
265
				],
			};
Shawn Tabrizi's avatar
Shawn Tabrizi committed
266
267
268
269
			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);
270
271
272
273
			Self::deposit_event(Event::Attempted(outcome));
			Ok(())
		}

Gavin Wood's avatar
Gavin Wood committed
274
275
276
277
278
279
280
281
282
283
284
		/// 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.
285
		#[pallet::weight(max_weight.saturating_add(100_000_000u64))]
Shawn Tabrizi's avatar
Shawn Tabrizi committed
286
287
288
289
290
		pub fn execute(
			origin: OriginFor<T>,
			message: Box<Xcm<T::Call>>,
			max_weight: Weight,
		) -> DispatchResult {
Gavin Wood's avatar
Gavin Wood committed
291
			let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
292
293
294
295
			let value = (origin_location, *message);
			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
296
297
298
299
300
301
302
303
			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
304
305
306
307
308
		pub fn send_xcm(
			interior: MultiLocation,
			dest: MultiLocation,
			message: Xcm<()>,
		) -> Result<(), XcmError> {
Gavin Wood's avatar
Gavin Wood committed
309
			let message = match interior {
Gavin Wood's avatar
Gavin Wood committed
310
				MultiLocation::Here => message,
Gavin Wood's avatar
Gavin Wood committed
311
312
				who => Xcm::<()>::RelayedFrom { who, message: Box::new(message) },
			};
Alexander Popiak's avatar
Alexander Popiak committed
313
			log::trace!(target: "xcm::send_xcm", "dest: {:?}, message: {:?}", &dest, &message);
Gavin Wood's avatar
Gavin Wood committed
314
315
			T::XcmRouter::send_xcm(dest, message)
		}
316
317
318
319
320

		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
321
322
	}

Shawn Tabrizi's avatar
Shawn Tabrizi committed
323
324
325
326
327
328
329
	/// Origin for the parachains module.
	#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)]
	#[pallet::origin]
	pub enum Origin {
		/// It comes from somewhere in the XCM space.
		Xcm(MultiLocation),
	}
Gavin Wood's avatar
Gavin Wood committed
330

Shawn Tabrizi's avatar
Shawn Tabrizi committed
331
332
333
334
	impl From<MultiLocation> for Origin {
		fn from(location: MultiLocation) -> Origin {
			Origin::Xcm(location)
		}
Gavin Wood's avatar
Gavin Wood committed
335
336
337
338
339
340
	}
}

/// Ensure that the origin `o` represents a sibling parachain.
/// Returns `Ok` with the parachain ID of the sibling or an `Err` otherwise.
pub fn ensure_xcm<OuterOrigin>(o: OuterOrigin) -> Result<MultiLocation, BadOrigin>
Shawn Tabrizi's avatar
Shawn Tabrizi committed
341
342
where
	OuterOrigin: Into<Result<Origin, OuterOrigin>>,
Gavin Wood's avatar
Gavin Wood committed
343
344
345
346
347
348
349
350
351
352
353
{
	match o.into() {
		Ok(Origin::Xcm(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`.
354
pub struct IsMajorityOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
355
impl<Prefix: Get<MultiLocation>, Body: Get<BodyId>> Contains<MultiLocation>
Shawn Tabrizi's avatar
Shawn Tabrizi committed
356
357
	for IsMajorityOfBody<Prefix, Body>
{
358
	fn contains(l: &MultiLocation) -> bool {
359
360
		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
361
362
363
	}
}

Shaun Wang's avatar
Shaun Wang committed
364
/// `EnsureOrigin` implementation succeeding with a `MultiLocation` value to recognize and filter the
Gavin Wood's avatar
Gavin Wood committed
365
366
/// `Origin::Xcm` item.
pub struct EnsureXcm<F>(PhantomData<F>);
367
impl<O: OriginTrait + From<Origin>, F: Contains<MultiLocation>> EnsureOrigin<O> for EnsureXcm<F>
Shawn Tabrizi's avatar
Shawn Tabrizi committed
368
369
where
	O::PalletsOrigin: From<Origin> + TryInto<Origin, Error = O::PalletsOrigin>,
Gavin Wood's avatar
Gavin Wood committed
370
371
372
373
{
	type Success = MultiLocation;

	fn try_origin(outer: O) -> Result<Self::Success, O> {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
374
375
		outer.try_with_caller(|caller| {
			caller.try_into().and_then(|Origin::Xcm(location)| {
376
				if F::contains(&location) {
Gavin Wood's avatar
Gavin Wood committed
377
378
379
380
					Ok(location)
				} else {
					Err(Origin::Xcm(location).into())
				}
Shawn Tabrizi's avatar
Shawn Tabrizi committed
381
382
			})
		})
Gavin Wood's avatar
Gavin Wood committed
383
384
385
386
	}

	#[cfg(feature = "runtime-benchmarks")]
	fn successful_origin() -> O {
Gavin Wood's avatar
Gavin Wood committed
387
		O::from(Origin::Xcm(MultiLocation::Here))
Gavin Wood's avatar
Gavin Wood committed
388
389
	}
}
390
391
392
393

/// 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
394
impl<Origin: From<crate::Origin>> ConvertOrigin<Origin> for XcmPassthrough<Origin> {
395
396
397
398
399
400
401
	fn convert_origin(origin: MultiLocation, kind: OriginKind) -> Result<Origin, MultiLocation> {
		match (kind, origin) {
			(OriginKind::Xcm, l) => Ok(crate::Origin::Xcm(l).into()),
			(_, origin) => Err(origin),
		}
	}
}