1#![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
87type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
89type AssetKindOf<T> = <T as Config>::AssetKind;
91type 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 type WeightInfo: WeightInfo;
107
108 #[allow(deprecated)]
110 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
111
112 type CreateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
114
115 type RemoveOrigin: EnsureOrigin<Self::RuntimeOrigin>;
117
118 type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
120
121 type Currency: Inspect<Self::AccountId>;
123
124 type AssetKind: Parameter + MaxEncodedLen;
126
127 #[cfg(feature = "runtime-benchmarks")]
129 type BenchmarkHelper: crate::AssetKindFactory<Self::AssetKind>;
130 }
131
132 #[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 AssetRateCreated { asset_kind: T::AssetKind, rate: FixedU128 },
144 AssetRateRemoved { asset_kind: T::AssetKind },
146 AssetRateUpdated { asset_kind: T::AssetKind, old: FixedU128, new: FixedU128 },
148 }
149
150 #[pallet::error]
151 pub enum Error<T> {
152 UnknownAssetKind,
154 AlreadyExists,
156 Overflow,
158 }
159
160 #[pallet::call]
161 impl<T: Config> Pallet<T> {
162 #[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 #[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 #[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
239impl<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 #[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
261impl<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 Ok(FixedU128::from_u32(1)
277 .checked_div(&rate)
278 .ok_or(pallet::Error::<T>::Overflow.into())?
279 .saturating_mul_int(balance))
280 }
281}