1#![cfg_attr(not(feature = "std"), no_std)]
39
40#[cfg(any(test, feature = "runtime-benchmarks"))]
41pub mod benchmarks;
42#[cfg(feature = "runtime-benchmarks")]
43pub use benchmarks::*;
44#[cfg(test)]
45pub mod mock;
46#[cfg(test)]
47pub mod tests;
48pub mod weights;
49
50mod evm;
51
52pub use pallet::*;
53pub use weights::WeightInfo;
54
55use self::evm::EvmCaller;
56use ethereum_types::{H160, U256};
57use frame_support::pallet_prelude::*;
58use frame_support::traits::Contains;
59use frame_support::{pallet, Deserialize, Serialize};
60use frame_system::pallet_prelude::*;
61use sp_std::{vec, vec::Vec};
62use xcm::latest::{
63 Asset, AssetId as XcmAssetId, Error as XcmError, Fungibility, Location, Result as XcmResult,
64 XcmContext,
65};
66use xcm::prelude::Parachain;
67use xcm_executor::traits::ConvertLocation;
68use xcm_executor::traits::Error as MatchError;
69
70const FOREIGN_ASSETS_PREFIX: [u8; 4] = [0xff, 0xff, 0xff, 0xff];
71
72pub trait ForeignAssetCreatedHook<ForeignAsset> {
74 fn on_asset_created(foreign_asset: &ForeignAsset, asset_id: &AssetId);
75}
76
77impl<ForeignAsset> ForeignAssetCreatedHook<ForeignAsset> for () {
78 fn on_asset_created(_foreign_asset: &ForeignAsset, _asset_id: &AssetId) {}
79}
80
81fn convert_location<T>(location: &Location) -> Result<T::AccountId, DispatchError>
83where
84 T: Config,
85{
86 match location.unpack() {
87 (1, [Parachain(_)]) => T::ConvertLocation::convert_location(location)
88 .ok_or(Error::<T>::CannotConvertLocationToAccount.into()),
89 _ => Err(DispatchError::BadOrigin.into()),
90 }
91}
92#[derive(Decode, Encode, Debug, PartialEq, TypeInfo, Clone)]
93pub enum OriginType {
94 XCM(Location),
95 Governance,
96}
97
98pub struct MapSuccessToGovernance<Original>(PhantomData<Original>);
100impl<O, Original: EnsureOrigin<O, Success = ()>> EnsureOrigin<O>
101 for MapSuccessToGovernance<Original>
102{
103 type Success = OriginType;
104 fn try_origin(o: O) -> Result<OriginType, O> {
105 Original::try_origin(o)?;
106 Ok(OriginType::Governance)
107 }
108 #[cfg(feature = "runtime-benchmarks")]
109 fn try_successful_origin() -> Result<O, ()> {
110 Original::try_successful_origin()
111 }
112}
113
114pub struct MapSuccessToXcm<Original>(PhantomData<Original>);
116impl<O, Original: EnsureOrigin<O, Success = Location>> EnsureOrigin<O>
117 for MapSuccessToXcm<Original>
118{
119 type Success = OriginType;
120 fn try_origin(o: O) -> Result<OriginType, O> {
121 Original::try_origin(o).map(OriginType::XCM)
122 }
123 #[cfg(feature = "runtime-benchmarks")]
124 fn try_successful_origin() -> Result<O, ()> {
125 Original::try_successful_origin()
126 }
127}
128
129pub(crate) struct ForeignAssetsMatcher<T>(core::marker::PhantomData<T>);
130
131impl<T: crate::Config> ForeignAssetsMatcher<T> {
132 fn match_asset(asset: &Asset) -> Result<(H160, U256, AssetStatus), MatchError> {
133 let (amount, location) = match (&asset.fun, &asset.id) {
134 (Fungibility::Fungible(ref amount), XcmAssetId(ref location)) => (amount, location),
135 _ => return Err(MatchError::AssetNotHandled),
136 };
137
138 if let Some((asset_id, asset_status)) = AssetsByLocation::<T>::get(&location) {
139 Ok((
140 Pallet::<T>::contract_address_from_asset_id(asset_id),
141 U256::from(*amount),
142 asset_status,
143 ))
144 } else {
145 Err(MatchError::AssetNotHandled)
146 }
147 }
148}
149
150#[derive(Decode, Debug, Encode, PartialEq, TypeInfo)]
151pub enum AssetStatus {
152 Active,
154 FrozenXcmDepositAllowed,
156 FrozenXcmDepositForbidden,
158}
159
160#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
161pub struct EvmForeignAssetInfo {
162 pub asset_id: AssetId,
163 pub xcm_location: Location,
164 pub decimals: u8,
165 pub symbol: BoundedVec<u8, ConstU32<256>>,
166 pub name: BoundedVec<u8, ConstU32<256>>,
167}
168
169#[pallet]
170pub mod pallet {
171 use super::*;
172 use frame_support::traits::{Currency, ReservableCurrency};
173 use pallet_evm::{GasWeightMapping, Runner};
174 use sp_runtime::traits::{AccountIdConversion, AtLeast32BitUnsigned, Convert};
175 use xcm_executor::traits::ConvertLocation;
176 use xcm_executor::traits::Error as MatchError;
177 use xcm_executor::AssetsInHolding;
178
179 #[pallet::pallet]
180 #[pallet::without_storage_info]
181 pub struct Pallet<T>(PhantomData<T>);
182
183 pub const PALLET_ID: frame_support::PalletId = frame_support::PalletId(*b"forgasst");
185
186 #[pallet::config]
187 pub trait Config:
188 frame_system::Config<RuntimeEvent: From<Event<Self>>>
189 + pallet_evm::Config
190 + scale_info::TypeInfo
191 {
192 type AccountIdToH160: Convert<Self::AccountId, H160>;
194
195 type AssetIdFilter: Contains<AssetId>;
197
198 type EvmRunner: Runner<Self>;
200
201 type ConvertLocation: ConvertLocation<Self::AccountId>;
202
203 type ForeignAssetCreatorOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = OriginType>;
205
206 type ForeignAssetFreezerOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = OriginType>;
208
209 type ForeignAssetModifierOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = OriginType>;
211
212 type ForeignAssetUnfreezerOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = OriginType>;
215
216 type OnForeignAssetCreated: ForeignAssetCreatedHook<Location>;
218
219 type MaxForeignAssets: Get<u32>;
221
222 type WeightInfo: WeightInfo;
224
225 type XcmLocationToH160: ConvertLocation<H160>;
227
228 type ForeignAssetCreationDeposit: Get<BalanceOf<Self>>;
230
231 type Balance: Member
233 + Parameter
234 + AtLeast32BitUnsigned
235 + Default
236 + Copy
237 + MaybeSerializeDeserialize
238 + MaxEncodedLen
239 + TypeInfo;
240
241 type Currency: ReservableCurrency<Self::AccountId>;
243 }
244
245 type BalanceOf<T> =
246 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
247
248 pub type AssetBalance = U256;
249 pub type AssetId = u128;
250
251 #[pallet::error]
253 pub enum Error<T> {
254 AssetAlreadyExists,
255 AssetAlreadyFrozen,
256 AssetDoesNotExist,
257 AssetIdFiltered,
258 AssetNotFrozen,
259 CorruptedStorageOrphanLocation,
260 Erc20ContractCreationFail,
261 EvmCallPauseFail,
262 EvmCallUnpauseFail,
263 EvmCallMintIntoFail,
264 EvmCallTransferFail,
265 EvmInternalError,
266 InsufficientBalance,
268 CannotConvertLocationToAccount,
269 LocationOutsideOfOrigin,
270 AssetNotInSiblingPara,
271 InvalidSymbol,
272 InvalidTokenName,
273 LocationAlreadyExists,
274 TooManyForeignAssets,
275 }
276
277 #[pallet::event]
278 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
279 pub enum Event<T: Config> {
280 ForeignAssetCreated {
282 contract_address: H160,
283 asset_id: AssetId,
284 xcm_location: Location,
285 deposit: Option<BalanceOf<T>>,
286 },
287 ForeignAssetXcmLocationChanged {
289 asset_id: AssetId,
290 previous_xcm_location: Location,
291 new_xcm_location: Location,
292 },
293 ForeignAssetFrozen {
295 asset_id: AssetId,
296 xcm_location: Location,
297 },
298 ForeignAssetUnfrozen {
300 asset_id: AssetId,
301 xcm_location: Location,
302 },
303 TokensLocked(T::AccountId, AssetId, AssetBalance),
305 }
306
307 #[pallet::storage]
311 #[pallet::getter(fn assets_by_id)]
312 pub type AssetsById<T: Config> =
313 CountedStorageMap<_, Blake2_128Concat, AssetId, Location, OptionQuery>;
314
315 #[pallet::storage]
319 #[pallet::getter(fn assets_by_location)]
320 pub type AssetsByLocation<T: Config> =
321 StorageMap<_, Blake2_128Concat, Location, (AssetId, AssetStatus)>;
322
323 #[pallet::storage]
325 #[pallet::getter(fn assets_creation_details)]
326 pub type AssetsCreationDetails<T: Config> =
327 StorageMap<_, Blake2_128Concat, AssetId, AssetDepositDetails<T>>;
328
329 #[derive(Clone, Decode, Encode, Eq, PartialEq, Debug, TypeInfo, MaxEncodedLen)]
330 pub struct AssetDepositDetails<T: Config> {
331 pub deposit_account: T::AccountId,
332 pub deposit: BalanceOf<T>,
333 }
334
335 #[pallet::genesis_config]
336 pub struct GenesisConfig<T: Config> {
337 pub assets: Vec<EvmForeignAssetInfo>,
338 pub _phantom: PhantomData<T>,
339 }
340
341 impl<T: Config> Default for GenesisConfig<T> {
342 fn default() -> Self {
343 Self {
344 assets: vec![],
345 _phantom: Default::default(),
346 }
347 }
348 }
349
350 #[pallet::genesis_build]
351 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
352 fn build(&self) {
353 for asset in self.assets.clone() {
354 Pallet::<T>::register_foreign_asset(
355 asset.asset_id,
356 asset.xcm_location,
357 asset.decimals,
358 asset.symbol,
359 asset.name,
360 )
361 .expect("couldn't register asset");
362 }
363 }
364 }
365
366 impl<T: Config> Pallet<T> {
367 #[inline]
369 pub fn account_id() -> H160 {
370 let account_id: T::AccountId = PALLET_ID.into_account_truncating();
371 T::AccountIdToH160::convert(account_id)
372 }
373
374 #[inline]
376 pub fn contract_address_from_asset_id(asset_id: AssetId) -> H160 {
377 let mut buffer = [0u8; 20];
378 buffer[..4].copy_from_slice(&FOREIGN_ASSETS_PREFIX);
379 buffer[4..].copy_from_slice(&asset_id.to_be_bytes());
380 H160(buffer)
381 }
382
383 pub fn register_foreign_asset(
386 asset_id: AssetId,
387 xcm_location: Location,
388 decimals: u8,
389 symbol: BoundedVec<u8, ConstU32<256>>,
390 name: BoundedVec<u8, ConstU32<256>>,
391 ) -> DispatchResult {
392 Self::do_create_asset(asset_id, xcm_location, decimals, symbol, name, None)
393 }
394
395 pub fn mint_into(
397 asset_id: AssetId,
398 beneficiary: T::AccountId,
399 amount: U256,
400 ) -> Result<(), evm::EvmError> {
401 frame_support::storage::with_storage_layer(|| {
404 EvmCaller::<T>::erc20_mint_into(
405 Self::contract_address_from_asset_id(asset_id),
406 T::AccountIdToH160::convert(beneficiary),
407 amount,
408 )
409 })
410 .map_err(Into::into)
411 }
412
413 pub fn transfer(
415 asset_id: AssetId,
416 from: T::AccountId,
417 to: T::AccountId,
418 amount: U256,
419 ) -> Result<(), evm::EvmError> {
420 frame_support::storage::with_storage_layer(|| {
421 EvmCaller::<T>::erc20_transfer(
422 Self::contract_address_from_asset_id(asset_id),
423 T::AccountIdToH160::convert(from),
424 T::AccountIdToH160::convert(to),
425 amount,
426 )
427 })
428 .map_err(Into::into)
429 }
430
431 pub fn balance(asset_id: AssetId, who: T::AccountId) -> Result<U256, evm::EvmError> {
432 EvmCaller::<T>::erc20_balance_of(asset_id, T::AccountIdToH160::convert(who))
433 .map_err(Into::into)
434 }
435
436 pub fn approve(
438 asset_id: AssetId,
439 owner: T::AccountId,
440 spender: T::AccountId,
441 amount: U256,
442 ) -> Result<(), evm::EvmError> {
443 frame_support::storage::with_storage_layer(|| {
446 EvmCaller::<T>::erc20_approve(
447 Self::contract_address_from_asset_id(asset_id),
448 T::AccountIdToH160::convert(owner),
449 T::AccountIdToH160::convert(spender),
450 amount,
451 )
452 })
453 .map_err(Into::into)
454 }
455
456 pub fn weight_of_erc20_burn() -> Weight {
457 T::GasWeightMapping::gas_to_weight(evm::ERC20_BURN_FROM_GAS_LIMIT, true)
458 }
459 pub fn weight_of_erc20_mint() -> Weight {
460 T::GasWeightMapping::gas_to_weight(evm::ERC20_MINT_INTO_GAS_LIMIT, true)
461 }
462 pub fn weight_of_erc20_transfer() -> Weight {
463 T::GasWeightMapping::gas_to_weight(evm::ERC20_TRANSFER_GAS_LIMIT, true)
464 }
465 #[cfg(feature = "runtime-benchmarks")]
466 pub fn set_asset(asset_location: Location, asset_id: AssetId) {
467 AssetsByLocation::<T>::insert(&asset_location, (asset_id, AssetStatus::Active));
468 AssetsById::<T>::insert(&asset_id, asset_location);
469 }
470 }
471
472 #[pallet::call]
473 impl<T: Config> Pallet<T> {
474 #[pallet::call_index(0)]
476 #[pallet::weight(<T as Config>::WeightInfo::create_foreign_asset())]
477 pub fn create_foreign_asset(
478 origin: OriginFor<T>,
479 asset_id: AssetId,
480 asset_xcm_location: Location,
481 decimals: u8,
482 symbol: BoundedVec<u8, ConstU32<256>>,
483 name: BoundedVec<u8, ConstU32<256>>,
484 ) -> DispatchResult {
485 let origin_type = T::ForeignAssetCreatorOrigin::ensure_origin(origin.clone())?;
486
487 Self::ensure_origin_can_modify_location(origin_type.clone(), &asset_xcm_location)?;
488 let deposit_account = Self::get_deposit_account(origin_type)?;
489
490 Self::do_create_asset(
491 asset_id,
492 asset_xcm_location,
493 decimals,
494 symbol,
495 name,
496 deposit_account,
497 )
498 }
499
500 #[pallet::call_index(1)]
504 #[pallet::weight(<T as Config>::WeightInfo::change_xcm_location())]
505 pub fn change_xcm_location(
506 origin: OriginFor<T>,
507 asset_id: AssetId,
508 new_xcm_location: Location,
509 ) -> DispatchResult {
510 let origin_type = T::ForeignAssetModifierOrigin::ensure_origin(origin.clone())?;
511
512 Self::ensure_origin_can_modify_location(origin_type.clone(), &new_xcm_location)?;
513
514 let previous_location =
515 AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
516
517 Self::ensure_origin_can_modify_location(origin_type, &previous_location)?;
518
519 Self::do_change_xcm_location(asset_id, previous_location, new_xcm_location)
520 }
521
522 #[pallet::call_index(2)]
524 #[pallet::weight(<T as Config>::WeightInfo::freeze_foreign_asset())]
525 pub fn freeze_foreign_asset(
526 origin: OriginFor<T>,
527 asset_id: AssetId,
528 allow_xcm_deposit: bool,
529 ) -> DispatchResult {
530 let origin_type = T::ForeignAssetFreezerOrigin::ensure_origin(origin.clone())?;
531
532 let xcm_location =
533 AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
534
535 Self::ensure_origin_can_modify_location(origin_type, &xcm_location)?;
536
537 Self::do_freeze_asset(asset_id, xcm_location, allow_xcm_deposit)
538 }
539
540 #[pallet::call_index(3)]
542 #[pallet::weight(<T as Config>::WeightInfo::unfreeze_foreign_asset())]
543 pub fn unfreeze_foreign_asset(origin: OriginFor<T>, asset_id: AssetId) -> DispatchResult {
544 let origin_type = T::ForeignAssetUnfreezerOrigin::ensure_origin(origin.clone())?;
545
546 let xcm_location =
547 AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
548
549 Self::ensure_origin_can_modify_location(origin_type, &xcm_location)?;
550
551 Self::do_unfreeze_asset(asset_id, xcm_location)
552 }
553 }
554
555 impl<T: Config> Pallet<T> {
556 fn ensure_origin_can_modify_location(
558 origin_type: OriginType,
559 location: &Location,
560 ) -> DispatchResult {
561 match origin_type {
562 OriginType::XCM(origin_location) => {
563 ensure!(
564 location.starts_with(&origin_location),
565 Error::<T>::LocationOutsideOfOrigin,
566 );
567 }
568 OriginType::Governance => {
569 }
571 };
572 Ok(())
573 }
574
575 fn get_deposit_account(
576 origin_type: OriginType,
577 ) -> Result<Option<T::AccountId>, DispatchError> {
578 match origin_type {
579 OriginType::XCM(origin_location) => {
580 let deposit_account = convert_location::<T>(&origin_location)?;
581 Ok(Some(deposit_account))
582 }
583 OriginType::Governance => Ok(None),
584 }
585 }
586
587 pub fn do_create_asset(
588 asset_id: AssetId,
589 asset_xcm_location: Location,
590 decimals: u8,
591 symbol: BoundedVec<u8, ConstU32<256>>,
592 name: BoundedVec<u8, ConstU32<256>>,
593 deposit_account: Option<T::AccountId>,
594 ) -> DispatchResult {
595 ensure!(
596 !AssetsById::<T>::contains_key(&asset_id),
597 Error::<T>::AssetAlreadyExists
598 );
599
600 ensure!(
601 !AssetsByLocation::<T>::contains_key(&asset_xcm_location),
602 Error::<T>::LocationAlreadyExists
603 );
604
605 ensure!(
606 AssetsById::<T>::count() < T::MaxForeignAssets::get(),
607 Error::<T>::TooManyForeignAssets
608 );
609
610 ensure!(
611 T::AssetIdFilter::contains(&asset_id),
612 Error::<T>::AssetIdFiltered
613 );
614
615 let symbol = core::str::from_utf8(&symbol).map_err(|_| Error::<T>::InvalidSymbol)?;
616 let name = core::str::from_utf8(&name).map_err(|_| Error::<T>::InvalidTokenName)?;
617 let contract_address = EvmCaller::<T>::erc20_create(asset_id, decimals, symbol, name)?;
618
619 let deposit = if let Some(deposit_account) = deposit_account {
620 let deposit = T::ForeignAssetCreationDeposit::get();
621
622 <T as Config>::Currency::reserve(&deposit_account, deposit)?;
624
625 AssetsCreationDetails::<T>::insert(
627 &asset_id,
628 AssetDepositDetails {
629 deposit_account,
630 deposit,
631 },
632 );
633
634 Some(deposit)
635 } else {
636 None
637 };
638
639 AssetsById::<T>::insert(&asset_id, &asset_xcm_location);
642 AssetsByLocation::<T>::insert(&asset_xcm_location, (asset_id, AssetStatus::Active));
643
644 T::OnForeignAssetCreated::on_asset_created(&asset_xcm_location, &asset_id);
645
646 Self::deposit_event(Event::ForeignAssetCreated {
647 contract_address,
648 asset_id,
649 xcm_location: asset_xcm_location,
650 deposit,
651 });
652 Ok(())
653 }
654
655 pub fn do_change_xcm_location(
656 asset_id: AssetId,
657 previous_xcm_location: Location,
658 new_xcm_location: Location,
659 ) -> DispatchResult {
660 ensure!(
661 !AssetsByLocation::<T>::contains_key(&new_xcm_location),
662 Error::<T>::LocationAlreadyExists
663 );
664
665 let (_asset_id, asset_status) = AssetsByLocation::<T>::take(&previous_xcm_location)
667 .ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
668
669 AssetsById::<T>::insert(&asset_id, &new_xcm_location);
671 AssetsByLocation::<T>::insert(&new_xcm_location, (asset_id, asset_status));
672
673 Self::deposit_event(Event::ForeignAssetXcmLocationChanged {
674 asset_id,
675 new_xcm_location,
676 previous_xcm_location,
677 });
678 Ok(())
679 }
680
681 pub fn do_freeze_asset(
682 asset_id: AssetId,
683 xcm_location: Location,
684 allow_xcm_deposit: bool,
685 ) -> DispatchResult {
686 let (_asset_id, asset_status) = AssetsByLocation::<T>::get(&xcm_location)
687 .ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
688
689 ensure!(
690 asset_status == AssetStatus::Active,
691 Error::<T>::AssetAlreadyFrozen
692 );
693
694 EvmCaller::<T>::erc20_pause(asset_id)?;
695
696 let new_asset_status = if allow_xcm_deposit {
697 AssetStatus::FrozenXcmDepositAllowed
698 } else {
699 AssetStatus::FrozenXcmDepositForbidden
700 };
701
702 AssetsByLocation::<T>::insert(&xcm_location, (asset_id, new_asset_status));
703
704 Self::deposit_event(Event::ForeignAssetFrozen {
705 asset_id,
706 xcm_location,
707 });
708 Ok(())
709 }
710
711 pub fn do_unfreeze_asset(asset_id: AssetId, xcm_location: Location) -> DispatchResult {
712 let (_asset_id, asset_status) = AssetsByLocation::<T>::get(&xcm_location)
713 .ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
714
715 ensure!(
716 asset_status == AssetStatus::FrozenXcmDepositAllowed
717 || asset_status == AssetStatus::FrozenXcmDepositForbidden,
718 Error::<T>::AssetNotFrozen
719 );
720
721 EvmCaller::<T>::erc20_unpause(asset_id)?;
722
723 AssetsByLocation::<T>::insert(&xcm_location, (asset_id, AssetStatus::Active));
724
725 Self::deposit_event(Event::ForeignAssetUnfrozen {
726 asset_id,
727 xcm_location,
728 });
729 Ok(())
730 }
731 }
732
733 impl<T: Config> xcm_executor::traits::TransactAsset for Pallet<T> {
734 fn deposit_asset(what: &Asset, who: &Location, _context: Option<&XcmContext>) -> XcmResult {
738 let (contract_address, amount, asset_status) =
739 ForeignAssetsMatcher::<T>::match_asset(what)?;
740
741 if let AssetStatus::FrozenXcmDepositForbidden = asset_status {
742 return Err(MatchError::AssetNotHandled.into());
743 }
744
745 let beneficiary = T::XcmLocationToH160::convert_location(who)
746 .ok_or(MatchError::AccountIdConversionFailed)?;
747
748 frame_support::storage::with_storage_layer(|| {
751 EvmCaller::<T>::erc20_mint_into(contract_address, beneficiary, amount)
752 })?;
753
754 Ok(())
755 }
756
757 fn internal_transfer_asset(
758 asset: &Asset,
759 from: &Location,
760 to: &Location,
761 _context: &XcmContext,
762 ) -> Result<AssetsInHolding, XcmError> {
763 let (contract_address, amount, asset_status) =
764 ForeignAssetsMatcher::<T>::match_asset(asset)?;
765
766 if let AssetStatus::FrozenXcmDepositForbidden | AssetStatus::FrozenXcmDepositAllowed =
767 asset_status
768 {
769 return Err(MatchError::AssetNotHandled.into());
770 }
771
772 let from = T::XcmLocationToH160::convert_location(from)
773 .ok_or(MatchError::AccountIdConversionFailed)?;
774
775 let to = T::XcmLocationToH160::convert_location(to)
776 .ok_or(MatchError::AccountIdConversionFailed)?;
777
778 frame_support::storage::with_storage_layer(|| {
781 EvmCaller::<T>::erc20_transfer(contract_address, from, to, amount)
782 })?;
783
784 Ok(asset.clone().into())
785 }
786
787 fn withdraw_asset(
795 what: &Asset,
796 who: &Location,
797 _context: Option<&XcmContext>,
798 ) -> Result<AssetsInHolding, XcmError> {
799 let (contract_address, amount, asset_status) =
800 ForeignAssetsMatcher::<T>::match_asset(what)?;
801 let who = T::XcmLocationToH160::convert_location(who)
802 .ok_or(MatchError::AccountIdConversionFailed)?;
803
804 if let AssetStatus::FrozenXcmDepositForbidden | AssetStatus::FrozenXcmDepositAllowed =
805 asset_status
806 {
807 return Err(MatchError::AssetNotHandled.into());
808 }
809
810 frame_support::storage::with_storage_layer(|| {
813 EvmCaller::<T>::erc20_burn_from(contract_address, who, amount)
814 })?;
815
816 Ok(what.clone().into())
817 }
818 }
819
820 impl<T: Config> sp_runtime::traits::MaybeEquivalence<Location, AssetId> for Pallet<T> {
821 fn convert(location: &Location) -> Option<AssetId> {
822 AssetsByLocation::<T>::get(location).map(|(asset_id, _)| asset_id)
823 }
824 fn convert_back(asset_id: &AssetId) -> Option<Location> {
825 AssetsById::<T>::get(asset_id)
826 }
827 }
828}