Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

```MathMasters::mulWadUp``` performs a potential wrong rounding Logic

Summary

The MathMasters::mulWadUp function performs a multiplication operation on two unsigned integers and returns the result as a fixed-point number, rounded up. However, there is an issue with the rounding logic that lead to incorrect results under certain conditions.

Vulnerability Details

The rounding logic in the mulWadUpv function is based on the condition if iszero(sub(div(add(z, x), y), 1)) { x := add(x, 1) }```. This condition checks if the sum of z and x divided by y minus 1 is zero. If it is, it increments x by 1. However, this logic isn't correct. The issue arises from the fact that the variable z is used before it's actually calculated.
In the code, z is used in the calculation div(add(z, x), y) before it's assigned a value. This is problematic because z is supposed to hold the result of the multiplication and division operation, but at this point in the code, it hasn't been assigned yet. As a result, the calculation doesn't behave as expected, leading to incorrect results.

/// @dev Equivalent to `(x * y) / WAD` rounded up.
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
if mul(y, gt(x, or(div(not(0), y), x))) {
mstore(0x40, 0xbac65e5b) // `MathMasters__MulWadFailed()`.
revert(0x1c, 0x04)
}
@> if iszero(sub(div(add(z, x), y), 1)) { x := add(x, 1) }
z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD))
}
}

Impact

The rounding logic is flawed, it leads to incorrect results when the mulWadUp function is used. This affects any functionality in any the application that relies on this function.

Run this test using:
runs = 1000000
in the foundry.toml

function testMulWadUpFuzz(uint256 x, uint256 y) public {
if (x == 0 || y == 0 || y <= type(uint256).max / x) {
uint256 result = MathMasters.mulWadUp(x, y);
uint256 expected = x * y == 0 ? 0 : (x * y - 1) / 1e18 + 1;
assertEq(result, expected);
}
}
Logs: Failing tests:
Encountered 1 failing test in test/MathMasters.t.sol:MathMastersTest
[FAIL. Reason: assertion failed; counterexample: calldata=0xfb6ea94f00000000000000000000000000000000000000000002bfc66f73219d55eb3274000000000000000000000000000000000000000000015fe337b990ceaaf5993b args=[3323484123583475243233908 [3.323e24], 1661742061791737621616955 [1.661e24]]] testMulWadUpFuzz(uint256,uint256) (runs: 3979, μ: 920, ~: 1167)

Tools Used

Manual review

Recommendations

To fix this issue, we could modify the rounding logic to ensure it handles all edge cases correctly. One way to do this is to always round up if there's a remainder after the division, regardless of whether the sum of z and x divided by y minus 1 is zero. Here's how you could implement this:

function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
if mul(y, gt(x, or(div(not(0), y), x))) {
mstore(0x40, 0xbac65e5b) // `MathMasters__MulWadFailed()`.
revert(0x1c, 0x04)
}
- if iszero(sub(div(add(z, x), y), 1)) { x := add(x, 1) }
z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD))
+ if iszero(sub(div(add(z, x), y), 1)) { x := add(x, 1) }
}
}

If you run the test again it passes.

Logs:
Running 1 test for test/MathMasters.t.sol:MathMastersTest
[PASS] testMulWadUpFuzz(uint256,uint256) (runs: 1000000, μ: 937, ~: 1182)
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 45.32s
Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)
Updates

Lead Judging Commences

inallhonesty Lead Judge
over 1 year ago
patrickalphac Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

`mulWadUp` has an unnecessary line that makes the result wrong for some inputs

Support

FAQs

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