Summary
The MathMasters.sol::mulWadUp()
function may produce incorrect results for certain input values.
Vulnerability Details
The MathMasters.sol::mulWadUp()
function contains a code segment that, if certain conditions are satisfied, modifies one of the input values (x
) used in later calculations, which could result in an incorrect outcome.
The code is as follows:
if iszero(sub(div(add(z, x), y), 1)) { x := add(x, 1) }
Impact
The MathMasters.sol::mulWadUp()
function will produce incorrect results for certain input values.
Proof of Concept (PoC)
We can verify this behavior by running the existing test testMulWadUpFuzz
in MathMasters.t.sol
.
Fuzz testing using Foundry
By increasing the number of fuzz runs for each fuzz test case, we can potentially obtain counterexamples for which this test will fail.
The foundry.toml
configuration file can be modified to increase the number of fuzz runs for each fuzz test case:
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
[fuzz]
-runs = 100
+runs = 1000000
# runs = 1000000 # Use this one for prod
max_test_rejects = 65536
seed = '0x1'
dictionary_weight = 40
include_storage = true
include_push_bytes = true
extra_output = ["storageLayout", "metadata"]
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
Run a test with forge test --mt testMulWadUpFuzz
.
We should see an output similar to this in the terminal:
Running 1 test for test/MathMasters.t.sol:MathMastersTest
[FAIL. Reason: assertion failed; counterexample: calldata=0xfb6ea94f00000000000000000000000000000000000000000002bfc66f73219d55eb3274000000000000000000000000000000000000000000015fe337b990ceaaf5993b args=[3323484123583475243233908 [3.323e24], 1661742061791737621616955 [1.661e24]]] testMulWadUpFuzz(uint256,uint256) (runs: 3979, μ: 944, ~: 1189)
Test result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 184.17ms
Formal verification testing using Halmos
We can also use Halmos, a symbolic testing tool, to execute the same test:
halmos --function testMulWadUpFuzz --solver-timeout-assertion 0
Halmos should find counterexamples for which this test fails. We should see an output similar to this in the terminal:
Running 1 tests for test/MathMasters.t.sol:MathMastersTest
Counterexample:
p_x_uint256 = 0x0000000000000000000000000000000100000003ae000000383e000000800214 (340282367212473342719134338885200380436)
p_y_uint256 = 0x00000000000000000000000000000000d01034837fffffffbd0a10977a23ed2d (276563564976801819313845769479676751149)
Tools Used
Manual review
Foundry
Halmos
Recommendations
It is recommended to remove the code segment that modifies the x
parameter.
Recommended changes to MathMasters.sol::mulWadUp()
function:
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))
}
}
We can create a tests to check inputs that both Foundry and Halmos identified as counterexamples while running the testMulWadUpFuzz
test in MathMasters.t.sol
.
Add the following tests in MathMasters.t.sol
:
function test_MulWadUpCounterexampleFuzz() public {
uint256 x = 3323484123583475243233908;
uint256 y = 1661742061791737621616955;
uint256 result = MathMasters.mulWadUp(x, y);
uint256 expected = x * y == 0 ? 0 : (x * y - 1) / 1e18 + 1;
assertEq(result, expected);
}
function test_MulWadUpCounterexampleHalmos() public {
uint256 x = 340282367212473342719134338885200380436;
uint256 y = 276563564976801819313845769479676751149;
uint256 result = MathMasters.mulWadUp(x, y);
uint256 expected = x * y == 0 ? 0 : (x * y - 1) / 1e18 + 1;
assertEq(result, expected);
}
Run both tests with forge test --mt test_MulWadUpCounterexample
.