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<(AssetId, 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 asset_id,
141 Pallet::<T>::contract_address_from_asset_id(asset_id),
142 U256::from(*amount),
143 asset_status,
144 ))
145 } else {
146 Err(MatchError::AssetNotHandled)
147 }
148 }
149}
150
151#[derive(Decode, Debug, Encode, PartialEq, TypeInfo)]
152pub enum AssetStatus {
153 Active,
155 FrozenXcmDepositAllowed,
157 FrozenXcmDepositForbidden,
159}
160
161impl AssetStatus {
162 pub fn is_frozen(&self) -> bool {
163 matches!(
164 self,
165 AssetStatus::FrozenXcmDepositAllowed | AssetStatus::FrozenXcmDepositForbidden
166 )
167 }
168}
169
170#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
171pub struct EvmForeignAssetInfo {
172 pub asset_id: AssetId,
173 pub xcm_location: Location,
174 pub decimals: u8,
175 pub symbol: BoundedVec<u8, ConstU32<256>>,
176 pub name: BoundedVec<u8, ConstU32<256>>,
177}
178
179#[pallet]
180pub mod pallet {
181 use super::*;
182 use frame_support::traits::{Currency, ReservableCurrency};
183 use pallet_evm::{GasWeightMapping, Runner};
184 use sp_runtime::traits::{AccountIdConversion, AtLeast32BitUnsigned, Convert};
185 use xcm_executor::traits::ConvertLocation;
186 use xcm_executor::traits::Error as MatchError;
187 use xcm_executor::AssetsInHolding;
188
189 #[pallet::pallet]
190 #[pallet::without_storage_info]
191 pub struct Pallet<T>(PhantomData<T>);
192
193 pub const PALLET_ID: frame_support::PalletId = frame_support::PalletId(*b"forgasst");
195
196 #[pallet::config]
197 pub trait Config:
198 frame_system::Config<RuntimeEvent: From<Event<Self>>>
199 + pallet_evm::Config
200 + scale_info::TypeInfo
201 {
202 type AccountIdToH160: Convert<Self::AccountId, H160>;
204
205 type AssetIdFilter: Contains<AssetId>;
207
208 type EvmRunner: Runner<Self>;
210
211 type ConvertLocation: ConvertLocation<Self::AccountId>;
212
213 type ForeignAssetCreatorOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = OriginType>;
215
216 type ForeignAssetFreezerOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = OriginType>;
218
219 type ForeignAssetModifierOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = OriginType>;
221
222 type ForeignAssetUnfreezerOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = OriginType>;
225
226 type OnForeignAssetCreated: ForeignAssetCreatedHook<Location>;
228
229 type MaxForeignAssets: Get<u32>;
231
232 type WeightInfo: WeightInfo;
234
235 type XcmLocationToH160: ConvertLocation<H160>;
237
238 type ForeignAssetCreationDeposit: Get<BalanceOf<Self>>;
240
241 type Balance: Member
243 + Parameter
244 + AtLeast32BitUnsigned
245 + Default
246 + Copy
247 + MaybeSerializeDeserialize
248 + MaxEncodedLen
249 + TypeInfo;
250
251 type Currency: ReservableCurrency<Self::AccountId>;
253 }
254
255 type BalanceOf<T> =
256 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
257
258 pub type AssetBalance = U256;
259 pub type AssetId = u128;
260
261 #[pallet::error]
263 pub enum Error<T> {
264 AssetAlreadyExists,
265 AssetAlreadyFrozen,
266 AssetDoesNotExist,
267 AssetIdFiltered,
268 AssetNotFrozen,
269 CorruptedStorageOrphanLocation,
270 Erc20ContractCreationFail,
271 EvmCallPauseFail,
272 EvmCallUnpauseFail,
273 EvmCallMintIntoFail,
274 EvmCallTransferFail,
275 EvmInternalError,
276 InsufficientBalance,
278 CannotConvertLocationToAccount,
279 LocationOutsideOfOrigin,
280 AssetNotInSiblingPara,
281 InvalidSymbol,
282 InvalidTokenName,
283 LocationAlreadyExists,
284 NoPendingDeposit,
285 AssetNotActive,
286 TooManyForeignAssets,
287 }
288
289 #[pallet::event]
290 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
291 pub enum Event<T: Config> {
292 ForeignAssetCreated {
294 contract_address: H160,
295 asset_id: AssetId,
296 xcm_location: Location,
297 deposit: Option<BalanceOf<T>>,
298 },
299 ForeignAssetXcmLocationChanged {
301 asset_id: AssetId,
302 previous_xcm_location: Location,
303 new_xcm_location: Location,
304 },
305 ForeignAssetFrozen {
307 asset_id: AssetId,
308 xcm_location: Location,
309 },
310 ForeignAssetUnfrozen {
312 asset_id: AssetId,
313 xcm_location: Location,
314 },
315 TokensLocked(T::AccountId, AssetId, AssetBalance),
317 PendingDepositRecorded {
319 asset_id: AssetId,
320 beneficiary: H160,
321 amount: U256,
322 total_pending: U256,
323 },
324 PendingDepositClaimed {
326 asset_id: AssetId,
327 beneficiary: H160,
328 amount: U256,
329 },
330 }
331
332 #[pallet::storage]
336 #[pallet::getter(fn assets_by_id)]
337 pub type AssetsById<T: Config> =
338 CountedStorageMap<_, Blake2_128Concat, AssetId, Location, OptionQuery>;
339
340 #[pallet::storage]
344 #[pallet::getter(fn assets_by_location)]
345 pub type AssetsByLocation<T: Config> =
346 StorageMap<_, Blake2_128Concat, Location, (AssetId, AssetStatus)>;
347
348 #[pallet::storage]
350 #[pallet::getter(fn assets_creation_details)]
351 pub type AssetsCreationDetails<T: Config> =
352 StorageMap<_, Blake2_128Concat, AssetId, AssetDepositDetails<T>>;
353
354 #[pallet::storage]
357 #[pallet::getter(fn pending_deposits)]
358 pub type PendingDeposits<T: Config> =
359 StorageDoubleMap<_, Blake2_128Concat, AssetId, Blake2_128Concat, H160, U256, OptionQuery>;
360
361 #[derive(Clone, Decode, Encode, Eq, PartialEq, Debug, TypeInfo, MaxEncodedLen)]
362 pub struct AssetDepositDetails<T: Config> {
363 pub deposit_account: T::AccountId,
364 pub deposit: BalanceOf<T>,
365 }
366
367 #[pallet::genesis_config]
368 pub struct GenesisConfig<T: Config> {
369 pub assets: Vec<EvmForeignAssetInfo>,
370 pub _phantom: PhantomData<T>,
371 }
372
373 impl<T: Config> Default for GenesisConfig<T> {
374 fn default() -> Self {
375 Self {
376 assets: vec![],
377 _phantom: Default::default(),
378 }
379 }
380 }
381
382 #[pallet::genesis_build]
383 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
384 fn build(&self) {
385 for asset in self.assets.clone() {
386 Pallet::<T>::register_foreign_asset(
387 asset.asset_id,
388 asset.xcm_location,
389 asset.decimals,
390 asset.symbol,
391 asset.name,
392 )
393 .expect("couldn't register asset");
394 }
395 }
396 }
397
398 impl<T: Config> Pallet<T> {
399 #[inline]
401 pub fn account_id() -> H160 {
402 let account_id: T::AccountId = PALLET_ID.into_account_truncating();
403 T::AccountIdToH160::convert(account_id)
404 }
405
406 #[inline]
408 pub fn contract_address_from_asset_id(asset_id: AssetId) -> H160 {
409 let mut buffer = [0u8; 20];
410 buffer[..4].copy_from_slice(&FOREIGN_ASSETS_PREFIX);
411 buffer[4..].copy_from_slice(&asset_id.to_be_bytes());
412 H160(buffer)
413 }
414
415 pub fn register_foreign_asset(
418 asset_id: AssetId,
419 xcm_location: Location,
420 decimals: u8,
421 symbol: BoundedVec<u8, ConstU32<256>>,
422 name: BoundedVec<u8, ConstU32<256>>,
423 ) -> DispatchResult {
424 Self::do_create_asset(asset_id, xcm_location, decimals, symbol, name, None)
425 }
426
427 pub fn mint_into(
429 asset_id: AssetId,
430 beneficiary: T::AccountId,
431 amount: U256,
432 ) -> Result<(), evm::EvmError> {
433 frame_support::storage::with_storage_layer(|| {
436 EvmCaller::<T>::erc20_mint_into(
437 Self::contract_address_from_asset_id(asset_id),
438 T::AccountIdToH160::convert(beneficiary),
439 amount,
440 )
441 })
442 .map_err(Into::into)
443 }
444
445 pub fn transfer(
447 asset_id: AssetId,
448 from: T::AccountId,
449 to: T::AccountId,
450 amount: U256,
451 ) -> Result<(), evm::EvmError> {
452 frame_support::storage::with_storage_layer(|| {
453 EvmCaller::<T>::erc20_transfer(
454 Self::contract_address_from_asset_id(asset_id),
455 T::AccountIdToH160::convert(from),
456 T::AccountIdToH160::convert(to),
457 amount,
458 )
459 })
460 .map_err(Into::into)
461 }
462
463 pub fn balance(asset_id: AssetId, who: T::AccountId) -> Result<U256, evm::EvmError> {
464 EvmCaller::<T>::erc20_balance_of(asset_id, T::AccountIdToH160::convert(who))
465 .map_err(Into::into)
466 }
467
468 pub fn approve(
470 asset_id: AssetId,
471 owner: T::AccountId,
472 spender: T::AccountId,
473 amount: U256,
474 ) -> Result<(), evm::EvmError> {
475 frame_support::storage::with_storage_layer(|| {
478 EvmCaller::<T>::erc20_approve(
479 Self::contract_address_from_asset_id(asset_id),
480 T::AccountIdToH160::convert(owner),
481 T::AccountIdToH160::convert(spender),
482 amount,
483 )
484 })
485 .map_err(Into::into)
486 }
487
488 pub fn weight_of_erc20_burn() -> Weight {
489 T::GasWeightMapping::gas_to_weight(evm::ERC20_BURN_FROM_GAS_LIMIT, true)
490 }
491 pub fn weight_of_erc20_mint() -> Weight {
492 T::GasWeightMapping::gas_to_weight(evm::ERC20_MINT_INTO_GAS_LIMIT, true)
493 }
494 pub fn weight_of_erc20_transfer() -> Weight {
495 T::GasWeightMapping::gas_to_weight(evm::ERC20_TRANSFER_GAS_LIMIT, true)
496 }
497 #[cfg(feature = "runtime-benchmarks")]
498 pub fn set_asset(asset_location: Location, asset_id: AssetId) {
499 AssetsByLocation::<T>::insert(&asset_location, (asset_id, AssetStatus::Active));
500 AssetsById::<T>::insert(&asset_id, asset_location);
501 }
502
503 #[cfg(feature = "runtime-benchmarks")]
504 pub fn create_asset_contract(
505 asset_id: AssetId,
506 decimals: u8,
507 symbol: &str,
508 name: &str,
509 ) -> Result<H160, Error<T>> {
510 EvmCaller::<T>::erc20_create(asset_id, decimals, symbol, name)
511 }
512 }
513
514 #[pallet::call]
515 impl<T: Config> Pallet<T> {
516 #[pallet::call_index(0)]
518 #[pallet::weight(<T as Config>::WeightInfo::create_foreign_asset())]
519 pub fn create_foreign_asset(
520 origin: OriginFor<T>,
521 asset_id: AssetId,
522 asset_xcm_location: Location,
523 decimals: u8,
524 symbol: BoundedVec<u8, ConstU32<256>>,
525 name: BoundedVec<u8, ConstU32<256>>,
526 ) -> DispatchResult {
527 let origin_type = T::ForeignAssetCreatorOrigin::ensure_origin(origin.clone())?;
528
529 Self::ensure_origin_can_modify_location(origin_type.clone(), &asset_xcm_location)?;
530 let deposit_account = Self::get_deposit_account(origin_type)?;
531
532 Self::do_create_asset(
533 asset_id,
534 asset_xcm_location,
535 decimals,
536 symbol,
537 name,
538 deposit_account,
539 )
540 }
541
542 #[pallet::call_index(1)]
546 #[pallet::weight(<T as Config>::WeightInfo::change_xcm_location())]
547 pub fn change_xcm_location(
548 origin: OriginFor<T>,
549 asset_id: AssetId,
550 new_xcm_location: Location,
551 ) -> DispatchResult {
552 let origin_type = T::ForeignAssetModifierOrigin::ensure_origin(origin.clone())?;
553
554 Self::ensure_origin_can_modify_location(origin_type.clone(), &new_xcm_location)?;
555
556 let previous_location =
557 AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
558
559 Self::ensure_origin_can_modify_location(origin_type, &previous_location)?;
560
561 Self::do_change_xcm_location(asset_id, previous_location, new_xcm_location)
562 }
563
564 #[pallet::call_index(2)]
566 #[pallet::weight(<T as Config>::WeightInfo::freeze_foreign_asset())]
567 pub fn freeze_foreign_asset(
568 origin: OriginFor<T>,
569 asset_id: AssetId,
570 allow_xcm_deposit: bool,
571 ) -> DispatchResult {
572 let origin_type = T::ForeignAssetFreezerOrigin::ensure_origin(origin.clone())?;
573
574 let xcm_location =
575 AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
576
577 Self::ensure_origin_can_modify_location(origin_type, &xcm_location)?;
578
579 Self::do_freeze_asset(asset_id, xcm_location, allow_xcm_deposit)
580 }
581
582 #[pallet::call_index(3)]
584 #[pallet::weight(<T as Config>::WeightInfo::unfreeze_foreign_asset())]
585 pub fn unfreeze_foreign_asset(origin: OriginFor<T>, asset_id: AssetId) -> DispatchResult {
586 let origin_type = T::ForeignAssetUnfreezerOrigin::ensure_origin(origin.clone())?;
587
588 let xcm_location =
589 AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
590
591 Self::ensure_origin_can_modify_location(origin_type, &xcm_location)?;
592
593 Self::do_unfreeze_asset(asset_id, xcm_location)
594 }
595
596 #[pallet::call_index(4)]
600 #[pallet::weight(<T as Config>::WeightInfo::claim_pending_deposit())]
601 pub fn claim_pending_deposit(
602 origin: OriginFor<T>,
603 asset_id: AssetId,
604 beneficiary: H160,
605 ) -> DispatchResult {
606 ensure_signed(origin)?;
607
608 let xcm_location =
609 AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
610 let (_id, asset_status) = AssetsByLocation::<T>::get(&xcm_location)
611 .ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
612
613 ensure!(
614 asset_status == AssetStatus::Active,
615 Error::<T>::AssetNotActive
616 );
617
618 let amount = PendingDeposits::<T>::get(asset_id, beneficiary)
619 .ok_or(Error::<T>::NoPendingDeposit)?;
620
621 let contract_address = Self::contract_address_from_asset_id(asset_id);
622
623 frame_support::storage::with_storage_layer(|| {
626 PendingDeposits::<T>::remove(asset_id, beneficiary);
627 EvmCaller::<T>::erc20_mint_into(contract_address, beneficiary, amount)
628 })
629 .map_err(|_| Error::<T>::EvmCallMintIntoFail)?;
630
631 Self::deposit_event(Event::PendingDepositClaimed {
632 asset_id,
633 beneficiary,
634 amount,
635 });
636
637 Ok(())
638 }
639 }
640
641 impl<T: Config> Pallet<T> {
642 fn ensure_origin_can_modify_location(
644 origin_type: OriginType,
645 location: &Location,
646 ) -> DispatchResult {
647 match origin_type {
648 OriginType::XCM(origin_location) => {
649 ensure!(
650 location.starts_with(&origin_location),
651 Error::<T>::LocationOutsideOfOrigin,
652 );
653 }
654 OriginType::Governance => {
655 }
657 };
658 Ok(())
659 }
660
661 fn get_deposit_account(
662 origin_type: OriginType,
663 ) -> Result<Option<T::AccountId>, DispatchError> {
664 match origin_type {
665 OriginType::XCM(origin_location) => {
666 let deposit_account = convert_location::<T>(&origin_location)?;
667 Ok(Some(deposit_account))
668 }
669 OriginType::Governance => Ok(None),
670 }
671 }
672
673 pub fn do_create_asset(
674 asset_id: AssetId,
675 asset_xcm_location: Location,
676 decimals: u8,
677 symbol: BoundedVec<u8, ConstU32<256>>,
678 name: BoundedVec<u8, ConstU32<256>>,
679 deposit_account: Option<T::AccountId>,
680 ) -> DispatchResult {
681 ensure!(
682 !AssetsById::<T>::contains_key(&asset_id),
683 Error::<T>::AssetAlreadyExists
684 );
685
686 ensure!(
687 !AssetsByLocation::<T>::contains_key(&asset_xcm_location),
688 Error::<T>::LocationAlreadyExists
689 );
690
691 ensure!(
692 AssetsById::<T>::count() < T::MaxForeignAssets::get(),
693 Error::<T>::TooManyForeignAssets
694 );
695
696 ensure!(
697 T::AssetIdFilter::contains(&asset_id),
698 Error::<T>::AssetIdFiltered
699 );
700
701 let symbol = core::str::from_utf8(&symbol).map_err(|_| Error::<T>::InvalidSymbol)?;
702 let name = core::str::from_utf8(&name).map_err(|_| Error::<T>::InvalidTokenName)?;
703 let contract_address = EvmCaller::<T>::erc20_create(asset_id, decimals, symbol, name)?;
704
705 let deposit = if let Some(deposit_account) = deposit_account {
706 let deposit = T::ForeignAssetCreationDeposit::get();
707
708 <T as Config>::Currency::reserve(&deposit_account, deposit)?;
710
711 AssetsCreationDetails::<T>::insert(
713 &asset_id,
714 AssetDepositDetails {
715 deposit_account,
716 deposit,
717 },
718 );
719
720 Some(deposit)
721 } else {
722 None
723 };
724
725 AssetsById::<T>::insert(&asset_id, &asset_xcm_location);
728 AssetsByLocation::<T>::insert(&asset_xcm_location, (asset_id, AssetStatus::Active));
729
730 T::OnForeignAssetCreated::on_asset_created(&asset_xcm_location, &asset_id);
731
732 Self::deposit_event(Event::ForeignAssetCreated {
733 contract_address,
734 asset_id,
735 xcm_location: asset_xcm_location,
736 deposit,
737 });
738 Ok(())
739 }
740
741 pub fn do_change_xcm_location(
742 asset_id: AssetId,
743 previous_xcm_location: Location,
744 new_xcm_location: Location,
745 ) -> DispatchResult {
746 ensure!(
747 !AssetsByLocation::<T>::contains_key(&new_xcm_location),
748 Error::<T>::LocationAlreadyExists
749 );
750
751 let (_asset_id, asset_status) = AssetsByLocation::<T>::take(&previous_xcm_location)
753 .ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
754
755 AssetsById::<T>::insert(&asset_id, &new_xcm_location);
757 AssetsByLocation::<T>::insert(&new_xcm_location, (asset_id, asset_status));
758
759 Self::deposit_event(Event::ForeignAssetXcmLocationChanged {
760 asset_id,
761 new_xcm_location,
762 previous_xcm_location,
763 });
764 Ok(())
765 }
766
767 pub fn do_freeze_asset(
768 asset_id: AssetId,
769 xcm_location: Location,
770 allow_xcm_deposit: bool,
771 ) -> DispatchResult {
772 let (_asset_id, asset_status) = AssetsByLocation::<T>::get(&xcm_location)
773 .ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
774
775 ensure!(!asset_status.is_frozen(), Error::<T>::AssetAlreadyFrozen);
776
777 EvmCaller::<T>::erc20_pause(asset_id)?;
778
779 let new_asset_status = if allow_xcm_deposit {
780 AssetStatus::FrozenXcmDepositAllowed
781 } else {
782 AssetStatus::FrozenXcmDepositForbidden
783 };
784
785 AssetsByLocation::<T>::insert(&xcm_location, (asset_id, new_asset_status));
786
787 Self::deposit_event(Event::ForeignAssetFrozen {
788 asset_id,
789 xcm_location,
790 });
791 Ok(())
792 }
793
794 pub fn do_unfreeze_asset(asset_id: AssetId, xcm_location: Location) -> DispatchResult {
795 let (_asset_id, asset_status) = AssetsByLocation::<T>::get(&xcm_location)
796 .ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
797
798 ensure!(asset_status.is_frozen(), Error::<T>::AssetNotFrozen);
799
800 EvmCaller::<T>::erc20_unpause(asset_id)?;
801
802 AssetsByLocation::<T>::insert(&xcm_location, (asset_id, AssetStatus::Active));
803
804 Self::deposit_event(Event::ForeignAssetUnfrozen {
805 asset_id,
806 xcm_location,
807 });
808 Ok(())
809 }
810 }
811
812 impl<T: Config> xcm_executor::traits::TransactAsset for Pallet<T> {
813 fn deposit_asset(what: &Asset, who: &Location, _context: Option<&XcmContext>) -> XcmResult {
817 let (asset_id, contract_address, amount, asset_status) =
818 ForeignAssetsMatcher::<T>::match_asset(what)?;
819
820 if let AssetStatus::FrozenXcmDepositForbidden = asset_status {
821 return Err(XcmError::FailedToTransactAsset(
822 "asset is frozen and XCM deposits are forbidden",
823 ));
824 }
825
826 let beneficiary = T::XcmLocationToH160::convert_location(who)
827 .ok_or(MatchError::AccountIdConversionFailed)?;
828
829 if matches!(asset_status, AssetStatus::FrozenXcmDepositAllowed) {
830 let total_pending = PendingDeposits::<T>::get(asset_id, beneficiary)
831 .unwrap_or(U256::zero())
832 .checked_add(amount)
833 .ok_or(XcmError::Overflow)?;
834
835 PendingDeposits::<T>::insert(asset_id, beneficiary, total_pending);
836
837 Pallet::<T>::deposit_event(Event::PendingDepositRecorded {
838 asset_id,
839 beneficiary,
840 amount,
841 total_pending,
842 });
843 } else {
844 frame_support::storage::with_storage_layer(|| {
847 EvmCaller::<T>::erc20_mint_into(contract_address, beneficiary, amount)
848 })?;
849 }
850
851 Ok(())
852 }
853
854 fn internal_transfer_asset(
855 asset: &Asset,
856 from: &Location,
857 to: &Location,
858 _context: &XcmContext,
859 ) -> Result<AssetsInHolding, XcmError> {
860 let (_asset_id, contract_address, amount, asset_status) =
861 ForeignAssetsMatcher::<T>::match_asset(asset)?;
862
863 if asset_status.is_frozen() {
864 return Err(XcmError::FailedToTransactAsset("asset is frozen"));
865 }
866
867 let from = T::XcmLocationToH160::convert_location(from)
868 .ok_or(MatchError::AccountIdConversionFailed)?;
869
870 let to = T::XcmLocationToH160::convert_location(to)
871 .ok_or(MatchError::AccountIdConversionFailed)?;
872
873 frame_support::storage::with_storage_layer(|| {
876 EvmCaller::<T>::erc20_transfer(contract_address, from, to, amount)
877 })?;
878
879 Ok(asset.clone().into())
880 }
881
882 fn withdraw_asset(
890 what: &Asset,
891 who: &Location,
892 _context: Option<&XcmContext>,
893 ) -> Result<AssetsInHolding, XcmError> {
894 let (_asset_id, contract_address, amount, asset_status) =
895 ForeignAssetsMatcher::<T>::match_asset(what)?;
896 let who = T::XcmLocationToH160::convert_location(who)
897 .ok_or(MatchError::AccountIdConversionFailed)?;
898
899 if asset_status.is_frozen() {
900 return Err(XcmError::FailedToTransactAsset("asset is frozen"));
901 }
902
903 frame_support::storage::with_storage_layer(|| {
906 EvmCaller::<T>::erc20_burn_from(contract_address, who, amount)
907 })?;
908
909 Ok(what.clone().into())
910 }
911
912 #[cfg(feature = "runtime-benchmarks")]
913 fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {
914 Ok(())
916 }
917
918 #[cfg(feature = "runtime-benchmarks")]
919 fn check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) {
920 }
922 }
923
924 impl<T: Config> sp_runtime::traits::MaybeEquivalence<Location, AssetId> for Pallet<T> {
925 fn convert(location: &Location) -> Option<AssetId> {
926 AssetsByLocation::<T>::get(location).map(|(asset_id, _)| asset_id)
927 }
928 fn convert_back(asset_id: &AssetId) -> Option<Location> {
929 AssetsById::<T>::get(asset_id)
930 }
931 }
932}