Skip to content

Commit 7fe62fb

Browse files
liamaharonmuharem
andauthored
Unbalanced and Balanced fungible conformance tests, and fungible fixes (paritytech#1296)
Original PR paritytech/substrate#14655 --- Partial paritytech#225 - [x] Adds conformance tests for Unbalanced - [x] Adds conformance tests for Balanced - Several minor fixes to fungible default implementations and the Balances pallet - [x] `Unbalanced::decrease_balance` can reap account when `Preservation` is `Preserve` - [x] `Balanced::pair` can return pairs of imbalances which do not cancel each other out - [x] Balances pallet `active_issuance` 'underflow' - [x] Refactors the conformance test file structure to match the fungible file structure: tests for traits in regular.rs go into a test file named regular.rs, tests for traits in freezes.rs go into a test file named freezes.rs, etc. - [x] Improve doc comments - [x] Simplify macros ## Fixes ### `Unbalanced::decrease_balance` can reap account when called with `Preservation::Preserve` There is a potential issue in the default implementation of `Unbalanced::decrease_balance`. The implementation can delete an account even when it is called with `preservation: Preservation::Preserve`. This seems to contradict the documentation of `Preservation::Preserve`: ```rust /// The account may not be killed and our provider reference must remain (in the context of /// tokens, this means that the account may not be dusted). Preserve, ``` I updated `Unbalanced::decrease_balance` to return `Err(TokenError::BelowMinimum)` when a withdrawal would cause the account to be reaped and `preservation: Preservation::Preserve`. - [ ] TODO Confirm with @gavofyork that this is correct behavior Test for this behavior: https://github.com/paritytech/polkadot-sdk/blob/e5c876dd6b59e2b7dbacaa4538cb42c802db3730/substrate/frame/support/src/traits/tokens/fungible/conformance_tests/regular.rs#L912-L937 ### `Balanced::pair` returning non-canceling pairs `Balanced::pair` is supposed to create a pair of imbalances that cancel each other out. However this is not the case when the method is called with an amount greater than the total supply. In the existing default implementation, `Balanced::pair` creates a pair by first rescinding the balance, creating `Debt`, and then issuing the balance, creating `Credit`. When creating `Debt`, if the amount to create exceeds the `total_supply`, `total_supply` units of `Debt` are created *instead* of `amount` units of `Debt`. This can lead to non-canceling amount of `Credit` and `Debt` being created. To address this, I create the credit and debt directly in the method instead of calling `issue` and `rescind`. Test for this behavior: https://github.com/paritytech/polkadot-sdk/blob/e5c876dd6b59e2b7dbacaa4538cb42c802db3730/substrate/frame/support/src/traits/tokens/fungible/conformance_tests/regular.rs#L1323-L1346 ### `Balances` pallet `active_issuance` 'underflow' This PR resolves an issue in the `Balances` pallet that can lead to odd behavior of `active_issuance`. Currently, the Balances pallet doesn't check if `InactiveIssuance` remains less than or equal to `TotalIssuance` when supply is deactivated. This allows `InactiveIssuance` to be greater than `TotalIssuance`, which can result in unexpected behavior from the perspective of the fungible API. `active_issuance` is derived from `TotalIssuance.saturating_sub(InactiveIssuance)`. If an `amount` is deactivated that causes `InactiveIssuance` to become greater TotalIssuance, `active_issuance` will return 0. However once in that state, reactivating an amount will not increase `active_issuance` by the reactivated `amount` as expected. Consider this test where the last assertion would fail due to this issue: https://github.com/paritytech/polkadot-sdk/blob/e5c876dd6b59e2b7dbacaa4538cb42c802db3730/substrate/frame/support/src/traits/tokens/fungible/conformance_tests/regular.rs#L1036-L1071 To address this, I've modified the `deactivate` function to ensure `InactiveIssuance` never surpasses `TotalIssuance`. --------- Co-authored-by: Muharem <[email protected]>
1 parent ab2dba9 commit 7fe62fb

File tree

13 files changed

+1547
-79
lines changed

13 files changed

+1547
-79
lines changed

substrate/frame/assets/src/tests/sets.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ fn pair_from_set_types_works() {
153153
assert_eq!(First::<Assets>::total_issuance(()), 100);
154154
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
155155

156-
let (debt, credit) = First::<Assets>::pair((), 100);
156+
let (debt, credit) = First::<Assets>::pair((), 100).unwrap();
157157
assert_eq!(First::<Assets>::total_issuance(()), 100);
158158
assert_eq!(debt.peek(), 100);
159159
assert_eq!(credit.peek(), 100);

substrate/frame/balances/src/impl_fungible.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,10 @@ impl<T: Config<I>, I: 'static> fungible::Unbalanced<T::AccountId> for Pallet<T,
177177
}
178178

179179
fn deactivate(amount: Self::Balance) {
180-
InactiveIssuance::<T, I>::mutate(|b| b.saturating_accrue(amount));
180+
InactiveIssuance::<T, I>::mutate(|b| {
181+
// InactiveIssuance cannot be greater than TotalIssuance.
182+
*b = b.saturating_add(amount).min(TotalIssuance::<T, I>::get());
183+
});
181184
}
182185

183186
fn reactivate(amount: Self::Balance) {

substrate/frame/balances/src/tests/fungible_conformance_tests.rs

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,30 @@ use super::*;
1919
use frame_support::traits::fungible::{conformance_tests, Inspect, Mutate};
2020
use paste::paste;
2121

22-
macro_rules! run_tests {
23-
($path:path, $ext_deposit:expr, $($name:ident),*) => {
22+
macro_rules! generate_tests {
23+
// Handle a conformance test that requires special testing with and without a dust trap.
24+
(dust_trap_variation, $base_path:path, $scope:expr, $trait:ident, $ext_deposit:expr, $($test_name:ident),*) => {
2425
$(
2526
paste! {
2627
#[test]
27-
fn [< $name _existential_deposit_ $ext_deposit _dust_trap_on >]() {
28+
fn [<$trait _ $scope _ $test_name _existential_deposit_ $ext_deposit _dust_trap_on >]() {
29+
// Some random trap account.
2830
let trap_account = <Test as frame_system::Config>::AccountId::from(65174286u64);
2931
let builder = ExtBuilder::default().existential_deposit($ext_deposit).dust_trap(trap_account);
3032
builder.build_and_execute_with(|| {
3133
Balances::set_balance(&trap_account, Balances::minimum_balance());
32-
$path::$name::<
34+
$base_path::$scope::$trait::$test_name::<
3335
Balances,
3436
<Test as frame_system::Config>::AccountId,
3537
>(Some(trap_account));
3638
});
3739
}
3840

3941
#[test]
40-
fn [< $name _existential_deposit_ $ext_deposit _dust_trap_off >]() {
42+
fn [< $trait _ $scope _ $test_name _existential_deposit_ $ext_deposit _dust_trap_off >]() {
4143
let builder = ExtBuilder::default().existential_deposit($ext_deposit);
4244
builder.build_and_execute_with(|| {
43-
$path::$name::<
45+
$base_path::$scope::$trait::$test_name::<
4446
Balances,
4547
<Test as frame_system::Config>::AccountId,
4648
>(None);
@@ -49,9 +51,37 @@ macro_rules! run_tests {
4951
}
5052
)*
5153
};
52-
($path:path, $ext_deposit:expr) => {
53-
run_tests!(
54-
$path,
54+
// Regular conformance test
55+
($base_path:path, $scope:expr, $trait:ident, $ext_deposit:expr, $($test_name:ident),*) => {
56+
$(
57+
paste! {
58+
#[test]
59+
fn [< $trait _ $scope _ $test_name _existential_deposit_ $ext_deposit>]() {
60+
let builder = ExtBuilder::default().existential_deposit($ext_deposit);
61+
builder.build_and_execute_with(|| {
62+
$base_path::$scope::$trait::$test_name::<
63+
Balances,
64+
<Test as frame_system::Config>::AccountId,
65+
>();
66+
});
67+
}
68+
}
69+
)*
70+
};
71+
($base_path:path, $ext_deposit:expr) => {
72+
// regular::mutate
73+
generate_tests!(
74+
dust_trap_variation,
75+
$base_path,
76+
regular,
77+
mutate,
78+
$ext_deposit,
79+
transfer_expendable_dust
80+
);
81+
generate_tests!(
82+
$base_path,
83+
regular,
84+
mutate,
5585
$ext_deposit,
5686
mint_into_success,
5787
mint_into_overflow,
@@ -66,7 +96,6 @@ macro_rules! run_tests {
6696
shelve_insufficient_funds,
6797
transfer_success,
6898
transfer_expendable_all,
69-
transfer_expendable_dust,
7099
transfer_protect_preserve,
71100
set_balance_mint_success,
72101
set_balance_burn_success,
@@ -79,10 +108,34 @@ macro_rules! run_tests {
79108
reducible_balance_expendable,
80109
reducible_balance_protect_preserve
81110
);
111+
// regular::unbalanced
112+
generate_tests!(
113+
$base_path,
114+
regular,
115+
unbalanced,
116+
$ext_deposit,
117+
write_balance,
118+
decrease_balance_expendable,
119+
decrease_balance_preserve,
120+
increase_balance,
121+
set_total_issuance,
122+
deactivate_and_reactivate
123+
);
124+
// regular::balanced
125+
generate_tests!(
126+
$base_path,
127+
regular,
128+
balanced,
129+
$ext_deposit,
130+
issue_and_resolve_credit,
131+
rescind_and_settle_debt,
132+
deposit,
133+
withdraw,
134+
pair
135+
);
82136
};
83137
}
84138

85-
run_tests!(conformance_tests::inspect_mutate, 1);
86-
run_tests!(conformance_tests::inspect_mutate, 2);
87-
run_tests!(conformance_tests::inspect_mutate, 5);
88-
run_tests!(conformance_tests::inspect_mutate, 1000);
139+
generate_tests!(conformance_tests, 1);
140+
generate_tests!(conformance_tests, 5);
141+
generate_tests!(conformance_tests, 1000);

substrate/frame/support/src/traits/tokens/fungible/conformance_tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@
1616
// limitations under the License.
1717

1818
pub mod inspect_mutate;
19+
pub mod regular;

0 commit comments

Comments
 (0)