referrerpolicy=no-referrer-when-downgrade

pallet_assets/
functions.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//! Functions for the Assets pallet.
19
20use super::*;
21use alloc::vec;
22use frame_support::{defensive, traits::Get, BoundedVec};
23
24#[must_use]
25pub(super) enum DeadConsequence {
26	Remove,
27	Keep,
28}
29
30use DeadConsequence::*;
31
32// The main implementation block for the module.
33impl<T: Config<I>, I: 'static> Pallet<T, I> {
34	// Public immutables
35
36	/// Return the extra "sid-car" data for `id`/`who`, or `None` if the account doesn't exist.
37	pub fn adjust_extra(
38		id: T::AssetId,
39		who: impl core::borrow::Borrow<T::AccountId>,
40	) -> Option<ExtraMutator<T, I>> {
41		ExtraMutator::maybe_new(id, who)
42	}
43
44	/// Get the asset `id` balance of `who`, or zero if the asset-account doesn't exist.
45	pub fn balance(id: T::AssetId, who: impl core::borrow::Borrow<T::AccountId>) -> T::Balance {
46		Self::maybe_balance(id, who).unwrap_or_default()
47	}
48
49	/// Get the asset `id` balance of `who` if the asset-account exists.
50	pub fn maybe_balance(
51		id: T::AssetId,
52		who: impl core::borrow::Borrow<T::AccountId>,
53	) -> Option<T::Balance> {
54		Account::<T, I>::get(id, who.borrow()).map(|a| a.balance)
55	}
56
57	/// Get the total supply of an asset `id`.
58	pub fn total_supply(id: T::AssetId) -> T::Balance {
59		Self::maybe_total_supply(id).unwrap_or_default()
60	}
61
62	/// Get the total supply of an asset `id` if the asset exists.
63	pub fn maybe_total_supply(id: T::AssetId) -> Option<T::Balance> {
64		Asset::<T, I>::get(id).map(|x| x.supply)
65	}
66
67	pub(super) fn new_account(
68		who: &T::AccountId,
69		d: &mut AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T, I>>,
70		maybe_deposit: Option<(&T::AccountId, DepositBalanceOf<T, I>)>,
71	) -> Result<ExistenceReasonOf<T, I>, DispatchError> {
72		let accounts = d.accounts.checked_add(1).ok_or(ArithmeticError::Overflow)?;
73		let reason = if let Some((depositor, deposit)) = maybe_deposit {
74			if depositor == who {
75				ExistenceReason::DepositHeld(deposit)
76			} else {
77				ExistenceReason::DepositFrom(depositor.clone(), deposit)
78			}
79		} else if d.is_sufficient {
80			frame_system::Pallet::<T>::inc_sufficients(who);
81			d.sufficients.saturating_inc();
82			ExistenceReason::Sufficient
83		} else {
84			frame_system::Pallet::<T>::inc_consumers(who)
85				.map_err(|_| Error::<T, I>::UnavailableConsumer)?;
86			// We ensure that we can still increment consumers once more because we could otherwise
87			// allow accidental usage of all consumer references which could cause grief.
88			if !frame_system::Pallet::<T>::can_inc_consumer(who) {
89				frame_system::Pallet::<T>::dec_consumers(who);
90				return Err(Error::<T, I>::UnavailableConsumer.into())
91			}
92			ExistenceReason::Consumer
93		};
94		d.accounts = accounts;
95		Ok(reason)
96	}
97
98	pub(super) fn ensure_account_can_die(id: T::AssetId, who: &T::AccountId) -> DispatchResult {
99		ensure!(
100			T::Holder::balance_on_hold(id.clone(), who).is_none(),
101			Error::<T, I>::ContainsHolds
102		);
103		ensure!(T::Freezer::frozen_balance(id, who).is_none(), Error::<T, I>::ContainsFreezes);
104		Ok(())
105	}
106
107	pub(super) fn dead_account(
108		who: &T::AccountId,
109		d: &mut AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T, I>>,
110		reason: &ExistenceReasonOf<T, I>,
111		force: bool,
112	) -> DeadConsequence {
113		use ExistenceReason::*;
114
115		match *reason {
116			Consumer => frame_system::Pallet::<T>::dec_consumers(who),
117			Sufficient => {
118				d.sufficients = d.sufficients.saturating_sub(1);
119				frame_system::Pallet::<T>::dec_sufficients(who);
120			},
121			DepositRefunded => {},
122			DepositHeld(_) | DepositFrom(..) if !force => return Keep,
123			DepositHeld(_) | DepositFrom(..) => {},
124		}
125		d.accounts = d.accounts.saturating_sub(1);
126		Remove
127	}
128
129	/// Returns `true` when the balance of `account` can be increased by `amount`.
130	///
131	/// - `id`: The id of the asset that should be increased.
132	/// - `who`: The account of which the balance should be increased.
133	/// - `amount`: The amount by which the balance should be increased.
134	/// - `increase_supply`: Will the supply of the asset be increased by `amount` at the same time
135	///   as crediting the `account`.
136	pub(super) fn can_increase(
137		id: T::AssetId,
138		who: &T::AccountId,
139		amount: T::Balance,
140		increase_supply: bool,
141	) -> DepositConsequence {
142		let details = match Asset::<T, I>::get(&id) {
143			Some(details) => details,
144			None => return DepositConsequence::UnknownAsset,
145		};
146		if details.status == AssetStatus::Destroying {
147			return DepositConsequence::UnknownAsset
148		}
149		if increase_supply && details.supply.checked_add(&amount).is_none() {
150			return DepositConsequence::Overflow
151		}
152		if let Some(account) = Account::<T, I>::get(id, who) {
153			if account.status.is_blocked() {
154				return DepositConsequence::Blocked
155			}
156			if account.balance.checked_add(&amount).is_none() {
157				return DepositConsequence::Overflow
158			}
159		} else {
160			if amount < details.min_balance {
161				return DepositConsequence::BelowMinimum
162			}
163			if !details.is_sufficient && !frame_system::Pallet::<T>::can_accrue_consumers(who, 2) {
164				return DepositConsequence::CannotCreate
165			}
166			if details.is_sufficient && details.sufficients.checked_add(1).is_none() {
167				return DepositConsequence::Overflow
168			}
169		}
170
171		DepositConsequence::Success
172	}
173
174	/// Return the consequence of a withdraw.
175	pub(super) fn can_decrease(
176		id: T::AssetId,
177		who: &T::AccountId,
178		amount: T::Balance,
179		keep_alive: bool,
180	) -> WithdrawConsequence<T::Balance> {
181		use WithdrawConsequence::*;
182		let details = match Asset::<T, I>::get(&id) {
183			Some(details) => details,
184			None => return UnknownAsset,
185		};
186		if details.supply.checked_sub(&amount).is_none() {
187			return Underflow
188		}
189		if details.status == AssetStatus::Frozen {
190			return Frozen
191		}
192		if details.status == AssetStatus::Destroying {
193			return UnknownAsset
194		}
195		if amount.is_zero() {
196			return Success
197		}
198		let account = match Account::<T, I>::get(&id, who) {
199			Some(a) => a,
200			None => return BalanceLow,
201		};
202		if account.status.is_frozen() {
203			return Frozen
204		}
205		if let Some(rest) = account.balance.checked_sub(&amount) {
206			match (
207				T::Holder::balance_on_hold(id.clone(), who),
208				T::Freezer::frozen_balance(id.clone(), who),
209			) {
210				(None, None) =>
211					if rest < details.min_balance {
212						if keep_alive {
213							WouldDie
214						} else {
215							ReducedToZero(rest)
216						}
217					} else {
218						Success
219					},
220				(maybe_held, maybe_frozen) => {
221					let frozen = maybe_frozen.unwrap_or_default();
222					let held = maybe_held.unwrap_or_default();
223
224					// The `untouchable` balance of the asset account of `who`. This is described
225					// here: https://paritytech.github.io/polkadot-sdk/master/frame_support/traits/tokens/fungible/index.html#visualising-balance-components-together-
226					let untouchable = frozen.saturating_sub(held).max(details.min_balance);
227					if rest < untouchable {
228						if !frozen.is_zero() {
229							Frozen
230						} else {
231							WouldDie
232						}
233					} else {
234						Success
235					}
236				},
237			}
238		} else {
239			BalanceLow
240		}
241	}
242
243	// Maximum `amount` that can be passed into `can_withdraw` to result in a `WithdrawConsequence`
244	// of `Success`.
245	pub(super) fn reducible_balance(
246		id: T::AssetId,
247		who: &T::AccountId,
248		keep_alive: bool,
249	) -> Result<T::Balance, DispatchError> {
250		let details = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
251		ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
252
253		let account = Account::<T, I>::get(&id, who).ok_or(Error::<T, I>::NoAccount)?;
254		ensure!(!account.status.is_frozen(), Error::<T, I>::Frozen);
255
256		let untouchable = match (
257			T::Holder::balance_on_hold(id.clone(), who),
258			T::Freezer::frozen_balance(id.clone(), who),
259			keep_alive,
260		) {
261			(None, None, true) => details.min_balance,
262			(None, None, false) => Zero::zero(),
263			(maybe_held, maybe_frozen, _) => {
264				let held = maybe_held.unwrap_or_default();
265				let frozen = maybe_frozen.unwrap_or_default();
266				frozen.saturating_sub(held).max(details.min_balance)
267			},
268		};
269		let amount = account.balance.saturating_sub(untouchable);
270
271		Ok(amount.min(details.supply))
272	}
273
274	/// Make preparatory checks for debiting some funds from an account. Flags indicate requirements
275	/// of the debit.
276	///
277	/// - `amount`: The amount desired to be debited. The actual amount returned for debit may be
278	///   less (in the case of `best_effort` being `true`) or greater by up to the minimum balance
279	///   less one.
280	/// - `keep_alive`: Require that `target` must stay alive.
281	/// - `respect_freezer`: Respect any freezes on the account or token (or not).
282	/// - `best_effort`: The debit amount may be less than `amount`.
283	///
284	/// On success, the amount which should be debited (this will always be at least `amount` unless
285	/// `best_effort` is `true`) together with an optional value indicating the argument which must
286	/// be passed into the `melted` function of the `T::Freezer` if `Some`.
287	///
288	/// If no valid debit can be made then return an `Err`.
289	pub(super) fn prep_debit(
290		id: T::AssetId,
291		target: &T::AccountId,
292		amount: T::Balance,
293		f: DebitFlags,
294	) -> Result<T::Balance, DispatchError> {
295		let actual = Self::reducible_balance(id.clone(), target, f.keep_alive)?.min(amount);
296		ensure!(f.best_effort || actual >= amount, Error::<T, I>::BalanceLow);
297
298		let conseq = Self::can_decrease(id, target, actual, f.keep_alive);
299		let actual = match conseq.into_result(f.keep_alive) {
300			Ok(dust) => actual.saturating_add(dust), //< guaranteed by reducible_balance
301			Err(e) => {
302				debug_assert!(false, "passed from reducible_balance; qed");
303				return Err(e)
304			},
305		};
306
307		Ok(actual)
308	}
309
310	/// Make preparatory checks for crediting some funds from an account. Flags indicate
311	/// requirements of the credit.
312	///
313	/// - `amount`: The amount desired to be credited.
314	/// - `debit`: The amount by which some other account has been debited. If this is greater than
315	///   `amount`, then the `burn_dust` parameter takes effect.
316	/// - `burn_dust`: Indicates that in the case of debit being greater than amount, the additional
317	///   (dust) value should be burned, rather than credited.
318	///
319	/// On success, the amount which should be credited (this will always be at least `amount`)
320	/// together with an optional value indicating the value which should be burned. The latter
321	/// will always be `None` as long as `burn_dust` is `false` or `debit` is no greater than
322	/// `amount`.
323	///
324	/// If no valid credit can be made then return an `Err`.
325	pub(super) fn prep_credit(
326		id: T::AssetId,
327		dest: &T::AccountId,
328		amount: T::Balance,
329		debit: T::Balance,
330		burn_dust: bool,
331	) -> Result<(T::Balance, Option<T::Balance>), DispatchError> {
332		let (credit, maybe_burn) = match (burn_dust, debit.checked_sub(&amount)) {
333			(true, Some(dust)) => (amount, Some(dust)),
334			_ => (debit, None),
335		};
336		Self::can_increase(id, dest, credit, false).into_result()?;
337		Ok((credit, maybe_burn))
338	}
339
340	/// Creates an account for `who` to hold asset `id` with a zero balance and takes a deposit.
341	pub(super) fn do_touch(
342		id: T::AssetId,
343		who: T::AccountId,
344		depositor: T::AccountId,
345	) -> DispatchResult {
346		ensure!(!Account::<T, I>::contains_key(&id, &who), Error::<T, I>::AlreadyExists);
347		let deposit = T::AssetAccountDeposit::get();
348		let mut details = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
349		ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
350		let reason = Self::new_account(&who, &mut details, Some((&depositor, deposit)))?;
351		T::Currency::reserve(&depositor, deposit)?;
352		Asset::<T, I>::insert(&id, details);
353		Account::<T, I>::insert(
354			&id,
355			&who,
356			AssetAccountOf::<T, I> {
357				balance: Zero::zero(),
358				status: AccountStatus::Liquid,
359				reason,
360				extra: T::Extra::default(),
361			},
362		);
363		Self::deposit_event(Event::Touched { asset_id: id, who, depositor });
364		Ok(())
365	}
366
367	/// Returns a deposit or a consumer reference, destroying an asset-account.
368	/// Non-zero balance accounts refunded and destroyed only if `allow_burn` is true.
369	pub(super) fn do_refund(id: T::AssetId, who: T::AccountId, allow_burn: bool) -> DispatchResult {
370		use AssetStatus::*;
371		use ExistenceReason::*;
372
373		let mut account = Account::<T, I>::get(&id, &who).ok_or(Error::<T, I>::NoDeposit)?;
374		ensure!(matches!(account.reason, Consumer | DepositHeld(..)), Error::<T, I>::NoDeposit);
375		let mut details = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
376		ensure!(matches!(details.status, Live | Frozen), Error::<T, I>::IncorrectStatus);
377		ensure!(account.balance.is_zero() || allow_burn, Error::<T, I>::WouldBurn);
378		Self::ensure_account_can_die(id.clone(), &who)?;
379
380		if let Some(deposit) = account.reason.take_deposit() {
381			T::Currency::unreserve(&who, deposit);
382		}
383
384		if let Remove = Self::dead_account(&who, &mut details, &account.reason, false) {
385			Account::<T, I>::remove(&id, &who);
386		} else {
387			debug_assert!(false, "refund did not result in dead account?!");
388			// deposit may have been refunded, need to update `Account`
389			Account::<T, I>::insert(id, &who, account);
390			return Ok(())
391		}
392
393		Asset::<T, I>::insert(&id, details);
394		// Executing a hook here is safe, since it is not in a `mutate`.
395		T::Freezer::died(id.clone(), &who);
396		T::Holder::died(id, &who);
397		Ok(())
398	}
399
400	/// Refunds the `DepositFrom` of an account only if its balance is zero.
401	///
402	/// If the `maybe_check_caller` parameter is specified, it must match the account that provided
403	/// the deposit or must be the admin of the asset.
404	pub(super) fn do_refund_other(
405		id: T::AssetId,
406		who: &T::AccountId,
407		maybe_check_caller: Option<T::AccountId>,
408	) -> DispatchResult {
409		let mut account = Account::<T, I>::get(&id, &who).ok_or(Error::<T, I>::NoDeposit)?;
410		let (depositor, deposit) =
411			account.reason.take_deposit_from().ok_or(Error::<T, I>::NoDeposit)?;
412		let mut details = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
413		ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
414		ensure!(!account.status.is_frozen(), Error::<T, I>::Frozen);
415		if let Some(caller) = maybe_check_caller {
416			ensure!(caller == depositor || caller == details.admin, Error::<T, I>::NoPermission);
417		}
418		ensure!(account.balance.is_zero(), Error::<T, I>::WouldBurn);
419		Self::ensure_account_can_die(id.clone(), who)?;
420
421		T::Currency::unreserve(&depositor, deposit);
422
423		if let Remove = Self::dead_account(&who, &mut details, &account.reason, false) {
424			Account::<T, I>::remove(&id, &who);
425		} else {
426			debug_assert!(false, "refund did not result in dead account?!");
427			// deposit may have been refunded, need to update `Account`
428			Account::<T, I>::insert(&id, &who, account);
429			return Ok(())
430		}
431		Asset::<T, I>::insert(&id, details);
432		// Executing a hook here is safe, since it is not in a `mutate`.
433		T::Freezer::died(id.clone(), who);
434		T::Holder::died(id, &who);
435		return Ok(())
436	}
437
438	/// Increases the asset `id` balance of `beneficiary` by `amount`.
439	///
440	/// This alters the registered supply of the asset and emits an event.
441	///
442	/// Will return an error or will increase the amount by exactly `amount`.
443	pub(super) fn do_mint(
444		id: T::AssetId,
445		beneficiary: &T::AccountId,
446		amount: T::Balance,
447		maybe_check_issuer: Option<T::AccountId>,
448	) -> DispatchResult {
449		Self::increase_balance(id.clone(), beneficiary, amount, |details| -> DispatchResult {
450			if let Some(check_issuer) = maybe_check_issuer {
451				ensure!(check_issuer == details.issuer, Error::<T, I>::NoPermission);
452			}
453			debug_assert!(details.supply.checked_add(&amount).is_some(), "checked in prep; qed");
454
455			details.supply = details.supply.saturating_add(amount);
456
457			Ok(())
458		})?;
459
460		Self::deposit_event(Event::Issued { asset_id: id, owner: beneficiary.clone(), amount });
461
462		Ok(())
463	}
464
465	/// Increases the asset `id` balance of `beneficiary` by `amount`.
466	///
467	/// LOW-LEVEL: Does not alter the supply of asset or emit an event. Use `do_mint` if you need
468	/// that. This is not intended to be used alone.
469	///
470	/// Will return an error or will increase the amount by exactly `amount`.
471	pub(super) fn increase_balance(
472		id: T::AssetId,
473		beneficiary: &T::AccountId,
474		amount: T::Balance,
475		check: impl FnOnce(
476			&mut AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T, I>>,
477		) -> DispatchResult,
478	) -> DispatchResult {
479		if amount.is_zero() {
480			return Ok(())
481		}
482
483		Self::can_increase(id.clone(), beneficiary, amount, true).into_result()?;
484		Asset::<T, I>::try_mutate(&id, |maybe_details| -> DispatchResult {
485			let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
486			ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
487			check(details)?;
488
489			Account::<T, I>::try_mutate(&id, beneficiary, |maybe_account| -> DispatchResult {
490				match maybe_account {
491					Some(ref mut account) => {
492						account.balance.saturating_accrue(amount);
493					},
494					maybe_account @ None => {
495						// Note this should never fail as it's already checked by
496						// `can_increase`.
497						ensure!(amount >= details.min_balance, TokenError::BelowMinimum);
498						*maybe_account = Some(AssetAccountOf::<T, I> {
499							balance: amount,
500							reason: Self::new_account(beneficiary, details, None)?,
501							status: AccountStatus::Liquid,
502							extra: T::Extra::default(),
503						});
504					},
505				}
506				Ok(())
507			})?;
508			Ok(())
509		})?;
510		Ok(())
511	}
512
513	/// Reduces asset `id` balance of `target` by `amount`. Flags `f` can be given to alter whether
514	/// it attempts a `best_effort` or makes sure to `keep_alive` the account.
515	///
516	/// This alters the registered supply of the asset and emits an event.
517	///
518	/// Will return an error and do nothing or will decrease the amount and return the amount
519	/// reduced by.
520	pub(super) fn do_burn(
521		id: T::AssetId,
522		target: &T::AccountId,
523		amount: T::Balance,
524		maybe_check_admin: Option<T::AccountId>,
525		f: DebitFlags,
526	) -> Result<T::Balance, DispatchError> {
527		let d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
528		ensure!(
529			d.status == AssetStatus::Live || d.status == AssetStatus::Frozen,
530			Error::<T, I>::IncorrectStatus
531		);
532
533		let actual = Self::decrease_balance(id.clone(), target, amount, f, |actual, details| {
534			// Check admin rights.
535			if let Some(check_admin) = maybe_check_admin {
536				ensure!(check_admin == details.admin, Error::<T, I>::NoPermission);
537			}
538
539			debug_assert!(details.supply >= actual, "checked in prep; qed");
540			details.supply = details.supply.saturating_sub(actual);
541
542			Ok(())
543		})?;
544		Self::deposit_event(Event::Burned { asset_id: id, owner: target.clone(), balance: actual });
545		Ok(actual)
546	}
547
548	/// Reduces asset `id` balance of `target` by `amount`. Flags `f` can be given to alter whether
549	/// it attempts a `best_effort` or makes sure to `keep_alive` the account.
550	///
551	/// LOW-LEVEL: Does not alter the supply of asset or emit an event. Use `do_burn` if you need
552	/// that. This is not intended to be used alone.
553	///
554	/// Will return an error and do nothing or will decrease the amount and return the amount
555	/// reduced by.
556	pub(super) fn decrease_balance(
557		id: T::AssetId,
558		target: &T::AccountId,
559		amount: T::Balance,
560		f: DebitFlags,
561		check: impl FnOnce(
562			T::Balance,
563			&mut AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T, I>>,
564		) -> DispatchResult,
565	) -> Result<T::Balance, DispatchError> {
566		if amount.is_zero() {
567			return Ok(amount)
568		}
569
570		let details = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
571		ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
572
573		let actual = Self::prep_debit(id.clone(), target, amount, f)?;
574		let mut target_died: Option<DeadConsequence> = None;
575
576		Asset::<T, I>::try_mutate(&id, |maybe_details| -> DispatchResult {
577			let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
578			check(actual, details)?;
579
580			Account::<T, I>::try_mutate(&id, target, |maybe_account| -> DispatchResult {
581				let mut account = maybe_account.take().ok_or(Error::<T, I>::NoAccount)?;
582				debug_assert!(account.balance >= actual, "checked in prep; qed");
583
584				// Make the debit.
585				account.balance = account.balance.saturating_sub(actual);
586				if account.balance < details.min_balance {
587					debug_assert!(account.balance.is_zero(), "checked in prep; qed");
588					Self::ensure_account_can_die(id.clone(), target)?;
589					target_died = Some(Self::dead_account(target, details, &account.reason, false));
590					if let Some(Remove) = target_died {
591						return Ok(())
592					}
593				};
594				*maybe_account = Some(account);
595				Ok(())
596			})?;
597
598			Ok(())
599		})?;
600
601		// Execute hook outside of `mutate`.
602		if let Some(Remove) = target_died {
603			T::Freezer::died(id.clone(), target);
604			T::Holder::died(id, target);
605		}
606		Ok(actual)
607	}
608
609	/// Reduces the asset `id` balance of `source` by some `amount` and increases the balance of
610	/// `dest` by (similar) amount.
611	///
612	/// Returns the actual amount placed into `dest`. Exact semantics are determined by the flags
613	/// `f`.
614	///
615	/// Will fail if the amount transferred is so small that it cannot create the destination due
616	/// to minimum balance requirements.
617	pub fn do_transfer(
618		id: T::AssetId,
619		source: &T::AccountId,
620		dest: &T::AccountId,
621		amount: T::Balance,
622		maybe_need_admin: Option<T::AccountId>,
623		f: TransferFlags,
624	) -> Result<T::Balance, DispatchError> {
625		let (balance, died) =
626			Self::transfer_and_die(id.clone(), source, dest, amount, maybe_need_admin, f)?;
627		if let Some(Remove) = died {
628			T::Freezer::died(id.clone(), source);
629			T::Holder::died(id, source);
630		}
631		Ok(balance)
632	}
633
634	/// Same as `do_transfer` but it does not execute the `FrozenBalance::died` hook and
635	/// instead returns whether and how the `source` account died in this operation.
636	fn transfer_and_die(
637		id: T::AssetId,
638		source: &T::AccountId,
639		dest: &T::AccountId,
640		amount: T::Balance,
641		maybe_need_admin: Option<T::AccountId>,
642		f: TransferFlags,
643	) -> Result<(T::Balance, Option<DeadConsequence>), DispatchError> {
644		// Early exit if no-op.
645		if amount.is_zero() {
646			return Ok((amount, None))
647		}
648		let details = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
649		ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
650
651		// Figure out the debit and credit, together with side-effects.
652		let debit = Self::prep_debit(id.clone(), source, amount, f.into())?;
653		let (credit, maybe_burn) = Self::prep_credit(id.clone(), dest, amount, debit, f.burn_dust)?;
654
655		let mut source_account =
656			Account::<T, I>::get(&id, &source).ok_or(Error::<T, I>::NoAccount)?;
657		let mut source_died: Option<DeadConsequence> = None;
658
659		Asset::<T, I>::try_mutate(&id, |maybe_details| -> DispatchResult {
660			let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
661
662			// Check admin rights.
663			if let Some(need_admin) = maybe_need_admin {
664				ensure!(need_admin == details.admin, Error::<T, I>::NoPermission);
665			}
666
667			// Skip if source == dest
668			if source == dest {
669				return Ok(())
670			}
671
672			// Burn any dust if needed.
673			if let Some(burn) = maybe_burn {
674				// Debit dust from supply; this will not saturate since it's already checked in
675				// prep.
676				debug_assert!(details.supply >= burn, "checked in prep; qed");
677				details.supply = details.supply.saturating_sub(burn);
678			}
679
680			// Debit balance from source; this will not saturate since it's already checked in prep.
681			debug_assert!(source_account.balance >= debit, "checked in prep; qed");
682			source_account.balance = source_account.balance.saturating_sub(debit);
683
684			// Pre-check that an account can die if is below min balance
685			if source_account.balance < details.min_balance {
686				debug_assert!(source_account.balance.is_zero(), "checked in prep; qed");
687				Self::ensure_account_can_die(id.clone(), source)?;
688			}
689
690			Account::<T, I>::try_mutate(&id, &dest, |maybe_account| -> DispatchResult {
691				match maybe_account {
692					Some(ref mut account) => {
693						// Calculate new balance; this will not saturate since it's already
694						// checked in prep.
695						debug_assert!(
696							account.balance.checked_add(&credit).is_some(),
697							"checked in prep; qed"
698						);
699						account.balance.saturating_accrue(credit);
700					},
701					maybe_account @ None => {
702						*maybe_account = Some(AssetAccountOf::<T, I> {
703							balance: credit,
704							status: AccountStatus::Liquid,
705							reason: Self::new_account(dest, details, None)?,
706							extra: T::Extra::default(),
707						});
708					},
709				}
710				Ok(())
711			})?;
712
713			// Remove source account if it's now dead.
714			if source_account.balance < details.min_balance {
715				debug_assert!(source_account.balance.is_zero(), "checked in prep; qed");
716				source_died =
717					Some(Self::dead_account(source, details, &source_account.reason, false));
718				if let Some(Remove) = source_died {
719					Account::<T, I>::remove(&id, &source);
720					return Ok(())
721				}
722			}
723			Account::<T, I>::insert(&id, &source, &source_account);
724			Ok(())
725		})?;
726
727		Self::deposit_event(Event::Transferred {
728			asset_id: id,
729			from: source.clone(),
730			to: dest.clone(),
731			amount: credit,
732		});
733		Ok((credit, source_died))
734	}
735
736	/// Create a new asset without taking a deposit.
737	///
738	/// * `id`: The `AssetId` you want the new asset to have. Must not already be in use.
739	/// * `owner`: The owner, issuer, admin, and freezer of this asset upon creation.
740	/// * `is_sufficient`: Whether this asset needs users to have an existential deposit to hold
741	///   this asset.
742	/// * `min_balance`: The minimum balance a user is allowed to have of this asset before they are
743	///   considered dust and cleaned up.
744	pub(super) fn do_force_create(
745		id: T::AssetId,
746		owner: T::AccountId,
747		is_sufficient: bool,
748		min_balance: T::Balance,
749	) -> DispatchResult {
750		ensure!(!Asset::<T, I>::contains_key(&id), Error::<T, I>::InUse);
751		ensure!(!min_balance.is_zero(), Error::<T, I>::MinBalanceZero);
752		if let Some(next_id) = NextAssetId::<T, I>::get() {
753			ensure!(id == next_id, Error::<T, I>::BadAssetId);
754		}
755
756		Asset::<T, I>::insert(
757			&id,
758			AssetDetails {
759				owner: owner.clone(),
760				issuer: owner.clone(),
761				admin: owner.clone(),
762				freezer: owner.clone(),
763				supply: Zero::zero(),
764				deposit: Zero::zero(),
765				min_balance,
766				is_sufficient,
767				accounts: 0,
768				sufficients: 0,
769				approvals: 0,
770				status: AssetStatus::Live,
771			},
772		);
773		ensure!(T::CallbackHandle::created(&id, &owner).is_ok(), Error::<T, I>::CallbackFailed);
774		Self::deposit_event(Event::ForceCreated { asset_id: id, owner: owner.clone() });
775		Ok(())
776	}
777
778	/// Start the process of destroying an asset, by setting the asset status to `Destroying`, and
779	/// emitting the `DestructionStarted` event.
780	pub(super) fn do_start_destroy(
781		id: T::AssetId,
782		maybe_check_owner: Option<T::AccountId>,
783	) -> DispatchResult {
784		Asset::<T, I>::try_mutate_exists(id.clone(), |maybe_details| -> Result<(), DispatchError> {
785			let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
786			if let Some(check_owner) = maybe_check_owner {
787				ensure!(details.owner == check_owner, Error::<T, I>::NoPermission);
788			}
789
790			ensure!(!T::Holder::contains_holds(id.clone()), Error::<T, I>::ContainsHolds);
791			ensure!(!T::Freezer::contains_freezes(id.clone()), Error::<T, I>::ContainsFreezes);
792
793			details.status = AssetStatus::Destroying;
794
795			Self::deposit_event(Event::DestructionStarted { asset_id: id });
796			Ok(())
797		})
798	}
799
800	/// Destroy accounts associated with a given asset up to the max (T::RemoveItemsLimit).
801	///
802	/// Each call emits the `Event::DestroyedAccounts` event.
803	/// Returns the number of destroyed accounts.
804	pub(super) fn do_destroy_accounts(
805		id: T::AssetId,
806		max_items: u32,
807	) -> Result<u32, DispatchError> {
808		let mut dead_accounts: Vec<T::AccountId> = vec![];
809		let mut remaining_accounts = 0;
810		Asset::<T, I>::try_mutate_exists(&id, |maybe_details| -> Result<(), DispatchError> {
811			let mut details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
812			// Should only destroy accounts while the asset is in a destroying state
813			ensure!(details.status == AssetStatus::Destroying, Error::<T, I>::IncorrectStatus);
814
815			for (i, (who, mut v)) in Account::<T, I>::iter_prefix(&id).enumerate() {
816				if Self::ensure_account_can_die(id.clone(), &who).is_err() {
817					continue
818				}
819				// unreserve the existence deposit if any
820				if let Some((depositor, deposit)) = v.reason.take_deposit_from() {
821					T::Currency::unreserve(&depositor, deposit);
822				} else if let Some(deposit) = v.reason.take_deposit() {
823					T::Currency::unreserve(&who, deposit);
824				}
825				if let Remove = Self::dead_account(&who, &mut details, &v.reason, false) {
826					Account::<T, I>::remove(&id, &who);
827					dead_accounts.push(who);
828				} else {
829					// deposit may have been released, need to update `Account`
830					Account::<T, I>::insert(&id, &who, v);
831					defensive!("destroy did not result in dead account?!");
832				}
833				if i + 1 >= (max_items as usize) {
834					break
835				}
836			}
837			remaining_accounts = details.accounts;
838			Ok(())
839		})?;
840
841		for who in &dead_accounts {
842			T::Freezer::died(id.clone(), &who);
843			T::Holder::died(id.clone(), &who);
844		}
845
846		Self::deposit_event(Event::AccountsDestroyed {
847			asset_id: id,
848			accounts_destroyed: dead_accounts.len() as u32,
849			accounts_remaining: remaining_accounts as u32,
850		});
851		Ok(dead_accounts.len() as u32)
852	}
853
854	/// Destroy approvals associated with a given asset up to the max (T::RemoveItemsLimit).
855	///
856	/// Each call emits the `Event::DestroyedApprovals` event
857	/// Returns the number of destroyed approvals.
858	pub(super) fn do_destroy_approvals(
859		id: T::AssetId,
860		max_items: u32,
861	) -> Result<u32, DispatchError> {
862		let mut removed_approvals = 0;
863		Asset::<T, I>::try_mutate_exists(
864			id.clone(),
865			|maybe_details| -> Result<(), DispatchError> {
866				let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
867
868				// Should only destroy accounts while the asset is in a destroying state.
869				ensure!(details.status == AssetStatus::Destroying, Error::<T, I>::IncorrectStatus);
870
871				for ((owner, _), approval) in Approvals::<T, I>::drain_prefix((id.clone(),)) {
872					T::Currency::unreserve(&owner, approval.deposit);
873					removed_approvals = removed_approvals.saturating_add(1);
874					details.approvals = details.approvals.saturating_sub(1);
875					if removed_approvals >= max_items {
876						break
877					}
878				}
879				Self::deposit_event(Event::ApprovalsDestroyed {
880					asset_id: id,
881					approvals_destroyed: removed_approvals as u32,
882					approvals_remaining: details.approvals as u32,
883				});
884				Ok(())
885			},
886		)?;
887		Ok(removed_approvals)
888	}
889
890	/// Complete destroying an asset and unreserve the deposit.
891	///
892	/// On success, the `Event::Destroyed` event is emitted.
893	pub(super) fn do_finish_destroy(id: T::AssetId) -> DispatchResult {
894		Asset::<T, I>::try_mutate_exists(id.clone(), |maybe_details| -> Result<(), DispatchError> {
895			let details = maybe_details.take().ok_or(Error::<T, I>::Unknown)?;
896			ensure!(details.status == AssetStatus::Destroying, Error::<T, I>::IncorrectStatus);
897			ensure!(details.accounts == 0, Error::<T, I>::InUse);
898			ensure!(details.approvals == 0, Error::<T, I>::InUse);
899			ensure!(T::CallbackHandle::destroyed(&id).is_ok(), Error::<T, I>::CallbackFailed);
900
901			let metadata = Metadata::<T, I>::take(&id);
902			T::Currency::unreserve(
903				&details.owner,
904				details.deposit.saturating_add(metadata.deposit),
905			);
906			Reserves::<T, I>::remove(&id);
907			Self::deposit_event(Event::Destroyed { asset_id: id });
908
909			Ok(())
910		})
911	}
912
913	/// Creates an approval from `owner` to spend `amount` of asset `id` tokens by 'delegate'
914	/// while reserving `T::ApprovalDeposit` from owner
915	///
916	/// If an approval already exists, the new amount is added to such existing approval
917	pub fn do_approve_transfer(
918		id: T::AssetId,
919		owner: &T::AccountId,
920		delegate: &T::AccountId,
921		amount: T::Balance,
922	) -> DispatchResult {
923		let mut d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
924		ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
925		Approvals::<T, I>::try_mutate(
926			(id.clone(), &owner, &delegate),
927			|maybe_approved| -> DispatchResult {
928				let mut approved = match maybe_approved.take() {
929					// an approval already exists and is being updated
930					Some(a) => a,
931					// a new approval is created
932					None => {
933						d.approvals.saturating_inc();
934						Default::default()
935					},
936				};
937				let deposit_required = T::ApprovalDeposit::get();
938				if approved.deposit < deposit_required {
939					T::Currency::reserve(owner, deposit_required - approved.deposit)?;
940					approved.deposit = deposit_required;
941				}
942				approved.amount = approved.amount.saturating_add(amount);
943				*maybe_approved = Some(approved);
944				Ok(())
945			},
946		)?;
947		Asset::<T, I>::insert(&id, d);
948		Self::deposit_event(Event::ApprovedTransfer {
949			asset_id: id,
950			source: owner.clone(),
951			delegate: delegate.clone(),
952			amount,
953		});
954
955		Ok(())
956	}
957
958	/// Reduces the asset `id` balance of `owner` by some `amount` and increases the balance of
959	/// `dest` by (similar) amount, checking that 'delegate' has an existing approval from `owner`
960	/// to spend`amount`.
961	///
962	/// Will fail if `amount` is greater than the approval from `owner` to 'delegate'
963	/// Will unreserve the deposit from `owner` if the entire approved `amount` is spent by
964	/// 'delegate'
965	pub fn do_transfer_approved(
966		id: T::AssetId,
967		owner: &T::AccountId,
968		delegate: &T::AccountId,
969		destination: &T::AccountId,
970		amount: T::Balance,
971	) -> DispatchResult {
972		let mut owner_died: Option<DeadConsequence> = None;
973
974		let d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
975		ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
976
977		Approvals::<T, I>::try_mutate_exists(
978			(id.clone(), &owner, delegate),
979			|maybe_approved| -> DispatchResult {
980				let mut approved = maybe_approved.take().ok_or(Error::<T, I>::Unapproved)?;
981				let remaining =
982					approved.amount.checked_sub(&amount).ok_or(Error::<T, I>::Unapproved)?;
983
984				let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false };
985				owner_died =
986					Self::transfer_and_die(id.clone(), owner, destination, amount, None, f)?.1;
987
988				if remaining.is_zero() {
989					T::Currency::unreserve(owner, approved.deposit);
990					Asset::<T, I>::mutate(id.clone(), |maybe_details| {
991						if let Some(details) = maybe_details {
992							details.approvals.saturating_dec();
993						}
994					});
995				} else {
996					approved.amount = remaining;
997					*maybe_approved = Some(approved);
998				}
999				Ok(())
1000			},
1001		)?;
1002
1003		// Execute hook outside of `mutate`.
1004		if let Some(Remove) = owner_died {
1005			T::Freezer::died(id.clone(), owner);
1006			T::Holder::died(id, owner);
1007		}
1008		Ok(())
1009	}
1010
1011	/// Do set metadata
1012	pub(super) fn do_set_metadata(
1013		id: T::AssetId,
1014		from: &T::AccountId,
1015		name: Vec<u8>,
1016		symbol: Vec<u8>,
1017		decimals: u8,
1018	) -> DispatchResult {
1019		let bounded_name: BoundedVec<u8, T::StringLimit> =
1020			name.clone().try_into().map_err(|_| Error::<T, I>::BadMetadata)?;
1021		let bounded_symbol: BoundedVec<u8, T::StringLimit> =
1022			symbol.clone().try_into().map_err(|_| Error::<T, I>::BadMetadata)?;
1023
1024		let d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
1025		ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
1026		ensure!(from == &d.owner, Error::<T, I>::NoPermission);
1027
1028		Metadata::<T, I>::try_mutate_exists(id.clone(), |metadata| {
1029			ensure!(metadata.as_ref().map_or(true, |m| !m.is_frozen), Error::<T, I>::NoPermission);
1030
1031			let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
1032			let new_deposit = Self::calc_metadata_deposit(&name, &symbol);
1033
1034			if new_deposit > old_deposit {
1035				T::Currency::reserve(from, new_deposit - old_deposit)?;
1036			} else {
1037				T::Currency::unreserve(from, old_deposit - new_deposit);
1038			}
1039
1040			*metadata = Some(AssetMetadata {
1041				deposit: new_deposit,
1042				name: bounded_name,
1043				symbol: bounded_symbol,
1044				decimals,
1045				is_frozen: false,
1046			});
1047
1048			Self::deposit_event(Event::MetadataSet {
1049				asset_id: id,
1050				name,
1051				symbol,
1052				decimals,
1053				is_frozen: false,
1054			});
1055			Ok(())
1056		})
1057	}
1058
1059	/// Calculate the metadata deposit for the provided data.
1060	pub(super) fn calc_metadata_deposit(name: &[u8], symbol: &[u8]) -> DepositBalanceOf<T, I> {
1061		T::MetadataDepositPerByte::get()
1062			.saturating_mul(((name.len() + symbol.len()) as u32).into())
1063			.saturating_add(T::MetadataDepositBase::get())
1064	}
1065
1066	/// Returns all the non-zero balances for all assets of the given `account`.
1067	pub fn account_balances(account: T::AccountId) -> Vec<(T::AssetId, T::Balance)> {
1068		Asset::<T, I>::iter_keys()
1069			.filter_map(|id| {
1070				Self::maybe_balance(id.clone(), account.clone()).map(|balance| (id, balance))
1071			})
1072			.collect::<Vec<_>>()
1073	}
1074
1075	/// Reset the team for the asset with the given `id`.
1076	///
1077	/// ### Parameters
1078	/// - `id`: The identifier of the asset for which the team is being reset.
1079	/// - `owner`: The new `owner` account for the asset.
1080	/// - `admin`: The new `admin` account for the asset.
1081	/// - `issuer`: The new `issuer` account for the asset.
1082	/// - `freezer`: The new `freezer` account for the asset.
1083	pub(crate) fn do_reset_team(
1084		id: T::AssetId,
1085		owner: T::AccountId,
1086		admin: T::AccountId,
1087		issuer: T::AccountId,
1088		freezer: T::AccountId,
1089	) -> DispatchResult {
1090		let mut d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
1091		d.owner = owner;
1092		d.admin = admin;
1093		d.issuer = issuer;
1094		d.freezer = freezer;
1095		Asset::<T, I>::insert(&id, d);
1096		Ok(())
1097	}
1098
1099	/// Helper function for setting reserves to be used in benchmarking and migrations.
1100	/// Does not check validity of asset id, caller should check it.
1101	pub fn unchecked_update_reserves(
1102		id: T::AssetId,
1103		reserves: Vec<T::ReserveData>,
1104	) -> Result<(), Error<T, I>> {
1105		if reserves.is_empty() {
1106			Reserves::<T, I>::remove(&id);
1107			Self::deposit_event(Event::ReservesRemoved { asset_id: id });
1108		} else {
1109			let bounded_reserves =
1110				reserves.clone().try_into().map_err(|_| Error::<T, I>::TooManyReserves)?;
1111			Reserves::<T, I>::set(&id, bounded_reserves);
1112			Self::deposit_event(Event::ReservesUpdated { asset_id: id, reserves });
1113		}
1114		Ok(())
1115	}
1116}