Kelp DAO | rsETH - ck'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: 27/185

Findings: 2

Award: $144.91

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

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

Vulnerability details

Impact

An attacker can take advantage and be the first depositor therefore stealing value from subsequent depositors.

When the supply of rsETH is 0, the price of rsETH is set to 1 ETH:

function getRSETHPrice() external view returns (uint256 rsETHPrice) {
        address rsETHTokenAddress = lrtConfig.rsETH();
        uint256 rsEthSupply = IRSETH(rsETHTokenAddress).totalSupply();

        if (rsEthSupply == 0) {
            return 1 ether;
        }

The first depositor can therefore donate a very small value of assets (1 wei) and then send a large amount of assets to the pool.

This would highly inflate the value of their rsETH holding and lead to loss of value for subsequent depositors.

Proof of Concept

  • The attacker deposits 1 wei of assets using the depositAsset function.
  • They get 1 wei of rsETH in return.
  • The attacker sends a large amount of the asset to the pool contract.
  • The value of the 1 wei of rsETH they hold is now very high.
  • Subsequent depositors would get very low amounts of rsETH when they deposits assets.
  • Eventually when withdrawals are available, the attacker would have gained a lot of value from subsequent depositors.

Tools Used

Manual review.

Seed the pool with initial rsETH liquidity. Alternatively set a minimum value of the amount of rsETH that can be minted by the first depositor.

Assessed type

MEV

#0 - c4-pre-sort

2023-11-16T18:57:45Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-11-16T18:57:53Z

raymondfam marked the issue as duplicate of #42

#2 - c4-judge

2023-12-01T17:04:14Z

fatherGoose1 marked the issue as satisfactory

Findings Information

Awards

140.2525 USDC - $140.25

Labels

bug
2 (Med Risk)
satisfactory
sufficient quality report
duplicate-148

External Links

Lines of code

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

Vulnerability details

Impact

The amount of rsETH to be minted is dependent on lrtOracle.getRSETHPrice().

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

The function getRSETHPrice() is reliant on the totalETHInPool value:

    function getRSETHPrice() external view returns (uint256 rsETHPrice) {
        address rsETHTokenAddress = lrtConfig.rsETH();
        uint256 rsEthSupply = IRSETH(rsETHTokenAddress).totalSupply();

        if (rsEthSupply == 0) {
            return 1 ether;
        }

        uint256 totalETHInPool;
        address lrtDepositPoolAddr = lrtConfig.getContract(LRTConstants.LRT_DEPOSIT_POOL);

        address[] memory supportedAssets = lrtConfig.getSupportedAssetList();
        uint256 supportedAssetCount = supportedAssets.length;

        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;
            }
        }

        return totalETHInPool / rsEthSupply;
    }

As can be seen the totalETHInPool is determined by totalAssetAmt:

uint256 totalAssetAmt = ILRTDepositPool(lrtDepositPoolAddr).getTotalAssetDeposits(asset); totalETHInPool += totalAssetAmt * assetER;

A component of this value is the assets in the deposit pool:

    function getTotalAssetDeposits(address asset) public view override returns (uint256 totalAssetDeposit) {
        (uint256 assetLyingInDepositPool, uint256 assetLyingInNDCs, uint256 assetStakedInEigenLayer) =
            getAssetDistributionData(asset);
        return (assetLyingInDepositPool + assetLyingInNDCs + assetStakedInEigenLayer);
    }

Anyone can deposit assets to the pool causing this value to be inflated e.g through a flashloan.

By manipulating the deposits in the pool, the attacker would be able to frontrun other users and reduce the amount of rsETH they get during a mint.

Proof of Concept

  • When an attacker deposits a large amount of assets, the assetLyingInDepositPool increases.
  • This leads to the totalAssetAmt and totalETHInPool values dramatically increasing.
  • getRSETHPrice() would therefore also increase due to the return value of return totalETHInPool / rsEthSupply;
  • An increase of the rsETH price would lead to a decrease in the amount of rsETH that can be minted for a given asset amount: rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice();
  • An attacker would therefore be able to frontun other deposits and increase the price of rsETH making them suffer losses.

Tools Used

Manual Review.

A possible mitigation would be to let depositors specify a minimum amountof rsETH they expect to receive during a mint.

Assessed type

Oracle

#0 - c4-pre-sort

2023-11-16T07:50:33Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-11-16T07:50:47Z

raymondfam marked the issue as duplicate of #39

#2 - c4-pre-sort

2023-11-17T06:43:12Z

raymondfam marked the issue as duplicate of #148

#3 - c4-judge

2023-11-29T19:10:27Z

fatherGoose1 marked the issue as satisfactory

Findings Information

Awards

140.2525 USDC - $140.25

Labels

bug
2 (Med Risk)
satisfactory
sufficient quality report
duplicate-148

External Links

Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L119-L144

Vulnerability details

Impact

When a deposit of assets is being made, the depositor specifies the depositAmount.

    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);

        emit AssetDeposit(asset, depositAmount, rsethAmountMinted);
    }

The rsethAmountMinted is calculated using the _mintRsETH function which in turn relies on the getRsETHAmountToMint function:

    function _mintRsETH(address _asset, uint256 _amount) private returns (uint256 rsethAmountToMint) {
        (rsethAmountToMint) = getRsETHAmountToMint(_asset, _amount);

        address rsethToken = lrtConfig.rsETH();
        // mint rseth for user
        IRSETH(rsethToken).mint(msg.sender, rsethAmountToMint);
    }
    function getRsETHAmountToMint(
        address asset,
        uint256 amount
    )
        public
        view
        override
        returns (uint256 rsethAmountToMint)
    {
        // setup oracle contract
        address lrtOracleAddress = lrtConfig.getContract(LRTConstants.LRT_ORACLE);
        ILRTOracle lrtOracle = ILRTOracle(lrtOracleAddress);

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

This value is likely to change as it relies on various factors such as the calculated getRSETHPrice.

If this values fluctuates highly, there are no protections for the user on what amount of rsETH they should receive.

The depositor is therefore very likely to suffer slippage loss.

Proof of Concept

  • The user attempts to deposit assets at a time of high price volatility.
  • The price returned by the oracle changes drastically.
  • The user receives less rsETH than they would have expected suffering losses.

Tools Used

Manual review.

Allow the depositor to specify an expected minimum amount of rsETH they should receive during deposits.

Assessed type

MEV

#0 - c4-pre-sort

2023-11-16T18:40:51Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-11-16T18:41:01Z

raymondfam marked the issue as duplicate of #39

#2 - c4-pre-sort

2023-11-17T06:43:14Z

raymondfam marked the issue as duplicate of #148

#3 - c4-judge

2023-11-29T19:10:35Z

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