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	pub(super) fn do_force_create(
759		id: T::AssetId,
760		owner: T::AccountId,
761		is_sufficient: bool,
762		min_balance: T::Balance,
763	) -> DispatchResult {
764		ensure!(!Asset::<T, I>::contains_key(&id), Error::<T, I>::InUse);
765		ensure!(!min_balance.is_zero(), Error::<T, I>::MinBalanceZero);
766		if let Some(next_id) = NextAssetId::<T, I>::get() {
767			ensure!(id == next_id, Error::<T, I>::BadAssetId);
768		}
769
770		Asset::<T, I>::insert(
771			&id,
772			AssetDetails {
773				owner: owner.clone(),
774				issuer: owner.clone(),
775				admin: owner.clone(),
776				freezer: owner.clone(),
777				supply: Zero::zero(),
778				deposit: Zero::zero(),
779				min_balance,
780				is_sufficient,
781				accounts: 0,
782				sufficients: 0,
783				approvals: 0,
784				status: AssetStatus::Live,
785			},
786		);
787		ensure!(T::CallbackHandle::created(&id, &owner).is_ok(), Error::<T, I>::CallbackFailed);
788		Self::deposit_event(Event::ForceCreated { asset_id: id, owner: owner.clone() });
789		Ok(())
790	}
791
792	/// Start the process of destroying an asset, by setting the asset status to `Destroying`, and
793	/// emitting the `DestructionStarted` event.
794	pub(super) fn do_start_destroy(
795		id: T::AssetId,
796		maybe_check_owner: Option<T::AccountId>,
797	) -> DispatchResult {
798		Asset::<T, I>::try_mutate_exists(id.clone(), |maybe_details| -> Result<(), DispatchError> {
799			let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
800			if let Some(check_owner) = maybe_check_owner {
801				ensure!(details.owner == check_owner, Error::<T, I>::NoPermission);
802			}
803
804			ensure!(!T::Holder::contains_holds(id.clone()), Error::<T, I>::ContainsHolds);
805			ensure!(!T::Freezer::contains_freezes(id.clone()), Error::<T, I>::ContainsFreezes);
806
807			details.status = AssetStatus::Destroying;
808
809			Self::deposit_event(Event::DestructionStarted { asset_id: id });
810			Ok(())
811		})
812	}
813
814	/// Destroy accounts associated with a given asset up to the max (T::RemoveItemsLimit).
815	///
816	/// Each call emits the `Event::DestroyedAccounts` event.
817	/// Returns the number of destroyed accounts.
818	pub(super) fn do_destroy_accounts(
819		id: T::AssetId,
820		max_items: u32,
821	) -> Result<u32, DispatchError> {
822		let mut dead_accounts: Vec<T::AccountId> = vec![];
823		let mut remaining_accounts = 0;
824		Asset::<T, I>::try_mutate_exists(&id, |maybe_details| -> Result<(), DispatchError> {
825			let mut details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
826			// Should only destroy accounts while the asset is in a destroying state
827			ensure!(details.status == AssetStatus::Destroying, Error::<T, I>::IncorrectStatus);
828
829			for (i, (who, mut v)) in Account::<T, I>::iter_prefix(&id).enumerate() {
830				if Self::ensure_account_can_die(id.clone(), &who).is_err() {
831					continue;
832				}
833				// unreserve the existence deposit if any
834				if let Some((depositor, deposit)) = v.reason.take_deposit_from() {
835					T::Currency::unreserve(&depositor, deposit);
836				} else if let Some(deposit) = v.reason.take_deposit() {
837					T::Currency::unreserve(&who, deposit);
838				}
839				if let Remove = Self::dead_account(&who, &mut details, &v.reason, false) {
840					Account::<T, I>::remove(&id, &who);
841					dead_accounts.push(who);
842				} else {
843					// deposit may have been released, need to update `Account`
844					Account::<T, I>::insert(&id, &who, v);
845					defensive!("destroy did not result in dead account?!");
846				}
847				if i + 1 >= (max_items as usize) {
848					break;
849				}
850			}
851			remaining_accounts = details.accounts;
852			Ok(())
853		})?;
854
855		for who in &dead_accounts {
856			T::Freezer::died(id.clone(), &who);
857			T::Holder::died(id.clone(), &who);
858		}
859
860		Self::deposit_event(Event::AccountsDestroyed {
861			asset_id: id,
862			accounts_destroyed: dead_accounts.len() as u32,
863			accounts_remaining: remaining_accounts as u32,
864		});
865		Ok(dead_accounts.len() as u32)
866	}
867
868	/// Destroy approvals associated with a given asset up to the max (T::RemoveItemsLimit).
869	///
870	/// Each call emits the `Event::DestroyedApprovals` event
871	/// Returns the number of destroyed approvals.
872	pub(super) fn do_destroy_approvals(
873		id: T::AssetId,
874		max_items: u32,
875	) -> Result<u32, DispatchError> {
876		let mut removed_approvals = 0;
877		Asset::<T, I>::try_mutate_exists(
878			id.clone(),
879			|maybe_details| -> Result<(), DispatchError> {
880				let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
881
882				// Should only destroy accounts while the asset is in a destroying state.
883				ensure!(details.status == AssetStatus::Destroying, Error::<T, I>::IncorrectStatus);
884
885				for ((owner, _), approval) in Approvals::<T, I>::drain_prefix((id.clone(),)) {
886					T::Currency::unreserve(&owner, approval.deposit);
887					removed_approvals = removed_approvals.saturating_add(1);
888					details.approvals = details.approvals.saturating_sub(1);
889					if removed_approvals >= max_items {
890						break;
891					}
892				}
893				Self::deposit_event(Event::ApprovalsDestroyed {
894					asset_id: id,
895					approvals_destroyed: removed_approvals as u32,
896					approvals_remaining: details.approvals as u32,
897				});
898				Ok(())
899			},
900		)?;
901		Ok(removed_approvals)
902	}
903
904	/// Complete destroying an asset and unreserve the deposit.
905	///
906	/// On success, the `Event::Destroyed` event is emitted.
907	pub(super) fn do_finish_destroy(id: T::AssetId) -> DispatchResult {
908		Asset::<T, I>::try_mutate_exists(id.clone(), |maybe_details| -> Result<(), DispatchError> {
909			let details = maybe_details.take().ok_or(Error::<T, I>::Unknown)?;
910			ensure!(details.status == AssetStatus::Destroying, Error::<T, I>::IncorrectStatus);
911			ensure!(details.accounts == 0, Error::<T, I>::InUse);
912			ensure!(details.approvals == 0, Error::<T, I>::InUse);
913			ensure!(T::CallbackHandle::destroyed(&id).is_ok(), Error::<T, I>::CallbackFailed);
914
915			let metadata = Metadata::<T, I>::take(&id);
916			T::Currency::unreserve(
917				&details.owner,
918				details.deposit.saturating_add(metadata.deposit),
919			);
920			Reserves::<T, I>::remove(&id);
921			Self::deposit_event(Event::Destroyed { asset_id: id });
922
923			Ok(())
924		})
925	}
926
927	/// Creates an approval from `owner` to spend `amount` of asset `id` tokens by 'delegate'
928	/// while reserving `T::ApprovalDeposit` from owner
929	///
930	/// If an approval already exists, the new amount is added to such existing approval
931	pub fn do_approve_transfer(
932		id: T::AssetId,
933		owner: &T::AccountId,
934		delegate: &T::AccountId,
935		amount: T::Balance,
936	) -> DispatchResult {
937		let mut d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
938		ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
939		Approvals::<T, I>::try_mutate(
940			(id.clone(), &owner, &delegate),
941			|maybe_approved| -> DispatchResult {
942				let mut approved = match maybe_approved.take() {
943					// an approval already exists and is being updated
944					Some(a) => a,
945					// a new approval is created
946					None => {
947						d.approvals.saturating_inc();
948						Default::default()
949					},
950				};
951				let deposit_required = T::ApprovalDeposit::get();
952				if approved.deposit < deposit_required {
953					T::Currency::reserve(owner, deposit_required - approved.deposit)?;
954					approved.deposit = deposit_required;
955				}
956				approved.amount = approved.amount.saturating_add(amount);
957				*maybe_approved = Some(approved);
958				Ok(())
959			},
960		)?;
961		Asset::<T, I>::insert(&id, d);
962		Self::deposit_event(Event::ApprovedTransfer {
963			asset_id: id,
964			source: owner.clone(),
965			delegate: delegate.clone(),
966			amount,
967		});
968
969		Ok(())
970	}
971
972	/// Cancels an existing approval from `owner` to `delegate` for asset `id`.
973	///
974	/// Removes the approval entry and unreserves the deposit. Emits `ApprovalCancelled`.
975	pub fn do_cancel_approval(
976		id: &T::AssetId,
977		owner: &T::AccountId,
978		delegate: &T::AccountId,
979	) -> DispatchResult {
980		let mut asset_details = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
981		ensure!(asset_details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
982
983		let approval =
984			Approvals::<T, I>::take((id.clone(), owner, delegate)).ok_or(Error::<T, I>::Unknown)?;
985		T::Currency::unreserve(owner, approval.deposit);
986
987		asset_details.approvals.saturating_dec();
988		Asset::<T, I>::insert(id, asset_details);
989
990		Self::deposit_event(Event::ApprovalCancelled {
991			asset_id: id.clone(),
992			owner: owner.clone(),
993			delegate: delegate.clone(),
994		});
995		Ok(())
996	}
997
998	/// Reduces the asset `id` balance of `owner` by some `amount` and increases the balance of
999	/// `dest` by (similar) amount, checking that 'delegate' has an existing approval from `owner`
1000	/// to spend`amount`.
1001	///
1002	/// Will fail if `amount` is greater than the approval from `owner` to 'delegate'
1003	/// Will unreserve the deposit from `owner` if the entire approved `amount` is spent by
1004	/// 'delegate'
1005	pub fn do_transfer_approved(
1006		id: T::AssetId,
1007		owner: &T::AccountId,
1008		delegate: &T::AccountId,
1009		destination: &T::AccountId,
1010		amount: T::Balance,
1011	) -> DispatchResult {
1012		let mut owner_died: Option<DeadConsequence> = None;
1013
1014		let d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
1015		ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
1016
1017		Approvals::<T, I>::try_mutate_exists(
1018			(id.clone(), &owner, delegate),
1019			|maybe_approved| -> DispatchResult {
1020				let mut approved = maybe_approved.take().ok_or(Error::<T, I>::Unapproved)?;
1021				let remaining =
1022					approved.amount.checked_sub(&amount).ok_or(Error::<T, I>::Unapproved)?;
1023
1024				let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false };
1025				owner_died =
1026					Self::transfer_and_die(id.clone(), owner, destination, amount, None, f)?.1;
1027
1028				if remaining.is_zero() {
1029					T::Currency::unreserve(owner, approved.deposit);
1030					Asset::<T, I>::mutate(id.clone(), |maybe_details| {
1031						if let Some(details) = maybe_details {
1032							details.approvals.saturating_dec();
1033						}
1034					});
1035				} else {
1036					approved.amount = remaining;
1037					*maybe_approved = Some(approved);
1038				}
1039				Ok(())
1040			},
1041		)?;
1042
1043		// Execute hook outside of `mutate`.
1044		if let Some(Remove) = owner_died {
1045			T::Freezer::died(id.clone(), owner);
1046			T::Holder::died(id, owner);
1047		}
1048		Ok(())
1049	}
1050
1051	/// Do set metadata
1052	pub(super) fn do_set_metadata(
1053		id: T::AssetId,
1054		from: &T::AccountId,
1055		name: Vec<u8>,
1056		symbol: Vec<u8>,
1057		decimals: u8,
1058	) -> DispatchResult {
1059		let bounded_name: BoundedVec<u8, T::StringLimit> =
1060			name.clone().try_into().map_err(|_| Error::<T, I>::BadMetadata)?;
1061		let bounded_symbol: BoundedVec<u8, T::StringLimit> =
1062			symbol.clone().try_into().map_err(|_| Error::<T, I>::BadMetadata)?;
1063
1064		let d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
1065		ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
1066		ensure!(from == &d.owner, Error::<T, I>::NoPermission);
1067
1068		Metadata::<T, I>::try_mutate_exists(id.clone(), |metadata| {
1069			ensure!(metadata.as_ref().map_or(true, |m| !m.is_frozen), Error::<T, I>::NoPermission);
1070
1071			let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
1072			let new_deposit = Self::calc_metadata_deposit(&name, &symbol);
1073
1074			if new_deposit > old_deposit {
1075				T::Currency::reserve(from, new_deposit - old_deposit)?;
1076			} else {
1077				T::Currency::unreserve(from, old_deposit - new_deposit);
1078			}
1079
1080			*metadata = Some(AssetMetadata {
1081				deposit: new_deposit,
1082				name: bounded_name,
1083				symbol: bounded_symbol,
1084				decimals,
1085				is_frozen: false,
1086			});
1087
1088			Self::deposit_event(Event::MetadataSet {
1089				asset_id: id,
1090				name,
1091				symbol,
1092				decimals,
1093				is_frozen: false,
1094			});
1095			Ok(())
1096		})
1097	}
1098
1099	/// Calculate the metadata deposit for the provided data.
1100	pub(super) fn calc_metadata_deposit(name: &[u8], symbol: &[u8]) -> DepositBalanceOf<T, I> {
1101		T::MetadataDepositPerByte::get()
1102			.saturating_mul(((name.len() + symbol.len()) as u32).into())
1103			.saturating_add(T::MetadataDepositBase::get())
1104	}
1105
1106	/// Returns all the non-zero balances for all assets of the given `account`.
1107	pub fn account_balances(account: T::AccountId) -> Vec<(T::AssetId, T::Balance)> {
1108		Asset::<T, I>::iter_keys()
1109			.filter_map(|id| {
1110				Self::maybe_balance(id.clone(), account.clone()).map(|balance| (id, balance))
1111			})
1112			.collect::<Vec<_>>()
1113	}
1114
1115	/// Reset the team for the asset with the given `id`.
1116	///
1117	/// ### Parameters
1118	/// - `id`: The identifier of the asset for which the team is being reset.
1119	/// - `owner`: The new `owner` account for the asset.
1120	/// - `admin`: The new `admin` account for the asset.
1121	/// - `issuer`: The new `issuer` account for the asset.
1122	/// - `freezer`: The new `freezer` account for the asset.
1123	pub(crate) fn do_reset_team(
1124		id: T::AssetId,
1125		owner: T::AccountId,
1126		admin: T::AccountId,
1127		issuer: T::AccountId,
1128		freezer: T::AccountId,
1129	) -> DispatchResult {
1130		let mut d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
1131		d.owner = owner;
1132		d.admin = admin;
1133		d.issuer = issuer;
1134		d.freezer = freezer;
1135		Asset::<T, I>::insert(&id, d);
1136		Ok(())
1137	}
1138
1139	/// Helper function for setting reserves to be used in benchmarking and migrations.
1140	/// Does not check validity of asset id, caller should check it.
1141	pub fn unchecked_update_reserves(
1142		id: T::AssetId,
1143		reserves: BoundedVec<T::ReserveData, ConstU32<MAX_RESERVES>>,
1144	) -> Result<(), Error<T, I>> {
1145		if reserves.is_empty() {
1146			Reserves::<T, I>::remove(&id);
1147			Self::deposit_event(Event::ReservesRemoved { asset_id: id });
1148		} else {
1149			let reserves_vec = reserves.clone().into_inner();
1150			Reserves::<T, I>::set(&id, reserves);
1151			Self::deposit_event(Event::ReservesUpdated { asset_id: id, reserves: reserves_vec });
1152		}
1153		Ok(())
1154	}
1155}