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

Rounding Errors in Integer Division Leading to Precision Loss in Stalk and Roots Minting

Description

The mintActiveStalk function is responsible for minting Stalk and Roots to a user's account. The calculation of Roots involves division operations, which are susceptible to rounding errors due to Solidity's integer division behavior. These rounding errors can lead to slight inaccuracies in user balances, which may accumulate over time and affect the overall system integrity.
The root cause of this issue is the use of integer division in Solidity, which always rounds down to the nearest integer. This can be observed in the mintActiveStalk function where the calculation of Roots involves division operations.

Proof of concept

1: initial state:

  • s.sys.silo.stalk = 1000

  • s.sys.silo.roots = 4999

  • User has 100 stalk

  • Minting 123 stalk

2: Calculation:

roots = s.sys.silo.roots.mul(stalk).div(s.sys.silo.stalk);
roots = 4999 * 123 / 1000;
roots = 614877 / 1000; // This division will truncate the decimal part
roots = 614; // Fractional part 0.877 is truncated

3: Result:

  • Expected Roots: 614.877 (accurate calculation)

  • Actual Roots: 614 (due to rounding down)

Test

Test Case 1: Small Values

function testMintActiveStalkSmallValues() public {
uint256 initialStalk = 1000;
uint256 initialRoots = 4999;
uint256 mintStalk = 123;
// Setup initial state
setupSilo(initialStalk, initialRoots);
// Mint stalk
mintActiveStalk(account, mintStalk);
// Check results
assertEqual(s.sys.silo.stalk, 1123);
assertEqual(s.sys.silo.roots, 5613); // 4999 + 614
}

Test Case 2: Large Values

function testMintActiveStalkLargeValues() public {
uint256 initialStalk = 1e18;
uint256 initialRoots = 5e18;
uint256 mintStalk = 1e17;
// Setup initial state
setupSilo(initialStalk, initialRoots);
// Mint stalk
mintActiveStalk(account, mintStalk);
// Check results
assertEqual(s.sys.silo.stalk, initialStalk + mintStalk);
assertEqual(s.sys.silo.roots, 5.1e18); // Adjusted for precision
}

Impact

1: Users' balances of Stalk and Roots will not accurately reflect their true value, leading to discrepancies.

2: Over time, the accumulation of rounding errors can affect the total supply calculations and the overall balance within the system.

3: Inaccurate balances can lead to user mistrust in the platform, potentially resulting in reduced user engagement and participation.

Tools Used

Manual review

Recommendations

1: Scaling Up Before Division

uint256 roots;
if (s.sys.silo.roots == 0) {
roots = uint256(stalk.mul(C.getRootsBase()));
} else {
uint256 totalStalk = s.sys.silo.stalk;
uint256 totalRoots = s.sys.silo.roots;
uint256 scaledRoots = totalRoots.mul(stalk).mul(PRECISION).div(totalStalk);
roots = scaledRoots.div(PRECISION);
}

2: Using Fixed-Point Arithmetic
Implementing a fixed-point arithmetic library to handle fractional values can ensure precision is maintained:

uint256 constant PRECISION = 1e18;
function mintActiveStalk(address account, uint256 stalk) internal {
AppStorage storage s = LibAppStorage.diamondStorage();
uint256 roots;
if (s.sys.silo.roots == 0) {
roots = uint256(stalk.mul(C.getRootsBase()));
} else {
uint256 totalStalk = s.sys.silo.stalk;
uint256 totalRoots = s.sys.silo.roots;
uint256 scaledStalk = stalk.mul(PRECISION);
roots = totalRoots.mul(scaledStalk).div(totalStalk).div(PRECISION);
}
s.sys.silo.stalk = s.sys.silo.stalk.add(stalk);
s.accts[account].stalk = s.accts[account].stalk.add(stalk);
s.sys.silo.roots = s.sys.silo.roots.add(roots);
s.accts[account].roots = s.accts[account].roots.add(roots);
emit StalkBalanceChanged(account, int256(stalk), int256(roots));
}
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.