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: 177/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#L162-L176
Node delegator is pushed to nodeDelegatorQueue
without checking if the node delegator already exists in the queue. Thus it is possible that one node delegator might be pushed to nodeDelegatorQueue
multiple times. If this happens, getAssetDistributionData
will produce wrong distribution for the specified asset, and affect the deposit of that asset.
LRTAdmin can add node delegator contract by function addNodeDelegatorContractToQueue
. The function pushes the provided node delegator(s) to the queue directly, without checking if the node delegator already exists in the queue. Therefore it is possible that one node delegator is pushed to the queue multiple times if LRTAdmin calls addNodeDelegatorContractToQueue
with an existing node delegator.
162: function addNodeDelegatorContractToQueue(address[] calldata nodeDelegatorContracts) external onlyLRTAdmin { 163: uint256 length = nodeDelegatorContracts.length; 164: if (nodeDelegatorQueue.length + length > maxNodeDelegatorCount) { 165: revert MaximumNodeDelegatorCountReached(); 166: } 167: 168: for (uint256 i; i < length;) { 169: UtilLib.checkNonZeroAddress(nodeDelegatorContracts[i]); 170: nodeDelegatorQueue.push(nodeDelegatorContracts[i]); 171: emit NodeDelegatorAddedinQueue(nodeDelegatorContracts[i]); 172: unchecked { 173: ++i; 174: } 175: } 176: }
https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L162-L176
If one node delegator is pushed into nodeDelegatorQueue
multiple times, one asset's distribution of that delegator will be calculated multiple times as well in getAssetDistributionData
, resulting a greater distribution than it should be.
Function: getAssetDistributionData 81: uint256 ndcsCount = nodeDelegatorQueue.length; 82: for (uint256 i; i < ndcsCount;) { 83: assetLyingInNDCs += IERC20(asset).balanceOf(nodeDelegatorQueue[i]); 84: assetStakedInEigenLayer += INodeDelegator(nodeDelegatorQueue[i]).getAssetBalance(asset); 85: unchecked { 86: ++i; 87: } 88: }
https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L81-L88
The reuslt of getAssetDistributionData
is used in depositAsset
to check if the amount being deposited will exceed the asset's limit. If the result is incorrect (greater than it should be), a normal deposit which should succeed may be reverted.
Function: depositAsset 132: if (depositAmount > getAssetCurrentLimit(asset)) { 133: revert MaximumDepositLimitReached(); 134: } Function: getAssetCurrentLimit 56: function getAssetCurrentLimit(address asset) public view override returns (uint256) { 57: return lrtConfig.depositLimitByAsset(asset) - getTotalAssetDeposits(asset); 58: } Function: getTotalAssetDeposits 47: function getTotalAssetDeposits(address asset) public view override returns (uint256 totalAssetDeposit) { 48: (uint256 assetLyingInDepositPool, uint256 assetLyingInNDCs, uint256 assetStakedInEigenLayer) = 49: getAssetDistributionData(asset); 50: return (assetLyingInDepositPool + assetLyingInNDCs + assetStakedInEigenLayer); 51: }
https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L132-L134 https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L56-L58 https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L47-L51
Manual audit
mapping(address nodeDelegator => bool isExist) addedNodeDelegators
.Other
#0 - c4-pre-sort
2023-11-16T21:40:11Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2023-11-16T21:40:20Z
raymondfam marked the issue as duplicate of #36
#2 - c4-judge
2023-11-29T21:35:51Z
fatherGoose1 changed the severity to QA (Quality Assurance)
#3 - c4-judge
2023-11-29T21:44:12Z
fatherGoose1 marked the issue as grade-b