medium

Calling `init()` will fail because the well that should be whitelisted has no...

Reward

Total

13807.57 USDC

Selected
13807.57 USDC
Selected Submission

Calling init() will fail because the well that should be whitelisted has no liquidity

Severity

High Risk

Relevant GitHub Links

https://github.com/Cyfrin/2024-04-beanstalk-2/blob/27ff8c87c9164c1fbff054be5f22e56f86cdf127/protocol/contracts/libraries/Well/LibWellBdv.sol#L37-L39

Impact

According to the team “the initial liquidity for the new Bean-wstEth well will be added to the well once the Eth from the old well is converted to wstEth”. The problem is that it will not be possible to initiate the migration to the new well because the init() function will revert if the new well does not have any liquidity in it.

Proof of Concept

When calling InitMigrateUnripeBeanEthToBeanSteth:init() to initiate the migration from the Bean-ETH well to the Bean-wstETH well, the new well is whitelisted by calling LibWhitelist:whitelistToken(). During this function call the function LibWellBdv:bdv is entered:

    function bdv(
        address well,
        uint amount
    ) internal view returns (uint _bdv) {
        uint beanIndex = LibWell.getBeanIndexFromWell(well);

        // For now, assume Beanstalk should always use the first pump and given that the Well has been whitelisted, it should be assumed
        // that the first Pump has been verified when the Well was whitelisted.
        Call[] memory pumps = IWell(well).pumps();
        uint[] memory reserves = IInstantaneousPump(pumps[0].target).readInstantaneousReserves(well, pumps[0].data);
        // If the Bean reserve is beneath the minimum balance, the oracle should be considered as off.
        require(reserves[beanIndex] >= C.WELL_MINIMUM_BEAN_BALANCE, "Silo: Well Bean balance below min"); 
        Call memory wellFunction = IWell(well).wellFunction();
        uint lpTokenSupplyBefore = IWellFunction(wellFunction.target).calcLpTokenSupply(reserves, wellFunction.data);
        reserves[beanIndex] = reserves[beanIndex].sub(BEAN_UNIT); // remove one Bean
        uint deltaLPTokenSupply = lpTokenSupplyBefore.sub(
            IWellFunction(wellFunction.target).calcLpTokenSupply(reserves, wellFunction.data)
        );
        _bdv = amount.mul(BEAN_UNIT).div(deltaLPTokenSupply);
    }

This function gets the reserves of the new Bean-wstETH well that should be whitelisted and checks if the bean reserves of the well are >= WELL_MINIMUM_BEAN_BALANCE which is set to 1000 beans:

uint[] memory reserves = IInstantaneousPump(pumps[0].target).readInstantaneousReserves(well, pumps[0].data);
        // If the Bean reserve is beneath the minimum balance, the oracle should be considered as off.
        require(reserves[beanIndex] >= C.WELL_MINIMUM_BEAN_BALANCE, "Silo: Well Bean balance
uint256 internal constant WELL_MINIMUM_BEAN_BALANCE = 1000_000_000; // 1,000 Beans

The issue arises from the fact that at the time the well is whitelisted it does not have any liquidity in it because according to the development team “the initial liquidity will be added to the well once the Eth is converted to wstEth”. This means the function will revert and therefore it will not be possible to initiate the migration.

Recommended Mitigation Steps

Do not whitelist the well when calling InitMigrateUnripeBeanEthToBeanSteth:init() but add a second function InitMigrateUnripeBeanEthToBeanSteth:finish() where the LP tokens for the new well are added and the new well which now has enough liquidity is whitelisted.