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	///
342	/// When `check_depositor` is set to true, the depositor must be either the asset's Admin or
343	/// Freezer, otherwise the depositor can be any account.
344	pub(super) fn do_touch(
345		id: T::AssetId,
346		who: T::AccountId,
347		depositor: T::AccountId,
348		check_depositor: bool,
349	) -> DispatchResult {
350		ensure!(!Account::<T, I>::contains_key(&id, &who), Error::<T, I>::AlreadyExists);
351		let deposit = T::AssetAccountDeposit::get();
352		let mut details = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
353		ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
354		ensure!(
355			!check_depositor || &depositor == &details.admin || &depositor == &details.freezer,
356			Error::<T, I>::NoPermission
357		);
358		let reason = Self::new_account(&who, &mut details, Some((&depositor, deposit)))?;
359		T::Currency::reserve(&depositor, deposit)?;
360		Asset::<T, I>::insert(&id, details);
361		Account::<T, I>::insert(
362			&id,
363			&who,
364			AssetAccountOf::<T, I> {
365				balance: Zero::zero(),
366				status: AccountStatus::Liquid,
367				reason,
368				extra: T::Extra::default(),
369			},
370		);
371		Self::deposit_event(Event::Touched { asset_id: id, who, depositor });
372		Ok(())
373	}
374
375	/// Returns a deposit or a consumer reference, destroying an asset-account.
376	/// Non-zero balance accounts refunded and destroyed only if `allow_burn` is true.
377	pub(super) fn do_refund(id: T::AssetId, who: T::AccountId, allow_burn: bool) -> DispatchResult {
378		use AssetStatus::*;
379		use ExistenceReason::*;
380
381		let mut account = Account::<T, I>::get(&id, &who).ok_or(Error::<T, I>::NoDeposit)?;
382		ensure!(matches!(account.reason, Consumer | DepositHeld(..)), Error::<T, I>::NoDeposit);
383		let mut details = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
384		ensure!(matches!(details.status, Live | Frozen), Error::<T, I>::IncorrectStatus);
385		ensure!(account.balance.is_zero() || allow_burn, Error::<T, I>::WouldBurn);
386		Self::ensure_account_can_die(id.clone(), &who)?;
387
388		if let Some(deposit) = account.reason.take_deposit() {
389			T::Currency::unreserve(&who, deposit);
390		}
391
392		if let Remove = Self::dead_account(&who, &mut details, &account.reason, false) {
393			Account::<T, I>::remove(&id, &who);
394		} else {
395			debug_assert!(false, "refund did not result in dead account?!");
396			// deposit may have been refunded, need to update `Account`
397			Account::<T, I>::insert(id, &who, account);
398			return Ok(())
399		}
400
401		Asset::<T, I>::insert(&id, details);
402		// Executing a hook here is safe, since it is not in a `mutate`.
403		T::Freezer::died(id.clone(), &who);
404		T::Holder::died(id, &who);
405		Ok(())
406	}
407
408	/// Refunds the `DepositFrom` of an account only if its balance is zero.
409	///
410	/// If the `maybe_check_caller` parameter is specified, it must match the account that provided
411	/// the deposit or must be the admin of the asset.
412	pub(super) fn do_refund_other(
413		id: T::AssetId,
414		who: &T::AccountId,
415		maybe_check_caller: Option<T::AccountId>,
416	) -> DispatchResult {
417		let mut account = Account::<T, I>::get(&id, &who).ok_or(Error::<T, I>::NoDeposit)?;
418		let (depositor, deposit) =
419			account.reason.take_deposit_from().ok_or(Error::<T, I>::NoDeposit)?;
420		let mut details = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
421		ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
422		ensure!(!account.status.is_frozen(), Error::<T, I>::Frozen);
423		if let Some(caller) = maybe_check_caller {
424			ensure!(caller == depositor || caller == details.admin, Error::<T, I>::NoPermission);
425		}
426		ensure!(account.balance.is_zero(), Error::<T, I>::WouldBurn);
427		Self::ensure_account_can_die(id.clone(), who)?;
428
429		T::Currency::unreserve(&depositor, deposit);
430
431		if let Remove = Self::dead_account(&who, &mut details, &account.reason, false) {
432			Account::<T, I>::remove(&id, &who);
433		} else {
434			debug_assert!(false, "refund did not result in dead account?!");
435			// deposit may have been refunded, need to update `Account`
436			Account::<T, I>::insert(&id, &who, account);
437			return Ok(())
438		}
439		Asset::<T, I>::insert(&id, details);
440		// Executing a hook here is safe, since it is not in a `mutate`.
441		T::Freezer::died(id.clone(), who);
442		T::Holder::died(id, &who);
443		return Ok(())
444	}
445
446	/// Increases the asset `id` balance of `beneficiary` by `amount`.
447	///
448	/// This alters the registered supply of the asset and emits an event.
449	///
450	/// Will return an error or will increase the amount by exactly `amount`.
451	pub(super) fn do_mint(
452		id: T::AssetId,
453		beneficiary: &T::AccountId,
454		amount: T::Balance,
455		maybe_check_issuer: Option<T::AccountId>,
456	) -> DispatchResult {
457		Self::increase_balance(id.clone(), beneficiary, amount, |details| -> DispatchResult {
458			if let Some(check_issuer) = maybe_check_issuer {
459				ensure!(check_issuer == details.issuer, Error::<T, I>::NoPermission);
460			}
461			debug_assert!(details.supply.checked_add(&amount).is_some(), "checked in prep; qed");
462
463			details.supply = details.supply.saturating_add(amount);
464
465			Ok(())
466		})?;
467
468		Self::deposit_event(Event::Issued { asset_id: id, owner: beneficiary.clone(), amount });
469
470		Ok(())
471	}
472
473	/// Increases the asset `id` balance of `beneficiary` by `amount`.
474	///
475	/// LOW-LEVEL: Does not alter the supply of asset or emit an event. Use `do_mint` if you need
476	/// that. This is not intended to be used alone.
477	///
478	/// Will return an error or will increase the amount by exactly `amount`.
479	pub(super) fn increase_balance(
480		id: T::AssetId,
481		beneficiary: &T::AccountId,
482		amount: T::Balance,
483		check: impl FnOnce(
484			&mut AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T, I>>,
485		) -> DispatchResult,
486	) -> DispatchResult {
487		if amount.is_zero() {
488			return Ok(())
489		}
490
491		Self::can_increase(id.clone(), beneficiary, amount, true).into_result()?;
492		Asset::<T, I>::try_mutate(&id, |maybe_details| -> DispatchResult {
493			let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
494			ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
495			check(details)?;
496
497			Account::<T, I>::try_mutate(&id, beneficiary, |maybe_account| -> DispatchResult {
498				match maybe_account {
499					Some(ref mut account) => {
500						account.balance.saturating_accrue(amount);
501					},
502					maybe_account @ None => {
503						// Note this should never fail as it's already checked by
504						// `can_increase`.
505						ensure!(amount >= details.min_balance, TokenError::BelowMinimum);
506						*maybe_account = Some(AssetAccountOf::<T, I> {
507							balance: amount,
508							reason: Self::new_account(beneficiary, details, None)?,
509							status: AccountStatus::Liquid,
510							extra: T::Extra::default(),
511						});
512					},
513				}
514				Ok(())
515			})?;
516			Ok(())
517		})?;
518		Ok(())
519	}
520
521	/// Reduces asset `id` balance of `target` by `amount`. Flags `f` can be given to alter whether
522	/// it attempts a `best_effort` or makes sure to `keep_alive` the account.
523	///
524	/// This alters the registered supply of the asset and emits an event.
525	///
526	/// Will return an error and do nothing or will decrease the amount and return the amount
527	/// reduced by.
528	pub(super) fn do_burn(
529		id: T::AssetId,
530		target: &T::AccountId,
531		amount: T::Balance,
532		maybe_check_admin: Option<T::AccountId>,
533		f: DebitFlags,
534	) -> Result<T::Balance, DispatchError> {
535		let d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
536		ensure!(
537			d.status == AssetStatus::Live || d.status == AssetStatus::Frozen,
538			Error::<T, I>::IncorrectStatus
539		);
540
541		let actual = Self::decrease_balance(id.clone(), target, amount, f, |actual, details| {
542			// Check admin rights.
543			if let Some(check_admin) = maybe_check_admin {
544				ensure!(check_admin == details.admin, Error::<T, I>::NoPermission);
545			}
546
547			debug_assert!(details.supply >= actual, "checked in prep; qed");
548			details.supply = details.supply.saturating_sub(actual);
549
550			Ok(())
551		})?;
552		Self::deposit_event(Event::Burned { asset_id: id, owner: target.clone(), balance: actual });
553		Ok(actual)
554	}
555
556	/// Reduces asset `id` balance of `target` by `amount`. Flags `f` can be given to alter whether
557	/// it attempts a `best_effort` or makes sure to `keep_alive` the account.
558	///
559	/// LOW-LEVEL: Does not alter the supply of asset or emit an event. Use `do_burn` if you need
560	/// that. This is not intended to be used alone.
561	///
562	/// Will return an error and do nothing or will decrease the amount and return the amount
563	/// reduced by.
564	pub(super) fn decrease_balance(
565		id: T::AssetId,
566		target: &T::AccountId,
567		amount: T::Balance,
568		f: DebitFlags,
569		check: impl FnOnce(
570			T::Balance,
571			&mut AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T, I>>,
572		) -> DispatchResult,
573	) -> Result<T::Balance, DispatchError> {
574		if amount.is_zero() {
575			return Ok(amount)
576		}
577
578		let details = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
579		ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
580
581		let actual = Self::prep_debit(id.clone(), target, amount, f)?;
582		let mut target_died: Option<DeadConsequence> = None;
583
584		Asset::<T, I>::try_mutate(&id, |maybe_details| -> DispatchResult {
585			let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
586			check(actual, details)?;
587
588			Account::<T, I>::try_mutate(&id, target, |maybe_account| -> DispatchResult {
589				let mut account = maybe_account.take().ok_or(Error::<T, I>::NoAccount)?;
590				debug_assert!(account.balance >= actual, "checked in prep; qed");
591
592				// Make the debit.
593				account.balance = account.balance.saturating_sub(actual);
594				if account.balance < details.min_balance {
595					debug_assert!(account.balance.is_zero(), "checked in prep; qed");
596					Self::ensure_account_can_die(id.clone(), target)?;
597					target_died = Some(Self::dead_account(target, details, &account.reason, false));
598					if let Some(Remove) = target_died {
599						return Ok(())
600					}
601				};
602				*maybe_account = Some(account);
603				Ok(())
604			})?;
605
606			Ok(())
607		})?;
608
609		// Execute hook outside of `mutate`.
610		if let Some(Remove) = target_died {
611			T::Freezer::died(id.clone(), target);
612			T::Holder::died(id, target);
613		}
614		Ok(actual)
615	}
616
617	/// Reduces the asset `id` balance of `source` by some `amount` and increases the balance of
618	/// `dest` by (similar) amount.
619	///
620	/// Returns the actual amount placed into `dest`. Exact semantics are determined by the flags
621	/// `f`.
622	///
623	/// Will fail if the amount transferred is so small that it cannot create the destination due
624	/// to minimum balance requirements.
625	pub(super) fn do_transfer(
626		id: T::AssetId,
627		source: &T::AccountId,
628		dest: &T::AccountId,
629		amount: T::Balance,
630		maybe_need_admin: Option<T::AccountId>,
631		f: TransferFlags,
632	) -> Result<T::Balance, DispatchError> {
633		let (balance, died) =
634			Self::transfer_and_die(id.clone(), source, dest, amount, maybe_need_admin, f)?;
635		if let Some(Remove) = died {
636			T::Freezer::died(id.clone(), source);
637			T::Holder::died(id, source);
638		}
639		Ok(balance)
640	}
641
642	/// Same as `do_transfer` but it does not execute the `FrozenBalance::died` hook and
643	/// instead returns whether and how the `source` account died in this operation.
644	fn transfer_and_die(
645		id: T::AssetId,
646		source: &T::AccountId,
647		dest: &T::AccountId,
648		amount: T::Balance,
649		maybe_need_admin: Option<T::AccountId>,
650		f: TransferFlags,
651	) -> Result<(T::Balance, Option<DeadConsequence>), DispatchError> {
652		// Early exit if no-op.
653		if amount.is_zero() {
654			return Ok((amount, None))
655		}
656		let details = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
657		ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
658
659		// Figure out the debit and credit, together with side-effects.
660		let debit = Self::prep_debit(id.clone(), source, amount, f.into())?;
661		let (credit, maybe_burn) = Self::prep_credit(id.clone(), dest, amount, debit, f.burn_dust)?;
662
663		let mut source_account =
664			Account::<T, I>::get(&id, &source).ok_or(Error::<T, I>::NoAccount)?;
665		let mut source_died: Option<DeadConsequence> = None;
666
667		Asset::<T, I>::try_mutate(&id, |maybe_details| -> DispatchResult {
668			let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
669
670			// Check admin rights.
671			if let Some(need_admin) = maybe_need_admin {
672				ensure!(need_admin == details.admin, Error::<T, I>::NoPermission);
673			}
674
675			// Skip if source == dest
676			if source == dest {
677				return Ok(())
678			}
679
680			// Burn any dust if needed.
681			if let Some(burn) = maybe_burn {
682				// Debit dust from supply; this will not saturate since it's already checked in
683				// prep.
684				debug_assert!(details.supply >= burn, "checked in prep; qed");
685				details.supply = details.supply.saturating_sub(burn);
686			}
687
688			// Debit balance from source; this will not saturate since it's already checked in prep.
689			debug_assert!(source_account.balance >= debit, "checked in prep; qed");
690			source_account.balance = source_account.balance.saturating_sub(debit);
691
692			// Pre-check that an account can die if is below min balance
693			if source_account.balance < details.min_balance {
694				debug_assert!(source_account.balance.is_zero(), "checked in prep; qed");
695				Self::ensure_account_can_die(id.clone(), source)?;
696			}
697
698			Account::<T, I>::try_mutate(&id, &dest, |maybe_account| -> DispatchResult {
699				match maybe_account {
700					Some(ref mut account) => {
701						// Calculate new balance; this will not saturate since it's already
702						// checked in prep.
703						debug_assert!(
704							account.balance.checked_add(&credit).is_some(),
705							"checked in prep; qed"
706						);
707						account.balance.saturating_accrue(credit);
708					},
709					maybe_account @ None => {
710						*maybe_account = Some(AssetAccountOf::<T, I> {
711							balance: credit,
712							status: AccountStatus::Liquid,
713							reason: Self::new_account(dest, details, None)?,
714							extra: T::Extra::default(),
715						});
716					},
717				}
718				Ok(())
719			})?;
720
721			// Remove source account if it's now dead.
722			if source_account.balance < details.min_balance {
723				debug_assert!(source_account.balance.is_zero(), "checked in prep; qed");
724				source_died =
725					Some(Self::dead_account(source, details, &source_account.reason, false));
726				if let Some(Remove) = source_died {
727					Account::<T, I>::remove(&id, &source);
728					return Ok(())
729				}
730			}
731			Account::<T, I>::insert(&id, &source, &source_account);
732			Ok(())
733		})?;
734
735		Self::deposit_event(Event::Transferred {
736			asset_id: id,
737			from: source.clone(),
738			to: dest.clone(),
739			amount: credit,
740		});
741		Ok((credit, source_died))
742	}
743
744	/// Create a new asset without taking a deposit.
745	///
746	/// * `id`: The `AssetId` you want the new asset to have. Must not already be in use.
747	/// * `owner`: The owner, issuer, admin, and freezer of this asset upon creation.
748	/// * `is_sufficient`: Whether this asset needs users to have an existential deposit to hold
749	///   this asset.
750	/// * `min_balance`: The minimum balance a user is allowed to have of this asset before they are
751	///   considered dust and cleaned up.
752	pub(super) fn do_force_create(
753		id: T::AssetId,
754		owner: T::AccountId,
755		is_sufficient: bool,
756		min_balance: T::Balance,
757	) -> DispatchResult {
758		ensure!(!Asset::<T, I>::contains_key(&id), Error::<T, I>::InUse);
759		ensure!(!min_balance.is_zero(), Error::<T, I>::MinBalanceZero);
760		if let Some(next_id) = NextAssetId::<T, I>::get() {
761			ensure!(id == next_id, Error::<T, I>::BadAssetId);
762		}
763
764		Asset::<T, I>::insert(
765			&id,
766			AssetDetails {
767				owner: owner.clone(),
768				issuer: owner.clone(),
769				admin: owner.clone(),
770				freezer: owner.clone(),
771				supply: Zero::zero(),
772				deposit: Zero::zero(),
773				min_balance,
774				is_sufficient,
775				accounts: 0,
776				sufficients: 0,
777				approvals: 0,
778				status: AssetStatus::Live,
779			},
780		);
781		ensure!(T::CallbackHandle::created(&id, &owner).is_ok(), Error::<T, I>::CallbackFailed);
782		Self::deposit_event(Event::ForceCreated { asset_id: id, owner: owner.clone() });
783		Ok(())
784	}
785
786	/// Start the process of destroying an asset, by setting the asset status to `Destroying`, and
787	/// emitting the `DestructionStarted` event.
788	pub(super) fn do_start_destroy(
789		id: T::AssetId,
790		maybe_check_owner: Option<T::AccountId>,
791	) -> DispatchResult {
792		Asset::<T, I>::try_mutate_exists(id.clone(), |maybe_details| -> Result<(), DispatchError> {
793			let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
794			if let Some(check_owner) = maybe_check_owner {
795				ensure!(details.owner == check_owner, Error::<T, I>::NoPermission);
796			}
797
798			ensure!(!T::Holder::contains_holds(id.clone()), Error::<T, I>::ContainsHolds);
799			ensure!(!T::Freezer::contains_freezes(id.clone()), Error::<T, I>::ContainsFreezes);
800
801			details.status = AssetStatus::Destroying;
802
803			Self::deposit_event(Event::DestructionStarted { asset_id: id });
804			Ok(())
805		})
806	}
807
808	/// Destroy accounts associated with a given asset up to the max (T::RemoveItemsLimit).
809	///
810	/// Each call emits the `Event::DestroyedAccounts` event.
811	/// Returns the number of destroyed accounts.
812	pub(super) fn do_destroy_accounts(
813		id: T::AssetId,
814		max_items: u32,
815	) -> Result<u32, DispatchError> {
816		let mut dead_accounts: Vec<T::AccountId> = vec![];
817		let mut remaining_accounts = 0;
818		Asset::<T, I>::try_mutate_exists(&id, |maybe_details| -> Result<(), DispatchError> {
819			let mut details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
820			// Should only destroy accounts while the asset is in a destroying state
821			ensure!(details.status == AssetStatus::Destroying, Error::<T, I>::IncorrectStatus);
822
823			for (i, (who, mut v)) in Account::<T, I>::iter_prefix(&id).enumerate() {
824				if Self::ensure_account_can_die(id.clone(), &who).is_err() {
825					continue
826				}
827				// unreserve the existence deposit if any
828				if let Some((depositor, deposit)) = v.reason.take_deposit_from() {
829					T::Currency::unreserve(&depositor, deposit);
830				} else if let Some(deposit) = v.reason.take_deposit() {
831					T::Currency::unreserve(&who, deposit);
832				}
833				if let Remove = Self::dead_account(&who, &mut details, &v.reason, false) {
834					Account::<T, I>::remove(&id, &who);
835					dead_accounts.push(who);
836				} else {
837					// deposit may have been released, need to update `Account`
838					Account::<T, I>::insert(&id, &who, v);
839					defensive!("destroy did not result in dead account?!");
840				}
841				if i + 1 >= (max_items as usize) {
842					break
843				}
844			}
845			remaining_accounts = details.accounts;
846			Ok(())
847		})?;
848
849		for who in &dead_accounts {
850			T::Freezer::died(id.clone(), &who);
851			T::Holder::died(id.clone(), &who);
852		}
853
854		Self::deposit_event(Event::AccountsDestroyed {
855			asset_id: id,
856			accounts_destroyed: dead_accounts.len() as u32,
857			accounts_remaining: remaining_accounts as u32,
858		});
859		Ok(dead_accounts.len() as u32)
860	}
861
862	/// Destroy approvals associated with a given asset up to the max (T::RemoveItemsLimit).
863	///
864	/// Each call emits the `Event::DestroyedApprovals` event
865	/// Returns the number of destroyed approvals.
866	pub(super) fn do_destroy_approvals(
867		id: T::AssetId,
868		max_items: u32,
869	) -> Result<u32, DispatchError> {
870		let mut removed_approvals = 0;
871		Asset::<T, I>::try_mutate_exists(
872			id.clone(),
873			|maybe_details| -> Result<(), DispatchError> {
874				let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
875
876				// Should only destroy accounts while the asset is in a destroying state.
877				ensure!(details.status == AssetStatus::Destroying, Error::<T, I>::IncorrectStatus);
878
879				for ((owner, _), approval) in Approvals::<T, I>::drain_prefix((id.clone(),)) {
880					T::Currency::unreserve(&owner, approval.deposit);
881					removed_approvals = removed_approvals.saturating_add(1);
882					details.approvals = details.approvals.saturating_sub(1);
883					if removed_approvals >= max_items {
884						break
885					}
886				}
887				Self::deposit_event(Event::ApprovalsDestroyed {
888					asset_id: id,
889					approvals_destroyed: removed_approvals as u32,
890					approvals_remaining: details.approvals as u32,
891				});
892				Ok(())
893			},
894		)?;
895		Ok(removed_approvals)
896	}
897
898	/// Complete destroying an asset and unreserve the deposit.
899	///
900	/// On success, the `Event::Destroyed` event is emitted.
901	pub(super) fn do_finish_destroy(id: T::AssetId) -> DispatchResult {
902		Asset::<T, I>::try_mutate_exists(id.clone(), |maybe_details| -> Result<(), DispatchError> {
903			let details = maybe_details.take().ok_or(Error::<T, I>::Unknown)?;
904			ensure!(details.status == AssetStatus::Destroying, Error::<T, I>::IncorrectStatus);
905			ensure!(details.accounts == 0, Error::<T, I>::InUse);
906			ensure!(details.approvals == 0, Error::<T, I>::InUse);
907			ensure!(T::CallbackHandle::destroyed(&id).is_ok(), Error::<T, I>::CallbackFailed);
908
909			let metadata = Metadata::<T, I>::take(&id);
910			T::Currency::unreserve(
911				&details.owner,
912				details.deposit.saturating_add(metadata.deposit),
913			);
914			Self::deposit_event(Event::Destroyed { asset_id: id });
915
916			Ok(())
917		})
918	}
919
920	/// Creates an approval from `owner` to spend `amount` of asset `id` tokens by 'delegate'
921	/// while reserving `T::ApprovalDeposit` from owner
922	///
923	/// If an approval already exists, the new amount is added to such existing approval
924	pub(super) fn do_approve_transfer(
925		id: T::AssetId,
926		owner: &T::AccountId,
927		delegate: &T::AccountId,
928		amount: T::Balance,
929	) -> DispatchResult {
930		let mut d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
931		ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
932		Approvals::<T, I>::try_mutate(
933			(id.clone(), &owner, &delegate),
934			|maybe_approved| -> DispatchResult {
935				let mut approved = match maybe_approved.take() {
936					// an approval already exists and is being updated
937					Some(a) => a,
938					// a new approval is created
939					None => {
940						d.approvals.saturating_inc();
941						Default::default()
942					},
943				};
944				let deposit_required = T::ApprovalDeposit::get();
945				if approved.deposit < deposit_required {
946					T::Currency::reserve(owner, deposit_required - approved.deposit)?;
947					approved.deposit = deposit_required;
948				}
949				approved.amount = approved.amount.saturating_add(amount);
950				*maybe_approved = Some(approved);
951				Ok(())
952			},
953		)?;
954		Asset::<T, I>::insert(&id, d);
955		Self::deposit_event(Event::ApprovedTransfer {
956			asset_id: id,
957			source: owner.clone(),
958			delegate: delegate.clone(),
959			amount,
960		});
961
962		Ok(())
963	}
964
965	/// Reduces the asset `id` balance of `owner` by some `amount` and increases the balance of
966	/// `dest` by (similar) amount, checking that 'delegate' has an existing approval from `owner`
967	/// to spend`amount`.
968	///
969	/// Will fail if `amount` is greater than the approval from `owner` to 'delegate'
970	/// Will unreserve the deposit from `owner` if the entire approved `amount` is spent by
971	/// 'delegate'
972	pub(super) fn do_transfer_approved(
973		id: T::AssetId,
974		owner: &T::AccountId,
975		delegate: &T::AccountId,
976		destination: &T::AccountId,
977		amount: T::Balance,
978	) -> DispatchResult {
979		let mut owner_died: Option<DeadConsequence> = None;
980
981		let d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
982		ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
983
984		Approvals::<T, I>::try_mutate_exists(
985			(id.clone(), &owner, delegate),
986			|maybe_approved| -> DispatchResult {
987				let mut approved = maybe_approved.take().ok_or(Error::<T, I>::Unapproved)?;
988				let remaining =
989					approved.amount.checked_sub(&amount).ok_or(Error::<T, I>::Unapproved)?;
990
991				let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false };
992				owner_died =
993					Self::transfer_and_die(id.clone(), owner, destination, amount, None, f)?.1;
994
995				if remaining.is_zero() {
996					T::Currency::unreserve(owner, approved.deposit);
997					Asset::<T, I>::mutate(id.clone(), |maybe_details| {
998						if let Some(details) = maybe_details {
999							details.approvals.saturating_dec();
1000						}
1001					});
1002				} else {
1003					approved.amount = remaining;
1004					*maybe_approved = Some(approved);
1005				}
1006				Ok(())
1007			},
1008		)?;
1009
1010		// Execute hook outside of `mutate`.
1011		if let Some(Remove) = owner_died {
1012			T::Freezer::died(id.clone(), owner);
1013			T::Holder::died(id, owner);
1014		}
1015		Ok(())
1016	}
1017
1018	/// Do set metadata
1019	pub(super) fn do_set_metadata(
1020		id: T::AssetId,
1021		from: &T::AccountId,
1022		name: Vec<u8>,
1023		symbol: Vec<u8>,
1024		decimals: u8,
1025	) -> DispatchResult {
1026		let bounded_name: BoundedVec<u8, T::StringLimit> =
1027			name.clone().try_into().map_err(|_| Error::<T, I>::BadMetadata)?;
1028		let bounded_symbol: BoundedVec<u8, T::StringLimit> =
1029			symbol.clone().try_into().map_err(|_| Error::<T, I>::BadMetadata)?;
1030
1031		let d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
1032		ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
1033		ensure!(from == &d.owner, Error::<T, I>::NoPermission);
1034
1035		Metadata::<T, I>::try_mutate_exists(id.clone(), |metadata| {
1036			ensure!(metadata.as_ref().map_or(true, |m| !m.is_frozen), Error::<T, I>::NoPermission);
1037
1038			let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
1039			let new_deposit = Self::calc_metadata_deposit(&name, &symbol);
1040
1041			if new_deposit > old_deposit {
1042				T::Currency::reserve(from, new_deposit - old_deposit)?;
1043			} else {
1044				T::Currency::unreserve(from, old_deposit - new_deposit);
1045			}
1046
1047			*metadata = Some(AssetMetadata {
1048				deposit: new_deposit,
1049				name: bounded_name,
1050				symbol: bounded_symbol,
1051				decimals,
1052				is_frozen: false,
1053			});
1054
1055			Self::deposit_event(Event::MetadataSet {
1056				asset_id: id,
1057				name,
1058				symbol,
1059				decimals,
1060				is_frozen: false,
1061			});
1062			Ok(())
1063		})
1064	}
1065
1066	/// Calculate the metadata deposit for the provided data.
1067	pub(super) fn calc_metadata_deposit(name: &[u8], symbol: &[u8]) -> DepositBalanceOf<T, I> {
1068		T::MetadataDepositPerByte::get()
1069			.saturating_mul(((name.len() + symbol.len()) as u32).into())
1070			.saturating_add(T::MetadataDepositBase::get())
1071	}
1072
1073	/// Returns all the non-zero balances for all assets of the given `account`.
1074	pub fn account_balances(account: T::AccountId) -> Vec<(T::AssetId, T::Balance)> {
1075		Asset::<T, I>::iter_keys()
1076			.filter_map(|id| {
1077				Self::maybe_balance(id.clone(), account.clone()).map(|balance| (id, balance))
1078			})
1079			.collect::<Vec<_>>()
1080	}
1081
1082	/// Reset the team for the asset with the given `id`.
1083	///
1084	/// ### Parameters
1085	/// - `id`: The identifier of the asset for which the team is being reset.
1086	/// - `owner`: The new `owner` account for the asset.
1087	/// - `admin`: The new `admin` account for the asset.
1088	/// - `issuer`: The new `issuer` account for the asset.
1089	/// - `freezer`: The new `freezer` account for the asset.
1090	pub(crate) fn do_reset_team(
1091		id: T::AssetId,
1092		owner: T::AccountId,
1093		admin: T::AccountId,
1094		issuer: T::AccountId,
1095		freezer: T::AccountId,
1096	) -> DispatchResult {
1097		let mut d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
1098		d.owner = owner;
1099		d.admin = admin;
1100		d.issuer = issuer;
1101		d.freezer = freezer;
1102		Asset::<T, I>::insert(&id, d);
1103		Ok(())
1104	}
1105}