- 
                Notifications
    
You must be signed in to change notification settings  - Fork 1.1k
 
Description
Description
I am creating this issue to confirm if the behaviour reported below is a bug or not. If it is not a bug, the documentation should be updated with an example explaining it.
Recently, for moonbeam, we received a bug report where the user could not add a proxy, even though he had enough usable balance to pay for the deposit.
Based on the documentation, 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 the following error LiquidityRestrictions.
Based on the documentation, this should not happen.
Visual example of how usable balance works:
|__total__________________________________|
|__on_hold__|_____________free____________|
|__________frozen___________|
|__on_hold__|__ed__|
            |__untouchable__|__spendable__|Based on the account example below, the user should be able to reserve an amount of 5 tokens.
free: 5
frozen: 10
reserved: 10In my understanding, the function below is incorrect, and instead of checking that the new free balance is higher or equal to the frozen balance, it should make sure that amount is less or equal to the usable balance.
polkadot-sdk/substrate/frame/balances/src/impl_currency.rs
Lines 315 to 326 in f9fac6c
| fn ensure_can_withdraw( | |
| who: &T::AccountId, | |
| amount: T::Balance, | |
| _reasons: WithdrawReasons, | |
| new_balance: T::Balance, | |
| ) -> DispatchResult { | |
| if amount.is_zero() { | |
| return Ok(()) | |
| } | |
| ensure!(new_balance >= Self::account(who).frozen, Error::<T, I>::LiquidityRestrictions); | |
| Ok(()) | |
| } | 
If it is confirmed to be a bug, I can work on the fix and open a pull request.
Suggested fix for function ensure_can_withdraw:
	fn ensure_can_withdraw(
		who: &T::AccountId,
		amount: T::Balance,
		reasons: WithdrawReasons,
		new_balance: T::Balance,
	) -> DispatchResult {
		if amount.is_zero() {
			return Ok(())
		}
		let account = Self::account(who);
		// Calculate the new reserved amount only if withdrawing for reservation.
		let updated_reserved = if matches!(reasons, WithdrawReasons::RESERVE) {
			account.reserved.saturating_add(amount)
		} else {
			account.reserved
		};
		// Frozen balance applies to total. Anything on hold therefore gets discounted from the
		// limit given by the freezes minus reserves.
		let untouchable = account
			.frozen
			.saturating_sub(updated_reserved)
			.max(T::ExistentialDeposit::get());
		ensure!(new_balance >= untouchable, Error::<T, I>::LiquidityRestrictions);
		Ok(())
	}Steps to reproduce
- Have an account with 20 tokens
 - Set a lock with 10 tokens
 - Reserve 10 tokens
 - Try to reserve 1 token // This will fail with LiquidityRestrictions
4.1. (But transferring the spendable balance works)