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
14
	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
38
	fn try_origin(o: O) -> Result<OriginType, O> {
102
38
		Original::try_origin(o)?;
103
26
		Ok(OriginType::Governance)
104
38
	}
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
26
	fn try_origin(o: O) -> Result<OriginType, O> {
118
26
		Original::try_origin(o).map(OriginType::XCM)
119
26
	}
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
65
	fn match_asset(asset: &Asset) -> Result<(H160, U256, AssetStatus), MatchError> {
130
65
		let (amount, location) = match (&asset.fun, &asset.id) {
131
65
			(Fungibility::Fungible(ref amount), XcmAssetId(ref location)) => (amount, location),
132
			_ => return Err(MatchError::AssetNotHandled),
133
		};
134

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

            
147
153
#[derive(Decode, Debug, Encode, PartialEq, TypeInfo)]
148
pub enum AssetStatus {
149
55
	/// 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
281
#[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
73
	#[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 different 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
50
	#[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
		EvmCallMintIntoFail,
251
		EvmCallTransferFail,
252
		EvmInternalError,
253
		/// Account has insufficient balance for locking
254
		InsufficientBalance,
255
		CannotConvertLocationToAccount,
256
		LocationOutsideOfOrigin,
257
		AssetNotInSiblingPara,
258
		InvalidSymbol,
259
		InvalidTokenName,
260
		LocationAlreadyExists,
261
		TooManyForeignAssets,
262
	}
263

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

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

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

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

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

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

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

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

            
351
		/// Mint an asset into a specific account
352
12
		pub fn mint_into(
353
12
			asset_id: AssetId,
354
12
			beneficiary: T::AccountId,
355
12
			amount: U256,
356
12
		) -> Result<(), evm::EvmError> {
357
12
			// We perform the evm call in a storage transaction to ensure that if it fail
358
12
			// any contract storage changes are rolled back.
359
12
			frame_support::storage::with_storage_layer(|| {
360
12
				EvmCaller::<T>::erc20_mint_into(
361
12
					Self::contract_address_from_asset_id(asset_id),
362
12
					T::AccountIdToH160::convert(beneficiary),
363
12
					amount,
364
12
				)
365
12
			})
366
12
			.map_err(Into::into)
367
12
		}
368

            
369
		/// Transfer an asset from an account to another one
370
4
		pub fn transfer(
371
4
			asset_id: AssetId,
372
4
			from: T::AccountId,
373
4
			to: T::AccountId,
374
4
			amount: U256,
375
4
		) -> Result<(), evm::EvmError> {
376
4
			frame_support::storage::with_storage_layer(|| {
377
4
				EvmCaller::<T>::erc20_transfer(
378
4
					Self::contract_address_from_asset_id(asset_id),
379
4
					T::AccountIdToH160::convert(from),
380
4
					T::AccountIdToH160::convert(to),
381
4
					amount,
382
4
				)
383
4
			})
384
4
			.map_err(Into::into)
385
4
		}
386

            
387
12
		pub fn balance(asset_id: AssetId, who: T::AccountId) -> Result<U256, evm::EvmError> {
388
12
			EvmCaller::<T>::erc20_balance_of(asset_id, T::AccountIdToH160::convert(who))
389
12
				.map_err(Into::into)
390
12
		}
391

            
392
		/// Approve a spender to spend a certain amount of tokens from the owner account
393
		pub fn approve(
394
			asset_id: AssetId,
395
			owner: T::AccountId,
396
			spender: T::AccountId,
397
			amount: U256,
398
		) -> Result<(), evm::EvmError> {
399
			// We perform the evm call in a storage transaction to ensure that if it fail
400
			// any contract storage changes are rolled back.
401
			frame_support::storage::with_storage_layer(|| {
402
				EvmCaller::<T>::erc20_approve(
403
					Self::contract_address_from_asset_id(asset_id),
404
					T::AccountIdToH160::convert(owner),
405
					T::AccountIdToH160::convert(spender),
406
					amount,
407
				)
408
			})
409
			.map_err(Into::into)
410
		}
411

            
412
122
		pub fn weight_of_erc20_burn() -> Weight {
413
122
			T::GasWeightMapping::gas_to_weight(evm::ERC20_BURN_FROM_GAS_LIMIT, true)
414
122
		}
415
2
		pub fn weight_of_erc20_mint() -> Weight {
416
2
			T::GasWeightMapping::gas_to_weight(evm::ERC20_MINT_INTO_GAS_LIMIT, true)
417
2
		}
418
22
		pub fn weight_of_erc20_transfer() -> Weight {
419
22
			T::GasWeightMapping::gas_to_weight(evm::ERC20_TRANSFER_GAS_LIMIT, true)
420
22
		}
421
		#[cfg(feature = "runtime-benchmarks")]
422
		pub fn set_asset(asset_location: Location, asset_id: AssetId) {
423
			AssetsByLocation::<T>::insert(&asset_location, (asset_id, AssetStatus::Active));
424
			AssetsById::<T>::insert(&asset_id, asset_location);
425
		}
426
	}
