diff --git a/prdoc/pr_2823.prdoc b/prdoc/pr_2823.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..64a309969efb353fe7d4d93b6a3e485e024f11f7 --- /dev/null +++ b/prdoc/pr_2823.prdoc @@ -0,0 +1,11 @@ +title: "`fungible::Unbalanced::decrease_balance`: Handle `precision` properly" + +doc: + - audience: Runtime Dev + description: | + `fungible::Unbalanced::decrease_balance` will now handle `precision` properly. This means when + passing `Exact`, it will ensure that the available balance is bigger or equal to the `amount` + that should be deducted. + +crates: + - name: "frame-support" diff --git a/substrate/frame/balances/src/tests/fungible_tests.rs b/substrate/frame/balances/src/tests/fungible_tests.rs index 8bf0509d8f77e00802e0378eaa4c9ec26e341534..52fbe10bedec0f7131fe35fa7e6f809968741d42 100644 --- a/substrate/frame/balances/src/tests/fungible_tests.rs +++ b/substrate/frame/balances/src/tests/fungible_tests.rs @@ -146,7 +146,7 @@ fn unbalanced_trait_decrease_balance_works_2() { assert_eq!(Balances::total_balance_on_hold(&1337), 60); assert_noop!( Balances::decrease_balance(&1337, 40, Exact, Expendable, Polite), - Error::<Test>::InsufficientBalance + TokenError::FundsUnavailable ); assert_eq!(Balances::decrease_balance(&1337, 39, Exact, Expendable, Polite), Ok(39)); assert_eq!(<Balances as fungible::Inspect<_>>::balance(&1337), 1); @@ -468,3 +468,28 @@ fn emit_events_with_changing_freezes() { assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Thawed { who: 1, amount: 15 })]); }); } + +#[test] +fn withdraw_precision_exact_works() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10)); + assert_eq!(Balances::account(&1).free, 10); + assert_eq!(Balances::account(&1).frozen, 10); + + // `BestEffort` will not reduce anything + assert_ok!(<Balances as fungible::Balanced<_>>::withdraw( + &1, 5, BestEffort, Preserve, Polite + )); + + assert_eq!(Balances::account(&1).free, 10); + assert_eq!(Balances::account(&1).frozen, 10); + + assert_noop!( + <Balances as fungible::Balanced<_>>::withdraw(&1, 5, Exact, Preserve, Polite), + TokenError::FundsUnavailable + ); + }); +} diff --git a/substrate/frame/support/src/traits/tokens/fungible/regular.rs b/substrate/frame/support/src/traits/tokens/fungible/regular.rs index aece73777d280ee40e70a2270bae7abe9189f774..87c6ef68d77d6c8ca0b88d650126c7e472126b85 100644 --- a/substrate/frame/support/src/traits/tokens/fungible/regular.rs +++ b/substrate/frame/support/src/traits/tokens/fungible/regular.rs @@ -181,9 +181,16 @@ pub trait Unbalanced<AccountId>: Inspect<AccountId> { ) -> Result<Self::Balance, DispatchError> { let old_balance = Self::balance(who); let free = Self::reducible_balance(who, preservation, force); - if let BestEffort = precision { - amount = amount.min(free); + match precision { + BestEffort => { + amount = amount.min(free); + }, + Exact => + if free < amount { + return Err(TokenError::FundsUnavailable.into()) + }, } + let new_balance = old_balance.checked_sub(&amount).ok_or(TokenError::FundsUnavailable)?; if let Some(dust) = Self::write_balance(who, new_balance)? { Self::handle_dust(Dust(dust));