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

`mulWadUp` has logic flaws which will produces wrong results in edge cases

Summary

logic flaw in mulWadUp function cause producing wrong results in certain cases.

Vulnerability Details

Library MathMasters contains a given function

/// @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))
}
}

The given function is used to return ( x * y ) / WAD rounded up. With current code it does so in most cases.
if we check line if mul(y, gt(x, or(div(not(0), y), x))) . It has or operation which will cause to overflow the multiplication of (x * y).
Let's check the other marked line if iszero(sub(div(add(z, x), y), 1)) { x := add(x, 1) }, it

  • add(z, x): Adds the values of z and x.

  • div(add(z, x), y): Divides the result of the addition by the value of y.

  • sub(div(add(z, x), y), 1): Subtracts 1 from the result of the division.

  • iszero(...): Checks if the result of the subtraction is zero.

  • if iszero(...) { x := add(x, 1) }: If the result is zero, it increments the value of x by 1.

This statement cause issue to final result in many cases, which can be verified via fuzz testing.
it fails miserably when we do fuzz testing.

POC

In foundry.toml update the fuzz runs to 1000000
let's test if reverts when (x * y) overflows.

In existing testsuite, write following test

function testOverFlowInMulWadUp () public {
uint256 x = 2;
uint256 y = type(uint256).max;
/// Ideally it should revert as x * y is overflowing.
uint256 result = MathMasters.mulWadUp(x, y);
console2.log("Result:", result);
}

run forge test --mt testOverFlowInMulWadUp -vv in your terminal and it will print following results.

Running 1 test for test/MathMasters.t.sol:MathMastersTest
[PASS] testOverFlowInMulWadUp() (gas: 3614)
Logs:
Result: 115792089237316195423570985008687907853269984665640564039458
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 429.63µs

then run forge --mt testMulWadFuzz -vv in your terminal, to test the scenarios where it fails to hold condition
uint256 expected = x * y == 0 ? 0 : (x * y - 1) / 1e18 + 1

results shows like this --

[FAIL. Reason: assertion failed; counterexample: calldata=0xfb6ea94f00000000000000000000000000000000000000000002bfc66f73219d55eb3274000000000000000000000000000000000000000000015fe337b990ceaaf5993b args=[3323484123583475243233908 [3.323e24], 1661742061791737621616955 [1.661e24]]] testMulWadUpFuzz(uint256,uint256) (runs: 3979, μ: 921, ~: 1167)
Encountered a total of 1 failing tests, 0 tests succeeded

we run test again, just to double confirm
we get

Test result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 179.89ms
Ran 1 test suites: 0 tests passed, 1 failed, 0 skipped (1 total tests)
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, μ: 921, ~: 1167)
Encountered a total of 1 failing tests, 0 tests succeeded

Impact

Wrong results will lead to make this library useless and projects which using this can have loss of funds

Tools Used

Foundry, Manual Review

Recommendations

Here is the updated snippet to fix the code

/// @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))) {
+ if mul(y, gt(x, div(not(0), y))){
- mstore(0x40, 0xbac65e5b) // `MathMasters__MulWadFailed()`.
+ mstore(0x00, 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))
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
over 1 year ago
inallhonesty 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

`mulWadUp` has a bad overflow check

Wrong error selector

Wrong error storage

Support

FAQs

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