Converting LP to bean during the conditions price > 1 and pod rate < %5 leads to penalty. But, by exploiting the lack of reentrancy guard it is possible to bypass the penalty.
Suppose the following conditions:
the BeanEth well has positive deltaB
price > 1
pod rate < %5
So, it is expected that converting LP to bean would result in penalty.
An attacker can skip the penalty of converting LP to bean by using the following steps:
Suppose the attacker has deposited some LP in season 2.
Now we are in season 4 that is raining.
P > 1, and podRate < 5%, so there will be a flood in season 5.
In season 4, the attacker would like to convert LP to bean without paying the penalty.
The attacker at the end of season 4 (i.e. 1 hour is so far elapsed from season 4, so it is possibe to step into the season 5), calls pipelineConvert
to convert LP to bean.
The passed parameter advancedFarmCalls
calls a series of function (encoded by the attacker) during the convert. In other words, first the input token (LP) will be withdrawn, then these series of functions will be invoked to do the conversion and well interaction, then output token (bean) is deposited. These series of functions are as follows in order:
calls sunrise
to step into the season 5. Since season 4 is rainy, and the conditions (P > 1, and podRate < %5) are met there will be a flood in season 5. So, the protocol will mint some beans and distribute them to the BeanEth well. Note that the function advancedPipe
in the contract DepotFacet
which is called during handling these series of function calls has modifier noSupplyIncrease
(i.e. it is not allowed to mint beans), but sunrise
a season that there is flood results in minting of beans. It is explained in the following steps how this limitation is bypassed.
calls removeLiquidityOneToken
in the well to burn LP and receive bean tokens.
calls run
in the contract MaliciousContract
where it sows some beans equal to the newly minted beans in the protocol. By sowing these beans, they will be burned and the total supply of beans will reduce to the initial value before converting. So, by sowing beans (equal to the newly minted beans) the limitation of noSupplyIncrease
is bypassed. It is assumed that this contract is already holding some beans at least equal to the newly minted beans in the protocol.
Note that since deltaB in the well is positive, burning LP to remove bean from the well increases the deltaB, so it leads to penalty. But, due to the flood, some beans will be minted into this well, decreasing the deltaB. Then, when the attacker burns LP to remove bean from this well, the deltaB of this well still is in a state better than before. So, here no penalty will be applied to the attacker, because the protocol calculates the penalty based on the start/finish deltaB of the pool, and since the deltaB was not increased, the protocol perceives this conversion as a conversion in the direction of pegging. But, in reality, this is the protocol that is decreasing deltaB, not the attacker. Specifically, the attacker is moving the deltaB in the direction of unpegging, but due to the newly minted beans thanks to the flood, the overall deltaB is in the direction of pegging, so no penalty is applied.
When the convert function is finished, the state is as follows:
attacker could convert LP to bean without paying penalty
attacker has some pods due to sowing beans in the field
total supply of beans is not changed (the same amount of the beans minted during the flood and incentivization of calling sunrise
is sowed in the field and burned)
pod rate is increased due to the sowing function
In the following foundry test, by calling the helper function setDeltaBforWell
, the state of the well is set so that it applies penalty when converting LP to beans. The attacker deposited 600e6 in season 2. Then two seasons are passed. Then, the contract MaliciousContract
is deployed, and it is assumed that this contract is holding 1000e6 beans. Then the function initialize
in this contract is called, where it records the current total supply of beans.
Then, the helper function setSoilE
is called to mint 1000e6 soil for the protocol. Then the farm calls are created in order of:
sunrise
removeLiquidityOneToken
run
Then, the helper function rainSunrise
is called where it mimics the start of the rainy season 4. Then, the function pipelineConvert
is called (in real case it should be called at the end of season 4 where it allows to step into season 5, but to simplify, timing is not taken into consideration in this test case).
It shows that before converting the season number was 4, but after converting execution the season is 5. It means the sunrise
is successfully called during the convert, and the limitation noSupplyIncrease
is bypassed. Moreover, it shows that the total pods before convert was equal to 0, while after the convert it is increased to 317354586
.
To show that no penalty is applied, the console.log
is used in the function executePipelineConvert
to show the value of pipeData.stalkPenaltyBdv
which is equal to zero.
Moreover, the grown stalks associated with beanEthWell before the converting is equal to 7088444616
, while the grown stalks associated with bean after the converting is equal to 7088444569
. The difference is 47
which is related to the rounding issue explained in other report completely.
The output is:
skipping the penalty for converting LP to bean during the condtions P > 1 and podRate < %5
acting in the direction of unpeggin without paying penalty
It is recommended to use nonReentrant
modifier for the function sowWithMin
.
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.