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_prelude::*;
56
use frame_support::traits::Contains;
57
use frame_support::{pallet, Deserialize, Serialize};
58
use frame_system::pallet_prelude::*;
59
use sp_std::{vec, vec::Vec};
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
14
	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
65
	fn match_asset(asset: &Asset) -> Result<(H160, U256, AssetStatus), MatchError> {
131
65
		let (amount, location) = match (&asset.fun, &asset.id) {
132
65
			(Fungibility::Fungible(ref amount), XcmAssetId(ref location)) => (amount, location),
133
			_ => return Err(MatchError::AssetNotHandled),
134
		};
135

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

            
148
171
#[derive(Decode, Debug, Encode, PartialEq, TypeInfo)]
149
pub enum AssetStatus {
150
55
	/// 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
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
159
pub struct EvmForeignAssetInfo {
160
	pub asset_id: AssetId,
161
	pub xcm_location: Location,
162
	pub decimals: u8,
163
	pub symbol: BoundedVec<u8, ConstU32<256>>,
164
	pub name: BoundedVec<u8, ConstU32<256>>,
165
}
166

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

            
177
69
	#[pallet::pallet]
178
	#[pallet::without_storage_info]
179
	pub struct Pallet<T>(PhantomData<T>);
180

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

            
184
	#[pallet::config]
185
	pub trait Config: frame_system::Config + pallet_evm::Config + scale_info::TypeInfo {
186
		// Convert AccountId to H160
187
		type AccountIdToH160: Convert<Self::AccountId, H160>;
188

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

            
192
		/// EVM runner
193
		type EvmRunner: Runner<Self>;
194

            
195
		type ConvertLocation: ConvertLocation<Self::AccountId>;
196

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

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

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

            
206
		/// Origin that is allowed to unfreeze all tokens of a foreign asset that was previously
207
		/// frozen
208
		type ForeignAssetUnfreezerOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = OriginType>;
209

            
210
		/// Hook to be called when new foreign asset is registered.
211
		type OnForeignAssetCreated: ForeignAssetCreatedHook<Location>;
212

            
213
		/// Maximum numbers of different foreign assets
214
		type MaxForeignAssets: Get<u32>;
215

            
216
		/// The overarching event type.
217
		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
218

            
219
		/// Weight information for extrinsics in this pallet.
220
		type WeightInfo: WeightInfo;
221

            
222
		// Convert XCM Location to H160
223
		type XcmLocationToH160: ConvertLocation<H160>;
224

            
225
		/// Amount of tokens required to lock for creating a new foreign asset
226
		type ForeignAssetCreationDeposit: Get<BalanceOf<Self>>;
227

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

            
238
		/// The currency type for locking funds
239
		type Currency: ReservableCurrency<Self::AccountId>;
240
	}
241

            
242
	type BalanceOf<T> =
243
		<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
244

            
245
	pub type AssetBalance = U256;
246
	pub type AssetId = u128;
247

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
428
12
		pub fn balance(asset_id: AssetId, who: T::AccountId) -> Result<U256, evm::EvmError> {
429
12
			EvmCaller::<T>::erc20_balance_of(asset_id, T::AccountIdToH160::convert(who))
430
12
				.map_err(Into::into)
431
12
		}
432

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

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

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

            
484
19
			Self::ensure_origin_can_modify_location(origin_type.clone(), &asset_xcm_location)?;
485
19
			let deposit_account = Self::get_deposit_account(origin_type)?;
486

            
487
19
			Self::do_create_asset(
488
19
				asset_id,
489
19
				asset_xcm_location,
490
19
				decimals,
491
19
				symbol,
492
19
				name,
493
19
				deposit_account,
494
19
			)
495
		}
496

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

            
509
5
			Self::ensure_origin_can_modify_location(origin_type.clone(), &new_xcm_location)?;
510

            
511
4
			let previous_location =
512
5
				AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
513

            
514
4
			Self::ensure_origin_can_modify_location(origin_type, &previous_location)?;
515

            
516
3
			Self::do_change_xcm_location(asset_id, previous_location, new_xcm_location)
517
		}
518

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

            
529
9
			let xcm_location =
530
9
				AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
531

            
532
9
			Self::ensure_origin_can_modify_location(origin_type, &xcm_location)?;
533

            
534
7
			Self::do_freeze_asset(asset_id, xcm_location, allow_xcm_deposit)
535
		}
536

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

            
543
6
			let xcm_location =
544
6
				AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
545

            
546
6
			Self::ensure_origin_can_modify_location(origin_type, &xcm_location)?;
547

            
548
6
			Self::do_unfreeze_asset(asset_id, xcm_location)
549
		}
550
	}
551

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

            
572
19
		fn get_deposit_account(
573
19
			origin_type: OriginType,
574
19
		) -> Result<Option<T::AccountId>, DispatchError> {
575
19
			match origin_type {
576
3
				OriginType::XCM(origin_location) => {
577
3
					let deposit_account = convert_location::<T>(&origin_location)?;
578
3
					Ok(Some(deposit_account))
579
				}
580
16
				OriginType::Governance => Ok(None),
581
			}
582
19
		}
583

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

            
597
22
			ensure!(
598
22
				!AssetsByLocation::<T>::contains_key(&asset_xcm_location),
599
1
				Error::<T>::LocationAlreadyExists
600
			);
