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: 178/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
function getAssetCurrentLimit(address asset) public view override returns (uint256) { return lrtConfig.depositLimitByAsset(asset) - getTotalAssetDeposits(asset); }
in checking if current limit for an asset has been reached via getAssetCurrentLimit()
, the function getTotalAssetDeposits()
is called and it calls getAssetDistributionData() which sums up all the Node delegators asset balances in EigenLayer. It gets the EigenLayer balance by called the EigenLayer Strategy contract's userUnderlyingView()
function.
LRTDepositPool.getTotalAssetDeposits() -> LRTDepositPool.getAssetDistributionData() -> NodeDelegator.getAssetBalance() -> EigenLayerStrategy.userUnderlyingView()
return IStrategy(strategy).userUnderlyingView(address(this));
The problem here is that the eigen layer function userUnderlyingView()
returns the amount of assets accrued to the user based on amount of shares the user has. The eigen layer contract is a yield bearing contract very similar to erc-4626 vaults, in the event that there is yield, the assets returned will not be the same as the one initially deposited by users via kelp Dao. So the getAssetCurrentLimit()
function is actually checking if deposit limit is reached by subtracting the set assets deposited + yield accrued from the set max Deposit limit for the asset
return lrtConfig.depositLimitByAsset(asset) - getTotalAssetDeposits(asset);
The above can be further explained to be lrtConfig.depositLimitByAsset(asset) - assets + yield
.
Yield amount should not be in this permutation. So if there is a scenario where by the depositLimit per asset is 100, 80 assets was deposited by users and 80 assets goes to eigen layer, eigen layer assigns 80 shares to node delegator. Later the 80 shares in eigen layer increases in value and becomes worth 110 assets. The function userUnderlyingView()
will return 110 as assets for the node delegator. getAssetCurrentLimit()
will use it as actual amount of deposits and proceed to do the math. This will become 100 - 110
and cause a revert.
Getting the total amount of assets deposited by calling userUnderlyingView() is not the best approach as it returns the asset value of the shares the caller is holding, not the initial amount of assets the caller deposited. See the notice tag in the snippet below.
/** * @notice convenience function for fetching the current underlying value of all of the `user`'s shares in * this strategy. In contrast to `userUnderlying`, this function guarantees no state modifications */ function userUnderlyingView(address user) external view virtual returns (uint256) { return sharesToUnderlyingView(shares(user)); }
110 assets were'nt deposited but 80, so it will be better to track the number of user depoits in the contract storage than utilize the function userUnderlyingView()
userUnderlyingView()
returing the asset value of shares held may mean that assets can be worth more than intially deposited. Using this value to calculate if the deposit limit of an asset is reached in getAssetCurrentLimit() can give errored results since the assets + yield
is not the same as the assets deposited
. getAssetCurrentLimit()
may revert or return 0, even before the users deposits have actually gotten to the set deposit limit.
manual review
mapping (address => uint256) private amountDepositedPerAsset; /// @notice helps user stake LST to the protocol /// @param asset LST asset address to stake /// @param depositAmount LST asset amount to stake function depositAsset( address asset, uint256 depositAmount ) external whenNotPaused nonReentrant onlySupportedAsset(asset) { // checks if (depositAmount == 0) { revert InvalidAmount(); } if (depositAmount > getAssetCurrentLimit(asset)) { revert MaximumDepositLimitReached(); } if (!IERC20(asset).transferFrom(msg.sender, address(this), depositAmount)) { revert TokenTransferFailed(); } // interactions uint256 rsethAmountMinted = _mintRsETH(asset, depositAmount); amountDepositedPerAsset[asset] += depositAmount; emit AssetDeposit(asset, depositAmount, rsethAmountMinted); }
amountDepositedPerAsset
in getAssetCurrentLimit()
function getAssetCurrentLimit(address asset) public view override returns (uint256) { return lrtConfig.depositLimitByAsset(asset) - amountDepositedPerAsset[asset]; }
Error
#0 - c4-pre-sort
2023-11-16T21:49:20Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-11-16T21:49:48Z
raymondfam marked the issue as duplicate of #294
#2 - c4-judge
2023-12-01T17:41:34Z
fatherGoose1 marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2023-12-06T18:18:18Z
fatherGoose1 marked the issue as not a duplicate
#4 - c4-judge
2023-12-06T18:18:44Z
fatherGoose1 marked the issue as duplicate of #372
#5 - c4-judge
2023-12-06T18:19:18Z
fatherGoose1 changed the severity to QA (Quality Assurance)
#6 - c4-judge
2023-12-06T18:19:44Z
fatherGoose1 marked the issue as grade-b