The MathMasters::mulWadUp
function should calculates the expression (x * y) / 1e18
and to round the result up. The function correctly check if there is a remainder and add 1
if it is necessary. But by some values for x
and y
the function increments x
with 1
which leads to incorrect calculation and incorrect final result of z
.
The function MathMasters::mulWadUp
accepts two input parameters (uint256 x
and uint256 y
) and calculates the expression x * y / 1e18
and rounds the result up.
There is a if
statement in the function that increments the x
value with 1
. Maybe, the reason for doing that is the result to be rounded up, but this is incorrect.
Let's take a look to the if
statement that increments the value of x
: iszero(sub(div(add(z, x), y), 1))
.
add(z, x)
: This add z
and x
. But z
is supposed to be the result from the MathMasters::mulWadUp
function, it is not initialized and therefore its value is 0
. So, this addition is useless.
div(add(z, x), y)
: This divides the result of addition that is x
by the value of y
.
sub(div(add(z, x), y), 1)
: This subtracts the result of division by 1
.
iszero(sub(div(add(z, x), y), 1))
: This checks if the result after subtraction is 0
. If it is 0
, the if
statement will be true and the x
value will be incremented by 1
.
When this result can be 0
?
If the values of x
and y
are equal.
If the value of x
is slightly greater than the value of y
but not enough to make the division result reach 2
when rounded down to the nearest integer.
In theese cases the value of x
will be incremented by 1
. But this incremention leads to incorrect final result.
In the last line of the function is assigned the final result to the variable z
: z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD))
. Also, there is a check if the result of the expression (x * y) / WAD
has a remainder. If there is a remainder, 1
is added to round up the result.
Let's consider the following scenario:
The values of x
and y
are both equal to 3e18
. The following test function testMulWadUpCalculation
calculates the expected solution and compares it to the solution retrieved from the MathMasters::mulWadUp
function. You can add this test function in the MathMasters.t.sol
file and execute it with the Foundry
command: forge test --match-test "testMulWadUpCalculation"
Test fails:
The result from the MathMasters::mulWadUp
function is 9000000000000000003
and the expected result is 9000000000000000000
. This difference is because the incorrect incrementation of the value of x
.
The following test in Halmos
found also the case by which the value of x
is slightly greater than the value of y
but not enough to make the division result reach 2
when rounded down to the nearest integer. Halmos
is a great tool for formal verification. To execute this test you should have Halmos
installed and import the SymTest
from lib/halmos-cheatcodes/src/SymTest.sol
. Then you can use the command to execute the test function: halmos --function check_check_MulWadUp --solver-timeout-assertion 0
.
The input arguments for the functions are of type uint128
, because with the type uint256
the result was Killed
.
So, the Halmos
found the counterexample: x = 20901944742440337407
and y = 15364007485707028177
. I stoped Halmos
after the first found example because it took about 30 minutes. After that, I decided to compare the results from MathMasters::mulWadUp
function and Solody::mulWadUp
function. The following test demonstrates this, again with the help of Halmos
:
This time the type of the input arguments is not changed and the result is:
All tests show that the function MathMasters::mulWadUp
calculates the expression (x * y) / 1e18
incorrectly due to the unnecessary increment of x
.
VS Code, Foundry, Halmos
Remove the if
statement that increments the value of x
.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.