DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: medium
Invalid

Rounding Errors in Division Operations Leading to Inaccurate Fertilizer Distribution

Summary

The mintFertilizer function calculates the amount of Fertilizer to mint based on the input token amount and the USD price of the Barn Raise token. Due to Solidity's integer division, the result is rounded down to the nearest integer, leading to users receiving less Fertilizer than they should.

function mintFertilizer(
uint256 tokenAmountIn,
uint256 minFertilizerOut,
uint256 minLPTokensOut
) external payable fundsSafu noOutFlow returns (uint256 fertilizerAmountOut) {
fertilizerAmountOut = _getMintFertilizerOut(
tokenAmountIn,
LibBarnRaise.getBarnRaiseToken()
);
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.
require(fertilizerAmountOut <= remaining, "Fertilizer: Not enough remaining.");
uint128 id = LibFertilizer.addFertilizer(
uint128(s.sys.season.current),
tokenAmountIn,
fertilizerAmountOut,
minLPTokensOut
);
C.fertilizer().beanstalkMint(
LibTractor._user(),
uint256(id),
(fertilizerAmountOut).toUint128(),
s.sys.fert.bpf
);
}

_getMintFertilizerOut Function:

function _getMintFertilizerOut(
uint256 tokenAmountIn,
address barnRaiseToken
) public view returns (uint256 fertilizerAmountOut) {
fertilizerAmountOut = tokenAmountIn.div(LibUsdOracle.getUsdPrice(barnRaiseToken));
}

Proof of Concept

Let's consider a scenario where the price of the Barn Raise token is $3.50. This scenario will help illustrate potential rounding errors and their impact on the mintFertilizer function.

Given Values:

  • tokenAmountIn is 1000 Barn Raise tokens.

  • The USD price of 1 Barn Raise token is $3.50, represented as 3500000 with 6 decimals of precision.

Calculation in _getMintFertilizerOut Function:

fertilizerAmountOut = tokenAmountIn.div(LibUsdOracle.getUsdPrice(barnRaiseToken));

Substituting the Values:

1: Multiplication:

  • tokenAmountIn = 1000 * 10^18 = 1000000000000000000000.

2: Division:

  • LibUsdOracle.getUsdPrice(barnRaiseToken) = 3500000.

3: Fertilizer Amount Calculation:

fertilizerAmountOut = 1000000000000000000000 / 3500000.

Detailed Division:
Exact Calculation:

  • 1000000000000000000000 / 3500000 = 285714285714285.714285714285714285714.

Truncated Result:

  • Since Solidity performs integer division, the result is truncated to 285714285714285.

Thus, the function would calculate fertilizerAmountOut as 285714285714285.

Rounding Error:

  • The exact result was slightly higher than 285714285714285 but less than 285714285714286.

  • Due to truncation, the user receives 285714285714285 units of Fertilizer instead of the exact fractional amount.

Impact

1: User Receives Less Fertilizer:

  • In this scenario, the user receives 285714285714285 units of Fertilizer instead of the exact fractional amount.

  • This minor discrepancy can accumulate over multiple transactions, leading to significant differences.

2: Discrepancies in Recapitalization:

  • Over time, these small rounding errors can result in noticeable discrepancies in the protocol's recapitalization calculations.

Tools Used

Manual review

Recommendations

1: Implement fixed-point arithmetic to handle fractional values accurately without rounding errors.

function _getMintFertilizerOut(
uint256 tokenAmountIn,
address barnRaiseToken
) public view returns (uint256 fertilizerAmountOut) {
uint256 usdPrice = LibUsdOracle.getUsdPrice(barnRaiseToken);
fertilizerAmountOut = (tokenAmountIn * 10**18 + usdPrice - 1) / usdPrice; // Add usdPrice - 1 for rounding up
}

2: Increase the precision of calculations by using a higher precision factor and then adjusting the result accordingly.

3: Introduce rounding mechanisms to round up or down based on specific rules to minimize the impact of rounding errors.

Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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