1
// Copyright 2019-2025 PureStake Inc.
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
//! # Asset Manager Pallet
18
//!
19
//! This pallet allows to register new assets if certain conditions are met
20
//! The main goal of this pallet is to allow moonbeam to register XCM assets
21
//! The assumption is we work with AssetTypes, which can then be compared to AssetIds
22
//!
23
//! This pallet has five storage items: AssetIdType, which holds a mapping from AssetId->AssetType
24
//! AssetTypeUnitsPerSecond: an AssetType->u128 mapping that holds how much each AssetType should
25
//! be charged per unit of second, in the case such an Asset is received as a XCM asset. Finally,
26
//! AssetTypeId holds a mapping from AssetType -> AssetId.
27
//!
28
//! This pallet has eight extrinsics: register_foreign_asset, which registers a foreign
29
//! asset in this pallet and creates the asset as dictated by the AssetRegistrar trait.
30
//! change_existing_asset_type: which allows to update the correspondence between AssetId and
31
//! AssetType
32
//! remove_supported_asset: which removes an asset from the supported assets for fee payment
33
//! remove_existing_asset_type: which removes a mapping from a foreign asset to an assetId
34
//! destroy_foreign_asset: which destroys a foreign asset and all its associated data
35

            
36
#![cfg_attr(not(feature = "std"), no_std)]
37

            
38
use frame_support::pallet;
39
pub use pallet::*;
40
#[cfg(any(test, feature = "runtime-benchmarks"))]
41
mod benchmarks;
42
pub mod migrations;
43
#[cfg(test)]
44
pub mod mock;
45
#[cfg(test)]
46
pub mod tests;
47
pub mod weights;
48

            
49
pub use crate::weights::WeightInfo;
50

            
51
412
#[pallet]
52
pub mod pallet {
53
	use super::*;
54
	use frame_support::{pallet_prelude::*, PalletId};
55
	use frame_system::pallet_prelude::*;
56
	use parity_scale_codec::HasCompact;
57
	use sp_runtime::traits::{AccountIdConversion, AtLeast32BitUnsigned};
58

            
59
76
	#[pallet::pallet]
60
	#[pallet::without_storage_info]
61
	pub struct Pallet<T>(PhantomData<T>);
62

            
63
	/// The AssetManagers's pallet id
64
	pub const PALLET_ID: PalletId = PalletId(*b"asstmngr");
65

            
66
	// The registrar trait. We need to comply with this
67
	pub trait AssetRegistrar<T: Config> {
68
		// How to create a foreign asset, meaning an asset whose reserve chain
69
		// is not our chain
70
		fn create_foreign_asset(
71
			_asset: T::AssetId,
72
			_min_balance: T::Balance,
73
			_metadata: T::AssetRegistrarMetadata,
74
			// Wether or not an asset-receiving account increments the sufficient counter
75
			_is_sufficient: bool,
76
		) -> DispatchResult {
77
			unimplemented!()
78
		}
79

            
80
		// How to destroy a foreign asset
81
		fn destroy_foreign_asset(_asset: T::AssetId) -> DispatchResult {
82
			unimplemented!()
83
		}
84

            
85
		// Get destroy asset weight dispatch info
86
		fn destroy_asset_dispatch_info_weight(_asset: T::AssetId) -> Weight;
87
	}
88

            
89
	// We implement this trait to be able to get the AssetType and units per second registered
90
	impl<T: Config> xcm_primitives::AssetTypeGetter<T::AssetId, T::ForeignAssetType> for Pallet<T> {
91
143
		fn get_asset_type(asset_id: T::AssetId) -> Option<T::ForeignAssetType> {
92
143
			AssetIdType::<T>::get(asset_id)
93
143
		}
94

            
95
231
		fn get_asset_id(asset_type: T::ForeignAssetType) -> Option<T::AssetId> {
96
231
			AssetTypeId::<T>::get(asset_type)
97
231
		}
98
		#[cfg(feature = "runtime-benchmarks")]
99
		fn set_asset_type_asset_id(asset_type: T::ForeignAssetType, asset_id: T::AssetId) {
100
			AssetTypeId::<T>::insert(&asset_type, asset_id);
101
			AssetIdType::<T>::insert(&asset_id, asset_type);
102
		}
103
	}
104

            
105
	#[pallet::config]
106
	pub trait Config: frame_system::Config {
107
		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
108

            
109
		/// The Asset Id. This will be used to create the asset and to associate it with
110
		/// a assetType
111
		type AssetId: Member + Parameter + Default + Copy + HasCompact + MaxEncodedLen;
112

            
113
		/// The Asset Metadata we want to store
114
		type AssetRegistrarMetadata: Member + Parameter + Default;
115

            
116
		/// The Foreign Asset Kind.
117
		type ForeignAssetType: Parameter + Member + Ord + PartialOrd + Into<Self::AssetId> + Default;
118

            
119
		/// The units in which we record balances.
120
		type Balance: Member + Parameter + AtLeast32BitUnsigned + Default + Copy + MaxEncodedLen;
121

            
122
		/// The asset Registrar.
123
		/// The trait we use to register Assets
124
		type AssetRegistrar: AssetRegistrar<Self>;
125

            
126
		/// Origin that is allowed to create and modify asset information for foreign assets
127
		type ForeignAssetModifierOrigin: EnsureOrigin<Self::RuntimeOrigin>;
128

            
129
		type WeightInfo: WeightInfo;
130
	}
131

            
132
	/// An error that can occur while executing the mapping pallet's logic.
133
8
	#[pallet::error]
134
	pub enum Error<T> {
135
		ErrorCreatingAsset,
136
		AssetAlreadyExists,
137
		AssetDoesNotExist,
138
		TooLowNumAssetsWeightHint,
139
		LocalAssetLimitReached,
140
		ErrorDestroyingAsset,
141
		NotSufficientDeposit,
142
		NonExistentLocalAsset,
143
	}
144

            
145
20
	#[pallet::event]
146
148
	#[pallet::generate_deposit(pub(crate) fn deposit_event)]
147
	pub enum Event<T: Config> {
148
17
		/// New asset with the asset manager is registered
149
		ForeignAssetRegistered {
150
			asset_id: T::AssetId,
151
			asset: T::ForeignAssetType,
152
			metadata: T::AssetRegistrarMetadata,
153
		},
154
		/// Changed the amount of units we are charging per execution second for a given asset
155
		#[deprecated(note = "Should not be used")]
156
		UnitsPerSecondChanged,
157
1
		/// Changed the xcm type mapping for a given asset id
158
		ForeignAssetXcmLocationChanged {
159
			asset_id: T::AssetId,
160
			new_asset_type: T::ForeignAssetType,
161
		},
162
2
		/// Removed all information related to an assetId
163
		ForeignAssetRemoved {
164
			asset_id: T::AssetId,
165
			asset_type: T::ForeignAssetType,
166
		},
167
		/// Supported asset type for fee payment removed
168
		SupportedAssetRemoved { asset_type: T::ForeignAssetType },
169
1
		/// Removed all information related to an assetId and destroyed asset
170
		ForeignAssetDestroyed {
171
			asset_id: T::AssetId,
172
			asset_type: T::ForeignAssetType,
173
		},
174
		/// Removed all information related to an assetId and destroyed asset
175
		LocalAssetDestroyed { asset_id: T::AssetId },
176
	}
177

            
178
	/// Mapping from an asset id to asset type.
179
	/// This is mostly used when receiving transaction specifying an asset directly,
180
	/// like transferring an asset from this chain to another.
181
493
	#[pallet::storage]
182
	#[pallet::getter(fn asset_id_type)]
183
	pub type AssetIdType<T: Config> =
184
		StorageMap<_, Blake2_128Concat, T::AssetId, T::ForeignAssetType>;
185

            
186
	/// Reverse mapping of AssetIdType. Mapping from an asset type to an asset id.
187
	/// This is mostly used when receiving a multilocation XCM message to retrieve
188
	/// the corresponding asset in which tokens should me minted.
189
412
	#[pallet::storage]
190
	#[pallet::getter(fn asset_type_id)]
191
	pub type AssetTypeId<T: Config> =
192
		StorageMap<_, Blake2_128Concat, T::ForeignAssetType, T::AssetId>;
193

            
194
	#[pallet::call]
195
	impl<T: Config> Pallet<T> {
196
		/// Register new asset with the asset manager
197
		#[pallet::call_index(0)]
198
		#[pallet::weight(T::WeightInfo::register_foreign_asset())]
199
		pub fn register_foreign_asset(
200
			origin: OriginFor<T>,
201
			asset: T::ForeignAssetType,
202
			metadata: T::AssetRegistrarMetadata,
203
			min_amount: T::Balance,
204
			is_sufficient: bool,
205
146
		) -> DispatchResult {
206
146
			T::ForeignAssetModifierOrigin::ensure_origin(origin)?;
207

            
208
			// Compute assetId from asset
209
145
			let asset_id: T::AssetId = asset.clone().into();
210
145

            
211
145
			// Ensure such an assetId does not exist
212
145
			ensure!(
213
145
				AssetIdType::<T>::get(&asset_id).is_none(),
214
1
				Error::<T>::AssetAlreadyExists
215
			);
216
144
			T::AssetRegistrar::create_foreign_asset(
217
144
				asset_id,
218
144
				min_amount,
219
144
				metadata.clone(),
220
144
				is_sufficient,
221
144
			)
222
144
			.map_err(|_| Error::<T>::ErrorCreatingAsset)?;
223

            
224
			// Insert the association assetId->assetType
225
144
			AssetIdType::<T>::insert(&asset_id, &asset);
226
144
			AssetTypeId::<T>::insert(&asset, &asset_id);
227
144

            
228
144
			Self::deposit_event(Event::ForeignAssetRegistered {
229
144
				asset_id,
230
144
				asset,
231
144
				metadata,
232
144
			});
233
144
			Ok(())
234
		}
235

            
236
		/// Change the xcm type mapping for a given assetId
237
		/// We also change this if the previous units per second where pointing at the old
238
		/// assetType
239
		#[pallet::call_index(2)]
240
		#[pallet::weight(T::WeightInfo::change_existing_asset_type())]
241
		pub fn change_existing_asset_type(
242
			origin: OriginFor<T>,
243
			asset_id: T::AssetId,
244
			new_asset_type: T::ForeignAssetType,
245
			_num_assets_weight_hint: u32,
246
3
		) -> DispatchResult {
247
3
			T::ForeignAssetModifierOrigin::ensure_origin(origin)?;
248

            
249
1
			let previous_asset_type =
250
2
				AssetIdType::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
251

            
252
			// Insert new asset type info
253
1
			AssetIdType::<T>::insert(&asset_id, &new_asset_type);
254
1
			AssetTypeId::<T>::insert(&new_asset_type, &asset_id);
255
1

            
256
1
			// Remove previous asset type info
257
1
			AssetTypeId::<T>::remove(&previous_asset_type);
258
1

            
259
1
			Self::deposit_event(Event::ForeignAssetXcmLocationChanged {
260
1
				asset_id,
261
1
				new_asset_type,
262
1
			});
263
1
			Ok(())
264
		}
265

            
266
		/// Remove a given assetId -> assetType association
267
		#[pallet::call_index(4)]
268
		#[pallet::weight(T::WeightInfo::remove_existing_asset_type())]
269
		pub fn remove_existing_asset_type(
270
			origin: OriginFor<T>,
271
			asset_id: T::AssetId,
272
			_num_assets_weight_hint: u32,
273
2
		) -> DispatchResult {
274
2
			T::ForeignAssetModifierOrigin::ensure_origin(origin)?;
275

            
276
2
			let asset_type =
277
2
				AssetIdType::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
278

            
279
			// Remove from AssetIdType
280
2
			AssetIdType::<T>::remove(&asset_id);
281
2
			// Remove from AssetTypeId
282
2
			AssetTypeId::<T>::remove(&asset_type);
283
2

            
284
2
			Self::deposit_event(Event::ForeignAssetRemoved {
285
2
				asset_id,
286
2
				asset_type,
287
2
			});
288
2
			Ok(())
289
		}
290

            
291
		/// Destroy a given foreign assetId
292
		/// The weight in this case is the one returned by the trait
293
		/// plus the db writes and reads from removing all the associated
294
		/// data
295
		#[pallet::call_index(6)]
296
		#[pallet::weight({
297
			let dispatch_info_weight = T::AssetRegistrar::destroy_asset_dispatch_info_weight(
298
				*asset_id
299
			);
300
			T::WeightInfo::remove_existing_asset_type()
301
			.saturating_add(dispatch_info_weight)
302
		})]
303
		pub fn destroy_foreign_asset(
304
			origin: OriginFor<T>,
305
			asset_id: T::AssetId,
306
			_num_assets_weight_hint: u32,
307
1
		) -> DispatchResult {
308
1
			T::ForeignAssetModifierOrigin::ensure_origin(origin)?;
309

            
310
1
			T::AssetRegistrar::destroy_foreign_asset(asset_id)
311
1
				.map_err(|_| Error::<T>::ErrorDestroyingAsset)?;
312

            
313
1
			let asset_type =
314
1
				AssetIdType::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
315

            
316
			// Remove from AssetIdType
317
1
			AssetIdType::<T>::remove(&asset_id);
318
1
			// Remove from AssetTypeId
319
1
			AssetTypeId::<T>::remove(&asset_type);
320
1

            
321
1
			Self::deposit_event(Event::ForeignAssetDestroyed {
322
1
				asset_id,
323
1
				asset_type,
324
1
			});
325
1
			Ok(())
326
		}
327
	}
328

            
329
	impl<T: Config> Pallet<T> {
330
		/// The account ID of AssetManager
331
308
		pub fn account_id() -> T::AccountId {
332
308
			PALLET_ID.into_account_truncating()
333
308
		}
334
	}
335
}