601

            
602
21
			ensure!(
603
21
				AssetsById::<T>::count() < T::MaxForeignAssets::get(),
604
				Error::<T>::TooManyForeignAssets
605
			);
606

            
607
21
			ensure!(
608
21
				T::AssetIdFilter::contains(&asset_id),
609
				Error::<T>::AssetIdFiltered
610
			);
611

            
612
21
			let symbol = core::str::from_utf8(&symbol).map_err(|_| Error::<T>::InvalidSymbol)?;
613
21
			let name = core::str::from_utf8(&name).map_err(|_| Error::<T>::InvalidTokenName)?;
614
21
			let contract_address = EvmCaller::<T>::erc20_create(asset_id, decimals, symbol, name)?;
615

            
616
21
			let deposit = if let Some(deposit_account) = deposit_account {
617
3
				let deposit = T::ForeignAssetCreationDeposit::get();
618
3

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

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

            
631
3
				Some(deposit)
632
			} else {
633
18
				None
634
			};
635

            
636
			// Insert the association assetId->foreigAsset
637
			// Insert the association foreigAsset->assetId
638
21
			AssetsById::<T>::insert(&asset_id, &asset_xcm_location);
639
21
			AssetsByLocation::<T>::insert(&asset_xcm_location, (asset_id, AssetStatus::Active));
640
21

            
641
21
			T::OnForeignAssetCreated::on_asset_created(&asset_xcm_location, &asset_id);
642
21

            
643
21
			Self::deposit_event(Event::ForeignAssetCreated {
644
21
				contract_address,
645
21
				asset_id,
646
21
				xcm_location: asset_xcm_location,
647
21
				deposit,
648
21
			});
649
21
			Ok(())
650
23
		}
651

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

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

            
666
			// Insert new foreign asset info
667
2
			AssetsById::<T>::insert(&asset_id, &new_xcm_location);
668
2
			AssetsByLocation::<T>::insert(&new_xcm_location, (asset_id, asset_status));
669
2

            
670
2
			Self::deposit_event(Event::ForeignAssetXcmLocationChanged {
671
2
				asset_id,
672
2
				new_xcm_location,
673
2
				previous_xcm_location,
674
2
			});
675
2
			Ok(())
676
3
		}
677

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

            
686
7
			ensure!(
687
7
				asset_status == AssetStatus::Active,
688
2
				Error::<T>::AssetAlreadyFrozen
689
			);
690

            
691
5
			EvmCaller::<T>::erc20_pause(asset_id)?;
692

            
693
5
			let new_asset_status = if allow_xcm_deposit {
694
5
				AssetStatus::FrozenXcmDepositAllowed
695
			} else {
696
				AssetStatus::FrozenXcmDepositForbidden
697
			};
698

            
699
5
			AssetsByLocation::<T>::insert(&xcm_location, (asset_id, new_asset_status));
700
5

            
701
5
			Self::deposit_event(Event::ForeignAssetFrozen {
702
5
				asset_id,
703
5
				xcm_location,
704
5
			});
705
5
			Ok(())
706
7
		}
707

            
708
6
		pub fn do_unfreeze_asset(asset_id: AssetId, xcm_location: Location) -> DispatchResult {
709
6
			let (_asset_id, asset_status) = AssetsByLocation::<T>::get(&xcm_location)
710
6
				.ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
711

            
712
6
			ensure!(
713
6
				asset_status == AssetStatus::FrozenXcmDepositAllowed
714
2
					|| asset_status == AssetStatus::FrozenXcmDepositForbidden,
715
2
				Error::<T>::AssetNotFrozen
716
			);
717

            
718
4
			EvmCaller::<T>::erc20_unpause(asset_id)?;
719

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

            
722
4
			Self::deposit_event(Event::ForeignAssetUnfrozen {
723
4
				asset_id,
724
4
				xcm_location,
725
4
			});
726
4
			Ok(())
727
6
		}
728
	}
729

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

            
738
4
			if let AssetStatus::FrozenXcmDepositForbidden = asset_status {
739
				return Err(MatchError::AssetNotHandled.into());
740
4
			}
741

            
742
4
			let beneficiary = T::XcmLocationToH160::convert_location(who)
743
4
				.ok_or(MatchError::AccountIdConversionFailed)?;
744

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

            
751
4
			Ok(())
752
4
		}
753

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

            
763
			if let AssetStatus::FrozenXcmDepositForbidden | AssetStatus::FrozenXcmDepositAllowed =
764
				asset_status
765
			{
766
				return Err(MatchError::AssetNotHandled.into());
767
			}
768

            
769
			let from = T::XcmLocationToH160::convert_location(from)
770
				.ok_or(MatchError::AccountIdConversionFailed)?;
771

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

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

            
781
			Ok(asset.clone().into())
782
		}
783

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

            
801
			if let AssetStatus::FrozenXcmDepositForbidden | AssetStatus::FrozenXcmDepositAllowed =
802
29
				asset_status
803
			{
804
				return Err(MatchError::AssetNotHandled.into());
805
29
			}
806
29

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

            
813
29
			Ok(what.clone().into())
814
61
		}
815
	}
816

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