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: 24/185
Findings: 3
Award: $155.30
🌟 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
https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L119-L145
User may receive less amount of rsETH during token minting (When calling LRTDepositPool#depositAsset
.
Because, amount of rsETH to be minted depends on total supply and underlying asset prices.
This might be happens due to:
Let's consider next case:
getRsETHAmountToMint(stETH, 1 ether)
, to determine how many rsETH he will get.
He expected to receive 1 ether
of rsETH;depositAsset(stETH, 1 ether)
, but due to high network loads his transaction might stuck in pool for some time, for example two hours;asset_amount * asset_price * rsETH_supply / (∑ asset_amount_i. * asset_price_i)
.
As you can notice from formula above, the amount of rsETH depends on multiple parameters, which can be changed independently (stETH, rETh and cbETH don't depends on each other, so their prices may change);1 ether
just 0.75 ether
of rsETH.This behavior is incorrect, and should be prohibited due to prevent occasionally user's lost.
Manual review
To function depositAsset
add parameter minOutputAmount
, which corresponds minimum amount of rsETH to be minted to user, or revert transaction otherwise.
See how it's implemented in uniswap: https://github.com/Uniswap/v2-periphery/blob/master/contracts/UniswapV2Router01.sol
Other
#0 - c4-pre-sort
2023-11-16T00:46:31Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-11-16T00:46:43Z
raymondfam marked the issue as duplicate of #39
#2 - c4-pre-sort
2023-11-17T06:43:07Z
raymondfam marked the issue as duplicate of #148
#3 - c4-judge
2023-11-29T19:08:59Z
fatherGoose1 marked the issue as satisfactory
#4 - c4-judge
2023-11-29T19:21:54Z
fatherGoose1 changed the severity to 2 (Med Risk)
🌟 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/NodeDelegator.sol#L38-L46
Current implementation of EIGEN_STRATEGY_MANAGER
is proxy, which means that Eigen Strategy manager can be changed in future.
NodeDelegator handles passed asset, and delegates them to Eigen Strategy manager, which delegates them to one of strategy.
If something happens with EigenLayer, and attacker can operate EigenLayer protocol, he can withdraw all funds from NodeDelegator, because this contract gives infinity approve to strategy manager address.
So, to sum up: In cases of EigenLayer will be hacked, Kelp may lost all money from NodeDelegator protocol.
Of course, manager can transfer money back to DepositPool, but old approve to StrategyManger not going anywhere, and any further transfers to NodeDelegator will carry the risks of losing money.
Let's consider next case:
maxApproveToEigenStrategyManager
;EIGEN_STRATEGY_MANAGER
address, then previously given approve is still active, and compromised address can execute transferFrom
for all approved funds.So, correct way to resolve such issue is add function to remove allowance for EIGEN_STRATEGY_MANAGER
contract.
Manual review
Add function to emergency revoke approve for contract EIGEN_STRATEGY_MANAGER
.
Other
#0 - c4-pre-sort
2023-11-16T01:16:28Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2023-11-16T01:16:49Z
raymondfam marked the issue as duplicate of #70
#2 - c4-judge
2023-11-29T19:28:16Z
fatherGoose1 changed the severity to QA (Quality Assurance)
#3 - c4-judge
2023-11-29T19:28:50Z
fatherGoose1 marked the issue as grade-b
🌟 Selected for report: CatsSecurity
Also found by: 0xSmartContract, 0xbrett8571, 0xepley, Bauchibred, KeyKiril, Myd, SAAJ, SBSecurity, Sathish9098, ZanyBonzy, albahaca, clara, digitizeworx, fouzantanveer, foxb868, glcanvas, hunter_w3b, invitedtea, sakshamguruji, unique
12.2949 USDC - $12.29
To understand protocol was used EigenLayer documentation.
Flow:
Current architecture is well designed.
It's absolutely correct to separate DepositPool and NodeDelagator into two different contracts. Because, if one of these contract will be hacked, then protocol lost only one part.
But here is one detail, which is important to highlight. Withdraws. Current implementation doesn't support withdraws. Devs told that it will be added later. But, when we deposit some asset (like stETH and rETH) to Kelp -- we erase the boundaries of these tokens and after that moment it becomes unclear who contributed which tokens. For example:
Moreover, if you go to prod right now, it will be impossible to determine later who deposit which tokens.
Need to support additional mapping of: user => asset => initial asset amount
.
Codebase is really good, all cases are handled correctly. Dev team writes good and clear comments and uses meaningful named-attributes in mapping.
However, devs uses transfer, transferFrom and approve instead of safe version of these functions. Better to use safe implementation by OppenZeppelin, because tokens, which will be added in future may return nothing in transfer
.
Additional notice:
LRTConfig has tokenMap
which is used for clarity description of what address responsible for.
But current implementation gives possibility to update one of isSupportedAsset
or depositLimitByAsset
or assetStrategy
keys without tokenMap
modification. These should be fixed and work synchronously.
Current protocol's version is absolute centralized. Core contracts, and core functions: DepositPool, NodeDelegator is under modifiers. Only admin can move funds to strategy. After you enter to protocol, only admins can control your money. This behavior is not desired.
Better develop or think about more freedom for users in terms of transfer funds.
The system consist of three main parts:
First part is DepositPool with Oracle. This is main entry point. User deposits funds use DepositPool contract, and Oracle contract uses to calculate price;
Second part is NodeDelegator part. This part responsible to communication with EigenLayer. Funds passed to this contract are passed to EigenLayer's Strategies, which bring profit;
Last one part is RsETH token, when user enter to protocol via DepositPool he receives RsETH, which is proof of ownership.
So, whole protocol in a general sense, can be represented as the following scheme:
User | | User's funds ∨ --------------- transfer assets ----------------- enter into strategy -------------- | DepositPool | <============> | NodeDelegator | =====> | EigenLayer | --------------- ----------------- -------------- ^ | | Request Price ∨ ---------- | Oracle | ----------
A systemic risk in this system is the dependence on ChaniLink.
In future, when protocol will have bigger liquidity, better to additionally fetch price from on chain dex, like it's implemented in GMX.
Because, Oracle can be stopped, can be hacked, so instead of relay only on one system, better has fallback option. Or even better to use two price providers (Oracle and DEX) and use mean price.
12 hours
#0 - c4-pre-sort
2023-11-17T03:19:27Z
raymondfam marked the issue as sufficient quality report
#1 - c4-judge
2023-11-29T18:54:40Z
fatherGoose1 marked the issue as grade-b