Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: high
Valid

Incorrect credit capacity checks in the `VaultRouterBranch::redeem` allows for over withdraw the vault

Summary

Ther is a flawed credit capacity check in the redeem function that allows users to withdraw more collateral assets than the vault’s available unlocked liquidity. This can drain the vault’s reserves, rendering it insolvent and unable to honor legitimate withdrawal requests.

Vulnerability

flawed code

// if the credit capacity delta is greater than the locked credit capacity before the state transition, revert
if (
ctx.creditCapacityBeforeRedeemUsdX18.sub(vault.getTotalCreditCapacityUsd()).lte(
ctx.lockedCreditCapacityBeforeRedeemUsdX18.intoSD59x18()
)
) {
revert Errors.NotEnoughUnlockedCreditCapacity();
}

The condition checks if the withdrawal amount (delta) is ≤ locked credit capacity, not unlocked. This allows withdrawals to exceed unlocked liquidity if delta > lockedCredit.

A Scenario of the flawed vault check
Vault State:

  1. Total Credit Capacity: 1,000 USD (assets available).

  2. Locked Credit Capacity: 300 USD (reserved for pending withdrawals).

  3. Unlocked Credit Capacity: 700 USD (available for immediate withdrawals).

Malicious Redemption:

  1. A user attempts to redeem shares worth 800 USD (exceeding the unlocked 700 USD).

  2. The flawed check incorrectly approves the withdrawal because 800 <= 300 evaluates to false.

  3. The vault transfers 800 USD to the user, overdrawing its liquidity by 100 USD.

Impact

Tools Used

Manual review.

Recommendation

The correct approach should have been recalculating the unlocked credit correctly and ensuring that the redeemed amount doesn't exceed it. The corrected condition should check if the delta (redeemed amount) is greater than the unlocked credit and revert if true.

+ SD59x18 unlockedCreditBeforeRedeemX18 = ctx.creditCapacityBeforeRedeemUsdX18.sub(
ctx.lockedCreditCapacityBeforeRedeemUsdX18.intoSD59x18()
);
+ SD59x18 deltaCreditX18 = ctx.creditCapacityBeforeRedeemUsdX18.sub(vault.getTotalCreditCapacityUsd());
+ if (deltaCreditX18.gt(unlockedCreditBeforeRedeemX18)) {
revert Errors.NotEnoughUnlockedCreditCapacity();
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

The check in VaultRouterBranch::redeem should be comparing remaining capacity against required locked capacity not delta against locked capacity

Support

FAQs

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