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 between 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(feature = "runtime-benchmarks")]
43
pub use benchmarks::*;
44
#[cfg(test)]
45
pub mod mock;
46
#[cfg(test)]
47
pub mod tests;
48
pub mod weights;
49

            
50
mod evm;
51

            
52
pub use pallet::*;
53
pub use weights::WeightInfo;
54

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

            
70
const FOREIGN_ASSETS_PREFIX: [u8; 4] = [0xff, 0xff, 0xff, 0xff];
71

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

            
77
impl<ForeignAsset> ForeignAssetCreatedHook<ForeignAsset> for () {
78
125
	fn on_asset_created(_foreign_asset: &ForeignAsset, _asset_id: &AssetId) {}
79
}
80

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

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

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

            
129
pub(crate) struct ForeignAssetsMatcher<T>(core::marker::PhantomData<T>);
130

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

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

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

            
160
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
161
pub struct EvmForeignAssetInfo {
162
	pub asset_id: AssetId,
163
	pub xcm_location: Location,
164
	pub decimals: u8,
165
	pub symbol: BoundedVec<u8, ConstU32<256>>,
166
	pub name: BoundedVec<u8, ConstU32<256>>,
167
}
168

            
169
#[pallet]
170
pub mod pallet {
171
	use super::*;
172
	use frame_support::traits::{Currency, ReservableCurrency};
173
	use pallet_evm::{GasWeightMapping, Runner};
174
	use sp_runtime::traits::{AccountIdConversion, AtLeast32BitUnsigned, Convert};
175
	use xcm_executor::traits::ConvertLocation;
176
	use xcm_executor::traits::Error as MatchError;
177
	use xcm_executor::AssetsInHolding;
178

            
179
	#[pallet::pallet]
180
	#[pallet::without_storage_info]
181
	pub struct Pallet<T>(PhantomData<T>);
182

            
183
	/// The moonbeam foreign assets's pallet id
184
	pub const PALLET_ID: frame_support::PalletId = frame_support::PalletId(*b"forgasst");
185

            
186
	#[pallet::config]
187
	pub trait Config:
188
		frame_system::Config<RuntimeEvent: From<Event<Self>>>
189
		+ pallet_evm::Config
190
		+ scale_info::TypeInfo
191
	{
192
		// Convert AccountId to H160
193
		type AccountIdToH160: Convert<Self::AccountId, H160>;
194

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

            
198
		/// EVM runner
199
		type EvmRunner: Runner<Self>;
200

            
201
		type ConvertLocation: ConvertLocation<Self::AccountId>;
202

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

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

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

            
212
		/// Origin that is allowed to unfreeze all tokens of a foreign asset that was previously
213
		/// frozen
214
		type ForeignAssetUnfreezerOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = OriginType>;
215

            
216
		/// Hook to be called when new foreign asset is registered.
217
		type OnForeignAssetCreated: ForeignAssetCreatedHook<Location>;
218

            
219
		/// Maximum numbers of different foreign assets
220
		type MaxForeignAssets: Get<u32>;
221

            
222
		/// Weight information for extrinsics in this pallet.
223
		type WeightInfo: WeightInfo;
224

            
225
		// Convert XCM Location to H160
226
		type XcmLocationToH160: ConvertLocation<H160>;
227

            
228
		/// Amount of tokens required to lock for creating a new foreign asset
229
		type ForeignAssetCreationDeposit: Get<BalanceOf<Self>>;
230

            
231
		/// The balance type for locking funds
232
		type Balance: Member
233
			+ Parameter
234
			+ AtLeast32BitUnsigned
235
			+ Default
236
			+ Copy
237
			+ MaybeSerializeDeserialize
238
			+ MaxEncodedLen
239
			+ TypeInfo;
240

            
241
		/// The currency type for locking funds
242
		type Currency: ReservableCurrency<Self::AccountId>;
243
	}
244

            
245
	type BalanceOf<T> =
246
		<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
247

            
248
	pub type AssetBalance = U256;
249
	pub type AssetId = u128;
250

            
251
	/// An error that can occur while executing the mapping pallet's logic.
252
	#[pallet::error]
253
	pub enum Error<T> {
254
		AssetAlreadyExists,
255
		AssetAlreadyFrozen,
256
		AssetDoesNotExist,
257
		AssetIdFiltered,
258
		AssetNotFrozen,
259
		CorruptedStorageOrphanLocation,
260
		Erc20ContractCreationFail,
261
		EvmCallPauseFail,
262
		EvmCallUnpauseFail,
263
		EvmCallMintIntoFail,
264
		EvmCallTransferFail,
265
		EvmInternalError,
266
		/// Account has insufficient balance for locking
267
		InsufficientBalance,
268
		CannotConvertLocationToAccount,
269
		LocationOutsideOfOrigin,
270
		AssetNotInSiblingPara,
271
		InvalidSymbol,
272
		InvalidTokenName,
273
		LocationAlreadyExists,
274
		TooManyForeignAssets,
275
	}
276

            
277
	#[pallet::event]
278
	#[pallet::generate_deposit(pub(crate) fn deposit_event)]
279
	pub enum Event<T: Config> {
280
		/// New asset with the asset manager is registered
281
		ForeignAssetCreated {
282
			contract_address: H160,
283
			asset_id: AssetId,
284
			xcm_location: Location,
285
			deposit: Option<BalanceOf<T>>,
286
		},
287
		/// Changed the xcm type mapping for a given asset id
288
		ForeignAssetXcmLocationChanged {
289
			asset_id: AssetId,
290
			previous_xcm_location: Location,
291
			new_xcm_location: Location,
292
		},
293
		// Freezes all tokens of a given asset id
294
		ForeignAssetFrozen {
295
			asset_id: AssetId,
296
			xcm_location: Location,
297
		},
298
		// Thawing a previously frozen asset
299
		ForeignAssetUnfrozen {
300
			asset_id: AssetId,
301
			xcm_location: Location,
302
		},
303
		/// Tokens have been locked for asset creation
304
		TokensLocked(T::AccountId, AssetId, AssetBalance),
305
	}
306

            
307
	/// Mapping from an asset id to a Foreign asset type.
308
	/// This is mostly used when receiving transaction specifying an asset directly,
309
	/// like transferring an asset from this chain to another.
310
	#[pallet::storage]
311
	#[pallet::getter(fn assets_by_id)]
312
	pub type AssetsById<T: Config> =
313
		CountedStorageMap<_, Blake2_128Concat, AssetId, Location, OptionQuery>;
314

            
315
	/// Reverse mapping of AssetsById. Mapping from a foreign asset to an asset id.
316
	/// This is mostly used when receiving a multilocation XCM message to retrieve
317
	/// the corresponding asset in which tokens should me minted.
318
	#[pallet::storage]
319
	#[pallet::getter(fn assets_by_location)]
320
	pub type AssetsByLocation<T: Config> =
321
		StorageMap<_, Blake2_128Concat, Location, (AssetId, AssetStatus)>;
322

            
323
	/// Mapping from an asset id to its creation details
324
	#[pallet::storage]
325
	#[pallet::getter(fn assets_creation_details)]
326
	pub type AssetsCreationDetails<T: Config> =
327
		StorageMap<_, Blake2_128Concat, AssetId, AssetDepositDetails<T>>;
328

            
329
	#[derive(Clone, Decode, Encode, Eq, PartialEq, Debug, TypeInfo, MaxEncodedLen)]
330
	pub struct AssetDepositDetails<T: Config> {
331
		pub deposit_account: T::AccountId,
332
		pub deposit: BalanceOf<T>,
333
	}
334

            
335
	#[pallet::genesis_config]
336
	pub struct GenesisConfig<T: Config> {
337
		pub assets: Vec<EvmForeignAssetInfo>,
338
		pub _phantom: PhantomData<T>,
339
	}
340

            
341
	impl<T: Config> Default for GenesisConfig<T> {
342
6
		fn default() -> Self {
343
6
			Self {
344
6
				assets: vec![],
345
6
				_phantom: Default::default(),
346
6
			}
347
6
		}
348
	}
349

            
350
	#[pallet::genesis_build]
351
	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
352
6
		fn build(&self) {
353
6
			for asset in self.assets.clone() {
354
				Pallet::<T>::register_foreign_asset(
355
					asset.asset_id,
356
					asset.xcm_location,
357
					asset.decimals,
358
					asset.symbol,
359
					asset.name,
360
				)
361
				.expect("couldn't register asset");
362
			}
363
6
		}
364
	}
