orml_xtokens/
lib.rs

1//! # Xtokens Module
2//!
3//! ## Overview
4//!
5//! The xtokens module provides cross-chain token transfer functionality, by
6//! cross-consensus messages(XCM).
7//!
8//! The xtokens module provides functions for
9//! - Token transfer from parachains to relay chain.
10//! - Token transfer between parachains, including relay chain tokens like DOT,
11//!   KSM, and parachain tokens like ACA, aUSD.
12//!
13//! ## Interface
14//!
15//! ### Dispatchable functions
16//!
17//! - `transfer`: Transfer local assets with given `CurrencyId` and `Amount`.
18//! - `transfer_multiasset`: Transfer `Asset` assets.
19//! - `transfer_with_fee`: Transfer native currencies specifying the fee and
20//!   amount as separate.
21//! - `transfer_multiasset_with_fee`: Transfer `Asset` specifying the fee and
22//!   amount as separate.
23//! - `transfer_multicurrencies`: Transfer several currencies specifying the
24//!   item to be used as fee.
25//! - `transfer_multiassets`: Transfer several `Asset` specifying the item to be
26//!   used as fee.
27
28#![cfg_attr(not(feature = "std"), no_std)]
29#![allow(clippy::from_over_into)]
30#![allow(clippy::unused_unit)]
31#![allow(clippy::large_enum_variant)]
32#![allow(clippy::boxed_local)]
33#![allow(clippy::too_many_arguments)]
34
35use frame_support::{
36	pallet_prelude::*,
37	require_transactional,
38	traits::{Contains, Get},
39	Parameter,
40};
41use frame_system::{ensure_signed, pallet_prelude::*};
42use sp_runtime::{
43	traits::{AtLeast32BitUnsigned, Bounded, Convert, MaybeSerializeDeserialize, Member, Zero},
44	DispatchError,
45};
46use sp_std::{prelude::*, result::Result};
47
48use xcm::{
49	v5::{prelude::*, Weight},
50	VersionedAsset, VersionedAssets, VersionedLocation,
51};
52use xcm_executor::traits::WeightBounds;
53
54pub use module::*;
55use orml_traits::{
56	location::{Parse, Reserve},
57	xcm_transfer::{Transferred, XtokensWeightInfo},
58	GetByKey, RateLimiter, XcmTransfer,
59};
60
61mod mock;
62mod tests;
63
64enum TransferKind {
65	/// Transfer self reserve asset.
66	SelfReserveAsset,
67	/// To reserve location.
68	ToReserve,
69	/// To non-reserve location.
70	ToNonReserve,
71}
72use TransferKind::*;
73
74#[frame_support::pallet]
75pub mod module {
76	use super::*;
77
78	#[pallet::config]
79	pub trait Config: frame_system::Config {
80		/// The balance type.
81		type Balance: Parameter
82			+ Member
83			+ AtLeast32BitUnsigned
84			+ Default
85			+ Copy
86			+ MaybeSerializeDeserialize
87			+ Into<u128>;
88
89		/// Currency Id.
90		type CurrencyId: Parameter + Member + Clone;
91
92		/// Convert `T::CurrencyId` to `Location`.
93		type CurrencyIdConvert: Convert<Self::CurrencyId, Option<Location>>;
94
95		/// Convert `T::AccountId` to `Location`.
96		type AccountIdToLocation: Convert<Self::AccountId, Location>;
97
98		/// Self chain location.
99		#[pallet::constant]
100		type SelfLocation: Get<Location>;
101
102		/// Minimum xcm execution fee paid on destination chain.
103		type MinXcmFee: GetByKey<Location, Option<u128>>;
104
105		/// XCM executor.
106		type XcmExecutor: ExecuteXcm<Self::RuntimeCall>;
107
108		/// Location filter
109		type LocationsFilter: Contains<Location>;
110
111		/// Means of measuring the weight consumed by an XCM message locally.
112		type Weigher: WeightBounds<Self::RuntimeCall>;
113
114		/// Base XCM weight.
115		///
116		/// The actually weight for an XCM message is `T::BaseXcmWeight +
117		/// T::Weigher::weight(&msg)`.
118		#[pallet::constant]
119		type BaseXcmWeight: Get<Weight>;
120
121		/// This chain's Universal Location.
122		type UniversalLocation: Get<InteriorLocation>;
123
124		/// The maximum number of distinct assets allowed to be transferred in a
125		/// single helper extrinsic.
126		type MaxAssetsForTransfer: Get<usize>;
127
128		/// The way to retreave the reserve of a Asset. This can be
129		/// configured to accept absolute or relative paths for self tokens
130		type ReserveProvider: Reserve;
131
132		/// The rate limiter used to limit the cross-chain transfer asset.
133		type RateLimiter: RateLimiter;
134
135		/// The id of the RateLimiter.
136		#[pallet::constant]
137		type RateLimiterId: Get<<Self::RateLimiter as RateLimiter>::RateLimiterId>;
138	}
139
140	#[pallet::event]
141	#[pallet::generate_deposit(fn deposit_event)]
142	pub enum Event<T: Config> {
143		/// Transferred `Asset` with fee.
144		TransferredAssets {
145			sender: T::AccountId,
146			assets: Assets,
147			fee: Asset,
148			dest: Location,
149		},
150	}
151
152	#[pallet::error]
153	pub enum Error<T> {
154		/// Asset has no reserve location.
155		AssetHasNoReserve,
156		/// Not cross-chain transfer.
157		NotCrossChainTransfer,
158		/// Invalid transfer destination.
159		InvalidDest,
160		/// Currency is not cross-chain transferable.
161		NotCrossChainTransferableCurrency,
162		/// The message's weight could not be determined.
163		UnweighableMessage,
164		/// XCM execution failed.
165		XcmExecutionFailed,
166		/// Could not re-anchor the assets to declare the fees for the
167		/// destination chain.
168		CannotReanchor,
169		/// Could not get ancestry of asset reserve location.
170		InvalidAncestry,
171		/// The Asset is invalid.
172		InvalidAsset,
173		/// The destination `Location` provided cannot be inverted.
174		DestinationNotInvertible,
175		/// The version of the `Versioned` value used is not able to be
176		/// interpreted.
177		BadVersion,
178		/// We tried sending distinct asset and fee but they have different
179		/// reserve chains.
180		DistinctReserveForAssetAndFee,
181		/// The fee is zero.
182		ZeroFee,
183		/// The transferring asset amount is zero.
184		ZeroAmount,
185		/// The number of assets to be sent is over the maximum.
186		TooManyAssetsBeingSent,
187		/// The specified index does not exist in a Assets struct.
188		AssetIndexNonExistent,
189		/// Fee is not enough.
190		FeeNotEnough,
191		/// Not supported Location
192		NotSupportedLocation,
193		/// MinXcmFee not registered for certain reserve location
194		MinXcmFeeNotDefined,
195		/// Asset transfer is limited by RateLimiter.
196		RateLimited,
197	}
198
199	#[pallet::hooks]
200	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
201
202	#[pallet::pallet]
203	pub struct Pallet<T>(_);
204
205	#[pallet::call]
206	impl<T: Config> Pallet<T> {
207		/// Transfer native currencies.
208		///
209		/// `dest_weight_limit` is the weight for XCM execution on the dest
210		/// chain, and it would be charged from the transferred assets. If set
211		/// below requirements, the execution may fail and assets wouldn't be
212		/// received.
213		///
214		/// It's a no-op if any error on local XCM execution or message sending.
215		/// Note sending assets out per se doesn't guarantee they would be
216		/// received. Receiving depends on if the XCM message could be delivered
217		/// by the network, and if the receiving chain would handle
218		/// messages correctly.
219		#[pallet::call_index(0)]
220		#[pallet::weight(XtokensWeight::<T>::weight_of_transfer(currency_id.clone(), *amount, dest))]
221		pub fn transfer(
222			origin: OriginFor<T>,
223			currency_id: T::CurrencyId,
224			amount: T::Balance,
225			dest: Box<VersionedLocation>,
226			dest_weight_limit: WeightLimit,
227		) -> DispatchResult {
228			let who = ensure_signed(origin)?;
229			let dest: Location = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
230			Self::do_transfer(who, currency_id, amount, dest, dest_weight_limit).map(|_| ())
231		}
232
233		/// Transfer `Asset`.
234		///
235		/// `dest_weight_limit` is the weight for XCM execution on the dest
236		/// chain, and it would be charged from the transferred assets. If set
237		/// below requirements, the execution may fail and assets wouldn't be
238		/// received.
239		///
240		/// It's a no-op if any error on local XCM execution or message sending.
241		/// Note sending assets out per se doesn't guarantee they would be
242		/// received. Receiving depends on if the XCM message could be delivered
243		/// by the network, and if the receiving chain would handle
244		/// messages correctly.
245		#[pallet::call_index(1)]
246		#[pallet::weight(XtokensWeight::<T>::weight_of_transfer_multiasset(asset, dest))]
247		pub fn transfer_multiasset(
248			origin: OriginFor<T>,
249			asset: Box<VersionedAsset>,
250			dest: Box<VersionedLocation>,
251			dest_weight_limit: WeightLimit,
252		) -> DispatchResult {
253			let who = ensure_signed(origin)?;
254			let asset: Asset = (*asset).try_into().map_err(|()| Error::<T>::BadVersion)?;
255			let dest: Location = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
256			Self::do_transfer_asset(who, asset, dest, dest_weight_limit).map(|_| ())
257		}
258
259		/// Transfer native currencies specifying the fee and amount as
260		/// separate.
261		///
262		/// `dest_weight_limit` is the weight for XCM execution on the dest
263		/// chain, and it would be charged from the transferred assets. If set
264		/// below requirements, the execution may fail and assets wouldn't be
265		/// received.
266		///
267		/// `fee` is the amount to be spent to pay for execution in destination
268		/// chain. Both fee and amount will be subtracted form the callers
269		/// balance.
270		///
271		/// If `fee` is not high enough to cover for the execution costs in the
272		/// destination chain, then the assets will be trapped in the
273		/// destination chain
274		///
275		/// It's a no-op if any error on local XCM execution or message sending.
276		/// Note sending assets out per se doesn't guarantee they would be
277		/// received. Receiving depends on if the XCM message could be delivered
278		/// by the network, and if the receiving chain would handle
279		/// messages correctly.
280		#[pallet::call_index(2)]
281		#[pallet::weight(XtokensWeight::<T>::weight_of_transfer(currency_id.clone(), *amount, dest))]
282		pub fn transfer_with_fee(
283			origin: OriginFor<T>,
284			currency_id: T::CurrencyId,
285			amount: T::Balance,
286			fee: T::Balance,
287			dest: Box<VersionedLocation>,
288			dest_weight_limit: WeightLimit,
289		) -> DispatchResult {
290			let who = ensure_signed(origin)?;
291			let dest: Location = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
292
293			Self::do_transfer_with_fee(who, currency_id, amount, fee, dest, dest_weight_limit).map(|_| ())
294		}
295
296		/// Transfer `Asset` specifying the fee and amount as separate.
297		///
298		/// `dest_weight_limit` is the weight for XCM execution on the dest
299		/// chain, and it would be charged from the transferred assets. If set
300		/// below requirements, the execution may fail and assets wouldn't be
301		/// received.
302		///
303		/// `fee` is the Asset to be spent to pay for execution in
304		/// destination chain. Both fee and amount will be subtracted form the
305		/// callers balance For now we only accept fee and asset having the same
306		/// `Location` id.
307		///
308		/// If `fee` is not high enough to cover for the execution costs in the
309		/// destination chain, then the assets will be trapped in the
310		/// destination chain
311		///
312		/// It's a no-op if any error on local XCM execution or message sending.
313		/// Note sending assets out per se doesn't guarantee they would be
314		/// received. Receiving depends on if the XCM message could be delivered
315		/// by the network, and if the receiving chain would handle
316		/// messages correctly.
317		#[pallet::call_index(3)]
318		#[pallet::weight(XtokensWeight::<T>::weight_of_transfer_multiasset(asset, dest))]
319		pub fn transfer_multiasset_with_fee(
320			origin: OriginFor<T>,
321			asset: Box<VersionedAsset>,
322			fee: Box<VersionedAsset>,
323			dest: Box<VersionedLocation>,
324			dest_weight_limit: WeightLimit,
325		) -> DispatchResult {
326			let who = ensure_signed(origin)?;
327			let asset: Asset = (*asset).try_into().map_err(|()| Error::<T>::BadVersion)?;
328			let fee: Asset = (*fee).try_into().map_err(|()| Error::<T>::BadVersion)?;
329			let dest: Location = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
330
331			Self::do_transfer_asset_with_fee(who, asset, fee, dest, dest_weight_limit).map(|_| ())
332		}
333
334		/// Transfer several currencies specifying the item to be used as fee
335		///
336		/// `dest_weight_limit` is the weight for XCM execution on the dest
337		/// chain, and it would be charged from the transferred assets. If set
338		/// below requirements, the execution may fail and assets wouldn't be
339		/// received.
340		///
341		/// `fee_item` is index of the currencies tuple that we want to use for
342		/// payment
343		///
344		/// It's a no-op if any error on local XCM execution or message sending.
345		/// Note sending assets out per se doesn't guarantee they would be
346		/// received. Receiving depends on if the XCM message could be delivered
347		/// by the network, and if the receiving chain would handle
348		/// messages correctly.
349		#[pallet::call_index(4)]
350		#[pallet::weight(XtokensWeight::<T>::weight_of_transfer_multicurrencies(currencies, fee_item, dest))]
351		pub fn transfer_multicurrencies(
352			origin: OriginFor<T>,
353			currencies: Vec<(T::CurrencyId, T::Balance)>,
354			fee_item: u32,
355			dest: Box<VersionedLocation>,
356			dest_weight_limit: WeightLimit,
357		) -> DispatchResult {
358			let who = ensure_signed(origin)?;
359			let dest: Location = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
360
361			Self::do_transfer_multicurrencies(who, currencies, fee_item, dest, dest_weight_limit).map(|_| ())
362		}
363
364		/// Transfer several `Asset` specifying the item to be used as fee
365		///
366		/// `dest_weight_limit` is the weight for XCM execution on the dest
367		/// chain, and it would be charged from the transferred assets. If set
368		/// below requirements, the execution may fail and assets wouldn't be
369		/// received.
370		///
371		/// `fee_item` is index of the Assets that we want to use for
372		/// payment
373		///
374		/// It's a no-op if any error on local XCM execution or message sending.
375		/// Note sending assets out per se doesn't guarantee they would be
376		/// received. Receiving depends on if the XCM message could be delivered
377		/// by the network, and if the receiving chain would handle
378		/// messages correctly.
379		#[pallet::call_index(5)]
380		#[pallet::weight(XtokensWeight::<T>::weight_of_transfer_multiassets(assets, fee_item, dest))]
381		pub fn transfer_multiassets(
382			origin: OriginFor<T>,
383			assets: Box<VersionedAssets>,
384			fee_item: u32,
385			dest: Box<VersionedLocation>,
386			dest_weight_limit: WeightLimit,
387		) -> DispatchResult {
388			let who = ensure_signed(origin)?;
389			let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
390			let dest: Location = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
391
392			// We first grab the fee
393			let fee: &Asset = assets.get(fee_item as usize).ok_or(Error::<T>::AssetIndexNonExistent)?;
394
395			Self::do_transfer_assets(who, assets.clone(), fee.clone(), dest, dest_weight_limit).map(|_| ())
396		}
397	}
398
399	impl<T: Config> Pallet<T> {
400		fn do_transfer(
401			who: T::AccountId,
402			currency_id: T::CurrencyId,
403			amount: T::Balance,
404			dest: Location,
405			dest_weight_limit: WeightLimit,
406		) -> Result<Transferred<T::AccountId>, DispatchError> {
407			let location: Location =
408				T::CurrencyIdConvert::convert(currency_id).ok_or(Error::<T>::NotCrossChainTransferableCurrency)?;
409
410			ensure!(!amount.is_zero(), Error::<T>::ZeroAmount);
411			ensure!(T::LocationsFilter::contains(&dest), Error::<T>::NotSupportedLocation);
412
413			let asset: Asset = (location, amount.into()).into();
414			Self::do_transfer_assets(who, vec![asset.clone()].into(), asset, dest, dest_weight_limit)
415		}
416
417		fn do_transfer_with_fee(
418			who: T::AccountId,
419			currency_id: T::CurrencyId,
420			amount: T::Balance,
421			fee: T::Balance,
422			dest: Location,
423			dest_weight_limit: WeightLimit,
424		) -> Result<Transferred<T::AccountId>, DispatchError> {
425			let location: Location =
426				T::CurrencyIdConvert::convert(currency_id).ok_or(Error::<T>::NotCrossChainTransferableCurrency)?;
427
428			ensure!(!amount.is_zero(), Error::<T>::ZeroAmount);
429			ensure!(!fee.is_zero(), Error::<T>::ZeroFee);
430			ensure!(T::LocationsFilter::contains(&dest), Error::<T>::NotSupportedLocation);
431
432			let asset = (location.clone(), amount.into()).into();
433			let fee_asset: Asset = (location, fee.into()).into();
434
435			// Push contains saturated addition, so we should be able to use it safely
436			let mut assets = Assets::new();
437			assets.push(asset);
438			assets.push(fee_asset.clone());
439
440			Self::do_transfer_assets(who, assets, fee_asset, dest, dest_weight_limit)
441		}
442
443		fn do_transfer_asset(
444			who: T::AccountId,
445			asset: Asset,
446			dest: Location,
447			dest_weight_limit: WeightLimit,
448		) -> Result<Transferred<T::AccountId>, DispatchError> {
449			Self::do_transfer_assets(who, vec![asset.clone()].into(), asset, dest, dest_weight_limit)
450		}
451
452		fn do_transfer_asset_with_fee(
453			who: T::AccountId,
454			asset: Asset,
455			fee: Asset,
456			dest: Location,
457			dest_weight_limit: WeightLimit,
458		) -> Result<Transferred<T::AccountId>, DispatchError> {
459			// Push contains saturated addition, so we should be able to use it safely
460			let mut assets = Assets::new();
461			assets.push(asset);
462			assets.push(fee.clone());
463
464			Self::do_transfer_assets(who, assets, fee, dest, dest_weight_limit)
465		}
466
467		fn do_transfer_multicurrencies(
468			who: T::AccountId,
469			currencies: Vec<(T::CurrencyId, T::Balance)>,
470			fee_item: u32,
471			dest: Location,
472			dest_weight_limit: WeightLimit,
473		) -> Result<Transferred<T::AccountId>, DispatchError> {
474			ensure!(
475				currencies.len() <= T::MaxAssetsForTransfer::get(),
476				Error::<T>::TooManyAssetsBeingSent
477			);
478			ensure!(T::LocationsFilter::contains(&dest), Error::<T>::NotSupportedLocation);
479
480			let mut assets = Assets::new();
481
482			// Lets grab the fee amount and location first
483			let (fee_currency_id, fee_amount) = currencies
484				.get(fee_item as usize)
485				.ok_or(Error::<T>::AssetIndexNonExistent)?;
486
487			for (currency_id, amount) in &currencies {
488				let location: Location = T::CurrencyIdConvert::convert(currency_id.clone())
489					.ok_or(Error::<T>::NotCrossChainTransferableCurrency)?;
490				ensure!(!amount.is_zero(), Error::<T>::ZeroAmount);
491
492				// Push contains saturated addition, so we should be able to use it safely
493				assets.push((location, (*amount).into()).into())
494			}
495
496			// We construct the fee now, since getting it from assets won't work as assets
497			// sorts it
498			let fee_location: Location = T::CurrencyIdConvert::convert(fee_currency_id.clone())
499				.ok_or(Error::<T>::NotCrossChainTransferableCurrency)?;
500
501			let fee: Asset = (fee_location, (*fee_amount).into()).into();
502
503			Self::do_transfer_assets(who, assets, fee, dest, dest_weight_limit)
504		}
505
506		fn do_transfer_assets(
507			who: T::AccountId,
508			assets: Assets,
509			fee: Asset,
510			dest: Location,
511			dest_weight_limit: WeightLimit,
512		) -> Result<Transferred<T::AccountId>, DispatchError> {
513			ensure!(
514				assets.len() <= T::MaxAssetsForTransfer::get(),
515				Error::<T>::TooManyAssetsBeingSent
516			);
517			ensure!(T::LocationsFilter::contains(&dest), Error::<T>::NotSupportedLocation);
518
519			// Fee payment can only be made by using the non-zero amount of fungibles
520			ensure!(
521				matches!(fee.fun, Fungibility::Fungible(x) if !x.is_zero()),
522				Error::<T>::InvalidAsset
523			);
524
525			let origin_location = T::AccountIdToLocation::convert(who.clone());
526
527			let mut non_fee_reserve: Option<Location> = None;
528			let asset_len = assets.len();
529			for i in 0..asset_len {
530				let asset = assets.get(i).ok_or(Error::<T>::AssetIndexNonExistent)?;
531
532				match asset.fun {
533					Fungibility::Fungible(x) => ensure!(!x.is_zero(), Error::<T>::InvalidAsset),
534					Fungibility::NonFungible(AssetInstance::Undefined) => return Err(Error::<T>::InvalidAsset.into()),
535					_ => {}
536				}
537
538				// `assets` includes fee, the reserve location is decided by non fee asset
539				if non_fee_reserve.is_none() && asset.id != fee.id {
540					non_fee_reserve = T::ReserveProvider::reserve(asset);
541				}
542
543				// make sure all non fee assets share the same reserve
544				if non_fee_reserve.is_some() {
545					ensure!(
546						non_fee_reserve == T::ReserveProvider::reserve(asset),
547						Error::<T>::DistinctReserveForAssetAndFee
548					);
549				}
550
551				// per asset check
552				let amount = match asset.fun {
553					Fungibility::Fungible(amount) => amount,
554					Fungibility::NonFungible(_) => 1,
555				};
556
557				let rate_limiter_id = T::RateLimiterId::get();
558
559				// try consume quota of the rate limiter.
560				T::RateLimiter::try_consume(rate_limiter_id, asset.id.clone(), amount, Some(&who))
561					.map_err(|_| Error::<T>::RateLimited)?;
562			}
563
564			let fee_reserve = T::ReserveProvider::reserve(&fee);
565			if asset_len > 1 && fee_reserve != non_fee_reserve {
566				// Current only support `ToReserve` with relay-chain asset as fee. other case
567				// like `NonReserve` or `SelfReserve` with relay-chain fee is not support.
568				ensure!(non_fee_reserve == dest.chain_part(), Error::<T>::InvalidAsset);
569
570				let reserve_location = non_fee_reserve.clone().ok_or(Error::<T>::AssetHasNoReserve)?;
571				let min_xcm_fee = T::MinXcmFee::get(&reserve_location).ok_or(Error::<T>::MinXcmFeeNotDefined)?;
572
573				// min xcm fee should less than user fee
574				let fee_to_dest: Asset = (fee.id.clone(), min_xcm_fee).into();
575				ensure!(fee_to_dest < fee, Error::<T>::FeeNotEnough);
576
577				let mut assets_to_dest = Assets::new();
578				for i in 0..asset_len {
579					let asset = assets.get(i).ok_or(Error::<T>::AssetIndexNonExistent)?;
580					if fee != *asset {
581						assets_to_dest.push(asset.clone());
582					} else {
583						assets_to_dest.push(fee_to_dest.clone());
584					}
585				}
586
587				let mut assets_to_fee_reserve = Assets::new();
588				let asset_to_fee_reserve = subtract_fee(&fee, min_xcm_fee);
589				assets_to_fee_reserve.push(asset_to_fee_reserve.clone());
590
591				let mut override_recipient = T::SelfLocation::get();
592				if override_recipient == Location::here() {
593					let dest_chain_part = dest.chain_part().ok_or(Error::<T>::InvalidDest)?;
594					let ancestry = T::UniversalLocation::get();
595					let _ = override_recipient
596						.reanchor(&dest_chain_part, &ancestry)
597						.map_err(|_| Error::<T>::CannotReanchor);
598				}
599
600				// First xcm sent to fee reserve chain and routed to dest chain.
601				// We can use `MinXcmFee` configuration to decide which target parachain use
602				// teleport. But as current there's only one case which is Parachain send back
603				// asset to Statemine/t, So we set `use_teleport` to always `true` in this case.
604				Self::execute_and_send_reserve_kind_xcm(
605					origin_location.clone(),
606					assets_to_fee_reserve,
607					asset_to_fee_reserve,
608					fee_reserve,
609					&dest,
610					Some(override_recipient),
611					dest_weight_limit.clone(),
612					true,
613				)?;
614
615				// Second xcm send to dest chain.
616				Self::execute_and_send_reserve_kind_xcm(
617					origin_location,
618					assets_to_dest,
619					fee_to_dest,
620					non_fee_reserve,
621					&dest,
622					None,
623					dest_weight_limit,
624					false,
625				)?;
626			} else {
627				Self::execute_and_send_reserve_kind_xcm(
628					origin_location,
629					assets.clone(),
630					fee.clone(),
631					fee_reserve,
632					&dest,
633					None,
634					dest_weight_limit,
635					false,
636				)?;
637			}
638
639			Self::deposit_event(Event::<T>::TransferredAssets {
640				sender: who.clone(),
641				assets: assets.clone(),
642				fee: fee.clone(),
643				dest: dest.clone(),
644			});
645
646			Ok(Transferred {
647				sender: who,
648				assets,
649				fee,
650				dest,
651			})
652		}
653
654		/// Execute and send xcm with given assets and fee to dest chain or
655		/// reserve chain.
656		fn execute_and_send_reserve_kind_xcm(
657			origin_location: Location,
658			assets: Assets,
659			fee: Asset,
660			reserve: Option<Location>,
661			dest: &Location,
662			maybe_recipient_override: Option<Location>,
663			dest_weight_limit: WeightLimit,
664			use_teleport: bool,
665		) -> DispatchResult {
666			let (transfer_kind, dest, reserve, recipient) = Self::transfer_kind(reserve, dest)?;
667			let recipient = match maybe_recipient_override {
668				Some(recipient) => recipient,
669				None => recipient,
670			};
671			let mut msg = match transfer_kind {
672				SelfReserveAsset => Self::transfer_self_reserve_asset(assets, fee, dest, recipient, dest_weight_limit)?,
673				ToReserve => Self::transfer_to_reserve(assets, fee, dest, recipient, dest_weight_limit)?,
674				ToNonReserve => Self::transfer_to_non_reserve(
675					assets,
676					fee,
677					reserve,
678					dest,
679					recipient,
680					dest_weight_limit,
681					use_teleport,
682				)?,
683			};
684			let mut hash = msg.using_encoded(sp_io::hashing::blake2_256);
685
686			let weight = T::Weigher::weight(&mut msg, Weight::MAX).map_err(|_| Error::<T>::UnweighableMessage)?;
687			T::XcmExecutor::prepare_and_execute(origin_location, msg, &mut hash, weight, weight)
688				.ensure_complete()
689				.map_err(|error| {
690					log::error!("Failed execute transfer message with {error:?}");
691					Error::<T>::XcmExecutionFailed
692				})?;
693
694			Ok(())
695		}
696
697		fn transfer_self_reserve_asset(
698			assets: Assets,
699			fee: Asset,
700			dest: Location,
701			recipient: Location,
702			dest_weight_limit: WeightLimit,
703		) -> Result<Xcm<T::RuntimeCall>, DispatchError> {
704			Ok(Xcm(vec![
705				SetFeesMode { jit_withdraw: true },
706				TransferReserveAsset {
707					assets: assets.clone(),
708					dest: dest.clone(),
709					xcm: Xcm(vec![
710						Self::buy_execution(fee, &dest, dest_weight_limit)?,
711						Self::deposit_asset(recipient, assets.len() as u32),
712					]),
713				},
714			]))
715		}
716
717		fn transfer_to_reserve(
718			assets: Assets,
719			fee: Asset,
720			reserve: Location,
721			recipient: Location,
722			dest_weight_limit: WeightLimit,
723		) -> Result<Xcm<T::RuntimeCall>, DispatchError> {
724			Ok(Xcm(vec![
725				WithdrawAsset(assets.clone()),
726				SetFeesMode { jit_withdraw: true },
727				InitiateReserveWithdraw {
728					assets: All.into(),
729					reserve: reserve.clone(),
730					xcm: Xcm(vec![
731						Self::buy_execution(fee, &reserve, dest_weight_limit)?,
732						Self::deposit_asset(recipient, assets.len() as u32),
733					]),
734				},
735			]))
736		}
737
738		fn transfer_to_non_reserve(
739			assets: Assets,
740			fee: Asset,
741			reserve: Location,
742			dest: Location,
743			recipient: Location,
744			dest_weight_limit: WeightLimit,
745			use_teleport: bool,
746		) -> Result<Xcm<T::RuntimeCall>, DispatchError> {
747			let mut reanchored_dest = dest.clone();
748			if reserve == Location::parent() {
749				if let (1, [Parachain(id)]) = dest.unpack() {
750					reanchored_dest = Parachain(*id).into();
751				}
752			}
753
754			let max_assets = assets.len() as u32;
755			if !use_teleport {
756				Ok(Xcm(vec![
757					WithdrawAsset(assets),
758					SetFeesMode { jit_withdraw: true },
759					InitiateReserveWithdraw {
760						assets: All.into(),
761						reserve: reserve.clone(),
762						xcm: Xcm(vec![
763							Self::buy_execution(half(&fee), &reserve, dest_weight_limit.clone())?,
764							DepositReserveAsset {
765								assets: AllCounted(max_assets).into(),
766								dest: reanchored_dest,
767								xcm: Xcm(vec![
768									Self::buy_execution(half(&fee), &dest, dest_weight_limit)?,
769									Self::deposit_asset(recipient, max_assets),
770								]),
771							},
772						]),
773					},
774				]))
775			} else {
776				Ok(Xcm(vec![
777					WithdrawAsset(assets),
778					SetFeesMode { jit_withdraw: true },
779					InitiateReserveWithdraw {
780						assets: All.into(),
781						reserve: reserve.clone(),
782						xcm: Xcm(vec![
783							Self::buy_execution(half(&fee), &reserve, dest_weight_limit.clone())?,
784							InitiateTeleport {
785								assets: All.into(),
786								dest: reanchored_dest,
787								xcm: Xcm(vec![
788									Self::buy_execution(half(&fee), &dest, dest_weight_limit)?,
789									Self::deposit_asset(recipient, max_assets),
790								]),
791							},
792						]),
793					},
794				]))
795			}
796		}
797
798		fn deposit_asset(recipient: Location, max_assets: u32) -> Instruction<()> {
799			DepositAsset {
800				assets: AllCounted(max_assets).into(),
801				beneficiary: recipient,
802			}
803		}
804
805		fn buy_execution(
806			asset: Asset,
807			at: &Location,
808			weight_limit: WeightLimit,
809		) -> Result<Instruction<()>, DispatchError> {
810			let ancestry = T::UniversalLocation::get();
811			let fees = asset
812				.reanchored(at, &ancestry)
813				.map_err(|_| Error::<T>::CannotReanchor)?;
814
815			Ok(BuyExecution { fees, weight_limit })
816		}
817
818		/// Ensure has the `dest` has chain part and recipient part.
819		fn ensure_valid_dest(dest: &Location) -> Result<(Location, Location), DispatchError> {
820			if let (Some(dest), Some(recipient)) = (dest.chain_part(), dest.non_chain_part()) {
821				Ok((dest, recipient))
822			} else {
823				Err(Error::<T>::InvalidDest.into())
824			}
825		}
826
827		/// Get the transfer kind.
828		///
829		/// Returns `Err` if `dest` combination doesn't make sense, or `reserve`
830		/// is none, else returns a tuple of:
831		/// - `transfer_kind`.
832		/// - asset's `reserve` parachain or relay chain location,
833		/// - `dest` parachain or relay chain location.
834		/// - `recipient` location.
835		fn transfer_kind(
836			reserve: Option<Location>,
837			dest: &Location,
838		) -> Result<(TransferKind, Location, Location, Location), DispatchError> {
839			let (dest, recipient) = Self::ensure_valid_dest(dest)?;
840
841			let self_location = T::SelfLocation::get();
842			ensure!(dest != self_location, Error::<T>::NotCrossChainTransfer);
843			let reserve = reserve.ok_or(Error::<T>::AssetHasNoReserve)?;
844			let transfer_kind = if reserve == self_location {
845				SelfReserveAsset
846			} else if reserve == dest {
847				ToReserve
848			} else {
849				ToNonReserve
850			};
851			Ok((transfer_kind, dest, reserve, recipient))
852		}
853
854		/// Get reserve location by `assets` and `fee_item`. the `assets`
855		/// includes fee asset and non fee asset. make sure assets have ge one
856		/// asset. all non fee asset should share same reserve location.
857		fn get_reserve_location(assets: &Assets, fee_item: &u32) -> Option<Location> {
858			let reserve_idx = if assets.len() == 1 {
859				0
860			} else {
861				(*fee_item == 0) as usize
862			};
863			let asset = assets.get(reserve_idx);
864			asset.and_then(T::ReserveProvider::reserve)
865		}
866	}
867
868	pub struct XtokensWeight<T>(PhantomData<T>);
869	// weights
870	impl<T: Config> XtokensWeightInfo<T::AccountId, T::Balance, T::CurrencyId> for XtokensWeight<T> {
871		/// Returns weight of `transfer_multiasset` call.
872		fn weight_of_transfer_multiasset(asset: &VersionedAsset, dest: &VersionedLocation) -> Weight {
873			let asset: Result<Asset, _> = asset.clone().try_into();
874			let dest = dest.clone().try_into();
875			if let (Ok(asset), Ok(dest)) = (asset, dest) {
876				if let Ok((transfer_kind, dest, _, reserve)) =
877					Pallet::<T>::transfer_kind(T::ReserveProvider::reserve(&asset), &dest)
878				{
879					let mut msg = match transfer_kind {
880						SelfReserveAsset => Xcm(vec![
881							SetFeesMode { jit_withdraw: true },
882							TransferReserveAsset {
883								assets: vec![asset].into(),
884								dest,
885								xcm: Xcm(vec![]),
886							},
887						]),
888						ToReserve | ToNonReserve => Xcm(vec![
889							WithdrawAsset(Assets::from(asset)),
890							SetFeesMode { jit_withdraw: true },
891							InitiateReserveWithdraw {
892								assets: All.into(),
893								// `dest` is always (equal to) `reserve` in both cases
894								reserve,
895								xcm: Xcm(vec![]),
896							},
897						]),
898					};
899					return T::Weigher::weight(&mut msg, Weight::MAX)
900						.map_or(Weight::max_value(), |w| T::BaseXcmWeight::get().saturating_add(w));
901				}
902			}
903			Weight::zero()
904		}
905
906		/// Returns weight of `transfer` call.
907		fn weight_of_transfer(currency_id: T::CurrencyId, amount: T::Balance, dest: &VersionedLocation) -> Weight {
908			if let Some(location) = T::CurrencyIdConvert::convert(currency_id) {
909				let asset = (location, amount.into()).into();
910				Self::weight_of_transfer_multiasset(&asset, dest)
911			} else {
912				Weight::zero()
913			}
914		}
915
916		/// Returns weight of `transfer` call.
917		fn weight_of_transfer_multicurrencies(
918			currencies: &[(T::CurrencyId, T::Balance)],
919			fee_item: &u32,
920			dest: &VersionedLocation,
921		) -> Weight {
922			let mut assets: Vec<Asset> = Vec::new();
923			for (currency_id, amount) in currencies {
924				if let Some(location) = T::CurrencyIdConvert::convert(currency_id.clone()) {
925					let asset: Asset = (location, (*amount).into()).into();
926					assets.push(asset);
927				} else {
928					return Weight::zero();
929				}
930			}
931
932			Self::weight_of_transfer_multiassets(&VersionedAssets::from(Assets::from(assets)), fee_item, dest)
933		}
934
935		/// Returns weight of `transfer_multiassets` call.
936		fn weight_of_transfer_multiassets(
937			assets: &VersionedAssets,
938			fee_item: &u32,
939			dest: &VersionedLocation,
940		) -> Weight {
941			let assets: Result<Assets, ()> = assets.clone().try_into();
942			let dest = dest.clone().try_into();
943			if let (Ok(assets), Ok(dest)) = (assets, dest) {
944				let reserve_location = Pallet::<T>::get_reserve_location(&assets, fee_item);
945				if let Ok((transfer_kind, dest, _, reserve)) = Pallet::<T>::transfer_kind(reserve_location, &dest) {
946					let mut msg = match transfer_kind {
947						SelfReserveAsset => Xcm(vec![
948							SetFeesMode { jit_withdraw: true },
949							TransferReserveAsset {
950								assets,
951								dest,
952								xcm: Xcm(vec![]),
953							},
954						]),
955						ToReserve | ToNonReserve => Xcm(vec![
956							WithdrawAsset(assets),
957							SetFeesMode { jit_withdraw: true },
958							InitiateReserveWithdraw {
959								assets: All.into(),
960								// `dest` is always (equal to) `reserve` in both cases
961								reserve,
962								xcm: Xcm(vec![]),
963							},
964						]),
965					};
966					return T::Weigher::weight(&mut msg, Weight::MAX)
967						.map_or(Weight::max_value(), |w| T::BaseXcmWeight::get().saturating_add(w));
968				}
969			}
970			Weight::zero()
971		}
972	}
973
974	impl<T: Config> XcmTransfer<T::AccountId, T::Balance, T::CurrencyId> for Pallet<T> {
975		#[require_transactional]
976		fn transfer(
977			who: T::AccountId,
978			currency_id: T::CurrencyId,
979			amount: T::Balance,
980			dest: Location,
981			dest_weight_limit: WeightLimit,
982		) -> Result<Transferred<T::AccountId>, DispatchError> {
983			Self::do_transfer(who, currency_id, amount, dest, dest_weight_limit)
984		}
985
986		#[require_transactional]
987		fn transfer_multiasset(
988			who: T::AccountId,
989			asset: Asset,
990			dest: Location,
991			dest_weight_limit: WeightLimit,
992		) -> Result<Transferred<T::AccountId>, DispatchError> {
993			Self::do_transfer_asset(who, asset, dest, dest_weight_limit)
994		}
995
996		#[require_transactional]
997		fn transfer_with_fee(
998			who: T::AccountId,
999			currency_id: T::CurrencyId,
1000			amount: T::Balance,
1001			fee: T::Balance,
1002			dest: Location,
1003			dest_weight_limit: WeightLimit,
1004		) -> Result<Transferred<T::AccountId>, DispatchError> {
1005			Self::do_transfer_with_fee(who, currency_id, amount, fee, dest, dest_weight_limit)
1006		}
1007
1008		#[require_transactional]
1009		fn transfer_multiasset_with_fee(
1010			who: T::AccountId,
1011			asset: Asset,
1012			fee: Asset,
1013			dest: Location,
1014			dest_weight_limit: WeightLimit,
1015		) -> Result<Transferred<T::AccountId>, DispatchError> {
1016			Self::do_transfer_asset_with_fee(who, asset, fee, dest, dest_weight_limit)
1017		}
1018
1019		#[require_transactional]
1020		fn transfer_multicurrencies(
1021			who: T::AccountId,
1022			currencies: Vec<(T::CurrencyId, T::Balance)>,
1023			fee_item: u32,
1024			dest: Location,
1025			dest_weight_limit: WeightLimit,
1026		) -> Result<Transferred<T::AccountId>, DispatchError> {
1027			Self::do_transfer_multicurrencies(who, currencies, fee_item, dest, dest_weight_limit)
1028		}
1029
1030		#[require_transactional]
1031		fn transfer_multiassets(
1032			who: T::AccountId,
1033			assets: Assets,
1034			fee: Asset,
1035			dest: Location,
1036			dest_weight_limit: WeightLimit,
1037		) -> Result<Transferred<T::AccountId>, DispatchError> {
1038			Self::do_transfer_assets(who, assets, fee, dest, dest_weight_limit)
1039		}
1040	}
1041}
1042
1043/// Returns amount if `asset` is fungible, or zero.
1044fn fungible_amount(asset: &Asset) -> u128 {
1045	if let Fungible(amount) = &asset.fun {
1046		*amount
1047	} else {
1048		Zero::zero()
1049	}
1050}
1051
1052fn half(asset: &Asset) -> Asset {
1053	let half_amount = fungible_amount(asset)
1054		.checked_div(2)
1055		.expect("div 2 can't overflow; qed");
1056	Asset {
1057		fun: Fungible(half_amount),
1058		id: asset.id.clone(),
1059	}
1060}
1061
1062fn subtract_fee(asset: &Asset, amount: u128) -> Asset {
1063	let final_amount = fungible_amount(asset).checked_sub(amount).expect("fee too low; qed");
1064	Asset {
1065		fun: Fungible(final_amount),
1066		id: asset.id.clone(),
1067	}
1068}