Status DataClose notification

OpenEden Disclosed Report

Liquidity Accounting Vulnerability: Any User Quota Deletion Leads to Incorrect Liquidity Distribution

Company
Created date
Oct 15 2025

Target

https://github.com/OpenEdenHQ/openeden.usdoexpress.audit/tree/f3f31d2ac15e3253cba342229f9d05495f95d6fd

Vulnerability Details

Contract: LiquidityController.sol
Location: https://github.com/OpenEdenHQ/openeden.usdoexpress.audit/blob/main/contracts/extensions/redemption/LiquidityController.sol#L109-L111 Severity: CRITICAL
Type: Phantom Liquidity Creation / Over-consumption

Vulnerability Description

Any removal of a user's quota (setting userQuota to 0) creates phantom liquidity by incorrectly decrementing the totalUsed counter. This breaks the fundamental accounting invariant and allows the system to allocate more liquidity than physically available.

Root Cause

When removing a user's quota via setUserQuota(user, 0), the contract incorrectly decrements totalUsed by the user's usedQuota amount:

// LiquidityController.sol, lines 106-113
} else {
    // Removing quota - also clear used quota
    authorizedUsers[user] = false;
    if (usedQuota > 0) {
        totalUsed = totalUsed - usedQuota;  // ❌ BUG HERE
        usedQuotas[user] = 0;
    }
}

The Problem: The code assumes that used liquidity "returns" to the pool when a quota is removed. However, this liquidity was already physically spent through UsycRedemption contract and does NOT return to the pool.

Impact

  • Phantom Liquidity Creation: Any quota deletion creates "fake" available liquidity that doesn't physically exist
  • Over-consumption: Allows spending 20-100%+ beyond the totalLiquidity limit
  • Incorrect Liquidity Distribution: System allocates more quotas than physically available
  • Silent Accumulation: Phantom liquidity accumulates during normal operations

Accumulation Effect

Day 1: 1 user removed → 50 USYC phantom liquidity
Day 2: 2 users removed → 100 USYC phantom liquidity  
Day 3: 3 users removed → 150 USYC phantom liquidity
...
Month: 30 users removed → 1,500 USYC phantom liquidity 💸

Recommended Fixes

Fix 1: Do Not Automatically Decrement totalUsed (Recommended)

Current problematic code:

} else {
    // Removing quota - also clear used quota
    authorizedUsers[user] = false;
    if (usedQuota > 0) {
        totalUsed = totalUsed - usedQuota;  // ❌ REMOVE THIS LINE
        usedQuotas[user] = 0;
    }
}

Fixed code:

} else {
    // Removing quota - DON'T automatically restore used quota
    authorizedUsers[user] = false;
    // Keep usedQuota in totalUsed - it was really consumed!
    // Owner must manually call restoreLiquidity() if appropriate
    usedQuotas[user] = 0;  // Clear user's record only
}

Key principle: totalUsed should always reflect really spent liquidity, not just sum of current usedQuotas of active users.

Validation steps

Partial Quota Usage - The Most Common Case

Description: User consumes only part of their quota before removal. This is the most realistic scenario as users rarely use 100% of allocated quotas.

Test: test_CRITICAL_PartialUsage_StillVulnerable() File: LiquidityControllerAccountingBug.t.sol

Simple Example, I also showed the whole path in the diagram below.

uploaded-image

Setup:

totalLiquidity = 1,000 USYC
Alice quota = 100, Bob quota = 60
totalAllocated = 160, totalUsed = 0

Step 1: Alice uses 50 USYC (partial)

reserveLiquidity(Alice, 50)
totalUsed = 50
Physical: 50 USYC GONE (converted to USDC)

Step 2: Admin removes Alice (BUG)

setUserQuota(Alice, 0)
Code: totalUsed = 50 - 50 = 0 ❌
Contract thinks: 50 USYC "returned"
Reality: 50 USYC still GONE

Step 3: Admin adds Carol

setUserQuota(Carol, 940)
Check: 60 + 940 ≤ 1,000 ✅ PASSES
totalAllocated = 1,000

Step 4: PROBLEM

Carol needs: 940 USYC
Available: 890 USYC (1K - 50 Alice - 60 Bob)
SHORTAGE: 50 USYC ❌

Result:

Admin can add to Carol: 940 ❌ WRONG
Admin only can add: 890 ✅ CORRECT  
But checks passed ❌

BUG: totalUsed incorrectly decremented on deletion
RESULT: 50 USYC phantom liquidity created

Why This Is Legitimate Admin Behavior

This scenario involves zero admin mistakes. Every action is standard operational procedure:

  1. Setting Quotas - Standard portfolio management
  2. Partial Usage - Users rarely use 100% of quota (normal)
  3. Removing User - Alice leaves program early (legitimate)
  4. Reallocating to New User - Admin wants to use "available" quota

Admin is doing exactly what the system appears to allow and what makes business sense, but the system silently creates phantom liquidity during this normal flow.

Test Implementation

File: test/LiquidityControllerAccountingBug.t.sol
Test Function: test_CRITICAL_PartialUsage_StillVulnerable()

Run Test:

forge test --match-test test_CRITICAL_PartialUsage_StillVulnerable -vv

Expected Output:

[PASS] test_CRITICAL_PartialUsage_StillVulnerable()
  Contract thinks: totalUsed = 1,000 USYC ✅
  Reality: 1,050 USYC consumed ❌
  EXCESS: 50 USYC (5%)

Summary

This vulnerability demonstrates how any user quota deletion creates phantom liquidity by incorrectly decrementing the totalUsed counter. The simple example above shows:

  • 5% over-consumption with just partial usage (1,050 vs 1,000 limit)
  • Legitimate admin operations trigger the bug
  • Silent accumulation during normal business flow
  • No error messages - system appears to work correctly

The bug is inevitable in any system with user quota management and rotation, making it a critical design flaw rather than an edge case.


Attachments

hidden
CommentsReport History
Comments on this report are hidden
Details
Statedisclosed
Severity
High
Bounty$300
Visibilitypartially
VulnerabilityBlockchain
Participants (4)
company admin
triage team
author
company admin