1
// Copyright 2025 Moonbeam Foundation.
2
// This file is part of Moonbeam.
3

            
4
// Moonbeam is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Moonbeam is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
//! # Moonbeam Foreign Assets pallet
18
//!
19
//! This pallets allow to create and manage XCM derivative assets (aka. foreign assets).
20
//!
21
//! Each asset is implemented by an evm smart contract that is deployed by this pallet
22
//! The evm smart contract for each asset is trusted by the runtime, and should
23
//! be deployed only by the runtime itself.
24
//!
25
//! This pallet made several assumptions on theses evm smarts contracts:
26
//! - Only this pallet should be able to mint and burn tokens
27
//! - The following selectors should be exposed and callable only by this pallet account:
28
//!   - burnFrom(address, uint256)
29
//!   - mintInto(address, uint256)
30
//!   - pause(address, uint256)
31
//!   - unpause(address, uint256)
32
//! - The smart contract should expose as weel the ERC20.transfer selector
33
//!
34
//! Each asset has a unique identifier that can never change.
35
//! This identifier is named "AssetId", it's an integer (u128).
36
//! This pallet maintain a two-way mapping beetween each AssetId the XCM Location of the asset.
37

            
38
#![cfg_attr(not(feature = "std"), no_std)]
39

            
40
#[cfg(any(test, feature = "runtime-benchmarks"))]
41
pub mod benchmarks;
42
#[cfg(test)]
43
pub mod mock;
44
#[cfg(test)]
45
pub mod tests;
46
pub mod weights;
47

            
48
mod evm;
49

            
50
pub use pallet::*;
51
pub use weights::WeightInfo;
52

            
53
use self::evm::EvmCaller;
54
use ethereum_types::{H160, U256};
55
use frame_support::pallet;
56
use frame_support::pallet_prelude::*;
57
use frame_support::traits::Contains;
58
use frame_system::pallet_prelude::*;
59
use xcm::latest::{
60
	Asset, AssetId as XcmAssetId, Error as XcmError, Fungibility, Location, Result as XcmResult,
61
	XcmContext,
62
};
63
use xcm::prelude::Parachain;
64
use xcm_executor::traits::ConvertLocation;
65
use xcm_executor::traits::Error as MatchError;
66

            
67
const FOREIGN_ASSETS_PREFIX: [u8; 4] = [0xff, 0xff, 0xff, 0xff];
68

            
69
/// Trait for the OnForeignAssetRegistered hook
70
pub trait ForeignAssetCreatedHook<ForeignAsset> {
71
	fn on_asset_created(foreign_asset: &ForeignAsset, asset_id: &AssetId);
72
}
73

            
74
impl<ForeignAsset> ForeignAssetCreatedHook<ForeignAsset> for () {
75
23
	fn on_asset_created(_foreign_asset: &ForeignAsset, _asset_id: &AssetId) {}
76
}
77

            
78
/// Ensure origin location is a sibling
79
3
fn convert_location<T>(location: &Location) -> Result<T::AccountId, DispatchError>
80
3
where
81
3
	T: Config,
82
3
{
83
3
	match location.unpack() {
84
3
		(1, [Parachain(_)]) => T::ConvertLocation::convert_location(location)
85
3
			.ok_or(Error::<T>::CannotConvertLocationToAccount.into()),
86
		_ => Err(DispatchError::BadOrigin.into()),
87
	}
88
3
}
89
#[derive(Decode, Encode, Debug, PartialEq, TypeInfo, Clone)]
90
pub enum OriginType {
91
	XCM(Location),
92
	Governance,
93
}
94

            
95
/// Used to convert the success of an EnsureOrigin into `OriginType::Governance`
96
pub struct MapSuccessToGovernance<Original>(PhantomData<Original>);
97
impl<O, Original: EnsureOrigin<O, Success = ()>> EnsureOrigin<O>
98
	for MapSuccessToGovernance<Original>
