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