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

Vault's index token balances are systematically under-valued

Summary

When performing a deposit while there is an open position, the vault's index tokens are valued using the oracle's bid price which minimizes the value of all other share tokens, rather than using the ask price which would maximize the value of all other share tokens. The depositor is effectively given a discount at the expense of the other token holders.

Vulnerability Details

The _totalAmount() function is used during minting in order to determine the current over-all value of the vault prior to the user's deposit being applied. It uses the index token's .min price which is the bid price, rather than the .max price, which is the ask:

uint256 total = IERC20(indexToken).balanceOf(address(this)) * prices.indexTokenPrice.min / prices.shortTokenPrice.min
+ collateralToken.balanceOf(address(this))
+ positionData.netValue / prices.shortTokenPrice.min;

The bid price is definitionally always smaller than the ask price (for example for BTC, the spread is ~$100) but it could even be almost zero when there is market turmoil. Because this code path is being executed in the afterOrderExecution(), there is no validation of the prices GMX itself uses. Because the order itself is a market buy, GMX itself will use the .max price, so there is nothing from stopping the .min price from being very small.

Impact

Any index tokens held by the perpetual vault will be under-valued and the first user to perform a deposit each time tokens exist will get the discount.

There are multiple scenarios that can cause the vault to have an index token balance. For instance, every order claims funding fees and these fees may not have been collected for a long time, resulting in a significant loss to holders. Another way is when there's an ADL operation when the position's value is too large and GMX forcibly reduces it, sending back tokens. GMX may be unable to swap the pnl token for the collateral token, resulting in a large index token balance.

Tools Used

Manual review

Recommendations

- uint256 total = IERC20(indexToken).balanceOf(address(this)) * prices.indexTokenPrice.min / prices.shortTokenPrice.min
+ uint256 total = IERC20(indexToken).balanceOf(address(this)) * prices.indexTokenPrice.max / prices.shortTokenPrice.min
Updates

Lead Judging Commences

n0kto Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

Informational or Gas

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.

Appeal created

fuzzysquirrel Submitter
9 months ago
n0kto Lead Judge
8 months ago
n0kto Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

Informational or Gas

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.

Support

FAQs

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

Give us feedback!