referrerpolicy=no-referrer-when-downgrade
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Various basic types for use in the assets pallet.

use super::*;
use frame_support::{
	pallet_prelude::*,
	traits::{fungible, tokens::ConversionToAssetBalance},
};
use sp_runtime::{traits::Convert, FixedPointNumber, FixedU128};

pub(super) type DepositBalanceOf<T, I = ()> =
	<<T as Config<I>>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
pub(super) type AssetAccountOf<T, I> = AssetAccount<
	<T as Config<I>>::Balance,
	DepositBalanceOf<T, I>,
	<T as Config<I>>::Extra,
	<T as SystemConfig>::AccountId,
>;
pub(super) type ExistenceReasonOf<T, I> =
	ExistenceReason<DepositBalanceOf<T, I>, <T as SystemConfig>::AccountId>;

/// AssetStatus holds the current state of the asset. It could either be Live and available for use,
/// or in a Destroying state.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub(super) enum AssetStatus {
	/// The asset is active and able to be used.
	Live,
	/// Whether the asset is frozen for non-admin transfers.
	Frozen,
	/// The asset is currently being destroyed, and all actions are no longer permitted on the
	/// asset. Once set to `Destroying`, the asset can never transition back to a `Live` state.
	Destroying,
}

#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub struct AssetDetails<Balance, AccountId, DepositBalance> {
	/// Can change `owner`, `issuer`, `freezer` and `admin` accounts.
	pub(super) owner: AccountId,
	/// Can mint tokens.
	pub(super) issuer: AccountId,
	/// Can thaw tokens, force transfers and burn tokens from any account.
	pub(super) admin: AccountId,
	/// Can freeze tokens.
	pub(super) freezer: AccountId,
	/// The total supply across all accounts.
	pub(super) supply: Balance,
	/// The balance deposited for this asset. This pays for the data stored here.
	pub(super) deposit: DepositBalance,
	/// The ED for virtual accounts.
	pub(super) min_balance: Balance,
	/// If `true`, then any account with this asset is given a provider reference. Otherwise, it
	/// requires a consumer reference.
	pub(super) is_sufficient: bool,
	/// The total number of accounts.
	pub(super) accounts: u32,
	/// The total number of accounts for which we have placed a self-sufficient reference.
	pub(super) sufficients: u32,
	/// The total number of approvals.
	pub(super) approvals: u32,
	/// The status of the asset
	pub(super) status: AssetStatus,
}

/// Data concerning an approval.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, MaxEncodedLen, TypeInfo)]
pub struct Approval<Balance, DepositBalance> {
	/// The amount of funds approved for the balance transfer from the owner to some delegated
	/// target.
	pub(super) amount: Balance,
	/// The amount reserved on the owner's account to hold this item in storage.
	pub(super) deposit: DepositBalance,
}

#[test]
fn ensure_bool_decodes_to_consumer_or_sufficient() {
	assert_eq!(false.encode(), ExistenceReason::<(), ()>::Consumer.encode());
	assert_eq!(true.encode(), ExistenceReason::<(), ()>::Sufficient.encode());
}

/// The reason for an account's existence within an asset class.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub enum ExistenceReason<Balance, AccountId> {
	/// A consumer reference was used to create this account.
	#[codec(index = 0)]
	Consumer,
	/// The asset class is `sufficient` for account existence.
	#[codec(index = 1)]
	Sufficient,
	/// The account holder has placed a deposit to exist within an asset class.
	#[codec(index = 2)]
	DepositHeld(Balance),
	/// A deposit was placed for this account to exist, but it has been refunded.
	#[codec(index = 3)]
	DepositRefunded,
	/// Some other `AccountId` has placed a deposit to make this account exist.
	/// An account with such a reason might not be referenced in `system`.
	#[codec(index = 4)]
	DepositFrom(AccountId, Balance),
}