365

            
366
	impl<T: Config> Pallet<T> {
367
		/// The account ID of this pallet
368
		#[inline]
369
696
		pub fn account_id() -> H160 {
370
696
			let account_id: T::AccountId = PALLET_ID.into_account_truncating();
371
696
			T::AccountIdToH160::convert(account_id)
372
696
		}
373

            
374
		/// Compute asset contract address from asset id
375
		#[inline]
376
569
		pub fn contract_address_from_asset_id(asset_id: AssetId) -> H160 {
377
569
			let mut buffer = [0u8; 20];
378
569
			buffer[..4].copy_from_slice(&FOREIGN_ASSETS_PREFIX);
379
569
			buffer[4..].copy_from_slice(&asset_id.to_be_bytes());
380
569
			H160(buffer)
381
569
		}
382

            
383
		/// This method only exists for migration purposes and will be deleted once the
384
		/// foreign assets migration is finished.
385
103
		pub fn register_foreign_asset(
386
103
			asset_id: AssetId,
387
103
			xcm_location: Location,
388
103
			decimals: u8,
389
103
			symbol: BoundedVec<u8, ConstU32<256>>,
390
103
			name: BoundedVec<u8, ConstU32<256>>,
391
103
		) -> DispatchResult {
392
103
			Self::do_create_asset(asset_id, xcm_location, decimals, symbol, name, None)
393
103
		}
394

            
395
		/// Mint an asset into a specific account
396
16
		pub fn mint_into(
397
16
			asset_id: AssetId,
398
16
			beneficiary: T::AccountId,
399
16
			amount: U256,
400
16
		) -> Result<(), evm::EvmError> {
401
			// We perform the evm call in a storage transaction to ensure that if it fail
402
			// any contract storage changes are rolled back.
403
16
			frame_support::storage::with_storage_layer(|| {
404
16
				EvmCaller::<T>::erc20_mint_into(
405
16
					Self::contract_address_from_asset_id(asset_id),
406
16
					T::AccountIdToH160::convert(beneficiary),
407
16
					amount,
408
				)
409
16
			})
410
16
			.map_err(Into::into)
411
16
		}
412

            
413
		/// Transfer an asset from an account to another one
414
7
		pub fn transfer(
415
7
			asset_id: AssetId,
416
7
			from: T::AccountId,
417
7
			to: T::AccountId,
418
7
			amount: U256,
419
7
		) -> Result<(), evm::EvmError> {
420
7
			frame_support::storage::with_storage_layer(|| {
421
7
				EvmCaller::<T>::erc20_transfer(
422
7
					Self::contract_address_from_asset_id(asset_id),
423
7
					T::AccountIdToH160::convert(from),
424
7
					T::AccountIdToH160::convert(to),
425
7
					amount,
426
				)
427
7
			})
428
7
			.map_err(Into::into)
429
7
		}
430

            
431
181
		pub fn balance(asset_id: AssetId, who: T::AccountId) -> Result<U256, evm::EvmError> {
432
181
			EvmCaller::<T>::erc20_balance_of(asset_id, T::AccountIdToH160::convert(who))
433
181
				.map_err(Into::into)
434
181
		}
435

            
436
		/// Approve a spender to spend a certain amount of tokens from the owner account
437
		pub fn approve(
438
			asset_id: AssetId,
439
			owner: T::AccountId,
440
			spender: T::AccountId,
441
			amount: U256,
442
		) -> Result<(), evm::EvmError> {
443
			// We perform the evm call in a storage transaction to ensure that if it fail
444
			// any contract storage changes are rolled back.
445
			frame_support::storage::with_storage_layer(|| {
446
				EvmCaller::<T>::erc20_approve(
447
					Self::contract_address_from_asset_id(asset_id),
448
					T::AccountIdToH160::convert(owner),
449
					T::AccountIdToH160::convert(spender),
450
					amount,
451
				)
452
			})
453
			.map_err(Into::into)
454
		}
455

            
456
43
		pub fn weight_of_erc20_burn() -> Weight {
457
43
			T::GasWeightMapping::gas_to_weight(evm::ERC20_BURN_FROM_GAS_LIMIT, true)
458
43
		}
459
8
		pub fn weight_of_erc20_mint() -> Weight {
460
8
			T::GasWeightMapping::gas_to_weight(evm::ERC20_MINT_INTO_GAS_LIMIT, true)
461
8
		}
462
20
		pub fn weight_of_erc20_transfer() -> Weight {
463
20
			T::GasWeightMapping::gas_to_weight(evm::ERC20_TRANSFER_GAS_LIMIT, true)
464
20
		}
465
		#[cfg(feature = "runtime-benchmarks")]
466
		pub fn set_asset(asset_location: Location, asset_id: AssetId) {
467
			AssetsByLocation::<T>::insert(&asset_location, (asset_id, AssetStatus::Active));
468
			AssetsById::<T>::insert(&asset_id, asset_location);
469
		}
470
	}
