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

ReseedBarn.sol doesn't initialize of `s.sys.fert.recapitalized`

Summary

After migration to L2 Fertilizer module will incorrectly calculate amount of debt to repay because during migration it doesn't initialize variable which tracks amount of recapitalized debt.

As a result, it has several consequensec described below.

Vulnerability Details

1st is that Fertilizer can be minted when debt is repaid because following line won't revert. Instead it will continue execution of Barn logic.
https://github.com/Cyfrin/2024-05-beanstalk-the-finale/blob/df2dd129a878d16d4adc75049179ac0029d9a96b/protocol/contracts/libraries/LibFertilizer.sol#L92

function addUnderlying(
uint256 tokenAmountIn,
uint256 usdAmount,
uint256 minAmountOut
) internal {
AppStorage storage s = LibAppStorage.diamondStorage();
// Calculate how many new Deposited Beans will be minted
@> uint256 percentToFill = usdAmount.mul(C.precision()).div(remainingRecapitalization());
...
}

2ns is that Unripe Convert works incorrectly because of incorrect calculation of conversion between UnripeLP and UnripeBean, perticularly it inflates ratio LP/Bean.
LibUnripe.percentLPRecapped() is used to calculate ratio between UnripeLP and UnripeBean. And as you can see it will overestimate price LP/Bean because LibUnripe.percentLPRecapped() returns much lower result than expected.
https://github.com/Cyfrin/2024-05-beanstalk-the-finale/blob/df2dd129a878d16d4adc75049179ac0029d9a96b/protocol/contracts/libraries/LibUnripe.sol#L42-L45

function percentLPRecapped() internal view returns (uint256 percent) {
AppStorage storage s = LibAppStorage.diamondStorage();
return C.unripeLPPerDollar().mul(s.sys.fert.recapitalized).div(C.unripeLP().totalSupply());
}

This function is used in the core logic of Unripe Convert:
https://github.com/Cyfrin/2024-05-beanstalk-the-finale/blob/df2dd129a878d16d4adc75049179ac0029d9a96b/protocol/contracts/libraries/Convert/LibUnripeConvert.sol#L21-L77

function convertLPToBeans(
bytes memory convertData
) internal returns (address tokenOut, address tokenIn, uint256 amountOut, uint256 amountIn) {
tokenOut = C.UNRIPE_BEAN;
tokenIn = C.UNRIPE_LP;
(uint256 lp, uint256 minBeans) = convertData.basicConvert();
uint256 minAmountOut = LibUnripe
.unripeToUnderlying(tokenOut, minBeans, IBean(C.UNRIPE_BEAN).totalSupply())
@> .mul(LibUnripe.percentLPRecapped())
.div(LibUnripe.percentBeansRecapped());
(uint256 outUnderlyingAmount, uint256 inUnderlyingAmount) = LibWellConvert
._wellRemoveLiquidityTowardsPeg(
LibUnripe.unripeToUnderlying(tokenIn, lp, IBean(C.UNRIPE_LP).totalSupply()),
minAmountOut,
LibBarnRaise.getBarnRaiseWell()
);
amountIn = LibUnripe.underlyingToUnripe(tokenIn, inUnderlyingAmount);
LibUnripe.removeUnderlying(tokenIn, inUnderlyingAmount);
IBean(tokenIn).burn(amountIn);
amountOut = LibUnripe
.underlyingToUnripe(tokenOut, outUnderlyingAmount)
.mul(LibUnripe.percentBeansRecapped())
@> .div(LibUnripe.percentLPRecapped());
LibUnripe.addUnderlying(tokenOut, outUnderlyingAmount);
@@>> IBean(tokenOut).mint(address(this), amountOut);
}
function convertBeansToLP(
bytes memory convertData
) internal returns (address tokenOut, address tokenIn, uint256 amountOut, uint256 amountIn) {
tokenIn = C.UNRIPE_BEAN;
tokenOut = C.UNRIPE_LP;
(uint256 beans, uint256 minLP) = convertData.basicConvert();
uint256 minAmountOut = LibUnripe
.unripeToUnderlying(tokenOut, minLP, IBean(C.UNRIPE_LP).totalSupply())
.mul(LibUnripe.percentBeansRecapped())
@> .div(LibUnripe.percentLPRecapped());
(uint256 outUnderlyingAmount, uint256 inUnderlyingAmount) = LibWellConvert
._wellAddLiquidityTowardsPeg(
LibUnripe.unripeToUnderlying(tokenIn, beans, IBean(C.UNRIPE_BEAN).totalSupply()),
minAmountOut,
LibBarnRaise.getBarnRaiseWell()
);
amountIn = LibUnripe.underlyingToUnripe(tokenIn, inUnderlyingAmount);
LibUnripe.removeUnderlying(tokenIn, inUnderlyingAmount);
IBean(tokenIn).burn(amountIn);
amountOut = LibUnripe
.underlyingToUnripe(tokenOut, outUnderlyingAmount)
@> .mul(LibUnripe.percentLPRecapped())
.div(LibUnripe.percentBeansRecapped());
LibUnripe.addUnderlying(tokenOut, outUnderlyingAmount);
@@>> IBean(tokenOut).mint(address(this), amountOut);
}

This issue will at least cause revert after conversion because Penalty was applied due to disbalance. And will at most cause incorrect conversion and as a result a drain of Beanstalk, however I don't have enough time left to investigate such attack vector.

Impact

Fertilizer will remain mintable when in fact debt is recapitalized. It means all minters of that extra Fertilizer will lose money.

And there is another more severe impact: Unripe Convert won't work because it calculates incorrect price between UnripeBean and UnripeLp.

Tools Used

Manual Review

Recommendations

Set s.sys.fert.recapitalized in ReseedBarn.sol.

Updates

Lead Judging Commences

inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Validated
Assigned finding tags:

ReseedBarn.sol doesn't initialize of `s.sys.fert.recapitalized`

Support

FAQs

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