Summary
When GMX Oracle uses Chainlink, the received values (usually 8 decimal) from Chainlink have to be normalized to 30 decimal . Since GMX works with 30 decimal, but when _getTokenPriceMinMaxFormatted()
is called the result is not as expected.
Vulnerability Details
_getTokenPriceMinMaxFormatted()
successfully calls Chainlink and when it tries to nromalize the value to from 30 decimal can result some unexpected results, as you can see below:
function _getTokenPriceMinMaxFormatted(address token) internal view returns (uint256) {
(int256 _price, uint8 _priceDecimals) = chainlinkOracle.consult(token);
return uint256(_price) * 10 ** (30 - IERC20Metadata(token).decimals() - _priceDecimals);
}
For example:
Call WBTC price feed Chainlink
WBTC = 8 decimal e.g. 35.000,00000000
_price = 3500000000000
_priceDecimals = 8
result = uint256(_price) * 10 ** (30 - IERC20Metadata(token).decimals() - _priceDecimals);
result = 3500000000000 * 10 ** (30 - 8 - 8)
result = 35 * 10 ** 8 * 10 ** 14
result = 35 * 10 ** 22
The result has to be 35 * 10 ** 30.
Call WETH price feed Chainlink
WETH = 8 decimal e.g. 35.000,00000000
_price = 200000000000
_priceDecimals = 8
result = uint256(_price) * 10 ** (30 - IERC20Metadata(token).decimals() - _priceDecimals);
result = 200000000000 * 10 ** (30 - 18 - 8)
result = 35 * 10 ** 8 * 10 ** 6
result = 35 * 10 ** 14
For different pairs, it returns different decimals
Impact
High, every request in GMX Oracle returns a wrong value.
Tools Used
Manual code review
Recommendations
Set the correct equation for normalizable to 1e30 as:
function _getTokenPriceMinMaxFormatted(address token) internal view returns (uint256) {
(int256 _price, uint8 _priceDecimals) = chainlinkOracle.consult(token);
- return uint256(_price) * 10 ** (30 - IERC20Metadata(token).decimals() - _priceDecimals);
+ return uint256(_price) * 10 ** (30 - _priceDecimals);
}