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

Rounding Errors in Bean to Pod Conversion Leading to Inconsistent Harvestable Calculations

Summary

The LibDibbler library uses a fixed interest rate known as the temperature to convert Beans into Pods. The conversion formula, pods = beans * (1 + temperature / 100%), involves multiplication and division operations that can introduce rounding errors. These errors occur because Solidity uses integer arithmetic, which truncates fractional parts. This results in users receiving a slightly different number of Pods than expected, affecting fairness and trust in the protocol.

Boundary Conditions and mulDiv Limitations

The mulDiv function is designed to mitigate rounding errors by performing multiplication and division in a single step, thus avoiding intermediate precision loss. However, it does not completely eliminate rounding errors, especially at boundary conditions where values are right at the edge of a rounding threshold.

i: Boundary Condition Example:

  • If a calculation yields a result like 4.99999, it will be rounded down to 4, causing a loss of precision.

  • This is a significant boundary condition issue, as users at the edge of the rounding threshold may receive fewer Pods than they should.

Relevant Code Snippets

beansToPods Function:

function beansToPods(uint256 beans, uint256 _morningTemperature) internal pure returns (uint256 pods) {
pods = beans.mulDiv(_morningTemperature.add(ONE_HUNDRED_PCT), ONE_HUNDRED_PCT);
}

scaleSoilUp Function:

function scaleSoilUp(uint256 soil, uint256 maxTemperature, uint256 _morningTemperature) internal pure returns (uint256) {
return soil.mulDiv(maxTemperature.add(ONE_HUNDRED_PCT), _morningTemperature.add(ONE_HUNDRED_PCT));
}

scaleSoilDown Function:

function scaleSoilDown(uint256 soil, uint256 _morningTemperature, uint256 maxTemperature) internal pure returns (uint256) {
return soil.mulDiv(_morningTemperature.add(ONE_HUNDRED_PCT), maxTemperature.add(ONE_HUNDRED_PCT), LibPRBMathRoundable.Rounding.Up);
}

Proof of Concept

Consider a scenario where a user sows Beans under specific temperature conditions. Due to rounding errors, the number of Pods received can be slightly less or more than expected. This scenario demonstrates a boundary condition where the rounding errors are most noticeable.

Example Scenario:

  • Beans: 999

  • Temperature: 5000000 (representing 5% with precision 1e6)

  • Expected Pods:

pods = 999 * (1 + 5000000/100000000) = 999 * 1.05 = 1048.95

Due to integer arithmetic, the result in Solidity will be truncated:

  • Actual Pods: 1048 (truncated from 1048.95)

This shows a boundary condition where the value 1048.95 is rounded down to 1048, leading to a slight under-allocation of Pods.

Test

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../contracts/LibDibbler.sol"; // Adjust the path as necessary
contract LibDibblerTest is Test {
function testBeansToPods_RoundingDown() public {
uint256 beans = 999;
uint256 temperature = 5000000; // 5% with precision 1e6
uint256 expectedPods = 1048; // Expected value after truncation
uint256 pods = LibDibbler.beansToPods(beans, temperature);
assertEq(pods, expectedPods, "Rounding down caused discrepancy in Pods");
}
function testBeansToPods_RoundingUp() public {
uint256 beans = 999;
uint256 temperature = 5000000; // 5% with precision 1e6
uint256 expectedPods = 1049; // Expected value after rounding up
// Adjust the beans to hit the rounding up condition
uint256 pods = LibDibbler.beansToPods(beans + 1, temperature);
assertEq(pods, expectedPods, "Rounding up caused discrepancy in Pods");
}
function testScaleSoilUp() public {
uint256 soil = 500;
uint256 maxTemperature = 1250000000; // 1250% with precision 1e6
uint256 morningTemperature = 5000000; // 5% with precision 1e6
uint256 expectedScaledSoil = 3292; // Expected value after truncation
uint256 scaledSoil = LibDibbler.scaleSoilUp(soil, maxTemperature, morningTemperature);
assertEq(scaledSoil, expectedScaledSoil, "Scaling up soil caused discrepancy");
}
function testScaleSoilDown() public {
uint256 soil = 500;
uint256 morningTemperature = 5000000; // 5% with precision 1e6
uint256 maxTemperature = 1250000000; // 1250% with precision 1e6
uint256 expectedScaledSoil = 75; // Expected value after truncation
uint256 scaledSoil = LibDibbler.scaleSoilDown(soil, morningTemperature, maxTemperature);
assertEq(scaledSoil, expectedScaledSoil, "Scaling down soil caused discrepancy");
}
}

Impact

  • Users may receive slightly fewer or more Pods than they should, leading to fairness issues.

  • Over time, these discrepancies can accumulate, potentially leading to significant imbalances within the protocol.

Tools Used

Manual review

Recommendations

  1. Explicitly specify rounding directions in critical functions to avoid unintended rounding down or up.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational/Gas

Invalid as per docs https://docs.codehawks.com/hawks-auditors/how-to-determine-a-finding-validity

Support

FAQs

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