427

            
428
	#[pallet::call]
429
	impl<T: Config> Pallet<T> {
430
		/// Create new asset with the ForeignAssetCreator
431
		#[pallet::call_index(0)]
432
		#[pallet::weight(<T as Config>::WeightInfo::create_foreign_asset())]
433
		pub fn create_foreign_asset(
434
			origin: OriginFor<T>,
435
			asset_id: AssetId,
436
			asset_xcm_location: Location,
437
			decimals: u8,
438
			symbol: BoundedVec<u8, ConstU32<256>>,
439
			name: BoundedVec<u8, ConstU32<256>>,
440
20
		) -> DispatchResult {
441
20
			let origin_type = T::ForeignAssetCreatorOrigin::ensure_origin(origin.clone())?;
442

            
443
19
			Self::ensure_origin_can_modify_location(origin_type.clone(), &asset_xcm_location)?;
444
19
			let deposit_account = Self::get_deposit_account(origin_type)?;
445

            
446
19
			Self::do_create_asset(
447
19
				asset_id,
448
19
				asset_xcm_location,
449
19
				decimals,
450
19
				symbol,
451
19
				name,
452
19
				deposit_account,
453
19
			)
454
		}
455

            
456
		/// Change the xcm type mapping for a given assetId
457
		/// We also change this if the previous units per second where pointing at the old
458
		/// assetType
459
		#[pallet::call_index(1)]
460
		#[pallet::weight(<T as Config>::WeightInfo::change_xcm_location())]
461
		pub fn change_xcm_location(
462
			origin: OriginFor<T>,
463
			asset_id: AssetId,
464
			new_xcm_location: Location,
465
6
		) -> DispatchResult {
466
6
			let origin_type = T::ForeignAssetModifierOrigin::ensure_origin(origin.clone())?;
467

            
468
5
			Self::ensure_origin_can_modify_location(origin_type.clone(), &new_xcm_location)?;
469

            
470
4
			let previous_location =
471
5
				AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
472

            
473
4
			Self::ensure_origin_can_modify_location(origin_type, &previous_location)?;
474

            
475
3
			Self::do_change_xcm_location(asset_id, previous_location, new_xcm_location)
476
		}
477

            
478
		/// Freeze a given foreign assetId
479
		#[pallet::call_index(2)]
480
		#[pallet::weight(<T as Config>::WeightInfo::freeze_foreign_asset())]
481
		pub fn freeze_foreign_asset(
482
			origin: OriginFor<T>,
483
			asset_id: AssetId,
484
			allow_xcm_deposit: bool,
485
9
		) -> DispatchResult {
486
9
			let origin_type = T::ForeignAssetFreezerOrigin::ensure_origin(origin.clone())?;
487

            
488
9
			let xcm_location =
489
9
				AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
490

            
491
9
			Self::ensure_origin_can_modify_location(origin_type, &xcm_location)?;
492

            
493
7
			Self::do_freeze_asset(asset_id, xcm_location, allow_xcm_deposit)
494
		}
495

            
496
		/// Unfreeze a given foreign assetId
497
		#[pallet::call_index(3)]
498
		#[pallet::weight(<T as Config>::WeightInfo::unfreeze_foreign_asset())]
499
6
		pub fn unfreeze_foreign_asset(origin: OriginFor<T>, asset_id: AssetId) -> DispatchResult {
500
6
			let origin_type = T::ForeignAssetUnfreezerOrigin::ensure_origin(origin.clone())?;
501

            
502
6
			let xcm_location =
503
6
				AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
504

            
505
6
			Self::ensure_origin_can_modify_location(origin_type, &xcm_location)?;
506

            
507
6
			Self::do_unfreeze_asset(asset_id, xcm_location)
508
		}
509
	}
