referrerpolicy=no-referrer-when-downgrade

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}