DeFiHardhat
35,000 USDC
View results
Submission Details
Severity: low
Invalid

`FertilizerFacet.sol` Capacity of fertilizer mint will be reached fast because of a rounding issue in the `mintFertilizer` function

Summary

Capacity of remaining fertilizers will be reached a lot faster than it should because the remaining capacity being returned during the check before minting is further rounded down when it has already been rounded down initially.

Vulnerability Details

The mintFertilizer function introduces a severe rounding down issue during minting of fertilizers in the FertilizerFacet.sol contract. The function below enforces a critical check to make sure the capacity of how much should be minted is not breached.

We fetch the remaining capacity from the function call below:
uint128 remaining = uint128(LibFertilizer.remainingRecapitalization().div(1e6));

Then, we proceed to make sure the amount being minted is not breaching capacity with the check that comes right after:
require(fertilizerAmountOut <= remaining, "Fertilizer: Not enough remaining.");

FILE: FertilizerFacet.sol
function mintFertilizer(
uint256 tokenAmountIn,
uint256 minFertilizerOut,
uint256 minLPTokensOut
) external payable returns (uint256 fertilizerAmountOut) {
fertilizerAmountOut = _getMintFertilizerOut(tokenAmountIn, LibBarnRaise.getBarnRaiseToken()); // @audit if the tokenAmountIn is 18 dec precision, what about the barnRaiseTokenDecimals? 18 still?
require(fertilizerAmountOut >= minFertilizerOut, "Fertilizer: Not enough bought.");
require(fertilizerAmountOut > 0, "Fertilizer: None bought.");
@> uint128 remaining = uint128(LibFertilizer.remainingRecapitalization().div(1e6)); // remaining <= 77_000_000 so downcasting is safe. @audit why do we divide this by 1e6 again causing it to round further down?
require(fertilizerAmountOut <= remaining, "Fertilizer: Not enough remaining.");
uint128 id = LibFertilizer.addFertilizer(
uint128(s.season.current),
tokenAmountIn,
fertilizerAmountOut,
minLPTokensOut
);
C.fertilizer().beanstalkMint(msg.sender, uint256(id), (fertilizerAmountOut).toUint128(), s.bpf);
}

A snippet of the remainingRecapitalization() function can be seen below from LibFertilizer.sol:

FILE: LibFertilizer.sol
function remainingRecapitalization()
internal
view
returns (uint256 remaining)
{
AppStorage storage s = LibAppStorage.diamondStorage();
uint256 totalDollars = C
.dollarPerUnripeLP()
.mul(C.unripeLP().totalSupply())
.div(DECIMALS);
@> totalDollars = totalDollars / 1e6 * 1e6; // round down to nearest USDC == @note scaled down
if (s.recapitalized >= totalDollars) return 0; // @note early return assuming no more recapitalization/buffer
return totalDollars.sub(s.recapitalized); // @note if there is still some mint buffer, return the remaining in USDC
}

Impact

The end result of this is that the remaining amount in USDC that can be minted will not be same figure as the LibFertilizer.remainingRecapitalization() returns it as. This means for example, for a remaining figure of 1 thousand USDC returned by LibFertilizer.remainingRecapitalization() as 1000e6, we go ahead to further round it down by USDC 6 decimals (1e6) so that 1000e6 will be 1e3. With this rough estimation of 1k USDC, we can already see how fast the capitalization will be reached, hence making minting of fertilizers impossible after a while of user mints because the true remaining figure got adversely rounded down further when it has already been handled in the LibFertilizer.remainingRecapitalization() function.

Tools Used

Manual Review + foundry

Recommendation

Lose the current excessive rounding in the FertilizerFacet.sol#mintFertilizer function during a mint of fertilizer where we check that we do not breach capacity for the remaining amounts as this is already being scaled in the LibFertilizer.remainingRecapitalization() function.

+ uint128 remaining = uint128(LibFertilizer.remainingRecapitalization()); // remaining <= 77_000_000 so downcasting is safe.
- uint128 remaining = uint128(LibFertilizer.remainingRecapitalization().div(1e6)); // remaining <= 77_000_000 so downcasting is safe.
Updates

Lead Judging Commences

giovannidisiena Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Other
Assigned finding tags:

Informational/Invalid

Support

FAQs

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