Line of code
https://github.com/Cyfrin/2024-05-beanstalk-the-finale/blob/df2dd129a878d16d4adc75049179ac0029d9a96b/protocol/contracts/libraries/LibReceiving.sol#L136
Summary
shipment barnReceive s.sys.fert.leftoverBeans missing state update
Vulnerability Details
while (newBpf >= firstBpf) {
deltaFertilized += (firstBpf - oldBpf) * s.sys.fert.activeFertilizer;
if (LibFertilizer.pop()) {
oldBpf = firstBpf;
firstBpf = s.sys.fert.fertFirst;
remainingBpf = (amountToFertilize - deltaFertilized) / s.sys.fert.activeFertilizer;
newBpf = oldBpf + remainingBpf;
}
else {
s.sys.fert.bpf = uint128(firstBpf);
s.sys.fert.fertilizedIndex += deltaFertilized;
@ require(amountToFertilize == deltaFertilized, "Inexact amount of Beans at Barn");
require(s.sys.fert.fertilizedIndex == s.sys.fert.unfertilizedIndex, "Paid != owed");
return;
}
}
s.sys.fert.bpf = uint128(newBpf);
deltaFertilized += (remainingBpf * s.sys.fert.activeFertilizer);
s.sys.fert.fertilizedIndex += deltaFertilized;
@ s.sys.fert.leftoverBeans = amountToFertilize - deltaFertilized;
In the code block above if we hit the else block, we will return and the remaining logic will not execute. More specifically we can observe the code block below.
s.sys.fert.bpf = uint128(firstBpf);
s.sys.fert.fertilizedIndex += deltaFertilized;
require(amountToFertilize == deltaFertilized, "Inexact amount of Beans at Barn");
require(s.sys.fert.fertilizedIndex == s.sys.fert.unfertilizedIndex, "Paid != owed");
return;
the code returns early, while amountToFertilize == deltaFertilized,, the code needs to reset leftoverBeans and set it to 0 but failed to reset leftoverBeans to 0
Impact
There will be up to activeFertilizer Beans leftover Beans that are not fertilized, These leftovers will be applied on future Fertilizer receipts, but in this case, there is no leftovers and should not be applied on future Fertilizer receipts, otherwise future Fertilizer receipts is incorrectly inflated when computing the function
https://github.com/Cyfrin/2024-05-beanstalk-the-finale/blob/df2dd129a878d16d4adc75049179ac0029d9a96b/protocol/contracts/beanstalk/Invariable.sol#L186
getTokenEntitlementsAndBalances, thus make all function that consume
https://github.com/Cyfrin/2024-05-beanstalk-the-finale/blob/df2dd129a878d16d4adc75049179ac0029d9a96b/protocol/contracts/beanstalk/Invariable.sol#L35
the modifier fundSafe inaccurate.
Tools Used
Manual review
Recommendations
ensure we reset the leftoverBeans by setting the value to 0 in the codeblock before returning.
s.sys.fert.leftoverBeans = 0;