impl<Balance, AccountId> ExistenceReason<Balance, AccountId>
where
	AccountId: Clone,
{
	pub(crate) fn take_deposit(&mut self) -> Option<Balance> {
		if !matches!(self, ExistenceReason::DepositHeld(_)) {
			return None
		}
		if let ExistenceReason::DepositHeld(deposit) =
			core::mem::replace(self, ExistenceReason::DepositRefunded)
		{
			Some(deposit)
		} else {
			None
		}
	}

	pub(crate) fn take_deposit_from(&mut self) -> Option<(AccountId, Balance)> {
		if !matches!(self, ExistenceReason::DepositFrom(..)) {
			return None
		}
		if let ExistenceReason::DepositFrom(depositor, deposit) =
			core::mem::replace(self, ExistenceReason::DepositRefunded)
		{
			Some((depositor, deposit))
		} else {
			None
		}
	}
}

#[test]
fn ensure_bool_decodes_to_liquid_or_frozen() {
	assert_eq!(false.encode(), AccountStatus::Liquid.encode());
	assert_eq!(true.encode(), AccountStatus::Frozen.encode());
}

/// The status of an asset account.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub enum AccountStatus {
	/// Asset account can receive and transfer the assets.
	Liquid,
	/// Asset account cannot transfer the assets.
	Frozen,
	/// Asset account cannot receive and transfer the assets.
	Blocked,
}
impl AccountStatus {
	/// Returns `true` if frozen or blocked.
	pub(crate) fn is_frozen(&self) -> bool {
		matches!(self, AccountStatus::Frozen | AccountStatus::Blocked)
	}
	/// Returns `true` if blocked.
	pub(crate) fn is_blocked(&self) -> bool {
		matches!(self, AccountStatus::Blocked)
	}
}

#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub struct AssetAccount<Balance, DepositBalance, Extra, AccountId> {
	/// The balance.
	pub(super) balance: Balance,
	/// The status of the account.
	pub(super) status: AccountStatus,
	/// The reason for the existence of the account.
	pub(super) reason: ExistenceReason<DepositBalance, AccountId>,
	/// Additional "sidecar" data, in case some other pallet wants to use this storage item.
	pub(super) extra: Extra,
}

#[derive(Clone, Encode, Decode, Eq, PartialEq, Default, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub struct AssetMetadata<DepositBalance, BoundedString> {
	/// The balance deposited for this metadata.
	///
	/// This pays for the data stored in this struct.
	pub(super) deposit: DepositBalance,
	/// The user friendly name of this asset. Limited in length by `StringLimit`.
	pub(super) name: BoundedString,
	/// The ticker symbol for this asset. Limited in length by `StringLimit`.
	pub(super) symbol: BoundedString,
	/// The number of decimals this asset uses to represent one unit.
	pub(super) decimals: u8,
	/// Whether the asset metadata may be changed by a non Force origin.
	pub(super) is_frozen: bool,
}

/// Trait for allowing a minimum balance on the account to be specified, beyond the
/// `minimum_balance` of the asset. This is additive - the `minimum_balance` of the asset must be
/// met *and then* anything here in addition.
pub trait FrozenBalance<AssetId, AccountId, Balance> {
	/// Return the frozen balance.
	///
	/// Generally, the balance of every account must be at least the sum of this (if `Some`) and
	/// the asset's `minimum_balance` (the latter since there may be complications to destroying an
	/// asset's account completely).
	///
	/// Under normal behaviour, the account balance should not go below the sum of this (if `Some`)
	/// and the asset's minimum balance. However, the account balance may reasonably begin below
	/// this sum (e.g. if less than the sum had ever been transferred into the account).
	///
	/// In special cases (privileged intervention) the account balance may also go below the sum.
	///
	/// If `None` is returned, then nothing special is enforced.
	fn frozen_balance(asset: AssetId, who: &AccountId) -> Option<Balance>;

	/// Called after an account has been removed.
	///
	/// NOTE: It is possible that the asset does no longer exist when this hook is called.
	fn died(asset: AssetId, who: &AccountId);
}