471

            
472
	#[pallet::call]
473
	impl<T: Config> Pallet<T> {
474
		/// Create new asset with the ForeignAssetCreator
475
		#[pallet::call_index(0)]
476
		#[pallet::weight(<T as Config>::WeightInfo::create_foreign_asset())]
477
		pub fn create_foreign_asset(
478
			origin: OriginFor<T>,
479
			asset_id: AssetId,
480
			asset_xcm_location: Location,
481
			decimals: u8,
482
			symbol: BoundedVec<u8, ConstU32<256>>,
483
			name: BoundedVec<u8, ConstU32<256>>,
484
32
		) -> DispatchResult {
485
32
			let origin_type = T::ForeignAssetCreatorOrigin::ensure_origin(origin.clone())?;
486

            
487
31
			Self::ensure_origin_can_modify_location(origin_type.clone(), &asset_xcm_location)?;
488
31
			let deposit_account = Self::get_deposit_account(origin_type)?;
489

            
490
31
			Self::do_create_asset(
491
31
				asset_id,
492
31
				asset_xcm_location,
493
31
				decimals,
494
31
				symbol,
495
31
				name,
496
31
				deposit_account,
497
			)
498
		}
499

            
500
		/// Change the xcm type mapping for a given assetId
501
		/// We also change this if the previous units per second where pointing at the old
502
		/// assetType
503
		#[pallet::call_index(1)]
504
		#[pallet::weight(<T as Config>::WeightInfo::change_xcm_location())]
505
		pub fn change_xcm_location(
506
			origin: OriginFor<T>,
507
			asset_id: AssetId,
508
			new_xcm_location: Location,
509
6
		) -> DispatchResult {
510
6
			let origin_type = T::ForeignAssetModifierOrigin::ensure_origin(origin.clone())?;
511

            
512
5
			Self::ensure_origin_can_modify_location(origin_type.clone(), &new_xcm_location)?;
513

            
514
4
			let previous_location =
515
5
				AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
516

            
517
4
			Self::ensure_origin_can_modify_location(origin_type, &previous_location)?;
518

            
519
3
			Self::do_change_xcm_location(asset_id, previous_location, new_xcm_location)
520
		}
521

            
522
		/// Freeze a given foreign assetId
523
		#[pallet::call_index(2)]
524
		#[pallet::weight(<T as Config>::WeightInfo::freeze_foreign_asset())]
525
		pub fn freeze_foreign_asset(
526
			origin: OriginFor<T>,
527
			asset_id: AssetId,
528
			allow_xcm_deposit: bool,
529
11
		) -> DispatchResult {
530
11
			let origin_type = T::ForeignAssetFreezerOrigin::ensure_origin(origin.clone())?;
531

            
532
11
			let xcm_location =
533
11
				AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
534

            
535
11
			Self::ensure_origin_can_modify_location(origin_type, &xcm_location)?;
536

            
537
9
			Self::do_freeze_asset(asset_id, xcm_location, allow_xcm_deposit)
538
		}
539

            
540
		/// Unfreeze a given foreign assetId
541
		#[pallet::call_index(3)]
542
		#[pallet::weight(<T as Config>::WeightInfo::unfreeze_foreign_asset())]
543
8
		pub fn unfreeze_foreign_asset(origin: OriginFor<T>, asset_id: AssetId) -> DispatchResult {
544
8
			let origin_type = T::ForeignAssetUnfreezerOrigin::ensure_origin(origin.clone())?;
545

            
546
8
			let xcm_location =
547
8
				AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
548

            
549
8
			Self::ensure_origin_can_modify_location(origin_type, &xcm_location)?;
550

            
551
8
			Self::do_unfreeze_asset(asset_id, xcm_location)
552
		}
553
	}
