From 1198c9f70b30d472a7d0ec021bec080622191b03 Mon Sep 17 00:00:00 2001 From: atarpara Date: Wed, 3 Dec 2025 18:05:01 +0530 Subject: [PATCH 1/5] Added CLZ opcodes --- foundry.toml | 2 +- src/utils/clz/FixedPointMathLib.sol | 1254 +++++++++++++++++++++++++++ src/utils/clz/LibBit.sol | 287 ++++++ src/utils/clz/LibZip.sol | 325 +++++++ 4 files changed, 1867 insertions(+), 1 deletion(-) create mode 100644 src/utils/clz/FixedPointMathLib.sol create mode 100644 src/utils/clz/LibBit.sol create mode 100644 src/utils/clz/LibZip.sol diff --git a/foundry.toml b/foundry.toml index bcfe0c08a..6fb19e8a1 100644 --- a/foundry.toml +++ b/foundry.toml @@ -10,7 +10,7 @@ auto_detect_solc = false optimizer = true optimizer_runs = 1_000 gas_limit = 100_000_000 # ETH is 30M, but we use a higher value. -skip = ["*/*7702*", "*/*BlockHashLib*", "*/*Transient*", "*/ext/ithaca/*", "*/ext/zksync/*"] +skip = ["*/*7702*", "*/*BlockHashLib*", "*/*Transient*", "*/ext/ithaca/*", "*/clz/*" ,"*/ext/zksync/*"] fs_permissions = [{ access = "read", path = "./test/data"}] remappings = [ "forge-std=test/utils/forge-std/" diff --git a/src/utils/clz/FixedPointMathLib.sol b/src/utils/clz/FixedPointMathLib.sol new file mode 100644 index 000000000..ba7692115 --- /dev/null +++ b/src/utils/clz/FixedPointMathLib.sol @@ -0,0 +1,1254 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.30; + +/// @notice Arithmetic library with operations for fixed-point numbers. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/FixedPointMathLib.sol) +/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol) +library FixedPointMathLib { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The operation failed, as the output exceeds the maximum value of uint256. + error ExpOverflow(); + + /// @dev The operation failed, as the output exceeds the maximum value of uint256. + error FactorialOverflow(); + + /// @dev The operation failed, due to an overflow. + error RPowOverflow(); + + /// @dev The mantissa is too big to fit. + error MantissaOverflow(); + + /// @dev The operation failed, due to an multiplication overflow. + error MulWadFailed(); + + /// @dev The operation failed, due to an multiplication overflow. + error SMulWadFailed(); + + /// @dev The operation failed, either due to a multiplication overflow, or a division by a zero. + error DivWadFailed(); + + /// @dev The operation failed, either due to a multiplication overflow, or a division by a zero. + error SDivWadFailed(); + + /// @dev The operation failed, either due to a multiplication overflow, or a division by a zero. + error MulDivFailed(); + + /// @dev The division failed, as the denominator is zero. + error DivFailed(); + + /// @dev The full precision multiply-divide operation failed, either due + /// to the result being larger than 256 bits, or a division by a zero. + error FullMulDivFailed(); + + /// @dev The output is undefined, as the input is less-than-or-equal to zero. + error LnWadUndefined(); + + /// @dev The input outside the acceptable domain. + error OutOfDomain(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The scalar of ETH and most ERC20s. + uint256 internal constant WAD = 1e18; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* SIMPLIFIED FIXED POINT OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Equivalent to `(x * y) / WAD` rounded down. + function mulWad(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 gt(x, div(not(0), y)) { + if y { + mstore(0x00, 0xbac65e5b) // `MulWadFailed()`. + revert(0x1c, 0x04) + } + } + z := div(mul(x, y), WAD) + } + } + + /// @dev Equivalent to `(x * y) / WAD` rounded down. + function sMulWad(int256 x, int256 y) internal pure returns (int256 z) { + /// @solidity memory-safe-assembly + assembly { + z := mul(x, y) + // Equivalent to `require((x == 0 || z / x == y) && !(x == -1 && y == type(int256).min))`. + if iszero(gt(or(iszero(x), eq(sdiv(z, x), y)), lt(not(x), eq(y, shl(255, 1))))) { + mstore(0x00, 0xedcd4dd4) // `SMulWadFailed()`. + revert(0x1c, 0x04) + } + z := sdiv(z, WAD) + } + } + + /// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks. + function rawMulWad(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := div(mul(x, y), WAD) + } + } + + /// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks. + function rawSMulWad(int256 x, int256 y) internal pure returns (int256 z) { + /// @solidity memory-safe-assembly + assembly { + z := sdiv(mul(x, y), WAD) + } + } + + /// @dev Equivalent to `(x * y) / WAD` rounded up. + function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := mul(x, y) + // Equivalent to `require(y == 0 || x <= type(uint256).max / y)`. + if iszero(eq(div(z, y), x)) { + if y { + mstore(0x00, 0xbac65e5b) // `MulWadFailed()`. + revert(0x1c, 0x04) + } + } + z := add(iszero(iszero(mod(z, WAD))), div(z, WAD)) + } + } + + /// @dev Equivalent to `(x * y) / WAD` rounded up, but without overflow checks. + function rawMulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD)) + } + } + + /// @dev Equivalent to `(x * WAD) / y` rounded down. + function divWad(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + // Equivalent to `require(y != 0 && x <= type(uint256).max / WAD)`. + if iszero(mul(y, lt(x, add(1, div(not(0), WAD))))) { + mstore(0x00, 0x7c5f487d) // `DivWadFailed()`. + revert(0x1c, 0x04) + } + z := div(mul(x, WAD), y) + } + } + + /// @dev Equivalent to `(x * WAD) / y` rounded down. + function sDivWad(int256 x, int256 y) internal pure returns (int256 z) { + /// @solidity memory-safe-assembly + assembly { + z := mul(x, WAD) + // Equivalent to `require(y != 0 && ((x * WAD) / WAD == x))`. + if iszero(mul(y, eq(sdiv(z, WAD), x))) { + mstore(0x00, 0x5c43740d) // `SDivWadFailed()`. + revert(0x1c, 0x04) + } + z := sdiv(z, y) + } + } + + /// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks. + function rawDivWad(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := div(mul(x, WAD), y) + } + } + + /// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks. + function rawSDivWad(int256 x, int256 y) internal pure returns (int256 z) { + /// @solidity memory-safe-assembly + assembly { + z := sdiv(mul(x, WAD), y) + } + } + + /// @dev Equivalent to `(x * WAD) / y` rounded up. + function divWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + // Equivalent to `require(y != 0 && x <= type(uint256).max / WAD)`. + if iszero(mul(y, lt(x, add(1, div(not(0), WAD))))) { + mstore(0x00, 0x7c5f487d) // `DivWadFailed()`. + revert(0x1c, 0x04) + } + z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y)) + } + } + + /// @dev Equivalent to `(x * WAD) / y` rounded up, but without overflow and divide by zero checks. + function rawDivWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y)) + } + } + + /// @dev Equivalent to `x` to the power of `y`. + /// because `x ** y = (e ** ln(x)) ** y = e ** (ln(x) * y)`. + /// Note: This function is an approximation. + function powWad(int256 x, int256 y) internal pure returns (int256) { + // Using `ln(x)` means `x` must be greater than 0. + return expWad((lnWad(x) * y) / int256(WAD)); + } + + /// @dev Returns `exp(x)`, denominated in `WAD`. + /// Credit to Remco Bloemen under MIT license: https://2π.com/22/exp-ln + /// Note: This function is an approximation. Monotonically increasing. + function expWad(int256 x) internal pure returns (int256 r) { + unchecked { + // When the result is less than 0.5 we return zero. + // This happens when `x <= (log(1e-18) * 1e18) ~ -4.15e19`. + if (x <= -41446531673892822313) return r; + + /// @solidity memory-safe-assembly + assembly { + // When the result is greater than `(2**255 - 1) / 1e18` we can not represent it as + // an int. This happens when `x >= floor(log((2**255 - 1) / 1e18) * 1e18) ≈ 135`. + if iszero(slt(x, 135305999368893231589)) { + mstore(0x00, 0xa37bfec9) // `ExpOverflow()`. + revert(0x1c, 0x04) + } + } + + // `x` is now in the range `(-42, 136) * 1e18`. Convert to `(-42, 136) * 2**96` + // for more intermediate precision and a binary basis. This base conversion + // is a multiplication by 1e18 / 2**96 = 5**18 / 2**78. + x = (x << 78) / 5 ** 18; + + // Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers + // of two such that exp(x) = exp(x') * 2**k, where k is an integer. + // Solving this gives k = round(x / log(2)) and x' = x - k * log(2). + int256 k = ((x << 96) / 54916777467707473351141471128 + 2 ** 95) >> 96; + x = x - k * 54916777467707473351141471128; + + // `k` is in the range `[-61, 195]`. + + // Evaluate using a (6, 7)-term rational approximation. + // `p` is made monic, we'll multiply by a scale factor later. + int256 y = x + 1346386616545796478920950773328; + y = ((y * x) >> 96) + 57155421227552351082224309758442; + int256 p = y + x - 94201549194550492254356042504812; + p = ((p * y) >> 96) + 28719021644029726153956944680412240; + p = p * x + (4385272521454847904659076985693276 << 96); + + // We leave `p` in `2**192` basis so we don't need to scale it back up for the division. + int256 q = x - 2855989394907223263936484059900; + q = ((q * x) >> 96) + 50020603652535783019961831881945; + q = ((q * x) >> 96) - 533845033583426703283633433725380; + q = ((q * x) >> 96) + 3604857256930695427073651918091429; + q = ((q * x) >> 96) - 14423608567350463180887372962807573; + q = ((q * x) >> 96) + 26449188498355588339934803723976023; + + /// @solidity memory-safe-assembly + assembly { + // Div in assembly because solidity adds a zero check despite the unchecked. + // The q polynomial won't have zeros in the domain as all its roots are complex. + // No scaling is necessary because p is already `2**96` too large. + r := sdiv(p, q) + } + + // r should be in the range `(0.09, 0.25) * 2**96`. + + // We now need to multiply r by: + // - The scale factor `s ≈ 6.031367120`. + // - The `2**k` factor from the range reduction. + // - The `1e18 / 2**96` factor for base conversion. + // We do this all at once, with an intermediate result in `2**213` + // basis, so the final right shift is always by a positive amount. + r = int256( + (uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k) + ); + } + } + + /// @dev Returns `ln(x)`, denominated in `WAD`. + /// Credit to Remco Bloemen under MIT license: https://2π.com/22/exp-ln + /// Note: This function is an approximation. Monotonically increasing. + function lnWad(int256 x) internal pure returns (int256 r) { + /// @solidity memory-safe-assembly + assembly { + if iszero(sgt(x, 0)) { + mstore(0x00, 0x1615e638) // `LnWadUndefined()`. + revert(0x1c, 0x04) + } + + // We want to convert `x` from `10**18` fixed point to `2**96` fixed point. + // We do this by multiplying by `2**96 / 10**18`. But since + // `ln(x * C) = ln(x) + ln(C)`, we can simply do nothing here + // and add `ln(2**96 / 10**18)` at the end. + // Compute `k = log2(x) - 96`, `r = 159 - k = 255 - log2(x) = 255 - (255 - clz(x)) + // then k = clz(x)`. + r := clz(x) + // Reduce range of x to (1, 2) * 2**96 + // ln(2^k * x) = k * ln(2) + ln(x) + x := shr(159, shl(r, x)) + + // Evaluate using a (8, 8)-term rational approximation. + // `p` is made monic, we will multiply by a scale factor later. + // forgefmt: disable-next-item + let p := sub( // This heavily nested expression is to avoid stack-too-deep for via-ir. + sar(96, mul(add(43456485725739037958740375743393, + sar(96, mul(add(24828157081833163892658089445524, + sar(96, mul(add(3273285459638523848632254066296, + x), x))), x))), x)), 11111509109440967052023855526967) + p := sub(sar(96, mul(p, x)), 45023709667254063763336534515857) + p := sub(sar(96, mul(p, x)), 14706773417378608786704636184526) + p := sub(mul(p, x), shl(96, 795164235651350426258249787498)) + // We leave `p` in `2**192` basis so we don't need to scale it back up for the division. + + // `q` is monic by convention. + let q := add(5573035233440673466300451813936, x) + q := add(71694874799317883764090561454958, sar(96, mul(x, q))) + q := add(283447036172924575727196451306956, sar(96, mul(x, q))) + q := add(401686690394027663651624208769553, sar(96, mul(x, q))) + q := add(204048457590392012362485061816622, sar(96, mul(x, q))) + q := add(31853899698501571402653359427138, sar(96, mul(x, q))) + q := add(909429971244387300277376558375, sar(96, mul(x, q))) + + // `p / q` is in the range `(0, 0.125) * 2**96`. + + // Finalization, we need to: + // - Multiply by the scale factor `s = 5.549…`. + // - Add `ln(2**96 / 10**18)`. + // - Add `k * ln(2)`. + // - Multiply by `10**18 / 2**96 = 5**18 >> 78`. + + // The q polynomial is known not to have zeros in the domain. + // No scaling required because p is already `2**96` too large. + p := sdiv(p, q) + // Multiply by the scaling factor: `s * 5**18 * 2**96`, base is now `5**18 * 2**192`. + p := mul(1677202110996718588342820967067443963516166, p) + // Add `ln(2) * k * 5**18 * 2**192`. + // forgefmt: disable-next-item + p := add(mul(16597577552685614221487285958193947469193820559219878177908093499208371, sub(159, r)), p) + // Add `ln(2**96 / 10**18) * 5**18 * 2**192`. + p := add(600920179829731861736702779321621459595472258049074101567377883020018308, p) + // Base conversion: mul `2**18 / 2**192`. + r := sar(174, p) + } + } + + /// @dev Returns `W_0(x)`, denominated in `WAD`. + /// See: https://en.wikipedia.org/wiki/Lambert_W_function + /// a.k.a. Product log function. This is an approximation of the principal branch. + /// Note: This function is an approximation. Monotonically increasing. + function lambertW0Wad(int256 x) + internal + pure + returns (int256 w) + { + + // forgefmt: disable-next-item + unchecked { + if ((w = x) <= -367879441171442322) revert OutOfDomain(); // `x` less than `-1/e`. + (int256 wad, int256 p) = (int256(WAD), x); + uint256 c; // Whether we need to avoid catastrophic cancellation. + uint256 i = 4; // Number of iterations. + if (w <= 0x1ffffffffffff) { + if (-0x4000000000000 <= w) { + i = 1; // Inputs near zero only take one step to converge. + } else if (w <= -0x3ffffffffffffff) { + i = 32; // Inputs near `-1/e` take very long to converge. + } + } else if (uint256(w >> 63) == uint256(0)) { + /// @solidity memory-safe-assembly + assembly { + // Inline log2 for more performance, since the range is small. + let v := shr(49, w) + let l := shl(3, lt(0xff, v)) + l := add(or(l, byte(and(0x1f, shr(shr(l, v), 0x8421084210842108cc6318c6db6d54be)), + 0x0706060506020504060203020504030106050205030304010505030400000000)), 49) + w := sdiv(shl(l, 7), byte(sub(l, 31), 0x0303030303030303040506080c13)) + c := gt(l, 60) + i := add(2, add(gt(l, 53), c)) + } + } else { + int256 ll = lnWad(w = lnWad(w)); + /// @solidity memory-safe-assembly + assembly { + // `w = ln(x) - ln(ln(x)) + b * ln(ln(x)) / ln(x)`. + w := add(sdiv(mul(ll, 1023715080943847266), w), sub(w, ll)) + i := add(3, iszero(shr(68, x))) + c := iszero(shr(143, x)) + } + if (c == uint256(0)) { + do { // If `x` is big, use Newton's so that intermediate values won't overflow. + int256 e = expWad(w); + /// @solidity memory-safe-assembly + assembly { + let t := mul(w, div(e, wad)) + w := sub(w, sdiv(sub(t, x), div(add(e, t), wad))) + } + if (p <= w) break; + p = w; + } while (--i != uint256(0)); + /// @solidity memory-safe-assembly + assembly { + w := sub(w, sgt(w, 2)) + } + return w; + } + } + do { // Otherwise, use Halley's for faster convergence. + int256 e = expWad(w); + /// @solidity memory-safe-assembly + assembly { + let t := add(w, wad) + let s := sub(mul(w, e), mul(x, wad)) + w := sub(w, sdiv(mul(s, wad), sub(mul(e, t), sdiv(mul(add(t, wad), s), add(t, t))))) + } + if (p <= w) break; + p = w; + } while (--i != c); + /// @solidity memory-safe-assembly + assembly { + w := sub(w, sgt(w, 2)) + } + // For certain ranges of `x`, we'll use the quadratic-rate recursive formula of + // R. Iacono and J.P. Boyd for the last iteration, to avoid catastrophic cancellation. + if (c == uint256(0)) return w; + int256 t = w | 1; + /// @solidity memory-safe-assembly + assembly { + x := sdiv(mul(x, wad), t) + } + x = (t * (wad + lnWad(x))); + /// @solidity memory-safe-assembly + assembly { + w := sdiv(x, add(wad, t)) + } + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* GENERAL NUMBER UTILITIES */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns `a * b == x * y`, with full precision. + function fullMulEq(uint256 a, uint256 b, uint256 x, uint256 y) + internal + pure + returns (bool result) + { + /// @solidity memory-safe-assembly + assembly { + result := and(eq(mul(a, b), mul(x, y)), eq(mulmod(x, y, not(0)), mulmod(a, b, not(0)))) + } + } + + /// @dev Calculates `floor(x * y / d)` with full precision. + /// Throws if result overflows a uint256 or when `d` is zero. + /// Credit to Remco Bloemen under MIT license: https://2π.com/21/muldiv + function fullMulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + // 512-bit multiply `[p1 p0] = x * y`. + // Compute the product mod `2**256` and mod `2**256 - 1` + // then use the Chinese Remainder Theorem to reconstruct + // the 512 bit result. The result is stored in two 256 + // variables such that `product = p1 * 2**256 + p0`. + + // Temporarily use `z` as `p0` to save gas. + z := mul(x, y) // Lower 256 bits of `x * y`. + for {} 1 {} { + // If overflows. + if iszero(mul(or(iszero(x), eq(div(z, x), y)), d)) { + let mm := mulmod(x, y, not(0)) + let p1 := sub(mm, add(z, lt(mm, z))) // Upper 256 bits of `x * y`. + + /*------------------- 512 by 256 division --------------------*/ + + // Make division exact by subtracting the remainder from `[p1 p0]`. + let r := mulmod(x, y, d) // Compute remainder using mulmod. + let t := and(d, sub(0, d)) // The least significant bit of `d`. `t >= 1`. + // Make sure `z` is less than `2**256`. Also prevents `d == 0`. + // Placing the check here seems to give more optimal stack operations. + if iszero(gt(d, p1)) { + mstore(0x00, 0xae47f702) // `FullMulDivFailed()`. + revert(0x1c, 0x04) + } + d := div(d, t) // Divide `d` by `t`, which is a power of two. + // Invert `d mod 2**256` + // Now that `d` is an odd number, it has an inverse + // modulo `2**256` such that `d * inv = 1 mod 2**256`. + // Compute the inverse by starting with a seed that is correct + // correct for four bits. That is, `d * inv = 1 mod 2**4`. + let inv := xor(2, mul(3, d)) + // Now use Newton-Raphson iteration to improve the precision. + // Thanks to Hensel's lifting lemma, this also works in modular + // arithmetic, doubling the correct bits in each step. + inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**8 + inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**16 + inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**32 + inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**64 + inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**128 + z := mul( + // Divide [p1 p0] by the factors of two. + // Shift in bits from `p1` into `p0`. For this we need + // to flip `t` such that it is `2**256 / t`. + or(mul(sub(p1, gt(r, z)), add(div(sub(0, t), t), 1)), div(sub(z, r), t)), + mul(sub(2, mul(d, inv)), inv) // inverse mod 2**256 + ) + break + } + z := div(z, d) + break + } + } + } + + /// @dev Calculates `floor(x * y / d)` with full precision. + /// Behavior is undefined if `d` is zero or the final result cannot fit in 256 bits. + /// Performs the full 512 bit calculation regardless. + function fullMulDivUnchecked(uint256 x, uint256 y, uint256 d) + internal + pure + returns (uint256 z) + { + /// @solidity memory-safe-assembly + assembly { + z := mul(x, y) + let mm := mulmod(x, y, not(0)) + let p1 := sub(mm, add(z, lt(mm, z))) + let t := and(d, sub(0, d)) + let r := mulmod(x, y, d) + d := div(d, t) + let inv := xor(2, mul(3, d)) + inv := mul(inv, sub(2, mul(d, inv))) + inv := mul(inv, sub(2, mul(d, inv))) + inv := mul(inv, sub(2, mul(d, inv))) + inv := mul(inv, sub(2, mul(d, inv))) + inv := mul(inv, sub(2, mul(d, inv))) + z := mul( + or(mul(sub(p1, gt(r, z)), add(div(sub(0, t), t), 1)), div(sub(z, r), t)), + mul(sub(2, mul(d, inv)), inv) + ) + } + } + + /// @dev Calculates `floor(x * y / d)` with full precision, rounded up. + /// Throws if result overflows a uint256 or when `d` is zero. + /// Credit to Uniswap-v3-core under MIT license: + /// https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/FullMath.sol + function fullMulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) { + z = fullMulDiv(x, y, d); + /// @solidity memory-safe-assembly + assembly { + if mulmod(x, y, d) { + z := add(z, 1) + if iszero(z) { + mstore(0x00, 0xae47f702) // `FullMulDivFailed()`. + revert(0x1c, 0x04) + } + } + } + } + + /// @dev Calculates `floor(x * y / 2 ** n)` with full precision. + /// Throws if result overflows a uint256. + /// Credit to Philogy under MIT license: + /// https://github.com/SorellaLabs/angstrom/blob/main/contracts/src/libraries/X128MathLib.sol + function fullMulDivN(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + // Temporarily use `z` as `p0` to save gas. + z := mul(x, y) // Lower 256 bits of `x * y`. We'll call this `z`. + for {} 1 {} { + if iszero(or(iszero(x), eq(div(z, x), y))) { + let k := and(n, 0xff) // `n`, cleaned. + let mm := mulmod(x, y, not(0)) + let p1 := sub(mm, add(z, lt(mm, z))) // Upper 256 bits of `x * y`. + // | p1 | z | + // Before: | p1_0 ¦ p1_1 | z_0 ¦ z_1 | + // Final: | 0 ¦ p1_0 | p1_1 ¦ z_0 | + // Check that final `z` doesn't overflow by checking that p1_0 = 0. + if iszero(shr(k, p1)) { + z := add(shl(sub(256, k), p1), shr(k, z)) + break + } + mstore(0x00, 0xae47f702) // `FullMulDivFailed()`. + revert(0x1c, 0x04) + } + z := shr(and(n, 0xff), z) + break + } + } + } + + /// @dev Returns `floor(x * y / d)`. + /// Reverts if `x * y` overflows, or `d` is zero. + function mulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := mul(x, y) + // Equivalent to `require(d != 0 && (y == 0 || x <= type(uint256).max / y))`. + if iszero(mul(or(iszero(x), eq(div(z, x), y)), d)) { + mstore(0x00, 0xad251c27) // `MulDivFailed()`. + revert(0x1c, 0x04) + } + z := div(z, d) + } + } + + /// @dev Returns `ceil(x * y / d)`. + /// Reverts if `x * y` overflows, or `d` is zero. + function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := mul(x, y) + // Equivalent to `require(d != 0 && (y == 0 || x <= type(uint256).max / y))`. + if iszero(mul(or(iszero(x), eq(div(z, x), y)), d)) { + mstore(0x00, 0xad251c27) // `MulDivFailed()`. + revert(0x1c, 0x04) + } + z := add(iszero(iszero(mod(z, d))), div(z, d)) + } + } + + /// @dev Returns `x`, the modular multiplicative inverse of `a`, such that `(a * x) % n == 1`. + function invMod(uint256 a, uint256 n) internal pure returns (uint256 x) { + /// @solidity memory-safe-assembly + assembly { + let g := n + let r := mod(a, n) + for { let y := 1 } 1 {} { + let q := div(g, r) + let t := g + g := r + r := sub(t, mul(r, q)) + let u := x + x := y + y := sub(u, mul(y, q)) + if iszero(r) { break } + } + x := mul(eq(g, 1), add(x, mul(slt(x, 0), n))) + } + } + + /// @dev Returns `ceil(x / d)`. + /// Reverts if `d` is zero. + function divUp(uint256 x, uint256 d) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + if iszero(d) { + mstore(0x00, 0x65244e4e) // `DivFailed()`. + revert(0x1c, 0x04) + } + z := add(iszero(iszero(mod(x, d))), div(x, d)) + } + } + + /// @dev Returns `max(0, x - y)`. Alias for `saturatingSub`. + function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := mul(gt(x, y), sub(x, y)) + } + } + + /// @dev Returns `max(0, x - y)`. + function saturatingSub(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := mul(gt(x, y), sub(x, y)) + } + } + + /// @dev Returns `min(2 ** 256 - 1, x + y)`. + function saturatingAdd(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := or(sub(0, lt(add(x, y), x)), add(x, y)) + } + } + + /// @dev Returns `min(2 ** 256 - 1, x * y)`. + function saturatingMul(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := or(sub(or(iszero(x), eq(div(mul(x, y), x), y)), 1), mul(x, y)) + } + } + + /// @dev Returns `condition ? x : y`, without branching. + function ternary(bool condition, uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := xor(x, mul(xor(x, y), iszero(condition))) + } + } + + /// @dev Returns `condition ? x : y`, without branching. + function ternary(bool condition, bytes32 x, bytes32 y) internal pure returns (bytes32 z) { + /// @solidity memory-safe-assembly + assembly { + z := xor(x, mul(xor(x, y), iszero(condition))) + } + } + + /// @dev Returns `condition ? x : y`, without branching. + function ternary(bool condition, address x, address y) internal pure returns (address z) { + /// @solidity memory-safe-assembly + assembly { + z := xor(x, mul(xor(x, y), iszero(condition))) + } + } + + /// @dev Returns `x != 0 ? x : y`, without branching. + function coalesce(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := or(x, mul(y, iszero(x))) + } + } + + /// @dev Returns `x != bytes32(0) ? x : y`, without branching. + function coalesce(bytes32 x, bytes32 y) internal pure returns (bytes32 z) { + /// @solidity memory-safe-assembly + assembly { + z := or(x, mul(y, iszero(x))) + } + } + + /// @dev Returns `x != address(0) ? x : y`, without branching. + function coalesce(address x, address y) internal pure returns (address z) { + /// @solidity memory-safe-assembly + assembly { + z := or(x, mul(y, iszero(shl(96, x)))) + } + } + + /// @dev Exponentiate `x` to `y` by squaring, denominated in base `b`. + /// Reverts if the computation overflows. + function rpow(uint256 x, uint256 y, uint256 b) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := mul(b, iszero(y)) // `0 ** 0 = 1`. Otherwise, `0 ** n = 0`. + if x { + z := xor(b, mul(xor(b, x), and(y, 1))) // `z = isEven(y) ? scale : x` + let half := shr(1, b) // Divide `b` by 2. + // Divide `y` by 2 every iteration. + for { y := shr(1, y) } y { y := shr(1, y) } { + let xx := mul(x, x) // Store x squared. + let xxRound := add(xx, half) // Round to the nearest number. + // Revert if `xx + half` overflowed, or if `x ** 2` overflows. + if or(lt(xxRound, xx), shr(128, x)) { + mstore(0x00, 0x49f7642b) // `RPowOverflow()`. + revert(0x1c, 0x04) + } + x := div(xxRound, b) // Set `x` to scaled `xxRound`. + // If `y` is odd: + if and(y, 1) { + let zx := mul(z, x) // Compute `z * x`. + let zxRound := add(zx, half) // Round to the nearest number. + // If `z * x` overflowed or `zx + half` overflowed: + if or(xor(div(zx, x), z), lt(zxRound, zx)) { + // Revert if `x` is non-zero. + if x { + mstore(0x00, 0x49f7642b) // `RPowOverflow()`. + revert(0x1c, 0x04) + } + } + z := div(zxRound, b) // Return properly scaled `zxRound`. + } + } + } + } + } + + /// @dev Returns the square root of `x`, rounded down. + function sqrt(uint256 x) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + // Step 1: Get the bit position of the most significant bit + // n = floor(log2(x)) + // For x ≈ 2^n, we know sqrt(x) ≈ 2^(n/2) + // We use (n+1)/2 instead of n/2 to round up slightly + // This gives a better initial approximation + // + // Formula: z = 2^((n+1)/2) = 2^(floor((n+1)/2)) + // Implemented as: z = 1 << ((n+1) >> 1) + z := shl(shr(1, sub(256, clz(x))), 1) + + /// (x/z + z) / 2 + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + + // If `x+1` is a perfect square, the Babylonian method cycles between + // `floor(sqrt(x))` and `ceil(sqrt(x))`. This statement ensures we return floor. + // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division + z := sub(z, lt(div(x, z), z)) + } + } + + /// @dev Returns the cube root of `x`, rounded down. + /// Credit to bout3fiddy and pcaversaccio under AGPLv3 license: + /// https://github.com/pcaversaccio/snekmate/blob/main/src/snekmate/utils/math.vy + /// Formally verified by xuwinnie: + /// https://github.com/vectorized/solady/blob/main/audits/xuwinnie-solady-cbrt-proof.pdf + function cbrt(uint256 x) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + // Initial guess z = 2^ceil((log2(x) + 2) / 3). + // Since log2(x) = 255 - clz(x), the expression shl((257 - clz(x)) / 3, 1) + // computes this over-estimate. Guaranteed ≥ cbrt(x) and safe for Newton-Raphson's. + z := shl(div(sub(257, clz(x)), 3), 1) + // Newton-Raphson's. + z := div(add(add(div(x, mul(z, z)), z), z), 3) + z := div(add(add(div(x, mul(z, z)), z), z), 3) + z := div(add(add(div(x, mul(z, z)), z), z), 3) + z := div(add(add(div(x, mul(z, z)), z), z), 3) + z := div(add(add(div(x, mul(z, z)), z), z), 3) + z := div(add(add(div(x, mul(z, z)), z), z), 3) + z := div(add(add(div(x, mul(z, z)), z), z), 3) + // Round down. + z := sub(z, lt(div(x, mul(z, z)), z)) + } + } + + /// @dev Returns the square root of `x`, denominated in `WAD`, rounded down. + function sqrtWad(uint256 x) internal pure returns (uint256 z) { + unchecked { + if (x <= type(uint256).max / 10 ** 18) return sqrt(x * 10 ** 18); + z = (1 + sqrt(x)) * 10 ** 9; + z = (fullMulDivUnchecked(x, 10 ** 18, z) + z) >> 1; + } + /// @solidity memory-safe-assembly + assembly { + z := sub(z, gt(999999999999999999, sub(mulmod(z, z, x), 1))) // Round down. + } + } + + /// @dev Returns the cube root of `x`, denominated in `WAD`, rounded down. + /// Formally verified by xuwinnie: + /// https://github.com/vectorized/solady/blob/main/audits/xuwinnie-solady-cbrt-proof.pdf + function cbrtWad(uint256 x) internal pure returns (uint256 z) { + unchecked { + if (x <= type(uint256).max / 10 ** 36) return cbrt(x * 10 ** 36); + z = (1 + cbrt(x)) * 10 ** 12; + z = (fullMulDivUnchecked(x, 10 ** 36, z * z) + z + z) / 3; + } + /// @solidity memory-safe-assembly + assembly { + let p := x + for {} 1 {} { + if iszero(shr(229, p)) { + if iszero(shr(199, p)) { + p := mul(p, 100000000000000000) // 10 ** 17. + break + } + p := mul(p, 100000000) // 10 ** 8. + break + } + if iszero(shr(249, p)) { p := mul(p, 100) } + break + } + let t := mulmod(mul(z, z), z, p) + z := sub(z, gt(lt(t, shr(1, p)), iszero(t))) // Round down. + } + } + + /// @dev Returns `sqrt(x * y)`. Also called the geometric mean. + function mulSqrt(uint256 x, uint256 y) internal pure returns (uint256 z) { + if (x == y) return x; + uint256 p = rawMul(x, y); + if (y == rawDiv(p, x)) return sqrt(p); + for (z = saturatingMul(rawAdd(sqrt(x), 1), rawAdd(sqrt(y), 1));; z = avg(z, p)) { + if ((p = fullMulDivUnchecked(x, y, z)) >= z) break; + } + } + + /// @dev Returns the factorial of `x`. + function factorial(uint256 x) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := 1 + if iszero(lt(x, 58)) { + mstore(0x00, 0xaba0f2a2) // `FactorialOverflow()`. + revert(0x1c, 0x04) + } + for {} x { x := sub(x, 1) } { z := mul(z, x) } + } + } + + /// @dev Returns the log2 of `x`. + /// Equivalent to computing the index of the most significant bit (MSB) of `x`. + /// Returns 0 if `x` is zero. + function log2(uint256 x) internal pure returns (uint256 r) { + /// @solidity memory-safe-assembly + assembly { + r := sub(255, clz(or(x, 1))) + } + } + + /// @dev Returns the log2 of `x`, rounded up. + /// Returns 0 if `x` is zero. + function log2Up(uint256 x) internal pure returns (uint256 r) { + r = log2(x); + /// @solidity memory-safe-assembly + assembly { + r := add(r, lt(shl(r, 1), x)) + } + } + + /// @dev Returns the log10 of `x`. + /// Returns 0 if `x` is zero. + function log10(uint256 x) internal pure returns (uint256 r) { + /// @solidity memory-safe-assembly + assembly { + r := shr(12, mul(1233, sub(256, clz(x)))) + r := add(sub(iszero(x), lt(x, exp(10, r))), r) + } + } + + /// @dev Returns the log10 of `x`, rounded up. + /// Returns 0 if `x` is zero. + function log10Up(uint256 x) internal pure returns (uint256 r) { + r = log10(x); + /// @solidity memory-safe-assembly + assembly { + r := add(r, lt(exp(10, r), x)) + } + } + + /// @dev Returns the log256 of `x`. + /// Returns 0 if `x` is zero. + function log256(uint256 x) internal pure returns (uint256 r) { + /// @solidity memory-safe-assembly + assembly { + r := shr(3, sub(255, clz(or(x, 1)))) + } + } + + /// @dev Returns the log256 of `x`, rounded up. + /// Returns 0 if `x` is zero. + function log256Up(uint256 x) internal pure returns (uint256 r) { + r = log256(x); + /// @solidity memory-safe-assembly + assembly { + r := add(r, lt(shl(shl(3, r), 1), x)) + } + } + + /// @dev Returns the scientific notation format `mantissa * 10 ** exponent` of `x`. + /// Useful for compressing prices (e.g. using 25 bit mantissa and 7 bit exponent). + function sci(uint256 x) internal pure returns (uint256 mantissa, uint256 exponent) { + /// @solidity memory-safe-assembly + assembly { + mantissa := x + if mantissa { + if iszero(mod(mantissa, 1000000000000000000000000000000000)) { + mantissa := div(mantissa, 1000000000000000000000000000000000) + exponent := 33 + } + if iszero(mod(mantissa, 10000000000000000000)) { + mantissa := div(mantissa, 10000000000000000000) + exponent := add(exponent, 19) + } + if iszero(mod(mantissa, 1000000000000)) { + mantissa := div(mantissa, 1000000000000) + exponent := add(exponent, 12) + } + if iszero(mod(mantissa, 1000000)) { + mantissa := div(mantissa, 1000000) + exponent := add(exponent, 6) + } + if iszero(mod(mantissa, 10000)) { + mantissa := div(mantissa, 10000) + exponent := add(exponent, 4) + } + if iszero(mod(mantissa, 100)) { + mantissa := div(mantissa, 100) + exponent := add(exponent, 2) + } + if iszero(mod(mantissa, 10)) { + mantissa := div(mantissa, 10) + exponent := add(exponent, 1) + } + } + } + } + + /// @dev Convenience function for packing `x` into a smaller number using `sci`. + /// The `mantissa` will be in bits [7..255] (the upper 249 bits). + /// The `exponent` will be in bits [0..6] (the lower 7 bits). + /// Use `SafeCastLib` to safely ensure that the `packed` number is small + /// enough to fit in the desired unsigned integer type: + /// ``` + /// uint32 packed = SafeCastLib.toUint32(FixedPointMathLib.packSci(777 ether)); + /// ``` + function packSci(uint256 x) internal pure returns (uint256 packed) { + (x, packed) = sci(x); // Reuse for `mantissa` and `exponent`. + /// @solidity memory-safe-assembly + assembly { + if shr(249, x) { + mstore(0x00, 0xce30380c) // `MantissaOverflow()`. + revert(0x1c, 0x04) + } + packed := or(shl(7, x), packed) + } + } + + /// @dev Convenience function for unpacking a packed number from `packSci`. + function unpackSci(uint256 packed) internal pure returns (uint256 unpacked) { + unchecked { + unpacked = (packed >> 7) * 10 ** (packed & 0x7f); + } + } + + /// @dev Returns the average of `x` and `y`. Rounds towards zero. + function avg(uint256 x, uint256 y) internal pure returns (uint256 z) { + unchecked { + z = (x & y) + ((x ^ y) >> 1); + } + } + + /// @dev Returns the average of `x` and `y`. Rounds towards negative infinity. + function avg(int256 x, int256 y) internal pure returns (int256 z) { + unchecked { + z = (x >> 1) + (y >> 1) + (x & y & 1); + } + } + + /// @dev Returns the absolute value of `x`. + function abs(int256 x) internal pure returns (uint256 z) { + unchecked { + z = (uint256(x) + uint256(x >> 255)) ^ uint256(x >> 255); + } + } + + /// @dev Returns the absolute distance between `x` and `y`. + function dist(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := add(xor(sub(0, gt(x, y)), sub(y, x)), gt(x, y)) + } + } + + /// @dev Returns the absolute distance between `x` and `y`. + function dist(int256 x, int256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := add(xor(sub(0, sgt(x, y)), sub(y, x)), sgt(x, y)) + } + } + + /// @dev Returns the minimum of `x` and `y`. + function min(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := xor(x, mul(xor(x, y), lt(y, x))) + } + } + + /// @dev Returns the minimum of `x` and `y`. + function min(int256 x, int256 y) internal pure returns (int256 z) { + /// @solidity memory-safe-assembly + assembly { + z := xor(x, mul(xor(x, y), slt(y, x))) + } + } + + /// @dev Returns the maximum of `x` and `y`. + function max(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := xor(x, mul(xor(x, y), gt(y, x))) + } + } + + /// @dev Returns the maximum of `x` and `y`. + function max(int256 x, int256 y) internal pure returns (int256 z) { + /// @solidity memory-safe-assembly + assembly { + z := xor(x, mul(xor(x, y), sgt(y, x))) + } + } + + /// @dev Returns `x`, bounded to `minValue` and `maxValue`. + function clamp(uint256 x, uint256 minValue, uint256 maxValue) + internal + pure + returns (uint256 z) + { + /// @solidity memory-safe-assembly + assembly { + z := xor(x, mul(xor(x, minValue), gt(minValue, x))) + z := xor(z, mul(xor(z, maxValue), lt(maxValue, z))) + } + } + + /// @dev Returns `x`, bounded to `minValue` and `maxValue`. + function clamp(int256 x, int256 minValue, int256 maxValue) internal pure returns (int256 z) { + /// @solidity memory-safe-assembly + assembly { + z := xor(x, mul(xor(x, minValue), sgt(minValue, x))) + z := xor(z, mul(xor(z, maxValue), slt(maxValue, z))) + } + } + + /// @dev Returns greatest common divisor of `x` and `y`. + function gcd(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + for { z := x } y {} { + let t := y + y := mod(z, y) + z := t + } + } + } + + /// @dev Returns `a + (b - a) * (t - begin) / (end - begin)`, + /// with `t` clamped between `begin` and `end` (inclusive). + /// Agnostic to the order of (`a`, `b`) and (`end`, `begin`). + /// If `begins == end`, returns `t <= begin ? a : b`. + function lerp(uint256 a, uint256 b, uint256 t, uint256 begin, uint256 end) + internal + pure + returns (uint256) + { + if (begin > end) (t, begin, end) = (~t, ~begin, ~end); + if (t <= begin) return a; + if (t >= end) return b; + unchecked { + if (b >= a) return a + fullMulDiv(b - a, t - begin, end - begin); + return a - fullMulDiv(a - b, t - begin, end - begin); + } + } + + /// @dev Returns `a + (b - a) * (t - begin) / (end - begin)`. + /// with `t` clamped between `begin` and `end` (inclusive). + /// Agnostic to the order of (`a`, `b`) and (`end`, `begin`). + /// If `begins == end`, returns `t <= begin ? a : b`. + function lerp(int256 a, int256 b, int256 t, int256 begin, int256 end) + internal + pure + returns (int256) + { + if (begin > end) (t, begin, end) = (~t, ~begin, ~end); + if (t <= begin) return a; + if (t >= end) return b; + // forgefmt: disable-next-item + unchecked { + if (b >= a) return int256(uint256(a) + fullMulDiv(uint256(b - a), + uint256(t - begin), uint256(end - begin))); + return int256(uint256(a) - fullMulDiv(uint256(a - b), + uint256(t - begin), uint256(end - begin))); + } + } + + /// @dev Returns if `x` is an even number. Some people may need this. + function isEven(uint256 x) internal pure returns (bool) { + return x & uint256(1) == uint256(0); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* RAW NUMBER OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns `x + y`, without checking for overflow. + function rawAdd(uint256 x, uint256 y) internal pure returns (uint256 z) { + unchecked { + z = x + y; + } + } + + /// @dev Returns `x + y`, without checking for overflow. + function rawAdd(int256 x, int256 y) internal pure returns (int256 z) { + unchecked { + z = x + y; + } + } + + /// @dev Returns `x - y`, without checking for underflow. + function rawSub(uint256 x, uint256 y) internal pure returns (uint256 z) { + unchecked { + z = x - y; + } + } + + /// @dev Returns `x - y`, without checking for underflow. + function rawSub(int256 x, int256 y) internal pure returns (int256 z) { + unchecked { + z = x - y; + } + } + + /// @dev Returns `x * y`, without checking for overflow. + function rawMul(uint256 x, uint256 y) internal pure returns (uint256 z) { + unchecked { + z = x * y; + } + } + + /// @dev Returns `x * y`, without checking for overflow. + function rawMul(int256 x, int256 y) internal pure returns (int256 z) { + unchecked { + z = x * y; + } + } + + /// @dev Returns `x / y`, returning 0 if `y` is zero. + function rawDiv(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := div(x, y) + } + } + + /// @dev Returns `x / y`, returning 0 if `y` is zero. + function rawSDiv(int256 x, int256 y) internal pure returns (int256 z) { + /// @solidity memory-safe-assembly + assembly { + z := sdiv(x, y) + } + } + + /// @dev Returns `x % y`, returning 0 if `y` is zero. + function rawMod(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := mod(x, y) + } + } + + /// @dev Returns `x % y`, returning 0 if `y` is zero. + function rawSMod(int256 x, int256 y) internal pure returns (int256 z) { + /// @solidity memory-safe-assembly + assembly { + z := smod(x, y) + } + } + + /// @dev Returns `(x + y) % d`, return 0 if `d` if zero. + function rawAddMod(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := addmod(x, y, d) + } + } + + /// @dev Returns `(x * y) % d`, return 0 if `d` if zero. + function rawMulMod(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := mulmod(x, y, d) + } + } +} diff --git a/src/utils/clz/LibBit.sol b/src/utils/clz/LibBit.sol new file mode 100644 index 000000000..6841db21a --- /dev/null +++ b/src/utils/clz/LibBit.sol @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.30; + +/// @notice Library for bit twiddling and boolean operations. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibBit.sol) +/// @author Inspired by (https://graphics.stanford.edu/~seander/bithacks.html) +library LibBit { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* BIT TWIDDLING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Find last set. + /// Returns the index of the most significant bit of `x`, + /// counting from the least significant bit position. + /// If `x` is zero, returns 256. + function fls(uint256 x) internal pure returns (uint256 r) { + /// @solidity memory-safe-assembly + assembly { + // r := add(xor(255, clz(or(x,1))), shl(8, iszero(x))) // 153092 + // r := add(sub(255, clz(x)), mul(257, iszero(x))) // 149252 + r := xor(xor(255, clz(x)), mul(255, iszero(x))) + } + } + + /// @dev Find first set. + /// Returns the index of the least significant bit of `x`, + /// counting from the least significant bit position. + /// If `x` is zero, returns 256. + /// Equivalent to `ctz` (count trailing zeros), which gives + /// the number of zeros following the least significant one bit. + function ffs(uint256 x) internal pure returns (uint256 r) { + /// @solidity memory-safe-assembly + assembly { + // Isolate the least significant bit. + x := and(x, add(not(x), 1)) + r := xor(xor(255, clz(x)), mul(255, iszero(x))) + } + } + + /// @dev Returns the number of set bits in `x`. + function popCount(uint256 x) internal pure returns (uint256 c) { + /// @solidity memory-safe-assembly + assembly { + let max := not(0) + let isMax := eq(x, max) + x := sub(x, and(shr(1, x), div(max, 3))) + x := add(and(x, div(max, 5)), and(shr(2, x), div(max, 5))) + x := and(add(x, shr(4, x)), div(max, 17)) + c := or(shl(8, isMax), shr(248, mul(x, div(max, 255)))) + } + } + + /// @dev Returns the number of zero bytes in `x`. + /// To get the number of non-zero bytes, simply do `32 - countZeroBytes(x)`. + function countZeroBytes(uint256 x) internal pure returns (uint256 c) { + /// @solidity memory-safe-assembly + assembly { + let m := 0x7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f + c := byte(0, mul(shr(7, not(m)), shr(7, not(or(or(add(and(x, m), m), x), m))))) + } + } + + /// @dev Returns the number of zero bytes in `s`. + /// To get the number of non-zero bytes, simply do `s.length - countZeroBytes(s)`. + function countZeroBytes(bytes memory s) internal pure returns (uint256 c) { + /// @solidity memory-safe-assembly + assembly { + function czb(x_) -> _c { + let _m := 0x7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f + _c := shr(7, not(or(or(add(and(x_, _m), _m), x_), _m))) + _c := byte(0, mul(shr(7, not(_m)), _c)) + } + let n := mload(s) + let l := shl(5, shr(5, n)) + s := add(s, 0x20) + for { let i } xor(i, l) { i := add(i, 0x20) } { c := add(czb(mload(add(s, i))), c) } + if lt(l, n) { + c := add(czb(or(shr(shl(3, sub(n, l)), not(0)), mload(add(s, l)))), c) + } + } + } + + /// @dev Returns the number of zero bytes in `s`. + /// To get the number of non-zero bytes, simply do `s.length - countZeroBytes(s)`. + function countZeroBytesCalldata(bytes calldata s) internal pure returns (uint256 c) { + /// @solidity memory-safe-assembly + assembly { + function czb(x_) -> _c { + let _m := 0x7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f + _c := shr(7, not(or(or(add(and(x_, _m), _m), x_), _m))) + _c := byte(0, mul(shr(7, not(_m)), _c)) + } + let l := shl(5, shr(5, s.length)) + for { let i } xor(i, l) { i := add(i, 0x20) } { + c := add(czb(calldataload(add(s.offset, i))), c) + } + if lt(l, s.length) { + let m := shr(shl(3, sub(s.length, l)), not(0)) + c := add(czb(or(m, calldataload(add(s.offset, l)))), c) + } + } + } + + /// @dev Returns whether `x` is a power of 2. + function isPo2(uint256 x) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + // Equivalent to `x && !(x & (x - 1))`. + result := iszero(add(and(x, sub(x, 1)), iszero(x))) + } + } + + /// @dev Returns `x` reversed at the bit level. + function reverseBits(uint256 x) internal pure returns (uint256 r) { + uint256 m0 = 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f; + uint256 m1 = m0 ^ (m0 << 2); + uint256 m2 = m1 ^ (m1 << 1); + r = reverseBytes(x); + r = (m2 & (r >> 1)) | ((m2 & r) << 1); + r = (m1 & (r >> 2)) | ((m1 & r) << 2); + r = (m0 & (r >> 4)) | ((m0 & r) << 4); + } + + /// @dev Returns `x` reversed at the byte level. + function reverseBytes(uint256 x) internal pure returns (uint256 r) { + unchecked { + // Computing masks on-the-fly reduces bytecode size by about 200 bytes. + uint256 m0 = 0x100000000000000000000000000000001 * (~toUint(x == uint256(0)) >> 192); + uint256 m1 = m0 ^ (m0 << 32); + uint256 m2 = m1 ^ (m1 << 16); + uint256 m3 = m2 ^ (m2 << 8); + r = (m3 & (x >> 8)) | ((m3 & x) << 8); + r = (m2 & (r >> 16)) | ((m2 & r) << 16); + r = (m1 & (r >> 32)) | ((m1 & r) << 32); + r = (m0 & (r >> 64)) | ((m0 & r) << 64); + r = (r >> 128) | (r << 128); + } + } + + /// @dev Return `x` leading zeroes bits. + function clz_(uint256 x) internal pure returns (uint256 r) { + assembly { + r := clz(x) + } + } + + /// @dev Returns the common prefix of `x` and `y` at the bit level. + function commonBitPrefix(uint256 x, uint256 y) internal pure returns (uint256) { + unchecked { + uint256 s = 256 - clz_(x ^ y); + return (x >> s) << s; + } + } + + /// @dev Returns the common prefix of `x` and `y` at the nibble level. + function commonNibblePrefix(uint256 x, uint256 y) internal pure returns (uint256) { + unchecked { + uint256 s = (64 - (clz_(x ^ y) >> 2)) << 2; + return (x >> s) << s; + } + } + + /// @dev Returns the common prefix of `x` and `y` at the byte level. + function commonBytePrefix(uint256 x, uint256 y) internal pure returns (uint256) { + unchecked { + uint256 s = (32 - (clz_(x ^ y) >> 3)) << 3; + return (x >> s) << s; + } + } + + /// @dev hex"ABCD" -> hex"0A0B0C0D". + function toNibbles(bytes memory s) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let n := mload(s) + mstore(result, add(n, n)) // Store the new length. + s := add(s, 0x20) + let o := add(result, 0x20) + // forgefmt: disable-next-item + for { let i := 0 } lt(i, n) { i := add(i, 0x10) } { + let x := shr(128, mload(add(s, i))) + x := and(0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff, or(shl(64, x), x)) + x := and(0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff, or(shl(32, x), x)) + x := and(0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff, or(shl(16, x), x)) + x := and(0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff, or(shl(8, x), x)) + mstore(add(o, add(i, i)), + and(0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f, or(shl(4, x), x))) + } + mstore(add(o, add(s, s)), 0) // Zeroize slot after result. + mstore(0x40, add(0x40, add(o, add(s, s)))) // Allocate memory. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* BOOLEAN OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // A Solidity bool on the stack or memory is represented as a 256-bit word. + // Non-zero values are true, zero is false. + // A clean bool is either 0 (false) or 1 (true) under the hood. + // Usually, if not always, the bool result of a regular Solidity expression, + // or the argument of a public/external function will be a clean bool. + // You can usually use the raw variants for more performance. + // If uncertain, test (best with exact compiler settings). + // Or use the non-raw variants (compiler can sometimes optimize out the double `iszero`s). + + /// @dev Returns `x & y`. Inputs must be clean. + function rawAnd(bool x, bool y) internal pure returns (bool z) { + /// @solidity memory-safe-assembly + assembly { + z := and(x, y) + } + } + + /// @dev Returns `x & y`. + function and(bool x, bool y) internal pure returns (bool z) { + /// @solidity memory-safe-assembly + assembly { + z := and(iszero(iszero(x)), iszero(iszero(y))) + } + } + + /// @dev Returns `w & x & y`. + function and(bool w, bool x, bool y) internal pure returns (bool z) { + /// @solidity memory-safe-assembly + assembly { + z := iszero(or(iszero(w), or(iszero(x), iszero(y)))) + } + } + + /// @dev Returns `v & w & x & y`. + function and(bool v, bool w, bool x, bool y) internal pure returns (bool z) { + /// @solidity memory-safe-assembly + assembly { + z := iszero(or(or(iszero(v), iszero(w)), or(iszero(x), iszero(y)))) + } + } + + /// @dev Returns `x | y`. Inputs must be clean. + function rawOr(bool x, bool y) internal pure returns (bool z) { + /// @solidity memory-safe-assembly + assembly { + z := or(x, y) + } + } + + /// @dev Returns `x | y`. + function or(bool x, bool y) internal pure returns (bool z) { + /// @solidity memory-safe-assembly + assembly { + z := iszero(iszero(or(x, y))) + } + } + + /// @dev Returns `w | x | y`. + function or(bool w, bool x, bool y) internal pure returns (bool z) { + /// @solidity memory-safe-assembly + assembly { + z := iszero(iszero(or(w, or(x, y)))) + } + } + + /// @dev Returns `v | w | x | y`. + function or(bool v, bool w, bool x, bool y) internal pure returns (bool z) { + /// @solidity memory-safe-assembly + assembly { + z := iszero(iszero(or(v, or(w, or(x, y))))) + } + } + + /// @dev Returns 1 if `b` is true, else 0. Input must be clean. + function rawToUint(bool b) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := b + } + } + + /// @dev Returns 1 if `b` is true, else 0. + function toUint(bool b) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := iszero(iszero(b)) + } + } +} diff --git a/src/utils/clz/LibZip.sol b/src/utils/clz/LibZip.sol new file mode 100644 index 000000000..2c57e0797 --- /dev/null +++ b/src/utils/clz/LibZip.sol @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.30; + +/// @notice Library for compressing and decompressing bytes. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibZip.sol) +/// @author Calldata compression by clabby (https://github.com/clabby/op-kompressor) +/// @author FastLZ by ariya (https://github.com/ariya/FastLZ) +/// +/// @dev Note: +/// The accompanying solady.js library includes implementations of +/// FastLZ and calldata operations for convenience. +library LibZip { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* FAST LZ OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // LZ77 implementation based on FastLZ. + // Equivalent to level 1 compression and decompression at the following commit: + // https://github.com/ariya/FastLZ/commit/344eb4025f9ae866ebf7a2ec48850f7113a97a42 + // Decompression is backwards compatible. + + /// @dev Returns the compressed `data`. + function flzCompress(bytes memory data) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + function ms8(d_, v_) -> _d { + mstore8(d_, v_) + _d := add(d_, 1) + } + function u24(p_) -> _u { + _u := mload(p_) + _u := or(shl(16, byte(2, _u)), or(shl(8, byte(1, _u)), byte(0, _u))) + } + function cmp(p_, q_, e_) -> _l { + for { e_ := sub(e_, q_) } lt(_l, e_) { _l := add(_l, 1) } { + e_ := mul(iszero(byte(0, xor(mload(add(p_, _l)), mload(add(q_, _l))))), e_) + } + } + function literals(runs_, src_, dest_) -> _o { + for { _o := dest_ } iszero(lt(runs_, 0x20)) { runs_ := sub(runs_, 0x20) } { + mstore(ms8(_o, 31), mload(src_)) + _o := add(_o, 0x21) + src_ := add(src_, 0x20) + } + if iszero(runs_) { leave } + mstore(ms8(_o, sub(runs_, 1)), mload(src_)) + _o := add(1, add(_o, runs_)) + } + function mt(l_, d_, o_) -> _o { + for { d_ := sub(d_, 1) } iszero(lt(l_, 263)) { l_ := sub(l_, 262) } { + o_ := ms8(ms8(ms8(o_, add(224, shr(8, d_))), 253), and(0xff, d_)) + } + if iszero(lt(l_, 7)) { + _o := ms8(ms8(ms8(o_, add(224, shr(8, d_))), sub(l_, 7)), and(0xff, d_)) + leave + } + _o := ms8(ms8(o_, add(shl(5, l_), shr(8, d_))), and(0xff, d_)) + } + function setHash(i_, v_) { + let p_ := add(mload(0x40), shl(2, i_)) + mstore(p_, xor(mload(p_), shl(224, xor(shr(224, mload(p_)), v_)))) + } + function getHash(i_) -> _h { + _h := shr(224, mload(add(mload(0x40), shl(2, i_)))) + } + function hash(v_) -> _r { + _r := and(shr(19, mul(2654435769, v_)), 0x1fff) + } + function setNextHash(ip_, ipStart_) -> _ip { + setHash(hash(u24(ip_)), sub(ip_, ipStart_)) + _ip := add(ip_, 1) + } + result := mload(0x40) + calldatacopy(result, calldatasize(), 0x8000) // Zeroize the hashmap. + let op := add(result, 0x8000) + let a := add(data, 0x20) + let ipStart := a + let ipLimit := sub(add(ipStart, mload(data)), 13) + for { let ip := add(2, a) } lt(ip, ipLimit) {} { + let r := 0 + let d := 0 + for {} 1 {} { + let s := u24(ip) + let h := hash(s) + r := add(ipStart, getHash(h)) + setHash(h, sub(ip, ipStart)) + d := sub(ip, r) + if iszero(lt(ip, ipLimit)) { break } + ip := add(ip, 1) + if iszero(gt(d, 0x1fff)) { if eq(s, u24(r)) { break } } + } + if iszero(lt(ip, ipLimit)) { break } + ip := sub(ip, 1) + if gt(ip, a) { op := literals(sub(ip, a), a, op) } + let l := cmp(add(r, 3), add(ip, 3), add(ipLimit, 9)) + op := mt(l, d, op) + ip := setNextHash(setNextHash(add(ip, l), ipStart), ipStart) + a := ip + } + // Copy the result to compact the memory, overwriting the hashmap. + let end := sub(literals(sub(add(ipStart, mload(data)), a), a, op), 0x7fe0) + let o := add(result, 0x20) + mstore(result, sub(end, o)) // Store the length. + for {} iszero(gt(o, end)) { o := add(o, 0x20) } { mstore(o, mload(add(o, 0x7fe0))) } + mstore(end, 0) // Zeroize the slot after the string. + mstore(0x40, add(end, 0x20)) // Allocate the memory. + } + } + + /// @dev Returns the decompressed `data`. + function flzDecompress(bytes memory data) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let op := add(result, 0x20) + let end := add(add(data, 0x20), mload(data)) + for { data := add(data, 0x20) } lt(data, end) {} { + let w := mload(data) + let c := byte(0, w) + let t := shr(5, c) + if iszero(t) { + mstore(op, mload(add(data, 1))) + data := add(data, add(2, c)) + op := add(op, add(1, c)) + continue + } + for { + let g := eq(t, 7) + let l := add(2, xor(t, mul(g, xor(t, add(7, byte(1, w)))))) // M + let s := add(add(shl(8, and(0x1f, c)), byte(add(1, g), w)), 1) // R + let r := sub(op, s) + let f := xor(s, mul(gt(s, 0x20), xor(s, 0x20))) + let j := 0 + } 1 {} { + mstore(add(op, j), mload(add(r, j))) + j := add(j, f) + if lt(j, l) { continue } + data := add(data, add(2, g)) + op := add(op, l) + break + } + } + mstore(result, sub(op, add(result, 0x20))) // Store the length. + mstore(op, 0) // Zeroize the slot after the string. + mstore(0x40, add(op, 0x20)) // Allocate the memory. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CALLDATA OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Calldata compression and decompression using selective run length encoding: + // - Sequences of 0x00 (up to 128 consecutive). + // - Sequences of 0xff (up to 32 consecutive). + // + // A run length encoded block consists of two bytes: + // (0) 0x00 + // (1) A control byte with the following bit layout: + // - [7] `0: 0x00, 1: 0xff`. + // - [0..6] `runLength - 1`. + // + // The first 4 bytes are bitwise negated so that the compressed calldata + // can be dispatched into the `fallback` and `receive` functions. + + /// @dev Returns the compressed `data`. + function cdCompress(bytes memory data) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + function min(x_, y_) -> _z { + _z := xor(x_, mul(xor(x_, y_), lt(y_, x_))) + } + + result := mload(0x40) + let end := add(data, mload(data)) + let m := 0x7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f + let o := add(result, 0x20) + + for { let i := data } iszero(eq(i, end)) {} { + i := add(i, 1) + let c := byte(31, mload(i)) + + /// check data[i] == 00 or data[i] == ff + if iszero(c) { + for {} 1 {} { + let x := mload(add(i, 0x20)) + if iszero(x) { + let r := min(sub(end, i), 0x20) + r := min(sub(0x7f, c), r) + i := add(i, r) + c := add(c, r) + if iszero(gt(r, 0x1f)) { break } + continue + } + + let r := shr(3, clz(x)) + r := min(sub(end, i), r) + i := add(i, r) + c := add(c, r) + break + } + mstore(o, shl(240, c)) + o := add(o, 2) + continue + } + if eq(c, 0xff) { + let r := 0x20 + let x := not(mload(add(i, r))) + if x { r := shr(3, clz(x)) } + r := min(min(sub(end, i), r), 0x1f) + i := add(i, r) + mstore(o, shl(240, or(r, 0x80))) + o := add(o, 2) + continue + } + mstore8(o, c) + o := add(o, 1) + c := mload(add(i, 0x20)) + mstore(o, c) + // `.each(b => b == 0x00 || b == 0xff ? 0x80 : 0x00)`. + c := not(or(and(or(add(and(c, m), m), c), or(add(and(not(c), m), m), not(c))), m)) + let r := shl(7, lt(0x8421084210842108cc6318c6db6d54be, c)) // Save bytecode. + r := or(shl(6, lt(0xffffffffffffffff, shr(r, c))), r) + // forgefmt: disable-next-item + r := add(iszero(c), shr(3, xor(byte(and(0x1f, shr(byte(24, + mul(0x02040810204081, shr(r, c))), 0x8421084210842108cc6318c6db6d54be)), + 0xc0c8c8d0c8e8d0d8c8e8e0e8d0d8e0f0c8d0e8d0e0e0d8f0d0d0e0d8f8f8f8f8), r))) + r := min(sub(end, i), r) + o := add(o, r) + i := add(i, r) + } + // Bitwise negate the first 4 bytes. + mstore(add(result, 4), not(mload(add(result, 4)))) + mstore(result, sub(o, add(result, 0x20))) // Store the length. + mstore(o, 0) // Zeroize the slot after the string. + mstore(0x40, add(o, 0x20)) // Allocate the memory. + } + } + + /// @dev Returns the decompressed `data`. + function cdDecompress(bytes memory data) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + if mload(data) { + result := mload(0x40) + let s := add(data, 4) + let v := mload(s) + let end := add(add(0x20, data), mload(data)) + let m := 0x7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f + let o := add(result, 0x20) + mstore(s, not(v)) // Bitwise negate the first 4 bytes. + for { let i := add(0x20, data) } 1 {} { + let c := mload(i) + if iszero(byte(0, c)) { + c := add(1, byte(1, c)) + if iszero(gt(c, 0x80)) { + i := add(i, 2) + calldatacopy(o, calldatasize(), c) // Fill with 0x00. + o := add(o, c) + if iszero(lt(i, end)) { break } + continue + } + i := add(i, 2) + mstore(o, not(0)) // Fill with 0xff. + o := add(o, sub(c, 0x80)) + if iszero(lt(i, end)) { break } + continue + } + mstore(o, c) + c := not(or(or(add(and(c, m), m), c), m)) // `.each(b => b == 0x00 ? 0x80 : 0x00)`. + let r := shl(7, lt(0x8421084210842108cc6318c6db6d54be, c)) // Save bytecode. + r := or(shl(6, lt(0xffffffffffffffff, shr(r, c))), r) + // forgefmt: disable-next-item + c := add(iszero(c), shr(3, xor(byte(and(0x1f, shr(byte(24, + mul(0x02040810204081, shr(r, c))), 0x8421084210842108cc6318c6db6d54be)), + 0xc0c8c8d0c8e8d0d8c8e8e0e8d0d8e0f0c8d0e8d0e0e0d8f0d0d0e0d8f8f8f8f8), r))) + o := add(o, c) + i := add(i, c) + if lt(i, end) { continue } + if gt(i, end) { o := sub(o, sub(i, end)) } + break + } + mstore(s, v) // Restore the first 4 bytes. + mstore(result, sub(o, add(result, 0x20))) // Store the length. + mstore(o, 0) // Zeroize the slot after the string. + mstore(0x40, add(o, 0x20)) // Allocate the memory. + } + } + } + + /// @dev To be called in the `fallback` function. + /// ``` + /// fallback() external payable { LibZip.cdFallback(); } + /// receive() external payable {} // Silence compiler warning to add a `receive` function. + /// ``` + /// For efficiency, this function will directly return the results, terminating the context. + /// If called internally, it must be called at the end of the function. + function cdFallback() internal { + /// @solidity memory-safe-assembly + assembly { + if iszero(calldatasize()) { return(calldatasize(), calldatasize()) } + let o := 0 + let f := not(3) // For negating the first 4 bytes. + for { let i := 0 } lt(i, calldatasize()) {} { + let c := byte(0, xor(add(i, f), calldataload(i))) + i := add(i, 1) + if iszero(c) { + let d := byte(0, xor(add(i, f), calldataload(i))) + i := add(i, 1) + // Fill with either 0xff or 0x00. + mstore(o, not(0)) + if iszero(gt(d, 0x7f)) { calldatacopy(o, calldatasize(), add(d, 1)) } + o := add(o, add(and(d, 0x7f), 1)) + continue + } + mstore8(o, c) + o := add(o, 1) + } + let success := delegatecall(gas(), address(), 0x00, o, codesize(), 0x00) + returndatacopy(0x00, 0x00, returndatasize()) + if iszero(success) { revert(0x00, returndatasize()) } + return(0x00, returndatasize()) + } + } +} From 5d9496a5030f2fbe8501ecbf99f47ac96d810747 Mon Sep 17 00:00:00 2001 From: atarpara Date: Mon, 29 Dec 2025 14:47:15 +0530 Subject: [PATCH 2/5] T --- src/utils/clz/LibBit.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/clz/LibBit.sol b/src/utils/clz/LibBit.sol index 6841db21a..9167a12c3 100644 --- a/src/utils/clz/LibBit.sol +++ b/src/utils/clz/LibBit.sol @@ -187,8 +187,8 @@ library LibBit { mstore(add(o, add(i, i)), and(0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f, or(shl(4, x), x))) } - mstore(add(o, add(s, s)), 0) // Zeroize slot after result. - mstore(0x40, add(0x40, add(o, add(s, s)))) // Allocate memory. + mstore(add(o, mload(result)), 0) // Zeroize slot after result. + mstore(0x40, add(0x40, add(o, mload(result)))) // Allocate memory. } } From 67bb59b8831fd87bc09e76dcb9a24b693adbc427 Mon Sep 17 00:00:00 2001 From: atarpara Date: Mon, 29 Dec 2025 15:13:12 +0530 Subject: [PATCH 3/5] Added test --- test/clz/FixedPointMathLib.t.sol | 2433 ++++++++++++++++++++++++++++++ test/clz/LibBit.t.sol | 385 +++++ test/clz/LibZip.t.sol | 543 +++++++ 3 files changed, 3361 insertions(+) create mode 100644 test/clz/FixedPointMathLib.t.sol create mode 100644 test/clz/LibBit.t.sol create mode 100644 test/clz/LibZip.t.sol diff --git a/test/clz/FixedPointMathLib.t.sol b/test/clz/FixedPointMathLib.t.sol new file mode 100644 index 000000000..5eeca929a --- /dev/null +++ b/test/clz/FixedPointMathLib.t.sol @@ -0,0 +1,2433 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.30; + +import "../utils/SoladyTest.sol"; +import {FixedPointMathLib} from "../../src/utils/clz/FixedPointMathLib.sol"; + +contract FixedPointMathLibWithCLZTest is SoladyTest { + function testExpWad() public { + assertEq(FixedPointMathLib.expWad(-41446531673892822312), 1); + assertEq(FixedPointMathLib.expWad(-41446531673892822313), 0); + + assertEq(FixedPointMathLib.expWad(-3e18), 49787068367863942); + assertEq(FixedPointMathLib.expWad(-2e18), 135335283236612691); + assertEq(FixedPointMathLib.expWad(-1e18), 367879441171442321); + + assertEq(FixedPointMathLib.expWad(-0.5e18), 606530659712633423); + assertEq(FixedPointMathLib.expWad(-0.3e18), 740818220681717866); + + assertEq(FixedPointMathLib.expWad(0), 1000000000000000000); + + assertEq(FixedPointMathLib.expWad(0.3e18), 1349858807576003103); + assertEq(FixedPointMathLib.expWad(0.5e18), 1648721270700128146); + + assertEq(FixedPointMathLib.expWad(1e18), 2718281828459045235); + assertEq(FixedPointMathLib.expWad(2e18), 7389056098930650227); + assertEq(FixedPointMathLib.expWad(3e18), 20085536923187667741); + // True value: 20085536923187667740.92 + + assertEq(FixedPointMathLib.expWad(10e18), 220264657948067165169_80); + // True value: 22026465794806716516957.90 + // Relative error 9.987984547746668e-22 + + assertEq(FixedPointMathLib.expWad(50e18), 5184705528587072464_148529318587763226117); + // True value: 5184705528587072464_087453322933485384827.47 + // Relative error: 1.1780031733243328e-20 + + assertEq( + FixedPointMathLib.expWad(100e18), + 268811714181613544841_34666106240937146178367581647816351662017 + ); + // True value: 268811714181613544841_26255515800135873611118773741922415191608 + // Relative error: 3.128803544297531e-22 + + assertEq( + FixedPointMathLib.expWad(135305999368893231588), + 578960446186580976_50144101621524338577433870140581303254786265309376407432913 + ); + // True value: 578960446186580976_49816762928942336782129491980154662247847962410455084893091 + // Relative error: 5.653904247484822e-21 + } + + // Notes on lambertW0Wad: + // + // If you want to attempt finding a better approximation, look at + // https://github.com/recmo/experiment-solexp/blob/main/approximate_mpmath.ipynb + // I somehow can't get it to reproduce the approximation constants for `lnWad`. + // Let me know if you can get the code to reproduce the approximation constants for `lnWad`. + + event TestingLambertW0WadMonotonicallyIncreasing( + int256 a, int256 b, int256 w0a, int256 w0b, bool success, uint256 gasUsed + ); + + int256 internal constant _ONE_DIV_EXP = 367879441171442321; + int256 internal constant _LAMBERT_W0_MIN = -367879441171442321; + int256 internal constant _EXP = 2718281828459045235; + int256 internal constant _WAD = 10 ** 18; + + function testLambertW0WadKnownValues() public { + _checkLambertW0Wad(0, 0); + _checkLambertW0Wad(1, 1); + _checkLambertW0Wad(2, 2); + _checkLambertW0Wad(3, 2); + _checkLambertW0Wad(131071, 131070); + _checkLambertW0Wad(17179869183, 17179868887); + _checkLambertW0Wad(1000000000000000000, 567143290409783872); + _checkLambertW0Wad(-3678794411715, -3678807945318); + _checkLambertW0Wad(_LAMBERT_W0_MIN, -999999999741585709); + // These are exact values. + _checkLambertW0Wad(2 ** 255 - 1, 130435123404408416612); + _checkLambertW0Wad(2 ** 254 - 1, 129747263755102316133); + _checkLambertW0Wad(2 ** 253 - 1, 129059431996357330139); + _checkLambertW0Wad(2 ** 252 - 1, 128371628422812486425); + _checkLambertW0Wad(2 ** 251 - 1, 127683853333788079721); + _checkLambertW0Wad(2 ** 250 - 1, 126996107033385166927); + _checkLambertW0Wad(2 ** 249 - 1, 126308389830587715420); + _checkLambertW0Wad(2 ** 248 - 1, 125620702039367489656); + _checkLambertW0Wad(2 ** 247 - 1, 124933043978791764502); + _checkLambertW0Wad(2 ** 246 - 1, 124245415973133957088); + _checkLambertW0Wad(2 ** 245 - 1, 123557818351987272451); + _checkLambertW0Wad(2 ** 244 - 1, 122870251450381461880); + _checkLambertW0Wad(2 ** 243 - 1, 122182715608902796703); + _checkLambertW0Wad(2 ** 242 - 1, 121495211173817364188); + _checkLambertW0Wad(2 ** 241 - 1, 120807738497197796422); + _checkLambertW0Wad(2 ** 240 - 1, 120120297937053547320); + _checkLambertW0Wad(2 ** 239 - 1, 119432889857464837488); + _checkLambertW0Wad(2 ** 238 - 1, 118745514628720391363); + _checkLambertW0Wad(2 ** 237 - 1, 118058172627459096009); + _checkLambertW0Wad(2 ** 236 - 1, 117370864236815716134); + _checkLambertW0Wad(2 ** 235 - 1, 116683589846570805279); + _checkLambertW0Wad(2 ** 234 - 1, 115996349853304958814); + _checkLambertW0Wad(2 ** 233 - 1, 115309144660557560280); + _checkLambertW0Wad(2 ** 232 - 1, 114621974678990178815); + _checkLambertW0Wad(2 ** 231 - 1, 113934840326554781918); + _checkLambertW0Wad(2 ** 230 - 1, 113247742028666934564); + _checkLambertW0Wad(2 ** 229 - 1, 112560680218384162820); + _checkLambertW0Wad(2 ** 228 - 1, 111873655336589667598); + _checkLambertW0Wad(2 ** 227 - 1, 111186667832181581935); + _checkLambertW0Wad(2 ** 226 - 1, 110499718162267973459); + _checkLambertW0Wad(2 ** 225 - 1, 109812806792367802251); + _checkLambertW0Wad(2 ** 224 - 1, 109125934196618053331); + _checkLambertW0Wad(2 ** 223 - 1, 108439100857987272488); + _checkLambertW0Wad(2 ** 222 - 1, 107752307268495744067); + _checkLambertW0Wad(2 ** 221 - 1, 107065553929442559763); + _checkLambertW0Wad(2 ** 220 - 1, 106378841351639838444); + _checkLambertW0Wad(2 ** 219 - 1, 105692170055654368478); + _checkLambertW0Wad(2 ** 218 - 1, 105005540572056956171); + _checkLambertW0Wad(2 ** 217 - 1, 104318953441679776592); + _checkLambertW0Wad(2 ** 216 - 1, 103632409215882036434); + _checkLambertW0Wad(2 ** 215 - 1, 102945908456824272609); + _checkLambertW0Wad(2 ** 214 - 1, 102259451737751625038); + _checkLambertW0Wad(2 ** 213 - 1, 101573039643286437675); + _checkLambertW0Wad(2 ** 212 - 1, 100886672769730558166); + _checkLambertW0Wad(2 ** 211 - 1, 100200351725377723788); + _checkLambertW0Wad(2 ** 210 - 1, 99514077130836439501); + _checkLambertW0Wad(2 ** 209 - 1, 98827849619363773067); + _checkLambertW0Wad(2 ** 208 - 1, 98141669837210512407); + _checkLambertW0Wad(2 ** 207 - 1, 97455538443978151616); + _checkLambertW0Wad(2 ** 206 - 1, 96769456112988194563); + _checkLambertW0Wad(2 ** 205 - 1, 96083423531664288650); + _checkLambertW0Wad(2 ** 204 - 1, 95397441401927726359); + _checkLambertW0Wad(2 ** 203 - 1, 94711510440606878644); + _checkLambertW0Wad(2 ** 202 - 1, 94025631379861152095); + _checkLambertW0Wad(2 ** 201 - 1, 93339804967620091367); + _checkLambertW0Wad(2 ** 200 - 1, 92654031968038279517); + _checkLambertW0Wad(2 ** 199 - 1, 91968313161966721893); + _checkLambertW0Wad(2 ** 198 - 1, 91282649347441434152); + _checkLambertW0Wad(2 ** 197 - 1, 90597041340189991908); + _checkLambertW0Wad(2 ** 196 - 1, 89911489974156838659); + _checkLambertW0Wad(2 ** 195 - 1, 89225996102048190100); + _checkLambertW0Wad(2 ** 194 - 1, 88540560595897416858); + _checkLambertW0Wad(2 ** 193 - 1, 87855184347651834275); + _checkLambertW0Wad(2 ** 192 - 1, 87169868269781877263); + _checkLambertW0Wad(2 ** 191 - 1, 86484613295913690725); + _checkLambertW0Wad(2 ** 190 - 1, 85799420381486221653); + _checkLambertW0Wad(2 ** 189 - 1, 85114290504433958190); + _checkLambertW0Wad(2 ** 188 - 1, 84429224665896523735); + _checkLambertW0Wad(2 ** 187 - 1, 83744223890956400983); + _checkLambertW0Wad(2 ** 186 - 1, 83059289229406131801); + _checkLambertW0Wad(2 ** 185 - 1, 82374421756546414467); + _checkLambertW0Wad(2 ** 184 - 1, 81689622574016600237); + _checkLambertW0Wad(2 ** 183 - 1, 81004892810659176931); + _checkLambertW0Wad(2 ** 182 - 1, 80320233623419918558); + _checkLambertW0Wad(2 ** 181 - 1, 79635646198285477393); + _checkLambertW0Wad(2 ** 180 - 1, 78951131751260298782); + _checkLambertW0Wad(2 ** 179 - 1, 78266691529384849812); + _checkLambertW0Wad(2 ** 178 - 1, 77582326811797271395); + _checkLambertW0Wad(2 ** 177 - 1, 76898038910840689756); + _checkLambertW0Wad(2 ** 176 - 1, 76213829173218558571); + _checkLambertW0Wad(2 ** 175 - 1, 75529698981200547567); + _checkLambertW0Wad(2 ** 174 - 1, 74845649753881648207); + _checkLambertW0Wad(2 ** 173 - 1, 74161682948497332759); + _checkLambertW0Wad(2 ** 172 - 1, 73477800061797780656); + _checkLambertW0Wad(2 ** 171 - 1, 72794002631484376331); + _checkLambertW0Wad(2 ** 170 - 1, 72110292237711886966); + _checkLambertW0Wad(2 ** 169 - 1, 71426670504659947705); + _checkLambertW0Wad(2 ** 168 - 1, 70743139102177717275); + _checkLambertW0Wad(2 ** 167 - 1, 70059699747505819935); + _checkLambertW0Wad(2 ** 166 - 1, 69376354207079961679); + _checkLambertW0Wad(2 ** 165 - 1, 68693104298420901379); + _checkLambertW0Wad(2 ** 164 - 1, 68009951892115772747); + _checkLambertW0Wad(2 ** 163 - 1, 67326898913896092682); + _checkLambertW0Wad(2 ** 162 - 1, 66643947346818157796); + _checkLambertW0Wad(2 ** 161 - 1, 65961099233551926143); + _checkLambertW0Wad(2 ** 160 - 1, 65278356678784907905); + _checkLambertW0Wad(2 ** 159 - 1, 64595721851748049983); + _checkLambertW0Wad(2 ** 158 - 1, 63913196988871098107); + _checkLambertW0Wad(2 ** 157 - 1, 63230784396575459844); + _checkLambertW0Wad(2 ** 156 - 1, 62548486454213176429); + _checkLambertW0Wad(2 ** 155 - 1, 61866305617161244980); + _checkLambertW0Wad(2 ** 154 - 1, 61184244420081220067); + _checkLambertW0Wad(2 ** 153 - 1, 60502305480354769865); + _checkLambertW0Wad(2 ** 152 - 1, 59820491501706673077); + _checkLambertW0Wad(2 ** 151 - 1, 59138805278027624755); + _checkLambertW0Wad(2 ** 150 - 1, 58457249697410179101); + _checkLambertW0Wad(2 ** 149 - 1, 57775827746412203235); + _checkLambertW0Wad(2 ** 148 - 1, 57094542514563356374); + _checkLambertW0Wad(2 ** 147 - 1, 56413397199131353678); + _checkLambertW0Wad(2 ** 146 - 1, 55732395110166133991); + _checkLambertW0Wad(2 ** 145 - 1, 55051539675841537897); + _checkLambertW0Wad(2 ** 144 - 1, 54370834448115730535); + _checkLambertW0Wad(2 ** 143 - 1, 53690283108733387465); + _checkLambertW0Wad(2 ** 142 - 1, 53009889475594618649); + _checkLambertW0Wad(2 ** 141 - 1, 52329657509517754228); + _checkLambertW0Wad(2 ** 140 - 1, 51649591321425477661); + _checkLambertW0Wad(2 ** 139 - 1, 50969695179986390948); + _checkLambertW0Wad(2 ** 138 - 1, 50289973519746960243); + _checkLambertW0Wad(2 ** 137 - 1, 49610430949791948630); + _checkLambertW0Wad(2 ** 136 - 1, 48931072262974930811); + _checkLambertW0Wad(2 ** 135 - 1, 48251902445764340905); + _checkLambertW0Wad(2 ** 134 - 1, 47572926688754773801); + _checkLambertW0Wad(2 ** 133 - 1, 46894150397897992742); + _checkLambertW0Wad(2 ** 132 - 1, 46215579206513348095); + _checkLambertW0Wad(2 ** 131 - 1, 45537218988143149666); + _checkLambertW0Wad(2 ** 130 - 1, 44859075870325031417); + _checkLambertW0Wad(2 ** 129 - 1, 44181156249360587882); + _checkLambertW0Wad(2 ** 128 - 1, 43503466806167642613); + _checkLambertW0Wad(2 ** 127 - 1, 42826014523312541917); + _checkLambertW0Wad(2 ** 126 - 1, 42148806703328979292); + _checkLambertW0Wad(2 ** 125 - 1, 41471850988441194251); + _checkLambertW0Wad(2 ** 124 - 1, 40795155381822122767); + _checkLambertW0Wad(2 ** 123 - 1, 40118728270531400808); + _checkLambertW0Wad(2 ** 122 - 1, 39442578450294263667); + _checkLambertW0Wad(2 ** 121 - 1, 38766715152300604375); + _checkLambertW0Wad(2 ** 120 - 1, 38091148072224059569); + _checkLambertW0Wad(2 ** 119 - 1, 37415887401684336100); + _checkLambertW0Wad(2 ** 118 - 1, 36740943862402491609); + _checkLambertW0Wad(2 ** 117 - 1, 36066328743329022902); + _checkLambertW0Wad(2 ** 116 - 1, 35392053941058967434); + _checkLambertW0Wad(2 ** 115 - 1, 34718132003887455986); + _checkLambertW0Wad(2 ** 114 - 1, 34044576179904059477); + _checkLambertW0Wad(2 ** 113 - 1, 33371400469575784902); + _checkLambertW0Wad(2 ** 112 - 1, 32698619683327803297); + _checkLambertW0Wad(2 ** 111 - 1, 32026249504699254799); + _checkLambertW0Wad(2 ** 110 - 1, 31354306559730344521); + _checkLambertW0Wad(2 ** 109 - 1, 30682808493328298780); + _checkLambertW0Wad(2 ** 108 - 1, 30011774053465850808); + _checkLambertW0Wad(2 ** 107 - 1, 29341223184189485097); + _checkLambertW0Wad(2 ** 106 - 1, 28671177128558970924); + _checkLambertW0Wad(2 ** 105 - 1, 28001658542808735364); + _checkLambertW0Wad(2 ** 104 - 1, 27332691623220201135); + _checkLambertW0Wad(2 ** 103 - 1, 26664302247428250682); + _checkLambertW0Wad(2 ** 102 - 1, 25996518132161712657); + _checkLambertW0Wad(2 ** 101 - 1, 25329369009746106264); + _checkLambertW0Wad(2 ** 100 - 1, 24662886826087826761); + _checkLambertW0Wad(2 ** 99 - 1, 23997105963326166352); + _checkLambertW0Wad(2 ** 98 - 1, 23332063490900058530); + _checkLambertW0Wad(2 ** 97 - 1, 22667799449451523321); + _checkLambertW0Wad(2 ** 96 - 1, 22004357172804292983); + _checkLambertW0Wad(2 ** 95 - 1, 21341783654247925671); + _checkLambertW0Wad(2 ** 94 - 1, 20680129964567978803); + _checkLambertW0Wad(2 ** 93 - 1, 20019451730746615034); + _checkLambertW0Wad(2 ** 92 - 1, 19359809686086176343); + _checkLambertW0Wad(2 ** 91 - 1, 18701270304772358157); + _checkLambertW0Wad(2 ** 90 - 1, 18043906536712772323); + _checkLambertW0Wad(2 ** 89 - 1, 17387798662016868795); + _checkLambertW0Wad(2 ** 88 - 1, 16733035288929945451); + _checkLambertW0Wad(2 ** 87 - 1, 16079714524670107222 + 1); + _checkLambertW0Wad(2 ** 86 - 1, 15427945355807184379); + _checkLambertW0Wad(2 ** 85 - 1, 14777849284057868231); + _checkLambertW0Wad(2 ** 84 - 1, 14129562275318189632); + _checkLambertW0Wad(2 ** 83 - 1, 13483237095324880705); + _checkLambertW0Wad(2 ** 82 - 1, 12839046125789215063); + _checkLambertW0Wad(2 ** 81 - 1, 12197184781931118579); + _checkLambertW0Wad(2 ** 80 - 1, 11557875688514566228 - 1); + _checkLambertW0Wad(2 ** 79 - 1, 10921373820226202580); + _checkLambertW0Wad(2 ** 78 - 1, 10287972878516218499); + _checkLambertW0Wad(2 ** 77 - 1, 9658013267990184319); + _checkLambertW0Wad(2 ** 76 - 1, 9031892161491509531); + _checkLambertW0Wad(2 ** 75 - 1, 8410076319328428686); + _checkLambertW0Wad(2 ** 74 - 1, 7793118576966979948); + _checkLambertW0Wad(2 ** 73 - 1, 7181679269695846234); + _checkLambertW0Wad(2 ** 72 - 1, 6576554370186862926); + _checkLambertW0Wad(2 ** 71 - 1, 5978712844468804878 - 1); + _checkLambertW0Wad(2 ** 70 - 1, 5389346779005776683); + _checkLambertW0Wad(2 ** 69 - 1, 4809939316762921936); + _checkLambertW0Wad(2 ** 68 - 1, 4242357480017482271); + _checkLambertW0Wad(2 ** 67 - 1, 3688979548845126287); + _checkLambertW0Wad(2 ** 66 - 1, 3152869312105232629); + _checkLambertW0Wad(2 ** 65 - 1, 2638010157689274059); + _checkLambertW0Wad(2 ** 64 - 1, 2149604165721149566); + _checkLambertW0Wad(2 ** 63 - 1, 1694407549795038335); + _checkLambertW0Wad(2 ** 62 - 1, 1280973323147500590); + _checkLambertW0Wad(2 ** 61 - 1, 919438481612859603); + _checkLambertW0Wad(2 ** 60 - 1, 620128202996354327); + _checkLambertW0Wad(2 ** 59 - 1, 390213425026895126); + _checkLambertW0Wad(2 ** 58 - 1, 229193491169149614); + _checkLambertW0Wad(2 ** 57 - 1, 126935310044982397); + _checkLambertW0Wad(2 ** 56 - 1, 67363429834711483); + _checkLambertW0Wad(2 ** 55 - 1, 34796675828817814); + _checkLambertW0Wad(2 ** 54 - 1, 17698377658513340); + _checkLambertW0Wad(2 ** 53 - 1, 8927148493627578); + _checkLambertW0Wad(2 ** 52 - 1, 4483453146102402); + _checkLambertW0Wad(2 ** 51 - 1, 2246746269994097); + _checkLambertW0Wad(2 ** 50 - 1, 1124634392838166); + _checkLambertW0Wad(2 ** 49 - 1, 562633308112667); + _checkLambertW0Wad(2 ** 48 - 1, 281395781982528); + _checkLambertW0Wad(2 ** 47 - 1, 140717685495042); + _checkLambertW0Wad(2 ** 46 - 1, 70363792940114); + _checkLambertW0Wad(2 ** 45 - 1, 35183134214121); + _checkLambertW0Wad(2 ** 44 - 1, 17591876567571); + _checkLambertW0Wad(2 ** 43 - 1, 8796015651975); + _checkLambertW0Wad(2 ** 42 - 1, 4398027168417); + _checkLambertW0Wad(2 ** 41 - 1, 2199018419863); + _checkLambertW0Wad(2 ** 40 - 1, 1099510418851); + _checkLambertW0Wad(2 ** 39 - 1, 549755511655); + _checkLambertW0Wad(2 ** 38 - 1, 274877831385); + _checkLambertW0Wad(2 ** 37 - 1, 137438934581); + _checkLambertW0Wad(2 ** 36 - 1, 68719472012); + _checkLambertW0Wad(2 ** 35 - 1, 34359737186); + _checkLambertW0Wad(2 ** 34 - 1, 17179868887); + _checkLambertW0Wad(2 ** 33 - 1, 8589934517); + _checkLambertW0Wad(2 ** 32 - 1, 4294967276); + _checkLambertW0Wad(2 ** 31 - 1, 2147483642); + _checkLambertW0Wad(2 ** 30 - 1, 1073741821); + _checkLambertW0Wad(2 ** 29 - 1, 536870910); + _checkLambertW0Wad(2 ** 28 - 1, 268435454); + _checkLambertW0Wad(2 ** 27 - 1, 134217726); + _checkLambertW0Wad(2 ** 26 - 1, 67108862); + _checkLambertW0Wad(2 ** 25 - 1, 33554430); + _checkLambertW0Wad(2 ** 24 - 1, 16777214); + _checkLambertW0Wad(2 ** 23 - 1, 8388606); + _checkLambertW0Wad(2 ** 22 - 1, 4194302); + _checkLambertW0Wad(2 ** 21 - 1, 2097150); + _checkLambertW0Wad(2 ** 20 - 1, 1048574); + _checkLambertW0Wad(2 ** 19 - 1, 524286); + _checkLambertW0Wad(2 ** 18 - 1, 262142); + _checkLambertW0Wad(2 ** 17 - 1, 131070); + _checkLambertW0Wad(2 ** 16 - 1, 65534); + _checkLambertW0Wad(2 ** 15 - 1, 32766); + _checkLambertW0Wad(2 ** 14 - 1, 16382); + _checkLambertW0Wad(2 ** 13 - 1, 8190); + _checkLambertW0Wad(2 ** 12 - 1, 4094); + _checkLambertW0Wad(2 ** 11 - 1, 2046); + _checkLambertW0Wad(2 ** 10 - 1, 1022); + _checkLambertW0Wad(2 ** 9 - 1, 510); + _checkLambertW0Wad(2 ** 8 - 1, 254); + } + + function testLambertW0WadRevertsForOutOfDomain() public { + FixedPointMathLib.lambertW0Wad(_LAMBERT_W0_MIN); + for (int256 i = 0; i <= 10; ++i) { + vm.expectRevert(FixedPointMathLib.OutOfDomain.selector); + this.lambertW0Wad(_LAMBERT_W0_MIN - 1 - i); + } + vm.expectRevert(FixedPointMathLib.OutOfDomain.selector); + this.lambertW0Wad(-type(int256).max); + } + + function lambertW0Wad(int256 x) public pure returns (int256) { + return FixedPointMathLib.lambertW0Wad(x); + } + + function _checkLambertW0Wad(int256 x, int256 expected) internal { + unchecked { + uint256 gasBefore = gasleft(); + int256 w = FixedPointMathLib.lambertW0Wad(x); + uint256 gasUsed = gasBefore - gasleft(); + emit LogInt("x", x); + emit LogUint("gasUsed", gasUsed); + assertEq(w, expected); + } + } + + function testLambertW0WadAccuracy() public { + testLambertW0WadAccuracy(uint184(int184(_testLamberW0WadAccuracyThres()))); + testLambertW0WadAccuracy(2 ** 184 - 1); + } + + function testLambertW0WadAccuracy(uint184 a) public { + int256 x = int256(int184(a)); + if (x >= _testLamberW0WadAccuracyThres()) { + int256 l = FixedPointMathLib.lnWad(x); + int256 r = x * l / _WAD; + int256 w = FixedPointMathLib.lambertW0Wad(r); + assertLt(FixedPointMathLib.abs(l - w), 0xff); + } + } + + function _testLamberW0WadAccuracyThres() internal pure returns (int256) { + unchecked { + return _ONE_DIV_EXP + _ONE_DIV_EXP * 0.01 ether / 1 ether; + } + } + + function testLambertW0WadWithinBounds(int256 x) public { + if (x <= 0) x = _boundLambertW0WadInput(x); + int256 w = FixedPointMathLib.lambertW0Wad(x); + assertTrue(w <= x); + unchecked { + if (x > _EXP) { + int256 l = FixedPointMathLib.lnWad(x); + assertGt(l, 0); + int256 ll = FixedPointMathLib.lnWad(l); + int256 q = ll * _WAD; + int256 lower = l - ll + q / (2 * l); + if (x > _EXP + 4) { + assertLt(lower, w + 1); + } else { + assertLt(lower, w + 2); + } + int256 upper = l - ll + (q * _EXP) / (l * (_EXP - _WAD)) + 1; + assertLt(w, upper); + } + } + } + + function testLambertW0WadWithinBounds() public { + unchecked { + for (int256 i = -10; i != 20; ++i) { + testLambertW0WadWithinBounds(_EXP + i); + } + testLambertW0WadWithinBounds(type(int256).max); + } + } + + function testLambertW0WadMonotonicallyIncreasing() public { + unchecked { + for (uint256 i; i <= 256; ++i) { + uint256 x = 1 << i; + testLambertW0WadMonotonicallyIncreasingAround(int256(x)); + testLambertW0WadMonotonicallyIncreasingAround(int256(x - 1)); + } + for (uint256 i; i <= 57; ++i) { + uint256 x = 1 << i; + testLambertW0WadMonotonicallyIncreasingAround(-int256(x)); + testLambertW0WadMonotonicallyIncreasingAround(-int256(x - 1)); + } + } + } + + function testLambertW0WadMonotonicallyIncreasing2() public { + // These are some problematic values gathered over the attempts. + // Some might not be problematic now. + _testLambertW0WadMonoAround(0x598cdf77327d789dc); + _testLambertW0WadMonoAround(0x3c8d97dfe4afb1b05); + _testLambertW0WadMonoAround(0x56a147b480c03cc22); + _testLambertW0WadMonoAround(0x3136f439c231d0bb9); + _testLambertW0WadMonoAround(0x2ae7cff17ef2469a1); + _testLambertW0WadMonoAround(0x1de668fd7afcf61cc); + _testLambertW0WadMonoAround(0x15024b2a35f2cdd95); + _testLambertW0WadMonoAround(0x11a65ae94b59590f9); + _testLambertW0WadMonoAround(0xf0c2c82174dffb7e); + _testLambertW0WadMonoAround(0xed3e56938cb11626); + _testLambertW0WadMonoAround(0xecf5c4e511142439); + _testLambertW0WadMonoAround(0xc0755fa2b4033cb0); + _testLambertW0WadMonoAround(0xa235db282ea4edc6); + _testLambertW0WadMonoAround(0x9ff2ec5c26eec112); + _testLambertW0WadMonoAround(0xa0c3c4e36f4415f1); + _testLambertW0WadMonoAround(0x9b9f0e8d61287782); + _testLambertW0WadMonoAround(0x7df719d1a4a7b8ad); + _testLambertW0WadMonoAround(0x7c881679a1464d25); + _testLambertW0WadMonoAround(0x7bec47487071495a); + _testLambertW0WadMonoAround(0x7be31c75fc717f9f); + _testLambertW0WadMonoAround(0x7bbb4e0716eeca53); + _testLambertW0WadMonoAround(0x78e59d40a92b443b); + _testLambertW0WadMonoAround(0x77658c4ad3af717d); + _testLambertW0WadMonoAround(0x75ae9afa425919fe); + _testLambertW0WadMonoAround(0x7526092d05bef41f); + _testLambertW0WadMonoAround(0x52896fe82be03dfe); + _testLambertW0WadMonoAround(0x4f05b0ddf3b71a19); + _testLambertW0WadMonoAround(0x3094b0feb93943fd); + _testLambertW0WadMonoAround(0x2ef215ae6701c40e); + _testLambertW0WadMonoAround(0x2ebd1c82095d6a92); + _testLambertW0WadMonoAround(0x2e520a4e670d52bb); + _testLambertW0WadMonoAround(0xfc2f004412e5ce69); + _testLambertW0WadMonoAround(0x158bc0b201103a7fc); + _testLambertW0WadMonoAround(0x39280df60945c436b); + _testLambertW0WadMonoAround(0x47256e5d374b35f74); + _testLambertW0WadMonoAround(0x2b9568ffb08c155a4); + _testLambertW0WadMonoAround(0x1b60b07806956f34d); + _testLambertW0WadMonoAround(0x21902755d1eee824c); + _testLambertW0WadMonoAround(0x6e15c8a6ee6e4fca4); + _testLambertW0WadMonoAround(0x5b13067d92d8e49c6); + _testLambertW0WadMonoAround(0x2826ebc1fce90cf6e); + _testLambertW0WadMonoAround(0x215eb5aa1041510a4); + _testLambertW0WadMonoAround(0x47b20347b57504c32); + _testLambertW0WadMonoAround(0x75e8fd53f8c90f95a); + _testLambertW0WadMonoAround(0x43e8d80f9af282627); + _testLambertW0WadMonoAround(0x3cf555b5fd4f20615); + _testLambertW0WadMonoAround(0xaff4b8b52f8355e6e); + _testLambertW0WadMonoAround(0x529e89e77ae046255); + _testLambertW0WadMonoAround(0x1f0289433f07cbf53b); + _testLambertW0WadMonoAround(0xc1f6e56c2001d9432); + _testLambertW0WadMonoAround(0x5e4117305c6e33ebc); + _testLambertW0WadMonoAround(0x2b416472dce2ea26d); + _testLambertW0WadMonoAround(0x71f55956ef3326067); + _testLambertW0WadMonoAround(0x35d9d57c965eb82c6); + _testLambertW0WadMonoAround(0x184f520f19335f25d); + _testLambertW0WadMonoAround(0x3c4bb8f445abe21a7); + _testLambertW0WadMonoAround(0x573e3b3e06e208201); + _testLambertW0WadMonoAround(0x184f520f19335f25d); + _testLambertW0WadMonoAround(0x573e3b3e06e208201); + _testLambertW0WadMonoAround(0x61e511ba00db632a4); + _testLambertW0WadMonoAround(0x12731b97bde57933d); + _testLambertW0WadMonoAround(0x79c29b05cf39be374); + _testLambertW0WadMonoAround(0x390fcd4186ac250b3); + _testLambertW0WadMonoAround(0x69c74b5975fd4832a); + _testLambertW0WadMonoAround(0x59db219a7048121bd); + _testLambertW0WadMonoAround(0x28f2adc4fab331d251); + _testLambertW0WadMonoAround(0x7be91527cc31769c); + _testLambertW0WadMonoAround(0x2ef215ae6701c40f); + _testLambertW0WadMonoAround(0x1240541334cfadd81); + _testLambertW0WadMonoAround(0x2a79eccb3d5f4faaed); + _testLambertW0WadMonoAround(0x7470d50c23bfd30e0); + _testLambertW0WadMonoAround(0x313386f14a7f95af9); + _testLambertW0WadMonoAround(0x2a60f3b64c57088e9); + _testLambertW0WadMonoAround(0x381298f7aa53edfe0); + _testLambertW0WadMonoAround(0x5cbfac5d7a1770806); + _testLambertW0WadMonoAround(0x19e46d1b5e6aba57e); + _testLambertW0WadMonoAround(0x19ff86906ae47c70a); + _testLambertW0WadMonoAround(0x164684654d9ca54ea1); + _testLambertW0WadMonoAround(0x99337fa75e803139); + _testLambertW0WadMonoAround(0x6fa0a50fcb8a95b97e); + _testLambertW0WadMonoAround(0xa117a195e06c3fd531); + _testLambertW0WadMonoAround(0x305da7073093bd8a07); + _testLambertW0WadMonoAround(0x98582b07fd3c6b64); + _testLambertW0WadMonoAround(0x1e824d2a367d9ce65); + _testLambertW0WadMonoAround(0x7bea796d633b386a); + _testLambertW0WadMonoAround(0x2fff5c38c6b2a2cd); + _testLambertW0WadMonoAround(0x198af4e7ffee1df7627); + _testLambertW0WadMonoAround(0x8ea8a7b6f7c7424d8d); + _testLambertW0WadMonoAround(0x11e504fa805e54e2ed8); + _testLambertW0WadMonoAround(0x3e5f2a7801badcdabd); + _testLambertW0WadMonoAround(0x1b7aaad69ac8770a3be); + _testLambertW0WadMonoAround(0x658acb00d525f3d345); + _testLambertW0WadMonoAround(0xd994d6447146880183f); + _testLambertW0WadMonoAround(0x2e07a342d7b1bc1a5ae); + } + + function testLambertW0WadMonoDebug() public { + unchecked { + for (int256 i = -9; i <= 9; ++i) { + _testLambertW0WadMonoAround(0x2e07a342d7b1bc1a5ae + i); + } + } + } + + function _testLambertW0WadMonoAround(int256 x) internal { + emit LogInt("x", x); + emit LogUint("log2(x)", FixedPointMathLib.log2(uint256(x))); + testLambertW0WadMonotonicallyIncreasingAround(x); + } + + function testLambertW0WadMonotonicallyIncreasingAround2(uint96 t) public { + int256 x = int256(uint256(t)); + testLambertW0WadMonotonicallyIncreasingAround(x); + if (t & 0xff == 0xab) { + _testLambertW0WadMonoFocus(x, 0, 0x1ffffffffffff, 0xffffffffffffffffff); + _testLambertW0WadMonoFocus(x, 1, 0x1fffffffffffff, 0xffffffffffffffffff); + _testLambertW0WadMonoFocus(x, 2, 0xfffffffffffffff, 0xffffffffffffffffff); + _testLambertW0WadMonoFocus(x, 3, 0xffffffffffffffff, 0xfffffffffffffffff); + _testLambertW0WadMonoFocus(x, 4, 0xffffffffffffffff, 0xfffffffffffffffff); + _testLambertW0WadMonoFocus(x, 5, 0xffffffffffffffff, 0xffffffffffffffffff); + _testLambertW0WadMonoFocus(x, 6, 0xffffffffffffffff, 0xffffffffffffffffff); + _testLambertW0WadMonoFocus(x, 7, 0xffffffffffffffff, 0xfffffffffffffffffff); + _testLambertW0WadMonoFocus(x, 8, 0xffffffffffffffff, 0xfffffffffffffffffff); + _testLambertW0WadMonoFocus(x, 9, 0xffffffffffffffff, 0xffffffffffffffffffff); + } + } + + function _testLambertW0WadMonoFocus(int256 t, int256 i, int256 low, int256 mask) internal { + int256 x; + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, t) + mstore(0x20, i) + x := and(keccak256(0x00, 0x40), mask) + } + do { + testLambertW0WadMonotonicallyIncreasingAround(x); + x >>= 1; + } while (x >= low); + } + + function testLambertW0WadMonotonicallyIncreasingAround(int256 t) public { + if (t < _LAMBERT_W0_MIN) t = _boundLambertW0WadInput(t); + unchecked { + int256 end = t + 2; + for (int256 x = t - 2; x != end; ++x) { + testLambertW0WadMonotonicallyIncreasing(x, x + 1); + } + } + } + + function testLambertW0WadMonotonicallyIncreasing(int256 a, int256 b) public { + if (a < _LAMBERT_W0_MIN) a = _boundLambertW0WadInput(a); + if (b < _LAMBERT_W0_MIN) b = _boundLambertW0WadInput(b); + if (a > b) { + int256 t = b; + b = a; + a = t; + } + unchecked { + uint256 gasBefore = gasleft(); + int256 w0a = FixedPointMathLib.lambertW0Wad(a); + uint256 gasUsed = gasBefore - gasleft(); + int256 w0b = FixedPointMathLib.lambertW0Wad(b); + bool success = w0a <= w0b; + emit TestingLambertW0WadMonotonicallyIncreasing(a, b, w0a, w0b, success, gasUsed); + if (!success) { + emit LogUint("log2(a)", FixedPointMathLib.log2(uint256(a))); + emit LogUint("log2(b)", FixedPointMathLib.log2(uint256(b))); + emit LogUint("log2(w0a)", FixedPointMathLib.log2(uint256(w0a))); + emit LogUint("log2(w0b)", FixedPointMathLib.log2(uint256(w0b))); + assertTrue(success); + } + } + } + + function _boundLambertW0WadInput(int256 x) internal pure returns (int256 result) { + /// @solidity memory-safe-assembly + assembly { + result := shr(1, shl(1, not(x))) + } + } + + function testMulWad() public { + assertEq(FixedPointMathLib.mulWad(2.5e18, 0.5e18), 1.25e18); + assertEq(FixedPointMathLib.mulWad(3e18, 1e18), 3e18); + assertEq(FixedPointMathLib.mulWad(369, 271), 0); + } + + function testMulWadEdgeCases() public { + assertEq(FixedPointMathLib.mulWad(0, 1e18), 0); + assertEq(FixedPointMathLib.mulWad(1e18, 0), 0); + assertEq(FixedPointMathLib.mulWad(0, 0), 0); + } + + function testSMulWad() public { + assertEq(FixedPointMathLib.sMulWad(0, -2e18), 0); + assertEq(FixedPointMathLib.sMulWad(1e18, -1), -1); + assertEq(FixedPointMathLib.sMulWad(-0.5e18, 2e18), -1e18); + assertEq(FixedPointMathLib.sMulWad(-0.5e18, -10e18), 5e18); + } + + function testSMulWadOverflowTrickDifferential(int256 x, int256 y) public { + unchecked { + bool c; + int256 z; + /// @solidity memory-safe-assembly + assembly { + z := mul(x, y) + c := iszero(gt(or(iszero(x), eq(sdiv(z, x), y)), lt(not(x), eq(y, shl(255, 1))))) + } + assertEq(c, !((x == 0 || z / x == y) && (x != -1 || y != type(int256).min))); + } + } + + function testSMulWadEdgeCases() public { + assertEq(FixedPointMathLib.sMulWad(1e18, type(int256).max / 1e18), type(int256).max / 1e18); + assertEq(FixedPointMathLib.sMulWad(-1e18, type(int256).min / 2e18), type(int256).max / 2e18); + assertEq(FixedPointMathLib.sMulWad(0, 0), 0); + } + + function testMulWadUp() public { + assertEq(FixedPointMathLib.mulWadUp(2.5e18, 0.5e18), 1.25e18); + assertEq(FixedPointMathLib.mulWadUp(3e18, 1e18), 3e18); + assertEq(FixedPointMathLib.mulWadUp(369, 271), 1); + } + + function testMulWadUpEdgeCases() public { + assertEq(FixedPointMathLib.mulWadUp(0, 1e18), 0); + assertEq(FixedPointMathLib.mulWadUp(1e18, 0), 0); + assertEq(FixedPointMathLib.mulWadUp(0, 0), 0); + } + + function testDivWad() public { + assertEq(FixedPointMathLib.divWad(1.25e18, 0.5e18), 2.5e18); + assertEq(FixedPointMathLib.divWad(3e18, 1e18), 3e18); + assertEq(FixedPointMathLib.divWad(2, 100000000000000e18), 0); + } + + function testDivWadEdgeCases() public { + assertEq(FixedPointMathLib.divWad(0, 1e18), 0); + } + + function testSDivWad() public { + assertEq(FixedPointMathLib.sDivWad(1.25e18, -0.5e18), -2.5e18); + assertEq(FixedPointMathLib.sDivWad(3e18, -1e18), -3e18); + assertEq(FixedPointMathLib.sDivWad(type(int256).min / 1e18, type(int256).max), 0); + } + + function testSDivWadEdgeCases() public { + assertEq(FixedPointMathLib.sDivWad(0, 1e18), 0); + } + + function testDivWadZeroDenominatorReverts() public { + vm.expectRevert(FixedPointMathLib.DivWadFailed.selector); + this.divWad(1e18, 0); + } + + function testDivWadUp() public { + assertEq(FixedPointMathLib.divWadUp(1.25e18, 0.5e18), 2.5e18); + assertEq(FixedPointMathLib.divWadUp(3e18, 1e18), 3e18); + assertEq(FixedPointMathLib.divWadUp(2, 100000000000000e18), 1); + unchecked { + for (uint256 i; i < 10; ++i) { + assertEq(FixedPointMathLib.divWadUp(2, 100000000000000e18), 1); + } + } + } + + function testDivWadUpEdgeCases() public { + assertEq(FixedPointMathLib.divWadUp(0, 1e18), 0); + } + + function testDivWadUpZeroDenominatorReverts() public { + vm.expectRevert(FixedPointMathLib.DivWadFailed.selector); + this.divWadUp(1e18, 0); + } + + function testMulDiv() public { + assertEq(FixedPointMathLib.mulDiv(2.5e27, 0.5e27, 1e27), 1.25e27); + assertEq(FixedPointMathLib.mulDiv(2.5e18, 0.5e18, 1e18), 1.25e18); + assertEq(FixedPointMathLib.mulDiv(2.5e8, 0.5e8, 1e8), 1.25e8); + assertEq(FixedPointMathLib.mulDiv(369, 271, 1e2), 999); + + assertEq(FixedPointMathLib.mulDiv(1e27, 1e27, 2e27), 0.5e27); + assertEq(FixedPointMathLib.mulDiv(1e18, 1e18, 2e18), 0.5e18); + assertEq(FixedPointMathLib.mulDiv(1e8, 1e8, 2e8), 0.5e8); + + assertEq(FixedPointMathLib.mulDiv(2e27, 3e27, 2e27), 3e27); + assertEq(FixedPointMathLib.mulDiv(3e18, 2e18, 3e18), 2e18); + assertEq(FixedPointMathLib.mulDiv(2e8, 3e8, 2e8), 3e8); + } + + function testMulDivEdgeCases() public { + assertEq(FixedPointMathLib.mulDiv(0, 1e18, 1e18), 0); + assertEq(FixedPointMathLib.mulDiv(1e18, 0, 1e18), 0); + assertEq(FixedPointMathLib.mulDiv(0, 0, 1e18), 0); + } + + function testMulDivZeroDenominatorReverts() public { + vm.expectRevert(FixedPointMathLib.MulDivFailed.selector); + this.mulDiv(1e18, 1e18, 0); + } + + function testMulDivUp() public { + assertEq(FixedPointMathLib.mulDivUp(2.5e27, 0.5e27, 1e27), 1.25e27); + assertEq(FixedPointMathLib.mulDivUp(2.5e18, 0.5e18, 1e18), 1.25e18); + assertEq(FixedPointMathLib.mulDivUp(2.5e8, 0.5e8, 1e8), 1.25e8); + assertEq(FixedPointMathLib.mulDivUp(369, 271, 1e2), 1000); + + assertEq(FixedPointMathLib.mulDivUp(1e27, 1e27, 2e27), 0.5e27); + assertEq(FixedPointMathLib.mulDivUp(1e18, 1e18, 2e18), 0.5e18); + assertEq(FixedPointMathLib.mulDivUp(1e8, 1e8, 2e8), 0.5e8); + + assertEq(FixedPointMathLib.mulDivUp(2e27, 3e27, 2e27), 3e27); + assertEq(FixedPointMathLib.mulDivUp(3e18, 2e18, 3e18), 2e18); + assertEq(FixedPointMathLib.mulDivUp(2e8, 3e8, 2e8), 3e8); + } + + function testMulDivUpEdgeCases() public { + assertEq(FixedPointMathLib.mulDivUp(0, 1e18, 1e18), 0); + assertEq(FixedPointMathLib.mulDivUp(1e18, 0, 1e18), 0); + assertEq(FixedPointMathLib.mulDivUp(0, 0, 1e18), 0); + } + + function testMulDivUpZeroDenominator() public { + vm.expectRevert(FixedPointMathLib.MulDivFailed.selector); + this.mulDivUp(1e18, 1e18, 0); + } + + function mulDivUp(uint256 x, uint256 y, uint256 d) public pure returns (uint256) { + return FixedPointMathLib.mulDivUp(x, y, d); + } + + function testLnWad() public { + assertEq(FixedPointMathLib.lnWad(1e18), 0); + + // Actual: 999999999999999999.8674576… + assertEq(FixedPointMathLib.lnWad(2718281828459045235), 999999999999999999); + + // Actual: 2461607324344817917.963296… + assertEq(FixedPointMathLib.lnWad(11723640096265400935), 2461607324344817918); + } + + function testLnWadSmall() public { + // Actual: -41446531673892822312.3238461… + assertEq(FixedPointMathLib.lnWad(1), -41446531673892822313); + + // Actual: -37708862055609454006.40601608… + assertEq(FixedPointMathLib.lnWad(42), -37708862055609454007); + + // Actual: -32236191301916639576.251880365581… + assertEq(FixedPointMathLib.lnWad(1e4), -32236191301916639577); + + // Actual: -20723265836946411156.161923092… + assertEq(FixedPointMathLib.lnWad(1e9), -20723265836946411157); + } + + function testLnWadBig() public { + // Actual: 135305999368893231589.070344787… + assertEq(FixedPointMathLib.lnWad(2 ** 255 - 1), 135305999368893231589); + + // Actual: 76388489021297880288.605614463571… + assertEq(FixedPointMathLib.lnWad(2 ** 170), 76388489021297880288); + + // Actual: 47276307437780177293.081865… + assertEq(FixedPointMathLib.lnWad(2 ** 128), 47276307437780177293); + } + + function testLnWadNegativeReverts() public { + vm.expectRevert(FixedPointMathLib.LnWadUndefined.selector); + this.lnWad(-1); + vm.expectRevert(FixedPointMathLib.LnWadUndefined.selector); + this.lnWad(-2 ** 255); + } + + function testLnWadOverflowReverts() public { + vm.expectRevert(FixedPointMathLib.LnWadUndefined.selector); + this.lnWad(0); + } + + function lnWad(int256 x) public pure returns (int256) { + return FixedPointMathLib.lnWad(x); + } + + function testRPow() public { + assertEq(FixedPointMathLib.rpow(0, 0, 0), 0); + assertEq(FixedPointMathLib.rpow(1, 0, 0), 0); + assertEq(FixedPointMathLib.rpow(0, 1, 0), 0); + assertEq(FixedPointMathLib.rpow(0, 0, 1), 1); + assertEq(FixedPointMathLib.rpow(1, 1, 0), 1); + assertEq(FixedPointMathLib.rpow(1, 1, 1), 1); + assertEq(FixedPointMathLib.rpow(2e27, 0, 1e27), 1e27); + assertEq(FixedPointMathLib.rpow(2e27, 2, 1e27), 4e27); + assertEq(FixedPointMathLib.rpow(2e18, 2, 1e18), 4e18); + assertEq(FixedPointMathLib.rpow(2e8, 2, 1e8), 4e8); + assertEq(FixedPointMathLib.rpow(8, 3, 1), 512); + } + + function testRPowOverflowReverts() public { + vm.expectRevert(FixedPointMathLib.RPowOverflow.selector); + this.rpow(2, type(uint128).max, 1); + vm.expectRevert(FixedPointMathLib.RPowOverflow.selector); + this.rpow(type(uint128).max, 3, 1); + } + + function rpow(uint256 x, uint256 y, uint256 b) public pure returns (uint256) { + return FixedPointMathLib.rpow(x, y, b); + } + + function testSqrt() public { + assertEq(FixedPointMathLib.sqrt(0), 0); + assertEq(FixedPointMathLib.sqrt(1), 1); + assertEq(FixedPointMathLib.sqrt(2704), 52); + assertEq(FixedPointMathLib.sqrt(110889), 333); + assertEq(FixedPointMathLib.sqrt(32239684), 5678); + unchecked { + for (uint256 i = 100; i < 200; ++i) { + assertEq(FixedPointMathLib.sqrt(i * i), i); + } + } + } + + function testSqrtWad() public { + assertEq(FixedPointMathLib.sqrtWad(0), 0); + assertEq(FixedPointMathLib.sqrtWad(1), 10 ** 9); + assertEq(FixedPointMathLib.sqrtWad(2), 1414213562); + assertEq(FixedPointMathLib.sqrtWad(4), 2000000000); + assertEq(FixedPointMathLib.sqrtWad(8), 2828427124); + assertEq(FixedPointMathLib.sqrtWad(16), 4000000000); + assertEq(FixedPointMathLib.sqrtWad(32), 5656854249); + assertEq(FixedPointMathLib.sqrtWad(64), 8000000000); + assertEq(FixedPointMathLib.sqrtWad(10 ** 18), 10 ** 18); + assertEq(FixedPointMathLib.sqrtWad(4 * 10 ** 18), 2 * 10 ** 18); + assertEq(FixedPointMathLib.sqrtWad(type(uint8).max), 15968719422); + assertEq(FixedPointMathLib.sqrtWad(type(uint16).max), 255998046867); + assertEq(FixedPointMathLib.sqrtWad(type(uint32).max), 65535999992370); + assertEq(FixedPointMathLib.sqrtWad(type(uint64).max), 4294967295999999999); + assertEq(FixedPointMathLib.sqrtWad(type(uint128).max), 18446744073709551615999999999); + assertEq( + FixedPointMathLib.sqrtWad(type(uint256).max), + 340282366920938463463374607431768211455999999999 + ); + } + + function testCbrt() public { + assertEq(FixedPointMathLib.cbrt(0), 0); + assertEq(FixedPointMathLib.cbrt(1), 1); + assertEq(FixedPointMathLib.cbrt(2), 1); + assertEq(FixedPointMathLib.cbrt(3), 1); + assertEq(FixedPointMathLib.cbrt(9), 2); + assertEq(FixedPointMathLib.cbrt(27), 3); + assertEq(FixedPointMathLib.cbrt(80), 4); + assertEq(FixedPointMathLib.cbrt(81), 4); + assertEq(FixedPointMathLib.cbrt(10 ** 18), 10 ** 6); + assertEq(FixedPointMathLib.cbrt(8 * 10 ** 18), 2 * 10 ** 6); + assertEq(FixedPointMathLib.cbrt(9 * 10 ** 18), 2080083); + assertEq(FixedPointMathLib.cbrt(type(uint8).max), 6); + assertEq(FixedPointMathLib.cbrt(type(uint16).max), 40); + assertEq(FixedPointMathLib.cbrt(type(uint32).max), 1625); + assertEq(FixedPointMathLib.cbrt(type(uint64).max), 2642245); + assertEq(FixedPointMathLib.cbrt(type(uint128).max), 6981463658331); + assertEq(FixedPointMathLib.cbrt(type(uint256).max), 48740834812604276470692694); + } + + function testCbrtWad() public { + assertEq(FixedPointMathLib.cbrtWad(0), 0); + assertEq(FixedPointMathLib.cbrtWad(1), 10 ** 12); + assertEq(FixedPointMathLib.cbrtWad(2), 1259921049894); + assertEq(FixedPointMathLib.cbrtWad(3), 1442249570307); + assertEq(FixedPointMathLib.cbrtWad(9), 2080083823051); + assertEq(FixedPointMathLib.cbrtWad(27), 3000000000000); + assertEq(FixedPointMathLib.cbrtWad(80), 4308869380063); + assertEq(FixedPointMathLib.cbrtWad(81), 4326748710922); + assertEq(FixedPointMathLib.cbrtWad(10 ** 18), 10 ** 18); + assertEq(FixedPointMathLib.cbrtWad(8 * 10 ** 18), 2 * 10 ** 18); + assertEq(FixedPointMathLib.cbrtWad(9 * 10 ** 18), 2080083823051904114); + assertEq(FixedPointMathLib.cbrtWad(type(uint8).max), 6341325705384); + assertEq(FixedPointMathLib.cbrtWad(type(uint16).max), 40317268530317); + assertEq(FixedPointMathLib.cbrtWad(type(uint32).max), 1625498677089280); + assertEq(FixedPointMathLib.cbrtWad(type(uint64).max), 2642245949629133047); + assertEq(FixedPointMathLib.cbrtWad(type(uint128).max), 6981463658331559092288464); + assertEq( + FixedPointMathLib.cbrtWad(type(uint256).max), 48740834812604276470692694885616578541 + ); + } + + function testCbrtWadDebug() public { + uint256 x = 57896044618658097711785492504343953926634992332820282019727; + uint256 z = FixedPointMathLib.cbrt(x); + emit LogUint(z); + z = (z + 1) * 10 ** 12; + z = (FixedPointMathLib.fullMulDivUnchecked(x, 10 ** 36, z * z) + z + z) / 3; + emit LogUint(z); + z = (FixedPointMathLib.fullMulDivUnchecked(x, 10 ** 36, z * z) + z + z) / 3; + emit LogUint(z); + z = (FixedPointMathLib.fullMulDivUnchecked(x, 10 ** 36, z * z) + z + z) / 3; + emit LogUint(z); + } + + function testLog2() public { + assertEq(FixedPointMathLib.log2(0), 0); + assertEq(FixedPointMathLib.log2(2), 1); + assertEq(FixedPointMathLib.log2(4), 2); + assertEq(FixedPointMathLib.log2(1024), 10); + assertEq(FixedPointMathLib.log2(1048576), 20); + assertEq(FixedPointMathLib.log2(1073741824), 30); + for (uint256 i = 1; i < 255; i++) { + assertEq(FixedPointMathLib.log2((1 << i) - 1), i - 1); + assertEq(FixedPointMathLib.log2((1 << i)), i); + assertEq(FixedPointMathLib.log2((1 << i) + 1), i); + } + } + + function testLog2Differential(uint256 x) public { + assertEq(FixedPointMathLib.log2(x), _log2Original(x)); + } + + function _log2Original(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 128; + } + if (value >> 64 > 0) { + value >>= 64; + result += 64; + } + if (value >> 32 > 0) { + value >>= 32; + result += 32; + } + if (value >> 16 > 0) { + value >>= 16; + result += 16; + } + if (value >> 8 > 0) { + value >>= 8; + result += 8; + } + if (value >> 4 > 0) { + value >>= 4; + result += 4; + } + if (value >> 2 > 0) { + value >>= 2; + result += 2; + } + if (value >> 1 > 0) { + result += 1; + } + } + return result; + } + + function testLog2Up() public { + assertEq(FixedPointMathLib.log2Up(0), 0); + assertEq(FixedPointMathLib.log2Up(1), 0); + assertEq(FixedPointMathLib.log2Up(2), 1); + assertEq(FixedPointMathLib.log2Up(2 + 1), 2); + assertEq(FixedPointMathLib.log2Up(4), 2); + assertEq(FixedPointMathLib.log2Up(4 + 1), 3); + assertEq(FixedPointMathLib.log2Up(4 + 2), 3); + assertEq(FixedPointMathLib.log2Up(1024), 10); + assertEq(FixedPointMathLib.log2Up(1024 + 1), 11); + assertEq(FixedPointMathLib.log2Up(1048576), 20); + assertEq(FixedPointMathLib.log2Up(1048576 + 1), 21); + assertEq(FixedPointMathLib.log2Up(1073741824), 30); + assertEq(FixedPointMathLib.log2Up(1073741824 + 1), 31); + for (uint256 i = 2; i < 255; i++) { + assertEq(FixedPointMathLib.log2Up((1 << i) - 1), i); + assertEq(FixedPointMathLib.log2Up((1 << i)), i); + assertEq(FixedPointMathLib.log2Up((1 << i) + 1), i + 1); + } + } + + function testAvg() public { + assertEq(FixedPointMathLib.avg(uint256(5), uint256(6)), uint256(5)); + assertEq(FixedPointMathLib.avg(uint256(0), uint256(1)), uint256(0)); + assertEq(FixedPointMathLib.avg(uint256(45645465), uint256(4846513)), uint256(25245989)); + } + + function testAvgSigned() public { + assertEq(FixedPointMathLib.avg(int256(5), int256(6)), int256(5)); + assertEq(FixedPointMathLib.avg(int256(0), int256(1)), int256(0)); + assertEq(FixedPointMathLib.avg(int256(45645465), int256(4846513)), int256(25245989)); + + assertEq(FixedPointMathLib.avg(int256(5), int256(-6)), int256(-1)); + assertEq(FixedPointMathLib.avg(int256(0), int256(-1)), int256(-1)); + assertEq(FixedPointMathLib.avg(int256(45645465), int256(-4846513)), int256(20399476)); + assertEq(FixedPointMathLib.avg(int256(-10), int256(-19)), int256(-15)); + } + + function testAvgEdgeCase() public { + assertEq(FixedPointMathLib.avg(uint256(2 ** 256 - 1), uint256(1)), uint256(2 ** 255)); + assertEq(FixedPointMathLib.avg(uint256(2 ** 256 - 1), uint256(10)), uint256(2 ** 255 + 4)); + assertEq( + FixedPointMathLib.avg(uint256(2 ** 256 - 1), uint256(2 ** 256 - 1)), + uint256(2 ** 256 - 1) + ); + } + + function testAbs() public { + assertEq(FixedPointMathLib.abs(0), 0); + assertEq(FixedPointMathLib.abs(-5), 5); + assertEq(FixedPointMathLib.abs(5), 5); + assertEq(FixedPointMathLib.abs(-1155656654), 1155656654); + assertEq(FixedPointMathLib.abs(621356166516546561651), 621356166516546561651); + } + + function testDist() public { + assertEq(FixedPointMathLib.dist(int256(0), int256(0)), 0); + assertEq(FixedPointMathLib.dist(int256(-5), int256(-4)), 1); + assertEq(FixedPointMathLib.dist(int256(5), int256(46)), 41); + assertEq(FixedPointMathLib.dist(int256(46), int256(5)), 41); + assertEq(FixedPointMathLib.dist(int256(-1155656654), int256(6544844)), 1162201498); + assertEq(FixedPointMathLib.dist(int256(-848877), int256(-8447631456)), 8446782579); + } + + function testDistEdgeCases() public { + assertEq(FixedPointMathLib.dist(type(int256).min, type(int256).max), type(uint256).max); + assertEq( + FixedPointMathLib.dist(type(int256).min, 0), + 0x8000000000000000000000000000000000000000000000000000000000000000 + ); + assertEq( + FixedPointMathLib.dist(type(int256).max, 5), + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa + ); + assertEq( + FixedPointMathLib.dist(type(int256).min, -5), + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb + ); + } + + function testAbsEdgeCases() public { + assertEq(FixedPointMathLib.abs(-(2 ** 255 - 1)), (2 ** 255 - 1)); + assertEq(FixedPointMathLib.abs((2 ** 255 - 1)), (2 ** 255 - 1)); + } + + function testGcd() public { + assertEq(FixedPointMathLib.gcd(0, 0), 0); + assertEq(FixedPointMathLib.gcd(85, 0), 85); + assertEq(FixedPointMathLib.gcd(0, 2), 2); + assertEq(FixedPointMathLib.gcd(56, 45), 1); + assertEq(FixedPointMathLib.gcd(12, 28), 4); + assertEq(FixedPointMathLib.gcd(12, 1), 1); + assertEq(FixedPointMathLib.gcd(486516589451122, 48656), 2); + assertEq(FixedPointMathLib.gcd(2 ** 254 - 4, 2 ** 128 - 1), 15); + assertEq(FixedPointMathLib.gcd(3, 26017198113384995722614372765093167890), 1); + unchecked { + for (uint256 i = 2; i < 10; ++i) { + assertEq(FixedPointMathLib.gcd(31 * (1 << i), 31), 31); + } + } + } + + function fullMulDiv(uint256 x, uint256 y, uint256 d) public pure returns (uint256) { + return FixedPointMathLib.fullMulDiv(x, y, d); + } + + function fullMulDivN(uint256 x, uint256 y, uint8 n) public pure returns (uint256) { + return FixedPointMathLib.fullMulDivN(x, y, n); + } + + function testFullMulDiv() public { + assertEq(FixedPointMathLib.fullMulDiv(0, 0, 1), 0); + assertEq(FixedPointMathLib.fullMulDiv(4, 4, 2), 8); + assertEq(FixedPointMathLib.fullMulDiv(2 ** 200, 2 ** 200, 2 ** 200), 2 ** 200); + } + + function testFullMulDivN() public { + assertEq(FixedPointMathLib.fullMulDivN(0, 0, 0), 0); + assertEq(FixedPointMathLib.fullMulDivN(4, 4, 1), 8); + assertEq(FixedPointMathLib.fullMulDivN(2 ** 200, 2 ** 200, 200), 2 ** 200); + } + + function testFullMulDivUnchecked() public { + assertEq(FixedPointMathLib.fullMulDivUnchecked(0, 0, 1), 0); + assertEq(FixedPointMathLib.fullMulDivUnchecked(4, 4, 2), 8); + assertEq(FixedPointMathLib.fullMulDivUnchecked(2 ** 200, 2 ** 200, 2 ** 200), 2 ** 200); + } + + function testFullMulDivAlwaysRevertsIfDivisorIsZero(uint256 a, uint256 b) public { + vm.expectRevert(FixedPointMathLib.FullMulDivFailed.selector); + this.fullMulDivUp(a, b, 0); + } + + function fullMulDivUp(uint256 a, uint256 b, uint256 d) public pure returns (uint256) { + return FixedPointMathLib.fullMulDivUp(a, b, d); + } + + function testFullMulDivUpRevertsIfRoundedUpResultOverflowsCase1() public { + vm.expectRevert(FixedPointMathLib.FullMulDivFailed.selector); + this.fullMulDivUp( + 535006138814359, 432862656469423142931042426214547535783388063929571229938474969, 2 + ); + } + + function testFullMulDivUpRevertsIfRoundedUpResultOverflowsCase2() public { + vm.expectRevert(FixedPointMathLib.FullMulDivFailed.selector); + this.fullMulDivUp( + 115792089237316195423570985008687907853269984659341747863450311749907997002549, + 115792089237316195423570985008687907853269984659341747863450311749907997002550, + 115792089237316195423570985008687907853269984653042931687443039491902864365164 + ); + } + + function testFullMulDivUnchecked(uint256 a, uint256 b, uint256 d) public { + a = _bound(a, 0, type(uint128).max); + b = _bound(b, 0, type(uint128).max); + d = _bound(d, 1, type(uint256).max); + assertEq(a * b / d, FixedPointMathLib.fullMulDivUnchecked(a, b, d)); + } + + function testFullMulDiv(uint256 a, uint256 b, uint256 d) public returns (uint256 result) { + if (d == 0) { + vm.expectRevert(FixedPointMathLib.FullMulDivFailed.selector); + this.fullMulDiv(a, b, d); + return 0; + } + + // Compute a * b in Chinese Remainder Basis + uint256 expectedA; + uint256 expectedB; + unchecked { + expectedA = a * b; + expectedB = mulmod(a, b, 2 ** 256 - 1); + } + + // Construct a * b + uint256 prod0; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + /// @solidity memory-safe-assembly + assembly { + let mm := mulmod(a, b, not(0)) + prod0 := mul(a, b) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + if (prod1 >= d) { + vm.expectRevert(FixedPointMathLib.FullMulDivFailed.selector); + this.fullMulDiv(a, b, d); + return 0; + } + + uint256 q = FixedPointMathLib.fullMulDiv(a, b, d); + uint256 r = mulmod(a, b, d); + + // Compute q * d + r in Chinese Remainder Basis + uint256 actualA; + uint256 actualB; + unchecked { + actualA = q * d + r; + actualB = addmod(mulmod(q, d, 2 ** 256 - 1), r, 2 ** 256 - 1); + } + + assertEq(actualA, expectedA); + assertEq(actualB, expectedB); + return q; + } + + function testFullMulDivN(uint256 a, uint256 b, uint8 n) public { + (bool success0, bytes memory result0) = address(this) + .staticcall( + abi.encodeWithSignature("fullMulDiv(uint256,uint256,uint256)", a, b, 1 << n) + ); + (bool success1, bytes memory result1) = address(this) + .staticcall(abi.encodeWithSignature("fullMulDivN(uint256,uint256,uint8)", a, b, n)); + assertEq(success0, success1); + if (success0) { + assertEq(abi.decode(result0, (uint256)), abi.decode(result1, (uint256))); + } + } + + function testFullMulDivUp(uint256 a, uint256 b, uint256 d) public { + uint256 fullMulDivResult = testFullMulDiv(a, b, d); + if (fullMulDivResult != 0) { + uint256 expectedResult = fullMulDivResult; + if (mulmod(a, b, d) > 0) { + if (!(fullMulDivResult < type(uint256).max)) { + vm.expectRevert(FixedPointMathLib.FullMulDivFailed.selector); + this.fullMulDivUp(a, b, d); + return; + } + expectedResult++; + } + assertEq(FixedPointMathLib.fullMulDivUp(a, b, d), expectedResult); + } + } + + function _sampleEdgeCases(int256 x, int256 y) internal returns (int256, int256) { + uint256 r = _randomUniform(); + if (r & 0xf000000 == uint256(0)) y = -1; + if (r & 0x0f00000 == uint256(0)) y = type(int256).min; + if (r & 0x00f0000 == uint256(0)) x = -1; + if (r & 0x000f000 == uint256(0)) x = type(int256).min; + if (r & 0x0000f00 == uint256(0)) y = 0; + if (r & 0x00000f0 == uint256(0)) x = 0; + if (r & 0x000000f == uint256(0)) (x, y) = (int256(_random()), int256(_random())); + return (x, y); + } + + function _sampleEdgeCases(uint256 x, uint256 y) internal returns (uint256, uint256) { + uint256 r = _randomUniform(); + if (r & 0xf000000 == uint256(0)) y = uint256(int256(-1)); + if (r & 0x0f00000 == uint256(0)) y = uint256(type(int256).min); + if (r & 0x00f0000 == uint256(0)) x = uint256(int256(-1)); + if (r & 0x000f000 == uint256(0)) x = uint256(type(int256).min); + if (r & 0x0000f00 == uint256(0)) y = 0; + if (r & 0x00000f0 == uint256(0)) x = 0; + if (r & 0x000000f == uint256(0)) (x, y) = (uint256(_random()), uint256(_random())); + return (x, y); + } + + function mulWadOriginal(uint256 x, uint256 y) public pure returns (uint256) { + return (x * y) / 1e18; + } + + function _mulWadWillFail(uint256 x, uint256 y) internal view returns (bool) { + bytes memory data = abi.encodeWithSignature("mulWadOriginal(uint256,uint256)", x, y); + (bool success,) = address(this).staticcall(data); + return !success; + } + + function testMulWad(uint256 x, uint256 y) public { + (x, y) = _sampleEdgeCases(x, y); + if (_mulWadWillFail(x, y)) { + vm.expectRevert(FixedPointMathLib.MulWadFailed.selector); + this.mulWad(x, y); + return; + } + uint256 result = FixedPointMathLib.mulWad(x, y); + assertEq(result, (x * y) / 1e18); + assertEq(FixedPointMathLib.rawMulWad(x, y), result); + } + + function mulWad(uint256 x, uint256 y) public pure returns (uint256) { + return FixedPointMathLib.mulWad(x, y); + } + + function sMulWadOriginal(int256 x, int256 y) public pure returns (int256) { + return (x * y) / 1e18; + } + + function _sMulWadWillFail(int256 x, int256 y) internal view returns (bool) { + bytes memory data = abi.encodeWithSignature("sMulWadOriginal(int256,int256)", x, y); + (bool success,) = address(this).staticcall(data); + return !success; + } + + function testSMulWad(int256 x, int256 y) public { + (x, y) = _sampleEdgeCases(x, y); + if (_sMulWadWillFail(x, y)) { + vm.expectRevert(FixedPointMathLib.SMulWadFailed.selector); + this.sMulWad(x, y); + return; + } + int256 result = FixedPointMathLib.sMulWad(x, y); + assertEq(result, int256((x * y) / 1e18)); + assertEq(FixedPointMathLib.rawSMulWad(x, y), result); + } + + function sMulWad(int256 x, int256 y) public pure returns (int256) { + return FixedPointMathLib.sMulWad(x, y); + } + + function testMulWadUp(uint256 x, uint256 y) public { + (x, y) = _sampleEdgeCases(x, y); + if (_mulWadWillFail(x, y)) { + vm.expectRevert(FixedPointMathLib.MulWadFailed.selector); + this.mulWadUp(x, y); + return; + } + assertEq(FixedPointMathLib.mulWadUp(x, y), x * y == 0 ? 0 : (x * y - 1) / 1e18 + 1); + } + + function mulWadUp(uint256 x, uint256 y) public pure returns (uint256) { + return FixedPointMathLib.mulWadUp(x, y); + } + + function divWadOriginal(uint256 x, uint256 y) public pure returns (uint256) { + return (x * 1e18) / y; + } + + function _divWadWillFail(uint256 x, uint256 y) internal view returns (bool) { + bytes memory data = abi.encodeWithSignature("divWadOriginal(uint256,uint256)", x, y); + (bool success,) = address(this).staticcall(data); + return !success; + } + + function testDivWad(uint256 x, uint256 y) public { + (x, y) = _sampleEdgeCases(x, y); + if (_divWadWillFail(x, y)) { + vm.expectRevert(FixedPointMathLib.DivWadFailed.selector); + this.divWad(x, y); + return; + } + uint256 result = FixedPointMathLib.divWad(x, y); + assertEq(result, (x * 1e18) / y); + assertEq(FixedPointMathLib.rawDivWad(x, y), result); + } + + function divWad(uint256 x, uint256 y) public pure returns (uint256) { + return FixedPointMathLib.divWad(x, y); + } + + function sDivWadOriginal(int256 x, int256 y) public pure returns (int256) { + return (x * 1e18) / y; + } + + function _sDivWadWillFail(int256 x, int256 y) internal view returns (bool) { + bytes memory data = abi.encodeWithSignature("sDivWadOriginal(int256,int256)", x, y); + (bool success,) = address(this).staticcall(data); + return !success; + } + + function testSDivWad(int256 x, int256 y) public { + (x, y) = _sampleEdgeCases(x, y); + if (_sDivWadWillFail(x, y)) { + vm.expectRevert(FixedPointMathLib.SDivWadFailed.selector); + this.sDivWad(x, y); + return; + } + int256 result = FixedPointMathLib.sDivWad(x, y); + assertEq(result, int256((x * 1e18) / y)); + assertEq(FixedPointMathLib.rawSDivWad(x, y), result); + } + + function sDivWad(int256 x, int256 y) public pure returns (int256) { + return FixedPointMathLib.sDivWad(x, y); + } + + function testDivWadUp(uint256 x, uint256 y) public { + (x, y) = _sampleEdgeCases(x, y); + if (_divWadWillFail(x, y)) { + vm.expectRevert(FixedPointMathLib.DivWadFailed.selector); + this.divWadUp(x, y); + return; + } + assertEq(FixedPointMathLib.divWadUp(x, y), x == 0 ? 0 : (x * 1e18 - 1) / y + 1); + } + + function divWadUp(uint256 x, uint256 y) public pure returns (uint256) { + return FixedPointMathLib.divWadUp(x, y); + } + + function mulDivOriginal(uint256 x, uint256 y, uint256 denominator) + public + pure + returns (uint256) + { + return (x * y) / denominator; + } + + function _mulDivWillFail(uint256 x, uint256 y, uint256 denominator) + internal + view + returns (bool) + { + bytes memory data = abi.encodeWithSignature( + "mulDivOriginal(uint256,uint256,uint256)", x, y, denominator + ); + (bool success,) = address(this).staticcall(data); + return !success; + } + + function testMulDiv(uint256 x, uint256 y, uint256 denominator) public { + (x, y) = _sampleEdgeCases(x, y); + if (_mulDivWillFail(x, y, denominator)) { + vm.expectRevert(FixedPointMathLib.MulDivFailed.selector); + this.mulDiv(x, y, denominator); + return; + } + assertEq(this.mulDiv(x, y, denominator), (x * y) / denominator); + } + + function mulDiv(uint256 x, uint256 y, uint256 d) public pure returns (uint256) { + return FixedPointMathLib.mulDiv(x, y, d); + } + + function testMulDivUp(uint256 x, uint256 y, uint256 denominator) public { + (x, y) = _sampleEdgeCases(x, y); + if (_mulDivWillFail(x, y, denominator)) { + vm.expectRevert(FixedPointMathLib.MulDivFailed.selector); + this.mulDivUp(x, y, denominator); + return; + } + assertEq(this.mulDivUp(x, y, denominator), x * y == 0 ? 0 : (x * y - 1) / denominator + 1); + } + + function testCbrt(uint256 x) public { + uint256 root = FixedPointMathLib.cbrt(x); + uint256 next = root + 1; + + // Ignore cases where `next * next * next` or `next * next` overflows. + unchecked { + if (next * next * next < next * next) return; + if (next * next < next) return; + } + + assertTrue(root * root * root <= x && next * next * next > x); + } + + function testCbrtWad(uint256 x) public { + uint256 result = FixedPointMathLib.cbrtWad(x); + uint256 floor = FixedPointMathLib.cbrt(x); + assertTrue(result >= floor * 10 ** 12 && result <= (floor + 1) * 10 ** 12); + assertEq(result / 10 ** 12, floor); + } + + function testCbrtWadMonotonicallyIncreasing(uint256 x, uint256 y) public { + unchecked { + while (x == type(uint256).max) x = _random(); + uint256 a = FixedPointMathLib.cbrtWad(x); + uint256 b = FixedPointMathLib.cbrtWad(x + 1); + assertLe(a, b); + if (x < y) { + assertLe(a, FixedPointMathLib.cbrtWad(y)); + } else { + assertLe(FixedPointMathLib.cbrtWad(y), a); + } + } + } + + function testCbrtWadMonotonicallyIncreasing() public { + this.testCbrtWadMonotonicallyIncreasing( + 57896044618658097711785492504343953926634992332820282019727, 939263490 + ); + } + + function testCbrtWadConverged(uint256 x) public { + unchecked { + x = _bound(x, type(uint256).max / 10 ** 36, type(uint256).max); + uint256 z = (1 + FixedPointMathLib.cbrt(x)) * 10 ** 12; + z = (FixedPointMathLib.fullMulDivUnchecked(x, 10 ** 36, z * z) + z + z) / 3; + uint256 zBefore = z; + z = (FixedPointMathLib.fullMulDivUnchecked(x, 10 ** 36, z * z) + z + z) / 3; + assertLt(FixedPointMathLib.dist(zBefore, z), 2); + } + } + + function testCbrtWadConverged() public { + this.testCbrtWadConverged(149402619197264205146140478723340791358082632884804826834926); + } + + function testCbrtBack(uint256 x) public { + unchecked { + x = _bound(x, 0, 48740834812604276470692694); + while (x != 0) { + assertEq(FixedPointMathLib.cbrt(x * x * x), x); + x >>= 1; + } + } + } + + function testSqrt(uint256 x) public { + uint256 root = FixedPointMathLib.sqrt(x); + uint256 next = root + 1; + + // Ignore cases where `next * next` overflows. + unchecked { + if (next * next < next) return; + } + + assertTrue(root * root <= x && next * next > x); + } + + function testSqrtWad(uint256 x) public { + uint256 result = FixedPointMathLib.sqrtWad(x); + uint256 floor = FixedPointMathLib.sqrt(x); + assertTrue(result >= floor * 10 ** 9 && result <= (floor + 1) * 10 ** 9); + assertEq(result / 10 ** 9, floor); + } + + function testSqrtWadMonotonicallyIncreasing(uint256 x, uint256 y) public { + while (x == type(uint256).max) x = _random(); + uint256 a = FixedPointMathLib.sqrtWad(x); + uint256 b = FixedPointMathLib.sqrtWad(x + 1); + assertLe(a, b); + if (x < y) { + assertLe(a, FixedPointMathLib.sqrtWad(y)); + } else { + assertLe(FixedPointMathLib.sqrtWad(y), a); + } + } + + function testSqrtWadConverged(uint256 x) public { + unchecked { + x = _bound(x, type(uint256).max / 10 ** 18, type(uint256).max); + uint256 z = (1 + FixedPointMathLib.sqrt(x)) * 10 ** 9; + z = (FixedPointMathLib.fullMulDivUnchecked(x, 10 ** 18, z) + z) >> 1; + uint256 zBefore = z; + z = (FixedPointMathLib.fullMulDivUnchecked(x, 10 ** 18, z) + z) >> 1; + assertLt(FixedPointMathLib.dist(zBefore, z), 2); + } + } + + function testSqrtBack(uint256 x) public { + unchecked { + x >>= 128; + while (x != 0) { + assertEq(FixedPointMathLib.sqrt(x * x), x); + x >>= 1; + } + } + } + + function testSqrtHashed(uint256 x) public { + testSqrtBack(uint256(keccak256(abi.encode(x)))); + } + + function testSqrtHashedSingle() public { + testSqrtHashed(123); + } + + function testMin(uint256 x, uint256 y) public { + uint256 z = x < y ? x : y; + assertEq(FixedPointMathLib.min(x, y), z); + } + + function testMinBrutalized(uint256 x, uint256 y) public { + uint32 xCasted; + uint32 yCasted; + /// @solidity memory-safe-assembly + assembly { + xCasted := x + yCasted := y + } + uint256 expected = xCasted < yCasted ? xCasted : yCasted; + assertEq(FixedPointMathLib.min(xCasted, yCasted), expected); + assertEq(FixedPointMathLib.min(uint32(x), uint32(y)), expected); + expected = uint32(x) < uint32(y) ? uint32(x) : uint32(y); + assertEq(FixedPointMathLib.min(xCasted, yCasted), expected); + } + + function testMinSigned(int256 x, int256 y) public { + int256 z = x < y ? x : y; + assertEq(FixedPointMathLib.min(x, y), z); + } + + function testMax(uint256 x, uint256 y) public { + uint256 z = x > y ? x : y; + assertEq(FixedPointMathLib.max(x, y), z); + } + + function testMaxSigned(int256 x, int256 y) public { + int256 z = x > y ? x : y; + assertEq(FixedPointMathLib.max(x, y), z); + } + + function testMaxCasted(uint32 x, uint32 y, uint256 brutalizer) public { + uint32 z = x > y ? x : y; + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, brutalizer) + mstore(0x20, 1) + x := or(shl(32, keccak256(0x00, 0x40)), x) + mstore(0x20, 2) + y := or(shl(32, keccak256(0x00, 0x40)), y) + } + assertTrue(FixedPointMathLib.max(x, y) == z); + } + + function testZeroFloorSub(uint256 x, uint256 y) public { + uint256 z = x > y ? x - y : 0; + assertEq(FixedPointMathLib.zeroFloorSub(x, y), z); + } + + function testZeroFloorSubCasted(uint32 x, uint32 y, uint256 brutalizer) public { + uint256 z = x > y ? x - y : 0; + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, brutalizer) + mstore(0x20, 1) + x := or(shl(32, keccak256(0x00, 0x40)), x) + mstore(0x20, 2) + y := or(shl(32, keccak256(0x00, 0x40)), y) + } + assertTrue(FixedPointMathLib.zeroFloorSub(x, y) == z); + } + + function testDist(uint256 x, uint256 y) public { + uint256 z; + unchecked { + if (x > y) { + z = uint256(x - y); + } else { + z = uint256(y - x); + } + } + assertEq(FixedPointMathLib.dist(x, y), z); + } + + function testDist(int256 x, int256 y) public { + uint256 z; + unchecked { + if (x > y) { + z = uint256(x - y); + assert(uint256(x) - uint256(y) == z); + } else { + z = uint256(y - x); + assert(uint256(y) - uint256(x) == z); + } + } + assertEq(FixedPointMathLib.dist(x, y), z); + } + + function testAbs(int256 x) public { + uint256 z = uint256(x); + if (x < 0) { + if (x == type(int256).min) { + z = uint256(type(int256).max) + 1; + } else { + z = uint256(-x); + } + } + assertEq(FixedPointMathLib.abs(x), z); + } + + function testGcd(uint256 x, uint256 y) public { + assertEq(FixedPointMathLib.gcd(x, y), _gcd(x, y)); + } + + function testClamp(uint256 x, uint256 minValue, uint256 maxValue) public { + uint256 clamped = x; + if (clamped < minValue) { + clamped = minValue; + } + if (clamped > maxValue) { + clamped = maxValue; + } + assertEq(FixedPointMathLib.clamp(x, minValue, maxValue), clamped); + } + + function testClampSigned(int256 x, int256 minValue, int256 maxValue) public { + int256 clamped = x; + if (clamped < minValue) { + clamped = minValue; + } + if (clamped > maxValue) { + clamped = maxValue; + } + assertEq(FixedPointMathLib.clamp(x, minValue, maxValue), clamped); + } + + function testFactorial() public { + uint256 result = 1; + assertEq(FixedPointMathLib.factorial(0), result); + unchecked { + for (uint256 i = 1; i != 58; ++i) { + result = result * i; + assertEq(FixedPointMathLib.factorial(i), result); + } + } + vm.expectRevert(FixedPointMathLib.FactorialOverflow.selector); + this.factorial(58); + } + + function factorial(uint256 x) public pure returns (uint256) { + return FixedPointMathLib.factorial(x); + } + + function testFactorialOriginal() public { + uint256 result = 1; + assertEq(_factorialOriginal(0), result); + unchecked { + for (uint256 i = 1; i != 58; ++i) { + result = result * i; + assertEq(_factorialOriginal(i), result); + } + } + } + + function _factorialOriginal(uint256 x) internal pure returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + result := 1 + for {} x {} { + result := mul(result, x) + x := sub(x, 1) + } + } + } + + function _gcd(uint256 x, uint256 y) internal pure returns (uint256 result) { + if (y == 0) { + return x; + } else { + return _gcd(y, x % y); + } + } + + function testRawAdd(uint256 x, uint256 y) public { + uint256 z; + /// @solidity memory-safe-assembly + assembly { + z := add(x, y) + } + assertEq(FixedPointMathLib.rawAdd(x, y), z); + } + + function testRawAdd(int256 x, int256 y) public { + int256 z; + /// @solidity memory-safe-assembly + assembly { + z := add(x, y) + } + assertEq(FixedPointMathLib.rawAdd(x, y), z); + } + + function testRawSub(uint256 x, uint256 y) public { + uint256 z; + /// @solidity memory-safe-assembly + assembly { + z := sub(x, y) + } + assertEq(FixedPointMathLib.rawSub(x, y), z); + } + + function testRawSub(int256 x, int256 y) public { + int256 z; + /// @solidity memory-safe-assembly + assembly { + z := sub(x, y) + } + assertEq(FixedPointMathLib.rawSub(x, y), z); + } + + function testRawMul(uint256 x, uint256 y) public { + uint256 z; + /// @solidity memory-safe-assembly + assembly { + z := mul(x, y) + } + assertEq(FixedPointMathLib.rawMul(x, y), z); + } + + function testRawMul(int256 x, int256 y) public { + int256 z; + /// @solidity memory-safe-assembly + assembly { + z := mul(x, y) + } + assertEq(FixedPointMathLib.rawMul(x, y), z); + } + + function testRawDiv(uint256 x, uint256 y) public { + uint256 z; + /// @solidity memory-safe-assembly + assembly { + z := div(x, y) + } + assertEq(FixedPointMathLib.rawDiv(x, y), z); + } + + function testRawSDiv(int256 x, int256 y) public { + int256 z; + /// @solidity memory-safe-assembly + assembly { + z := sdiv(x, y) + } + assertEq(FixedPointMathLib.rawSDiv(x, y), z); + } + + function testRawMod(uint256 x, uint256 y) public { + uint256 z; + /// @solidity memory-safe-assembly + assembly { + z := mod(x, y) + } + assertEq(FixedPointMathLib.rawMod(x, y), z); + } + + function testRawSMod(int256 x, int256 y) public { + int256 z; + /// @solidity memory-safe-assembly + assembly { + z := smod(x, y) + } + assertEq(FixedPointMathLib.rawSMod(x, y), z); + } + + function testRawAddMod(uint256 x, uint256 y, uint256 denominator) public { + uint256 z; + /// @solidity memory-safe-assembly + assembly { + z := addmod(x, y, denominator) + } + assertEq(FixedPointMathLib.rawAddMod(x, y, denominator), z); + } + + function testRawMulMod(uint256 x, uint256 y, uint256 denominator) public { + uint256 z; + /// @solidity memory-safe-assembly + assembly { + z := mulmod(x, y, denominator) + } + assertEq(FixedPointMathLib.rawMulMod(x, y, denominator), z); + } + + function testLog10() public { + assertEq(FixedPointMathLib.log10(0), 0); + assertEq(FixedPointMathLib.log10(1), 0); + assertEq(FixedPointMathLib.log10(type(uint256).max), 77); + unchecked { + for (uint256 i = 1; i <= 77; ++i) { + uint256 x = 10 ** i; + assertEq(FixedPointMathLib.log10(x), i); + assertEq(FixedPointMathLib.log10(x - 1), i - 1); + assertEq(FixedPointMathLib.log10(x + 1), i); + } + } + } + + function testLog10(uint256 i, uint256 j) public { + i = _bound(i, 0, 77); + uint256 low = 10 ** i; + uint256 high = i == 77 ? type(uint256).max : (10 ** (i + 1)) - 1; + uint256 x = _bound(j, low, high); + assertEq(FixedPointMathLib.log10(x), i); + } + + function testLog10Up() public { + assertEq(FixedPointMathLib.log10Up(0), 0); + assertEq(FixedPointMathLib.log10Up(1), 0); + assertEq(FixedPointMathLib.log10Up(9), 1); + assertEq(FixedPointMathLib.log10Up(10), 1); + assertEq(FixedPointMathLib.log10Up(99), 2); + assertEq(FixedPointMathLib.log10Up(100), 2); + assertEq(FixedPointMathLib.log10Up(999), 3); + assertEq(FixedPointMathLib.log10Up(1000), 3); + assertEq(FixedPointMathLib.log10Up(10 ** 77), 77); + assertEq(FixedPointMathLib.log10Up(10 ** 77 + 1), 78); + assertEq(FixedPointMathLib.log10Up(type(uint256).max), 78); + } + + function testLog256() public { + assertEq(FixedPointMathLib.log256(0), 0); + assertEq(FixedPointMathLib.log256(1), 0); + assertEq(FixedPointMathLib.log256(256), 1); + assertEq(FixedPointMathLib.log256(type(uint256).max), 31); + unchecked { + for (uint256 i = 1; i <= 31; ++i) { + uint256 x = 256 ** i; + assertEq(FixedPointMathLib.log256(x), i); + assertEq(FixedPointMathLib.log256(x - 1), i - 1); + assertEq(FixedPointMathLib.log256(x + 1), i); + } + } + } + + function testLog256(uint256 i, uint256 j) public { + i = _bound(i, 0, 31); + uint256 low = 256 ** i; + uint256 high = i == 31 ? type(uint256).max : (256 ** (i + 1)) - 1; + uint256 x = _bound(j, low, high); + assertEq(FixedPointMathLib.log256(x), i); + } + + function testLog256Up() public { + assertEq(FixedPointMathLib.log256Up(0), 0); + assertEq(FixedPointMathLib.log256Up(0x01), 0); + assertEq(FixedPointMathLib.log256Up(0x02), 1); + assertEq(FixedPointMathLib.log256Up(0xff), 1); + assertEq(FixedPointMathLib.log256Up(0x0100), 1); + assertEq(FixedPointMathLib.log256Up(0x0101), 2); + assertEq(FixedPointMathLib.log256Up(0xffff), 2); + assertEq(FixedPointMathLib.log256Up(0x010000), 2); + assertEq(FixedPointMathLib.log256Up(0x010001), 3); + assertEq(FixedPointMathLib.log256Up(type(uint256).max - 1), 32); + assertEq(FixedPointMathLib.log256Up(type(uint256).max), 32); + } + + function testSci() public { + _testSci(0, 0, 0); + _testSci(1, 1, 0); + _testSci(13, 13, 0); + _testSci(130, 13, 1); + _testSci(1300, 13, 2); + unchecked { + uint256 a = 103; + uint256 exponent = 0; + uint256 m = 1; + uint256 n = 78 - FixedPointMathLib.log10Up(a); + for (uint256 i; i < n; ++i) { + _testSci(a * m, a, exponent); + exponent += 1; + m *= 10; + } + } + _testSci(10 ** 77, 1, 77); + _testSci(2 * (10 ** 76), 2, 76); + _testSci(9 * (10 ** 76), 9, 76); + unchecked { + for (uint256 i; i < 32; ++i) { + testSci(11 + i * i * 100); + } + for (uint256 i; i < 500; ++i) { + _testSci(0, 0, 0); + } + } + unchecked { + uint256 x = 30000000000000000000000000000000000000000000000001; + _testSci(x, x, 0); + } + } + + function testSci(uint256 a) public { + unchecked { + while (a % 10 == 0) a = _random(); + uint256 exponent = 0; + uint256 m = 1; + uint256 n = 78 - FixedPointMathLib.log10Up(a); + for (uint256 i; i < n; ++i) { + _testSci(a * m, a, exponent); + uint256 x = a * 10 ** exponent; + assertEq(x, a * m); + exponent += 1; + m *= 10; + } + } + } + + function testSci2(uint256 x) public { + unchecked { + (uint256 mantissa, uint256 exponent) = FixedPointMathLib.sci(x); + assertEq(x % 10 ** exponent, 0); + if (x != 0) { + assertTrue(x % 10 ** (exponent + 1) > 0); + assertTrue(mantissa % 10 != 0); + } else { + assertEq(mantissa, 0); + assertEq(exponent, 0); + } + } + } + + function _testSci(uint256 x, uint256 expectedMantissa, uint256 expectedExponent) internal { + (uint256 mantissa, uint256 exponent) = FixedPointMathLib.sci(x); + assertEq(mantissa, expectedMantissa); + assertEq(exponent, expectedExponent); + } + + function unpackSci(uint256 packed) public pure returns (uint256) { + return FixedPointMathLib.unpackSci(packed); + } + + function packSci(uint256 x) public pure returns (uint256) { + return FixedPointMathLib.packSci(x); + } + + function testPackUnpackSci(uint256) public { + unchecked { + uint256 x = (_random() & 0x1) * 10 ** (_random() % 70); + uint8 packed = uint8(FixedPointMathLib.packSci(x)); + uint256 unpacked = FixedPointMathLib.unpackSci(packed); + assertEq(unpacked, x); + } + unchecked { + uint256 x = (_random() & 0x1ff) * 10 ** (_random() % 70); + uint16 packed = uint16(FixedPointMathLib.packSci(x)); + uint256 unpacked = FixedPointMathLib.unpackSci(packed); + assertEq(unpacked, x); + } + unchecked { + uint256 x = (_random() & 0x1ffffff) * 10 ** (_random() % 70); + uint32 packed = uint32(FixedPointMathLib.packSci(x)); + uint256 unpacked = FixedPointMathLib.unpackSci(packed); + assertEq(unpacked, x); + } + unchecked { + uint256 x = (_random() & 0x1ffffffffffffff) * 10 ** (_random() % 60); + uint64 packed = uint64(FixedPointMathLib.packSci(x)); + uint256 unpacked = FixedPointMathLib.unpackSci(packed); + assertEq(unpacked, x); + } + unchecked { + uint256 x = (_random() * 10 ** (_random() % 78)) & ((1 << 249) - 1); + uint256 packed = FixedPointMathLib.packSci(x); + uint256 unpacked = FixedPointMathLib.unpackSci(packed); + assertEq(unpacked, x); + } + } + + function testPackUnpackSci() public { + uint256 mantissaSize = 249; + unchecked { + for (uint256 i; i <= mantissaSize; ++i) { + uint256 x = (1 << i) - 1; + uint256 packed = FixedPointMathLib.packSci(x); + uint256 unpacked = FixedPointMathLib.unpackSci(packed); + assertEq(unpacked, x); + } + } + unchecked { + uint256 x = (1 << (mantissaSize + 1)) - 1; + vm.expectRevert(FixedPointMathLib.MantissaOverflow.selector); + this.packSci(x); + } + } + + function testLerpUint(uint256 a, uint256 b, uint256 t, uint256 begin, uint256 end) public { + assertEq( + _lerpUintOriginal(a, b, t, begin, end), FixedPointMathLib.lerp(a, b, t, begin, end) + ); + } + + function testLerpInt(int256 a, int256 b, int256 t, int256 begin, int256 end) public { + assertEq(_lerpIntOriginal(a, b, t, begin, end), FixedPointMathLib.lerp(a, b, t, begin, end)); + } + + function testLerpUint() public { + uint256 a = 100; + uint256 b = 200; + + assertEq(FixedPointMathLib.lerp(a, b, 0, 5, 10), 100); + assertEq(FixedPointMathLib.lerp(a, b, 5, 5, 10), 100); + assertEq(FixedPointMathLib.lerp(a, b, 10, 5, 10), 200); + assertEq(FixedPointMathLib.lerp(a, b, 15, 5, 10), 200); + assertEq(FixedPointMathLib.lerp(a, b, 6, 5, 10), 120); + assertEq(FixedPointMathLib.lerp(a, b, 9, 5, 10), 180); + + assertEq(FixedPointMathLib.lerp(b, a, 0, 5, 10), 200); + assertEq(FixedPointMathLib.lerp(b, a, 5, 5, 10), 200); + assertEq(FixedPointMathLib.lerp(b, a, 10, 5, 10), 100); + assertEq(FixedPointMathLib.lerp(b, a, 15, 5, 10), 100); + assertEq(FixedPointMathLib.lerp(b, a, 6, 5, 10), 180); + assertEq(FixedPointMathLib.lerp(b, a, 9, 5, 10), 120); + + assertEq(FixedPointMathLib.lerp(b, a, 0, 10, 5), 100); + assertEq(FixedPointMathLib.lerp(b, a, 5, 10, 5), 100); + assertEq(FixedPointMathLib.lerp(b, a, 10, 10, 5), 200); + assertEq(FixedPointMathLib.lerp(b, a, 15, 10, 5), 200); + assertEq(FixedPointMathLib.lerp(b, a, 6, 10, 5), 120); + assertEq(FixedPointMathLib.lerp(b, a, 9, 10, 5), 180); + + assertEq(FixedPointMathLib.lerp(a, b, 0, 10, 5), 200); + assertEq(FixedPointMathLib.lerp(a, b, 5, 10, 5), 200); + assertEq(FixedPointMathLib.lerp(a, b, 10, 10, 5), 100); + assertEq(FixedPointMathLib.lerp(a, b, 15, 10, 5), 100); + assertEq(FixedPointMathLib.lerp(a, b, 6, 10, 5), 180); + assertEq(FixedPointMathLib.lerp(a, b, 9, 10, 5), 120); + } + + function testLerpInt() public { + int256 a = -50; + int256 b = 50; + + assertEq(FixedPointMathLib.lerp(a, b, 0, 5, 10), -50); + assertEq(FixedPointMathLib.lerp(a, b, 5, 5, 10), -50); + assertEq(FixedPointMathLib.lerp(a, b, 10, 5, 10), 50); + assertEq(FixedPointMathLib.lerp(a, b, 15, 5, 10), 50); + assertEq(FixedPointMathLib.lerp(a, b, 6, 5, 10), -30); + assertEq(FixedPointMathLib.lerp(a, b, 9, 5, 10), 30); + + assertEq(FixedPointMathLib.lerp(b, a, 0, 5, 10), 50); + assertEq(FixedPointMathLib.lerp(b, a, 5, 5, 10), 50); + assertEq(FixedPointMathLib.lerp(b, a, 10, 5, 10), -50); + assertEq(FixedPointMathLib.lerp(b, a, 15, 5, 10), -50); + assertEq(FixedPointMathLib.lerp(b, a, 6, 5, 10), 30); + assertEq(FixedPointMathLib.lerp(b, a, 9, 5, 10), -30); + + assertEq(FixedPointMathLib.lerp(b, a, 0, 10, 5), -50); + assertEq(FixedPointMathLib.lerp(b, a, 5, 10, 5), -50); + assertEq(FixedPointMathLib.lerp(b, a, 10, 10, 5), 50); + assertEq(FixedPointMathLib.lerp(b, a, 15, 10, 5), 50); + assertEq(FixedPointMathLib.lerp(b, a, 6, 10, 5), -30); + assertEq(FixedPointMathLib.lerp(b, a, 9, 10, 5), 30); + + assertEq(FixedPointMathLib.lerp(a, b, 0, 10, 5), 50); + assertEq(FixedPointMathLib.lerp(a, b, 5, 10, 5), 50); + assertEq(FixedPointMathLib.lerp(a, b, 10, 10, 5), -50); + assertEq(FixedPointMathLib.lerp(a, b, 15, 10, 5), -50); + assertEq(FixedPointMathLib.lerp(a, b, 6, 10, 5), 30); + assertEq(FixedPointMathLib.lerp(a, b, 9, 10, 5), -30); + } + + function _lerpUintOriginal(uint256 a, uint256 b, uint256 t, uint256 begin, uint256 end) + internal + pure + returns (uint256) + { + if (begin == end) return t <= begin ? a : b; + if (begin < end) { + if (t <= begin) return a; + if (t >= end) return b; + } + if (begin > end) { + if (t >= begin) return a; + if (t <= end) return b; + } + uint256 delta = FixedPointMathLib.fullMulDiv( + FixedPointMathLib.dist(a, b), + FixedPointMathLib.dist(t, begin), + FixedPointMathLib.dist(end, begin) + ); + if (b > a) { + uint256 result = a + delta; + assert(a <= result && result <= b); + return result; + } + if (b < a) { + uint256 result = a - delta; + assert(a >= result && result >= b); + return result; + } + return a; + } + + function _lerpIntOriginal(int256 a, int256 b, int256 t, int256 begin, int256 end) + internal + pure + returns (int256) + { + int256 result1 = _lerpIntOriginal1(a, b, t, begin, end); + int256 result2 = _lerpIntOriginal2(a, b, t, begin, end); + assert(result1 == result2); + return result2; + } + + function _lerpIntOriginal1(int256 a, int256 b, int256 t, int256 begin, int256 end) + internal + pure + returns (int256) + { + if (begin == end) return t <= begin ? a : b; + unchecked { + uint256 w = 1 << 255; + return int256( + _lerpUintOriginal( + uint256(a) + w, + uint256(b) + w, + uint256(t) + w, + uint256(begin) + w, + uint256(end) + w + ) + w + ); + } + } + + function _lerpIntOriginal2(int256 a, int256 b, int256 t, int256 begin, int256 end) + internal + pure + returns (int256) + { + if (begin == end) return t <= begin ? a : b; + if (begin < end) { + if (t <= begin) return a; + if (t >= end) return b; + } + if (begin > end) { + if (t >= begin) return a; + if (t <= end) return b; + } + uint256 delta = FixedPointMathLib.fullMulDiv( + FixedPointMathLib.dist(a, b), + FixedPointMathLib.dist(t, begin), + FixedPointMathLib.dist(end, begin) + ); + unchecked { + if (b > a) { + int256 result = int256(uint256(a) + delta); + assert(a <= result && result <= b); + return result; + } + if (b < a) { + int256 result = int256(uint256(a) - delta); + assert(a >= result && result >= b); + return result; + } + } + return a; + } + + function testCoalesce(uint256 x, uint256 y) public { + assertEq(x == 0 ? y : x, FixedPointMathLib.coalesce(x, y)); + } + + function testCoalesce(address x, address y) public { + assertEq(x == address(0) ? y : x, FixedPointMathLib.coalesce(x, y)); + } + + function testCoalesce(bytes32 x, bytes32 y) public { + assertEq(x == bytes32(0) ? y : x, FixedPointMathLib.coalesce(x, y)); + } + + function testTernary(bool condition, uint256 x, uint256 y) public { + assertEq(condition ? x : y, FixedPointMathLib.ternary(condition, x, y)); + } + + function testTernary(bool condition, bytes32 x, bytes32 y) public { + assertEq(condition ? x : y, FixedPointMathLib.ternary(condition, x, y)); + } + + function testTernary(bool condition, address x, address y) public { + assertEq(condition ? x : y, FixedPointMathLib.ternary(condition, x, y)); + } + + function testIsEven(uint256 x) public { + assertEq(FixedPointMathLib.isEven(x), x % 2 == 0); + } + + function testFullMulEqEquivalence(uint256 a, uint256 b, uint256 x, uint256 y) public { + assertEq(_fullMulEqOriginal(a, b, x, y), FixedPointMathLib.fullMulEq(a, b, x, y)); + } + + function _fullMulEqOriginal(uint256 a, uint256 b, uint256 x, uint256 y) + internal + pure + returns (bool result) + { + /// @solidity memory-safe-assembly + assembly { + let xy := mul(x, y) + let z := mulmod(x, y, not(0)) + let ab := mul(a, b) + let c := mulmod(a, b, not(0)) + result := and(eq(xy, ab), eq(sub(z, add(xy, lt(z, xy))), sub(c, add(ab, lt(c, ab))))) + } + } + + function testInvMod(uint256 a, uint256 p) public { + uint256 x = FixedPointMathLib.invMod(a, p); + if (x != 0) { + assertEq(mulmod(a, x, p), 1); + } + } + + function testInvMod() public { + uint256 a = 0xe1b81abec8db239a5c843eff0a1c4472b02982433bb3f538d4e20eb8463330dc; + uint256 n = 0x4b4ecedb4964a40fe416b16c7bd8b46092040ec42ef0aa69e59f09872f105cf3; + uint256 x = 0x164a3ce484b95d23ce8552368f477627a85a1fce9882c3011eb38eda8bcc0dd2; + assertEq(FixedPointMathLib.invMod(a, n), x); + assertEq(FixedPointMathLib.invMod(a, 0), 0); + } + + function testSaturatingAdd(uint256 x, uint256 y) public view { + bytes memory data = abi.encodeWithSignature("add(uint256,uint256)", x, y); + (bool success,) = address(this).staticcall(data); + uint256 expected = !success ? type(uint256).max : x + y; + assert(FixedPointMathLib.saturatingAdd(x, y) == expected); + } + + function testSaturatingAdd() public view { + testSaturatingAdd(123, 456); + } + + function check_SaturatingAddEquivalence(uint256 x, uint256 y) public view { + testSaturatingAdd(x, y); + } + + function add(uint256 x, uint256 y) public pure returns (uint256) { + return x + y; + } + + function testSaturatingMul(uint256 x, uint256 y) public view { + bytes memory data = abi.encodeWithSignature("mul(uint256,uint256)", x, y); + (bool success,) = address(this).staticcall(data); + uint256 expected = !success ? type(uint256).max : x * y; + assert(FixedPointMathLib.saturatingMul(x, y) == expected); + } + + function check_SaturatingMulEquivalence(uint256 x, uint256 y) public view { + testSaturatingMul(x, y); + } + + function testSaturatingMul() public view { + testSaturatingMul(123, 456); + } + + function mul(uint256 x, uint256 y) public pure returns (uint256) { + return x * y; + } + + function testMulSqrtSmall(uint256 x, uint256 y) public { + x = _bound(x, 0, 2 ** 128 - 1); + y = _bound(y, 0, 2 ** 128 - 1); + assertEq(FixedPointMathLib.mulSqrt(x, y), FixedPointMathLib.sqrt(x * y)); + } + + function testMulSqrt(uint256 x, uint256 y) public { + if (x == y) { + assertEq(FixedPointMathLib.mulSqrt(x, y), x); + return; + } + uint256 z = FixedPointMathLib.mulSqrt(x, y); + + emit LogUint("z", z); + (uint256 p0, uint256 p1) = _fullMul(x, y); + (uint256 z0, uint256 z1) = _fullMul(z, z); + + if (z == type(uint256).max) return; + (uint256 zp0, uint256 zp1) = _fullMul(z + 1, z + 1); + + assertTrue((z1 < p1) || (z1 == p1 && z0 <= p0)); + assertTrue((p1 < zp1) || (p1 == zp1 && p0 < zp0)); + } + + function _fullMul(uint256 x, uint256 y) internal pure returns (uint256 p0, uint256 p1) { + /// @solidity memory-safe-assembly + assembly { + p0 := mul(x, y) + let mm := mulmod(x, y, not(0)) + p1 := sub(mm, add(p0, lt(mm, p0))) + } + } + + function _testMulSqrt(uint256 x, uint256 y, uint256 z) public { + assertEq(FixedPointMathLib.mulSqrt(x, y), z); + assertEq(FixedPointMathLib.mulSqrt(y, x), z); + } + + function testMulSqrtDifferential(uint256 x, uint256 y) public { + assertEq(FixedPointMathLib.mulSqrt(x, y), _mulSqrtOriginal(x, y)); + } + + function _mulSqrtOriginal(uint256 x, uint256 y) internal pure returns (uint256 z) { + if (x == 0 || y == 0) return 0; + if (x == y) return x; + uint256 p = FixedPointMathLib.rawMul(x, y); + if (y == p / x) return FixedPointMathLib.sqrt(p); + uint256 sqrtX = FixedPointMathLib.sqrt(x); + uint256 sqrtY = FixedPointMathLib.sqrt(y); + for (z = FixedPointMathLib.saturatingMul(sqrtX + 1, sqrtY + 1);;) { + uint256 zNext = FixedPointMathLib.fullMulDivUnchecked(x, y, z); + zNext = FixedPointMathLib.avg(z, zNext); + if (zNext >= z) break; + z = zNext; + } + } + + function testMulSqrt() public { + // forgefmt: disable-start + this._testMulSqrt(1,40899,202); + this._testMulSqrt(2,126475466778170,15904431); + this._testMulSqrt(1,7531755327269063297785,86785686188); + this._testMulSqrt(4,1145375910940206129316611151348,2140444730368159); + this._testMulSqrt(1,6000249325576079771745702731971550324701397394,77461276297102668974080); + this._testMulSqrt(19990973968927499539515487029982657202480,351588509,2651150077078442553440984); + this._testMulSqrt(884589532366,8944067071329830864412327072945291263924221217993,2812797203510731992653280306277); + this._testMulSqrt(6,231793030923641024945144526163927788762812410599871060107884767317,1179304110711841006457676781313383); + this._testMulSqrt(7,25918817354934577870911410398594504620656949910895919963803454597218,13469659293558320924081748803885430); + this._testMulSqrt(2,6184767715379885918995468503098991544649672240672289523626396828206377,111218413182169488449746241587513722); + this._testMulSqrt(11262102866604455182725961449733527925032397745743648245810757,2289531243,160576886179084819940315039841174360); + this._testMulSqrt(2,386128680208745785717489332755004235996307094856550925460526784315796445,878781747885953518587400193470685282); + this._testMulSqrt(20,133823397008372075942399557492827432876972721009673572412945073431794356,1635991424234076331806369976651119304); + this._testMulSqrt(64385011511148092020199840672969561808819251747679314637,16788189010063055495896874,32877161414335023454300652451233783772805); + this._testMulSqrt(307695058396291282867900569126929438472472421745,16650777576165153239643978609535636374,2263484477225720726470604509270073120374679); + this._testMulSqrt(37369048817028070,403547546041382623075657545097967233546021171804178222743888832459267032,122801416718262293521136665081803979269652981); + this._testMulSqrt(108757921013865829718776238158835403990222487425921424856119171941423640,311530010377180825,184068889989727037643726419490138992138431758); + this._testMulSqrt(71968390915585988831780603371197224690006783200448692196810504,1317434458380206291313344071812603846743105529396590981616,307918232825485645925260268942424415111262027158036186068393); + this._testMulSqrt(169786846411291950176086783315992030432326301383306594102503280,262091314248283560844199184902391093238338514869222967307939442153535,210949419809600269188992711762466980237552872839406150493359215238); + this._testMulSqrt(13125530490447682132624267118512105994743022946653752643039816341671957523,2703992648396778482623576839914577810228120087337379417522074085943689092,5957460696679273501196497896950129901097275853419042552123499864438199223); + this._testMulSqrt(759343816572101418250144283506759951951139556413747077593352856047453741042,5783606897390246194179048291049046711192537808438654462819097925235107871790,2095649334935842670420229418803511179917932829424728325651967137561547960515); + this._testMulSqrt(1453693402301034209593542434097237131647588800659364354488100189009468111235,3021095182839377497836893600683197190212994171204262758821708252445961887922,2095649334935842670420229418803511179917932829424728325651967137561547960515); + this._testMulSqrt(1500322080289732537847477243966572315817180906638531006084846839970956040737,2927202227250387439989971689119336628619848415002314520835497068418776903816,2095649334935842670420229418803511179917932829424728325651967137561547960515); + this._testMulSqrt(1418188734037590650038894298784738194557827267133881612919761886151138174982,3096728968163296409290287978802863257249612545684811929660405453902456993799,2095649334935842670420229418803511179917932829424728325651967137561547960515); + this._testMulSqrt(72649803940522399886719930058523700039023197833404875144131115786317960957502,1058032812382543109188733542274803566994606781443813721786480120922367936989,8767318654082973239818507301503235653779978250722724160009440005060322575671); + this._testMulSqrt(5373922104039726654109199686576171393319670483688158333460332678277457737472,29698357225200387337129817970745776673739254860209898274723677280893657304883,12633157101301807356759851956784431867751428103301339039224348881394628761742); + this._testMulSqrt(4110435947028096136332171788401813256200194295940648314773165026203401589664,47629135145959642265403944627116611572745368779360562832098585565036412006720,13992015910147179362140946267238865150610331346635053034797342180359096262561); + this._testMulSqrt(6670439349065019066867111962665902305718250536829827331422008770563721526834,23925958995271068490107533294147958150213516645841822380228819548185161301331,12633157101301807356759851956784431867751428103301339039224348881394628761742); + this._testMulSqrt(2969132035655378982552797810065716750184480456087472143547576763384185040150,65937286344558903530415649738599306075672958462949062884739418323163356428267,13992015910147179362140946267238865150610331346635053034797342180359096262561); + this._testMulSqrt(7491404489992521390184991330802592986343520371455692604718960104437178752665,21303970244748005562096092471791804290084308713402287738808276131330039069727,12633157101301807356759851956784431867751428103301339039224348881394628761742); + this._testMulSqrt(6121895596657181220666267444651436648237333295237933576541838582595521000977,70855413278339511916656827078592824537105782268733407488004367896456997184904,20827132364970253561103779902092578934359252280464989168083476470668469620748); + this._testMulSqrt(9663464530001823655566072879594302677711586601817760096066022478590461523681,44887570208519155988166566051306580252890388816305191368753991821976222373501,20827132364970253561103779902092578934359252280464989168083476470668469620748); + this._testMulSqrt(3146233082020622571934575333002634745373987161876939823151669615808091349575,62225685168905914639685682949327855631601195004914029384687573254167511859580,13992015910147179362140946267238865150610331346635053034797342180359096262561); + this._testMulSqrt(81292148375414543538357432189784118864691517292040335173170009268459477601704,2516741501174680293198396542611706642053439931235618117323066049365884914216,14303542342233117118521903523784342299628960478384746102119577025303325649767); + this._testMulSqrt(6235952310590095577242208648287326629803669887249954646740971273060426215074,86267974483515850745154997335441073241510839437043584529161844505392388495406,23194028861118718470348997581404528819309525163067296635449034115712705381145); + this._testMulSqrt(8202270509291188368161098479682643484535231613691719026429761609950890177708,110093889479585348411203054124693950081761292952495296567025182505398163143046,30050288899302887082115267696735954136708432495128508248566789198018211109331); + this._testMulSqrt(58567595367007003434029003100140382325611053031291395486564269268008176012515,58567595367007003434029003100140382325611053031291395486564269268008176012528,58567595367007003434029003100140382325611053031291395486564269268008176012521); + this._testMulSqrt(66674750232480974592958260963213523118377591895163248013022969308057159666106,66674750232480974592958260963213523118377591895163248013022969308057159666106,66674750232480974592958260963213523118377591895163248013022969308057159666106); + this._testMulSqrt(33585912866580500017428740937261524277391765196007489833430561956127819834566,52199687981615271967060816285063413507671559301970565963089267667884242865491,41870922753304826754657295225348362266441633929084405750532911003697653673376); + this._testMulSqrt(67059371761480772870120378810526551624172903367552398549958050667166366320070,18204720352041367142096118905131984188387648432760494796813976946511020703864,34939907124967249547474326305667618125822338407481424538436068764138475863743); + this._testMulSqrt(91758599666022652235931878429035236115420549503042702161402892861112704213437,91758599666022652235931878429035236115420549503042702161402892861112704213446,91758599666022652235931878429035236115420549503042702161402892861112704213441); + this._testMulSqrt(57643576768106877450231061433780292981040727232162059949268652098179482795200,25879261867897264383063632807223059137462762609820894729277419678914890663158,38623480140765111324896240658186465312359836452286369356057144242747750076434); + this._testMulSqrt(97780904848435401688577228945316029068942540304391356583531299677110827351347,97780904848435401688577228945316029068942540304391356583531299677110827351347,97780904848435401688577228945316029068942540304391356583531299677110827351347); + this._testMulSqrt(39125312885387593452616334467597728868299242980364796136151236474971873470685,86828779128818620639494340617473793523235661436852771257233255355108198117255,58285531230925917668113509553487197292966405880544341575100636208705254025063); + this._testMulSqrt(28103463145585940293035771056326313713678024127581863885732629790555352610632,76815787111206973955699536219912347630679580756295209504314028436731758019833,46462776951867402066001606132202385212266391791890179377014155917534178988307); + this._testMulSqrt(55085887312063313304662947434367535525632525671759520071608379009942981796106,82800294118819938958395766683447025163837791408331540436336605895422829072207,67536121233270524981094905764087840435055016173869770891190734091029911998939); + this._testMulSqrt(11374112687571915868904337573234209429066724575146519193518714103220820309055,38136552227230499196160353868270595333171776294556651442476000096535825394060,20827132364970253561103779902092578934359252280464989168083476470668469620748); + this._testMulSqrt(58730317605090576282758883651000593881824017604589031401161423874294761362545,58730317605090576282758883651000593881824017604589031401161423874294761362556,58730317605090576282758883651000593881824017604589031401161423874294761362550); + this._testMulSqrt(50831499485199945339590926255329493296549952108872771415944026608406615545066,50831499485199945339590926255329493296549952108872771415944026608406615545075,50831499485199945339590926255329493296549952108872771415944026608406615545070); + this._testMulSqrt(85172087893546842302544308826796315864783084900992147451076334534702648635115,85172087893546842302544308826796315864783084900992147451076334534702648635115,85172087893546842302544308826796315864783084900992147451076334534702648635115); + this._testMulSqrt(65565054857983221818976561024613786630057794203730058177227059603745603013729,65565054857983221818976561024613786630057794203730058177227059603745603013729,65565054857983221818976561024613786630057794203730058177227059603745603013729); + this._testMulSqrt(55911495894251782213187662010802819104143247543461889936919543738942314665456,55911495894251782213187662010802819104143247543461889936919543738942314665463,55911495894251782213187662010802819104143247543461889936919543738942314665459); + this._testMulSqrt(17879363847304668652748901453932635872715480532463893033011272860213208357255,30088485217079270634851249038350032152097092409273038120371466325762898598320,23194028861118718470348997581404528819309525163067296635449034115712705381145); + this._testMulSqrt(22574079799238893389795996210248534007844658027018240720384507454263215868089,23831003504672027024172723977212295224054264531333207324229467512231849039503,23194028861118718470348997581404528819309525163067296635449034115712705381145); + this._testMulSqrt(10611306079283782718696192094919244168932152831659131747498031131196511452379,15040246427133913011360593741211490257502964477997080664156994032702222657211,12633157101301807356759851956784431867751428103301339039224348881394628761742); + this._testMulSqrt(73858585336675259861717749966041474360396794822352124567893338971277971476418,73858585336675259861717749966041474360396794822352124567893338971277971476418,73858585336675259861717749966041474360396794822352124567893338971277971476418); + this._testMulSqrt(93493926051444437853990951601184659120548710831844470529111733337613793165495,54214116350546943146180291574636539900604297425564498997024649792129389692208,71194737059858860760008503832934929635934877910339827282451164211383009505882); + this._testMulSqrt(31741633454414803277828892614935784705765176373701489896871054927885458857680,31741633454414803277828892614935784705765176373701489896871054927885458857683,31741633454414803277828892614935784705765176373701489896871054927885458857681); + this._testMulSqrt(73131310464141944405157343302384814531452681906375563388541324773631263794690,73131310464141944405157343302384814531452681906375563388541324773631263794693,73131310464141944405157343302384814531452681906375563388541324773631263794691); + this._testMulSqrt(65455015498287996254702939291233980776874205016778705952818114191323439048349,65455015498287996254702939291233980776874205016778705952818114191323439048349,65455015498287996254702939291233980776874205016778705952818114191323439048349); + this._testMulSqrt(81462032043753857067983762687246123130574773932537561797650535926862438796461,81462032043753857067983762687246123130574773932537561797650535926862438796478,81462032043753857067983762687246123130574773932537561797650535926862438796469); + this._testMulSqrt(78209457813515644067726265623499202575598482171839000555604704620897437284905,78209457813515644067726265623499202575598482171839000555604704620897437284905,78209457813515644067726265623499202575598482171839000555604704620897437284905); + this._testMulSqrt(13412474850080548643225312277495789264111309375181229577549786099110203380624,40109150684236126557038510591608000846411423768057235283250658763488731560550,23194028861118718470348997581404528819309525163067296635449034115712705381145); + this._testMulSqrt(40011146698470653129691243350367280704077684991355208240666470591824566065223,40011146698470653129691243350367280704077684991355208240666470591824566065236,40011146698470653129691243350367280704077684991355208240666470591824566065229); + this._testMulSqrt(84469863776974389618863256801434264209320466806351315058376170339221016552781,105252699476938925947685053655574147531645177588390239327939130772357535593314,94290408775102118898547330988195929704226272047925587806802926560630963930928); + this._testMulSqrt(30101390199343843269859768555851977933764901751609935209985867350357455919180,112858679561759692460068611865224624921408338260436569801949431998683320106122,58285531230925917668113509553487197292966405880544341575100636208705254025063); + this._testMulSqrt(49252281286569566201714256790755535986103544271522937010444395775245383022031,103313582627134147756455088718855511170155287731084491962378585746899181375663,71333229509639179283943164935234626333287573837435931792829607795291911850054); + this._testMulSqrt(103203975778750389840486623148704163858899099747818966282132716369708045200084,103203975778750389840486623148704163858899099747818966282132716369708045200084,103203975778750389840486623148704163858899099747818966282132716369708045200084); + this._testMulSqrt(107596843987355231847546681774981365988735519046298392541656481902159008087733,107596843987355231847546681774981365988735519046298392541656481902159008087733,107596843987355231847546681774981365988735519046298392541656481902159008087733); + this._testMulSqrt(102830484155533021557704227144831932637592583365851544616380718131012301176108,102830484155533021557704227144831932637592583365851544616380718131012301176108,102830484155533021557704227144831932637592583365851544616380718131012301176108); + this._testMulSqrt(115780671983614014435071450400947059939097134791386840528286481782155947852587,115780671983614014435071450400947059939097134791386840528286481782155947852608,115780671983614014435071450400947059939097134791386840528286481782155947852597); + this._testMulSqrt(102599946188735338018377241438053351371953655256423785960693281164040632229692,113977391922349753717143966833940674036437676311140700292665530749893373322354,108139143134969783777522484667256465358316928947625235238969806848330075092141); + this._testMulSqrt(104750355640940008131284436009813329129514270958235948287118057008845178828468,111637561575924997019627058419809811559908173308145846004665185899087410950352,108139143134969783777522484667256465358316928947625235238969806848330075092141); + // forgefmt: disable-end + } +} diff --git a/test/clz/LibBit.t.sol b/test/clz/LibBit.t.sol new file mode 100644 index 000000000..7283ba331 --- /dev/null +++ b/test/clz/LibBit.t.sol @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "../utils/SoladyTest.sol"; +import {LibBit} from "../../src/utils/clz/LibBit.sol"; + +contract LibBitWithCLZTest is SoladyTest { + function testFLS() public { + assertEq(LibBit.fls(0xff << 3), 10); + for (uint256 i = 1; i < 255; i++) { + assertEq(LibBit.fls((1 << i) - 1), i - 1); + assertEq(LibBit.fls((1 << i)), i); + assertEq(LibBit.fls((1 << i) + 1), i); + } + assertEq(LibBit.fls(0), 256); + } + + function testCLZ() public { + for (uint256 i = 1; i < 255; i++) { + assertEq(LibBit.clz_((1 << i) - 1), 255 - (i - 1)); + assertEq(LibBit.clz_((1 << i)), 255 - i); + assertEq(LibBit.clz_((1 << i) + 1), 255 - i); + } + assertEq(LibBit.clz_(0), 256); + } + + function testFFS() public { + assertEq(LibBit.ffs(0xff << 3), 3); + uint256 brutalizer = uint256(keccak256(abi.encode(address(this), block.timestamp))); + for (uint256 i = 0; i < 256; i++) { + assertEq(LibBit.ffs(1 << i), i); + assertEq(LibBit.ffs(type(uint256).max << i), i); + assertEq(LibBit.ffs((brutalizer | 1) << i), i); + } + assertEq(LibBit.ffs(0), 256); + } + + function testPopCount(uint256 x) public { + uint256 c; + unchecked { + for (uint256 t = x; t != 0; c++) { + t &= t - 1; + } + } + assertEq(LibBit.popCount(x), c); + } + + function testPopCount() public { + unchecked { + for (uint256 i = 1; i < 256; ++i) { + assertEq(LibBit.popCount((1 << i) | 1), 2); + } + } + } + + function testIsPo2(uint8 a, uint8 b) public { + unchecked { + uint256 x = (1 << uint256(a)) | (1 << uint256(b)); + if (a == b) { + assertTrue(LibBit.isPo2(x)); + } else { + assertFalse(LibBit.isPo2(x)); + } + } + } + + function testIsPo2(uint256 x) public { + uint256 c; + unchecked { + for (uint256 t = x; t != 0; c++) { + t &= t - 1; + } + } + assertEq(LibBit.isPo2(x), c == 1); + } + + function testIsPo2() public { + assertFalse(LibBit.isPo2(0)); + assertFalse(LibBit.isPo2(type(uint256).max)); + unchecked { + for (uint256 i; i < 256; ++i) { + uint256 x = 1 << i; + assertTrue(LibBit.isPo2(x)); + assertFalse(LibBit.isPo2(~x)); + } + } + } + + function testAnd(bool v, bool w, bool x, bool y) public { + assertEq(LibBit.and(x, y), x && y); + assertEq(LibBit.and(w, x, y), w && x && y); + assertEq(LibBit.and(v, w, x, y), v && w && x && y); + assertEq(LibBit.rawAnd(x, y), LibBit.and(x, y)); + } + + function testAnd() public { + unchecked { + for (uint256 t; t != 100; ++t) { + uint256 i = _random(); + uint256 j = _random(); + uint256 k = _random(); + bool a = i < j; + bool b = j < k; + assertEq(LibBit.and(a, b), i < j && j < k); + } + } + } + + function testOr(bool v, bool w, bool x, bool y) public { + assertEq(LibBit.or(x, y), x || y); + assertEq(LibBit.or(w, x, y), w || x || y); + assertEq(LibBit.or(v, w, x, y), v || w || x || y); + assertEq(LibBit.rawOr(x, y), LibBit.or(x, y)); + } + + function testOr() public { + unchecked { + for (uint256 t; t != 100; ++t) { + uint256 i = _random(); + uint256 j = _random(); + uint256 k = _random(); + bool a = i < j; + bool b = j < k; + assertEq(LibBit.or(a, b), i < j || j < k); + } + } + } + + function testAutoClean(uint256 x, uint256 y) public { + bool xCasted; + bool yCasted; + /// @solidity memory-safe-assembly + assembly { + xCasted := x + yCasted := y + } + bool result = LibBit.and(true, LibBit.or(xCasted, yCasted)); + assertEq(result, xCasted || yCasted); + } + + function testReturnsBool() public { + bool result; + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0x40b98a2f) + mstore(0x20, 123) + pop(staticcall(gas(), address(), 0x1c, 0x24, 0x00, 0x20)) + result := eq(mload(0x00), 1) + } + assertTrue(result); + } + + function returnsBool(uint256 i) public pure returns (bool b) { + /// @solidity memory-safe-assembly + assembly { + b := i + } + } + + function testPassInBool() public { + bool result; + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0x59a3028a) + mstore(0x20, 1) + pop(staticcall(gas(), address(), 0x1c, 0x24, 0x00, 0x20)) + result := eq(mload(0x00), 1) + } + assertTrue(result); + } + + function acceptsBool(bool) public pure returns (bool) { + return true; + } + + function testBoolToUint(bool b) public { + uint256 z; + /// @solidity memory-safe-assembly + assembly { + z := b + } + assertEq(LibBit.toUint(b), z); + assertEq(LibBit.rawToUint(b), z); + } + + function testReverseBits() public { + uint256 x = 0xf2e857a5b8e3fec9f9c60ae71ba63813c96741bc837169cf0f29f113ede5956f; + uint256 r = 0xf6a9a7b7c88f94f0f3968ec13d82e693c81c65d8e750639f937fc71da5ea174f; + assertEq(LibBit.reverseBits(x), r); + unchecked { + for (uint256 i; i < 256; ++i) { + assertEq(LibBit.reverseBits(1 << i), (1 << 255) >> i); + } + } + } + + function testReverseBitsDifferential(uint256 x) public { + assertEq(LibBit.reverseBits(x), _reverseBitsOriginal(x)); + } + + function _reverseBitsOriginal(uint256 x) internal pure returns (uint256 r) { + unchecked { + for (uint256 i; i != 256; ++i) { + r = (r << 1) | ((x >> i) & 1); + } + } + } + + function testReverseBytes() public { + uint256 x = 0x112233445566778899aa112233445566778899aa112233445566778899aa1122; + uint256 r = 0x2211aa998877665544332211aa998877665544332211aa998877665544332211; + assertEq(LibBit.reverseBytes(x), r); + unchecked { + for (uint256 i; i < 256; i += 8) { + assertEq(LibBit.reverseBytes(0xff << i), (0xff << 248) >> i); + } + } + } + + function testReverseBytesDifferential(uint256 x) public { + assertEq(LibBit.reverseBytes(x), _reverseBytesOriginal(x)); + } + + function _reverseBytesOriginal(uint256 x) internal pure returns (uint256 r) { + /// @solidity memory-safe-assembly + assembly { + for { let i := 0 } lt(i, 32) { i := add(i, 1) } { mstore8(i, byte(sub(31, i), x)) } + r := mload(0x00) + } + } + + function testCommonNibblePrefix() public { + assertEq(LibBit.commonNibblePrefix(0x1, 0x2), 0); + assertEq(LibBit.commonNibblePrefix(0x1234abc, 0x1234bbb), 0x1234000); + assertEq(LibBit.commonNibblePrefix(0x1234abc, 0x1234abc), 0x1234abc); + } + + function testCommonNibblePrefixDifferential(uint256 x, uint256 y) public { + assertEq(LibBit.commonNibblePrefix(x, y), _commonNibblePrefixOriginal(x, y)); + } + + function _commonNibblePrefixOriginal(uint256 x, uint256 y) internal pure returns (uint256 z) { + uint256 m = 0xf000000000000000000000000000000000000000000000000000000000000000; + while (m != 0) { + if ((x & m) == (y & m)) z |= x & m; + else break; + m >>= 4; + } + } + + function testCommonBytePrefix() public { + assertEq(LibBit.commonBytePrefix(0xaabbcc, 0xaabbcc), 0xaabbcc); + assertEq(LibBit.commonBytePrefix(0xaabbcc, 0xaabbc0), 0xaabb00); + assertEq(LibBit.commonBytePrefix(0xaabbcc, 0xaab0c0), 0xaa0000); + assertEq(LibBit.commonBytePrefix(0xaabbcc, 0xa0b0c0), 0x000000); + } + + function testCommonBytePrefixDifferential(uint256 x, uint256 y) public { + assertEq(LibBit.commonBytePrefix(x, y), _commonBytePrefixOriginal(x, y)); + } + + function _commonBytePrefixOriginal(uint256 x, uint256 y) internal pure returns (uint256 z) { + uint256 m = 0xff00000000000000000000000000000000000000000000000000000000000000; + while (m != 0) { + if ((x & m) == (y & m)) z |= x & m; + else break; + m >>= 8; + } + } + + function testCommonBitPrefixDifferential(uint256 x, uint256 y) public { + assertEq(LibBit.commonBitPrefix(x, y), _commonBitPrefixOriginal(x, y)); + } + + function _commonBitPrefixOriginal(uint256 x, uint256 y) internal pure returns (uint256 z) { + uint256 m = 0x8000000000000000000000000000000000000000000000000000000000000000; + while (m != 0) { + if ((x & m) == (y & m)) z |= x & m; + else break; + m >>= 1; + } + } + + function testCountZeroBytesDifferential(uint256 x) public { + assertEq(LibBit.countZeroBytes(x), _countZeroBytesOriginal(x)); + } + + function testCountZeroBytes() public { + uint256 x = 0xff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff; + x |= uint256(uint160(address(this))) << 16; + assertEq(LibBit.countZeroBytes(x), 2); + } + + function _countZeroBytesOriginal(uint256 x) internal pure returns (uint256 c) { + unchecked { + for (uint256 i; i < 32; ++i) { + c += (x & (0xff << (i * 8))) == 0 ? 1 : 0; + } + return c; + } + } + + function testCountZeroBytesDifferential(bytes32) public { + testCountZeroBytesDifferential(_randomSmallBytes()); + } + + function testCountZeroBytesDifferential(bytes memory s) public { + assertEq(LibBit.countZeroBytes(s), _countZeroBytesOriginal(s)); + } + + function testCountZeroBytesCalldataDifferential(bytes32) public { + this.testCountZeroBytesCalldataDifferential(_randomSmallBytes()); + } + + function testCountZeroBytesCalldataDifferential(bytes calldata s) public { + assertEq(LibBit.countZeroBytesCalldata(s), _countZeroBytesOriginal(s)); + } + + function _countZeroBytesOriginal(bytes memory s) internal pure returns (uint256 c) { + unchecked { + for (uint256 i; i < s.length; ++i) { + c += uint8(s[i]) == 0 ? 1 : 0; + } + return c; + } + } + + function _randomSmallBytes() internal returns (bytes memory) { + uint256 n = _bound(_random(), 0, 100); + uint256 r = _randomUniform(); + uint256 x = r >> 248; + bytes memory s = new bytes(n); + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, r) + for { let i := 0 } lt(i, n) { i := add(i, 1) } { + if and(1, shr(i, r)) { mstore8(add(add(s, 0x20), i), x) } + } + } + return s; + } + + function testToNibblesGas() public { + bytes memory s = hex"123456789abcdef123456789abcdef123456789abcdef123456789abcdef"; + bytes memory expected = + hex"0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f"; + assertEq(LibBit.toNibbles(s), expected); + + bytes memory s1 = + hex"6080604052600436106100b15760003560e01c8063545e7c611161006957806399a88ec41161004e57806399a88ec41461019d578063a97b90d5146101b0578063db4c545e146101c357600080fd5b8063545e7c61146101775780639623609d1461018a57600080fd5b80633729f9221161009a5780633729f922146101315780634314f120146101445780635414dff01461015757600080fd5b80631acfd02a146100b65780632abbef15146100d8575b600080fd5b3480156100c257600080fd5b506100d66100d1366004610604565b6101e6565b005b3480156100e457600080fd5b506101076100f3366004610637565b30600c908152600091909152602090205490565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b61010761013f366004610652565b610237565b6101076101523660046106d7565b61024e565b34801561016357600080fd5b50610107610172366004610738565b610267565b610107610185366004610604565b61029a565b6100d66101983660046106d7565b6102af565b6100d66101ab366004610604565b61035f565b6101076101be366004610751565b610370565b3480156101cf57600080fd5b506101d86103a9565b604051908152602001610128565b30600c52816000526020600c2033815414610209576382b429006000526004601cfd5b81905580827f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f600080a35050565b60006102468484843685610370565b949350505050565b600061025e8585838087876103c2565b95945050505050565b6000806102726103a9565b905060ff600053806035523060601b6001528260155260556000209150600060355250919050565b60006102a88383368461024e565b9392505050565b30600c5283600052336020600c2054146102d1576382b429006000526004601cfd5b6040518381527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc602082015281836040830137600080836040018334895af1610331573d610327576355299b496000526004601cfd5b3d6000803e3d6000fd5b5082847f5d611f318680d00598bb735d61bacf0c514c6b50e1e5ad30040a4df2b12791c7600080a350505050565b61036c82823660006102af565b5050565b60008360601c33148460601c151761039057632f6348366000526004601cfd5b61039f868686600187876103c2565b9695505050505050565b6000806103b461049c565b608960139091012092915050565b6000806103cd61049c565b90508480156103e757866089601384016000f592506103f3565b6089601383016000f092505b50816104075763301164256000526004601cfd5b8781527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc602082015282846040830137600080846040018334865af161045a573d6103275763301164256000526004601cfd5b30600c5281600052866020600c20558688837fc95935a66d15e0da5e412aca0ad27ae891d20b2fb91cf3994b6a3bf2b8178082600080a4509695505050505050565b6040513060701c801561054257666052573d6000fd607b8301527f3d356020355560408036111560525736038060403d373d3d355af43d6000803e60748301527f3735a920a3ca505d382bbc545af43d6000803e6052573d6000fd5b3d6000f35b60548301527f14605757363d3d37363d7f360894a13ba1a3210667c828492db98dca3e2076cc60348301523060148301526c607f3d8160093d39f33d3d337382525090565b66604c573d6000fd60758301527f3d3560203555604080361115604c5736038060403d373d3d355af43d6000803e606e8301527f3735a920a3ca505d382bbc545af43d6000803e604c573d6000fd5b3d6000f35b604e8301527f14605157363d3d37363d7f360894a13ba1a3210667c828492db98dca3e2076cc602e83015230600e8301526c60793d8160093d39f33d3d336d82525090565b803573ffffffffffffffffffffffffffffffffffffffff811681146105ff57600080fd5b919050565b6000806040838503121561061757600080fd5b610620836105db565b915061062e602084016105db565b90509250929050565b60006020828403121561064957600080fd5b6102a8826105db565b60008060006060848603121561066757600080fd5b610670846105db565b925061067e602085016105db565b9150604084013590509250925092565b60008083601f8401126106a057600080fd5b50813567ffffffffffffffff8111156106b857600080fd5b6020830191508360208285010111156106d057600080fd5b9250929050565b600080600080606085870312156106ed57600080fd5b6106f6856105db565b9350610704602086016105db565b9250604085013567ffffffffffffffff81111561072057600080fd5b61072c8782880161068e565b95989497509550505050565b60006020828403121561074a57600080fd5b5035919050565b60008060008060006080868803121561076957600080fd5b610772866105db565b9450610780602087016105db565b935060408601359250606086013567ffffffffffffffff8111156107a357600080fd5b6107af8882890161068e565b96999598509396509294939250505056fea26469706673582212200ac7c3ccbc2d311c48bf5465b021542e0e306fe3c462c060ba6a3d2f81ff6c5f64736f6c63430008130033"; + bytes memory expected1 = + hex"060008000600040005020600000403060100060100000b01050706000000030506000e00010c080006030504050e070c0601010106010000060905070800060309090a08080e0c04010106010000040e05070800060309090a08080e0c04010406010001090d0507080006030a09070b09000d050104060100010b000507080006030d0b040c0504050e0104060100010c0305070600000008000f0d050b080006030504050e070c06010104060100010707050708000603090602030600090d010406010001080a05070600000008000f0d050b08000603030702090f090202010106010000090a050708000603030702090f0902020104060100010301050708000603040301040f0102000104060100010404050708000603050401040d0f0f00010406010001050705070600000008000f0d050b08000603010a0c0f0d00020a0104060100000b06050708000603020a0b0b0e0f01050104060100000d080507050b0600000008000f0d050b030408000105060100000c0205070600000008000f0d050b0500060100000d06060100000d010306060000040601000600040506050b060100010e060506050b0000050b030408000105060100000e0405070600000008000f0d050b0500060100010007060100000f030306060000040601000603070506050b03000600000c0900080105020600000009010900090105020600020009000200050409000506050b06000400050107030f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f09000901010608010502060002000001050b06000400050108000901000309000f03050b06010001000706010001030f0306060000040601000605020506050b0601000203070506050b060100010007060100010502030606000004060100060d070506050b06010002040e0506050b03040800010506010001060305070600000008000f0d050b05000601000100070601000107020306060000040601000703080506050b0601000206070506050b0601000100070601000108050306060000040601000600040506050b06010002090a0506050b060100000d06060100010908030606000004060100060d070506050b060100020a0f0506050b060100000d06060100010a0b0306060000040601000600040506050b06010003050f0506050b060100010007060100010b0e0306060000040601000705010506050b0601000307000506050b030408000105060100010c0f05070600000008000f0d050b0500060100010d08060100030a090506050b0600040005010900080105020600020000010601000102080506050b03000600000c05020801060000000502060002000600000c020003030801050401040601000200090507060308020b0402090000060000000502060000040600010c0f0d050b08010900050508000802070f070e0604040d07090402020f01070c00010e040809040b050f040f0508080d0303010e0b0f0a02080605030d04020a0e0803020d0c05090e03080c090709080f0600000008000a03050005000506050b06000000060100020406080408040804030608050601000307000506050b0904090305000500050005000506050b0600000006010002050e080508050803080008070807060100030c020506050b09050904050005000500050005000506050b060000000800060100020702060100030a090506050b0900050006000f0f0600000005030800060003050502030006000600010b060000010502080206000105050206000505060000000200090105000600000006000305050205000901090005000506050b06000000060100020a08080308030306080406010002040e0506050b090309020500050005000506050b03000600000c050208030600000005020303060002000600000c020005040104060100020d010507060308020b0402090000060000000502060000040600010c0f0d050b060004000501080308010502070f0306000809040a01030b0a010a030201000606070c0802080409020d0b09080d0c0a030e020007060c0c030703050a0902000a030c0a0500050d0308020b0b0c0600020008020001050208010803060004000803000103070600000008000803060004000001080303040809050a0f010601000303010507030d0601000302070507060305050209090b0409060000000502060000040600010c0f0d050b030d060000000800030e030d060000000f0d050b050008020804070f050d0601010f0301080608000d00000509080b0b0703050d06010b0a0c0f000c0501040c060b05000e010e050a0d03000004000a040d0f020b01020709010c070600000008000a0305000500050005000506050b06010003060c08020802030606000000060100020a0f0506050b050005000506050b06000000080306000600010c03030104080406000600010c0105010706010003090005070603020f060304080306060000000502060000040600010c0f0d050b06010003090f0806080608060600000108070807060100030c020506050b090609050500050005000500050005000506050b060000000800060100030b0406010004090c0506050b0600080906000103090009010001020009020901050005000506050b060000000800060100030c0d06010004090c0506050b09000500080408000105060100030e0705070806060008090600010308040001060000000f0509020500060100030f030506050b060008090600010308030001060000000f0009020500050b05000801060100040007050706030300010106040205060000000502060000040600010c0f0d050b080708010502070f0306000809040a01030b0a010a030201000606070c0802080409020d0b09080d0c0a030e020007060c0c030703050a0902000a030c0a0500050d0308020b0b0c0600020008020001050208020804060004000803000103070600000008000804060004000001080303040806050a0f0106010004050a0507030d060100030207050706030300010106040205060000000502060000040600010c0f0d050b03000600000c050208010600000005020806060002000600000c02000505080608080803070f0c09050903050a06060d01050e000d0a050e0401020a0c0a000a0d02070a0e0809010d02000b020f0b09010c0f030909040b060a030b0f020b080107080008020600000008000a040500090609050500050005000500050005000506050b060004000501030006000700010c0800010506010005040205070606060005020507030d060000000f0d0600070b080300010502070f030d0305060002000305050506000400080003060101010506000502050703060003080006000400030d0307030d030d0305050a0f04030d060000000800030e06000704080300010502070f030703050a0902000a030c0a0500050d0308020b0b0c0504050a0f04030d060000000800030e060005020507030d060000000f0d050b030d060000000f03050b06000504080300010502070f01040600050705070306030d030d03070306030d070f0306000809040a01030b0a010a030201000606070c0802080409020d0b09080d0c0a030e020007060c0c06000304080300010502030006000104080300010502060c0600070f030d080106000009030d03090f03030d030d0303070308020502050009000506050b06060600040c0507030d060000000f0d06000705080300010502070f030d030506000200030505050600040008000306010101050600040c050703060003080006000400030d0307030d030d0305050a0f04030d060000000800030e0600060e080300010502070f030703050a0902000a030c0a0500050d0308020b0b0c0504050a0f04030d060000000800030e0600040c0507030d060000000f0d050b030d060000000f03050b0600040e080300010502070f01040600050105070306030d030d03070306030d070f0306000809040a01030b0a010a030201000606070c0802080409020d0b09080d0c0a030e020007060c0c0600020e08030001050203000600000e080300010502060c06000709030d080106000009030d03090f03030d030d0303060d08020502050009000506050b0800030507030f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0801010608010104060100050f0f05070600000008000f0d050b0901090005000506050b060000000800060004000803080500030102010506010006010705070600000008000f0d050b0601000602000803060100050d0b0506050b0901050006010006020e0600020008040001060100050d0b0506050b09000500090205000902090005000506050b06000000060002000802080400030102010506010006040905070600000008000f0d050b060100020a080802060100050d0b0506050b06000000080006000000060006000804080600030102010506010006060705070600000008000f0d050b0601000607000804060100050d0b0506050b0902050006010006070e0600020008050001060100050d0b0506050b090105000600040008040001030509000500090205000902050009020506050b06000000080008030600010f080400010102060100060a0005070600000008000f0d050b05000801030506070f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f080101010105060100060b0805070600000008000f0d050b060002000803000109010500080306000200080208050001000101010105060100060d0005070600000008000f0d050b090205000902090005000506050b0600000008000600000008000600060008050807000301020105060100060e0d05070600000008000f0d050b060100060f060805060100050d0b0506050b090305000601000700040600020008060001060100050d0b0506050b090205000600040008050001030506070f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f08010101010506010007020005070600000008000f0d050b06010007020c080708020808000106010006080e0506050b09050908090409070500090505000500050005000506050b06000000060002000802080400030102010506010007040a05070600000008000f0d050b050003050901090005000506050b06000000080006000000080006000000060008000806080800030102010506010007060905070600000008000f0d050b0601000707020806060100050d0b0506050b090405000601000708000600020008070001060100050d0b0506050b0903050006000400080600010305090205000600060008060001030506070f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f080101010105060100070a0305070600000008000f0d050b060100070a0f080808020809000106010006080e0506050b09060909090509080500090309060500090209040903090205000500050005060f0e0a02060406090700060607030508020201020200000a0c070c030c0c0b0c020d0301010c04080b0f050406050b0002010504020e000e0300060f0e030c0406020c0006000b0a060a030d020f08010f0f060c050f06040703060f060c0603040300000008010300000303"; + assertEq(LibBit.toNibbles(s1), expected1); + } + + function testToNibblesDifferential(uint256 r, bytes memory s) public { + if (r & 0x01 == 0) { + _brutalizeMemory(); + _misalignFreeMemoryPointer(); + } + bytes memory computed = LibBit.toNibbles(s); + _checkMemory(computed); + assertEq(computed, _toNibblesOriginal(s)); + } + + // Original code from Optimism (MIT-licensed): https://github.com/ethereum-optimism/optimism/blob/1bfc93f7c1fe1846217795a1f6051e1b0260f597/packages/contracts-bedrock/src/libraries/Bytes.sol#L94 + function _toNibblesOriginal(bytes memory input) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let bytesLength := mload(input) + let nibblesLength := shl(0x01, bytesLength) + mstore(0x40, add(result, and(not(0x1f), add(nibblesLength, 0x3f)))) + mstore(result, nibblesLength) + let bytesStart := add(input, 0x20) + let nibblesStart := add(result, 0x20) + for { let i := 0x00 } lt(i, bytesLength) { i := add(i, 0x01) } { + let offset := add(nibblesStart, shl(0x01, i)) + let b := byte(0x00, mload(add(bytesStart, i))) + mstore8(offset, shr(0x04, b)) + mstore8(add(offset, 0x01), and(b, 0x0F)) + } + } + } +} diff --git a/test/clz/LibZip.t.sol b/test/clz/LibZip.t.sol new file mode 100644 index 000000000..f6537ecbe --- /dev/null +++ b/test/clz/LibZip.t.sol @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "../utils/SoladyTest.sol"; +import {MockCd, MockCdFallbackDecompressor} from "../utils/mocks/MockCd.sol"; +import {LibClone} from "../../src/utils/LibClone.sol"; +import {ERC1967Factory} from "../../src/utils/ERC1967Factory.sol"; +import {LibString} from "../../src/utils/LibString.sol"; +import {DynamicBufferLib} from "../../src/utils/DynamicBufferLib.sol"; +import {LibBytes} from "../../src/utils/LibBytes.sol"; +import {LibZip} from "../../src/utils/clz/LibZip.sol"; + +contract LibZiWithCLZTest is SoladyTest { + using LibBytes for LibBytes.BytesStorage; + using DynamicBufferLib for DynamicBufferLib.DynamicBuffer; + + LibBytes.BytesStorage internal _bytesStorage; + + struct ABC { + uint256 a; + uint256 b; + uint256 c; + } + + struct ABCPacked { + uint32 a; + uint64 b; + uint32 c; + } + + uint256 internal constant _A = 0x112233; + uint256 internal constant _B = 0x0102030405060708; + uint256 internal constant _C = 0xf1f2f3; + + ABC internal _abc; + ABCPacked internal _abcPacked; + + bytes internal constant _CD_COMPRESS_INPUT = + hex"00000000000000000000000000000000000000000000000000000000000ae11c0000000000000000000000000000000000000000000000000000002b9cdca0ab0000000000000000000000000000000000003961790f8baa365051889e4c367d00000000000000000000000000000000000026d85539440bc844167ac0cc42320000000000000000000000000000000000000000000000007b55939986433925"; + + bytes internal constant _CD_COMPRESS_OUTPUT = + hex"ffe3f51e1c001a2b9cdca0ab00113961790f8baa365051889e4c367d001126d85539440bc844167ac0cc423200177b55939986433925"; + + function testABCCdCompressAndDecompressGas() public { + bytes memory data = abi.encode(_A, _B, _C); + assertEq(LibZip.cdDecompress(LibZip.cdCompress(data)).length, data.length); + } + + function testABCCdCompressAndDecompressOriginalGas() public { + bytes memory data = abi.encode(_A, _B, _C); + assertEq(_cdDecompressOriginal(_cdCompressOriginal(data)).length, data.length); + } + + function testCdDecompressGas() public { + bytes memory data = _CD_COMPRESS_OUTPUT; + assertGt(LibZip.cdDecompress(data).length, data.length); + } + + function testCdDecompressOriginalGas() public { + bytes memory data = _CD_COMPRESS_OUTPUT; + assertGt(_cdDecompressOriginal(data).length, data.length); + } + + function testCdCompressGas() public { + bytes memory data = _CD_COMPRESS_INPUT; + assertLt(LibZip.cdCompress(data).length, data.length); + } + + function testCdCompressOriginalGas() public { + bytes memory data = _CD_COMPRESS_INPUT; + assertLt(_cdCompressOriginal(data).length, data.length); + } + + function testABCStoreWithCdCompressGas() public { + _bytesStorage.set(LibZip.cdCompress(abi.encode(_A, _B, _C))); + } + + function testABCStoreWithCdCompressOriginalGas() public { + _bytesStorage.set(_cdCompressOriginal(abi.encode(_A, _B, _C))); + } + + function testABCStoreWithFlzCompressGas() public { + _bytesStorage.set(LibZip.flzCompress(abi.encode(_A, _B, _C))); + } + + function testABCStoreGas() public { + _abc.a = _A; + _abc.b = _B; + _abc.c = _C; + } + + function testABCStorePackedGas() public { + _abcPacked.a = uint32(_A); + _abcPacked.b = uint64(_B); + _abcPacked.c = uint32(_C); + } + + function testCdCompressDifferential(bytes32) public { + bytes memory data; + if (_randomChance(8)) data = _randomCd(); + uint256 t = _randomUniform() % 4; + for (uint256 i; i < t; ++i) { + if (_randomChance(2)) data = abi.encodePacked(data, _random()); + if (_randomChance(2)) data = abi.encodePacked(data, new bytes(_random() & 0x3ff)); + if (_randomChance(2)) data = abi.encodePacked(data, _random()); + if (_randomChance(32)) data = abi.encodePacked(data, _randomCd()); + } + testCdCompressDifferential(data); + } + + function testCdCompressDifferential(bytes memory data) public { + if (_randomChance(32)) _misalignFreeMemoryPointer(); + if (_randomChance(32)) _brutalizeMemory(); + bytes memory computed = LibZip.cdCompress(data); + assertEq(computed, _cdCompressOriginal(data)); + } + + function testCdDecompressDifferential(bytes32) public { + bytes memory data = _randomCd(); + if (_randomChance(2)) { + testCdDecompressDifferential(LibZip.cdCompress(data)); + } else { + testCdDecompressDifferential(data); + } + } + + function testCdDecompressDifferential(bytes memory data) public { + assertEq(LibZip.cdDecompress(data), _cdDecompressOriginal(data)); + } + + function _cdCompressOriginal(bytes memory data) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + function rle(v_, o_, d_) -> _o, _d { + mstore(o_, shl(240, or(and(0xff, add(d_, 0xff)), and(0x80, v_)))) + _o := add(o_, 2) + } + result := mload(0x40) + let o := add(result, 0x20) + let z := 0 // Number of consecutive 0x00. + let y := 0 // Number of consecutive 0xff. + for { let end := add(data, mload(data)) } iszero(eq(data, end)) {} { + data := add(data, 1) + let c := byte(31, mload(data)) + if iszero(c) { + if y { o, y := rle(0xff, o, y) } + z := add(z, 1) + if eq(z, 0x80) { o, z := rle(0x00, o, 0x80) } + continue + } + if eq(c, 0xff) { + if z { o, z := rle(0x00, o, z) } + y := add(y, 1) + if eq(y, 0x20) { o, y := rle(0xff, o, 0x20) } + continue + } + if y { o, y := rle(0xff, o, y) } + if z { o, z := rle(0x00, o, z) } + mstore8(o, c) + o := add(o, 1) + } + if y { o, y := rle(0xff, o, y) } + if z { o, z := rle(0x00, o, z) } + // Bitwise negate the first 4 bytes. + mstore(add(result, 4), not(mload(add(result, 4)))) + mstore(result, sub(o, add(result, 0x20))) // Store the length. + mstore(o, 0) // Zeroize the slot after the string. + mstore(0x40, add(o, 0x20)) // Allocate the memory. + } + } + + function _cdDecompressOriginal(bytes memory data) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + if mload(data) { + result := mload(0x40) + let o := add(result, 0x20) + let s := add(data, 4) + let v := mload(s) + let end := add(data, mload(data)) + mstore(s, not(v)) // Bitwise negate the first 4 bytes. + for {} lt(data, end) {} { + data := add(data, 1) + let c := byte(31, mload(data)) + if iszero(c) { + data := add(data, 1) + let d := byte(31, mload(data)) + // Fill with either 0xff or 0x00. + mstore(o, not(0)) + if iszero(gt(d, 0x7f)) { + calldatacopy(o, calldatasize(), add(d, 1)) + } + o := add(o, add(and(d, 0x7f), 1)) + continue + } + mstore8(o, c) + o := add(o, 1) + } + mstore(s, v) // Restore the first 4 bytes. + mstore(result, sub(o, add(result, 0x20))) // Store the length. + mstore(o, 0) // Zeroize the slot after the string. + mstore(0x40, add(o, 0x20)) // Allocate the memory. + } + } + } + + function testFlzCompressDecompress() public brutalizeMemory { + assertEq(LibZip.flzCompress(""), ""); + assertEq(LibZip.flzDecompress(""), ""); + bytes memory compressed = + hex"1f4e65772077617665206469676974616c206172742073686f756c64206e6f74201f6265206a756467656420736f6c656c79206f6e20616573746865746963206d65017269202301757420240e737420617320696d706f7274616e74202a1b666f7220697473206162696c69747920746f20646576656c6f702061200c406d03766973692051026f662020510320576972206801616e200301626f209315616c6c20637265617465206e6574776f726b20737069207201756140500e2e20416e6369656e7420477265656b60ba04697320686520bb01696e6051036869676820ad062072656761726420cd0463617573652095c089406f0220776820db03206d79742007016f6720a801686141040061401d0272656c2119066f6e2c206d6f72607660a0017761210a0866206c6966652e2054212f016768607c20ff026c6963205fa080414e40b80073402de003ae016365613741630373756666203320c8213406646973656e676141710366726f6d208b016520201102736f722097006f208e03666c6563205e21570b6f776e2070726f647563747340840065400d03626c656d406b056d6f6465726e417621120074201ba17a006d2066401f0272617420d521f00273656c200d805620118048036265617520e920e140c00561707065617220dc21fb41d5006d21310a69636820636f6e74726173208a02616273200920972142410a40450275732ce0081720852028016f798092056e616976652f40dd02756f752044057070726563696098c07a2072076578657274696e6760f3046e617475722172015265818b20dc02736c612025016279208e006d20400068212c60c701507221de2281026e7465409a201304207769746820a200652008426906696e766f6c7665808c20bf006e21940373657061811b4082227c2312e006dc620004616363656c20580074203e0165782062214b01697380bb2072026f207020a74009017569407a072c20656d626f647940c74080c2cf42ec01756e4183214c0273636921074204027261774029006f235905747275746873e2010c02636f6c42012139223c046f73706865410f025468724178205b2111016265603c830280ea0077228a02636869237e21c20172652144201d006f214e2047002ce0078140b620da2064c20f06696e64697669642377032065676f6175036163726922b6234060ee40d4e00b9a405c210121be205701756e2294208063fc23fa201583d040c0427523aa427021cf404f80ba20d04325016f6e21186297036966756c633c82fc21800061e1004f804e20a722f9052e2049206c6f20634488024e657443870d2053706972697475616c69747921"; + bytes memory decompressed = LibZip.flzDecompress(compressed); + bytes memory expectedDecompressed = + "New wave digital art should not be judged solely on aesthetic merit but just as importantly for its ability to develop a total vision of the Wired and above all create network spirituality. Ancient Greek art is held in the highest regard because it developed a whole mythology that shaped religion, morality and way of life. Thought is implicit in the art works of Ancient Greece but not sufficiently disengaged from the sensory to reflect its own products. The problem of modernity is the development of rational self reflection. The beauty of art appears in a form which contrasts abstract thought. Thus, abstract thought destroys the naive/sensuous appreciation of art in exerting its nature. Reality is slain by comprehension. Proper interaction with the wired involves the trance separation of real abstract thought and accelerates externalisation into pure intuition, embodying the network and unselfconsciously drawing out truths from the collective noosphere. Through this being on the wired we achieve a return to naive, unselfconscious interaction. The individual ego is sacrificed into the collective noosphere, uniting us under a totalising spirit. The best art in the wired is not only beautiful but produces a network spirituality. I long for Network Spirituality!"; + assertEq(decompressed, expectedDecompressed); + assertEq(LibZip.flzCompress(decompressed), compressed); + // Check backwards compatibility with older FastLZ releases. + compressed = + hex"1f4e65772077617665206469676974616c206172742073686f756c64206e6f74201f6265206a756467656420736f6c656c79206f6e20616573746865746963206d65017269202301757420240e737420617320696d706f7274616e74202a1b666f7220697473206162696c69747920746f20646576656c6f702061200c406d03766973692051026f662020510320576972206801616e200301626f209315616c6c20637265617465206e6574776f726b20737069207201756140500e2e20416e6369656e7420477265656b60ba04697320686520bb01696e6051036869676820ad062072656761726420cd0463617573652095c089406f0220776820db03206d79742007016f6720a801686141040061401d0272656c2119066f6e2c206d6f72607660a0017761210a0866206c6966652e2054212f016768607c20ff026c6963205fa080414e40b80073402de003ae016365613741630373756666203320c8213406646973656e676141710366726f6d208b016520201102736f722097006f208e03666c6563205e21570b6f776e2070726f647563747340840065400d03626c656d406b056d6f6465726e417621124044a17a006d2066401f0272617420d521f00273656c200d805620118048036265617520e920e140c00561707065617220dc21fb41d5006d21310a69636820636f6e74726173208a02616273200920972142410a40450275732ce0081720852028016f798092056e616976652f40dd02756f752044057070726563696098c07a2072076578657274696e6760f3046e617475722172015265818b20dc02736c612025016279208e006d20400068212c60c701507221de2281026e7465409a201304207769746820a200652008426906696e766f6c7665808c20bf006e21940373657061811b4082227c2312e006dc620004616363656c20580074203e0165782062214b01697380bb2072026f207020a74009017569407a072c20656d626f647940c74080c2cf42ec01756e4183214c0273636921074204027261774029006f235905747275746873e2010c02636f6c42012139223c046f73706865410f025468724178205b2111016265603c830280ea0077228a02636869237e21c20172652144201d006f214e2047002ce0078140b620da2064c20f06696e64697669642377032065676f6175036163726922b6234060ee40d4e00b9a405c210121be205701756e2294208063fc23fa201583d040c0427523aa427021cf404f80ba20d04325016f6e21186297036966756c633c82fc21800061e1004f804e20a722f9052e2049206c6f20634488004ea4400c53706972697475616c69747921"; + assertEq(LibZip.flzDecompress(compressed), decompressed); + } + + function _expandedData(bytes memory data) internal returns (bytes memory) { + unchecked { + DynamicBufferLib.DynamicBuffer memory buffer; + bytes memory r = abi.encode(_random()); + if (_randomChance(8)) { + r = abi.encodePacked(r, r, r, r); + r = bytes(LibString.slice(string(r), 0, _random() % r.length)); + } + uint256 n = _random() % 16 + 1; + uint256 c = _random(); + for (uint256 i; i < n; ++i) { + buffer.p((c >> i) & 1 == 0 ? r : data); + } + return buffer.data; + } + } + + function testFlzCompressDecompress(bytes memory data) public brutalizeMemory { + if (_randomChance(2)) { + data = _expandedData(data); + } + bytes32 dataHash = keccak256(data); + _misalignFreeMemoryPointer(); + bytes memory compressed = LibZip.flzCompress(data); + bytes32 compressedHash = keccak256(compressed); + _checkMemory(compressed); + _misalignFreeMemoryPointer(); + bytes memory decompressed = LibZip.flzDecompress(compressed); + _checkMemory(compressed); + _checkMemory(decompressed); + assertEq(decompressed, data); + assertEq(keccak256(data), dataHash); + assertEq(keccak256(compressed), compressedHash); + } + + function testFlzCompressDecompress2() public brutalizeMemory { + bytes memory data = + "______________________________________________________________e_______8______________________________________________________________________________________________________________________12_______8______________________________________________________________________________________________________________________16_______8______________________________________________________________________________________________________________________1a_______________________________________________________________2_____________________________________________732e2_5_726f2_49__73______________________________________________________________2_____________________________________________732e2_5_726f2_49__73______________________________________________________________2_____________________________________________732e2_5_726f2_49__73______________________________________________________________2_____________________________________________732e2_5_726f2_49__73"; + bytes32 dataHash = keccak256(data); + bytes memory expectedCompressed = + hex"015f5fe033010065a03c0038a007e06600013132a070e06f7f0036e0767f0061a07fe02f00c13fe01d000f37333265325f355f37323666325f34394011e01d39e00f00e0fd7fe02e7f04395f5f3733"; + bytes memory compressed = LibZip.flzCompress(data); + assertEq(compressed, expectedCompressed); + bytes32 compressedHash = keccak256(compressed); + _checkMemory(compressed); + bytes memory decompressed = LibZip.flzDecompress(compressed); + _checkMemory(compressed); + _checkMemory(decompressed); + assertEq(decompressed, data); + assertEq(keccak256(data), dataHash); + assertEq(keccak256(compressed), compressedHash); + } + + function testCdCompressDecompress(bytes memory data) public brutalizeMemory { + if (_randomChance(8)) { + data = _expandedData(data); + } + bytes32 dataHash = keccak256(data); + _misalignFreeMemoryPointer(); + bytes memory compressed = LibZip.cdCompress(data); + bytes32 compressedHash = keccak256(compressed); + _checkMemory(compressed); + _misalignFreeMemoryPointer(); + bytes memory decompressed = LibZip.cdDecompress(compressed); + _checkMemory(compressed); + _checkMemory(decompressed); + assertEq(decompressed, data); + assertEq(keccak256(data), dataHash); + assertEq(keccak256(compressed), compressedHash); + } + + function _randomCd() internal returns (bytes memory data) { + uint256 n = _randomChance(8) ? _random() % 2048 : _random() % 256; + data = new bytes(n); + if (_randomChance(32)) { + uint256 r = _randomUniform(); + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, r) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(0x20, xor("randomUniform", i)) + mstore(add(add(data, 0x20), i), keccak256(0x00, 0x40)) + } + } + } + if (_randomChance(4)) { + /// @solidity memory-safe-assembly + assembly { + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(data, 0x20), i), not(0)) + } + } + } + if (_randomChance(16)) { + uint256 r = _randomUniform(); + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, r) + mstore(0x20, xor("mode", not(0))) + let mode := and(1, keccak256(0x00, 0x40)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(0x20, xor("mode", i)) + mode := xor(mode, iszero(and(keccak256(0x00, 0x40), 7))) + mstore(add(add(data, 0x20), i), mul(iszero(mode), not(0))) + } + } + } + if (_randomChance(16)) { + uint256 r = _randomUniform(); + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, r) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(0x20, xor("0", i)) + let p := keccak256(0x00, 0x40) + if and(0x01, p) { mstore(add(add(data, 0x20), i), 0) } + } + } + } + if (_randomChance(16)) { + uint256 r = _randomUniform(); + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, r) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(0x20, xor("not(0)", i)) + let p := keccak256(0x00, 0x40) + if and(0x10, p) { mstore(add(add(data, 0x20), i), not(0)) } + } + } + } + if (_randomChance(2)) { + if (n != 0) { + uint256 m = _random() % 8; + for (uint256 j; j < m; ++j) { + data[_random() % n] = bytes1(uint8(_random())); + } + } + } + } + + function testCdCompressDecompress(uint256) public brutalizeMemory { + unchecked { + bytes memory data = _randomCd(); + bytes memory compressed = LibZip.cdCompress(data); + bytes memory decompressed = LibZip.cdDecompress(compressed); + assertEq(decompressed, data); + } + } + + function testCdFallbackDecompressor(bytes memory data) public { + bytes memory compressed = LibZip.cdCompress(data); + MockCdFallbackDecompressor decompressor = new MockCdFallbackDecompressor(); + (, bytes memory result) = address(decompressor).call(compressed); + assertEq(abi.decode(result, (bytes32)), keccak256(data)); + } + + function testCdFallbackDecompressor(uint256) public { + bytes memory data = _randomCd(); + bytes memory compressed = LibZip.cdCompress(data); + MockCdFallbackDecompressor decompressor = new MockCdFallbackDecompressor(); + (, bytes memory result) = address(decompressor).call(compressed); + assertEq(abi.decode(result, (bytes32)), keccak256(data)); + } + + function testCdCompress() public { + assertEq(LibZip.cdCompress(""), ""); + assertEq(LibZip.cdDecompress(""), ""); + bytes memory data = + hex"ac9650d80000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000005b70e00000000000000000000000000000000000000000000000000000dfc79825feb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000645c48a7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000005b70e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004449404b7c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f1cdf1a632eaaab40d1c263edf49faf749010a1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064df2ab5bb0000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c3160700000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f1cdf1a632eaaab40d1c263edf49faf749010a100000000000000000000000000000000000000000000000000000000"; + bytes memory expected = + hex"5369af27001e20001e04001e80001d0160001d0220001d02a0001ea40c49ccbe001c05b70e00190dfc79825feb005b645c48a7003a84fc6f7865001c05b70e002f008f000f008f003a4449404b7c002b1f1cdf1a632eaaab40d1c263edf49faf749010a1003a64df2ab5bb000b7f5c764cbc14f9669b88837ca1490cca17c31607002b1f1cdf1a632eaaab40d1c263edf49faf749010a1001b"; + assertEq(LibZip.cdCompress(data), expected); + } + + function testCdDecompressOnInvalidInput() public { + bytes memory data = hex"ffffffff00ff"; + bytes memory expected = + hex"0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + bytes memory decompressed = LibZip.cdDecompress(data); + assertEq(decompressed, expected); + } + + function testDecompressWontRevert(bytes memory data) public brutalizeMemory { + data = LibZip.cdDecompress(data); + bytes memory compressed = LibZip.cdCompress(data); + bytes memory decompressed = LibZip.cdDecompress(compressed); + assertEq(decompressed, data); + } + + function testCdFallback() public { + MockCd mockCd = new MockCd(); + _testCdFallback(mockCd); + // Check if it also works for clones. + mockCd = MockCd(payable(LibClone.clone(address(mockCd)))); + _testCdFallback(mockCd); + // Check if it also works for CWIA. + mockCd = MockCd(payable(LibClone.clone(address(mockCd), ""))); + _testCdFallback(mockCd); + // Check if it also works for ERC1967 proxies. + ERC1967Factory factory = new ERC1967Factory(); + mockCd = MockCd(payable(factory.deploy(address(mockCd), address(this)))); + _testCdFallback(mockCd); + } + + function _testCdFallback(MockCd mockCd) internal { + uint256[] memory numbers = new uint256[](100); + unchecked { + for (uint256 i; i < numbers.length; ++i) { + numbers[i] = i % 2 == 0 ? i : ~i; + } + } + assertEq(mockCd.numbersHash(), 0); + assertEq(mockCd.lastCallvalue(), 0); + assertEq(mockCd.lastCaller(), address(0)); + + uint256 callValue = 123 ether; + vm.deal(address(this), callValue * 2); + + (bool success, bytes memory result) = payable(mockCd).call{value: callValue}( + LibZip.cdCompress( + abi.encodeWithSignature("storeNumbersHash(uint256[],bool)", numbers, true) + ) + ); + + assertTrue(success); + bytes32 decodedNumbersHash = abi.decode(result, (bytes32)); + bytes32 expectedNumbersHash = keccak256(abi.encode(numbers)); + assertEq(decodedNumbersHash, expectedNumbersHash); + assertEq(mockCd.numbersHash(), expectedNumbersHash); + assertEq(mockCd.lastCallvalue(), callValue); + assertEq(mockCd.lastCaller(), address(this)); + assertEq(address(mockCd).balance, callValue); + + (success, result) = payable(mockCd).call{value: callValue}( + LibZip.cdCompress( + abi.encodeWithSignature("storeNumbersHash(uint256[],bool)", numbers, false) + ) + ); + + assertFalse(success); + assertEq(address(mockCd).balance, callValue); + assertEq(abi.encodeWithSelector(MockCd.Hash.selector, expectedNumbersHash), result); + assertEq(address(mockCd).balance, callValue); + + (success, result) = payable(mockCd).call{value: callValue}(""); + assertEq(address(mockCd).balance, callValue * 2); + assertTrue(success); + } + + function testCdFallback(bytes memory data, uint256 callValue) public brutalizeMemory { + MockCd mockCd = new MockCd(); + callValue = _bound(callValue, 0, 123 ether); + vm.deal(address(this), callValue * 2); + if (_randomChance(8)) { + data = _expandedData(data); + } + + (bool success, bytes memory result) = payable(mockCd).call{value: callValue}( + LibZip.cdCompress(abi.encodeWithSignature("storeDataHash(bytes,bool)", data, true)) + ); + + assertTrue(success); + bytes32 decodedDataHash = abi.decode(result, (bytes32)); + bytes32 expectedDataHash = keccak256(data); + assertEq(decodedDataHash, expectedDataHash); + assertEq(mockCd.dataHash(), expectedDataHash); + assertEq(mockCd.lastCallvalue(), callValue); + assertEq(mockCd.lastCaller(), address(this)); + assertEq(address(mockCd).balance, callValue); + + (success, result) = payable(mockCd).call{value: callValue}( + LibZip.cdCompress(abi.encodeWithSignature("storeDataHash(bytes,bool)", data, false)) + ); + + assertFalse(success); + assertEq(address(mockCd).balance, callValue); + assertEq(abi.encodeWithSelector(MockCd.Hash.selector, expectedDataHash), result); + assertEq(address(mockCd).balance, callValue); + + (success, result) = payable(mockCd).call{value: callValue}(""); + assertEq(address(mockCd).balance, callValue * 2); + assertTrue(success); + } + + function testCdFallbackMaskTrick(uint256 i, uint256 j) public { + i = _bound(i, 0, 2 ** 248 - 1); + uint256 a; + uint256 b; + /// @solidity memory-safe-assembly + assembly { + a := byte(0, xor(add(i, not(3)), j)) + b := xor(byte(i, shl(224, 0xffffffff)), byte(0, j)) + } + assertEq(a, b); + } + + function testCountLeadingNonZeroBytes(bytes32 s) public { + uint256 expected; + uint256 computed; + /// @solidity memory-safe-assembly + assembly { + let n := 0 + for {} // Scan for '\0'. + byte(n, s) { n := add(n, 1) } {} + expected := n + let m := 0x7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F + let x := not(or(or(add(and(s, m), m), s), m)) + computed := 0x20 + if x { + let r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x)) + r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) + r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) + r := or(r, shl(4, lt(0xffff, shr(r, x)))) + r := xor(31, or(shr(3, r), lt(0xff, shr(r, x)))) + computed := r + } + } + assertEq(computed, expected); + } +} From 03d4e7fcc9a45ab9d2360637e5d0dc2963d11544 Mon Sep 17 00:00:00 2001 From: atarpara Date: Mon, 29 Dec 2025 15:16:14 +0530 Subject: [PATCH 4/5] T --- foundry.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/foundry.toml b/foundry.toml index 6fb19e8a1..4795904b3 100644 --- a/foundry.toml +++ b/foundry.toml @@ -33,7 +33,7 @@ skip = [] gas_limit = 16_777_216 # To prevent EVM error. evm_version = "prague" test = "test/ext/ithaca" -skip = [] +skip = ["*/clz/*"] [profile.solx] solc_version = "/usr/local/bin/solx" From ff6256a18851749e765355b3e21dc9bfa417255b Mon Sep 17 00:00:00 2001 From: atarpara Date: Mon, 29 Dec 2025 15:19:02 +0530 Subject: [PATCH 5/5] T --- foundry.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/foundry.toml b/foundry.toml index 4795904b3..8f5a61162 100644 --- a/foundry.toml +++ b/foundry.toml @@ -17,7 +17,7 @@ remappings = [ ] [profile.pre_global_structs] -skip = ["*/g/*", "*/*7702*", "*/*BlockHashLib*", "*/*Transient*", "*/ext/ithaca/*", "*/ext/zksync/*"] +skip = ["*/g/*", "*/*7702*", "*/*BlockHashLib*", "*/*Transient*", "*/ext/ithaca/*", "*/clz/*", "*/ext/zksync/*"] [profile.post_osaka] @@ -27,7 +27,7 @@ skip = ["*/ext/ithaca/*"] [profile.zksync] match_path = "*/ext/zksync/*" evm_version = "cancun" -skip = [] +skip = ["*/clz/*"] [profile.ithaca] gas_limit = 16_777_216 # To prevent EVM error.