DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: high
Invalid

`LibFlood.handleRain()` uses reserves subject to manipulation to calculate `deltaB`

Summary

LibFlood.handleRain() is used to return Bean price to 1 USD in the start of the season in case price is > 1 USD. Here's docs page

It is done by:

  1. Calculate deltaB of every soppable Well

  2. Reduce positive deltaB by negative deltaB to get "clean" Bean shortage

  3. Mint that shortage of Beans

  4. Swap Bean to non-Bean token in Well.

That's how Flood concept reduces Bean price down to 1 USD. Problem is that in step 1 it calculates deltaB based on current manipulateable reserves.

It creates following attack vector, suppose Well is USDC / Bean:

  1. Attacker inflates Bean price by supplying big amount of USDC.

  2. Attacker calls sunrize() and Flood is performed.

  3. Calculated deltaB is very big.

  4. So it mints big amount of Bean. Real value of Bean decreases down to 0.

  5. Attacker successfully decreased Bean price below the peg, breaking main purpose of Beanstalk. He can utilize it on his own purposes, for example liquidate positions on Lending market collateralized by Bean and so on.

Vulnerability Details

There is following chain of calls:

SeasonFacet.gm()
Weather.calcCaseIdandUpdate()
LibFlood.handleRain() <-------------- Here executes Flood logic when Price > 1 USD
LibFlood.getWellsByDeltaB() <------ Here calculates deltaB
LibFlood.sopWell() <--------------- Here mints Bean and performs swap in Well

Here you can see it calls LibDeltaB.currentDeltaB() to calculate deltaB:

function getWellsByDeltaB()
internal
view
returns (...)
{
...
for (uint i = 0; i < wells.length; i++) {
@> wellDeltaBs[i] = WellDeltaB(wells[i], LibDeltaB.currentDeltaB(wells[i]));
if (wellDeltaBs[i].deltaB > 0) {
totalPositiveDeltaB += uint256(wellDeltaBs[i].deltaB);
positiveDeltaBCount++;
} else {
totalNegativeDeltaB += uint256(-wellDeltaBs[i].deltaB);
}
}
...
}
function currentDeltaB(address well) internal view returns (int256) {
@> uint256[] memory reserves = IWell(well).getReserves();
return calculateDeltaBFromReserves(well, reserves, ZERO_LOOKBACK);
}

Here you can see it mints that deltaB amount of Beans:

function sopWell(WellDeltaB memory wellDeltaB) private {
AppStorage storage s = LibAppStorage.diamondStorage();
if (wellDeltaB.deltaB > 0) {
IERC20 sopToken = LibWell.getNonBeanTokenFromWell(wellDeltaB.well);
@> uint256 sopBeans = uint256(wellDeltaB.deltaB);
@> C.bean().mint(address(this), sopBeans);
// Approve and Swap Beans for the non-bean token of the SOP well.
C.bean().approve(wellDeltaB.well, sopBeans);
uint256 amountOut = IWell(wellDeltaB.well).swapFrom(
C.bean(),
sopToken,
sopBeans,
0,
address(this),
type(uint256).max
);
rewardSop(wellDeltaB.well, amountOut, address(sopToken));
emit SeasonOfPlentyWell(
s.sys.season.current,
wellDeltaB.well,
address(sopToken),
amountOut
);
}
}

Impact

Attacker can increase the supply of Bean and therefore decrease it's price down to 0 by manipulating reserves in soppable Well.

Tools Used

Manual Review

Recommendations

Use Instantaneous reserves to calculate deltaBin LibFlood.getWellsByDeltaB() instead of current.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

T1MOH Submitter
about 1 year ago
inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.