510

            
511
	impl<T: Config> Pallet<T> {
512
		/// Ensure that the caller origin can modify the location,
513
43
		fn ensure_origin_can_modify_location(
514
43
			origin_type: OriginType,
515
43
			location: &Location,
516
43
		) -> DispatchResult {
517
43
			match origin_type {
518
14
				OriginType::XCM(origin_location) => {
519
14
					ensure!(
520
14
						location.starts_with(&origin_location),
521
3
						Error::<T>::LocationOutsideOfOrigin,
522
					);
523
				}
524
29
				OriginType::Governance => {
525
29
					// nothing to check Governance can change any asset
526
29
				}
527
			};
528
40
			Ok(())
529
43
		}
530

            
531
19
		fn get_deposit_account(
532
19
			origin_type: OriginType,
533
19
		) -> Result<Option<T::AccountId>, DispatchError> {
534
19
			match origin_type {
535
3
				OriginType::XCM(origin_location) => {
536
3
					let deposit_account = convert_location::<T>(&origin_location)?;
537
3
					Ok(Some(deposit_account))
538
				}
539
16
				OriginType::Governance => Ok(None),
540
			}
541
19
		}
542

            
543
23
		pub fn do_create_asset(
544
23
			asset_id: AssetId,
545
23
			asset_xcm_location: Location,
546
23
			decimals: u8,
547
23
			symbol: BoundedVec<u8, ConstU32<256>>,
548
23
			name: BoundedVec<u8, ConstU32<256>>,
549
23
			deposit_account: Option<T::AccountId>,
550
23
		) -> DispatchResult {
551
23
			ensure!(
552
23
				!AssetsById::<T>::contains_key(&asset_id),
553
1
				Error::<T>::AssetAlreadyExists
554
			);
555

            
556
22
			ensure!(
557
22
				!AssetsByLocation::<T>::contains_key(&asset_xcm_location),
558
1
				Error::<T>::LocationAlreadyExists
559
			);
560

            
561
21
			ensure!(
562
21
				AssetsById::<T>::count() < T::MaxForeignAssets::get(),
563
				Error::<T>::TooManyForeignAssets
564
			);
565

            
566
21
			ensure!(
567
21
				T::AssetIdFilter::contains(&asset_id),
568
				Error::<T>::AssetIdFiltered
569
			);
570

            
571
21
			let symbol = core::str::from_utf8(&symbol).map_err(|_| Error::<T>::InvalidSymbol)?;
572
21
			let name = core::str::from_utf8(&name).map_err(|_| Error::<T>::InvalidTokenName)?;
573
21
			let contract_address = EvmCaller::<T>::erc20_create(asset_id, decimals, symbol, name)?;
574

            
575
21
			let deposit = if let Some(deposit_account) = deposit_account {
576
3
				let deposit = T::ForeignAssetCreationDeposit::get();
577
3

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

            
581
				// Insert the amount that is reserved from the user
582
3
				AssetsCreationDetails::<T>::insert(
583
3
					&asset_id,
584
3
					AssetDepositDetails {
585
3
						deposit_account,
586
3
						deposit,
587
3
					},
588
3
				);
589
3

            
590
3
				Some(deposit)
591
			} else {
592
18
				None
593
			};
594

            
595
			// Insert the association assetId->foreigAsset
596
			// Insert the association foreigAsset->assetId
597
21
			AssetsById::<T>::insert(&asset_id, &asset_xcm_location);
598
21
			AssetsByLocation::<T>::insert(&asset_xcm_location, (asset_id, AssetStatus::Active));
599
21

            
600
21
			T::OnForeignAssetCreated::on_asset_created(&asset_xcm_location, &asset_id);
601
21

            
602
21
			Self::deposit_event(Event::ForeignAssetCreated {
603
21
				contract_address,
604
21
				asset_id,
605
21
				xcm_location: asset_xcm_location,
606
21
				deposit,
607
21
			});
608
21
			Ok(())
609
23
		}
610

            
611
3
		pub fn do_change_xcm_location(
612
3
			asset_id: AssetId,
613
3
			previous_xcm_location: Location,
614
3
			new_xcm_location: Location,
615
3
		) -> DispatchResult {
616
3
			ensure!(
617
3
				!AssetsByLocation::<T>::contains_key(&new_xcm_location),
618
1
				Error::<T>::LocationAlreadyExists
619
			);
620

            
621
			// Remove previous foreign asset info
622
2
			let (_asset_id, asset_status) = AssetsByLocation::<T>::take(&previous_xcm_location)
623
2
				.ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
624

            
625
			// Insert new foreign asset info
626
2
			AssetsById::<T>::insert(&asset_id, &new_xcm_location);
627
2
			AssetsByLocation::<T>::insert(&new_xcm_location, (asset_id, asset_status));
628
2

            
629
2
			Self::deposit_event(Event::ForeignAssetXcmLocationChanged {
630
2
				asset_id,
631
2
				new_xcm_location,
632
2
				previous_xcm_location,
633
2
			});
634
2
			Ok(())
635
3
		}
636

            
637
7
		pub fn do_freeze_asset(
638
7
			asset_id: AssetId,
639
7
			xcm_location: Location,
640
7
			allow_xcm_deposit: bool,
641
7
		) -> DispatchResult {
642
7
			let (_asset_id, asset_status) = AssetsByLocation::<T>::get(&xcm_location)
643
7
				.ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
644

            
645
7
			ensure!(
646
7
				asset_status == AssetStatus::Active,
647
2
				Error::<T>::AssetAlreadyFrozen
648
			);
649

            
650
5
			EvmCaller::<T>::erc20_pause(asset_id)?;
651

            
652
5
			let new_asset_status = if allow_xcm_deposit {
653
5
				AssetStatus::FrozenXcmDepositAllowed
654
			} else {
655
				AssetStatus::FrozenXcmDepositForbidden
656
			};
657

            
658
5
			AssetsByLocation::<T>::insert(&xcm_location, (asset_id, new_asset_status));
659
5

            
660
5
			Self::deposit_event(Event::ForeignAssetFrozen {
661
5
				asset_id,
662
5
				xcm_location,
663
5
			});
664
5
			Ok(())
665
7
		}
666

            
667
6
		pub fn do_unfreeze_asset(asset_id: AssetId, xcm_location: Location) -> DispatchResult {
668
6
			let (_asset_id, asset_status) = AssetsByLocation::<T>::get(&xcm_location)
669
6
				.ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
670

            
671
6
			ensure!(
672
6
				asset_status == AssetStatus::FrozenXcmDepositAllowed
673
2
					|| asset_status == AssetStatus::FrozenXcmDepositForbidden,
674
2
				Error::<T>::AssetNotFrozen
675
			);
676

            
677
4
			EvmCaller::<T>::erc20_unpause(asset_id)?;
678

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

            
681
4
			Self::deposit_event(Event::ForeignAssetUnfrozen {
682
4
				asset_id,
683
4
				xcm_location,
684
4
			});
685
4
			Ok(())
686
6
		}
687
	}
