Skip to content

Account for reserved balance in ensure_can_withdraw function #8108

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

RomarQ
Copy link
Contributor

@RomarQ RomarQ commented Mar 31, 2025

Description

Solves: #8099

Based on the documentation and existing code, the usable balance is computed with the following formula:

// If Fortitude == Polite 
let usable_balance = free - max(frozen - reserved, existential balance)

The problem:

If an account's free balance is lower than frozen balance, no reserves will be allowed even though the usable balance is enough to cover the reserve, resulting in a LiquidityRestrictions error, which should not happen.

Visual example of how usable/spendable balance works:

|__total__________________________________|
|__on_hold__|_____________free____________|
|__________frozen___________|
|__on_hold__|__ed__|
            |__untouchable__|__spendable__|

Integration

No action is required, the changes only change existing code, it does not add or change any API.

Review Notes

From my understanding, the function ensure_can_withdraw was incorrect, and instead of checking that the new free balance is higher or equal to the frozen balance, it should make sure the new free balance is higher or equal to the usable balance.

@RomarQ RomarQ marked this pull request as ready for review March 31, 2025 13:43
@RomarQ RomarQ requested a review from a team as a code owner March 31, 2025 13:43
@gui1117
Copy link
Contributor

gui1117 commented Apr 9, 2025

code looks good, but it is difficult to say if this is not breaking some other assumptions in the code elsewhere in the currency implementation.

Maybe @muharem you could have some opinion

@RomarQ
Copy link
Contributor Author

RomarQ commented Apr 9, 2025

code looks good, but it is difficult to say if this is not breaking some other assumptions in the code elsewhere in the currency implementation.

Maybe @muharem you could have some opinion

Thanks for having a look 👍

@bkontur
Copy link
Contributor

bkontur commented Apr 9, 2025

code looks good, but it is difficult to say if this is not breaking some other assumptions in the code elsewhere in the currency implementation.

Maybe, we could catch some assumptions with fn try_state for pallet_balances.

This PR fixes Currency::ensure_can_withdraw (we are trying to deprecated Currency in favor of fungible traits), there is also fn can_withdraw( in the impl_fungible.rs - I don't know if it make sense to align behavior for both?

@RomarQ
Copy link
Contributor Author

RomarQ commented Apr 9, 2025

code looks good, but it is difficult to say if this is not breaking some other assumptions in the code elsewhere in the currency implementation.

Maybe, we could catch some assumptions with fn try_state for pallet_balances.

This PR fixes Currency::ensure_can_withdraw (we are trying to deprecated Currency in favor of fungible traits), there is also fn can_withdraw( in the impl_fungible.rs - I don't know if it make sense to align behavior for both?

The can_withdraw from impl_fungible.rs is only applicable for balance withdraws, not used for Holds. The deprecated Currency::ensure_can_withdraw is called for Reserves, where the operation can be distinguished by checking the WithdrawReasons. The method try_mutate_account also performs sanity checks that make sure we do not break account balance assumptions.

I think we should include this change, since there are multiple pallets (eg. pallet_proxy) still using impl_currency.rs traits.

Image

Image

Comment on lines +326 to +331
// Account for the new reserved amount only if withdrawing for a reserve.
let updated_reserved = if matches!(reasons, WithdrawReasons::RESERVE) {
account.reserved.saturating_add(amount)
} else {
account.reserved
};
Copy link
Contributor

@gui1117 gui1117 Apr 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a correct assumption of the withdraw reason API.

WithdrawReasons::RESERVE says:

In order to reserve some funds for a later return or repatriation.

But it doesn't say the reserved amount will be on the same account.

So I think in this function we shouldn't add the reserved amount.
Then in can_reserve and reserve we can be more precise so we can create a new function which does add the amount to the reserved amount.

Then after this I will approve the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants