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: 30/185
Findings: 2
Award: $143.01
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: max10afternoon
Also found by: 0xMilenov, 0xbrett8571, 0xhacksmithh, 0xmystery, Aymen0909, Bauer, Daniel526, PENGUN, Pechenite, Shaheen, adriro, anarcheuz, btk, ck, ge6a, glcanvas, hals, turvy_fuzz
140.2525 USDC - $140.25
In the NodeDelegator.sol
contract, the function depositAssetIntoStrategy
is utilized to allocate users' assets into the Eigenlayer Strategy. However, it lacks a mechanism to ensure that the expected shares are returned, which can result in loss of shares for the protocol and users.
function depositAssetIntoStrategy(address asset) external override whenNotPaused nonReentrant onlySupportedAsset(asset) onlyLRTManager { address strategy = lrtConfig.assetStrategy(asset); IERC20 token = IERC20(asset); address eigenlayerStrategyManagerAddress = lrtConfig.getContract(LRTConstants.EIGEN_STRATEGY_MANAGER); uint256 balance = token.balanceOf(address(this)); emit AssetDepositIntoStrategy(asset, strategy, balance); ///@audit-ok check the return value `shares` is greater than zero to make sure successful deposit - this is internally checked by eigenlayer ///@audit-issue M a slippage protection should be added because "share rate can be massively inflated" ~ EigenLayer IEigenStrategyManager(eigenlayerStrategyManagerAddress).depositIntoStrategy(IStrategy(strategy), token, balance); }
The EigenLayer's depositIntoStrategy()
function is susceptible to griefing attacks, as highlighted in their C4 audit. While EigenLayer acknowledges this issue, their mitigation approach is to verify that the returned shares are not zero, considering it as a worst-case scenario when the share rate is "massively inflated"
-> // checks to ensure correctness / avoid edge case where share rate can be massively inflated as a 'griefing' sort of attack require(newShares != 0, "StrategyBase.deposit: newShares cannot be zero");
EigenLayer does not implement slippage protection within this function for not so "massive" inflation attacks, leaving it to the discretion of strategy designers. Strategy designers are advised to incorporate slippage protection into their implementations, as discussed in their audit findings:
This is good information for Strategy designers - EigenLayer Dev
Shaheeniyat 🦅
To enhance security for the users'/protocol's funds, it is recommended to add a slippage protection mechanism using minShares
:
function depositAssetIntoStrategy(address asset, uint256 minShares) external override whenNotPaused nonReentrant onlySupportedAsset(asset) onlyLRTManager { address strategy = lrtConfig.assetStrategy(asset); IERC20 token = IERC20(asset); address eigenlayerStrategyManagerAddress = lrtConfig.getContract(LRTConstants.EIGEN_STRATEGY_MANAGER); uint256 balance = token.balanceOf(address(this)); emit AssetDepositIntoStrategy(asset, strategy, balance); uint256 shares = IEigenStrategyManager(eigenlayerStrategyManagerAddress).depositIntoStrategy(IStrategy(strategy), token, balance); require(shares >= minShares, "Slippage Protected") }
Context
#0 - c4-pre-sort
2023-11-16T02:18:43Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-11-16T02:18:49Z
raymondfam marked the issue as primary issue
#2 - raymondfam
2023-11-16T02:19:13Z
Inadequate integration.
#3 - c4-pre-sort
2023-11-17T08:20:58Z
raymondfam marked the issue as high quality report
#4 - c4-sponsor
2023-11-28T07:39:05Z
manoj9april (sponsor) disputed
#5 - manoj9april
2023-11-28T07:42:23Z
Please check the eigen layer latest code here. They have implemented proper mitigation for inflated shares attack.
#6 - fatherGoose1
2023-12-01T18:36:25Z
Sponsor is correct that Eigenlayer has mitigated this issue.
#7 - c4-judge
2023-12-01T18:36:35Z
fatherGoose1 marked the issue as unsatisfactory: Invalid
#8 - DevABDee
2023-12-02T05:14:06Z
Thanks sir @manoj9april & judge @fatherGoose1!
Sir @manoj9april, I believe this finding should be fixed so the users' funds can be safe and the protocol gets fair share. You are right that EigenLayer's latest code migitates griefing attacks but they are only handling the worst-case scenario, where the attack is so big that the shares become equal to zero. But what if there is small griefing attack or a large attack which will leave only 1 or 2 shares when the protocol was expecting like 100 shares.
Judge @fatherGoose1, If the sponsor agrees to the vulnerability, I request you to upgrade this issue to medium. And If there is something, I'm missing and assuming wrong, pls do let me know, so I can learn. Thanks!
#9 - manoj9april
2023-12-03T07:46:32Z
@DevABDee eigen layer code that you were referring is old code. eigenlayer's latest code implements best possible mitigation using virtual shares and offset as suggested by openzepplin, which practically mitigates all scenarios.
#10 - DevABDee
2023-12-04T04:04:29Z
Thanks for re-reviewing Sir @manoj9april
Sir I would like to point out that you are talking about the inflation attack (or the first depositor attack or donation attack) issue and yes virtual shares and offset migitates that issue but our issue is not about that. We are raising the issue of poor slippage protection. Both are different issues. Interestingly good slippage protects from inflation attacks but inflation attack mitigation (offset usage) does not solve the unexpected slippage issue. That's why OpenZepplin mentions in their ERC4646 implementation:
Users can protect against this (inflation) attack as well as unexpected slippage in general by verifying the amount received is as expected, using a wrapper that performs these checks such as
https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]
It's crucial to note that Eigenlayer's slippage protection is suboptimal/poor since it only validates the zero value (which is the worst-case scenario and extra check for the donation attack), expecting strategy implementations to handle the verification of expected minimum amounts.
I hope that I've presented the case clearly. Following this, I will wholeheartedly accept and respect the final decision of the sponsors and the judge.
Thanks, Shaheen.
#11 - manoj9april
2023-12-04T10:48:34Z
Thanks for clarifying @DevABDee Even in that case I agree with EigenLayer Devs
This is good information for Strategy designers to have, but does not appear to have appreciable impact at the moment, so seems more appropriate to have informational severity. We may consider adding an optional parameter in the future.
that it won't have any appreciable impact as asset prices are stable enough. Slippage protection makes more sense in DEXes. I will leave upto the judge to decide.
#12 - c4-judge
2023-12-04T16:45:16Z
fatherGoose1 marked the issue as duplicate of #148
#13 - fatherGoose1
2023-12-04T16:45:33Z
Properly a duplicate of Medium #148
#14 - c4-judge
2023-12-04T16:45:39Z
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/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L79 https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L83
The protocol allows users to deposit LSTs (cbETH, stETH, rETH) and mint RSETH tokens. The RSETH amount to mint & RSETH's price highly dependent on total assets deposit into the protocol. The total deposited assets can be in the LRTDepositPool
, NodeDelegator
& EigenLayer's Strategy
. The are summed to get the total assets deposited:
function getTotalAssetDeposits(address asset) public view override returns (uint256 totalAssetDeposit) { (uint256 assetLyingInDepositPool, uint256 assetLyingInNDCs, uint256 assetStakedInEigenLayer) = getAssetDistributionData(asset); return (assetLyingInDepositPool + assetLyingInNDCs + assetStakedInEigenLayer); }
The protocol uses ERC20.balanceOf()
to determine the deposited assets in the LRTDepositPool
& NodeDelegator
, which can be dangerous.
function getAssetDistributionData(address asset) public view override onlySupportedAsset(asset) returns (.....) { ///@audit-issue use of balanceOf can be dangerous for the rsETH price. -> 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; } } }
The protocol anticipates a parallel increase in total deposited assets and the RSETH total supply. This assumption is rooted in the belief that assets will only enter the protocol through users invoking depositAsset()
and minting a fair share of RSETH tokens. Consequently, the protocol utilizes contract token balances to compute the getRSETHPrice().
Contrary to this expectation, the total assets within the contracts can expand without a corresponding minting of RSETH, thereby affecting the RSETH price. An attacker could potentially inject assets directly into these contracts, disrupting the equilibrium between the actual deposited assets and the contract balance.
Shaheeniyat 🦅
Avoid using the contract balance balanceOf()
method in price calculations. Instead, maintain a totalAssetsDeposited
variable that is updated when assets enter or exit the contract. This approach ensures a more accurate representation of the deposited assets, mitigating the risk of price manipulation.
ERC20
#0 - c4-pre-sort
2023-11-16T05:02:31Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2023-11-16T05:03:46Z
raymondfam marked the issue as duplicate of #168
#2 - c4-judge
2023-12-01T16:58:42Z
fatherGoose1 changed the severity to QA (Quality Assurance)