pallet_xc_asset_config/
lib.rs

1// This file is part of Astar.
2
3// Copyright (C) Stake Technologies Pte.Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later
5
6// Astar is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// Astar is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with Astar. If not, see <http://www.gnu.org/licenses/>.
18
19//! # Cross-chain Asset Config Pallet
20//!
21//! ## Overview
22//!
23//! This pallet provides mappings between local asset Id and remove asset location.
24//! E.g. a multilocation like `{parents: 0, interior: X1::(Junction::Parachain(1000))}` could ba mapped to local asset Id `789`.
25//!
26//! The pallet ensures that the latest Location version is always used. Developers must ensure to properly migrate legacy versions
27//! to newest when they become available.
28//!
29//! Additionally, it stores information whether a foreign asset is supported as a payment currency for execution on local network.
30//!
31//! ## Interface
32//!
33//! ### Dispatchable Function
34//!
35//! - `register_asset_location` - used to register mapping between local asset Id and remote asset location
36//! - `set_asset_units_per_second` - registers asset as payment currency and sets the desired payment per second of execution time
37//! - `change_existing_asset_location` - changes the remote location of an existing local asset Id
38//! - `remove_payment_asset` - removes asset from the set of supported payment assets
39//! - `remove_asset` - removes all information related to this asset
40//!
41//! User is encouraged to refer to specific function implementations for more comprehensive documentation.
42//!
43//! ### Other
44//!
45//! `AssetLocationGetter` interface for mapping asset Id to asset location and vice versa
46//! - `get_xc_asset_location`
47//! - `get_asset_id`
48//!
49//! `ExecutionPaymentRate` interface for fetching `units per second` if asset is supported payment asset
50//! - `get_units_per_second`
51//!
52//! - `weight_to_fee` method is used to convert weight to fee based on units per second and weight.
53
54#![cfg_attr(not(feature = "std"), no_std)]
55
56use frame_support::pallet;
57pub use pallet::*;
58
59#[cfg(any(test, feature = "runtime-benchmarks"))]
60mod benchmarking;
61
62#[cfg(test)]
63pub mod mock;
64#[cfg(test)]
65pub mod tests;
66
67pub mod migrations;
68
69pub mod weights;
70pub use weights::WeightInfo;
71
72#[pallet]
73pub mod pallet {
74    use crate::weights::WeightInfo;
75    use frame_support::{
76        pallet_prelude::*, traits::EnsureOrigin, weights::constants::WEIGHT_REF_TIME_PER_SECOND,
77    };
78    use frame_system::pallet_prelude::*;
79    use parity_scale_codec::HasCompact;
80    use sp_std::boxed::Box;
81    use xcm::{v5::Location, VersionedLocation};
82
83    const STORAGE_VERSION: StorageVersion = StorageVersion::new(5);
84
85    #[pallet::pallet]
86    #[pallet::storage_version(STORAGE_VERSION)]
87    #[pallet::without_storage_info]
88    pub struct Pallet<T>(PhantomData<T>);
89
90    /// Defines conversion between asset Id and cross-chain asset location
91    pub trait XcAssetLocation<AssetId> {
92        /// Get asset type from assetId
93        fn get_xc_asset_location(asset_id: AssetId) -> Option<Location>;
94
95        /// Get local asset Id from asset location
96        fn get_asset_id(xc_asset_location: Location) -> Option<AssetId>;
97    }
98
99    /// Used to fetch `units per second` if cross-chain asset is applicable for local execution payment.
100    pub trait ExecutionPaymentRate {
101        /// returns units per second from asset type or `None` if asset type isn't a supported payment asset.
102        fn get_units_per_second(asset_location: Location) -> Option<u128>;
103    }
104
105    impl<T: Config> XcAssetLocation<T::AssetId> for Pallet<T> {
106        fn get_xc_asset_location(asset_id: T::AssetId) -> Option<Location> {
107            AssetIdToLocation::<T>::get(asset_id).and_then(|x| x.try_into().ok())
108        }
109
110        fn get_asset_id(asset_location: Location) -> Option<T::AssetId> {
111            AssetLocationToId::<T>::get(asset_location.into_versioned())
112        }
113    }
114
115    impl<T: Config> ExecutionPaymentRate for Pallet<T> {
116        fn get_units_per_second(asset_location: Location) -> Option<u128> {
117            AssetLocationUnitsPerSecond::<T>::get(asset_location.into_versioned())
118        }
119    }
120
121    impl<T: Config> Pallet<T> {
122        /// Convert weight to fee based on units per second and weight.
123        pub fn weight_to_fee(weight: Weight, units_per_second: u128) -> u128 {
124            units_per_second.saturating_mul(weight.ref_time() as u128)
125                / (WEIGHT_REF_TIME_PER_SECOND as u128)
126        }
127    }
128
129    #[pallet::config]
130    pub trait Config: frame_system::Config {
131        /// The Asset Id. This will be used to create the asset and to associate it with
132        /// a AssetLocation
133        type AssetId: Member + Parameter + Default + Copy + HasCompact + MaxEncodedLen;
134
135        /// The required origin for managing cross-chain asset configuration
136        ///
137        /// Should most likely be root.
138        type ManagerOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;
139
140        type WeightInfo: WeightInfo;
141    }
142
143    #[pallet::error]
144    pub enum Error<T> {
145        /// Asset is already registered.
146        AssetAlreadyRegistered,
147        /// Asset does not exist (hasn't been registered).
148        AssetDoesNotExist,
149        /// Failed to convert to latest versioned Location
150        MultiLocationNotSupported,
151    }
152
153    #[allow(clippy::large_enum_variant)]
154    #[pallet::event]
155    #[pallet::generate_deposit(pub(crate) fn deposit_event)]
156    pub enum Event<T: Config> {
157        /// Registed mapping between asset type and asset Id.
158        AssetRegistered {
159            asset_location: VersionedLocation,
160            asset_id: T::AssetId,
161        },
162        /// Changed the amount of units we are charging per execution second for an asset
163        UnitsPerSecondChanged {
164            asset_location: VersionedLocation,
165            units_per_second: u128,
166        },
167        /// Changed the asset type mapping for a given asset id
168        AssetLocationChanged {
169            previous_asset_location: VersionedLocation,
170            asset_id: T::AssetId,
171            new_asset_location: VersionedLocation,
172        },
173        /// Supported asset type for fee payment removed.
174        SupportedAssetRemoved { asset_location: VersionedLocation },
175        /// Removed all information related to an asset Id
176        AssetRemoved {
177            asset_location: VersionedLocation,
178            asset_id: T::AssetId,
179        },
180    }
181
182    /// Mapping from an asset id to asset type.
183    /// Can be used when receiving transaction specifying an asset directly,
184    /// like transferring an asset from this chain to another.
185    #[pallet::storage]
186    pub type AssetIdToLocation<T: Config> =
187        StorageMap<_, Twox64Concat, T::AssetId, VersionedLocation>;
188
189    /// Mapping from an asset type to an asset id.
190    /// Can be used when receiving a multilocation XCM message to retrieve
191    /// the corresponding asset in which tokens should me minted.
192    #[pallet::storage]
193    pub type AssetLocationToId<T: Config> =
194        StorageMap<_, Twox64Concat, VersionedLocation, T::AssetId>;
195
196    /// Stores the units per second for local execution for a AssetLocation.
197    /// This is used to know how to charge for XCM execution in a particular asset.
198    ///
199    /// Not all asset types are supported for payment. If value exists here, it means it is supported.
200    #[pallet::storage]
201    pub type AssetLocationUnitsPerSecond<T: Config> =
202        StorageMap<_, Twox64Concat, VersionedLocation, u128>;
203
204    #[pallet::call]
205    impl<T: Config> Pallet<T> {
206        /// Register new asset location to asset Id mapping.
207        ///
208        /// This makes the asset eligible for XCM interaction.
209        #[pallet::call_index(0)]
210        #[pallet::weight(T::WeightInfo::register_asset_location())]
211        pub fn register_asset_location(
212            origin: OriginFor<T>,
213            asset_location: Box<VersionedLocation>,
214            #[pallet::compact] asset_id: T::AssetId,
215        ) -> DispatchResult {
216            T::ManagerOrigin::ensure_origin(origin)?;
217
218            // Ensure such an assetId does not exist
219            ensure!(
220                !AssetIdToLocation::<T>::contains_key(asset_id),
221                Error::<T>::AssetAlreadyRegistered
222            );
223
224            let v5_asset_loc = Location::try_from(*asset_location)
225                .map_err(|_| Error::<T>::MultiLocationNotSupported)?;
226            let asset_location = VersionedLocation::V5(v5_asset_loc);
227
228            AssetIdToLocation::<T>::insert(asset_id, asset_location.clone());
229            AssetLocationToId::<T>::insert(&asset_location, asset_id);
230
231            Self::deposit_event(Event::AssetRegistered {
232                asset_location,
233                asset_id,
234            });
235            Ok(())
236        }
237
238        /// Change the amount of units we are charging per execution second
239        /// for a given AssetLocation.
240        #[pallet::call_index(1)]
241        #[pallet::weight(T::WeightInfo::set_asset_units_per_second())]
242        pub fn set_asset_units_per_second(
243            origin: OriginFor<T>,
244            asset_location: Box<VersionedLocation>,
245            #[pallet::compact] units_per_second: u128,
246        ) -> DispatchResult {
247            T::ManagerOrigin::ensure_origin(origin)?;
248
249            let v5_asset_loc = Location::try_from(*asset_location)
250                .map_err(|_| Error::<T>::MultiLocationNotSupported)?;
251            let asset_location = VersionedLocation::V5(v5_asset_loc);
252
253            ensure!(
254                AssetLocationToId::<T>::contains_key(&asset_location),
255                Error::<T>::AssetDoesNotExist
256            );
257
258            AssetLocationUnitsPerSecond::<T>::insert(&asset_location, units_per_second);
259
260            Self::deposit_event(Event::UnitsPerSecondChanged {
261                asset_location,
262                units_per_second,
263            });
264            Ok(())
265        }
266
267        /// Change the xcm type mapping for a given asset Id.
268        /// The new asset type will inherit old `units per second` value.
269        #[pallet::call_index(2)]
270        #[pallet::weight(T::WeightInfo::change_existing_asset_location())]
271        pub fn change_existing_asset_location(
272            origin: OriginFor<T>,
273            new_asset_location: Box<VersionedLocation>,
274            #[pallet::compact] asset_id: T::AssetId,
275        ) -> DispatchResult {
276            T::ManagerOrigin::ensure_origin(origin)?;
277
278            let v5_asset_loc = Location::try_from(*new_asset_location)
279                .map_err(|_| Error::<T>::MultiLocationNotSupported)?;
280            let new_asset_location = VersionedLocation::V5(v5_asset_loc);
281
282            let previous_asset_location =
283                AssetIdToLocation::<T>::get(asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
284
285            // Insert new asset type info
286            AssetIdToLocation::<T>::insert(asset_id, new_asset_location.clone());
287            AssetLocationToId::<T>::insert(&new_asset_location, asset_id);
288
289            // Remove previous asset type info
290            AssetLocationToId::<T>::remove(&previous_asset_location);
291
292            // Change AssetLocationUnitsPerSecond
293            if let Some(units) = AssetLocationUnitsPerSecond::<T>::take(&previous_asset_location) {
294                AssetLocationUnitsPerSecond::<T>::insert(&new_asset_location, units);
295            }
296
297            Self::deposit_event(Event::AssetLocationChanged {
298                previous_asset_location,
299                asset_id,
300                new_asset_location,
301            });
302            Ok(())
303        }
304
305        /// Removes asset from the set of supported payment assets.
306        ///
307        /// The asset can still be interacted with via XCM but it cannot be used to pay for execution time.
308        #[pallet::call_index(3)]
309        #[pallet::weight(T::WeightInfo::remove_payment_asset())]
310        pub fn remove_payment_asset(
311            origin: OriginFor<T>,
312            asset_location: Box<VersionedLocation>,
313        ) -> DispatchResult {
314            T::ManagerOrigin::ensure_origin(origin)?;
315
316            let v5_asset_loc = Location::try_from(*asset_location)
317                .map_err(|_| Error::<T>::MultiLocationNotSupported)?;
318            let asset_location = VersionedLocation::V5(v5_asset_loc);
319
320            AssetLocationUnitsPerSecond::<T>::remove(&asset_location);
321
322            Self::deposit_event(Event::SupportedAssetRemoved { asset_location });
323            Ok(())
324        }
325
326        /// Removes all information related to asset, removing it from XCM support.
327        #[pallet::call_index(4)]
328        #[pallet::weight(T::WeightInfo::remove_asset())]
329        pub fn remove_asset(
330            origin: OriginFor<T>,
331            #[pallet::compact] asset_id: T::AssetId,
332        ) -> DispatchResult {
333            T::ManagerOrigin::ensure_origin(origin)?;
334
335            let asset_location =
336                AssetIdToLocation::<T>::get(asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
337
338            AssetIdToLocation::<T>::remove(asset_id);
339            AssetLocationToId::<T>::remove(&asset_location);
340            AssetLocationUnitsPerSecond::<T>::remove(&asset_location);
341
342            Self::deposit_event(Event::AssetRemoved {
343                asset_id,
344                asset_location,
345            });
346            Ok(())
347        }
348    }
349}