referrerpolicy=no-referrer-when-downgrade

pallet_asset_rate/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! # Asset Rate Pallet
19//!
20//! - [`Config`]
21//! - [`Call`]
22//!
23//! ## Overview
24//!
25//! The AssetRate pallet provides means of setting conversion rates for some asset to native
26//! balance.
27//!
28//! The supported dispatchable functions are documented in the [`Call`] enum.
29//!
30//! ### Terminology
31//!
32//! * **Asset balance**: The balance type of an arbitrary asset. The network might only know about
33//!   the identifier of the asset and nothing more.
34//! * **Native balance**: The balance type of the network's native currency.
35//!
36//! ### Goals
37//!
38//! The asset-rate system in Substrate is designed to make the following possible:
39//!
40//! * Providing a soft conversion for the balance of supported assets to a default asset class.
41//! * Updating existing conversion rates.
42//!
43//! ## Interface
44//!
45//! ### Permissioned Functions
46//!
47//! * `create`: Creates a new asset conversion rate.
48//! * `remove`: Removes an existing asset conversion rate.
49//! * `update`: Overwrites an existing assert conversion rate.
50//!
51//! Please refer to the [`Call`] enum and its associated variants for documentation on each
52//! function.
53//!
54//! ### Assumptions
55//!
56//! * Conversion rates are only used as estimates, and are not designed to be precise or closely
57//!   tracking real world values.
58//! * All conversion rates reflect the ration of some asset to native, e.g. native = asset * rate.
59
60#![cfg_attr(not(feature = "std"), no_std)]
61
62extern crate alloc;
63
64use alloc::boxed::Box;
65use frame_support::traits::{
66	fungible::Inspect,
67	tokens::{ConversionFromAssetBalance, ConversionToAssetBalance},
68};
69use sp_runtime::{
70	traits::{CheckedDiv, Zero},
71	FixedPointNumber, FixedU128,
72};
73
74pub use pallet::*;
75pub use weights::WeightInfo;
76
77#[cfg(feature = "runtime-benchmarks")]
78mod benchmarking;
79#[cfg(test)]
80mod mock;
81#[cfg(test)]
82mod tests;
83pub mod weights;
84#[cfg(feature = "runtime-benchmarks")]
85pub use benchmarking::AssetKindFactory;
86
87// Type alias for `frame_system`'s account id.
88type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
89// This pallet's asset kind and balance type.
90type AssetKindOf<T> = <T as Config>::AssetKind;
91// Generic fungible balance type.
92type BalanceOf<T> = <<T as Config>::Currency as Inspect<AccountIdOf<T>>>::Balance;
93
94#[frame_support::pallet]
95pub mod pallet {
96	use super::*;
97	use frame_support::pallet_prelude::*;
98	use frame_system::pallet_prelude::*;
99
100	#[pallet::pallet]
101	pub struct Pallet<T>(_);
102
103	#[pallet::config]
104	pub trait Config: frame_system::Config {
105		/// The Weight information for extrinsics in this pallet.
106		type WeightInfo: WeightInfo;
107
108		/// The runtime event type.
109		#[allow(deprecated)]
110		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
111
112		/// The origin permissioned to create a conversion rate for an asset.
113		type CreateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
114
115		/// The origin permissioned to remove an existing conversion rate for an asset.
116		type RemoveOrigin: EnsureOrigin<Self::RuntimeOrigin>;
117
118		/// The origin permissioned to update an existing conversion rate for an asset.
119		type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
120
121		/// The currency mechanism for this pallet.
122		type Currency: Inspect<Self::AccountId>;
123
124		/// The type for asset kinds for which the conversion rate to native balance is set.
125		type AssetKind: Parameter + MaxEncodedLen;
126
127		/// Helper type for benchmarks.
128		#[cfg(feature = "runtime-benchmarks")]
129		type BenchmarkHelper: crate::AssetKindFactory<Self::AssetKind>;
130	}
131
132	/// Maps an asset to its fixed point representation in the native balance.
133	///
134	/// E.g. `native_amount = asset_amount * ConversionRateToNative::<T>::get(asset_kind)`
135	#[pallet::storage]
136	pub type ConversionRateToNative<T: Config> =
137		StorageMap<_, Blake2_128Concat, T::AssetKind, FixedU128, OptionQuery>;
138
139	#[pallet::event]
140	#[pallet::generate_deposit(pub(super) fn deposit_event)]
141	pub enum Event<T: Config> {
142		// Some `asset_kind` conversion rate was created.
143		AssetRateCreated { asset_kind: T::AssetKind, rate: FixedU128 },
144		// Some `asset_kind` conversion rate was removed.
145		AssetRateRemoved { asset_kind: T::AssetKind },
146		// Some existing `asset_kind` conversion rate was updated from `old` to `new`.
147		AssetRateUpdated { asset_kind: T::AssetKind, old: FixedU128, new: FixedU128 },
148	}
149
150	#[pallet::error]
151	pub enum Error<T> {
152		/// The given asset ID is unknown.
153		UnknownAssetKind,
154		/// The given asset ID already has an assigned conversion rate and cannot be re-created.
155		AlreadyExists,
156		/// Overflow ocurred when calculating the inverse rate.
157		Overflow,
158	}
159
160	#[pallet::call]
161	impl<T: Config> Pallet<T> {
162		/// Initialize a conversion rate to native balance for the given asset.
163		///
164		/// ## Complexity
165		/// - O(1)
166		#[pallet::call_index(0)]
167		#[pallet::weight(T::WeightInfo::create())]
168		pub fn create(
169			origin: OriginFor<T>,
170			asset_kind: Box<T::AssetKind>,
171			rate: FixedU128,
172		) -> DispatchResult {
173			T::CreateOrigin::ensure_origin(origin)?;
174
175			ensure!(
176				!ConversionRateToNative::<T>::contains_key(asset_kind.as_ref()),
177				Error::<T>::AlreadyExists
178			);
179			ConversionRateToNative::<T>::set(asset_kind.as_ref(), Some(rate));
180
181			Self::deposit_event(Event::AssetRateCreated { asset_kind: *asset_kind, rate });
182			Ok(())
183		}
184
185		/// Update the conversion rate to native balance for the given asset.
186		///
187		/// ## Complexity
188		/// - O(1)
189		#[pallet::call_index(1)]
190		#[pallet::weight(T::WeightInfo::update())]
191		pub fn update(
192			origin: OriginFor<T>,
193			asset_kind: Box<T::AssetKind>,
194			rate: FixedU128,
195		) -> DispatchResult {
196			T::UpdateOrigin::ensure_origin(origin)?;
197
198			let mut old = FixedU128::zero();
199			ConversionRateToNative::<T>::mutate(asset_kind.as_ref(), |maybe_rate| {
200				if let Some(r) = maybe_rate {
201					old = *r;
202					*r = rate;
203
204					Ok(())
205				} else {
206					Err(Error::<T>::UnknownAssetKind)
207				}
208			})?;
209
210			Self::deposit_event(Event::AssetRateUpdated {
211				asset_kind: *asset_kind,
212				old,
213				new: rate,
214			});
215			Ok(())
216		}
217
218		/// Remove an existing conversion rate to native balance for the given asset.
219		///
220		/// ## Complexity
221		/// - O(1)
222		#[pallet::call_index(2)]
223		#[pallet::weight(T::WeightInfo::remove())]
224		pub fn remove(origin: OriginFor<T>, asset_kind: Box<T::AssetKind>) -> DispatchResult {
225			T::RemoveOrigin::ensure_origin(origin)?;
226
227			ensure!(
228				ConversionRateToNative::<T>::contains_key(asset_kind.as_ref()),
229				Error::<T>::UnknownAssetKind
230			);
231			ConversionRateToNative::<T>::remove(asset_kind.as_ref());
232
233			Self::deposit_event(Event::AssetRateRemoved { asset_kind: *asset_kind });
234			Ok(())
235		}
236	}
237}
238
239/// Exposes conversion of an arbitrary balance of an asset to native balance.
240impl<T> ConversionFromAssetBalance<BalanceOf<T>, AssetKindOf<T>, BalanceOf<T>> for Pallet<T>
241where
242	T: Config,
243{
244	type Error = pallet::Error<T>;
245
246	fn from_asset_balance(
247		balance: BalanceOf<T>,
248		asset_kind: AssetKindOf<T>,
249	) -> Result<BalanceOf<T>, pallet::Error<T>> {
250		let rate = pallet::ConversionRateToNative::<T>::get(asset_kind)
251			.ok_or(pallet::Error::<T>::UnknownAssetKind.into())?;
252		Ok(rate.saturating_mul_int(balance))
253	}
254	/// Set a conversion rate to `1` for the `asset_id`.
255	#[cfg(feature = "runtime-benchmarks")]
256	fn ensure_successful(asset_id: AssetKindOf<T>) {
257		pallet::ConversionRateToNative::<T>::set(asset_id.clone(), Some(1.into()));
258	}
259}
260
261/// Exposes conversion of a native balance to an asset balance.
262impl<T> ConversionToAssetBalance<BalanceOf<T>, AssetKindOf<T>, BalanceOf<T>> for Pallet<T>
263where
264	T: Config,
265{
266	type Error = pallet::Error<T>;
267
268	fn to_asset_balance(
269		balance: BalanceOf<T>,
270		asset_kind: AssetKindOf<T>,
271	) -> Result<BalanceOf<T>, pallet::Error<T>> {
272		let rate = pallet::ConversionRateToNative::<T>::get(asset_kind)
273			.ok_or(pallet::Error::<T>::UnknownAssetKind.into())?;
274
275		// We cannot use `saturating_div` here so we use `checked_div`.
276		Ok(FixedU128::from_u32(1)
277			.checked_div(&rate)
278			.ok_or(pallet::Error::<T>::Overflow.into())?
279			.saturating_mul_int(balance))
280	}
281}