Platform: Code4rena
Start Date: 10/11/2023
Pot Size: $28,000 USDC
Total HM: 5
Participants: 185
Period: 5 days
Judge: 0xDjango
Id: 305
League: ETH
Rank: 182/185
Findings: 1
Award: $2.76
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: m_Rassska
Also found by: 0x1337, 0xAadi, 0xHelium, 0xLeveler, 0xblackskull, 0xbrett8571, 0xepley, 0xffchain, 0xluckhu, 0xmystery, 0xrugpull_detector, 0xvj, ABAIKUNANBAEV, Aamir, AerialRaider, Amithuddar, Bauchibred, Bauer, CatsSecurity, Cryptor, Daniel526, Draiakoo, Eigenvectors, ElCid, GREY-HAWK-REACH, Inspecktor, Juntao, King_, LinKenji, Madalad, MaslarovK, Matin, MatricksDeCoder, McToady, Noro, PENGUN, Pechenite, Phantasmagoria, RaoulSchaffranek, SBSecurity, SandNallani, Shaheen, Soul22, Stormreckson, T1MOH, Tadev, TeamSS, TheSchnilch, Topmark, Tumelo_Crypto, Udsen, Yanchuan, ZanyBonzy, _thanos1, adeolu, adriro, alexfilippov314, almurhasan, amaechieth, anarcheuz, ayden, baice, bareli, boredpukar, bronze_pickaxe, btk, cartlex_, catellatech, chaduke, cheatc0d3, circlelooper, codynhat, crack-the-kelp, critical-or-high, debo, deepkin, desaperh, dipp, eeshenggoh, evmboi32, ge6a, gesha17, glcanvas, gumgumzum, hals, hihen, hunter_w3b, jasonxiale, joaovwfreire, ke1caM, leegh, lsaudit, marchev, merlinboii, niser93, osmanozdemir1, paritomarrr, passion, pep7siup, phoenixV110, pipidu83, poneta, ro1sharkm, rouhsamad, rvierdiiev, sakshamguruji, seerether, shealtielanz, soliditytaker, spark, squeaky_cactus, stackachu, supersizer0x, tallo, taner2344, turvy_fuzz, twcctop, ubl4nk, wisdomn_, xAriextz, zach, zhaojie, zhaojohnson, ziyou-
2.7592 USDC - $2.76
https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L141 https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L152 https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L109 https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTOracle.sol#L63-L78 https://github.com/code-423n4/2023-11-kelp/blob/main/src/oracles/ChainlinkPriceOracle.sol#L37-L39
The LRTDepositPool.getRsETHAmountToMint
function is used to calculate the amount of rsEth
tokens to mint when a particular asset is deposited via LRTDepositPool.depositAsset
function. The lrtOracle.getRSETHPrice()
function is called for the rsEth amount calculation as shown below:
rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice(); //@audit - check these external functions being called
The lrtOracle.getRSETHPrice()
function in its execution flow gets the price for each of the assets in the supportedAssets
array by calling the LRTOracle.getAssetPrice
function as shown below:
address[] memory supportedAssets = lrtConfig.getSupportedAssetList(); //@audit-info - get the supported asset list from the config contract uint256 supportedAssetCount = supportedAssets.length; //@audit-info - get the supported assets list length for (uint16 asset_idx; asset_idx < supportedAssetCount;) { address asset = supportedAssets[asset_idx]; uint256 assetER = getAssetPrice(asset); //@audit-info - get the asset price for each of the assets in the supported asset list uint256 totalAssetAmt = ILRTDepositPool(lrtDepositPoolAddr).getTotalAssetDeposits(asset); //@audit-info - get the total asset amount from all three contracts totalETHInPool += totalAssetAmt * assetER; //@audit-info - increment the total asset amount in the contract via the ETH amount (because it is common denominator) unchecked { ++asset_idx; } //@audit-info - increment the index by one } return totalETHInPool / rsEthSupply;
In each of the above iterations in the for
loop in supportedAssets array, the getAssetPrice
function calls the chainlink price feed of the given asset to receive the current price of that asset as shown below:
function getAssetPrice(address asset) public view onlySupportedAsset(asset) returns (uint256) { return IPriceFetcher(assetPriceOracle[asset]).getAssetPrice(asset); //@audit-info - get the asset price from the oracle }
The ChainlinkPriceOracle.getAssetPrice
implemenation is as follows:
function getAssetPrice(address asset) external view onlySupportedAsset(asset) returns (uint256) { return AggregatorInterface(assetPriceFeed[asset]).latestAnswer(); }
The issue here is that Chainlink's multisigs
can immediately block access to price feeds
at will. If this happens to any of the price feeds of the supported assets
in the protocol the ChainlinkPriceOracle.getAssetPrice
function will revert while calling latestAnswer
for that blocked price feed. If a new pricefeed is not registered for that asset
by chainlink immediately the calls to the blocked price feed
will always revert.
The only other option left is to remove this particular asset (of which the pricefeed was blocked by chainlink) from the LRTConfig.supportedAssetList
array. This way this particular asset will not be considered in the lrtOracle.getRSETHPrice()
function for rsEth
price calculation. But there is no functionality to remove an already added supported asset from the supportedAssetList
array in the LRTConfig
contract.
As a result this will revert the entire LRTDepositPool.depositAsset
function (since the depositAsset
function calls the lrtOracle.getRSETHPrice()
in its execution flow) thus DoS
the asset deposit functionality of the LRTDepositPool
contract.
uint256 rsethAmountMinted = _mintRsETH(asset, depositAmount);
https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L141
(rsethAmountToMint) = getRsETHAmountToMint(_asset, _amount);
https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L152
rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice();
https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L109
address[] memory supportedAssets = lrtConfig.getSupportedAssetList(); uint256 supportedAssetCount = supportedAssets.length; for (uint16 asset_idx; asset_idx < supportedAssetCount;) { address asset = supportedAssets[asset_idx]; uint256 assetER = getAssetPrice(asset); uint256 totalAssetAmt = ILRTDepositPool(lrtDepositPoolAddr).getTotalAssetDeposits(asset); totalETHInPool += totalAssetAmt * assetER; unchecked { ++asset_idx; } } return totalETHInPool / rsEthSupply;
https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTOracle.sol#L63-L78
function getAssetPrice(address asset) external view onlySupportedAsset(asset) returns (uint256) { return AggregatorInterface(assetPriceFeed[asset]).latestAnswer(); }
https://github.com/code-423n4/2023-11-kelp/blob/main/src/oracles/ChainlinkPriceOracle.sol#L37-L39
Manual Review and VSCode
Hence it is recommended to implement following remedies to solve the above vulnerability:
The call to the latestAnswer
(even this function should be replaced by the latestRoundData
function) in the ChainlinkPriceOracle.getAssetPrice
function, should be surrounded by the solidity's try/catch
block. In a scenario where the call to latestAnswer
reverts, the caller contract is still in control and can use the catch block
to call a fallback oracle
or handle
the error
in any other suitable way
which is safe and explicit.
Implement a functionality in the LRTConfig
contract which would allow to remove an already supportedAsset of the protocol if the need arises. This way if a particular price feed of a supported asset is blocked by chainlink and any new chainlink price feed is not added immediately to that asset, this particular supported asset can be removed from the LRTConfig.supportedAssetList
array thus preventing the LRTDepositPool.depositAsset
functionality being DoS
.
DoS
#0 - c4-pre-sort
2023-11-16T22:15:15Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-11-16T22:15:22Z
raymondfam marked the issue as duplicate of #32
#2 - c4-pre-sort
2023-11-17T04:46:31Z
raymondfam marked the issue as not a duplicate
#3 - c4-pre-sort
2023-11-17T04:46:57Z
raymondfam marked the issue as duplicate of #878
#4 - c4-pre-sort
2023-11-17T07:33:21Z
raymondfam marked the issue as high quality report
#5 - c4-pre-sort
2023-11-17T07:33:27Z
raymondfam marked the issue as not a duplicate
#6 - c4-pre-sort
2023-11-17T07:33:33Z
raymondfam marked the issue as primary issue
#7 - c4-sponsor
2023-11-24T11:30:21Z
manoj9april (sponsor) disputed
#8 - manoj9april
2023-11-24T11:30:22Z
This is very rare to happen. Still if something of this happens, we have facility to pause the protocol and update chainlink price feed or even use some other priceFeed by upgrading contracts.
#9 - fatherGoose1
2023-12-01T17:38:21Z
QA. I agree with sponsor that this is extremely unlikely to occur. Given the unlikeliness, it is an acceptable course of action to pause the contract and update the implementation. The DOS of deposit functionality does not impose any risk to funds.
#10 - c4-judge
2023-12-01T17:38:33Z
fatherGoose1 changed the severity to QA (Quality Assurance)
#11 - c4-judge
2023-12-01T17:38:38Z
fatherGoose1 marked the issue as grade-b