99
{
100
	type Success = OriginType;
101
34
	fn try_origin(o: O) -> Result<OriginType, O> {
102
34
		Original::try_origin(o)?;
103
22
		Ok(OriginType::Governance)
104
34
	}
105
	#[cfg(feature = "runtime-benchmarks")]
106
	fn try_successful_origin() -> Result<O, ()> {
107
		Original::try_successful_origin()
108
	}
109
}
110

            
111
/// Used to convert the success of an EnsureOrigin into `OriginType::XCM`
112
pub struct MapSuccessToXcm<Original>(PhantomData<Original>);
113
impl<O, Original: EnsureOrigin<O, Success = Location>> EnsureOrigin<O>
114
	for MapSuccessToXcm<Original>
115
{
116
	type Success = OriginType;
117
22
	fn try_origin(o: O) -> Result<OriginType, O> {
118
22
		Original::try_origin(o).map(OriginType::XCM)
119
22
	}
120
	#[cfg(feature = "runtime-benchmarks")]
121
	fn try_successful_origin() -> Result<O, ()> {
122
		Original::try_successful_origin()
123
	}
124
}
125

            
126
pub(crate) struct ForeignAssetsMatcher<T>(core::marker::PhantomData<T>);
127

            
128
impl<T: crate::Config> ForeignAssetsMatcher<T> {
129
66
	fn match_asset(asset: &Asset) -> Result<(H160, U256, AssetStatus), MatchError> {
130
66
		let (amount, location) = match (&asset.fun, &asset.id) {
131
66
			(Fungibility::Fungible(ref amount), XcmAssetId(ref location)) => (amount, location),
132
			_ => return Err(MatchError::AssetNotHandled),
133
		};
134

            
135
66
		if let Some((asset_id, asset_status)) = AssetsByLocation::<T>::get(&location) {
136
32
			Ok((
137
32
				Pallet::<T>::contract_address_from_asset_id(asset_id),
138
32
				U256::from(*amount),
139
32
				asset_status,
140
32
			))
141
		} else {
142
34
			Err(MatchError::AssetNotHandled)
143
		}
144
66
	}
145
}
146

            
147
189
#[derive(Decode, Debug, Encode, PartialEq, TypeInfo)]
148
pub enum AssetStatus {
149
54
	/// All operations are enabled
150
	Active,
151
10
	/// The asset is frozen, but deposit from XCM still work
152
	FrozenXcmDepositAllowed,
153
	/// The asset is frozen, and deposit from XCM will fail
154
	FrozenXcmDepositForbidden,
155
}
156

            
157
1017
#[pallet]
158
pub mod pallet {
159
	use super::*;
160
	use frame_support::traits::{Currency, ReservableCurrency};
161
	use pallet_evm::{GasWeightMapping, Runner};
162
	use sp_runtime::traits::{AccountIdConversion, AtLeast32BitUnsigned, Convert};
163
	use xcm_executor::traits::ConvertLocation;
164
	use xcm_executor::traits::Error as MatchError;
165
	use xcm_executor::AssetsInHolding;
166

            
167
75
	#[pallet::pallet]
168
	#[pallet::without_storage_info]
169
	pub struct Pallet<T>(PhantomData<T>);
170

            
171
	/// The moonbeam foreign assets's pallet id
172
	pub const PALLET_ID: frame_support::PalletId = frame_support::PalletId(*b"forgasst");
173

            
174
	#[pallet::config]
175
	pub trait Config: frame_system::Config + pallet_evm::Config + scale_info::TypeInfo {
176
		// Convert AccountId to H160
177
		type AccountIdToH160: Convert<Self::AccountId, H160>;
178

            
179
		/// A filter to forbid some AssetId values, if you don't use it, put "Everything"
180
		type AssetIdFilter: Contains<AssetId>;
181

            
182
		/// EVM runner
183
		type EvmRunner: Runner<Self>;
184

            
185
		type ConvertLocation: ConvertLocation<Self::AccountId>;
186

            
187
		/// Origin that is allowed to create a new foreign assets
188
		type ForeignAssetCreatorOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = OriginType>;
189

            
190
		/// Origin that is allowed to freeze all tokens of a foreign asset
191
		type ForeignAssetFreezerOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = OriginType>;
192

            
193
		/// Origin that is allowed to modify asset information for foreign assets
194
		type ForeignAssetModifierOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = OriginType>;
195

            
196
		/// Origin that is allowed to unfreeze all tokens of a foreign asset that was previously
197
		/// frozen
198
		type ForeignAssetUnfreezerOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = OriginType>;
199

            
200
		/// Hook to be called when new foreign asset is registered.
201
		type OnForeignAssetCreated: ForeignAssetCreatedHook<Location>;
202

            
203
		/// Maximum numbers of differnt foreign assets
204
		type MaxForeignAssets: Get<u32>;
205

            
206
		/// The overarching event type.
207
		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
208

            
209
		/// Weight information for extrinsics in this pallet.
210
		type WeightInfo: WeightInfo;
211

            
212
		// Convert XCM Location to H160
213
		type XcmLocationToH160: ConvertLocation<H160>;
214

            
215
		/// Amount of tokens required to lock for creating a new foreign asset
216
		type ForeignAssetCreationDeposit: Get<BalanceOf<Self>>;
217

            
218
		/// The balance type for locking funds
219
		type Balance: Member
220
			+ Parameter
221
			+ AtLeast32BitUnsigned
222
			+ Default
223
			+ Copy
224
			+ MaybeSerializeDeserialize
225
			+ MaxEncodedLen
226
			+ TypeInfo;
227

            
228
		/// The currency type for locking funds
229
		type Currency: ReservableCurrency<Self::AccountId>;
230
	}
231

            
232
	type BalanceOf<T> =
233
		<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
234

            
235
	pub type AssetBalance = U256;
236
	pub type AssetId = u128;
237

            
238
	/// An error that can occur while executing the mapping pallet's logic.
239
25
	#[pallet::error]
240
	pub enum Error<T> {
241
		AssetAlreadyExists,
242
		AssetAlreadyFrozen,
243
		AssetDoesNotExist,
244
		AssetIdFiltered,
245
		AssetNotFrozen,
246
		CorruptedStorageOrphanLocation,
247
		Erc20ContractCreationFail,
248
		EvmCallPauseFail,
249
		EvmCallUnpauseFail,
250
		EvmInternalError,
251
		/// Account has insufficient balance for locking
252
		InsufficientBalance,
253
		CannotConvertLocationToAccount,
254
		LocationOutsideOfOrigin,
255
		AssetNotInSiblingPara,
256
		InvalidSymbol,
257
		InvalidTokenName,
258
		LocationAlreadyExists,
259
		TooManyForeignAssets,
260
	}
261

            
262
	#[pallet::event]
263
41
	#[pallet::generate_deposit(pub(crate) fn deposit_event)]
264
	pub enum Event<T: Config> {
265
3
		/// New asset with the asset manager is registered
266
		ForeignAssetCreated {
267
			contract_address: H160,
268
			asset_id: AssetId,
269
			xcm_location: Location,
270
			deposit: Option<BalanceOf<T>>,
271
		},
272
1
		/// Changed the xcm type mapping for a given asset id
273
		ForeignAssetXcmLocationChanged {
274
			asset_id: AssetId,
275
			previous_xcm_location: Location,
276
			new_xcm_location: Location,
277
		},
278
		// Freezes all tokens of a given asset id
279
		ForeignAssetFrozen {
280
			asset_id: AssetId,
281
			xcm_location: Location,
282
		},
283
		// Thawing a previously frozen asset
284
		ForeignAssetUnfrozen {
285
			asset_id: AssetId,
286
			xcm_location: Location,
287
		},
288
		/// Tokens have been locked for asset creation
289
		TokensLocked(T::AccountId, AssetId, AssetBalance),
290
	}
291

            
292
	/// Mapping from an asset id to a Foreign asset type.
293
	/// This is mostly used when receiving transaction specifying an asset directly,
294
	/// like transferring an asset from this chain to another.
295
382
	#[pallet::storage]
296
	#[pallet::getter(fn assets_by_id)]
297
	pub type AssetsById<T: Config> =
298
		CountedStorageMap<_, Blake2_128Concat, AssetId, Location, OptionQuery>;
299

            
300
	/// Reverse mapping of AssetsById. Mapping from a foreign asset to an asset id.
301
	/// This is mostly used when receiving a multilocation XCM message to retrieve
302
	/// the corresponding asset in which tokens should me minted.
303
192
	#[pallet::storage]
304
	#[pallet::getter(fn assets_by_location)]
305
	pub type AssetsByLocation<T: Config> =
306
		StorageMap<_, Blake2_128Concat, Location, (AssetId, AssetStatus)>;
307

            
308
	/// Mapping from an asset id to its creation details
309
3
	#[pallet::storage]
310
	#[pallet::getter(fn assets_creation_details)]
311
	pub type AssetsCreationDetails<T: Config> =
312
		StorageMap<_, Blake2_128Concat, AssetId, AssetDepositDetails<T>>;
313

            
314
44
	#[derive(Clone, Decode, Encode, Eq, PartialEq, Debug, TypeInfo, MaxEncodedLen)]
315
	pub struct AssetDepositDetails<T: Config> {
316
		pub deposit_account: T::AccountId,
317
		pub deposit: BalanceOf<T>,
318
	}
319

            
320
	impl<T: Config> Pallet<T> {
321
		/// The account ID of this pallet
322
		#[inline]
323
120
		pub fn account_id() -> H160 {
324
120
			let account_id: T::AccountId = PALLET_ID.into_account_truncating();
325
120
			T::AccountIdToH160::convert(account_id)
326
120
		}
327

            
328
		/// Compute asset contract address from asset id
329
		#[inline]
330
114
		pub fn contract_address_from_asset_id(asset_id: AssetId) -> H160 {
331
114
			let mut buffer = [0u8; 20];
332
114
			buffer[..4].copy_from_slice(&FOREIGN_ASSETS_PREFIX);
333
114
			buffer[4..].copy_from_slice(&asset_id.to_be_bytes());
334
114
			H160(buffer)
335
114
		}
336

            
337
		/// This method only exists for migration purposes and will be deleted once the
338
		/// foreign assets migration is finished.
339
17
		pub fn register_foreign_asset(
340
17
			asset_id: AssetId,
341
17
			xcm_location: Location,
342
17
			decimals: u8,
343
17
			symbol: BoundedVec<u8, ConstU32<256>>,
344
17
			name: BoundedVec<u8, ConstU32<256>>,
345
17
		) -> DispatchResult {
346
17
			Self::do_create_asset(asset_id, xcm_location, decimals, symbol, name, None)
347
17
		}
348

            
349
		/// Mint an asset into a specific account
350
15
		pub fn mint_into(
351
15
			asset_id: AssetId,
352
15
			beneficiary: T::AccountId,
353
15
			amount: U256,
354
15
		) -> Result<(), evm::EvmError> {
355
15
			EvmCaller::<T>::erc20_mint_into(
356
15
				Self::contract_address_from_asset_id(asset_id),
357
15
				T::AccountIdToH160::convert(beneficiary),
358
15
				amount,
359
15
			)
360
15
			.map_err(Into::into)
361
15
		}
362

            
363
		/// Aprrove a spender to spend a certain amount of tokens from the owner account
364
13
		pub fn approve(
365
13
			asset_id: AssetId,
366
13
			owner: T::AccountId,
367
13
			spender: T::AccountId,
368
13
			amount: U256,
369
13
		) -> Result<(), evm::EvmError> {
370
13
			EvmCaller::<T>::erc20_approve(
371
13
				Self::contract_address_from_asset_id(asset_id),
372
13
				T::AccountIdToH160::convert(owner),
373
13
				T::AccountIdToH160::convert(spender),
374
13
				amount,
375
13
			)
376
13
			.map_err(Into::into)
377
13
		}
378

            
379
132
		pub fn weight_of_erc20_burn() -> Weight {
380
132
			T::GasWeightMapping::gas_to_weight(evm::ERC20_BURN_FROM_GAS_LIMIT, true)
381
132
		}
382
		pub fn weight_of_erc20_mint() -> Weight {
383
			T::GasWeightMapping::gas_to_weight(evm::ERC20_MINT_INTO_GAS_LIMIT, true)
384
		}
385
20
		pub fn weight_of_erc20_transfer() -> Weight {
386
20
			T::GasWeightMapping::gas_to_weight(evm::ERC20_TRANSFER_GAS_LIMIT, true)
387
20
		}
388
		#[cfg(feature = "runtime-benchmarks")]
389
		pub fn set_asset(asset_location: Location, asset_id: AssetId) {
390
			AssetsByLocation::<T>::insert(&asset_location, (asset_id, AssetStatus::Active));
391
			AssetsById::<T>::insert(&asset_id, asset_location);
392
		}
393
	}
394

            
395
	#[pallet::call]
396
	impl<T: Config> Pallet<T> {
397
		/// Create new asset with the ForeignAssetCreator
398
		#[pallet::call_index(0)]
399
		#[pallet::weight(<T as Config>::WeightInfo::create_foreign_asset())]
400
		pub fn create_foreign_asset(
401
			origin: OriginFor<T>,
402
			asset_id: AssetId,
403
			asset_xcm_location: Location,
404
			decimals: u8,
405
			symbol: BoundedVec<u8, ConstU32<256>>,
406
			name: BoundedVec<u8, ConstU32<256>>,
407
16
		) -> DispatchResult {
408
16
			let origin_type = T::ForeignAssetCreatorOrigin::ensure_origin(origin.clone())?;
409

            
410
15
			Self::ensure_origin_can_modify_location(origin_type.clone(), &asset_xcm_location)?;
411
15
			let deposit_account = Self::get_deposit_account(origin_type)?;
412

            
413
15
			Self::do_create_asset(
414
15
				asset_id,
415
15
				asset_xcm_location,
416
15
				decimals,
417
15
				symbol,
418
15
				name,
419
15
				deposit_account,
420
15
			)
421
		}
422

            
423
		/// Change the xcm type mapping for a given assetId
424
		/// We also change this if the previous units per second where pointing at the old
425
		/// assetType
426
		#[pallet::call_index(1)]
427
		#[pallet::weight(<T as Config>::WeightInfo::change_xcm_location())]
428
		pub fn change_xcm_location(
429
			origin: OriginFor<T>,
430
			asset_id: AssetId,
431
			new_xcm_location: Location,
432
6
		) -> DispatchResult {
433
6
			let origin_type = T::ForeignAssetModifierOrigin::ensure_origin(origin.clone())?;
434

            
435
5
			Self::ensure_origin_can_modify_location(origin_type.clone(), &new_xcm_location)?;
436

            
437
4
			let previous_location =
438
5
				AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
439

            
440
4
			Self::ensure_origin_can_modify_location(origin_type, &previous_location)?;
441

            
442
3
			Self::do_change_xcm_location(asset_id, previous_location, new_xcm_location)
443
		}
444

            
445
		/// Freeze a given foreign assetId
446
		#[pallet::call_index(2)]
447
		#[pallet::weight(<T as Config>::WeightInfo::freeze_foreign_asset())]
448
		pub fn freeze_foreign_asset(
449
			origin: OriginFor<T>,
450
			asset_id: AssetId,
451
			allow_xcm_deposit: bool,
452
9
		) -> DispatchResult {
453
9
			let origin_type = T::ForeignAssetFreezerOrigin::ensure_origin(origin.clone())?;
454

            
455
9
			let xcm_location =
456
9
				AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
457

            
458
9
			Self::ensure_origin_can_modify_location(origin_type, &xcm_location)?;
459

            
460
7
			Self::do_freeze_asset(asset_id, xcm_location, allow_xcm_deposit)
461
		}
462

            
463
		/// Unfreeze a given foreign assetId
464
		#[pallet::call_index(3)]
465
		#[pallet::weight(<T as Config>::WeightInfo::unfreeze_foreign_asset())]
466
6
		pub fn unfreeze_foreign_asset(origin: OriginFor<T>, asset_id: AssetId) -> DispatchResult {
467
6
			let origin_type = T::ForeignAssetUnfreezerOrigin::ensure_origin(origin.clone())?;
468

            
469
6
			let xcm_location =
470
6
				AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
471

            
472
6
			Self::ensure_origin_can_modify_location(origin_type, &xcm_location)?;
473

            
474
6
			Self::do_unfreeze_asset(asset_id, xcm_location)
475
		}
476
	}
477

            
478
	impl<T: Config> Pallet<T> {
479
		/// Ensure that the caller origin can modify the location,
480
39
		fn ensure_origin_can_modify_location(
481
39
			origin_type: OriginType,
482
39
			location: &Location,
483
39
		) -> DispatchResult {
484
39
			match origin_type {
485
14
				OriginType::XCM(origin_location) => {
486
14
					ensure!(
487
14
						location.starts_with(&origin_location),
488
3
						Error::<T>::LocationOutsideOfOrigin,
489
					);
490
				}
491
25
				OriginType::Governance => {
492
25
					// nothing to check Governance can change any asset
493
25
				}
494
			};
495
36
			Ok(())
496
39
		}
497

            
498
15
		fn get_deposit_account(
499
15
			origin_type: OriginType,
500
15
		) -> Result<Option<T::AccountId>, DispatchError> {
501
15
			match origin_type {
502
3
				OriginType::XCM(origin_location) => {
503
3
					let deposit_account = convert_location::<T>(&origin_location)?;
504
3
					Ok(Some(deposit_account))
505
				}
506
12
				OriginType::Governance => Ok(None),
507
			}
508
15
		}
509

            
510
32
		pub fn do_create_asset(
511
32
			asset_id: AssetId,
512
32
			asset_xcm_location: Location,
513
32
			decimals: u8,
514
32
			symbol: BoundedVec<u8, ConstU32<256>>,
515
32
			name: BoundedVec<u8, ConstU32<256>>,
516
32
			deposit_account: Option<T::AccountId>,
517
32
		) -> DispatchResult {
518
32
			ensure!(
519
32
				!AssetsById::<T>::contains_key(&asset_id),
520
1
				Error::<T>::AssetAlreadyExists
521
			);
522

            
523
31
			ensure!(
524
31
				!AssetsByLocation::<T>::contains_key(&asset_xcm_location),
525
1
				Error::<T>::LocationAlreadyExists
526
			);
527

            
528
30
			ensure!(
529
30
				AssetsById::<T>::count() < T::MaxForeignAssets::get(),
530
				Error::<T>::TooManyForeignAssets
531
			);
532

            
533
30
			ensure!(
534
30
				T::AssetIdFilter::contains(&asset_id),
535
				Error::<T>::AssetIdFiltered
536
			);
537

            
538
30
			let symbol = core::str::from_utf8(&symbol).map_err(|_| Error::<T>::InvalidSymbol)?;
539
30
			let name = core::str::from_utf8(&name).map_err(|_| Error::<T>::InvalidTokenName)?;
540
30
			let contract_address = EvmCaller::<T>::erc20_create(asset_id, decimals, symbol, name)?;
541

            
542
30
			let deposit = if let Some(deposit_account) = deposit_account {
543
3
				let deposit = T::ForeignAssetCreationDeposit::get();
544
3

            
545
3
				// Reserve _deposit_ amount of funds from the caller
546
3
				<T as Config>::Currency::reserve(&deposit_account, deposit)?;
547

            
548
				// Insert the amount that is reserved from the user
549
3
				AssetsCreationDetails::<T>::insert(
550
3
					&asset_id,
551
3
					AssetDepositDetails {
552
3
						deposit_account,
553
3
						deposit,
554
3
					},
555
3
				);
556
3

            
557
3
				Some(deposit)
558
			} else {
559
27
				None
560
			};
561

            
562
			// Insert the association assetId->foreigAsset
563
			// Insert the association foreigAsset->assetId
564
30
			AssetsById::<T>::insert(&asset_id, &asset_xcm_location);
565
30
			AssetsByLocation::<T>::insert(&asset_xcm_location, (asset_id, AssetStatus::Active));
566
30

            
567
30
			T::OnForeignAssetCreated::on_asset_created(&asset_xcm_location, &asset_id);
568
30

            
569
30
			Self::deposit_event(Event::ForeignAssetCreated {
570
30
				contract_address,
571
30
				asset_id,
572
30
				xcm_location: asset_xcm_location,
573
30
				deposit,
574
30
			});
575
30
			Ok(())
576
32
		}
577

            
578
3
		pub fn do_change_xcm_location(
579
3
			asset_id: AssetId,
580
3
			previous_xcm_location: Location,
581
3
			new_xcm_location: Location,
582
3
		) -> DispatchResult {
583
3
			ensure!(
584
3
				!AssetsByLocation::<T>::contains_key(&new_xcm_location),
585
1
				Error::<T>::LocationAlreadyExists
586
			);
587

            
588
			// Remove previous foreign asset info
589
2
			let (_asset_id, asset_status) = AssetsByLocation::<T>::take(&previous_xcm_location)
590
2
				.ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
591

            
592
			// Insert new foreign asset info
593
2
			AssetsById::<T>::insert(&asset_id, &new_xcm_location);
594
2
			AssetsByLocation::<T>::insert(&new_xcm_location, (asset_id, asset_status));
595
2

            
596
2
			Self::deposit_event(Event::ForeignAssetXcmLocationChanged {
597
2
				asset_id,
598
2
				new_xcm_location,
599
2
				previous_xcm_location,
600
2
			});
601
2
			Ok(())
602
3
		}
603

            
604
7
		pub fn do_freeze_asset(
605
7
			asset_id: AssetId,
606
7
			xcm_location: Location,
607
7
			allow_xcm_deposit: bool,
608
7
		) -> DispatchResult {
609
7
			let (_asset_id, asset_status) = AssetsByLocation::<T>::get(&xcm_location)
610
7
				.ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
611

            
612
7
			ensure!(
613
7
				asset_status == AssetStatus::Active,
614
2
				Error::<T>::AssetAlreadyFrozen
615
			);
616

            
617
5
			EvmCaller::<T>::erc20_pause(asset_id)?;
618

            
619
5
			let new_asset_status = if allow_xcm_deposit {
620
5
				AssetStatus::FrozenXcmDepositAllowed
621
			} else {
622
				AssetStatus::FrozenXcmDepositForbidden
623
			};
624

            
625
5
			AssetsByLocation::<T>::insert(&xcm_location, (asset_id, new_asset_status));
626
5

            
627
5
			Self::deposit_event(Event::ForeignAssetFrozen {
628
5
				asset_id,
629
5
				xcm_location,
630
5
			});
631
5
			Ok(())
632
7
		}
633

            
634
6
		pub fn do_unfreeze_asset(asset_id: AssetId, xcm_location: Location) -> DispatchResult {
635
6
			let (_asset_id, asset_status) = AssetsByLocation::<T>::get(&xcm_location)
636
6
				.ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
637

            
638
6
			ensure!(
639
6
				asset_status == AssetStatus::FrozenXcmDepositAllowed
640
2
					|| asset_status == AssetStatus::FrozenXcmDepositForbidden,
641
2
				Error::<T>::AssetNotFrozen
642
			);
643

            
644
4
			EvmCaller::<T>::erc20_unpause(asset_id)?;
645

            
646
4
			AssetsByLocation::<T>::insert(&xcm_location, (asset_id, AssetStatus::Active));
647
4

            
648
4
			Self::deposit_event(Event::ForeignAssetUnfrozen {
649
4
				asset_id,
650
4
				xcm_location,
651
4
			});
652
4
			Ok(())
653
6
		}
654
	}
655

            
656
	impl<T: Config> xcm_executor::traits::TransactAsset for Pallet<T> {
657
		// For optimization reasons, the asset we want to deposit has not really been withdrawn,
658
		// we have just traced from which account it should have been withdrawn.
659
		// So we will retrieve these information and make the transfer from the origin account.
660
		fn deposit_asset(what: &Asset, who: &Location, _context: Option<&XcmContext>) -> XcmResult {
661
			let (contract_address, amount, asset_status) =
662
				ForeignAssetsMatcher::<T>::match_asset(what)?;
663

            
664
			if let AssetStatus::FrozenXcmDepositForbidden = asset_status {
665
				return Err(MatchError::AssetNotHandled.into());
666
			}
667

            
668
			let beneficiary = T::XcmLocationToH160::convert_location(who)
669
				.ok_or(MatchError::AccountIdConversionFailed)?;
670

            
671
			EvmCaller::<T>::erc20_mint_into(contract_address, beneficiary, amount)?;
672

            
673
			Ok(())
674
		}
675

            
676
		fn internal_transfer_asset(
677
			asset: &Asset,
678
			from: &Location,
679
			to: &Location,
680
			_context: &XcmContext,
681
		) -> Result<AssetsInHolding, XcmError> {
682
			let (contract_address, amount, asset_status) =
683
				ForeignAssetsMatcher::<T>::match_asset(asset)?;
684

            
685
			if let AssetStatus::FrozenXcmDepositForbidden | AssetStatus::FrozenXcmDepositAllowed =
686
				asset_status
687
			{
688
				return Err(MatchError::AssetNotHandled.into());
689
			}
690

            
691
			let from = T::XcmLocationToH160::convert_location(from)
692
				.ok_or(MatchError::AccountIdConversionFailed)?;
693

            
694
			let to = T::XcmLocationToH160::convert_location(to)
695
				.ok_or(MatchError::AccountIdConversionFailed)?;
696

            
697
			EvmCaller::<T>::erc20_transfer(contract_address, from, to, amount)?;
698

            
699
			Ok(asset.clone().into())
700
		}
701

            
702
		// Since we don't control the erc20 contract that manages the asset we want to withdraw,
703
		// we can't really withdraw this asset, we can only transfer it to another account.
704
		// It would be possible to transfer the asset to a dedicated account that would reflect
705
		// the content of the xcm holding, but this would imply to perform two evm calls instead of
706
		// one (1 to withdraw the asset and a second one to deposit it).
707
		// In order to perform only one evm call, we just trace the origin of the asset,
708
		// and then the transfer will only really be performed in the deposit instruction.
709
66
		fn withdraw_asset(
710
66
			what: &Asset,
711
66
			who: &Location,
712
66
			_context: Option<&XcmContext>,
713
66
		) -> Result<AssetsInHolding, XcmError> {
714
32
			let (contract_address, amount, asset_status) =
715
66
				ForeignAssetsMatcher::<T>::match_asset(what)?;
716
32
			let who = T::XcmLocationToH160::convert_location(who)
717
32
				.ok_or(MatchError::AccountIdConversionFailed)?;
718

            
719
32
			if let AssetStatus::FrozenXcmDepositForbidden | AssetStatus::FrozenXcmDepositAllowed =
720
32
				asset_status
721
			{
722
				return Err(MatchError::AssetNotHandled.into());
723
32
			}
724
32

            
725
32
			EvmCaller::<T>::erc20_burn_from(contract_address, who, amount)?;
726

            
727
32
			Ok(what.clone().into())
728
66
		}
729
	}
730

            
731
	impl<T: Config> sp_runtime::traits::MaybeEquivalence<Location, AssetId> for Pallet<T> {
732
		fn convert(location: &Location) -> Option<AssetId> {
733
			AssetsByLocation::<T>::get(location).map(|(asset_id, _)| asset_id)
734
		}
735
66
		fn convert_back(asset_id: &AssetId) -> Option<Location> {
736
66
			AssetsById::<T>::get(asset_id)
737
66
		}
738
	}
739
}