554

            
555
	impl<T: Config> Pallet<T> {
556
		/// Ensure that the caller origin can modify the location,
557
59
		fn ensure_origin_can_modify_location(
558
59
			origin_type: OriginType,
559
59
			location: &Location,
560
59
		) -> DispatchResult {
561
59
			match origin_type {
562
17
				OriginType::XCM(origin_location) => {
563
17
					ensure!(
564
17
						location.starts_with(&origin_location),
565
3
						Error::<T>::LocationOutsideOfOrigin,
566
					);
567
				}
568
42
				OriginType::Governance => {
569
42
					// nothing to check Governance can change any asset
570
42
				}
571
			};
572
56
			Ok(())
573
59
		}
574

            
575
31
		fn get_deposit_account(
576
31
			origin_type: OriginType,
577
31
		) -> Result<Option<T::AccountId>, DispatchError> {
578
31
			match origin_type {
579
4
				OriginType::XCM(origin_location) => {
580
4
					let deposit_account = convert_location::<T>(&origin_location)?;
581
4
					Ok(Some(deposit_account))
582
				}
583
27
				OriginType::Governance => Ok(None),
584
			}
585
31
		}
586

            
587
134
		pub fn do_create_asset(
588
134
			asset_id: AssetId,
589
134
			asset_xcm_location: Location,
590
134
			decimals: u8,
591
134
			symbol: BoundedVec<u8, ConstU32<256>>,
592
134
			name: BoundedVec<u8, ConstU32<256>>,
593
134
			deposit_account: Option<T::AccountId>,
594
134
		) -> DispatchResult {
595
134
			ensure!(
596
134
				!AssetsById::<T>::contains_key(&asset_id),
597
1
				Error::<T>::AssetAlreadyExists
598
			);
599

            
600
133
			ensure!(
601
133
				!AssetsByLocation::<T>::contains_key(&asset_xcm_location),
602
1
				Error::<T>::LocationAlreadyExists
603
			);
604

            
605
132
			ensure!(
606
132
				AssetsById::<T>::count() < T::MaxForeignAssets::get(),
607
				Error::<T>::TooManyForeignAssets
608
			);
609

            
610
132
			ensure!(
611
132
				T::AssetIdFilter::contains(&asset_id),
612
				Error::<T>::AssetIdFiltered
613
			);
614

            
615
132
			let symbol = core::str::from_utf8(&symbol).map_err(|_| Error::<T>::InvalidSymbol)?;
616
132
			let name = core::str::from_utf8(&name).map_err(|_| Error::<T>::InvalidTokenName)?;
617
132
			let contract_address = EvmCaller::<T>::erc20_create(asset_id, decimals, symbol, name)?;
618

            
619
132
			let deposit = if let Some(deposit_account) = deposit_account {
620
4
				let deposit = T::ForeignAssetCreationDeposit::get();
621

            
622
				// Reserve _deposit_ amount of funds from the caller
623
4
				<T as Config>::Currency::reserve(&deposit_account, deposit)?;
624

            
625
				// Insert the amount that is reserved from the user
626
4
				AssetsCreationDetails::<T>::insert(
627
4
					&asset_id,
628
4
					AssetDepositDetails {
629
4
						deposit_account,
630
4
						deposit,
631
4
					},
632
				);
633

            
634
4
				Some(deposit)
635
			} else {
636
128
				None
637
			};
638

            
639
			// Insert the association assetId->foreigAsset
640
			// Insert the association foreigAsset->assetId
641
132
			AssetsById::<T>::insert(&asset_id, &asset_xcm_location);
642
132
			AssetsByLocation::<T>::insert(&asset_xcm_location, (asset_id, AssetStatus::Active));
643

            
644
132
			T::OnForeignAssetCreated::on_asset_created(&asset_xcm_location, &asset_id);
645

            
646
132
			Self::deposit_event(Event::ForeignAssetCreated {
647
132
				contract_address,
648
132
				asset_id,
649
132
				xcm_location: asset_xcm_location,
650
132
				deposit,
651
132
			});
652
132
			Ok(())
653
134
		}
654

            
655
3
		pub fn do_change_xcm_location(
656
3
			asset_id: AssetId,
657
3
			previous_xcm_location: Location,
658
3
			new_xcm_location: Location,
659
3
		) -> DispatchResult {
660
3
			ensure!(
661
3
				!AssetsByLocation::<T>::contains_key(&new_xcm_location),
662
1
				Error::<T>::LocationAlreadyExists
663
			);
664

            
665
			// Remove previous foreign asset info
666
2
			let (_asset_id, asset_status) = AssetsByLocation::<T>::take(&previous_xcm_location)
667
2
				.ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
668

            
669
			// Insert new foreign asset info
670
2
			AssetsById::<T>::insert(&asset_id, &new_xcm_location);
671
2
			AssetsByLocation::<T>::insert(&new_xcm_location, (asset_id, asset_status));
672

            
673
2
			Self::deposit_event(Event::ForeignAssetXcmLocationChanged {
674
2
				asset_id,
675
2
				new_xcm_location,
676
2
				previous_xcm_location,
677
2
			});
678
2
			Ok(())
679
3
		}
680

            
681
9
		pub fn do_freeze_asset(
682
9
			asset_id: AssetId,
683
9
			xcm_location: Location,
684
9
			allow_xcm_deposit: bool,
685
9
		) -> DispatchResult {
686
9
			let (_asset_id, asset_status) = AssetsByLocation::<T>::get(&xcm_location)
687
9
				.ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
688

            
689
9
			ensure!(
690
9
				asset_status == AssetStatus::Active,
691
2
				Error::<T>::AssetAlreadyFrozen
692
			);
693

            
694
7
			EvmCaller::<T>::erc20_pause(asset_id)?;
695

            
696
7
			let new_asset_status = if allow_xcm_deposit {
697
7
				AssetStatus::FrozenXcmDepositAllowed
698
			} else {
699
				AssetStatus::FrozenXcmDepositForbidden
700
			};
701

            
702
7
			AssetsByLocation::<T>::insert(&xcm_location, (asset_id, new_asset_status));
703

            
704
7
			Self::deposit_event(Event::ForeignAssetFrozen {
705
7
				asset_id,
706
7
				xcm_location,
707
7
			});
708
7
			Ok(())
709
9
		}
710

            
711
8
		pub fn do_unfreeze_asset(asset_id: AssetId, xcm_location: Location) -> DispatchResult {
712
8
			let (_asset_id, asset_status) = AssetsByLocation::<T>::get(&xcm_location)
713
8
				.ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
714

            
715
8
			ensure!(
716
8
				asset_status == AssetStatus::FrozenXcmDepositAllowed
717
2
					|| asset_status == AssetStatus::FrozenXcmDepositForbidden,
718
2
				Error::<T>::AssetNotFrozen
719
			);
720

            
721
6
			EvmCaller::<T>::erc20_unpause(asset_id)?;
722

            
723
6
			AssetsByLocation::<T>::insert(&xcm_location, (asset_id, AssetStatus::Active));
724

            
725
6
			Self::deposit_event(Event::ForeignAssetUnfrozen {
726
6
				asset_id,
727
6
				xcm_location,
728
6
			});
729
6
			Ok(())
730
8
		}
731
	}
