DeFiFoundry
50,000 USDC
View results
Submission Details
Severity: low
Invalid

USDC Depegging Induces Share Mispricing and Vault Insolvency

Summary

The protocol currently assumes a fixed peg of $1.00 per USDC for deposit and withdrawal calculations. However, if USDC depegs—even by a few percentage points—the share pricing mechanism becomes inaccurate.

Using historical observed values for USDC (high = 1.0285, low = 0.9679), a depositor’s effective value may deviate by around ±3% from the actual market value. While a 3% deviation might seem marginal under normal conditions, this mis-pricing could compound over time or worsen under more severe depeg events, potentially draining vault collateral and undermining depositor fairness.

Vulnerability Details

The vault’s accounting system assigns shares based on the assumption that every USDC is worth exactly $1.00. Referencing the _mint() function from the PerpetualVault contract:-

/**
* @notice this function is an end of deposit flow.
* @dev should update all necessary global state variables
*
* @param depositId `depositId` of mint operation
* @param amount actual deposit amount. if `_isLongOneLeverage` is `true`, amount of `indexToken`, or amount of `collateralToken`
*/
function _mint(uint256 depositId, uint256 amount, bool refundFee, MarketPrices memory prices) internal {
uint256 _shares;
if (totalShares == 0) {
_shares = depositInfo[depositId].amount * 1e8;
} else {
uint256 totalAmountBefore;
if (positionIsClosed == false && _isLongOneLeverage(beenLong)) {
totalAmountBefore = IERC20(indexToken).balanceOf(address(this)) - amount;
} else {
totalAmountBefore = _totalAmount(prices) - amount;
}
if (totalAmountBefore == 0) totalAmountBefore = 1;
_shares = amount * totalShares / totalAmountBefore;
}
depositInfo[depositId].shares = _shares;
totalShares = totalShares + _shares;
if (refundFee) {
uint256 usedFee = callbackGasLimit * tx.gasprice;
if (depositInfo[counter].executionFee > usedFee) {
try IGmxProxy(gmxProxy).refundExecutionFee(depositInfo[counter].owner, depositInfo[counter].executionFee - usedFee) {} catch {}
}
}
emit Minted(depositId, depositInfo[depositId].owner, _shares, amount);
}

Flawed Part of the Code:-

if (totalShares == 0) {
_shares = amount;
}

In practice, if USDC’s market value deviates from this peg, the following differences occur:

--> When USDC Trades High (e.g., historical high of USD 1.0285):

  • A user depositing 1,000 USDC should have a market value of $1,028.50.

  • However, if the share allocation is based on a fixed $1.00 peg, the user will be allocated shares representing only $1,000.

  • Upon withdrawal, they receive less than the market value of their deposit.

--> When USDC Trades Low (e.g., historical low of USD 0.9679):

  • A 1,000 USDC deposit is actually worth $967.90.

  • Yet, if the withdrawal calculation still assumes $1.00 per USDC, the user redeems shares for $1,000—effectively gaining an extra $32.10 (approximately 3.32%) over the true collateral value.

This misalignment in share pricing results from using a fixed peg in both deposit and withdrawal formulas. The vulnerability becomes particularly dangerous during extreme depeg events, where the percentage error could be significantly higher, leading to systemic collateral drain or insolvency.

Impact

Persistent mispricing can accumulate over multiple transactions. In a severe depeg event, this could force the vault into insolvency as the collateral drained from overvalued redemptions overwhelms incoming deposits.

Tools Used

Manual Review

Recommendations

Adjust the formulas so that both deposit and redemption values reflect the actual market price of USDC. For example, if a user deposits 1,000 USDC and the oracle reports a price of 0.9679, assign shares based on a $967.90 value. Similarly, during withdrawals, use the oracle price to calculate the redeemable value.

--> Using a dynamic pricing module

uint256 usdcPrice = ChainlinkOracle(USDC_USD).latestAnswer();
uint256 shares = (amount * 1e8) / usdcPrice;

--> Utilizing a circuit Breaker

if (usdcPrice < 0.98e8 || usdcPrice > 1.02e8) {
pauseDepositsAndWithdrawals();
}
Updates

Lead Judging Commences

n0kto Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

Suppositions

There is no real proof, concrete root cause, specific impact, or enough details in those submissions. Examples include: "It could happen" without specifying when, "If this impossible case happens," "Unexpected behavior," etc. Make a Proof of Concept (PoC) using external functions and realistic parameters. Do not test only the internal function where you think you found something.

Support

FAQs

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

Give us feedback!