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

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

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

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

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

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

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

            
48
mod evm;
49

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
367
		/// Aprrove a spender to spend a certain amount of tokens from the owner account
368
13
		pub fn approve(
369
13
			asset_id: AssetId,
370
13
			owner: T::AccountId,
371
13
			spender: T::AccountId,
372
13
			amount: U256,
373
13
		) -> Result<(), evm::EvmError> {
374
13
			// We perform the evm call in a storage transaction to ensure that if it fail
375
13
			// any contract storage changes are rolled back.
376
13
			EvmCaller::<T>::erc20_approve(
377
13
				Self::contract_address_from_asset_id(asset_id),
378
13
				T::AccountIdToH160::convert(owner),
379
13
				T::AccountIdToH160::convert(spender),
380
13
				amount,
381
13
			)
382
13
			.map_err(Into::into)
383
13
		}
384

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

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

            
416
15
			Self::ensure_origin_can_modify_location(origin_type.clone(), &asset_xcm_location)?;
417
15
			let deposit_account = Self::get_deposit_account(origin_type)?;
418

            
419
15
			Self::do_create_asset(
420
15
				asset_id,
421
15
				asset_xcm_location,
422
15
				decimals,
423
15
				symbol,
424
15
				name,
425
15
				deposit_account,
426
15
			)
427
		}
428

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

            
441
5
			Self::ensure_origin_can_modify_location(origin_type.clone(), &new_xcm_location)?;
442

            
443
4
			let previous_location =
444
5
				AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
445

            
446
4
			Self::ensure_origin_can_modify_location(origin_type, &previous_location)?;
447

            
448
3
			Self::do_change_xcm_location(asset_id, previous_location, new_xcm_location)
449
		}
450

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

            
461
9
			let xcm_location =
462
9
				AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
463

            
464
9
			Self::ensure_origin_can_modify_location(origin_type, &xcm_location)?;
465

            
466
7
			Self::do_freeze_asset(asset_id, xcm_location, allow_xcm_deposit)
467
		}
468

            
469
		/// Unfreeze a given foreign assetId
470
		#[pallet::call_index(3)]
471
		#[pallet::weight(<T as Config>::WeightInfo::unfreeze_foreign_asset())]
472
6
		pub fn unfreeze_foreign_asset(origin: OriginFor<T>, asset_id: AssetId) -> DispatchResult {
473
6
			let origin_type = T::ForeignAssetUnfreezerOrigin::ensure_origin(origin.clone())?;
474

            
475
6
			let xcm_location =
476
6
				AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
477

            
478
6
			Self::ensure_origin_can_modify_location(origin_type, &xcm_location)?;
479

            
480
6
			Self::do_unfreeze_asset(asset_id, xcm_location)
481
		}
482
	}
483

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

            
504
15
		fn get_deposit_account(
505
15
			origin_type: OriginType,
506
15
		) -> Result<Option<T::AccountId>, DispatchError> {
507
15
			match origin_type {
508
3
				OriginType::XCM(origin_location) => {
509
3
					let deposit_account = convert_location::<T>(&origin_location)?;
510
3
					Ok(Some(deposit_account))
511
				}
512
12
				OriginType::Governance => Ok(None),
513
			}
514
15
		}
