referrerpolicy=no-referrer-when-downgrade

frame_support/traits/tokens/fungible/conformance_tests/
inspect_mutate.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
18use crate::traits::{
19	fungible::{Inspect, Mutate},
20	tokens::{
21		DepositConsequence, Fortitude, Precision, Preservation, Provenance, WithdrawConsequence,
22	},
23};
24use core::fmt::Debug;
25use sp_arithmetic::traits::AtLeast8BitUnsigned;
26use sp_runtime::traits::{Bounded, Zero};
27
28/// Test the `mint_into` function for successful token minting.
29///
30/// This test checks the `mint_into` function in the `Mutate` trait implementation for type `T`.
31/// It ensures that account balances and total issuance values are updated correctly after minting
32/// tokens into two distinct accounts.
33///
34/// # Type Parameters
35///
36/// ```text
37/// - `T`: Implements `Mutate<AccountId>`.
38/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`.
39/// ```
40pub fn mint_into_success<T, AccountId>(_dust_trap: Option<AccountId>)
41where
42	T: Mutate<AccountId>,
43	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
44	AccountId: AtLeast8BitUnsigned,
45{
46	let initial_total_issuance = T::total_issuance();
47	let initial_active_issuance = T::active_issuance();
48	let account_0 = AccountId::from(0);
49	let account_1 = AccountId::from(1);
50
51	// Test: Mint an amount into each account
52	let amount_0 = T::minimum_balance();
53	let amount_1 = T::minimum_balance() + 5.into();
54	T::mint_into(&account_0, amount_0).unwrap();
55	T::mint_into(&account_1, amount_1).unwrap();
56
57	// Verify: Account balances are updated correctly
58	assert_eq!(T::total_balance(&account_0), amount_0);
59	assert_eq!(T::total_balance(&account_1), amount_1);
60	assert_eq!(T::balance(&account_0), amount_0);
61	assert_eq!(T::balance(&account_1), amount_1);
62
63	// Verify: Total issuance is updated correctly
64	assert_eq!(T::total_issuance(), initial_total_issuance + amount_0 + amount_1);
65	assert_eq!(T::active_issuance(), initial_active_issuance + amount_0 + amount_1);
66}
67
68/// Test the `mint_into` function for overflow prevention.
69///
70/// This test ensures that minting tokens beyond the maximum balance value for an account
71/// returns an error and does not change the account balance or total issuance values.
72///
73/// # Type Parameters
74///
75/// ```text
76/// - `T`: Implements `Mutate<AccountId>`.
77/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`.
78/// ```
79pub fn mint_into_overflow<T, AccountId>(_dust_trap: Option<AccountId>)
80where
81	T: Mutate<AccountId>,
82	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
83	AccountId: AtLeast8BitUnsigned,
84{
85	let initial_total_issuance = T::total_issuance();
86	let initial_active_issuance = T::active_issuance();
87	let account = AccountId::from(10);
88	let amount = T::Balance::max_value() - 5.into() - initial_total_issuance;
89
90	// Mint just below the maximum balance
91	T::mint_into(&account, amount).unwrap();
92
93	// Verify: Minting beyond the maximum balance value returns an Err
94	T::mint_into(&account, 10.into()).unwrap_err();
95
96	// Verify: The balance did not change
97	assert_eq!(T::total_balance(&account), amount);
98	assert_eq!(T::balance(&account), amount);
99
100	// Verify: The total issuance did not change
101	assert_eq!(T::total_issuance(), initial_total_issuance + amount);
102	assert_eq!(T::active_issuance(), initial_active_issuance + amount);
103}
104
105/// Test the `mint_into` function for handling balances below the minimum value.
106///
107/// This test verifies that minting tokens below the minimum balance for an account
108/// returns an error and has no impact on the account balance or total issuance values.
109///
110/// # Type Parameters
111///
112/// ```text
113/// - `T`: Implements `Mutate<AccountId>`.
114/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`.
115/// ```
116pub fn mint_into_below_minimum<T, AccountId>(_dust_trap: Option<AccountId>)
117where
118	T: Mutate<AccountId>,
119	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
120	AccountId: AtLeast8BitUnsigned,
121{
122	// Skip if there is no minimum balance
123	if T::minimum_balance() == T::Balance::zero() {
124		return
125	}
126
127	let initial_total_issuance = T::total_issuance();
128	let initial_active_issuance = T::active_issuance();
129	let account = AccountId::from(10);
130	let amount = T::minimum_balance() - 1.into();
131
132	// Verify: Minting below the minimum balance returns Err
133	T::mint_into(&account, amount).unwrap_err();
134
135	// Verify: noop
136	assert_eq!(T::total_balance(&account), T::Balance::zero());
137	assert_eq!(T::balance(&account), T::Balance::zero());
138	assert_eq!(T::total_issuance(), initial_total_issuance);
139	assert_eq!(T::active_issuance(), initial_active_issuance);
140}
141
142/// Test the `burn_from` function for successfully burning an exact amount of tokens.
143///
144/// This test checks that the `burn_from` function with `Precision::Exact` correctly
145/// reduces the account balance and total issuance values by the burned amount.
146///
147/// # Type Parameters
148///
149/// ```text
150/// - `T`: Implements `Mutate` for `AccountId`.
151/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`.
152/// ```
153pub fn burn_from_exact_success<T, AccountId>(_dust_trap: Option<AccountId>)
154where
155	T: Mutate<AccountId>,
156	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
157	AccountId: AtLeast8BitUnsigned,
158{
159	let initial_total_issuance = T::total_issuance();
160	let initial_active_issuance = T::active_issuance();
161
162	// Setup account
163	let account = AccountId::from(5);
164	let initial_balance = T::minimum_balance() + 10.into();
165	T::mint_into(&account, initial_balance).unwrap();
166
167	// Test: Burn an exact amount from the account
168	let amount_to_burn = T::Balance::from(5);
169	let preservation = Preservation::Expendable;
170	let precision = Precision::Exact;
171	let force = Fortitude::Polite;
172	T::burn_from(&account, amount_to_burn, preservation, precision, force).unwrap();
173
174	// Verify: The balance and total issuance should be reduced by the burned amount
175	assert_eq!(T::balance(&account), initial_balance - amount_to_burn);
176	assert_eq!(T::total_balance(&account), initial_balance - amount_to_burn);
177	assert_eq!(T::total_issuance(), initial_total_issuance + initial_balance - amount_to_burn);
178	assert_eq!(T::active_issuance(), initial_active_issuance + initial_balance - amount_to_burn);
179}
180
181/// Test the `burn_from` function for successfully burning tokens with a best-effort approach.
182///
183/// This test verifies that the `burn_from` function with `Precision::BestEffort` correctly
184/// reduces the account balance and total issuance values by the reducible balance when
185/// attempting to burn an amount greater than the reducible balance.
186///
187/// # Type Parameters
188///
189/// ```text
190/// - `T`: Implements `Mutate` for `AccountId`.
191/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`.
192/// ```
193pub fn burn_from_best_effort_success<T, AccountId>(_dust_trap: Option<AccountId>)
194where
195	T: Mutate<AccountId>,
196	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
197	AccountId: AtLeast8BitUnsigned,
198{
199	let initial_total_issuance = T::total_issuance();
200	let initial_active_issuance = T::active_issuance();
201
202	// Setup account
203	let account = AccountId::from(5);
204	let initial_balance = T::minimum_balance() + 10.into();
205	T::mint_into(&account, initial_balance).unwrap();
206
207	// Get reducible balance
208	let force = Fortitude::Polite;
209	let reducible_balance = T::reducible_balance(&account, Preservation::Expendable, force);
210
211	// Test: Burn a best effort amount from the account that is greater than the reducible balance
212	let amount_to_burn = reducible_balance + 5.into();
213	let preservation = Preservation::Expendable;
214	let precision = Precision::BestEffort;
215	assert!(amount_to_burn > reducible_balance);
216	assert!(amount_to_burn > T::balance(&account));
217	T::burn_from(&account, amount_to_burn, preservation, precision, force).unwrap();
218
219	// Verify: The balance and total issuance should be reduced by the reducible_balance
220	assert_eq!(T::balance(&account), initial_balance - reducible_balance);
221	assert_eq!(T::total_balance(&account), initial_balance - reducible_balance);
222	assert_eq!(T::total_issuance(), initial_total_issuance + initial_balance - reducible_balance);
223	assert_eq!(T::active_issuance(), initial_active_issuance + initial_balance - reducible_balance);
224}
225
226/// Test the `burn_from` function for handling insufficient funds with `Precision::Exact`.
227///
228/// This test verifies that burning an amount greater than the account's balance with
229/// `Precision::Exact` returns an error and does not change the account balance or total issuance
230/// values.
231///
232/// # Type Parameters
233///
234/// ```text
235/// - `T`: Implements `Mutate<AccountId>`.
236/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`.
237/// ```
238pub fn burn_from_exact_insufficient_funds<T, AccountId>(_dust_trap: Option<AccountId>)
239where
240	T: Mutate<AccountId>,
241	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
242	AccountId: AtLeast8BitUnsigned,
243{
244	// Set up the initial conditions and parameters for the test
245	let account = AccountId::from(5);
246	let initial_balance = T::minimum_balance() + 10.into();
247	T::mint_into(&account, initial_balance).unwrap();
248	let initial_total_issuance = T::total_issuance();
249	let initial_active_issuance = T::active_issuance();
250
251	// Verify: Burn an amount greater than the account's balance with Exact precision returns Err
252	let amount_to_burn = initial_balance + 10.into();
253	let preservation = Preservation::Expendable;
254	let precision = Precision::Exact;
255	let force = Fortitude::Polite;
256	T::burn_from(&account, amount_to_burn, preservation, precision, force).unwrap_err();
257
258	// Verify: The balance and total issuance should remain unchanged
259	assert_eq!(T::balance(&account), initial_balance);
260	assert_eq!(T::total_balance(&account), initial_balance);
261	assert_eq!(T::total_issuance(), initial_total_issuance);
262	assert_eq!(T::active_issuance(), initial_active_issuance);
263}
264
265/// Test the `restore` function for successful restoration.
266///
267/// This test verifies that restoring an amount into each account updates their balances and the
268/// total issuance values correctly.
269///
270/// # Type Parameters
271///
272/// ```text
273/// - `T`: Implements `Mutate<AccountId>`.
274/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`.
275/// ```
276pub fn restore_success<T, AccountId>(_dust_trap: Option<AccountId>)
277where
278	T: Mutate<AccountId>,
279	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
280	AccountId: AtLeast8BitUnsigned,
281{
282	let account_0 = AccountId::from(0);
283	let account_1 = AccountId::from(1);
284
285	// Test: Restore an amount into each account
286	let amount_0 = T::minimum_balance();
287	let amount_1 = T::minimum_balance() + 5.into();
288	let initial_total_issuance = T::total_issuance();
289	let initial_active_issuance = T::active_issuance();
290	T::restore(&account_0, amount_0).unwrap();
291	T::restore(&account_1, amount_1).unwrap();
292
293	// Verify: Account balances are updated correctly
294	assert_eq!(T::total_balance(&account_0), amount_0);
295	assert_eq!(T::total_balance(&account_1), amount_1);
296	assert_eq!(T::balance(&account_0), amount_0);
297	assert_eq!(T::balance(&account_1), amount_1);
298
299	// Verify: Total issuance is updated correctly
300	assert_eq!(T::total_issuance(), initial_total_issuance + amount_0 + amount_1);
301	assert_eq!(T::active_issuance(), initial_active_issuance + amount_0 + amount_1);
302}
303
304/// Test the `restore` function for handling balance overflow.
305///
306/// This test verifies that restoring an amount beyond the maximum balance returns an error and
307/// does not change the account balance or total issuance values.
308///
309/// # Type Parameters
310///
311/// ```text
312/// - `T`: Implements `Mutate<AccountId>`.
313/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`.
314/// ```
315pub fn restore_overflow<T, AccountId>(_dust_trap: Option<AccountId>)
316where
317	T: Mutate<AccountId>,
318	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
319	AccountId: AtLeast8BitUnsigned,
320{
321	let initial_total_issuance = T::total_issuance();
322	let initial_active_issuance = T::active_issuance();
323	let account = AccountId::from(10);
324	let amount = T::Balance::max_value() - 5.into() - initial_total_issuance;
325
326	// Restore just below the maximum balance
327	T::restore(&account, amount).unwrap();
328
329	// Verify: Restoring beyond the maximum balance returns an Err
330	T::restore(&account, 10.into()).unwrap_err();
331
332	// Verify: The balance and total issuance did not change
333	assert_eq!(T::total_balance(&account), amount);
334	assert_eq!(T::balance(&account), amount);
335	assert_eq!(T::total_issuance(), initial_total_issuance + amount);
336	assert_eq!(T::active_issuance(), initial_active_issuance + amount);
337}
338
339/// Test the `restore` function for handling restoration below the minimum balance.
340///
341/// This test verifies that restoring an amount below the minimum balance returns an error and
342/// does not change the account balance or total issuance values.
343///
344/// # Type Parameters
345///
346/// ```text
347/// - `T`: Implements `Mutate<AccountId>`.
348/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`.
349/// ```
350pub fn restore_below_minimum<T, AccountId>(_dust_trap: Option<AccountId>)
351where
352	T: Mutate<AccountId>,
353	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
354	AccountId: AtLeast8BitUnsigned,
355{
356	// Skip if there is no minimum balance
357	if T::minimum_balance() == T::Balance::zero() {
358		return
359	}
360
361	let account = AccountId::from(10);
362	let amount = T::minimum_balance() - 1.into();
363	let initial_total_issuance = T::total_issuance();
364	let initial_active_issuance = T::active_issuance();
365
366	// Verify: Restoring below the minimum balance returns Err
367	T::restore(&account, amount).unwrap_err();
368
369	// Verify: noop
370	assert_eq!(T::total_balance(&account), T::Balance::zero());
371	assert_eq!(T::balance(&account), T::Balance::zero());
372	assert_eq!(T::total_issuance(), initial_total_issuance);
373	assert_eq!(T::active_issuance(), initial_active_issuance);
374}
375
376/// Test the `shelve` function for successful shelving.
377///
378/// This test verifies that shelving an amount from an account reduces the account balance and
379/// total issuance values by the shelved amount.
380///
381/// # Type Parameters
382///
383/// ```text
384/// - `T`: Implements `Mutate<AccountId>`.
385/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`.
386/// ```
387pub fn shelve_success<T, AccountId>(_dust_trap: Option<AccountId>)
388where
389	T: Mutate<AccountId>,
390	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
391	AccountId: AtLeast8BitUnsigned,
392{
393	let initial_total_issuance = T::total_issuance();
394	let initial_active_issuance = T::active_issuance();
395
396	// Setup account
397	let account = AccountId::from(5);
398	let initial_balance = T::minimum_balance() + 10.into();
399
400	T::restore(&account, initial_balance).unwrap();
401
402	// Test: Shelve an amount from the account
403	let amount_to_shelve = T::Balance::from(5);
404	T::shelve(&account, amount_to_shelve).unwrap();
405
406	// Verify: The balance and total issuance should be reduced by the shelved amount
407	assert_eq!(T::balance(&account), initial_balance - amount_to_shelve);
408	assert_eq!(T::total_balance(&account), initial_balance - amount_to_shelve);
409	assert_eq!(T::total_issuance(), initial_total_issuance + initial_balance - amount_to_shelve);
410	assert_eq!(T::active_issuance(), initial_active_issuance + initial_balance - amount_to_shelve);
411}
412
413/// Test the `shelve` function for handling insufficient funds.
414///
415/// This test verifies that attempting to shelve an amount greater than the account's balance
416/// returns an error and does not change the account balance or total issuance values.
417///
418/// # Type Parameters
419///
420/// ```text
421/// - `T`: Implements `Mutate<AccountId>`.
422/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`.
423/// ```
424pub fn shelve_insufficient_funds<T, AccountId>(_dust_trap: Option<AccountId>)
425where
426	T: Mutate<AccountId>,
427	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
428	AccountId: AtLeast8BitUnsigned,
429{
430	let initial_total_issuance = T::total_issuance();
431	let initial_active_issuance = T::active_issuance();
432
433	// Set up the initial conditions and parameters for the test
434	let account = AccountId::from(5);
435	let initial_balance = T::minimum_balance() + 10.into();
436	T::restore(&account, initial_balance).unwrap();
437
438	// Verify: Shelving greater than the balance with Exact precision returns Err
439	let amount_to_shelve = initial_balance + 10.into();
440	T::shelve(&account, amount_to_shelve).unwrap_err();
441
442	// Verify: The balance and total issuance should remain unchanged
443	assert_eq!(T::balance(&account), initial_balance);
444	assert_eq!(T::total_balance(&account), initial_balance);
445	assert_eq!(T::total_issuance(), initial_total_issuance + initial_balance);
446	assert_eq!(T::active_issuance(), initial_active_issuance + initial_balance);
447}
448
449/// Test the `transfer` function for a successful transfer.
450///
451/// This test verifies that transferring an amount between two accounts with
452/// `Preservation::Expendable` updates the account balances and maintains the total issuance and
453/// active issuance values.
454///
455/// # Type Parameters
456///
457/// ```text
458/// - `T`: Implements `Mutate<AccountId>`.
459/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`.
460/// ```
461pub fn transfer_success<T, AccountId>(_dust_trap: Option<AccountId>)
462where
463	T: Mutate<AccountId>,
464	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
465	AccountId: AtLeast8BitUnsigned,
466{
467	let initial_total_issuance = T::total_issuance();
468	let initial_active_issuance = T::active_issuance();
469	let account_0 = AccountId::from(0);
470	let account_1 = AccountId::from(1);
471	let initial_balance = T::minimum_balance() + 10.into();
472	T::set_balance(&account_0, initial_balance);
473	T::set_balance(&account_1, initial_balance);
474
475	// Test: Transfer an amount from account_0 to account_1
476	let transfer_amount = T::Balance::from(3);
477	T::transfer(&account_0, &account_1, transfer_amount, Preservation::Expendable).unwrap();
478
479	// Verify: Account balances are updated correctly
480	assert_eq!(T::total_balance(&account_0), initial_balance - transfer_amount);
481	assert_eq!(T::total_balance(&account_1), initial_balance + transfer_amount);
482	assert_eq!(T::balance(&account_0), initial_balance - transfer_amount);
483	assert_eq!(T::balance(&account_1), initial_balance + transfer_amount);
484
485	// Verify: Total issuance doesn't change
486	assert_eq!(T::total_issuance(), initial_total_issuance + initial_balance * 2.into());
487	assert_eq!(T::active_issuance(), initial_active_issuance + initial_balance * 2.into());
488}
489
490/// Test the `transfer` function with `Preservation::Expendable` for transferring the entire
491/// balance.
492///
493/// This test verifies that transferring the entire balance from one account to another with
494/// `Preservation::Expendable` updates the account balances and maintains the total issuance and
495/// active issuance values.
496///
497/// # Type Parameters
498///
499/// ```text
500/// - `T`: Implements `Mutate<AccountId>`.
501/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`.
502/// ```
503pub fn transfer_expendable_all<T, AccountId>(_dust_trap: Option<AccountId>)
504where
505	T: Mutate<AccountId>,
506	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
507	AccountId: AtLeast8BitUnsigned,
508{
509	let initial_total_issuance = T::total_issuance();
510	let initial_active_issuance = T::active_issuance();
511	let account_0 = AccountId::from(0);
512	let account_1 = AccountId::from(1);
513	let initial_balance = T::minimum_balance() + 10.into();
514	T::set_balance(&account_0, initial_balance);
515	T::set_balance(&account_1, initial_balance);
516
517	// Test: Transfer entire balance from account_0 to account_1
518	let preservation = Preservation::Expendable;
519	let transfer_amount = initial_balance;
520	T::transfer(&account_0, &account_1, transfer_amount, preservation).unwrap();
521
522	// Verify: Account balances are updated correctly
523	assert_eq!(T::total_balance(&account_0), T::Balance::zero());
524	assert_eq!(T::total_balance(&account_1), initial_balance * 2.into());
525	assert_eq!(T::balance(&account_0), T::Balance::zero());
526	assert_eq!(T::balance(&account_1), initial_balance * 2.into());
527
528	// Verify: Total issuance doesn't change
529	assert_eq!(T::total_issuance(), initial_total_issuance + initial_balance * 2.into());
530	assert_eq!(T::active_issuance(), initial_active_issuance + initial_balance * 2.into());
531}
532
533/// Test the transfer function with Preservation::Expendable for transferring amounts that leaves
534/// an account with less than the minimum balance.
535///
536/// This test verifies that when transferring an amount using Preservation::Expendable and an
537/// account will be left with less than the minimum balance, the account balances are updated, dust
538/// is collected properly depending on whether a dust_trap exists, and the total issuance and active
539/// issuance values remain consistent.
540///
541/// # Parameters
542///
543/// - dust_trap: An optional account identifier to which dust will be collected. If None, dust will
544///   be removed from the total and active issuance.
545///
546/// # Type Parameters
547///
548/// ```text
549/// - T: Implements Mutate<AccountId>.
550/// - AccountId: Account identifier implementing AtLeast8BitUnsigned.
551/// ```
552pub fn transfer_expendable_dust<T, AccountId>(dust_trap: Option<AccountId>)
553where
554	T: Mutate<AccountId>,
555	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
556	AccountId: AtLeast8BitUnsigned,
557{
558	if T::minimum_balance() == T::Balance::zero() {
559		return
560	}
561
562	let account_0 = AccountId::from(10);
563	let account_1 = AccountId::from(20);
564	let initial_balance = T::minimum_balance() + 10.into();
565	T::set_balance(&account_0, initial_balance);
566	T::set_balance(&account_1, initial_balance);
567
568	let initial_total_issuance = T::total_issuance();
569	let initial_active_issuance = T::active_issuance();
570	let initial_dust_trap_balance = match dust_trap.clone() {
571		Some(dust_trap) => T::total_balance(&dust_trap),
572		None => T::Balance::zero(),
573	};
574
575	// Test: Transfer balance
576	let preservation = Preservation::Expendable;
577	let transfer_amount = T::Balance::from(11);
578	T::transfer(&account_0, &account_1, transfer_amount, preservation).unwrap();
579
580	// Verify: Account balances are updated correctly
581	assert_eq!(T::total_balance(&account_0), T::Balance::zero());
582	assert_eq!(T::total_balance(&account_1), initial_balance + transfer_amount);
583	assert_eq!(T::balance(&account_0), T::Balance::zero());
584	assert_eq!(T::balance(&account_1), initial_balance + transfer_amount);
585
586	match dust_trap {
587		Some(dust_trap) => {
588			// Verify: Total issuance and active issuance don't change
589			assert_eq!(T::total_issuance(), initial_total_issuance);
590			assert_eq!(T::active_issuance(), initial_active_issuance);
591			// Verify: Dust is collected into dust trap
592			assert_eq!(
593				T::total_balance(&dust_trap),
594				initial_dust_trap_balance + T::minimum_balance() - 1.into()
595			);
596			assert_eq!(
597				T::balance(&dust_trap),
598				initial_dust_trap_balance + T::minimum_balance() - 1.into()
599			);
600		},
601		None => {
602			// Verify: Total issuance and active issuance are reduced by the dust amount
603			assert_eq!(
604				T::total_issuance(),
605				initial_total_issuance - T::minimum_balance() + 1.into()
606			);
607			assert_eq!(
608				T::active_issuance(),
609				initial_active_issuance - T::minimum_balance() + 1.into()
610			);
611		},
612	}
613}
614
615/// Test the `transfer` function with `Preservation::Protect` and `Preservation::Preserve` for
616/// transferring the entire balance.
617///
618/// This test verifies that attempting to transfer the entire balance with `Preservation::Protect`
619/// or `Preservation::Preserve` returns an error, and the account balances, total issuance, and
620/// active issuance values remain unchanged.
621///
622/// # Type Parameters
623///
624/// ```text
625/// - `T`: Implements `Mutate<AccountId>`.
626/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`.
627/// ```
628pub fn transfer_protect_preserve<T, AccountId>(_dust_trap: Option<AccountId>)
629where
630	T: Mutate<AccountId>,
631	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
632	AccountId: AtLeast8BitUnsigned,
633{
634	// This test means nothing if there is no minimum balance
635	if T::minimum_balance() == T::Balance::zero() {
636		return
637	}
638
639	let initial_total_issuance = T::total_issuance();
640	let initial_active_issuance = T::active_issuance();
641	let account_0 = AccountId::from(0);
642	let account_1 = AccountId::from(1);
643	let initial_balance = T::minimum_balance() + 10.into();
644	T::set_balance(&account_0, initial_balance);
645	T::set_balance(&account_1, initial_balance);
646
647	// Verify: Transfer Protect entire balance from account_0 to account_1 should Err
648	let preservation = Preservation::Protect;
649	let transfer_amount = initial_balance;
650	T::transfer(&account_0, &account_1, transfer_amount, preservation).unwrap_err();
651
652	// Verify: Noop
653	assert_eq!(T::total_balance(&account_0), initial_balance);
654	assert_eq!(T::total_balance(&account_1), initial_balance);
655	assert_eq!(T::balance(&account_0), initial_balance);
656	assert_eq!(T::balance(&account_1), initial_balance);
657	assert_eq!(T::total_issuance(), initial_total_issuance + initial_balance * 2.into());
658	assert_eq!(T::active_issuance(), initial_active_issuance + initial_balance * 2.into());
659
660	// Verify: Transfer Preserve entire balance from account_0 to account_1 should Err
661	let preservation = Preservation::Preserve;
662	T::transfer(&account_0, &account_1, transfer_amount, preservation).unwrap_err();
663
664	// Verify: Noop
665	assert_eq!(T::total_balance(&account_0), initial_balance);
666	assert_eq!(T::total_balance(&account_1), initial_balance);
667	assert_eq!(T::balance(&account_0), initial_balance);
668	assert_eq!(T::balance(&account_1), initial_balance);
669	assert_eq!(T::total_issuance(), initial_total_issuance + initial_balance * 2.into());
670	assert_eq!(T::active_issuance(), initial_active_issuance + initial_balance * 2.into());
671}
672
673/// Test the set_balance function for successful minting.
674///
675/// This test verifies that minting a balance using set_balance updates the account balance, total
676/// issuance, and active issuance correctly.
677///
678/// # Type Parameters
679///
680/// ```text
681/// - T: Implements Mutate<AccountId>.
682/// - AccountId: Account identifier implementing AtLeast8BitUnsigned.
683/// ```
684pub fn set_balance_mint_success<T, AccountId>(_dust_trap: Option<AccountId>)
685where
686	T: Mutate<AccountId>,
687	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
688	AccountId: AtLeast8BitUnsigned,
689{
690	let initial_total_issuance = T::total_issuance();
691	let initial_active_issuance = T::active_issuance();
692	let account = AccountId::from(10);
693	let initial_balance = T::minimum_balance() + 10.into();
694	T::mint_into(&account, initial_balance).unwrap();
695
696	// Test: Increase the account balance with set_balance
697	let increase_amount: T::Balance = 5.into();
698	let new = T::set_balance(&account, initial_balance + increase_amount);
699
700	// Verify: set_balance returned the new balance
701	let expected_new = initial_balance + increase_amount;
702	assert_eq!(new, expected_new);
703
704	// Verify: Balance and issuance is updated correctly
705	assert_eq!(T::total_balance(&account), expected_new);
706	assert_eq!(T::balance(&account), expected_new);
707	assert_eq!(T::total_issuance(), initial_total_issuance + expected_new);
708	assert_eq!(T::active_issuance(), initial_active_issuance + expected_new);
709}
710
711/// Test the set_balance function for successful burning.
712///
713/// This test verifies that burning a balance using set_balance updates the account balance, total
714/// issuance, and active issuance correctly.
715///
716/// # Type Parameters
717///
718/// ```text
719/// - T: Implements Mutate<AccountId>.
720/// - AccountId: Account identifier implementing AtLeast8BitUnsigned.
721/// ```
722pub fn set_balance_burn_success<T, AccountId>(_dust_trap: Option<AccountId>)
723where
724	T: Mutate<AccountId>,
725	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
726	AccountId: AtLeast8BitUnsigned,
727{
728	let initial_total_issuance = T::total_issuance();
729	let initial_active_issuance = T::active_issuance();
730	let account = AccountId::from(10);
731	let initial_balance = T::minimum_balance() + 10.into();
732	T::mint_into(&account, initial_balance).unwrap();
733
734	// Test: Increase the account balance with set_balance
735	let burn_amount: T::Balance = 5.into();
736	let new = T::set_balance(&account, initial_balance - burn_amount);
737
738	// Verify: set_balance returned the new balance
739	let expected_new = initial_balance - burn_amount;
740	assert_eq!(new, expected_new);
741
742	// Verify: Balance and issuance is updated correctly
743	assert_eq!(T::total_balance(&account), expected_new);
744	assert_eq!(T::balance(&account), expected_new);
745	assert_eq!(T::total_issuance(), initial_total_issuance + expected_new);
746	assert_eq!(T::active_issuance(), initial_active_issuance + expected_new);
747}
748
749/// Test the can_deposit function for returning a success value.
750///
751/// This test verifies that the can_deposit function returns DepositConsequence::Success when
752/// depositing a reasonable amount.
753///
754/// # Type Parameters
755///
756/// ```text
757/// - T: Implements Mutate<AccountId>.
758/// - AccountId: Account identifier implementing AtLeast8BitUnsigned.
759/// ```
760pub fn can_deposit_success<T, AccountId>(_dust_trap: Option<AccountId>)
761where
762	T: Mutate<AccountId>,
763	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
764	AccountId: AtLeast8BitUnsigned,
765{
766	let account = AccountId::from(10);
767	let initial_balance = T::minimum_balance() + 10.into();
768	T::mint_into(&account, initial_balance).unwrap();
769
770	// Test: can_deposit a reasonable amount
771	let ret = T::can_deposit(&account, 5.into(), Provenance::Minted);
772
773	// Verify: Returns success
774	assert_eq!(ret, DepositConsequence::Success);
775}
776
777/// Test the can_deposit function for returning a minimum balance error.
778///
779/// This test verifies that the can_deposit function returns DepositConsequence::BelowMinimum when
780/// depositing below the minimum balance.
781///
782/// # Type Parameters
783///
784/// ```text
785/// - T: Implements Mutate<AccountId>.
786/// - AccountId: Account identifier implementing AtLeast8BitUnsigned.
787/// ```
788pub fn can_deposit_below_minimum<T, AccountId>(_dust_trap: Option<AccountId>)
789where
790	T: Mutate<AccountId>,
791	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
792	AccountId: AtLeast8BitUnsigned,
793{
794	// can_deposit always returns Success for amount 0
795	if T::minimum_balance() < 2.into() {
796		return
797	}
798
799	let account = AccountId::from(10);
800
801	// Test: can_deposit below the minimum
802	let ret = T::can_deposit(&account, T::minimum_balance() - 1.into(), Provenance::Minted);
803
804	// Verify: Returns success
805	assert_eq!(ret, DepositConsequence::BelowMinimum);
806}
807
808/// Test the can_deposit function for returning an overflow error.
809///
810/// This test verifies that the can_deposit function returns DepositConsequence::Overflow when
811/// depositing an amount that would cause an overflow.
812///
813/// # Type Parameters
814///
815/// ```text
816/// - T: Implements Mutate<AccountId>.
817/// - AccountId: Account identifier implementing AtLeast8BitUnsigned.
818/// ```
819pub fn can_deposit_overflow<T, AccountId>(_dust_trap: Option<AccountId>)
820where
821	T: Mutate<AccountId>,
822	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
823	AccountId: AtLeast8BitUnsigned,
824{
825	let account = AccountId::from(10);
826
827	// Test: Try deposit over the max balance
828	let initial_balance = T::Balance::max_value() - 5.into() - T::total_issuance();
829	T::mint_into(&account, initial_balance).unwrap();
830	let ret = T::can_deposit(&account, 10.into(), Provenance::Minted);
831
832	// Verify: Returns success
833	assert_eq!(ret, DepositConsequence::Overflow);
834}
835
836/// Test the can_withdraw function for returning a success value.
837///
838/// This test verifies that the can_withdraw function returns WithdrawConsequence::Success when
839/// withdrawing a reasonable amount.
840///
841/// # Type Parameters
842///
843/// ```text
844/// - T: Implements Mutate<AccountId>.
845/// - AccountId: Account identifier implementing AtLeast8BitUnsigned.
846/// ```
847pub fn can_withdraw_success<T, AccountId>(_dust_trap: Option<AccountId>)
848where
849	T: Mutate<AccountId>,
850	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
851	AccountId: AtLeast8BitUnsigned,
852{
853	let account = AccountId::from(10);
854	let initial_balance = T::minimum_balance() + 10.into();
855	T::mint_into(&account, initial_balance).unwrap();
856
857	// Test: can_withdraw a reasonable amount
858	let ret = T::can_withdraw(&account, 5.into());
859
860	// Verify: Returns success
861	assert_eq!(ret, WithdrawConsequence::Success);
862}
863
864/// Test the can_withdraw function for withdrawal resulting in a reduced balance of zero.
865///
866/// This test verifies that the can_withdraw function returns WithdrawConsequence::ReducedToZero
867/// when withdrawing an amount that would reduce the account balance below the minimum balance.
868///
869/// # Type Parameters
870///
871/// ```text
872/// - T: Implements Mutate<AccountId>.
873/// - AccountId: Account identifier implementing AtLeast8BitUnsigned.
874/// ```
875pub fn can_withdraw_reduced_to_zero<T, AccountId>(_dust_trap: Option<AccountId>)
876where
877	T: Mutate<AccountId>,
878	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
879	AccountId: AtLeast8BitUnsigned,
880{
881	if T::minimum_balance() == T::Balance::zero() {
882		return
883	}
884
885	let account = AccountId::from(10);
886	let initial_balance = T::minimum_balance();
887	T::mint_into(&account, initial_balance).unwrap();
888
889	// Verify: can_withdraw below the minimum balance returns ReducedToZero
890	let ret = T::can_withdraw(&account, 1.into());
891	assert_eq!(ret, WithdrawConsequence::ReducedToZero(T::minimum_balance() - 1.into()));
892}
893
894/// Test the can_withdraw function for returning a low balance error.
895///
896/// This test verifies that the can_withdraw function returns WithdrawConsequence::BalanceLow when
897/// withdrawing an amount that would result in an account balance below the current balance.
898///
899/// # Type Parameters
900///
901/// ```text
902/// - T: Implements Mutate<AccountId>.
903/// - AccountId: Account identifier implementing AtLeast8BitUnsigned.
904/// ```
905pub fn can_withdraw_balance_low<T, AccountId>(_dust_trap: Option<AccountId>)
906where
907	T: Mutate<AccountId>,
908	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
909	AccountId: AtLeast8BitUnsigned,
910{
911	if T::minimum_balance() == T::Balance::zero() {
912		return
913	}
914
915	let account = AccountId::from(10);
916	let other_account = AccountId::from(100);
917	let initial_balance = T::minimum_balance() + 5.into();
918	T::mint_into(&account, initial_balance).unwrap();
919	T::mint_into(&other_account, initial_balance * 2.into()).unwrap();
920
921	// Verify: can_withdraw below the account balance returns BalanceLow
922	let ret = T::can_withdraw(&account, initial_balance + 1.into());
923	assert_eq!(ret, WithdrawConsequence::BalanceLow);
924}
925
926/// Test the reducible_balance function with Preservation::Expendable.
927///
928/// This test verifies that the reducible_balance function returns the full account balance when
929/// using Preservation::Expendable.
930///
931/// # Type Parameters
932///
933/// ```text
934/// - T: Implements Mutate<AccountId>.
935/// - AccountId: Account identifier implementing AtLeast8BitUnsigned.
936/// ```
937pub fn reducible_balance_expendable<T, AccountId>(_dust_trap: Option<AccountId>)
938where
939	T: Mutate<AccountId>,
940	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
941	AccountId: AtLeast8BitUnsigned,
942{
943	let account = AccountId::from(10);
944	let initial_balance = T::minimum_balance() + 10.into();
945	T::mint_into(&account, initial_balance).unwrap();
946
947	// Verify: reducible_balance returns the full balance
948	let ret = T::reducible_balance(&account, Preservation::Expendable, Fortitude::Polite);
949	assert_eq!(ret, initial_balance);
950}
951
952/// Test the reducible_balance function with Preservation::Protect and Preservation::Preserve.
953///
954/// This test verifies that the reducible_balance function returns the account balance minus the
955/// minimum balance when using either Preservation::Protect or Preservation::Preserve.
956///
957/// # Type Parameters
958///
959/// ```text
960/// - T: Implements Mutate<AccountId>.
961/// - AccountId: Account identifier implementing AtLeast8BitUnsigned.
962/// ```
963pub fn reducible_balance_protect_preserve<T, AccountId>(_dust_trap: Option<AccountId>)
964where
965	T: Mutate<AccountId>,
966	<T as Inspect<AccountId>>::Balance: AtLeast8BitUnsigned + Debug,
967	AccountId: AtLeast8BitUnsigned,
968{
969	let account = AccountId::from(10);
970	let initial_balance = T::minimum_balance() + 10.into();
971	T::mint_into(&account, initial_balance).unwrap();
972
973	// Verify: reducible_balance returns the full balance - min balance
974	let ret = T::reducible_balance(&account, Preservation::Protect, Fortitude::Polite);
975	assert_eq!(ret, initial_balance - T::minimum_balance());
976	let ret = T::reducible_balance(&account, Preservation::Preserve, Fortitude::Polite);
977	assert_eq!(ret, initial_balance - T::minimum_balance());
978}