Platform: Code4rena
Start Date: 04/03/2024
Pot Size: $88,500 USDC
Total HM: 31
Participants: 105
Period: 11 days
Judge: ronnyx2017
Total Solo HM: 7
Id: 342
League: ETH
Rank: 49/105
Findings: 2
Award: $184.22
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: JecikPo
Also found by: KupiaSec, SpicyMeatball, kennedy1030, kfx, linmiaomiao, t4sk
92.1136 USDC - $92.11
when using 18 decimals token as (TWAP) reference token, token price above 19 of chainlink reference token will overflow and revert in getValue
,and cause many vault function can't use.
It will cause function getValue
of oracle revert,and cause many function using _requireLoanIsHealthy
or _checkLoanIsHealthy
to getValue
can't use.
It include function transform
,borrow
,decreaseLiquidityAndCollect
and liquidate
.User can't transform loan,borrow asset decrease liiquidity and liquadate portion.
For convenience, we use DAI as (TWAP) reference token(18 decimals) and 0 address (USD) as chainlink reference token,
In _getReferenceTokenPriceX96,it calculate price in Q96:
function _getReferenceTokenPriceX96(address token, uint256 cachedChainlinkReferencePriceX96) internal view returns (uint256 priceX96, uint256 chainlinkReferencePriceX96) { if (token == referenceToken) { return (Q96, chainlinkReferencePriceX96); } TokenConfig memory feedConfig = feedConfigs[token]; if (feedConfig.mode == Mode.NOT_SET) { revert NotConfigured(); } uint256 verifyPriceX96; bool usesChainlink = ( feedConfig.mode == Mode.CHAINLINK_TWAP_VERIFY || feedConfig.mode == Mode.TWAP_CHAINLINK_VERIFY || feedConfig.mode == Mode.CHAINLINK ); bool usesTWAP = ( feedConfig.mode == Mode.CHAINLINK_TWAP_VERIFY || feedConfig.mode == Mode.TWAP_CHAINLINK_VERIFY || feedConfig.mode == Mode.TWAP ); if (usesChainlink) { uint256 chainlinkPriceX96 = _getChainlinkPriceX96(token); chainlinkReferencePriceX96 = cachedChainlinkReferencePriceX96 == 0 ? _getChainlinkPriceX96(referenceToken) : cachedChainlinkReferencePriceX96; chainlinkPriceX96 = (10 ** referenceTokenDecimals) * chainlinkPriceX96 * Q96 / chainlinkReferencePriceX96 / (10 ** feedConfig.tokenDecimals); if (feedConfig.mode == Mode.TWAP_CHAINLINK_VERIFY) { verifyPriceX96 = chainlinkPriceX96; } else { priceX96 = chainlinkPriceX96; } } if (usesTWAP) { uint256 twapPriceX96 = _getTWAPPriceX96(feedConfig); if (feedConfig.mode == Mode.CHAINLINK_TWAP_VERIFY) { verifyPriceX96 = twapPriceX96; } else { priceX96 = twapPriceX96; } } if (feedConfig.mode == Mode.CHAINLINK_TWAP_VERIFY || feedConfig.mode == Mode.TWAP_CHAINLINK_VERIFY) { _requireMaxDifference(priceX96, verifyPriceX96, feedConfig.maxDifference); } }
there is the revert code:
chainlinkPriceX96 = (10 ** referenceTokenDecimals) * chainlinkPriceX96 * Q96 / chainlinkReferencePriceX96 / (10 ** feedConfig.tokenDecimals);
in this formulation,any token price upper 19 USD will overflow,because:
and :
So :
Therefore the previous multiplication operation will cause overflow, and the price query function getValue
will revert:
function getValue(uint256 tokenId, address token) external view override returns (uint256 value, uint256 feeValue, uint256 price0X96, uint256 price1X96) { ... (price0X96, cachedChainlinkReferencePriceX96) = _getReferenceTokenPriceX96(token0, cachedChainlinkReferencePriceX96); (price1X96, cachedChainlinkReferencePriceX96) = _getReferenceTokenPriceX96(token1, cachedChainlinkReferencePriceX96); uint256 priceTokenX96; if (token0 == token) { priceTokenX96 = price0X96; } else if (token1 == token) { priceTokenX96 = price1X96; } else { (priceTokenX96,) = _getReferenceTokenPriceX96(token, cachedChainlinkReferencePriceX96); } ... }
It will cause transform
,borrow
,decreaseLiquidityAndCollect
and liquidate
can't use.Because bad debt cannot be liquidated, so I think it is high severity.
One possible liquidation scenario is that the postion NFT‘s token0 price rises above 19USD, and token1 falls (still less than 18USD), which then causes the entire position NFT's collateral value to drop to the liquidation standard. Since the price of token1 will cause revert during liquidation, the user's postion NFT cannot be liquidated in time.
By the way,i don't think this is an issue caused by an administrator configuration error,because I didn't find any configuration about reference token in the white paper and c4 page.At the same time, 18 decimals are more commonly used in standard ERC20 tokens than other decimals tokens,It should be taken into account.
Use the FullMath.sol in the uniswap v3 library to perform 512-bit calculations(for example,mulDiv) to prevent overflow.
Under/Overflow
#0 - c4-pre-sort
2024-03-22T07:24:38Z
0xEVom marked the issue as duplicate of #409
#1 - c4-pre-sort
2024-03-22T07:24:41Z
0xEVom marked the issue as sufficient quality report
#2 - c4-judge
2024-03-31T14:51:09Z
jhsagd76 changed the severity to 2 (Med Risk)
#3 - c4-judge
2024-04-01T15:11:08Z
jhsagd76 marked the issue as satisfactory
🌟 Selected for report: Aymen0909
Also found by: KupiaSec, Topmark, befree3x, kennedy1030, linmiaomiao, pynschon
92.1136 USDC - $92.11
In V3vault deposit, lend share instead of amount is mistakenly used to check whether the globalLendLimit is exceeded. As time increases, the total max available deposit amount will become more and more.
As time increases, the total max available deposit amount will become more and more.
In white paper:
globalLendLimit: Limits the total lending token amount that can be deposited. It limits new deposits but does not affect existing ones.
and it is used correctly in maxDeposit
and maxMint
:
function maxDeposit(address) external view override returns (uint256) { (, uint256 lendExchangeRateX96) = _calculateGlobalInterest(); uint256 value = _convertToAssets(totalSupply(), lendExchangeRateX96, Math.Rounding.Up); if (value >= globalLendLimit) { return 0; } else { return globalLendLimit - value; } }
/// @inheritdoc IERC4626 function maxMint(address) external view override returns (uint256) { (, uint256 lendExchangeRateX96) = _calculateGlobalInterest(); uint256 value = _convertToAssets(totalSupply(), lendExchangeRateX96, Math.Rounding.Up); if (value >= globalLendLimit) { return 0; } else { return _convertToShares(globalLendLimit - value, lendExchangeRateX96, Math.Rounding.Down); } }
but it used incorrectly in function _deposit
:
function _deposit(address receiver, uint256 amount, bool isShare, bytes memory permitData) internal returns (uint256 assets, uint256 shares) { ··· if (totalSupply() > globalLendLimit) { revert GlobalLendLimit(); } if (assets > dailyLendIncreaseLimitLeft) { revert DailyLendIncreaseLimit(); } else { dailyLendIncreaseLimitLeft -= assets; } emit Deposit(msg.sender, receiver, assets, shares); }
because vault is positive rebase token,one share represents more and more amount as time increases.So in fixed globalLendLimit,the total max available deposit amount will become more and more with time increases。
This error breaks the protocol's lend limit, so I consider it is a medium severity finding.
vscode、foundary
Use this in check:
function _deposit(address receiver, uint256 amount, bool isShare, bytes memory permitData) internal returns (uint256 assets, uint256 shares) { ··· uint256 totalAsset = _convertToAssets(totalSupply(), newLendExchangeRateX96, Math.Rounding.Up); if (totalAsset > globalLendLimit) { revert GlobalLendLimit(); } if (assets > dailyLendIncreaseLimitLeft) { revert DailyLendIncreaseLimit(); } else { dailyLendIncreaseLimitLeft -= assets; } emit Deposit(msg.sender, receiver, assets, shares); }
Error
#0 - c4-pre-sort
2024-03-18T19:02:41Z
0xEVom marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-03-18T19:03:45Z
0xEVom marked the issue as duplicate of #324
#2 - c4-judge
2024-04-01T07:41:29Z
jhsagd76 marked the issue as satisfactory