// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract Bit {
// Find most significant bit using binary search
function mostSignificantBit(uint256 x)
external
pure
returns (uint256 msb)
{
// x >= 2 ** 128
if (x >= 0x100000000000000000000000000000000) {
x >>= 128;
msb += 128;
}
// x >= 2 ** 64
if (x >= 0x10000000000000000) {
x >>= 64;
msb += 64;
}
// x >= 2 ** 32
if (x >= 0x100000000) {
x >>= 32;
msb += 32;
}
// x >= 2 ** 16
if (x >= 0x10000) {
x >>= 16;
msb += 16;
}
// x >= 2 ** 8
if (x >= 0x100) {
x >>= 8;
msb += 8;
}
// x >= 2 ** 4
if (x >= 0x10) {
x >>= 4;
msb += 4;
}
// x >= 2 ** 2
if (x >= 0x4) {
x >>= 2;
msb += 2;
}
// x >= 2 ** 1
if (x >= 0x2) msb += 1;
}
}
Test file:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "forge-std/Test.sol";
import {Bit} from "../src/Bit.sol";
// forge test --match-path test/Fuzz.t.sol
// Topics
// - fuzz
// - assume and bound
// - stats
// (runs: 256, μ: 18301, ~: 10819)
// runs - number of tests
// μ - mean gas used
// ~ - median gas used
contract FuzzTest is Test {
Bit public b;
function setUp() public {
b = new Bit();
}
function mostSignificantBit(uint256 x) private pure returns (uint256) {
uint256 i = 0;
while ((x >>= 1) > 0) {
i++;
}
return i;
}
function testMostSignificantBitManual() public {
assertEq(b.mostSignificantBit(0), 0);
assertEq(b.mostSignificantBit(1), 0);
assertEq(b.mostSignificantBit(2), 1);
assertEq(b.mostSignificantBit(4), 2);
assertEq(b.mostSignificantBit(8), 3);
assertEq(b.mostSignificantBit(type(uint256).max), 255);
}
function testMostSignificantBitFuzz(uint256 x) public {
// assume - If false, the fuzzer will discard the current fuzz inputs
// and start a new fuzz run
// Skip x = 0
// vm.assume(x > 0);
// assertGt(x, 0);
// bound(input, min, max) - bound input between min and max
// Bound
x = bound(x, 1, 10);
// assertGe(x, 1);
// assertLe(x, 10);
uint256 i = b.mostSignificantBit(x);
assertEq(i, mostSignificantBit(x));
}
}
What is fuzzing?
Fuzzing basically means generating a bunch of random inputs and feeding them into a function. If the input violates the assertion, then we just found a bug.
A usual test case in a Foundry test does not have input parameter. If there is input parameter, then Foundry will consider it as a fuzzing scenario. For example:
contract SafeTest is Test {
// ...
function testFuzz_Withdraw(uint256 amount) public {
payable(address(safe)).transfer(amount);
uint256 preBalance = address(this).balance;
safe.withdraw();
uint256 postBalance = address(this).balance;
assertEq(preBalance + amount, postBalance);
}
}
bound and assume
Back to the Bit contract. If we want to limit the value of x in a certain range, we can use vm.assume(). This cheatcode is going to set a constrait on x generated by the fuzzer: if the generated input does not satisfy the condition, fuzzer is going to drop it and start another round of fuzzing. For example:
vm.assume(x > 0);
If x <= 0 then it will be dropped.
Another way of setting a range for x is using bound(). It sets lower bound and upper bound for x:
x = bound(x, 1, 10);
In the end we would feed x into the function and test its result:
uint256 i = b.mostSignificantBit(x);
assertEq(i, mostSignificantBit(x));