Beanstalk Notion
Beanstalk Notion
/
🪲
Bug Reports
/
BIC Notes
/
📄
Report #32373
📄

Report #32373

Report Date
June 19, 2024
Status
Confirmed
Payout
10,000

User will lose the rest of their plenty earning if they withdraw during raining season

‣
Report Info

Report ID

#32373

Report type

Smart Contract

Has PoC?

Yes

Target

https://etherscan.io/address/0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5

Impacts

Permanent freezing of unclaimed yield

Description

User will lose the rest of their plenty earning if they withdraw during raining season.

When a user mows during a Raining Season X , they're set sop.roots (now called rainRoots). Then, the next time the user mows during a raining season, if there has been a SOP following the Raining Season X, these rain roots are used to calculate the user's plenty.

        if (s.season.raining) {
            // If rain started after update, set account variables to track rain.
            if (s.season.rainStart > lastUpdate) {
                s.a[account].lastRain = s.season.rainStart;
                s.a[account].sop.roots = s.a[account].roots;
            }

The problem is that in the beginning of the handleRainAndSops function, if the user has 0 roots, it does a early return and does not allocate the necessary plenty for the user's rainRoots

    function handleRainAndSops(address account, uint32 lastUpdate) private {
        AppStorage storage s = LibAppStorage.diamondStorage();
        // If no roots, reset Sop counters variables
        if (s.a[account].roots == 0) {
            s.a[account].lastSop = s.season.rainStart;
            s.a[account].lastRain = 0;
            return;
        }

Impact Details

If the user fully withdraws during a raining season, and then SOP(s) follow, they'll lose the plenty they're obliged to.

References

Add any relevant links to documentation or code

Proof of concept

Add the following test to Flood.t.sol

BIR-18: Lost Plenty Edge Case

BIC Response

After some investigation we have confirmed that this is a valid bug report. Specifically, we have determined that if a Farmer earns Plenty from a Flood, Withdraws all of their assets, and then Mows during the next Raining Season, they lose the Plenty associated with that Flood.

Also, we agree with the selected Impact of "Permanent freezing of unclaimed yield", and thus High severity. Given that the practicable economic damage is effectively zero due to the low likelihood of Flood occurring in the near future (and also the very low likelihood of the specific edge case occurring even if Flood occurs), the BIC has determined that this bug report be rewarded the minimum reward for High severity reports of 10k Beans.

        if (s.a[account].lastRain > 0) {
            // if the last processed SOP = the lastRain processed season,
            // then we use the stored roots to get the delta.
            if (a.lastSop == a.lastRain) previousPPR = a.sop.plentyPerRoot;
            else previousPPR = s.sops[a.lastSop];
            uint256 lastRainPPR = s.sops[s.a[account].lastRain];

            // If there has been a SOP duing the rain sesssion since last update, process SOP.
            if (lastRainPPR > previousPPR) {
                uint256 plentyPerRoot = lastRainPPR - previousPPR;
                previousPPR = lastRainPPR;
                plenty = plenty.add(plentyPerRoot.mul(s.a[account].sop.roots).div(C.SOP_PRECISION));
            }
    function test_lostPlenty() public {
        address sopWell = C.BEAN_ETH_WELL;
        setReserves(sopWell, 1000000e6, 1100e18);

        season.rainSunrise();
        bs.mow(users[1], C.BEAN);

        uint256[] memory depositIds = bs.getTokenDepositIdsForAccount(users[1], C.BEAN);
        (, int96 stem) = LibBytes.unpackAddressAndStem(depositIds[0]);

        LibTransfer.To mode;

        vm.prank(users[1]);
        bs.withdrawDeposit(C.BEAN, stem, 1000e6, 0);
        assertEq(siloGetters.balanceOfRainRoots(users[1]), 10004000000000000000000000);
        assertEq(siloGetters.balanceOfRoots(users[1]), 0);

        season.rainSunrise();
        bs.mow(users[1], C.BEAN);

        uint256 userPlenty = bs.balanceOfPlenty(users[1], sopWell);
        assertEq(userPlenty, 0);
    }