1#![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 SelfReserveAsset,
67 ToReserve,
69 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 type Balance: Parameter
82 + Member
83 + AtLeast32BitUnsigned
84 + Default
85 + Copy
86 + MaybeSerializeDeserialize
87 + Into<u128>;
88
89 type CurrencyId: Parameter + Member + Clone;
91
92 type CurrencyIdConvert: Convert<Self::CurrencyId, Option<Location>>;
94
95 type AccountIdToLocation: Convert<Self::AccountId, Location>;
97
98 #[pallet::constant]
100 type SelfLocation: Get<Location>;
101
102 type MinXcmFee: GetByKey<Location, Option<u128>>;
104
105 type XcmExecutor: ExecuteXcm<Self::RuntimeCall>;
107
108 type LocationsFilter: Contains<Location>;
110
111 type Weigher: WeightBounds<Self::RuntimeCall>;
113
114 #[pallet::constant]
119 type BaseXcmWeight: Get<Weight>;
120
121 type UniversalLocation: Get<InteriorLocation>;
123
124 type MaxAssetsForTransfer: Get<usize>;
127
128 type ReserveProvider: Reserve;
131
132 type RateLimiter: RateLimiter;
134
135 #[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 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 AssetHasNoReserve,
156 NotCrossChainTransfer,
158 InvalidDest,
160 NotCrossChainTransferableCurrency,
162 UnweighableMessage,
164 XcmExecutionFailed,
166 CannotReanchor,
169 InvalidAncestry,
171 InvalidAsset,
173 DestinationNotInvertible,
175 BadVersion,
178 DistinctReserveForAssetAndFee,
181 ZeroFee,
183 ZeroAmount,
185 TooManyAssetsBeingSent,
187 AssetIndexNonExistent,
189 FeeNotEnough,
191 NotSupportedLocation,
193 MinXcmFeeNotDefined,
195 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 #[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 #[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 #[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 #[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 #[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 #[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 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 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 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 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 ¤cies {
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 assets.push((location, (*amount).into()).into())
494 }
495
496 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 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 if non_fee_reserve.is_none() && asset.id != fee.id {
540 non_fee_reserve = T::ReserveProvider::reserve(asset);
541 }
542
543 if non_fee_reserve.is_some() {
545 ensure!(
546 non_fee_reserve == T::ReserveProvider::reserve(asset),
547 Error::<T>::DistinctReserveForAssetAndFee
548 );
549 }
550
551 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 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 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 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 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 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 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 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 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 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 impl<T: Config> XtokensWeightInfo<T::AccountId, T::Balance, T::CurrencyId> for XtokensWeight<T> {
871 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 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 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 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 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 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
1043fn 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}