Summary
The LibEvaluate
calculates the caseId
based on the state of Beanstalk.
This library has the following functions
The problem is that calcDeltaPodDemand
generates a wrong value whenever the change in soil demand should be set to steady. i.e: Decimal.from(1e18)
:
function calcDeltaPodDemand(
uint256 dsoil
)
internal
view
returns (Decimal.D256 memory deltaPodDemand, uint32 lastSowTime, uint32 thisSowTime)
{
AppStorage storage s = LibAppStorage.diamondStorage();
if (s.sys.weather.thisSowTime < type(uint32).max) {
if (
s.sys.weather.lastSowTime == type(uint32).max ||
s.sys.weather.thisSowTime < SOW_TIME_DEMAND_INCR ||
(s.sys.weather.lastSowTime > SOW_TIME_STEADY &&
s.sys.weather.thisSowTime < s.sys.weather.lastSowTime.sub(SOW_TIME_STEADY))
) {
@> deltaPodDemand = Decimal.from(1e18);
} else if (
s.sys.weather.thisSowTime <= s.sys.weather.lastSowTime.add(SOW_TIME_STEADY)
) {
deltaPodDemand = Decimal.one();
} else {
deltaPodDemand = Decimal.zero();
}
} else {
uint256 lastDeltaSoil = s.sys.weather.lastDeltaSoil;
if (dsoil == 0) {
deltaPodDemand = Decimal.zero();
} else if (lastDeltaSoil == 0) {
@> deltaPodDemand = Decimal.from(1e18);
} else {
deltaPodDemand = Decimal.ratio(dsoil, lastDeltaSoil);
}
}
lastSowTime = s.sys.weather.thisSowTime;
thisSowTime = type(uint32).max;
}
Notice that two paths can assign the Decimal.from(1e18);
to deltaPodDemand
.
The value returned by Decimal.from(1e18);
is 1e36
. This occurs because the function from
multiplies the value passed as a parameter by 1e18:
Decimal -> from
uint256 constant BASE = 10 ** 18;
function from(uint256 a) internal pure returns (D256 memory) {
return D256({value: a.mul(BASE)});
}
With the wrong value returned for the delta pod demand, the function evalDeltaPodDemand
will assign caseId = 2, indicating increasing demand for soil.
function evalDeltaPodDemand(
Decimal.D256 memory deltaPodDemand
) internal view returns (uint256 caseId) {
AppStorage storage s = LibAppStorage.diamondStorage();
if (
@> deltaPodDemand.greaterThanOrEqualTo(
s.sys.seedGaugeSettings.deltaPodDemandUpperBound.toDecimal()
)
) {
caseId = 2;
} else if (
deltaPodDemand.greaterThanOrEqualTo(
s.sys.seedGaugeSettings.deltaPodDemandLowerBound.toDecimal()
)
) {
caseId = 1;
}
}
But in this case, the correct value for the caseId should be 1
.
Further, this value is used to update caseId, temperature and the gauge system.
Weather -> calcCaseIdandUpdate
function calcCaseIdandUpdate(int256 deltaB) internal returns (uint256) {
uint256 beanSupply = C.bean().totalSupply();
if (beanSupply == 0) {
s.sys.weather.temp = 1;
return 9;
}
@> (uint256 caseId, bool oracleFailure) = LibEvaluate.evaluateBeanstalk(deltaB, beanSupply);
@> updateTemperatureAndBeanToMaxLpGpPerBdvRatio(caseId, oracleFailure);
@> LibFlood.handleRain(caseId);
return caseId;
}
The wrong caseId
will also impact the demand for soil.
PoC
On MockSeasonFacet
add a function to retrieve the delta pod demand:
import {Decimal} from "contracts/libraries/Decimal.sol";
function getPodDemand(uint256 soil) external view returns (Decimal.D256 memory deltaPodDemand) {
(deltaPodDemand, ,) = LibEvaluate.calcDeltaPodDemand(soil);
}
On Gauge.t.sol
add the proper imports and the test:
import {Decimal} from "contracts/libraries/Decimal.sol";
import "forge-std/console.sol";
function test_whenPodDemandIsSteady_shouldReturnCorrectValue() public {
uint256 dsoil = 1;
Decimal.D256 memory deltaPodDemand = season.getPodDemand(dsoil);
console.log("deltaPodDemand: %e", deltaPodDemand.value);
assertEq(deltaPodDemand.value, 1e18, "deltaPodDemand should be 1e18");
}
Run: forge test --match-test test_whenPodDemandIsSteady_shouldReturnCorrectValue -vv
Output:
@> [FAIL. Reason: deltaPodDemand should be 1e18: 1000000000000000000000000000000000000 != 1000000000000000000] test_whenPodDemandIsSteady_shouldReturnCorrectValue() (gas: 20840)
Logs:
@> deltaPodDemand: 1e36
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 249.53ms (1.02ms CPU time
Impact
Beanstalk will adjust caseId and temperature as if there is a increasing demand for pods, even though the demand is actually steady.
Beanstalk will update the gauge points per BDV for LP with the incorrect value.
The demand for soil will be incorrectly calculated.
The logic for the handleRain(caseId)
will also be impacted.
Tools Used
Manual Review & Foundry
Recommendations
Replace Decimal.from(1e18)
with Decimal.one()