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

Inconsistent Decimal Precision in Total Amount Calculation Leads to Incorrect Share Distribution and Token Withdrawals

Summary

The _totalAmount function in PerpetualVault.sol adds values with different decimal precisions (1e18 and 1e6) without proper normalization, resulting in incorrect total amount calculations. This affects share minting and withdrawal calculations, potentially causing users to receive incorrect amounts of shares or tokens.

Vulnerability Details

The _totalAmount function calculates the total value of the vault by adding three components:

  1. IndexToken value: IERC20(indexToken).balanceOf(address(this)) * prices.indexTokenPrice.min / prices.shortTokenPrice.min (1e18 decimals)

  2. Collateral token balance: collateralToken.balanceOf(address(this)) (collateralToken decimals)

  3. Position net value: positionData.netValue / prices.shortTokenPrice.min (collateralToken decimals)

Visual representation of the decimal precision issue:

Total = IndexToken_Value + CollateralToken_Balance + Position_NetValue
(1e18 decimals) + (collateral decimals 1e6) + (collateral decimals 1e6)

This inconsistency means values with different precisions are added directly, leading to incorrect proportions in the final sum.

Proof of Concept

Example scenario:

// Assume these values
indexToken.balanceOf(vault) = 1 * 1e18 // 1 token with 18 decimals
collateralToken.balanceOf(vault) = 1 * 1e6 // 1 token with 6 decimals
positionNetValue = 1 * 1e6 // 1 token equivalent with 6 decimals
// Current calculation
total = 1e18 + 1e6 + 1e6 = 1.000002 * 1e18 // Incorrect: collateral and position values appear 1000x smaller
// Expected calculation
total = (1e18 / 1e12) + 1e6 + 1e6 = 3 * 1e6 // Correct: all values normalized to 6 decimals

Impact Details

The incorrect total amount calculation severely impacts the vault's core functionality by causing users to receive incorrect share amounts during minting, ultimately compromising the vault's accounting accuracy and user fairness.

Recommendations

Normalize all values to match the collateral token's decimals before addition:

function _totalAmount(MarketPrices memory prices) internal view returns (uint256) {
if (positionIsClosed) {
return collateralToken.balanceOf(address(this));
} else {
IVaultReader.PositionData memory positionData = vaultReader.getPositionInfo(curPositionKey, prices);
// Get decimals of tokens
uint256 indexDecimals = IERC20(indexToken).decimals();
uint256 collateralDecimals = collateralToken.decimals();
// Convert indexToken value to collateral token decimals
uint256 indexValue = (IERC20(indexToken).balanceOf(address(this)) * prices.indexTokenPrice.min / prices.shortTokenPrice.min);
if (indexDecimals > collateralDecimals) {
indexValue = indexValue / (10 ** (indexDecimals - collateralDecimals));
}
uint256 total = indexValue
+ collateralToken.balanceOf(address(this))
+ positionData.netValue / prices.shortTokenPrice.min;
return total;
}
}

This solution dynamically adjusts for any collateral token's decimal precision, ensuring correct calculations regardless of the tokens involved.

Updates

Lead Judging Commences

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

invalid_prices_decimals

GMX github documentation: “Prices stored within the Oracle contract represent the price of one unit of the token using a value with 30 decimals of precision. Representing the prices in this way allows for conversions between token amounts and fiat values to be simplified, e.g. to calculate the fiat value of a given number of tokens the calculation would just be: token amount * oracle price, to calculate the token amount for a fiat value it would be: fiat value / oracle price.” Sponsor confirmed the keeper does the same, so price decimals change in function of the token, to be sure the above rule is true. Example for USDC (6 decimals): Prices will have 24 decimals → 1e6 * 1e24 = 1e30. Just a reminder for some submissions: shortToken == collateralTokens, so the decimals is 1e24 for shortToken prices.

Support

FAQs

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