DeFiFoundry
60,000 USDC
View results
Submission Details
Severity: medium
Invalid

Accounts that partially exits their positions will not be be able to withdraw the funds in their balance if their margin is below the InitialMarginRate

Summary

The current implementation prevents users from withdrawing their funds if their margin falls below the initial margin requirement, even when they are allowed to reduce their position size as long as they are above the maintenance margin. This restriction makes funds that should be available for withdrawal effectively inaccessible, which contradicts the intended behavior described in the documentation.

https://docs.zaros.fi/overview/resources/faq#:~:text=Can I withdraw,available for withdrawal.

Vulnerability Details

When a position is reduced, the required margin should switch from the initial margin requirement to the maintenance margin requirement. However, the current implementation uses the initial margin requirement when validating withdrawals, preventing users from accessing their available funds if their margin falls below the initial margin requirement but is still above the maintenance margin requirement.

### Key Points:

1. **Current Behavior**:

- Users cannot withdraw funds if their margin balance falls below the initial margin requirement, even if they have reduced their position and are above the maintenance margin.

2. **Issue**:

- Funds that should be available for withdrawal are not accessible, causing inconvenience and financial constraints for users. Contradicts docs - https://docs.zaros.fi/overview/resources/faq#:~:text=Can I withdraw,available for withdrawal. Available funds from closed positions should be withdrawable.

*Code Snippet with Practical Examples:**

1. User A opens a $3000 position:

- Initial margin: $2000.

- Required margin: $1000.

- Balance: $3000.

- PnL: -$1200.

2. User A decides to reduce his position by $795:

- Balance: $3000 - $1200 = $1800.

- New position size: $3000 - $795 = $2205.

- Below initial margin: $2000.

3. User opens a trade to reduce his debt by half to $795:

- This triggers a market order and the following checks are made:

```solidity

(requiredInitialMarginUsdX18, requiredMaintenanceMarginUsdX18, ctx.accountTotalUnrealizedPnlUsdX18) =

tradingAccount.getAccountMarginRequirementUsdAndUnrealizedPnlUsd(marketId, ctx.sizeDeltaX18);

@audit>> new balance>>> marginBalanceUsdX18 = tradingAccount.getMarginBalanceUsd(ctx.accountTotalUnrealizedPnlUsdX18);

@audit>> previous margin>>> (, ctx.previousRequiredMaintenanceMarginUsdX18,) =

tradingAccount.getAccountMarginRequirementUsdAndUnrealizedPnlUsd(0, SD59x18_ZERO);

@audit >> ensures buffer >>> if (TradingAccount.isLiquidatable(ctx.previousRequiredMaintenanceMarginUsdX18, marginBalanceUsdX18)) {

revert Errors.AccountIsLiquidatable(tradingAccountId);

}

```

4. Comparison of balance with previous required maintenance margin:

- Previous maintenance margin: $1000

- New balance: $1800 - $795 = $1005

5. User A new position

- New position: $3000 - $795 = $2205

- New initial margin: $1468 (66.6% of position size)

- New maintenance margin: $734

- New balance: $2205 - $1200 = $1005

-Withdrawalable amount = balance $1005 + closed position $795 - New initial margin $1468 = $332

  • Lock fund = $795 - $332 = $463

User A will not be able to withdraw $463 from his closed position even though he reduced his position and his New Maintenance margin is buffered enough preventing liquidation anytime soon.

Users should be able to withdraw this fund unto their funds unto their previous Maintenance Margin.

3. **Desired Behavior**:

- Users should be able to withdraw funds when their margin balance is above the maintenance margin requirement, even if it is below the initial margin requirement, as long as they have reduced their position size.

/// @notice Creates a market order for the given trading account and market ids.
/// @dev See {CreateMarketOrderParams}.
function createMarketOrder(CreateMarketOrderParams calldata params) external {
// working data
CreateMarketOrderContext memory ctx;
------------------------------------------------------------
// check maintenance margin if:
// 1) position is not increasing AND
// 2) existing position is being decreased in size
//
// when a position is under the higher initial margin requirement but over the
// lower maintenance margin requirement, we want to allow the trader to decrease
// their losing position size before they become subject to liquidation
//
// but if the trader is opening a new position or increasing the size
// of their existing position we want to ensure they satisfy the higher
// initial margin requirement
ctx.shouldUseMaintenanceMargin = !Position.isIncreasing(params.tradingAccountId, params.marketId, params.sizeDelta)
&& ctx.isMarketWithActivePosition;
@audit>> when we close part of our position >>> ctx.requiredMarginUsdX18 =
ctx.shouldUseMaintenanceMargin ? ctx.requiredMaintenanceMarginUsdX18 : ctx.requiredInitialMarginUsdX18;
// reverts if the trader can't satisfy the appropriate margin requirement
tradingAccount.validateMarginRequirement(
ctx.requiredMarginUsdX18, ctx.marginBalanceUsdX18, ctx.orderFeeUsdX18.add(ctx.settlementFeeUsdX18)
);

Yet the funds from the closed position are now available in the balance but cannot be withdrawn.

/// @notice Withdraws available margin collateral from the given trading account.
/// @param tradingAccountId The trading account id.
/// @param collateralType The margin collateral address.
/// @param amount The UD60x18 amount of margin collateral to withdraw.
function withdrawMargin(uint128 tradingAccountId, address collateralType, uint256 amount) external {
// fetch storage slot for this collateral's config config
MarginCollateralConfiguration.Data storage marginCollateralConfiguration =
MarginCollateralConfiguration.load(collateralType);
------------------------------------------------------
// load account required initial margin requirement & unrealized USD profit/loss
// ignores "required maintenance margin" output parameter
(UD60x18 requiredInitialMarginUsdX18,, SD59x18 accountTotalUnrealizedPnlUsdX18) =
tradingAccount.getAccountMarginRequirementUsdAndUnrealizedPnlUsd(0, SD59x18_ZERO);
// get trader's margin balance
SD59x18 marginBalanceUsdX18 = tradingAccount.getMarginBalanceUsd(accountTotalUnrealizedPnlUsdX18);
// check against initial margin requirement as initial margin > maintenance margin
// hence prevent the user from withdrawing all the way to the maintenance margin
// so that they couldn't be liquidated very soon afterwards if their position
// goes against them even a little bit
@audit>> we cannot withdraw collateral gotten from the partially closed position >>> tradingAccount.validateMarginRequirement(requiredInitialMarginUsdX18, marginBalanceUsdX18, UD60x18_ZERO);

Impact

This issue prevents users from accessing funds that should be available for withdrawal.

Tools Used

- Solidity code analysis

- Review of Order Branch, margin and withdrawal logic

- Review of developer comments and documentation

Recommendations

  1. **Track Position-Specific Margin Requirements of each account in their trading ID**: Implement a state variable to track the Requiredmargin requirements for each position, allowing users to withdraw funds when they have reduced their position size and are above their Previous maintenance margin requirement.

  2. **Update Withdrawal Logic**: Modify the withdrawal function to use the Requiredmargin(use initial or maintance whatever value it is) requirement based on the account state when validating withdrawals for reduced positions.

Updates

Lead Judging Commences

inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.