515

            
516
32
		pub fn do_create_asset(
517
32
			asset_id: AssetId,
518
32
			asset_xcm_location: Location,
519
32
			decimals: u8,
520
32
			symbol: BoundedVec<u8, ConstU32<256>>,
521
32
			name: BoundedVec<u8, ConstU32<256>>,
522
32
			deposit_account: Option<T::AccountId>,
523
32
		) -> DispatchResult {
524
32
			ensure!(
525
32
				!AssetsById::<T>::contains_key(&asset_id),
526
1
				Error::<T>::AssetAlreadyExists
527
			);
528

            
529
31
			ensure!(
530
31
				!AssetsByLocation::<T>::contains_key(&asset_xcm_location),
531
1
				Error::<T>::LocationAlreadyExists
532
			);
533

            
534
30
			ensure!(
535
30
				AssetsById::<T>::count() < T::MaxForeignAssets::get(),
536
				Error::<T>::TooManyForeignAssets
537
			);
538

            
539
30
			ensure!(
540
30
				T::AssetIdFilter::contains(&asset_id),
541
				Error::<T>::AssetIdFiltered
542
			);
543

            
544
30
			let symbol = core::str::from_utf8(&symbol).map_err(|_| Error::<T>::InvalidSymbol)?;
545
30
			let name = core::str::from_utf8(&name).map_err(|_| Error::<T>::InvalidTokenName)?;
546
30
			let contract_address = EvmCaller::<T>::erc20_create(asset_id, decimals, symbol, name)?;
547

            
548
30
			let deposit = if let Some(deposit_account) = deposit_account {
549
3
				let deposit = T::ForeignAssetCreationDeposit::get();
550
3

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

            
554
				// Insert the amount that is reserved from the user
555
3
				AssetsCreationDetails::<T>::insert(
556
3
					&asset_id,
557
3
					AssetDepositDetails {
558
3
						deposit_account,
559
3
						deposit,
560
3
					},
561
3
				);
562
3

            
563
3
				Some(deposit)
564
			} else {
565
27
				None
566
			};
567

            
568
			// Insert the association assetId->foreigAsset
569
			// Insert the association foreigAsset->assetId
570
30
			AssetsById::<T>::insert(&asset_id, &asset_xcm_location);
571
30
			AssetsByLocation::<T>::insert(&asset_xcm_location, (asset_id, AssetStatus::Active));
572
30

            
573
30
			T::OnForeignAssetCreated::on_asset_created(&asset_xcm_location, &asset_id);
574
30

            
575
30
			Self::deposit_event(Event::ForeignAssetCreated {
576
30
				contract_address,
577
30
				asset_id,
578
30
				xcm_location: asset_xcm_location,
579
30
				deposit,
580
30
			});
581
30
			Ok(())
582
32
		}
583

            
584
3
		pub fn do_change_xcm_location(
585
3
			asset_id: AssetId,
586
3
			previous_xcm_location: Location,
587
3
			new_xcm_location: Location,
588
3
		) -> DispatchResult {
589
3
			ensure!(
590
3
				!AssetsByLocation::<T>::contains_key(&new_xcm_location),
591
1
				Error::<T>::LocationAlreadyExists
592
			);
593

            
594
			// Remove previous foreign asset info
595
2
			let (_asset_id, asset_status) = AssetsByLocation::<T>::take(&previous_xcm_location)
596
2
				.ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
597

            
598
			// Insert new foreign asset info
599
2
			AssetsById::<T>::insert(&asset_id, &new_xcm_location);
600
2
			AssetsByLocation::<T>::insert(&new_xcm_location, (asset_id, asset_status));
601
2

            
602
2
			Self::deposit_event(Event::ForeignAssetXcmLocationChanged {
603
2
				asset_id,
604
2
				new_xcm_location,
605
2
				previous_xcm_location,
606
2
			});
607
2
			Ok(())
608
3
		}
609

            
610
7
		pub fn do_freeze_asset(
611
7
			asset_id: AssetId,
612
7
			xcm_location: Location,
613
7
			allow_xcm_deposit: bool,
614
7
		) -> DispatchResult {
615
7
			let (_asset_id, asset_status) = AssetsByLocation::<T>::get(&xcm_location)
616
7
				.ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
617

            
618
7
			ensure!(
619
7
				asset_status == AssetStatus::Active,
620
2
				Error::<T>::AssetAlreadyFrozen
621
			);
622

            
623
5
			EvmCaller::<T>::erc20_pause(asset_id)?;
624

            
625
5
			let new_asset_status = if allow_xcm_deposit {
626
5
				AssetStatus::FrozenXcmDepositAllowed
627
			} else {
628
				AssetStatus::FrozenXcmDepositForbidden
629
			};
630

            
631
5
			AssetsByLocation::<T>::insert(&xcm_location, (asset_id, new_asset_status));
632
5

            
633
5
			Self::deposit_event(Event::ForeignAssetFrozen {
634
5
				asset_id,
635
5
				xcm_location,
636
5
			});
637
5
			Ok(())
638
7
		}
