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

            
59
use frame_system::pallet_prelude::*;
60
use xcm::latest::{
61
	Asset, AssetId as XcmAssetId, Error as XcmError, Fungibility, Location, Result as XcmResult,
62
	XcmContext,
63
};
64
use xcm::prelude::Parachain;
65
use xcm_executor::traits::ConvertLocation;
66
use xcm_executor::traits::Error as MatchError;
67

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
589
3
				Some(deposit)
590
			} else {
591
31
				None
592
			};
593

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

            
599
34
			T::OnForeignAssetCreated::on_asset_created(&asset_xcm_location, &asset_id);
600
34

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
709
			Ok(())
710
		}
711

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

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

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

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

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

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

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

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

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

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

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