referrerpolicy=no-referrer-when-downgrade

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