688

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

            
697
4
			if let AssetStatus::FrozenXcmDepositForbidden = asset_status {
698
				return Err(MatchError::AssetNotHandled.into());
699
4
			}
700

            
701
4
			let beneficiary = T::XcmLocationToH160::convert_location(who)
702
4
				.ok_or(MatchError::AccountIdConversionFailed)?;
703

            
704
			// We perform the evm transfers in a storage transaction to ensure that if it fail
705
			// any contract storage changes are rolled back.
706
4
			frame_support::storage::with_storage_layer(|| {
707
4
				EvmCaller::<T>::erc20_mint_into(contract_address, beneficiary, amount)
708
4
			})?;
709

            
710
4
			Ok(())
711
4
		}
712

            
713
		fn internal_transfer_asset(
714
			asset: &Asset,
715
			from: &Location,
716
			to: &Location,
717
			_context: &XcmContext,
718
		) -> Result<AssetsInHolding, XcmError> {
719
			let (contract_address, amount, asset_status) =
720
				ForeignAssetsMatcher::<T>::match_asset(asset)?;
721

            
722
			if let AssetStatus::FrozenXcmDepositForbidden | AssetStatus::FrozenXcmDepositAllowed =
723
				asset_status
724
			{
725
				return Err(MatchError::AssetNotHandled.into());
726
			}
727

            
728
			let from = T::XcmLocationToH160::convert_location(from)
729
				.ok_or(MatchError::AccountIdConversionFailed)?;
730

            
731
			let to = T::XcmLocationToH160::convert_location(to)
732
				.ok_or(MatchError::AccountIdConversionFailed)?;
733

            
734
			// We perform the evm transfers in a storage transaction to ensure that if it fail
735
			// any contract storage changes are rolled back.
736
			frame_support::storage::with_storage_layer(|| {
737
				EvmCaller::<T>::erc20_transfer(contract_address, from, to, amount)
738
			})?;
739

            
740
			Ok(asset.clone().into())
741
		}
