Kelp DAO | rsETH - peanuts's results

A collective DAO designed to unlock liquidity, DeFi and higher rewards for restaked assets through liquid restaking.

General Information

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

Kelp DAO

Findings Distribution

Researcher Performance

Rank: 114/185

Findings: 1

Award: $4.66

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L95-L110 https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTOracle.sol#L52-L79

Vulnerability details

Proof of Concept

This is the formula to calculate how much rseth to mint from the amount of LST, in LRTDepositPool.getRsETHAmountToMint()

// calculate rseth amount to mint based on asset amount and asset exchange rate rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice();

lrtOracle.getAssetPrice(asset) returns the ETH price of the asset, using the asset/ETH oracle and returning latestAnswer (in 18 decimal places), LRTOracle.getAssetPrice()

function getAssetPrice(address asset) public view onlySupportedAsset(asset) returns (uint256) { return IPriceFetcher(assetPriceOracle[asset]).getAssetPrice(asset); }

lrtOracle.getRSETHPrice() gets the price of rsETH. It takes the totalETHInPool and divides by the rsETHSupply. If there is no supply yet, return 1 ether.

function getRSETHPrice() external view returns (uint256 rsETHPrice) { if (rsEthSupply == 0) { return 1 ether; } return totalETHInPool / rsEthSupply;

From all these, we know that the rsETH price is dependent on every LST asset deposited. Let's look at what potential issue can a first depositor surface.

Assume that there is no deposits, and no rsETH minted. The LST used is rETH, with a ratio of 1:1.09, rETH:ETH. (1 rETH is worth 1.09 ETH)

  • Malicious user deposits 1 wei of rETH through depositAsset()
  • getRsETHAmountToMint() is called.
  • lrtOracle.getAssetPrice(rETH) returns 1.09e18.
  • lrtOracle.getRSETHPrice() returns 1e18 since rsETH supply == 0.
  • rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice();
  • rsethAmountToMint = (1 * 1.09e18) / 1e18;
  • 1.09 wei of rsETH tokens is minted

Next, a normal user deposits 1e18 rETH through depositAsset()

  • getRsETHAmountToMint() is called.
  • lrtOracle.getAssetPrice(rETH) returns 1.09e18.
  • Since rsETH supply is not zero anymore, getRSETHPrice() calculation is executed
  • TotalAssetDeposits is 1e18 + 1 wei of rETH. (user deposit of 1e18 + malicious user deposit of 1 wei)
  • totalETHinPool is (1e18 + 1) * 1.09e18 = 1.09e36
  • totalETHinPool / rsEthSupply = 1.09e36 / 1.09 = 1e36
  • rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice();
  • rsethAmountToMint = (1e18 * 1.09e18) / 1e36 = 1.09
  • Normal user deposited 1e18 worth of rETH and got back 1.09 wei worth of rsETH.
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; }

rETH/ETH latestAnswer returns 18 decimals.

https://etherscan.io/address/0x536218f9e9eb48863970252233c8f271f554c2d0#readContract

Impact

First depositor breaks the entire accounting of rsETH mints. Subsequent depositors, if not careful, will lose their funds when converting LST to rsETH.

Tools Used

VSCode

Either make sure the first user cannot deposit such a dust amount. Or make sure the protocol mints the first few rsETH supply, so that the first depositor problem can be mitigated.

Assessed type

Invalid Validation

#0 - c4-pre-sort

2023-11-16T22:39:23Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-11-16T22:39:34Z

raymondfam marked the issue as duplicate of #42

#2 - c4-judge

2023-12-01T17:02:48Z

fatherGoose1 changed the severity to 3 (High Risk)

#3 - c4-judge

2023-12-01T17:06:34Z

fatherGoose1 marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter