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: 101/185
Findings: 2
Award: $7.42
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Krace
Also found by: 0xDING99YA, 0xrugpull_detector, Aamir, AlexCzm, Aymen0909, Banditx0x, Bauer, CatsSecurity, GREY-HAWK-REACH, Madalad, Phantasmagoria, QiuhaoLi, Ruhum, SBSecurity, SandNallani, SpicyMeatball, T1MOH, TheSchnilch, adam-idarrha, adriro, almurhasan, ast3ros, ayden, bronze_pickaxe, btk, chaduke, ck, crack-the-kelp, critical-or-high, deth, gumgumzum, jasonxiale, joaovwfreire, ke1caM, m_Rassska, mahdirostami, mahyar, max10afternoon, osmanozdemir1, peanuts, pep7siup, peter, ptsanev, qpzm, rouhsamad, rvierdiiev, spark, twcctop, ubl4nk, wisdomn_, zach, zhaojie
4.6614 USDC - $4.66
https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L71 https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTOracle.sol#L52
Impact
The vulnerability allows users to manipulate the rseth price by directly transferring assets to the deposit pool or node delegator contracts.
By increasing the totalAssetAmt
used in the price calculation, users can artificially inflate the rseth price and potentially earn profits from this manipulation.
Proof of Concept
For example, let's assume the current asset price is denoted as p
. Now, a user transfers an amount x
of assets to the deposit pool.
User's cost for the transferred assets would be p*x
.
Before the transfer, the rseth total supply is denoted as t
, and the price of rseth is represented as rp
.
Here is the code rseth price can be inflated.
function getRSETHPrice() external view returns (uint256 rsETHPrice) { address rsETHTokenAddress = lrtConfig.rsETH(); uint256 rsEthSupply = IRSETH(rsETHTokenAddress).totalSupply(); if (rsEthSupply == 0) { return 1 ether; } // ...... 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; }
After the transfer, the new rseth price, denoted as rsethPrice_new
, can be calculated using the following formula:
rsethPrice_new = rp * (1 + (p*x) / t)
Here is the code snippet of getAssetDistributionData
function.
Both assetLyingInDepositPool
and assetLyingInNDCs
can be manipulated.
assetLyingInDepositPool = IERC20(asset).balanceOf(address(this)); uint256 ndcsCount = nodeDelegatorQueue.length; for (uint256 i; i < ndcsCount;) { assetLyingInNDCs += IERC20(asset).balanceOf(nodeDelegatorQueue[i]); assetStakedInEigenLayer += INodeDelegator(nodeDelegatorQueue[i]).getAssetBalance(asset); unchecked { ++i; } }
If the getRSETHPrice()
function is used as the rseth price oracle, the user can easily manipulate the rseth price by directly transferring assets to the deposit pool.
By transferring a specific amount of assets, the user can influence the calculation and potentially earn profits from the manipulated rseth price.
Recommended Mitigation Steps
Use custom storage to store the asset balance instead of simple IERC20.balanceOf
Other
#0 - c4-pre-sort
2023-11-16T20:11:09Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-11-16T20:11:18Z
raymondfam marked the issue as duplicate of #42
#2 - c4-judge
2023-12-01T17:05:40Z
fatherGoose1 marked the issue as satisfactory
🌟 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#L162
Impact
The vulnerability arises from the addNodeDelegatorContractToQueue
function failing to check for duplicated node delegator contracts.
This leads to potential issues in the getAssetDistributionData
function, where it loops through all node delegators to calculate their asset balance and tokens staked in the eigen layer.
If the nodeDelegatorQueue
array contains duplicated node delegators, the calculations can be incorrect, ultimately impacting the rseth price and mint amount.
Proof of Concept
LRTAdmin may add the same node delegator contract multiple times wrongly to the nodeDelegatorQueue
array, creating duplications.
Here is the example code:
function test_AddDuplicatedNodeDelegatorToQueue() external{ vm.startPrank(admin); nodeDelegatorQueueProspectives.push(nodeDelegatorContractOne); lrtDepositPool.addNodeDelegatorContractToQueue(nodeDelegatorQueueProspectives); assertEq( lrtDepositPool.nodeDelegatorQueue(0), nodeDelegatorQueueProspectives[0], "Node delegator index 0 contract is not added" ); assertEq( lrtDepositPool.nodeDelegatorQueue(1), nodeDelegatorQueueProspectives[1], "Node delegator index 1 contract is not added" ); assertEq( lrtDepositPool.nodeDelegatorQueue(2), nodeDelegatorQueueProspectives[2], "Node delegator index 2 contract is not added" ); assertEq( lrtDepositPool.nodeDelegatorQueue(3), nodeDelegatorQueueProspectives[0], "Node delegator index 3 contract is not added" ); vm.stopPrank(); }
Recommended Mitigation Steps
Implement a check in the addNodeDelegatorContractToQueue
function to ensure that duplicate node delegator contracts cannot be added to the nodeDelegatorQueue array.
Consider using data structures that prevent duplicates, such as sets or mapping with unique keys, to store node delegator contracts.
Other
#0 - c4-pre-sort
2023-11-16T04:32:28Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2023-11-16T04:32:37Z
raymondfam marked the issue as duplicate of #36
#2 - c4-judge
2023-11-29T21:35:52Z
fatherGoose1 changed the severity to QA (Quality Assurance)
#3 - c4-judge
2023-11-29T21:42:44Z
fatherGoose1 marked the issue as grade-b