742

            
743
		// Since we don't control the erc20 contract that manages the asset we want to withdraw,
744
		// we can't really withdraw this asset, we can only transfer it to another account.
745
		// It would be possible to transfer the asset to a dedicated account that would reflect
746
		// the content of the xcm holding, but this would imply to perform two evm calls instead of
747
		// one (1 to withdraw the asset and a second one to deposit it).
748
		// In order to perform only one evm call, we just trace the origin of the asset,
749
		// and then the transfer will only really be performed in the deposit instruction.
750
61
		fn withdraw_asset(
751
61
			what: &Asset,
752
61
			who: &Location,
753
61
			_context: Option<&XcmContext>,
754
61
		) -> Result<AssetsInHolding, XcmError> {
755
29
			let (contract_address, amount, asset_status) =
756
61
				ForeignAssetsMatcher::<T>::match_asset(what)?;
757
29
			let who = T::XcmLocationToH160::convert_location(who)
758
29
				.ok_or(MatchError::AccountIdConversionFailed)?;
759

            
760
			if let AssetStatus::FrozenXcmDepositForbidden | AssetStatus::FrozenXcmDepositAllowed =
761
29
				asset_status
762
			{
763
				return Err(MatchError::AssetNotHandled.into());
764
29
			}
765
29

            
766
29
			// We perform the evm transfers in a storage transaction to ensure that if it fail
767
29
			// any contract storage changes are rolled back.
768
29
			frame_support::storage::with_storage_layer(|| {
769
29
				EvmCaller::<T>::erc20_burn_from(contract_address, who, amount)
770
29
			})?;
771

            
772
29
			Ok(what.clone().into())
773
61
		}
774
	}
775

            
776
	impl<T: Config> sp_runtime::traits::MaybeEquivalence<Location, AssetId> for Pallet<T> {
777
		fn convert(location: &Location) -> Option<AssetId> {
778
			AssetsByLocation::<T>::get(location).map(|(asset_id, _)| asset_id)
779
		}
780
93
		fn convert_back(asset_id: &AssetId) -> Option<Location> {
781
93
			AssetsById::<T>::get(asset_id)
782
93
		}
783
	}
784
}