Part 2

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

Flawed Credit Capacity Validation in `VaultRouterBranch` Blocks Valid Withdrawals

Summary

A critical vulnerability exists in the VaultRouterBranch.redeem() function where the credit capacity validation logic is implemented backwards, causing it to block legitimate withdrawals even when sufficient collateral would remain locked. The issue stems from comparing capacity reduction against locked amount instead of verifying remaining capacity adequacy.

Vulnerability Details

The protocol implements a safety mechanism requiring a portion of vault assets to remain locked as collateral for credit obligations. This is managed through a lockedCreditRatio parameter.

The flawed validation appears in VaultRouterBranch.sol:

// In redeem():
if (
ctx.creditCapacityBeforeRedeemUsdX18.sub(vault.getTotalCreditCapacityUsd()).lte(
ctx.lockedCreditCapacityBeforeRedeemUsdX18.intoSD59x18()
)
) {
revert Errors.NotEnoughUnlockedCreditCapacity();
}

The validation attempts to ensure sufficient capacity remains locked by:

  1. Calculating capacity reduction: creditCapacityBeforeRedeem - getTotalCreditCapacityUsd()

  2. Comparing reduction against locked amount: reduction <= lockedCapacity

  3. Reverting if the condition is true

This logic is fundamentally flawed because:

Wrong Comparison Target:

  • Current: Checks if withdrawal amount ≤ locked amount

  • Correct: Should check if remaining capacity ≥ locked amount

Backwards Logic:

  • Current: Reverts when withdrawal ≤ locked (should be safe)

  • Correct: Should revert when remaining L) are allowed
    Which is opposite of desired safety property!

Scenario 1: Safe Withdrawal Gets Blocked


Initial State:

  • Total Capacity = 1000 USD

  • Locked Ratio = 25%

  • Locked Amount = 250 USD

  • Withdrawal = 200 USD

Validation:

  1. Capacity Reduction = 1000 - 800 = 200 USD

  2. Check: 200 ≤ 250

  3. Result: True → REVERTS

But this should be allowed because:

  • Remaining = 800 USD

  • Required Lock = 250 USD

  • 800 > 250 ✓ (Safe!)

Scenario 2: Unsafe Withdrawal Gets Allowed


Initial State:

  • Total Capacity = 1000 USD

  • Locked Ratio = 25%

  • Locked Amount = 250 USD

  • Withdrawal = 800 USD

Validation:

  1. Capacity Reduction = 1000 - 200 = 800 USD

  2. Check: 800 ≤ 250

  3. Result: False → ALLOWS

But this should revert because:

  • Remaining = 200 USD

  • Required Lock = 250 USD

  • 200 < 250 ✗ (Unsafe!)

Impact

The flawed validation has two critical consequences:

  1. Breaks withdrawals: Users cannot redeem funds even when sufficient unlocked capacity exists

  2. Compromises safety: Large withdrawals that should be blocked (leaving insufficient locked capacity) are allowed through

Recommendations :

  • Replace the current check with proper remaining capacity validation:

function redeem(uint128 vaultId, uint128 withdrawalRequestId, uint256 minAssets) external {
// ... existing code ...
- if (
- ctx.creditCapacityBeforeRedeemUsdX18.sub(vault.getTotalCreditCapacityUsd()).lte(
- ctx.lockedCreditCapacityBeforeRedeemUsdX18.intoSD59x18()
- )
- ) {
- revert Errors.NotEnoughUnlockedCreditCapacity();
- }
+ if (vault.getTotalCreditCapacityUsd() < ctx.lockedCreditCapacityBeforeRedeemUsdX18.intoSD59x18()) {
+ revert Errors.NotEnoughUnlockedCreditCapacity();
+ }
// ... rest of the function ...
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 6 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.