sherlock-audit / 2024-05-napier-update-judging

8 stars 7 forks source link

BiasedMerc - RsETHAdapter::_stake() does not have any checks on the amount of RSETH minted during depositETH call #69

Closed sherlock-admin3 closed 4 months ago

sherlock-admin3 commented 5 months ago

BiasedMerc

medium

RsETHAdapter::_stake() does not have any checks on the amount of RSETH minted during depositETH call

Summary

RsETHAdapter::_stake() only ensures that the amount of RSETH minted is not 0, however no other checks are in place to ensure that an appropriate amount of RSETH was minted for the deposit amount. The amount is not returned during the call, and no upstream calls check this amount either.

Vulnerability Detail

RsETHAdapter::_stake()

    function _stake(uint256 stakeAmount) internal override returns (uint256) {
        if (stakeAmount == 0) return 0;

        // Check LRTDepositPool stake limit
        uint256 stakeLimit = RSETH_DEPOSIT_POOL.getAssetCurrentLimit(Constants.ETH);
        if (stakeAmount > stakeLimit) {
            // Cap stake amount
            stakeAmount = stakeLimit;
        }
        // Check LRTDepositPool minAmountToDeposit
        if (stakeAmount <= RSETH_DEPOSIT_POOL.minAmountToDeposit()) revert MinAmountToDepositError();
        // Check paused of LRTDepositPool
        if (RSETH_DEPOSIT_POOL.paused()) revert ProtocolPaused();

        // Interact
        IWETH9(Constants.WETH).withdraw(stakeAmount);
        uint256 _rsETHAmt = RSETH.balanceOf(address(this));
>>      RSETH_DEPOSIT_POOL.depositETH{value: stakeAmount}(0, REFERRAL_ID);
        _rsETHAmt = RSETH.balanceOf(address(this)) - _rsETHAmt;

        if (_rsETHAmt == 0) revert InvariantViolation();

        return stakeAmount;
    }

When calling RSETH_DEPOSIT_POOL.depositETH, 0 amount is passed as the minimum RSETH expected: LRTDepositPool::depositETH()

    function depositETH(
        uint256 minRSETHAmountExpected,
        string calldata referralId
    )

LRTDepositPool::getRsETHAmountToMint()

    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.rsETHPrice();
    }

The amount of RSETH minted during the call if based on the current exchange rate fetched from the lrtOracle::rsETHPrice()

    function updateRSETHPrice() external {
        address rsETHTokenAddress = lrtConfig.rsETH();
        uint256 rsEthSupply = IRSETH(rsETHTokenAddress).totalSupply();

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

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

        rsETHPrice = totalETHInPool / rsEthSupply;
    }

rsETHPrice is based off of the asset price of all currently utilised assets, meaning that all of these asset prices directly affect RSETH price. Currently there are 3 supported tokens that determine the price: 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84 = Lido: stETH 0xA35b1B31Ce002FBF2058D22F30f95D405200A15b = Stader ETHx 0xac3E018457B222d93114458476f3E3416Abbe38F Frax sfrxETH The fact that 3 of these LST tokens determine the price of RSETH means that RSETH price will be directly affected (proportionally to holdings) of any fluctuations of the price of these tokens.

Impact

When any of the LST tokens outlined have a price spike due to demand, it will cause the price of RSETH to increase leading to a worse minting price for the protocol, which can lead to losses.

Code Snippet

RsETHAdapter::_stake() LRTDepositPool::depositETH() LRTDepositPool::getRsETHAmountToMint() lrtOracle::rsETHPrice()

Tool used

Manual Review

Recommendation

Ensure a minimum value of non-zero is passed to LRTDepositPool::depositETH() to ensure the protocol does not mint at unfavourable prices.

Duplicate of #26