732

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

            
741
125
			if let AssetStatus::FrozenXcmDepositForbidden = asset_status {
742
				return Err(MatchError::AssetNotHandled.into());
743
125
			}
744

            
745
125
			let beneficiary = T::XcmLocationToH160::convert_location(who)
746
125
				.ok_or(MatchError::AccountIdConversionFailed)?;
747

            
748
			// We perform the evm transfers in a storage transaction to ensure that if it fail
749
			// any contract storage changes are rolled back.
750
125
			frame_support::storage::with_storage_layer(|| {
751
125
				EvmCaller::<T>::erc20_mint_into(contract_address, beneficiary, amount)
752
125
			})?;
753

            
754
125
			Ok(())
755
125
		}
756

            
757
		fn internal_transfer_asset(
758
			asset: &Asset,
759
			from: &Location,
760
			to: &Location,
761
			_context: &XcmContext,
762
		) -> Result<AssetsInHolding, XcmError> {
763
			let (contract_address, amount, asset_status) =
764
				ForeignAssetsMatcher::<T>::match_asset(asset)?;
765

            
766
			if let AssetStatus::FrozenXcmDepositForbidden | AssetStatus::FrozenXcmDepositAllowed =
767
				asset_status
768
			{
769
				return Err(MatchError::AssetNotHandled.into());
770
			}
771

            
772
			let from = T::XcmLocationToH160::convert_location(from)
773
				.ok_or(MatchError::AccountIdConversionFailed)?;
774

            
775
			let to = T::XcmLocationToH160::convert_location(to)
776
				.ok_or(MatchError::AccountIdConversionFailed)?;
777

            
778
			// We perform the evm transfers in a storage transaction to ensure that if it fail
779
			// any contract storage changes are rolled back.
780
			frame_support::storage::with_storage_layer(|| {
781
				EvmCaller::<T>::erc20_transfer(contract_address, from, to, amount)
782
			})?;
783

            
784
			Ok(asset.clone().into())
785
		}
