referrerpolicy=no-referrer-when-downgrade

staging_xcm_executor/traits/
transact_asset.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17use crate::{AssetsInHolding, Weight};
18use core::result::Result;
19use xcm::latest::{Asset, Error as XcmError, Location, Result as XcmResult, XcmContext};
20
21/// Facility for asset transacting.
22///
23/// This should work with as many asset/location combinations as possible. Locations to support may
24/// include non-account locations such as a `[Junction::Parachain]`. Different
25/// chains may handle them in different ways.
26///
27/// Can be amalgamated as a tuple of items that implement this trait. In such executions, if any of
28/// the transactors returns `Ok(())`, then it will short circuit. Else, execution is passed to the
29/// next transactor.
30pub trait TransactAsset {
31	/// Ensure that `check_in` will do as expected.
32	///
33	/// When composed as a tuple, all type-items are called and at least one must result in `Ok`.
34	fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {
35		Err(XcmError::Unimplemented)
36	}
37
38	/// An asset has been teleported in from the given origin. This should do whatever housekeeping
39	/// is needed.
40	///
41	/// NOTE: This will make only a best-effort at bookkeeping. The caller should ensure that
42	/// `can_check_in` has returned with `Ok` in order to guarantee that this operation proceeds
43	/// properly.
44	///
45	/// Implementation note: In general this will do one of two things: On chains where the asset is
46	/// native, it will reduce the assets from a special "teleported" account so that a)
47	/// total-issuance is preserved; and b) to ensure that no more assets can be teleported in than
48	/// were teleported out overall (this should not be needed if the teleporting chains are to be
49	/// trusted, but better to be safe than sorry). On chains where the asset is not native then it
50	/// will generally just be a no-op.
51	///
52	/// When composed as a tuple, all type-items are called. It is up to the implementer that there
53	/// exists no value for `_what` which can cause side-effects for more than one of the
54	/// type-items.
55	fn check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) {}
56
57	/// Ensure that `check_out` will do as expected.
58	///
59	/// When composed as a tuple, all type-items are called and at least one must result in `Ok`.
60	fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {
61		Err(XcmError::Unimplemented)
62	}
63
64	/// An asset has been teleported out to the given destination. This should do whatever
65	/// housekeeping is needed.
66	///
67	/// Implementation note: In general this will do one of two things: On chains where the asset is
68	/// native, it will increase the assets in a special "teleported" account so that a)
69	/// total-issuance is preserved; and b) to ensure that no more assets can be teleported in than
70	/// were teleported out overall (this should not be needed if the teleporting chains are to be
71	/// trusted, but better to be safe than sorry). On chains where the asset is not native then it
72	/// will generally just be a no-op.
73	///
74	/// When composed as a tuple, all type-items are called. It is up to the implementer that there
75	/// exists no value for `_what` which can cause side-effects for more than one of the
76	/// type-items.
77	fn check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) {}
78
79	/// Deposit the `what` asset in holding into the account of `who`.
80	///
81	/// Implementations should return `XcmError::FailedToTransactAsset` if deposit failed.
82	fn deposit_asset(
83		what: AssetsInHolding,
84		_who: &Location,
85		_context: Option<&XcmContext>,
86	) -> Result<(), (AssetsInHolding, XcmError)> {
87		Err((what, XcmError::Unimplemented))
88	}
89
90	/// Identical to `deposit_asset` but returning the surplus, if any.
91	///
92	/// Return the difference between the worst-case weight and the actual weight consumed.
93	/// This can be zero most of the time unless there's some metering involved.
94	fn deposit_asset_with_surplus(
95		what: AssetsInHolding,
96		who: &Location,
97		context: Option<&XcmContext>,
98	) -> Result<Weight, (AssetsInHolding, XcmError)> {
99		Self::deposit_asset(what, who, context).map(|()| Weight::zero())
100	}
101
102	/// Withdraw the given asset from the consensus system.
103	///
104	/// Return the actual asset(s) withdrawn, which should always be equal to `_what`.
105	///
106	/// The XCM `_maybe_context` parameter may be `None` when the caller of `withdraw_asset` is
107	/// outside of the context of a currently-executing XCM. An example will be the `charge_fees`
108	/// method in the XCM executor.
109	///
110	/// Implementations should return `XcmError::FailedToTransactAsset` if withdraw failed.
111	fn withdraw_asset(
112		_what: &Asset,
113		_who: &Location,
114		_maybe_context: Option<&XcmContext>,
115	) -> Result<AssetsInHolding, XcmError> {
116		Err(XcmError::Unimplemented)
117	}
118
119	/// Withdraw assets returning surplus.
120	///
121	/// The surplus is the difference between the worst-case weight and the actual weight consumed.
122	/// This can be zero most of the time unless there's some metering involved.
123	fn withdraw_asset_with_surplus(
124		what: &Asset,
125		who: &Location,
126		maybe_context: Option<&XcmContext>,
127	) -> Result<(AssetsInHolding, Weight), XcmError> {
128		Self::withdraw_asset(what, who, maybe_context).map(|assets| (assets, Weight::zero()))
129	}
130
131	/// Move an `asset` `from` one location in `to` another location.
132	///
133	/// Returns `XcmError::FailedToTransactAsset` if transfer failed.
134	///
135	/// ## Notes
136	/// This function is meant to only be implemented by the type implementing `TransactAsset`, and
137	/// not be called directly. Most common API usages will instead call `transfer_asset`, which in
138	/// turn has a default implementation that calls `internal_transfer_asset`. As such, **please
139	/// do not call this method directly unless you know what you're doing**.
140	fn internal_transfer_asset(
141		_asset: &Asset,
142		_from: &Location,
143		_to: &Location,
144		_context: &XcmContext,
145	) -> Result<Asset, XcmError> {
146		Err(XcmError::Unimplemented)
147	}
148
149	/// Identical to `internal_transfer_asset` but returning the surplus, if any.
150	///
151	/// The surplus is the difference between the worst-case weight and the actual
152	/// consumed weight.
153	/// This can be zero usually if there's no metering involved.
154	fn internal_transfer_asset_with_surplus(
155		asset: &Asset,
156		from: &Location,
157		to: &Location,
158		context: &XcmContext,
159	) -> Result<(Asset, Weight), XcmError> {
160		Self::internal_transfer_asset(asset, from, to, context).map(|asset| (asset, Weight::zero()))
161	}
162
163	/// Move an `asset` `from` one location in `to` another location.
164	///
165	/// Attempts to use `internal_transfer_asset` and if not available then falls back to using a
166	/// two-part withdraw/deposit.
167	fn transfer_asset(
168		asset: &Asset,
169		from: &Location,
170		to: &Location,
171		context: &XcmContext,
172	) -> Result<Asset, XcmError> {
173		match Self::internal_transfer_asset(asset, from, to, context) {
174			Err(XcmError::AssetNotFound | XcmError::Unimplemented) => {
175				let credit = Self::withdraw_asset(asset, from, Some(context))?;
176				Self::deposit_asset(credit, to, Some(context)).map_err(|(unspent, error)| {
177					// best effort try to return the assets to original owner
178					let _ = Self::deposit_asset(unspent, from, Some(context));
179					error
180				})?;
181				Ok(asset.clone())
182			},
183			result => result,
184		}
185	}
186
187	/// Identical to `transfer_asset` but returning the surplus, if any.
188	///
189	/// The surplus is the difference between the worst-case weight and the actual
190	/// consumed weight.
191	/// This can be zero usually if there's no metering involved.
192	fn transfer_asset_with_surplus(
193		asset: &Asset,
194		from: &Location,
195		to: &Location,
196		context: &XcmContext,
197	) -> Result<(Asset, Weight), XcmError> {
198		match Self::internal_transfer_asset_with_surplus(asset, from, to, context) {
199			Err(XcmError::AssetNotFound | XcmError::Unimplemented) => {
200				let (credit, withdraw_surplus) =
201					Self::withdraw_asset_with_surplus(asset, from, Some(context))?;
202				let deposit_surplus = Self::deposit_asset_with_surplus(credit, to, Some(context))
203					.map_err(|(unspent, error)| {
204					// best effort try to return the assets to original owner
205					let _ = Self::deposit_asset(unspent, from, Some(context));
206					error
207				})?;
208				let total_surplus = withdraw_surplus.saturating_add(deposit_surplus);
209				Ok((asset.clone(), total_surplus))
210			},
211			result => result,
212		}
213	}
214
215	/// An asset has been minted and the imbalance returned into holding. This should do whatever
216	/// housekeeping is needed.
217	///
218	/// When composed as a tuple, all type-items are called and at least one must result in `Ok`.
219	fn mint_asset(_what: &Asset, _context: &XcmContext) -> Result<AssetsInHolding, XcmError> {
220		Err(XcmError::Unimplemented)
221	}
222}
223
224#[impl_trait_for_tuples::impl_for_tuples(30)]
225impl TransactAsset for Tuple {
226	fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
227		for_tuples!( #(
228			match Tuple::can_check_in(origin, what, context) {
229				Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (),
230				r => return r,
231			}
232		)* );
233		tracing::trace!(
234			target: "xcm::TransactAsset::can_check_in",
235			?what,
236			?origin,
237			?context,
238			"asset not found",
239		);
240		Err(XcmError::AssetNotFound)
241	}
242
243	fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
244		for_tuples!( #(
245			Tuple::check_in(origin, what, context);
246		)* );
247	}
248
249	fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
250		for_tuples!( #(
251			match Tuple::can_check_out(dest, what, context) {
252				Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (),
253				r => return r,
254			}
255		)* );
256		tracing::trace!(
257			target: "xcm::TransactAsset::can_check_out",
258			?what,
259			?dest,
260			?context,
261			"asset not found",
262		);
263		Err(XcmError::AssetNotFound)
264	}
265
266	fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
267		for_tuples!( #(
268			Tuple::check_out(dest, what, context);
269		)* );
270	}
271
272	fn deposit_asset(
273		mut what: AssetsInHolding,
274		who: &Location,
275		context: Option<&XcmContext>,
276	) -> Result<(), (AssetsInHolding, XcmError)> {
277		for_tuples!( #(
278			match Tuple::deposit_asset(what, who, context) {
279				Err((unspent, XcmError::AssetNotFound)) | Err((unspent, XcmError::Unimplemented)) => {
280					what = unspent;
281					// continue
282				},
283				r => return r,
284			}
285		)* );
286		tracing::trace!(
287			target: "xcm::TransactAsset::deposit_asset",
288			?what,
289			?who,
290			?context,
291			"did not deposit asset",
292		);
293		Err((what, XcmError::AssetNotFound))
294	}
295
296	fn deposit_asset_with_surplus(
297		mut what: AssetsInHolding,
298		who: &Location,
299		context: Option<&XcmContext>,
300	) -> Result<Weight, (AssetsInHolding, XcmError)> {
301		for_tuples!( #(
302			match Tuple::deposit_asset_with_surplus(what, who, context) {
303				Err((unspent, XcmError::AssetNotFound)) | Err((unspent, XcmError::Unimplemented)) => {
304					what = unspent;
305					// continue
306				},
307				r => return r,
308			}
309		)* );
310		tracing::trace!(
311			target: "xcm::TransactAsset::deposit_asset_with_surplus",
312			?what,
313			?who,
314			?context,
315			"did not deposit asset",
316		);
317		Err((what, XcmError::AssetNotFound))
318	}
319
320	fn withdraw_asset(
321		what: &Asset,
322		who: &Location,
323		maybe_context: Option<&XcmContext>,
324	) -> Result<AssetsInHolding, XcmError> {
325		for_tuples!( #(
326			match Tuple::withdraw_asset(what, who, maybe_context) {
327				Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (),
328				r => return r,
329			}
330		)* );
331		tracing::trace!(
332			target: "xcm::TransactAsset::withdraw_asset",
333			?what,
334			?who,
335			?maybe_context,
336			"did not withdraw asset",
337		);
338		Err(XcmError::AssetNotFound)
339	}
340
341	fn withdraw_asset_with_surplus(
342		what: &Asset,
343		who: &Location,
344		maybe_context: Option<&XcmContext>,
345	) -> Result<(AssetsInHolding, Weight), XcmError> {
346		for_tuples!( #(
347			match Tuple::withdraw_asset_with_surplus(what, who, maybe_context) {
348				Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (),
349				r => return r,
350			}
351		)* );
352		tracing::trace!(
353			target: "xcm::TransactAsset::withdraw_asset_with_surplus",
354			?what,
355			?who,
356			?maybe_context,
357			"did not withdraw asset",
358		);
359		Err(XcmError::AssetNotFound)
360	}
361
362	fn internal_transfer_asset(
363		what: &Asset,
364		from: &Location,
365		to: &Location,
366		context: &XcmContext,
367	) -> Result<Asset, XcmError> {
368		for_tuples!( #(
369			match Tuple::internal_transfer_asset(what, from, to, context) {
370				Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (),
371				r => return r,
372			}
373		)* );
374		tracing::trace!(
375			target: "xcm::TransactAsset::internal_transfer_asset",
376			?what,
377			?from,
378			?to,
379			?context,
380			"did not transfer asset",
381		);
382		Err(XcmError::AssetNotFound)
383	}
384
385	fn internal_transfer_asset_with_surplus(
386		what: &Asset,
387		from: &Location,
388		to: &Location,
389		context: &XcmContext,
390	) -> Result<(Asset, Weight), XcmError> {
391		for_tuples!( #(
392			match Tuple::internal_transfer_asset_with_surplus(what, from, to, context) {
393				Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (),
394				r => return r,
395			}
396		)* );
397		tracing::trace!(
398			target: "xcm::TransactAsset::internal_transfer_asset_with_surplus",
399			?what,
400			?from,
401			?to,
402			?context,
403			"did not transfer asset",
404		);
405		Err(XcmError::AssetNotFound)
406	}
407
408	fn mint_asset(what: &Asset, context: &XcmContext) -> Result<AssetsInHolding, XcmError> {
409		for_tuples!( #(
410			match Tuple::mint_asset(what, context) {
411				Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (),
412				r => return r,
413			}
414		)* );
415		tracing::trace!(
416			target: "xcm::TransactAsset::mint_asset",
417			?what,
418			?context,
419			"no match. did not mint asset",
420		);
421		Err(XcmError::AssetNotFound)
422	}
423}
424
425#[cfg(test)]
426mod tests {
427	use super::*;
428	use xcm::latest::{AssetId, Junctions::Here};
429
430	pub struct UnimplementedTransactor;
431	impl TransactAsset for UnimplementedTransactor {}
432
433	pub struct NotFoundTransactor;
434	impl TransactAsset for NotFoundTransactor {
435		fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {
436			Err(XcmError::AssetNotFound)
437		}
438
439		fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {
440			Err(XcmError::AssetNotFound)
441		}
442
443		fn deposit_asset(
444			what: AssetsInHolding,
445			_who: &Location,
446			_context: Option<&XcmContext>,
447		) -> Result<(), (AssetsInHolding, XcmError)> {
448			Err((what, XcmError::AssetNotFound))
449		}
450
451		fn withdraw_asset(
452			_what: &Asset,
453			_who: &Location,
454			_context: Option<&XcmContext>,
455		) -> Result<AssetsInHolding, XcmError> {
456			Err(XcmError::AssetNotFound)
457		}
458
459		fn internal_transfer_asset(
460			_what: &Asset,
461			_from: &Location,
462			_to: &Location,
463			_context: &XcmContext,
464		) -> Result<Asset, XcmError> {
465			Err(XcmError::AssetNotFound)
466		}
467
468		fn mint_asset(_: &Asset, _: &XcmContext) -> Result<AssetsInHolding, XcmError> {
469			Err(XcmError::AssetNotFound)
470		}
471	}
472
473	pub struct OverflowTransactor;
474	impl TransactAsset for OverflowTransactor {
475		fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {
476			Err(XcmError::Overflow)
477		}
478
479		fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {
480			Err(XcmError::Overflow)
481		}
482
483		fn deposit_asset(
484			what: AssetsInHolding,
485			_who: &Location,
486			_context: Option<&XcmContext>,
487		) -> Result<(), (AssetsInHolding, XcmError)> {
488			Err((what, XcmError::Overflow))
489		}
490
491		fn withdraw_asset(
492			_what: &Asset,
493			_who: &Location,
494			_context: Option<&XcmContext>,
495		) -> Result<AssetsInHolding, XcmError> {
496			Err(XcmError::Overflow)
497		}
498
499		fn internal_transfer_asset(
500			_what: &Asset,
501			_from: &Location,
502			_to: &Location,
503			_context: &XcmContext,
504		) -> Result<Asset, XcmError> {
505			Err(XcmError::Overflow)
506		}
507
508		fn mint_asset(_: &Asset, _: &XcmContext) -> Result<AssetsInHolding, XcmError> {
509			Err(XcmError::Overflow)
510		}
511	}
512
513	pub struct SuccessfulTransactor;
514	impl TransactAsset for SuccessfulTransactor {
515		fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {
516			Ok(())
517		}
518
519		fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {
520			Ok(())
521		}
522
523		fn deposit_asset(
524			_what: AssetsInHolding,
525			_who: &Location,
526			_context: Option<&XcmContext>,
527		) -> Result<(), (AssetsInHolding, XcmError)> {
528			Ok(())
529		}
530
531		fn withdraw_asset(
532			what: &Asset,
533			_who: &Location,
534			_context: Option<&XcmContext>,
535		) -> Result<AssetsInHolding, XcmError> {
536			Ok(asset_to_holding(what.clone()))
537		}
538
539		fn mint_asset(what: &Asset, _context: &XcmContext) -> Result<AssetsInHolding, XcmError> {
540			Ok(asset_to_holding(what.clone()))
541		}
542
543		fn internal_transfer_asset(
544			_what: &Asset,
545			_from: &Location,
546			_to: &Location,
547			_context: &XcmContext,
548		) -> Result<Asset, XcmError> {
549			Ok(Asset::from((AssetId(Location::here()), 42u128)))
550		}
551	}
552
553	/// Helper to convert a single Asset into AssetsInHolding for tests
554	fn asset_to_holding(asset: Asset) -> AssetsInHolding {
555		crate::test_helpers::mock_asset_to_holding(asset)
556	}
557
558	#[test]
559	fn defaults_to_asset_not_found() {
560		type MultiTransactor =
561			(UnimplementedTransactor, NotFoundTransactor, UnimplementedTransactor);
562
563		let asset: Asset = (Here, 1u128).into();
564		let assets_in_holding: AssetsInHolding = asset_to_holding(asset);
565		assert_eq!(
566			MultiTransactor::deposit_asset(
567				assets_in_holding,
568				&Here.into(),
569				Some(&XcmContext::with_message_id([0; 32])),
570			)
571			.map_err(|(_, e)| e),
572			Err(XcmError::AssetNotFound)
573		);
574	}
575
576	#[test]
577	fn unimplemented_and_not_found_continue_iteration() {
578		type MultiTransactor = (UnimplementedTransactor, NotFoundTransactor, SuccessfulTransactor);
579
580		let asset: Asset = (Here, 1u128).into();
581		let assets_in_holding: AssetsInHolding = asset_to_holding(asset);
582		assert_eq!(
583			MultiTransactor::deposit_asset(
584				assets_in_holding,
585				&Here.into(),
586				Some(&XcmContext::with_message_id([0; 32])),
587			),
588			Ok(())
589		);
590	}
591
592	#[test]
593	fn unexpected_error_stops_iteration() {
594		type MultiTransactor = (OverflowTransactor, SuccessfulTransactor);
595
596		let asset: Asset = (Here, 1u128).into();
597		let assets_in_holding: AssetsInHolding = asset_to_holding(asset);
598		assert_eq!(
599			MultiTransactor::deposit_asset(
600				assets_in_holding,
601				&Here.into(),
602				Some(&XcmContext::with_message_id([0; 32])),
603			)
604			.map_err(|(_, e)| e),
605			Err(XcmError::Overflow)
606		);
607	}
608
609	#[test]
610	fn success_stops_iteration() {
611		type MultiTransactor = (SuccessfulTransactor, OverflowTransactor);
612
613		let asset: Asset = (Here, 1u128).into();
614		let assets_in_holding: AssetsInHolding = asset_to_holding(asset);
615		assert_eq!(
616			MultiTransactor::deposit_asset(
617				assets_in_holding,
618				&Here.into(),
619				Some(&XcmContext::with_message_id([0; 32])),
620			),
621			Ok(()),
622		);
623	}
624}