referrerpolicy=no-referrer-when-downgrade

pallet_balances/
impl_fungible.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//! Implementation of `fungible` traits for Balances pallet.
19use super::*;
20use frame_support::traits::{
21	tokens::{
22		Fortitude,
23		Preservation::{self, Preserve, Protect},
24		Provenance::{self, Minted},
25	},
26	AccountTouch,
27};
28
29impl<T: Config<I>, I: 'static> fungible::Inspect<T::AccountId> for Pallet<T, I> {
30	type Balance = T::Balance;
31
32	fn total_issuance() -> Self::Balance {
33		TotalIssuance::<T, I>::get()
34	}
35	fn active_issuance() -> Self::Balance {
36		TotalIssuance::<T, I>::get().saturating_sub(InactiveIssuance::<T, I>::get())
37	}
38	fn minimum_balance() -> Self::Balance {
39		T::ExistentialDeposit::get()
40	}
41	fn total_balance(who: &T::AccountId) -> Self::Balance {
42		Self::account(who).total()
43	}
44	fn balance(who: &T::AccountId) -> Self::Balance {
45		Self::account(who).free
46	}
47	fn reducible_balance(
48		who: &T::AccountId,
49		preservation: Preservation,
50		force: Fortitude,
51	) -> Self::Balance {
52		let a = Self::account(who);
53		let mut untouchable = Zero::zero();
54		if force == Polite {
55			// Frozen balance applies to total. Anything on hold therefore gets discounted from the
56			// limit given by the freezes.
57			untouchable = a.frozen.saturating_sub(a.reserved);
58		}
59		// If we want to keep our provider ref..
60		if preservation == Preserve
61			// ..or we don't want the account to die and our provider ref is needed for it to live..
62			|| preservation == Protect && !a.free.is_zero() &&
63				frame_system::Pallet::<T>::providers(who) == 1
64			// ..or we don't care about the account dying but our provider ref is required..
65			|| preservation == Expendable && !a.free.is_zero() &&
66				!frame_system::Pallet::<T>::can_dec_provider(who)
67		{
68			// ..then the ED needed..
69			untouchable = untouchable.max(T::ExistentialDeposit::get());
70		}
71		// Liquid balance is what is neither on hold nor frozen/required for provider.
72		a.free.saturating_sub(untouchable)
73	}
74	fn can_deposit(
75		who: &T::AccountId,
76		amount: Self::Balance,
77		provenance: Provenance,
78	) -> DepositConsequence {
79		if amount.is_zero() {
80			return DepositConsequence::Success;
81		}
82
83		if provenance == Minted && TotalIssuance::<T, I>::get().checked_add(&amount).is_none() {
84			return DepositConsequence::Overflow;
85		}
86
87		let account = Self::account(who);
88		let new_free = match account.free.checked_add(&amount) {
89			None => return DepositConsequence::Overflow,
90			Some(x) if x < T::ExistentialDeposit::get() => return DepositConsequence::BelowMinimum,
91			Some(x) => x,
92		};
93
94		match account.reserved.checked_add(&new_free) {
95			Some(_) => {},
96			None => return DepositConsequence::Overflow,
97		};
98
99		// NOTE: We assume that we are a provider, so don't need to do any checks in the
100		// case of account creation.
101
102		DepositConsequence::Success
103	}
104	fn can_withdraw(
105		who: &T::AccountId,
106		amount: Self::Balance,
107	) -> WithdrawConsequence<Self::Balance> {
108		if amount.is_zero() {
109			return WithdrawConsequence::Success;
110		}
111
112		if TotalIssuance::<T, I>::get().checked_sub(&amount).is_none() {
113			return WithdrawConsequence::Underflow;
114		}
115
116		let account = Self::account(who);
117		let new_free_balance = match account.free.checked_sub(&amount) {
118			Some(x) => x,
119			None => return WithdrawConsequence::BalanceLow,
120		};
121
122		let liquid = Self::reducible_balance(who, Expendable, Polite);
123		if amount > liquid {
124			return WithdrawConsequence::Frozen;
125		}
126
127		// Provider restriction - total account balance cannot be reduced to zero if it cannot
128		// sustain the loss of a provider reference.
129		// NOTE: This assumes that the pallet is a provider (which is true). Is this ever changes,
130		// then this will need to adapt accordingly.
131		let ed = T::ExistentialDeposit::get();
132		let success = if new_free_balance < ed {
133			if frame_system::Pallet::<T>::can_dec_provider(who) {
134				WithdrawConsequence::ReducedToZero(new_free_balance)
135			} else {
136				return WithdrawConsequence::WouldDie;
137			}
138		} else {
139			WithdrawConsequence::Success
140		};
141
142		let new_total_balance = new_free_balance.saturating_add(account.reserved);
143
144		// Eventual free funds must be no less than the frozen balance.
145		if new_total_balance < account.frozen {
146			return WithdrawConsequence::Frozen;
147		}
148
149		success
150	}
151}
152
153impl<T: Config<I>, I: 'static> fungible::Unbalanced<T::AccountId> for Pallet<T, I> {
154	fn handle_dust(dust: fungible::Dust<T::AccountId, Self>) {
155		T::DustRemoval::on_unbalanced(dust.into_credit());
156	}
157	fn write_balance(
158		who: &T::AccountId,
159		amount: Self::Balance,
160	) -> Result<Option<Self::Balance>, DispatchError> {
161		let max_reduction =
162			<Self as fungible::Inspect<_>>::reducible_balance(who, Expendable, Force);
163		let (result, maybe_dust) = Self::mutate_account(who, false, |account| -> DispatchResult {
164			// Make sure the reduction (if there is one) is no more than the maximum allowed.
165			let reduction = account.free.saturating_sub(amount);
166			ensure!(reduction <= max_reduction, Error::<T, I>::InsufficientBalance);
167
168			account.free = amount;
169			Ok(())
170		})?;
171		result?;
172		Ok(maybe_dust)
173	}
174
175	fn set_total_issuance(amount: Self::Balance) {
176		TotalIssuance::<T, I>::mutate(|t| *t = amount);
177	}
178
179	fn deactivate(amount: Self::Balance) {
180		InactiveIssuance::<T, I>::mutate(|b| {
181			let active = TotalIssuance::<T, I>::get().saturating_sub(*b);
182			// Only cap the delta and don't re-cap the existing inactive issuance.
183			let increase = amount.min(active);
184
185			*b = b.saturating_add(increase);
186		});
187	}
188
189	fn reactivate(amount: Self::Balance) {
190		InactiveIssuance::<T, I>::mutate(|b| b.saturating_reduce(amount));
191	}
192}
193
194impl<T: Config<I>, I: 'static> fungible::Mutate<T::AccountId> for Pallet<T, I> {
195	fn done_mint_into(who: &T::AccountId, amount: Self::Balance) {
196		Self::deposit_event(Event::<T, I>::Minted { who: who.clone(), amount });
197	}
198	fn done_burn_from(who: &T::AccountId, amount: Self::Balance) {
199		Self::deposit_event(Event::<T, I>::Burned { who: who.clone(), amount });
200	}
201	fn done_shelve(who: &T::AccountId, amount: Self::Balance) {
202		Self::deposit_event(Event::<T, I>::Suspended { who: who.clone(), amount });
203	}
204	fn done_restore(who: &T::AccountId, amount: Self::Balance) {
205		Self::deposit_event(Event::<T, I>::Restored { who: who.clone(), amount });
206	}
207	fn done_transfer(source: &T::AccountId, dest: &T::AccountId, amount: Self::Balance) {
208		Self::deposit_event(Event::<T, I>::Transfer {
209			from: source.clone(),
210			to: dest.clone(),
211			amount,
212		});
213	}
214}
215
216impl<T: Config<I>, I: 'static> fungible::MutateHold<T::AccountId> for Pallet<T, I> {
217	fn done_hold(reason: &Self::Reason, who: &T::AccountId, amount: Self::Balance) {
218		Self::deposit_event(Event::<T, I>::Held { reason: *reason, who: who.clone(), amount });
219	}
220	fn done_release(reason: &Self::Reason, who: &T::AccountId, amount: Self::Balance) {
221		Self::deposit_event(Event::<T, I>::Released { reason: *reason, who: who.clone(), amount });
222	}
223	fn done_burn_held(reason: &Self::Reason, who: &T::AccountId, amount: Self::Balance) {
224		Self::deposit_event(Event::<T, I>::BurnedHeld {
225			reason: *reason,
226			who: who.clone(),
227			amount,
228		});
229	}
230	fn done_transfer_on_hold(
231		reason: &Self::Reason,
232		source: &T::AccountId,
233		dest: &T::AccountId,
234		amount: Self::Balance,
235	) {
236		// Emit on-hold transfer event
237		Self::deposit_event(Event::<T, I>::TransferOnHold {
238			reason: *reason,
239			source: source.clone(),
240			dest: dest.clone(),
241			amount,
242		});
243	}
244	fn done_transfer_and_hold(
245		reason: &Self::Reason,
246		source: &T::AccountId,
247		dest: &T::AccountId,
248		transferred: Self::Balance,
249	) {
250		Self::deposit_event(Event::<T, I>::TransferAndHold {
251			reason: *reason,
252			source: source.clone(),
253			dest: dest.clone(),
254			transferred,
255		})
256	}
257}
258
259impl<T: Config<I>, I: 'static> fungible::InspectHold<T::AccountId> for Pallet<T, I> {
260	type Reason = T::RuntimeHoldReason;
261
262	fn total_balance_on_hold(who: &T::AccountId) -> T::Balance {
263		Self::account(who).reserved
264	}
265	fn reducible_total_balance_on_hold(who: &T::AccountId, force: Fortitude) -> Self::Balance {
266		// The total balance must never drop below the freeze requirements if we're not forcing:
267		let a = Self::account(who);
268		let unavailable = if force == Force {
269			Self::Balance::zero()
270		} else {
271			// The freeze lock applies to the total balance, so we can discount the free balance
272			// from the amount which the total reserved balance must provide to satisfy it.
273			a.frozen.saturating_sub(a.free)
274		};
275		a.reserved.saturating_sub(unavailable)
276	}
277	fn balance_on_hold(reason: &Self::Reason, who: &T::AccountId) -> T::Balance {
278		Holds::<T, I>::get(who)
279			.iter()
280			.find(|x| &x.id == reason)
281			.map_or_else(Zero::zero, |x| x.amount)
282	}
283	fn hold_available(reason: &Self::Reason, who: &T::AccountId) -> bool {
284		if frame_system::Pallet::<T>::providers(who) == 0 {
285			return false;
286		}
287		let holds = Holds::<T, I>::get(who);
288		if holds.is_full() && !holds.iter().any(|x| &x.id == reason) {
289			return false;
290		}
291		true
292	}
293}
294
295impl<T: Config<I>, I: 'static> fungible::UnbalancedHold<T::AccountId> for Pallet<T, I> {
296	fn set_balance_on_hold(
297		reason: &Self::Reason,
298		who: &T::AccountId,
299		amount: Self::Balance,
300	) -> DispatchResult {
301		let mut new_account = Self::account(who);
302		let mut holds = Holds::<T, I>::get(who);
303		let mut increase = true;
304		let mut delta = amount;
305
306		if let Some(item) = holds.iter_mut().find(|x| &x.id == reason) {
307			delta = item.amount.max(amount) - item.amount.min(amount);
308			increase = amount > item.amount;
309			item.amount = amount;
310			holds.retain(|x| !x.amount.is_zero());
311		} else {
312			if !amount.is_zero() {
313				holds
314					.try_push(IdAmount { id: *reason, amount })
315					.map_err(|_| Error::<T, I>::TooManyHolds)?;
316			}
317		}
318
319		new_account.reserved = if increase {
320			new_account.reserved.checked_add(&delta).ok_or(ArithmeticError::Overflow)?
321		} else {
322			new_account.reserved.checked_sub(&delta).ok_or(ArithmeticError::Underflow)?
323		};
324
325		let (result, maybe_dust) =
326			Self::try_mutate_account(who, false, |a, _| -> DispatchResult {
327				*a = new_account;
328				Ok(())
329			})?;
330
331		if holds.is_empty() {
332			Holds::<T, I>::remove(who)
333		} else {
334			Holds::<T, I>::insert(who, holds)
335		}
336
337		if let Some(dust) = maybe_dust {
338			<Self as fungible::Unbalanced<_>>::handle_raw_dust(dust);
339		}
340
341		Ok(result)
342	}
343}
344
345impl<T: Config<I>, I: 'static> fungible::InspectFreeze<T::AccountId> for Pallet<T, I> {
346	type Id = T::RuntimeFreezeReason;
347
348	fn balance_frozen(id: &Self::Id, who: &T::AccountId) -> Self::Balance {
349		let locks = Freezes::<T, I>::get(who);
350		locks.into_iter().find(|l| &l.id == id).map_or(Zero::zero(), |l| l.amount)
351	}
352
353	fn can_freeze(id: &Self::Id, who: &T::AccountId) -> bool {
354		let l = Freezes::<T, I>::get(who);
355		!l.is_full() || l.iter().any(|x| &x.id == id)
356	}
357}
358
359impl<T: Config<I>, I: 'static> fungible::MutateFreeze<T::AccountId> for Pallet<T, I> {
360	fn set_freeze(id: &Self::Id, who: &T::AccountId, amount: Self::Balance) -> DispatchResult {
361		if amount.is_zero() {
362			return Self::thaw(id, who);
363		}
364		let mut locks = Freezes::<T, I>::get(who);
365		if let Some(i) = locks.iter_mut().find(|x| &x.id == id) {
366			i.amount = amount;
367		} else {
368			locks
369				.try_push(IdAmount { id: *id, amount })
370				.map_err(|_| Error::<T, I>::TooManyFreezes)?;
371		}
372		Self::update_freezes(who, locks.as_bounded_slice())
373	}
374
375	fn extend_freeze(id: &Self::Id, who: &T::AccountId, amount: Self::Balance) -> DispatchResult {
376		if amount.is_zero() {
377			return Ok(());
378		}
379		let mut locks = Freezes::<T, I>::get(who);
380		if let Some(i) = locks.iter_mut().find(|x| &x.id == id) {
381			i.amount = i.amount.max(amount);
382		} else {
383			locks
384				.try_push(IdAmount { id: *id, amount })
385				.map_err(|_| Error::<T, I>::TooManyFreezes)?;
386		}
387		Self::update_freezes(who, locks.as_bounded_slice())
388	}
389
390	fn thaw(id: &Self::Id, who: &T::AccountId) -> DispatchResult {
391		let mut locks = Freezes::<T, I>::get(who);
392		locks.retain(|l| &l.id != id);
393		Self::update_freezes(who, locks.as_bounded_slice())
394	}
395}
396
397impl<T: Config<I>, I: 'static> fungible::Balanced<T::AccountId> for Pallet<T, I> {
398	type OnDropCredit = NegativeImbalance<T, I>;
399	type OnDropDebt = PositiveImbalance<T, I>;
400
401	fn done_deposit(who: &T::AccountId, amount: Self::Balance) {
402		Self::deposit_event(Event::<T, I>::Deposit { who: who.clone(), amount });
403	}
404	fn done_withdraw(who: &T::AccountId, amount: Self::Balance) {
405		Self::deposit_event(Event::<T, I>::Withdraw { who: who.clone(), amount });
406	}
407	fn done_issue(amount: Self::Balance) {
408		if !amount.is_zero() {
409			Self::deposit_event(Event::<T, I>::Issued { amount });
410		}
411	}
412	fn done_rescind(amount: Self::Balance) {
413		Self::deposit_event(Event::<T, I>::Rescinded { amount });
414	}
415}
416
417impl<T: Config<I>, I: 'static> fungible::BalancedHold<T::AccountId> for Pallet<T, I> {}
418
419impl<T: Config<I>, I: 'static>
420	fungible::hold::DoneSlash<T::RuntimeHoldReason, T::AccountId, T::Balance> for Pallet<T, I>
421{
422	fn done_slash(reason: &T::RuntimeHoldReason, who: &T::AccountId, amount: T::Balance) {
423		T::DoneSlashHandler::done_slash(reason, who, amount);
424	}
425}
426
427impl<T: Config<I>, I: 'static> AccountTouch<(), T::AccountId> for Pallet<T, I> {
428	type Balance = T::Balance;
429	fn deposit_required(_: ()) -> Self::Balance {
430		Self::Balance::zero()
431	}
432	fn should_touch(_: (), _: &T::AccountId) -> bool {
433		false
434	}
435	fn touch(_: (), _: &T::AccountId, _: &T::AccountId) -> DispatchResult {
436		Ok(())
437	}
438}