639

            
640
6
		pub fn do_unfreeze_asset(asset_id: AssetId, xcm_location: Location) -> DispatchResult {
641
6
			let (_asset_id, asset_status) = AssetsByLocation::<T>::get(&xcm_location)
642
6
				.ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
643

            
644
6
			ensure!(
645
6
				asset_status == AssetStatus::FrozenXcmDepositAllowed
646
2
					|| asset_status == AssetStatus::FrozenXcmDepositForbidden,
647
2
				Error::<T>::AssetNotFrozen
648
			);
649

            
650
4
			EvmCaller::<T>::erc20_unpause(asset_id)?;
651

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

            
654
4
			Self::deposit_event(Event::ForeignAssetUnfrozen {
655
4
				asset_id,
656
4
				xcm_location,
657
4
			});
658
4
			Ok(())
659
6
		}
660
	}
661

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

            
670
			if let AssetStatus::FrozenXcmDepositForbidden = asset_status {
671
				return Err(MatchError::AssetNotHandled.into());
672
			}
673

            
674
			let beneficiary = T::XcmLocationToH160::convert_location(who)
675
				.ok_or(MatchError::AccountIdConversionFailed)?;
676

            
677
			// We perform the evm transfers in a storage transaction to ensure that if it fail
678
			// any contract storage changes are rolled back.
679
			frame_support::storage::with_storage_layer(|| {
680
				EvmCaller::<T>::erc20_mint_into(contract_address, beneficiary, amount)
681
			})?;
682

            
683
			Ok(())
684
		}
685

            
686
		fn internal_transfer_asset(
687
			asset: &Asset,
688
			from: &Location,
689
			to: &Location,
690
			_context: &XcmContext,
691
		) -> Result<AssetsInHolding, XcmError> {
692
			let (contract_address, amount, asset_status) =
693
				ForeignAssetsMatcher::<T>::match_asset(asset)?;
694

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

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

            
704
			let to = T::XcmLocationToH160::convert_location(to)
705
				.ok_or(MatchError::AccountIdConversionFailed)?;
706

            
707
			// We perform the evm transfers in a storage transaction to ensure that if it fail
708
			// any contract storage changes are rolled back.
709
			frame_support::storage::with_storage_layer(|| {
710
				EvmCaller::<T>::erc20_transfer(contract_address, from, to, amount)
711
			})?;
712

            
713
			Ok(asset.clone().into())
714
		}
715

            
716
		// Since we don't control the erc20 contract that manages the asset we want to withdraw,
717
		// we can't really withdraw this asset, we can only transfer it to another account.
718
		// It would be possible to transfer the asset to a dedicated account that would reflect
719
		// the content of the xcm holding, but this would imply to perform two evm calls instead of
720
		// one (1 to withdraw the asset and a second one to deposit it).
721
		// In order to perform only one evm call, we just trace the origin of the asset,
722
		// and then the transfer will only really be performed in the deposit instruction.
723
66
		fn withdraw_asset(
724
66
			what: &Asset,
725
66
			who: &Location,
726
66
			_context: Option<&XcmContext>,
727
66
		) -> Result<AssetsInHolding, XcmError> {
728
32
			let (contract_address, amount, asset_status) =
729
66
				ForeignAssetsMatcher::<T>::match_asset(what)?;
730
32
			let who = T::XcmLocationToH160::convert_location(who)
731
32
				.ok_or(MatchError::AccountIdConversionFailed)?;
732

            
733
32
			if let AssetStatus::FrozenXcmDepositForbidden | AssetStatus::FrozenXcmDepositAllowed =
734
32
				asset_status
735
			{
736
				return Err(MatchError::AssetNotHandled.into());
737
32
			}
738
32

            
739
32
			// We perform the evm transfers in a storage transaction to ensure that if it fail
740
32
			// any contract storage changes are rolled back.
741
32
			frame_support::storage::with_storage_layer(|| {
742
32
				EvmCaller::<T>::erc20_burn_from(contract_address, who, amount)
743
32
			})?;
744

            
745
32
			Ok(what.clone().into())
746
66
		}
747
	}
748

            
749
	impl<T: Config> sp_runtime::traits::MaybeEquivalence<Location, AssetId> for Pallet<T> {
750
		fn convert(location: &Location) -> Option<AssetId> {
751
			AssetsByLocation::<T>::get(location).map(|(asset_id, _)| asset_id)
752
		}
753
66
		fn convert_back(asset_id: &AssetId) -> Option<Location> {
754
66
			AssetsById::<T>::get(asset_id)
755
66
		}
756
	}
757
}