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

Incorrect decimal handling in the `KeeperProxy::_check` function.

Summary

The KeeperProxy::_check function contains incorrect logic for decimal calculation and the price difference check.

Vulnerability Details

The expression decimals = 30 - IERC20Meta(token).decimals(); subtracts the token's decimals from 30 and stores the result in decimals.

Then, decimals is further subtracted by 8 and raised to the power of 10.

then the price of token is divided by the 10 power of (decimals - 8),

The entire logic is incorrect and returns a price value that differs from the Chainlink price.

If the decimals of price and chainLinkPrice are not equal, the require statement will always revert.

function _check(address token, uint256 price) internal view {
// https://github.com/code-423n4/2021-06-tracer-findings/issues/145
(, int chainLinkPrice, , uint256 updatedAt, ) = AggregatorV2V3Interface(dataFeed[token]).latestRoundData();
require(updatedAt > block.timestamp - maxTimeWindow[token], "stale price feed");
uint256 decimals = 30 - IERC20Meta(token).decimals();
price = price / 10 ** (decimals - 8); // Chainlink price decimals is always 8.
require(
_absDiff(price, chainLinkPrice.toUint256()) * BPS / chainLinkPrice.toUint256() < priceDiffThreshold[token],
"price offset too big"
);
}

https://github.com/CodeHawks-Contests/2025-02-gamma/blob/84b9da452fc84762378481fa39b4087b10bab5e0/contracts/KeeperProxy.sol#L188

Proof of code :-

  1. first case [ Token has 18 decimals ]

    // uint256 decimals = 30 - IERC20Meta(token).decimals();
    decimals = 30 - 18 = 12
    1. //price = price / 10 ** (decimals - 8); [if the price has 18 decimals]
    price = price with 18 decimals / 1e4
    price will have 14 decimals which is not equal to 8 decimals of chainlink.
    2. //price = price / 10 ** (decimals - 8); [if the price has 8 decimals]
    price = price with 8 decimals / 1e4
    price will have 4 decimals which is not equal to 8 decimals of chainlink.
  2. second case [ Token with 12 decimals ]

    // uint256 decimals = 30 - IERC20Meta(token).decimals();
    decimals = 30 - 12 = 18
    1. //price = price / 10 ** (decimals - 8); [if the price has 12 decimals]
    price = price with 12 decimals / 1e10
    price will have 2 decimals which is not equal to 8 decimals of chainlink.
    2. //price = price / 10 ** (decimals - 8); [if the price has 8 decimals]
    price = price with 8 decimals / 1e10
    price will be 0 decimals and zero value which is not equal to 8 decimals of chainlink.

Impact

Incorrect calculation of decimals will cause the require statement to revert unintentionally.

Incorrect decimal handling can cause unintended reverts in the require statement, leading to failures in price validation.

Tools Used

Manual Review

Recommendations

Implement the correct logic for decimal calculation to ensure accurate price checks.

Updates

Lead Judging Commences

n0kto Lead Judge 9 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.

Give us feedback!