DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: high
Invalid

LibUniswapOracle returns answer in Qoute's decimals instead of expected 1e6 precision

Summary

Protocol widely assumes price from oracle adapters has 1e6 precission which is explicitly defined in Natspec. However LibUniswapOracle has arbitrary precision depending on Quote token decimals.

For example let's have a look on LibUsdOracle.getUsdPrice() which is used to fetch price in Fertilizer. getUsdPrice() -> getTokenPriceFromExternal() -> LibUniswapOracle.getTwap()

function getUsdPrice(address token, uint256 lookback) internal view returns (uint256) {
...
// 1e18 * 1e6 = 1e24.
@> uint256 tokenPrice = getTokenPriceFromExternal(token, lookback);
if (tokenPrice == 0) return 0;
return uint256(1e24).div(tokenPrice);
}
function getTokenPriceFromExternal(
address token,
uint256 lookback
) internal view returns (uint256 tokenPrice) {
...
} else if (oracleImpl.encodeType == bytes1(0x02)) {
// assumes a dollar stablecoin is passed in
// if the encodeType is type 2, use a uniswap oracle implementation.
address chainlinkToken = IUniswapV3PoolImmutables(oracleImpl.target).token0();
chainlinkToken = chainlinkToken == token
? IUniswapV3PoolImmutables(oracleImpl.target).token1()
: token;
@> tokenPrice = LibUniswapOracle.getTwap(
lookback == 0 ? LibUniswapOracle.FIFTEEN_MINUTES : uint32(lookback),
oracleImpl.target,
chainlinkToken,
token,
uint128(10) ** uint128(IERC20Decimals(token).decimals())
);
// USDC/USD
// call chainlink oracle from the OracleImplmentation contract
Implementation memory chainlinkOracleImpl = s.sys.oracleImplementation[chainlinkToken];
address chainlinkOraclePriceAddress = chainlinkOracleImpl.target;
if (chainlinkOraclePriceAddress == address(0)) {
// use the chainlink registry
chainlinkOraclePriceAddress = ChainlinkPriceFeedRegistry(chainlinkRegistry).getFeed(
chainlinkToken,
0x0000000000000000000000000000000000000348
); // 0x0348 is the address for USD
}
@> uint256 chainlinkTokenPrice = LibChainlinkOracle.getTokenPrice(
chainlinkOraclePriceAddress,
LibChainlinkOracle.FOUR_HOUR_TIMEOUT,
lookback
);
@> return tokenPrice.mul(chainlinkTokenPrice).div(1e6);
}
...
}

As you can see returned value does not have 1e6 precission if LibUniswapOracle.getTwap() doesn't return so.

Vulnerability Details

OracleLibrary.sol by definition returns how much of token2 is required to buy oneToken amount of token1. As you can see getTwap() doesn't adjust decimals of returned answer.

function getTwap(
uint32 lookback,
address pool,
address token1,
address token2,
uint128 oneToken
) internal view returns (uint256 price) {
(bool success, int24 tick) = consult(pool, lookback);
if (!success) return 0;
@> price = OracleLibrary.getQuoteAtTick(tick, oneToken, token1, token2);
}

Impact

Tokens in Fertilizer are incorrectly priced when quote token is different than 6 decimals.

Tools Used

Manual Review

Recommendations

Normalize price in LibUniswapOracle by Qoute's decimals to have 1e6 precision. Also update logic in LibWstethEthOracle because it correctly handles 1e18 precision currently.

Updates

Lead Judging Commences

inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

T1MOH Submitter
11 months ago
inallhonesty Lead Judge
11 months ago
inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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