impl<AssetId, AccountId, Balance> FrozenBalance<AssetId, AccountId, Balance> for () {
	fn frozen_balance(_: AssetId, _: &AccountId) -> Option<Balance> {
		None
	}
	fn died(_: AssetId, _: &AccountId) {}
}

#[derive(Copy, Clone, PartialEq, Eq)]
pub(super) struct TransferFlags {
	/// The debited account must stay alive at the end of the operation; an error is returned if
	/// this cannot be achieved legally.
	pub(super) keep_alive: bool,
	/// Less than the amount specified needs be debited by the operation for it to be considered
	/// successful. If `false`, then the amount debited will always be at least the amount
	/// specified.
	pub(super) best_effort: bool,
	/// Any additional funds debited (due to minimum balance requirements) should be burned rather
	/// than credited to the destination account.
	pub(super) burn_dust: bool,
}

#[derive(Copy, Clone, PartialEq, Eq)]
pub(super) struct DebitFlags {
	/// The debited account must stay alive at the end of the operation; an error is returned if
	/// this cannot be achieved legally.
	pub(super) keep_alive: bool,
	/// Less than the amount specified needs be debited by the operation for it to be considered
	/// successful. If `false`, then the amount debited will always be at least the amount
	/// specified.
	pub(super) best_effort: bool,
}

impl From<TransferFlags> for DebitFlags {
	fn from(f: TransferFlags) -> Self {
		Self { keep_alive: f.keep_alive, best_effort: f.best_effort }
	}
}

/// Possible errors when converting between external and asset balances.
#[derive(Eq, PartialEq, Copy, Clone, RuntimeDebug, Encode, Decode)]
pub enum ConversionError {
	/// The external minimum balance must not be zero.
	MinBalanceZero,
	/// The asset is not present in storage.
	AssetMissing,
	/// The asset is not sufficient and thus does not have a reliable `min_balance` so it cannot be
	/// converted.
	AssetNotSufficient,
}

// Type alias for `frame_system`'s account id.
type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
// This pallet's asset id and balance type.
type AssetIdOf<T, I> = <T as Config<I>>::AssetId;
type AssetBalanceOf<T, I> = <T as Config<I>>::Balance;
// Generic fungible balance type.
type BalanceOf<F, T> = <F as fungible::Inspect<AccountIdOf<T>>>::Balance;

/// Converts a balance value into an asset balance based on the ratio between the fungible's
/// minimum balance and the minimum asset balance.
pub struct BalanceToAssetBalance<F, T, CON, I = ()>(PhantomData<(F, T, CON, I)>);
impl<F, T, CON, I> ConversionToAssetBalance<BalanceOf<F, T>, AssetIdOf<T, I>, AssetBalanceOf<T, I>>
	for BalanceToAssetBalance<F, T, CON, I>
where
	F: fungible::Inspect<AccountIdOf<T>>,
	T: Config<I>,
	I: 'static,
	CON: Convert<BalanceOf<F, T>, AssetBalanceOf<T, I>>,
{
	type Error = ConversionError;

	/// Convert the given balance value into an asset balance based on the ratio between the
	/// fungible's minimum balance and the minimum asset balance.
	///
	/// Will return `Err` if the asset is not found, not sufficient or the fungible's minimum
	/// balance is zero.
	fn to_asset_balance(
		balance: BalanceOf<F, T>,
		asset_id: AssetIdOf<T, I>,
	) -> Result<AssetBalanceOf<T, I>, ConversionError> {
		let asset = Asset::<T, I>::get(asset_id).ok_or(ConversionError::AssetMissing)?;
		// only sufficient assets have a min balance with reliable value
		ensure!(asset.is_sufficient, ConversionError::AssetNotSufficient);
		let min_balance = CON::convert(F::minimum_balance());
		// make sure we don't divide by zero
		ensure!(!min_balance.is_zero(), ConversionError::MinBalanceZero);
		let balance = CON::convert(balance);
		// balance * asset.min_balance / min_balance
		Ok(FixedU128::saturating_from_rational(asset.min_balance, min_balance)
			.saturating_mul_int(balance))
	}
}