The ConvertFacet features a convert function designed to facilitate the conversion of one deposit into another, incorporating a decreaseBDV flag to manage adjustments to the Bean Denominated Value (BDV). This flag, when activated, logically necessitates setting toBDV to the lesser value between newBdv and fromBdv, aligning with the intention to reduce BDVs. Documentation further clarifies that, absent the decreaseBDV flag, toBDV should reflect the higher of these two values. Consequently, enabling decreaseBDV implies adjusting toBDV to the lower value, a logical approach intended to conserve resources. However, the current implementation incorrectly sets toBDV to newBdv regardless of the decreaseBDV flag's status, undermining the intended functionality and potentially affecting the accuracy of deposit germination statuses through incorrect stem data processing.
Take a look at https://github.com/Cyfrin/2024-05-Beanstalk-3/blob/662d26f12ee219ee92dc485c06e01a4cb5ee8dfb/protocol/contracts/beanstalk/silo/ConvertFacet.sol#L69-L116
This function allows a user to convert a deposit to another deposit, given that the conversion is supported by the ConvertFacet.
Now, protocol implements a decreaseBDV flag that could be set/unset, whenever this is set it's understandable that the toBDV needs to be set to the lower value of newBdv & fromBdv, this can be concluded from common logic since we are trying to decrease the BDVs and also the fact that the documentation clearly states that if the decreaseBDV flag is not enabled, then the toBDV needs to be set to the max of the two bdvs. So this means that if the decreaseBDV is indeed enabled, the opposite should be done and the toBDV should be set to the lower value of the two bdvs.
This however is not correctly implemented due to the way the check is coded. The code reads:
toBdv = (newBdv > fromBdv || cp.decreaseBDV) ? newBdv : fromBdv;
This would always chose the right value for toBdv whenever the decreaseBDV flag is not enabled since the cp.decreaseBDV check would always evaluate to false and the final value that gets set for toBDV depends on the newBdv > fromBdv , however, whenever the decreaseBDV flag is indeed enabled, then the cp.decreaseBDV check is always going to be true which means that regardless of the newBdv > fromBdv check, toBDV is always going to be set to newBdv which would flaw the execution, since the decrease flag is set and least of the two between newBdv & fromBdv needs to be set to toBDV.
Keep in mind that the toStem value calculated is directly affected by the toBDV passed, since the _depositTokensForConvert() function delegates the calculation to LibTokenSilo#calculateStalkFromStemAndBdv() which has a direct relation between the toStem & the toBDV value.
Now since the toStem value processed by the flow would be flawed, this would then lead to the wrong finalization of the germinating status of a deposit's stem, since the evaluation in LibGerminate._getGerminationState() would be checking with the wrong stem data.
NB: The work flow to
LibGerminate._getGerminationState()is shown below:
ConvertFacet.convert() -> ConvertFacet._depositTokensForConvert() -> LibTokenSilo.calculateStemForTokenFromGrownStalk() -> LibGerminate.getGerminationState() -> LibGerminate._getGerminationState()
The ConvertFacet#convert() does not always work as expected when the decreaseBDV flag is enabled, this report shows how there is a disparity in how the conversions treat converts in relation to when the decreaseBDV flag is set or not.
Which leads to multiple issues including the wrong finalization of the stem status since the LibGerminate._getGerminationState() integrates with wrong data.
Manual review
Reimplement the way toBDV is set in ConvertFacet#convert() whenever the decreaseBDV flag is enabled, consider breaking the checks into two and then have the toBDV when the decreaseBDV flag is not enabled set as the max between newBdv & fromBdv and then when the decreaseBDV flag is enabled, have the toBDV set to the least between newBdv & fromBdv.
Below is a pseudo fix in how the new implementation of the function should look
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.