pallet_assets/types.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//! Various basic types for use in the assets pallet.
19
20use super::*;
21use frame_support::{
22 pallet_prelude::*,
23 traits::{fungible, tokens::ConversionToAssetBalance},
24};
25use sp_runtime::{traits::Convert, FixedPointNumber, FixedU128};
26
27pub type DepositBalanceOf<T, I = ()> =
28 <<T as Config<I>>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
29pub type AssetAccountOf<T, I> = AssetAccount<
30 <T as Config<I>>::Balance,
31 DepositBalanceOf<T, I>,
32 <T as Config<I>>::Extra,
33 <T as SystemConfig>::AccountId,
34>;
35pub type ExistenceReasonOf<T, I> =
36 ExistenceReason<DepositBalanceOf<T, I>, <T as SystemConfig>::AccountId>;
37
38/// AssetStatus holds the current state of the asset. It could either be Live and available for use,
39/// or in a Destroying state.
40#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
41pub enum AssetStatus {
42 /// The asset is active and able to be used.
43 Live,
44 /// Whether the asset is frozen for non-admin transfers.
45 Frozen,
46 /// The asset is currently being destroyed, and all actions are no longer permitted on the
47 /// asset. Once set to `Destroying`, the asset can never transition back to a `Live` state.
48 Destroying,
49}
50
51#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
52pub struct AssetDetails<Balance, AccountId, DepositBalance> {
53 /// Can change `owner`, `issuer`, `freezer` and `admin` accounts.
54 pub owner: AccountId,
55 /// Can mint tokens.
56 pub issuer: AccountId,
57 /// Can thaw tokens, force transfers and burn tokens from any account.
58 pub admin: AccountId,
59 /// Can freeze tokens.
60 pub freezer: AccountId,
61 /// The total supply across all accounts.
62 pub supply: Balance,
63 /// The balance deposited for this asset. This pays for the data stored here.
64 pub deposit: DepositBalance,
65 /// The ED for virtual accounts.
66 pub min_balance: Balance,
67 /// If `true`, then any account with this asset is given a provider reference. Otherwise, it
68 /// requires a consumer reference.
69 pub is_sufficient: bool,
70 /// The total number of accounts.
71 pub accounts: u32,
72 /// The total number of accounts for which we have placed a self-sufficient reference.
73 pub sufficients: u32,
74 /// The total number of approvals.
75 pub approvals: u32,
76 /// The status of the asset
77 pub status: AssetStatus,
78}
79
80/// Data concerning an approval.
81#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, MaxEncodedLen, TypeInfo)]
82pub struct Approval<Balance, DepositBalance> {
83 /// The amount of funds approved for the balance transfer from the owner to some delegated
84 /// target.
85 pub amount: Balance,
86 /// The amount reserved on the owner's account to hold this item in storage.
87 pub deposit: DepositBalance,
88}
89
90#[test]
91fn ensure_bool_decodes_to_consumer_or_sufficient() {
92 assert_eq!(false.encode(), ExistenceReason::<(), ()>::Consumer.encode());
93 assert_eq!(true.encode(), ExistenceReason::<(), ()>::Sufficient.encode());
94}
95
96/// The reason for an account's existence within an asset class.
97#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
98pub enum ExistenceReason<Balance, AccountId> {
99 /// A consumer reference was used to create this account.
100 #[codec(index = 0)]
101 Consumer,
102 /// The asset class is `sufficient` for account existence.
103 #[codec(index = 1)]
104 Sufficient,
105 /// The account holder has placed a deposit to exist within an asset class.
106 #[codec(index = 2)]
107 DepositHeld(Balance),
108 /// A deposit was placed for this account to exist, but it has been refunded.
109 #[codec(index = 3)]
110 DepositRefunded,
111 /// Some other `AccountId` has placed a deposit to make this account exist.
112 /// An account with such a reason might not be referenced in `system`.
113 #[codec(index = 4)]
114 DepositFrom(AccountId, Balance),
115}
116
117impl<Balance, AccountId> ExistenceReason<Balance, AccountId>
118where
119 AccountId: Clone,
120{
121 pub fn take_deposit(&mut self) -> Option<Balance> {
122 if !matches!(self, ExistenceReason::DepositHeld(_)) {
123 return None
124 }
125 if let ExistenceReason::DepositHeld(deposit) =
126 core::mem::replace(self, ExistenceReason::DepositRefunded)
127 {
128 Some(deposit)
129 } else {
130 None
131 }
132 }
133
134 pub fn take_deposit_from(&mut self) -> Option<(AccountId, Balance)> {
135 if !matches!(self, ExistenceReason::DepositFrom(..)) {
136 return None
137 }
138 if let ExistenceReason::DepositFrom(depositor, deposit) =
139 core::mem::replace(self, ExistenceReason::DepositRefunded)
140 {
141 Some((depositor, deposit))
142 } else {
143 None
144 }
145 }
146}
147
148#[test]
149fn ensure_bool_decodes_to_liquid_or_frozen() {
150 assert_eq!(false.encode(), AccountStatus::Liquid.encode());
151 assert_eq!(true.encode(), AccountStatus::Frozen.encode());
152}
153
154/// The status of an asset account.
155#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
156pub enum AccountStatus {
157 /// Asset account can receive and transfer the assets.
158 Liquid,
159 /// Asset account cannot transfer the assets.
160 Frozen,
161 /// Asset account cannot receive and transfer the assets.
162 Blocked,
163}
164impl AccountStatus {
165 /// Returns `true` if frozen or blocked.
166 pub fn is_frozen(&self) -> bool {
167 matches!(self, AccountStatus::Frozen | AccountStatus::Blocked)
168 }
169 /// Returns `true` if blocked.
170 pub fn is_blocked(&self) -> bool {
171 matches!(self, AccountStatus::Blocked)
172 }
173}
174
175#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
176pub struct AssetAccount<Balance, DepositBalance, Extra, AccountId> {
177 /// The account's balance.
178 ///
179 /// The part of the `balance` may be frozen by the [`Config::Freezer`]. The on-hold portion is
180 /// not included here and is tracked by the [`Config::Holder`].
181 pub balance: Balance,
182 /// The status of the account.
183 pub status: AccountStatus,
184 /// The reason for the existence of the account.
185 pub reason: ExistenceReason<DepositBalance, AccountId>,
186 /// Additional "sidecar" data, in case some other pallet wants to use this storage item.
187 pub extra: Extra,
188}
189
190#[derive(Clone, Encode, Decode, Eq, PartialEq, Default, RuntimeDebug, MaxEncodedLen, TypeInfo)]
191pub struct AssetMetadata<DepositBalance, BoundedString> {
192 /// The balance deposited for this metadata.
193 ///
194 /// This pays for the data stored in this struct.
195 pub deposit: DepositBalance,
196 /// The user friendly name of this asset. Limited in length by `StringLimit`.
197 pub name: BoundedString,
198 /// The ticker symbol for this asset. Limited in length by `StringLimit`.
199 pub symbol: BoundedString,
200 /// The number of decimals this asset uses to represent one unit.
201 pub decimals: u8,
202 /// Whether the asset metadata may be changed by a non Force origin.
203 pub is_frozen: bool,
204}
205
206/// Trait for allowing a minimum balance on the account to be specified, beyond the
207/// `minimum_balance` of the asset. This is additive - the `minimum_balance` of the asset must be
208/// met *and then* anything here in addition.
209pub trait FrozenBalance<AssetId, AccountId, Balance> {
210 /// Return the frozen balance.
211 ///
212 /// Generally, the balance of every account must be at least the sum of this (if `Some`) and
213 /// the asset's `minimum_balance` (the latter since there may be complications to destroying an
214 /// asset's account completely).
215 ///
216 /// Under normal behaviour, the account balance should not go below the sum of this (if `Some`)
217 /// and the asset's minimum balance. However, the account balance may reasonably begin below
218 /// this sum (e.g. if less than the sum had ever been transferred into the account).
219 ///
220 /// In special cases (privileged intervention) the account balance may also go below the sum.
221 ///
222 /// If `None` is returned, then nothing special is enforced.
223 fn frozen_balance(asset: AssetId, who: &AccountId) -> Option<Balance>;
224
225 /// Called after an account has been removed.
226 fn died(asset: AssetId, who: &AccountId);
227
228 /// Return a value that indicates if there are registered freezes for a given asset.
229 fn contains_freezes(asset: AssetId) -> bool;
230}
231
232impl<AssetId, AccountId, Balance> FrozenBalance<AssetId, AccountId, Balance> for () {
233 fn frozen_balance(_: AssetId, _: &AccountId) -> Option<Balance> {
234 None
235 }
236 fn died(_: AssetId, _: &AccountId) {}
237 fn contains_freezes(_: AssetId) -> bool {
238 false
239 }
240}
241
242/// This trait indicates a balance that is _on hold_ for an asset account.
243///
244/// A balance _on hold_ is a balance that, while is assigned to an account,
245/// is outside the direct control of it. Instead, is being _held_ by the
246/// system logic (i.e. Pallets) and can be eventually burned or released.
247pub trait BalanceOnHold<AssetId, AccountId, Balance> {
248 /// Return the held balance.
249 ///
250 /// If `Some`, it means some balance is _on hold_, and it can be
251 /// infallibly burned.
252 ///
253 /// If `None` is returned, then no balance is _on hold_ for `who`'s asset
254 /// account.
255 fn balance_on_hold(asset: AssetId, who: &AccountId) -> Option<Balance>;
256
257 /// Called after an account has been removed.
258 ///
259 /// It is expected that this method is called only when there is no balance
260 /// on hold. Otherwise, an account should not be removed.
261 fn died(asset: AssetId, who: &AccountId);
262
263 /// Return a value that indicates if there are registered holds for a given asset.
264 fn contains_holds(asset: AssetId) -> bool;
265}
266
267impl<AssetId, AccountId, Balance> BalanceOnHold<AssetId, AccountId, Balance> for () {
268 fn balance_on_hold(_: AssetId, _: &AccountId) -> Option<Balance> {
269 None
270 }
271 fn died(_: AssetId, _: &AccountId) {}
272 fn contains_holds(_: AssetId) -> bool {
273 false
274 }
275}
276
277#[derive(Copy, Clone, PartialEq, Eq)]
278pub struct TransferFlags {
279 /// The debited account must stay alive at the end of the operation; an error is returned if
280 /// this cannot be achieved legally.
281 pub keep_alive: bool,
282 /// Less than the amount specified needs be debited by the operation for it to be considered
283 /// successful. If `false`, then the amount debited will always be at least the amount
284 /// specified.
285 pub best_effort: bool,
286 /// Any additional funds debited (due to minimum balance requirements) should be burned rather
287 /// than credited to the destination account.
288 pub burn_dust: bool,
289}
290
291#[derive(Copy, Clone, PartialEq, Eq)]
292pub struct DebitFlags {
293 /// The debited account must stay alive at the end of the operation; an error is returned if
294 /// this cannot be achieved legally.
295 pub keep_alive: bool,
296 /// Less than the amount specified needs be debited by the operation for it to be considered
297 /// successful. If `false`, then the amount debited will always be at least the amount
298 /// specified.
299 pub best_effort: bool,
300}
301
302impl From<TransferFlags> for DebitFlags {
303 fn from(f: TransferFlags) -> Self {
304 Self { keep_alive: f.keep_alive, best_effort: f.best_effort }
305 }
306}
307
308/// Possible errors when converting between external and asset balances.
309#[derive(Eq, PartialEq, Copy, Clone, RuntimeDebug, Encode, Decode)]
310pub enum ConversionError {
311 /// The external minimum balance must not be zero.
312 MinBalanceZero,
313 /// The asset is not present in storage.
314 AssetMissing,
315 /// The asset is not sufficient and thus does not have a reliable `min_balance` so it cannot be
316 /// converted.
317 AssetNotSufficient,
318}
319
320// Type alias for `frame_system`'s account id.
321type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
322// This pallet's asset id and balance type.
323type AssetIdOf<T, I> = <T as Config<I>>::AssetId;
324type AssetBalanceOf<T, I> = <T as Config<I>>::Balance;
325// Generic fungible balance type.
326type BalanceOf<F, T> = <F as fungible::Inspect<AccountIdOf<T>>>::Balance;
327
328/// Converts a balance value into an asset balance based on the ratio between the fungible's
329/// minimum balance and the minimum asset balance.
330pub struct BalanceToAssetBalance<F, T, CON, I = ()>(PhantomData<(F, T, CON, I)>);
331impl<F, T, CON, I> ConversionToAssetBalance<BalanceOf<F, T>, AssetIdOf<T, I>, AssetBalanceOf<T, I>>
332 for BalanceToAssetBalance<F, T, CON, I>
333where
334 F: fungible::Inspect<AccountIdOf<T>>,
335 T: Config<I>,
336 I: 'static,
337 CON: Convert<BalanceOf<F, T>, AssetBalanceOf<T, I>>,
338{
339 type Error = ConversionError;
340
341 /// Convert the given balance value into an asset balance based on the ratio between the
342 /// fungible's minimum balance and the minimum asset balance.
343 ///
344 /// Will return `Err` if the asset is not found, not sufficient or the fungible's minimum
345 /// balance is zero.
346 fn to_asset_balance(
347 balance: BalanceOf<F, T>,
348 asset_id: AssetIdOf<T, I>,
349 ) -> Result<AssetBalanceOf<T, I>, ConversionError> {
350 let asset = Asset::<T, I>::get(asset_id).ok_or(ConversionError::AssetMissing)?;
351 // only sufficient assets have a min balance with reliable value
352 ensure!(asset.is_sufficient, ConversionError::AssetNotSufficient);
353 let min_balance = CON::convert(F::minimum_balance());
354 // make sure we don't divide by zero
355 ensure!(!min_balance.is_zero(), ConversionError::MinBalanceZero);
356 let balance = CON::convert(balance);
357 // balance * asset.min_balance / min_balance
358 Ok(FixedU128::saturating_from_rational(asset.min_balance, min_balance)
359 .saturating_mul_int(balance))
360 }
361}