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 #[cfg(feature = "runtime-benchmarks")]
472 pub fn create_asset_contract(
473 asset_id: AssetId,
474 decimals: u8,
475 symbol: &str,
476 name: &str,
477 ) -> Result<H160, Error<T>> {
478 EvmCaller::<T>::erc20_create(asset_id, decimals, symbol, name)
479 }
480 }
481
482 #[pallet::call]
483 impl<T: Config> Pallet<T> {
484 #[pallet::call_index(0)]
486 #[pallet::weight(<T as Config>::WeightInfo::create_foreign_asset())]
487 pub fn create_foreign_asset(
488 origin: OriginFor<T>,
489 asset_id: AssetId,
490 asset_xcm_location: Location,
491 decimals: u8,
492 symbol: BoundedVec<u8, ConstU32<256>>,
493 name: BoundedVec<u8, ConstU32<256>>,
494 ) -> DispatchResult {
495 let origin_type = T::ForeignAssetCreatorOrigin::ensure_origin(origin.clone())?;
496
497 Self::ensure_origin_can_modify_location(origin_type.clone(), &asset_xcm_location)?;
498 let deposit_account = Self::get_deposit_account(origin_type)?;
499
500 Self::do_create_asset(
501 asset_id,
502 asset_xcm_location,
503 decimals,
504 symbol,
505 name,
506 deposit_account,
507 )
508 }
509
510 #[pallet::call_index(1)]
514 #[pallet::weight(<T as Config>::WeightInfo::change_xcm_location())]
515 pub fn change_xcm_location(
516 origin: OriginFor<T>,
517 asset_id: AssetId,
518 new_xcm_location: Location,
519 ) -> DispatchResult {
520 let origin_type = T::ForeignAssetModifierOrigin::ensure_origin(origin.clone())?;
521
522 Self::ensure_origin_can_modify_location(origin_type.clone(), &new_xcm_location)?;
523
524 let previous_location =
525 AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
526
527 Self::ensure_origin_can_modify_location(origin_type, &previous_location)?;
528
529 Self::do_change_xcm_location(asset_id, previous_location, new_xcm_location)
530 }
531
532 #[pallet::call_index(2)]
534 #[pallet::weight(<T as Config>::WeightInfo::freeze_foreign_asset())]
535 pub fn freeze_foreign_asset(
536 origin: OriginFor<T>,
537 asset_id: AssetId,
538 allow_xcm_deposit: bool,
539 ) -> DispatchResult {
540 let origin_type = T::ForeignAssetFreezerOrigin::ensure_origin(origin.clone())?;
541
542 let xcm_location =
543 AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
544
545 Self::ensure_origin_can_modify_location(origin_type, &xcm_location)?;
546
547 Self::do_freeze_asset(asset_id, xcm_location, allow_xcm_deposit)
548 }
549
550 #[pallet::call_index(3)]
552 #[pallet::weight(<T as Config>::WeightInfo::unfreeze_foreign_asset())]
553 pub fn unfreeze_foreign_asset(origin: OriginFor<T>, asset_id: AssetId) -> DispatchResult {
554 let origin_type = T::ForeignAssetUnfreezerOrigin::ensure_origin(origin.clone())?;
555
556 let xcm_location =
557 AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
558
559 Self::ensure_origin_can_modify_location(origin_type, &xcm_location)?;
560
561 Self::do_unfreeze_asset(asset_id, xcm_location)
562 }
563 }
564
565 impl<T: Config> Pallet<T> {
566 fn ensure_origin_can_modify_location(
568 origin_type: OriginType,
569 location: &Location,
570 ) -> DispatchResult {
571 match origin_type {
572 OriginType::XCM(origin_location) => {
573 ensure!(
574 location.starts_with(&origin_location),
575 Error::<T>::LocationOutsideOfOrigin,
576 );
577 }
578 OriginType::Governance => {
579 }
581 };
582 Ok(())
583 }
584
585 fn get_deposit_account(
586 origin_type: OriginType,
587 ) -> Result<Option<T::AccountId>, DispatchError> {
588 match origin_type {
589 OriginType::XCM(origin_location) => {
590 let deposit_account = convert_location::<T>(&origin_location)?;
591 Ok(Some(deposit_account))
592 }
593 OriginType::Governance => Ok(None),
594 }
595 }
596
597 pub fn do_create_asset(
598 asset_id: AssetId,
599 asset_xcm_location: Location,
600 decimals: u8,
601 symbol: BoundedVec<u8, ConstU32<256>>,
602 name: BoundedVec<u8, ConstU32<256>>,
603 deposit_account: Option<T::AccountId>,
604 ) -> DispatchResult {
605 ensure!(
606 !AssetsById::<T>::contains_key(&asset_id),
607 Error::<T>::AssetAlreadyExists
608 );
609
610 ensure!(
611 !AssetsByLocation::<T>::contains_key(&asset_xcm_location),
612 Error::<T>::LocationAlreadyExists
613 );
614
615 ensure!(
616 AssetsById::<T>::count() < T::MaxForeignAssets::get(),
617 Error::<T>::TooManyForeignAssets
618 );
619
620 ensure!(
621 T::AssetIdFilter::contains(&asset_id),
622 Error::<T>::AssetIdFiltered
623 );
624
625 let symbol = core::str::from_utf8(&symbol).map_err(|_| Error::<T>::InvalidSymbol)?;
626 let name = core::str::from_utf8(&name).map_err(|_| Error::<T>::InvalidTokenName)?;
627 let contract_address = EvmCaller::<T>::erc20_create(asset_id, decimals, symbol, name)?;
628
629 let deposit = if let Some(deposit_account) = deposit_account {
630 let deposit = T::ForeignAssetCreationDeposit::get();
631
632 <T as Config>::Currency::reserve(&deposit_account, deposit)?;
634
635 AssetsCreationDetails::<T>::insert(
637 &asset_id,
638 AssetDepositDetails {
639 deposit_account,
640 deposit,
641 },
642 );
643
644 Some(deposit)
645 } else {
646 None
647 };
648
649 AssetsById::<T>::insert(&asset_id, &asset_xcm_location);
652 AssetsByLocation::<T>::insert(&asset_xcm_location, (asset_id, AssetStatus::Active));
653
654 T::OnForeignAssetCreated::on_asset_created(&asset_xcm_location, &asset_id);
655
656 Self::deposit_event(Event::ForeignAssetCreated {
657 contract_address,
658 asset_id,
659 xcm_location: asset_xcm_location,
660 deposit,
661 });
662 Ok(())
663 }
664
665 pub fn do_change_xcm_location(
666 asset_id: AssetId,
667 previous_xcm_location: Location,
668 new_xcm_location: Location,
669 ) -> DispatchResult {
670 ensure!(
671 !AssetsByLocation::<T>::contains_key(&new_xcm_location),
672 Error::<T>::LocationAlreadyExists
673 );
674
675 let (_asset_id, asset_status) = AssetsByLocation::<T>::take(&previous_xcm_location)
677 .ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
678
679 AssetsById::<T>::insert(&asset_id, &new_xcm_location);
681 AssetsByLocation::<T>::insert(&new_xcm_location, (asset_id, asset_status));
682
683 Self::deposit_event(Event::ForeignAssetXcmLocationChanged {
684 asset_id,
685 new_xcm_location,
686 previous_xcm_location,
687 });
688 Ok(())
689 }
690
691 pub fn do_freeze_asset(
692 asset_id: AssetId,
693 xcm_location: Location,
694 allow_xcm_deposit: bool,
695 ) -> DispatchResult {
696 let (_asset_id, asset_status) = AssetsByLocation::<T>::get(&xcm_location)
697 .ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
698
699 ensure!(
700 asset_status == AssetStatus::Active,
701 Error::<T>::AssetAlreadyFrozen
702 );
703
704 EvmCaller::<T>::erc20_pause(asset_id)?;
705
706 let new_asset_status = if allow_xcm_deposit {
707 AssetStatus::FrozenXcmDepositAllowed
708 } else {
709 AssetStatus::FrozenXcmDepositForbidden
710 };
711
712 AssetsByLocation::<T>::insert(&xcm_location, (asset_id, new_asset_status));
713
714 Self::deposit_event(Event::ForeignAssetFrozen {
715 asset_id,
716 xcm_location,
717 });
718 Ok(())
719 }
720
721 pub fn do_unfreeze_asset(asset_id: AssetId, xcm_location: Location) -> DispatchResult {
722 let (_asset_id, asset_status) = AssetsByLocation::<T>::get(&xcm_location)
723 .ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
724
725 ensure!(
726 asset_status == AssetStatus::FrozenXcmDepositAllowed
727 || asset_status == AssetStatus::FrozenXcmDepositForbidden,
728 Error::<T>::AssetNotFrozen
729 );
730
731 EvmCaller::<T>::erc20_unpause(asset_id)?;
732
733 AssetsByLocation::<T>::insert(&xcm_location, (asset_id, AssetStatus::Active));
734
735 Self::deposit_event(Event::ForeignAssetUnfrozen {
736 asset_id,
737 xcm_location,
738 });
739 Ok(())
740 }
741 }
742
743 impl<T: Config> xcm_executor::traits::TransactAsset for Pallet<T> {
744 fn deposit_asset(what: &Asset, who: &Location, _context: Option<&XcmContext>) -> XcmResult {
748 let (contract_address, amount, asset_status) =
749 ForeignAssetsMatcher::<T>::match_asset(what)?;
750
751 if let AssetStatus::FrozenXcmDepositForbidden = asset_status {
752 return Err(MatchError::AssetNotHandled.into());
753 }
754
755 let beneficiary = T::XcmLocationToH160::convert_location(who)
756 .ok_or(MatchError::AccountIdConversionFailed)?;
757
758 frame_support::storage::with_storage_layer(|| {
761 EvmCaller::<T>::erc20_mint_into(contract_address, beneficiary, amount)
762 })?;
763
764 Ok(())
765 }
766
767 fn internal_transfer_asset(
768 asset: &Asset,
769 from: &Location,
770 to: &Location,
771 _context: &XcmContext,
772 ) -> Result<AssetsInHolding, XcmError> {
773 let (contract_address, amount, asset_status) =
774 ForeignAssetsMatcher::<T>::match_asset(asset)?;
775
776 if let AssetStatus::FrozenXcmDepositForbidden | AssetStatus::FrozenXcmDepositAllowed =
777 asset_status
778 {
779 return Err(MatchError::AssetNotHandled.into());
780 }
781
782 let from = T::XcmLocationToH160::convert_location(from)
783 .ok_or(MatchError::AccountIdConversionFailed)?;
784
785 let to = T::XcmLocationToH160::convert_location(to)
786 .ok_or(MatchError::AccountIdConversionFailed)?;
787
788 frame_support::storage::with_storage_layer(|| {
791 EvmCaller::<T>::erc20_transfer(contract_address, from, to, amount)
792 })?;
793
794 Ok(asset.clone().into())
795 }
796
797 fn withdraw_asset(
805 what: &Asset,
806 who: &Location,
807 _context: Option<&XcmContext>,
808 ) -> Result<AssetsInHolding, XcmError> {
809 let (contract_address, amount, asset_status) =
810 ForeignAssetsMatcher::<T>::match_asset(what)?;
811 let who = T::XcmLocationToH160::convert_location(who)
812 .ok_or(MatchError::AccountIdConversionFailed)?;
813
814 if let AssetStatus::FrozenXcmDepositForbidden | AssetStatus::FrozenXcmDepositAllowed =
815 asset_status
816 {
817 return Err(MatchError::AssetNotHandled.into());
818 }
819
820 frame_support::storage::with_storage_layer(|| {
823 EvmCaller::<T>::erc20_burn_from(contract_address, who, amount)
824 })?;
825
826 Ok(what.clone().into())
827 }
828
829 #[cfg(feature = "runtime-benchmarks")]
830 fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {
831 Ok(())
833 }
834
835 #[cfg(feature = "runtime-benchmarks")]
836 fn check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) {
837 }
839 }
840
841 impl<T: Config> sp_runtime::traits::MaybeEquivalence<Location, AssetId> for Pallet<T> {
842 fn convert(location: &Location) -> Option<AssetId> {
843 AssetsByLocation::<T>::get(location).map(|(asset_id, _)| asset_id)
844 }
845 fn convert_back(asset_id: &AssetId) -> Option<Location> {
846 AssetsById::<T>::get(asset_id)
847 }
848 }
849}