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