786

            
787
		// Since we don't control the erc20 contract that manages the asset we want to withdraw,
788
		// we can't really withdraw this asset, we can only transfer it to another account.
789
		// It would be possible to transfer the asset to a dedicated account that would reflect
790
		// the content of the xcm holding, but this would imply to perform two evm calls instead of
791
		// one (1 to withdraw the asset and a second one to deposit it).
792
		// In order to perform only one evm call, we just trace the origin of the asset,
793
		// and then the transfer will only really be performed in the deposit instruction.
794
95
		fn withdraw_asset(
795
95
			what: &Asset,
796
95
			who: &Location,
797
95
			_context: Option<&XcmContext>,
798
95
		) -> Result<AssetsInHolding, XcmError> {
799
95
			let (contract_address, amount, asset_status) =
800
95
				ForeignAssetsMatcher::<T>::match_asset(what)?;
801
95
			let who = T::XcmLocationToH160::convert_location(who)
802
95
				.ok_or(MatchError::AccountIdConversionFailed)?;
803

            
804
			if let AssetStatus::FrozenXcmDepositForbidden | AssetStatus::FrozenXcmDepositAllowed =
805
95
				asset_status
806
			{
807
				return Err(MatchError::AssetNotHandled.into());
808
95
			}
809

            
810
			// We perform the evm transfers in a storage transaction to ensure that if it fail
811
			// any contract storage changes are rolled back.
812
95
			frame_support::storage::with_storage_layer(|| {
813
95
				EvmCaller::<T>::erc20_burn_from(contract_address, who, amount)
814
95
			})?;
815

            
816
95
			Ok(what.clone().into())
817
95
		}
818
	}
819

            
820
	impl<T: Config> sp_runtime::traits::MaybeEquivalence<Location, AssetId> for Pallet<T> {
821
		fn convert(location: &Location) -> Option<AssetId> {
822
			AssetsByLocation::<T>::get(location).map(|(asset_id, _)| asset_id)
823
		}
824
87
		fn convert_back(asset_id: &AssetId) -> Option<Location> {
825
87
			AssetsById::<T>::get(asset_id)
826
87
		}
827
	}
828
}