From 6cc2820ff3ea090d9819b52514536ed8baac8e6e Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Tue, 9 May 2023 15:16:56 +0200 Subject: [PATCH 01/32] created folder and copied ABDK properties --- .../PRBMath/PRBMathSD59x18PropertyTests.sol | 1850 +++++++++++++++++ .../PRBMath/PRBMathUD60x18PropertyTests.sol | 1850 +++++++++++++++++ package-lock.json | 11 + package.json | 1 + 4 files changed, 3712 insertions(+) create mode 100644 contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol create mode 100644 contracts/Math/PRBMath/PRBMathUD60x18PropertyTests.sol diff --git a/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol new file mode 100644 index 0000000..766aa06 --- /dev/null +++ b/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol @@ -0,0 +1,1850 @@ +pragma solidity ^0.8.19; + +import { SD59x18 } from "@prb/math/SD59x18.sol"; + +contract CryticPRBMathUD59x18Properties { + /* ================================================================ + 64x64 fixed-point constants used for testing specific values. + This assumes that ABDK library's fromInt(x) works as expected. + ================================================================ */ + int128 internal ZERO_FP = ABDKMath64x64.fromInt(0); + int128 internal ONE_FP = ABDKMath64x64.fromInt(1); + int128 internal MINUS_ONE_FP = ABDKMath64x64.fromInt(-1); + int128 internal TWO_FP = ABDKMath64x64.fromInt(2); + int128 internal THREE_FP = ABDKMath64x64.fromInt(3); + int128 internal EIGHT_FP = ABDKMath64x64.fromInt(8); + int128 internal THOUSAND_FP = ABDKMath64x64.fromInt(1000); + int128 internal MINUS_SIXTY_FOUR_FP = ABDKMath64x64.fromInt(-64); + int128 internal EPSILON = 1; + int128 internal ONE_TENTH_FP = + ABDKMath64x64.div(ABDKMath64x64.fromInt(1), ABDKMath64x64.fromInt(10)); + + /* ================================================================ + Constants used for precision loss calculations + ================================================================ */ + uint256 internal REQUIRED_SIGNIFICANT_BITS = 10; + + /* ================================================================ + Integer representations maximum values. + These constants are used for testing edge cases or limits for + possible values. + ================================================================ */ + int128 private constant MIN_64x64 = -0x80000000000000000000000000000000; + int128 private constant MAX_64x64 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + int256 private constant MAX_256 = + 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + int256 private constant MIN_256 = + -0x8000000000000000000000000000000000000000000000000000000000000000; + uint256 private constant MAX_U256 = + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + + /* ================================================================ + Events used for debugging or showing information. + ================================================================ */ + event Value(string reason, int128 val); + event LogErr(bytes error); + + /* ================================================================ + Helper functions. + ================================================================ */ + + // These functions allows to compare a and b for equality, discarding + // the last precision_bits bits. + // An absolute value function is implemented inline in order to not use + // the implementation from the library under test. + function equal_within_precision( + int128 a, + int128 b, + uint256 precision_bits + ) public pure returns (bool) { + int128 max = (a > b) ? a : b; + int128 min = (a > b) ? b : a; + int128 r = (max - min) >> precision_bits; + + return (r == 0); + } + + function equal_within_precision_u( + uint256 a, + uint256 b, + uint256 precision_bits + ) public pure returns (bool) { + uint256 max = (a > b) ? a : b; + uint256 min = (a > b) ? b : a; + uint256 r = (max - min) >> precision_bits; + + return (r == 0); + } + + // This function determines if the relative error between a and b is less + // than error_percent % (expressed as a 64x64 value) + // Uses functions from the library under test! + function equal_within_tolerance( + int128 a, + int128 b, + int128 error_percent + ) public pure returns (bool) { + int128 tol_value = abs(mul(a, div(error_percent, fromUInt(100)))); + + return (abs(sub(b, a)) <= tol_value); + } + + // Check that there are remaining significant digits after a multiplication + // Uses functions from the library under test! + function significant_digits_lost_in_mult( + int128 a, + int128 b + ) public pure returns (bool) { + int128 x = a >= 0 ? a : -a; + int128 y = b >= 0 ? b : -b; + + int128 lx = toInt(log_2(x)); + int128 ly = toInt(log_2(y)); + + return (lx + ly - 1 <= -64); + } + + // Return how many significant bits will remain after multiplying a and b + // Uses functions from the library under test! + function significant_bits_after_mult( + int128 a, + int128 b + ) public pure returns (uint256) { + int128 x = a >= 0 ? a : -a; + int128 y = b >= 0 ? b : -b; + + int128 lx = toInt(log_2(x)); + int128 ly = toInt(log_2(y)); + int256 prec = lx + ly - 1; + + if (prec < -64) return 0; + else return (64 + uint256(prec)); + } + + // Return the i most significant bits from |n|. If n has less than i significant bits, return |n| + // Uses functions from the library under test! + function most_significant_bits( + int128 n, + uint256 i + ) public pure returns (uint256) { + // Create a mask consisting of i bits set to 1 + uint256 mask = (2 ** i) - 1; + + // Get the position of the MSB set to 1 of n + uint256 pos = uint64(toInt(log_2(n)) + 64 + 1); + + // Get the positive value of n + uint256 value = (n > 0) ? uint128(n) : uint128(-n); + + // Shift the mask to match the rightmost 1-set bit + if (pos > i) { + mask <<= (pos - i); + } + + return (value & mask); + } + + // Returns true if the n most significant bits of a and b are almost equal + // Uses functions from the library under test! + function equal_most_significant_bits_within_precision( + int128 a, + int128 b, + uint256 bits + ) public pure returns (bool) { + // Get the number of bits in a and b + // Since log(x) returns in the interval [-64, 63), add 64 to be in the interval [0, 127) + uint256 a_bits = uint256(int256(toInt(log_2(a)) + 64)); + uint256 b_bits = uint256(int256(toInt(log_2(b)) + 64)); + + // a and b lengths may differ in 1 bit, so the shift should take into account the longest + uint256 shift_bits = (a_bits > b_bits) + ? (a_bits - bits) + : (b_bits - bits); + + // Get the _bits_ most significant bits of a and b + uint256 a_msb = most_significant_bits(a, bits) >> shift_bits; + uint256 b_msb = most_significant_bits(b, bits) >> shift_bits; + + // See if they are equal within 1 bit precision + // This could be modified to get the precision as a parameter to the function + return equal_within_precision_u(a_msb, b_msb, 1); + } + + /* ================================================================ + Library wrappers. + These functions allow calling the ABDKMath64x64 library. + ================================================================ */ + function debug(string calldata x, int128 y) public { + emit Value(x, ABDKMath64x64.toInt(y)); + } + + function fromInt(int256 x) public pure returns (int128) { + return ABDKMath64x64.fromInt(x); + } + + function toInt(int128 x) public pure returns (int64) { + return ABDKMath64x64.toInt(x); + } + + function fromUInt(uint256 x) public pure returns (int128) { + return ABDKMath64x64.fromUInt(x); + } + + function toUInt(int128 x) public pure returns (uint64) { + return ABDKMath64x64.toUInt(x); + } + + function add(int128 x, int128 y) public pure returns (int128) { + return ABDKMath64x64.add(x, y); + } + + function sub(int128 x, int128 y) public pure returns (int128) { + return ABDKMath64x64.sub(x, y); + } + + function mul(int128 x, int128 y) public pure returns (int128) { + return ABDKMath64x64.mul(x, y); + } + + function mulu(int128 x, uint256 y) public pure returns (uint256) { + return ABDKMath64x64.mulu(x, y); + } + + function div(int128 x, int128 y) public pure returns (int128) { + return ABDKMath64x64.div(x, y); + } + + function neg(int128 x) public pure returns (int128) { + return ABDKMath64x64.neg(x); + } + + function abs(int128 x) public pure returns (int128) { + return ABDKMath64x64.abs(x); + } + + function inv(int128 x) public pure returns (int128) { + return ABDKMath64x64.inv(x); + } + + function avg(int128 x, int128 y) public pure returns (int128) { + return ABDKMath64x64.avg(x, y); + } + + function gavg(int128 x, int128 y) public pure returns (int128) { + return ABDKMath64x64.gavg(x, y); + } + + function pow(int128 x, uint256 y) public pure returns (int128) { + return ABDKMath64x64.pow(x, y); + } + + function sqrt(int128 x) public pure returns (int128) { + return ABDKMath64x64.sqrt(x); + } + + function log_2(int128 x) public pure returns (int128) { + return ABDKMath64x64.log_2(x); + } + + function ln(int128 x) public pure returns (int128) { + return ABDKMath64x64.ln(x); + } + + function exp_2(int128 x) public pure returns (int128) { + return ABDKMath64x64.exp_2(x); + } + + function exp(int128 x) public pure returns (int128) { + return ABDKMath64x64.exp(x); + } + + /* ================================================================ + Start of tests + ================================================================ */ + + /* ================================================================ + + TESTS FOR FUNCTION add() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for commutative property + // x + y == y + x + function add_test_commutative(int128 x, int128 y) public pure { + int128 x_y = add(x, y); + int128 y_x = add(y, x); + + assert(x_y == y_x); + } + + // Test for associative property + // (x + y) + z == x + (y + z) + function add_test_associative(int128 x, int128 y, int128 z) public pure { + int128 x_y = add(x, y); + int128 y_z = add(y, z); + int128 xy_z = add(x_y, z); + int128 x_yz = add(x, y_z); + + assert(xy_z == x_yz); + } + + // Test for identity operation + // x + 0 == x (equivalent to x + (-x) == 0) + function add_test_identity(int128 x) public view { + int128 x_0 = add(x, ZERO_FP); + + assert(x_0 == x); + assert(add(x, neg(x)) == ZERO_FP); + } + + // Test that the result increases or decreases depending + // on the value to be added + function add_test_values(int128 x, int128 y) public view { + int128 x_y = add(x, y); + + if (y >= ZERO_FP) { + assert(x_y >= x); + } else { + assert(x_y < x); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These should make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // The result of the addition must be between the maximum + // and minimum allowed values for 64x64 + function add_test_range(int128 x, int128 y) public view { + int128 result; + try this.add(x, y) { + result = this.add(x, y); + assert(result <= MAX_64x64 && result >= MIN_64x64); + } catch { + // If it reverts, just ignore + } + } + + // Adding zero to the maximum value shouldn't revert, as it is valid + // Moreover, the result must be MAX_64x64 + function add_test_maximum_value() public view { + int128 result; + try this.add(MAX_64x64, ZERO_FP) { + // Expected behaviour, does not revert + result = this.add(MAX_64x64, ZERO_FP); + assert(result == MAX_64x64); + } catch { + assert(false); + } + } + + // Adding one to the maximum value should revert, as it is out of range + function add_test_maximum_value_plus_one() public view { + try this.add(MAX_64x64, ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + // Adding zero to the minimum value shouldn't revert, as it is valid + // Moreover, the result must be MIN_64x64 + function add_test_minimum_value() public view { + int128 result; + try this.add(MIN_64x64, ZERO_FP) { + // Expected behaviour, does not revert + result = this.add(MIN_64x64, ZERO_FP); + assert(result == MIN_64x64); + } catch { + assert(false); + } + } + + // Adding minus one to the maximum value should revert, as it is out of range + function add_test_minimum_value_plus_negative_one() public view { + try this.add(MIN_64x64, MINUS_ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + /* ================================================================ + + TESTS FOR FUNCTION sub() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test equivalence to addition + // x - y == x + (-y) + function sub_test_equivalence_to_addition(int128 x, int128 y) public pure { + int128 minus_y = neg(y); + int128 addition = add(x, minus_y); + int128 subtraction = sub(x, y); + + assert(addition == subtraction); + } + + // Test for non-commutative property + // x - y == -(y - x) + function sub_test_non_commutative(int128 x, int128 y) public pure { + int128 x_y = sub(x, y); + int128 y_x = sub(y, x); + + assert(x_y == neg(y_x)); + } + + // Test for identity operation + // x - 0 == x (equivalent to x - x == 0) + function sub_test_identity(int128 x) public view { + int128 x_0 = sub(x, ZERO_FP); + + assert(x_0 == x); + assert(sub(x, x) == ZERO_FP); + } + + // Test for neutrality over addition and subtraction + // (x - y) + y == (x + y) - y == x + function sub_test_neutrality(int128 x, int128 y) public pure { + int128 x_minus_y = sub(x, y); + int128 x_plus_y = add(x, y); + + int128 x_minus_y_plus_y = add(x_minus_y, y); + int128 x_plus_y_minus_y = sub(x_plus_y, y); + + assert(x_minus_y_plus_y == x_plus_y_minus_y); + assert(x_minus_y_plus_y == x); + } + + // Test that the result increases or decreases depending + // on the value to be subtracted + function sub_test_values(int128 x, int128 y) public view { + int128 x_y = sub(x, y); + + if (y >= ZERO_FP) { + assert(x_y <= x); + } else { + assert(x_y > x); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // The result of the subtraction must be between the maximum + // and minimum allowed values for 64x64 + function sub_test_range(int128 x, int128 y) public view { + int128 result; + try this.sub(x, y) { + result = this.sub(x, y); + assert(result <= MAX_64x64 && result >= MIN_64x64); + } catch { + // If it reverts, just ignore + } + } + + // Subtracting zero from the maximum value shouldn't revert, as it is valid + // Moreover, the result must be MAX_64x64 + function sub_test_maximum_value() public view { + int128 result; + try this.sub(MAX_64x64, ZERO_FP) { + // Expected behaviour, does not revert + result = this.sub(MAX_64x64, ZERO_FP); + assert(result == MAX_64x64); + } catch { + assert(false); + } + } + + // Subtracting minus one from the maximum value should revert, + // as it is out of range + function sub_test_maximum_value_minus_neg_one() public view { + try this.sub(MAX_64x64, MINUS_ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + // Subtracting zero from the minimum value shouldn't revert, as it is valid + // Moreover, the result must be MIN_64x64 + function sub_test_minimum_value() public view { + int128 result; + try this.sub(MIN_64x64, ZERO_FP) { + // Expected behaviour, does not revert + result = this.sub(MIN_64x64, ZERO_FP); + assert(result == MIN_64x64); + } catch { + assert(false); + } + } + + // Subtracting one from the minimum value should revert, as it is out of range + function sub_test_minimum_value_minus_one() public view { + try this.sub(MIN_64x64, ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + /* ================================================================ + + TESTS FOR FUNCTION mul() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for commutative property + // x * y == y * x + function mul_test_commutative(int128 x, int128 y) public pure { + int128 x_y = mul(x, y); + int128 y_x = mul(y, x); + + assert(x_y == y_x); + } + + // Test for associative property + // (x * y) * z == x * (y * z) + function mul_test_associative(int128 x, int128 y, int128 z) public view { + int128 x_y = mul(x, y); + int128 y_z = mul(y, z); + int128 xy_z = mul(x_y, z); + int128 x_yz = mul(x, y_z); + + // Failure if all significant digits are lost + require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + require(significant_bits_after_mult(y, z) > REQUIRED_SIGNIFICANT_BITS); + require( + significant_bits_after_mult(x_y, z) > REQUIRED_SIGNIFICANT_BITS + ); + require( + significant_bits_after_mult(x, y_z) > REQUIRED_SIGNIFICANT_BITS + ); + + assert(equal_within_tolerance(xy_z, x_yz, ONE_TENTH_FP)); + } + + // Test for distributive property + // x * (y + z) == x * y + x * z + function mul_test_distributive(int128 x, int128 y, int128 z) public view { + int128 y_plus_z = add(y, z); + int128 x_times_y_plus_z = mul(x, y_plus_z); + + int128 x_times_y = mul(x, y); + int128 x_times_z = mul(x, z); + + // Failure if all significant digits are lost + require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + require(significant_bits_after_mult(x, z) > REQUIRED_SIGNIFICANT_BITS); + require( + significant_bits_after_mult(x, y_plus_z) > REQUIRED_SIGNIFICANT_BITS + ); + + assert( + equal_within_tolerance( + add(x_times_y, x_times_z), + x_times_y_plus_z, + ONE_TENTH_FP + ) + ); + } + + // Test for identity operation + // x * 1 == x (also check that x * 0 == 0) + function mul_test_identity(int128 x) public view { + int128 x_1 = mul(x, ONE_FP); + int128 x_0 = mul(x, ZERO_FP); + + assert(x_0 == ZERO_FP); + assert(x_1 == x); + } + + // Test that the result increases or decreases depending + // on the value to be added + function mul_test_values(int128 x, int128 y) public view { + require(x != ZERO_FP && y != ZERO_FP); + + int128 x_y = mul(x, y); + + require(significant_digits_lost_in_mult(x, y) == false); + + if (x >= ZERO_FP) { + if (y >= ONE_FP) { + assert(x_y >= x); + } else { + assert(x_y <= x); + } + } else { + if (y >= ONE_FP) { + assert(x_y <= x); + } else { + assert(x_y >= x); + } + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // The result of the multiplication must be between the maximum + // and minimum allowed values for 64x64 + function mul_test_range(int128 x, int128 y) public view { + int128 result; + try this.mul(x, y) { + result = this.mul(x, y); + assert(result <= MAX_64x64 && result >= MIN_64x64); + } catch { + // If it reverts, just ignore + } + } + + // Multiplying the maximum value times one shouldn't revert, as it is valid + // Moreover, the result must be MAX_64x64 + function mul_test_maximum_value() public view { + int128 result; + try this.mul(MAX_64x64, ONE_FP) { + // Expected behaviour, does not revert + result = this.mul(MAX_64x64, ONE_FP); + assert(result == MAX_64x64); + } catch { + assert(false); + } + } + + // Multiplying the minimum value times one shouldn't revert, as it is valid + // Moreover, the result must be MIN_64x64 + function mul_test_minimum_value() public view { + int128 result; + try this.mul(MIN_64x64, ONE_FP) { + // Expected behaviour, does not revert + result = this.mul(MIN_64x64, ONE_FP); + assert(result == MIN_64x64); + } catch { + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION div() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for identity property + // x / 1 == x (equivalent to x / x == 1) + // Moreover, x/x should not revert unless x == 0 + function div_test_division_identity(int128 x) public view { + int128 div_1 = div(x, ONE_FP); + assert(x == div_1); + + int128 div_x; + + try this.div(x, x) { + // This should always equal one + div_x = div(x, x); + assert(div_x == ONE_FP); + } catch { + // The only allowed case to revert is if x == 0 + assert(x == ZERO_FP); + } + } + + // Test for negative divisor + // x / -y == -(x / y) + function div_test_negative_divisor(int128 x, int128 y) public view { + require(y < ZERO_FP); + + int128 x_y = div(x, y); + int128 x_minus_y = div(x, neg(y)); + + assert(x_y == neg(x_minus_y)); + } + + // Test for division with 0 as numerator + // 0 / x = 0 + function div_test_division_num_zero(int128 x) public view { + require(x != ZERO_FP); + + int128 div_0 = div(ZERO_FP, x); + + assert(ZERO_FP == div_0); + } + + // Test that the absolute value of the result increases or + // decreases depending on the denominator's absolute value + function div_test_values(int128 x, int128 y) public view { + require(y != ZERO_FP); + + int128 x_y = abs(div(x, y)); + + if (abs(y) >= ONE_FP) { + assert(x_y <= abs(x)); + } else { + assert(x_y >= abs(x)); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for division by zero + function div_test_div_by_zero(int128 x) public view { + try this.div(x, ZERO_FP) { + // Unexpected, this should revert + assert(false); + } catch { + // Expected revert + } + } + + // Test for division by a large value, the result should be less than one + function div_test_maximum_denominator(int128 x) public view { + int128 div_large = div(x, MAX_64x64); + + assert(abs(div_large) <= ONE_FP); + } + + // Test for division of a large value + // This should revert if |x| < 1 as it would return a value higher than max + function div_test_maximum_numerator(int128 x) public view { + int128 div_large; + + try this.div(MAX_64x64, x) { + // If it didn't revert, then |x| >= 1 + div_large = div(MAX_64x64, x); + + assert(abs(x) >= ONE_FP); + } catch { + // Expected revert as result is higher than max + } + } + + // Test for values in range + function div_test_range(int128 x, int128 y) public view { + int128 result; + + try this.div(x, y) { + // If it returns a value, it must be in range + result = div(x, y); + assert(result <= MAX_64x64 && result >= MIN_64x64); + } catch { + // Otherwise, it should revert + } + } + + /* ================================================================ + + TESTS FOR FUNCTION neg() + + ================================================================ */ + + /* ================================================================ + Tests for mathematical properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for the double negation + // -(-x) == x + function neg_test_double_negation(int128 x) public pure { + int128 double_neg = neg(neg(x)); + + assert(x == double_neg); + } + + // Test for the identity operation + // x + (-x) == 0 + function neg_test_identity(int128 x) public view { + int128 neg_x = neg(x); + + assert(add(x, neg_x) == ZERO_FP); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for the zero-case + // -0 == 0 + function neg_test_zero() public view { + int128 neg_x = neg(ZERO_FP); + + assert(neg_x == ZERO_FP); + } + + // Test for the maximum value case + // Since this is implementation-dependant, we will actually test with MAX_64x64-EPS + function neg_test_maximum() public view { + try this.neg(sub(MAX_64x64, EPSILON)) { + // Expected behaviour, does not revert + } catch { + assert(false); + } + } + + // Test for the minimum value case + // Since this is implementation-dependant, we will actually test with MIN_64x64+EPS + function neg_test_minimum() public view { + try this.neg(add(MIN_64x64, EPSILON)) { + // Expected behaviour, does not revert + } catch { + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION abs() + + ================================================================ */ + + /* ================================================================ + Tests for mathematical properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test that the absolute value is always positive + function abs_test_positive(int128 x) public view { + int128 abs_x = abs(x); + + assert(abs_x >= ZERO_FP); + } + + // Test that the absolute value of a number equals the + // absolute value of the negative of the same number + function abs_test_negative(int128 x) public pure { + int128 abs_x = abs(x); + int128 abs_minus_x = abs(neg(x)); + + assert(abs_x == abs_minus_x); + } + + // Test the multiplicativeness property + // | x * y | == |x| * |y| + function abs_test_multiplicativeness(int128 x, int128 y) public pure { + int128 abs_x = abs(x); + int128 abs_y = abs(y); + int128 abs_xy = abs(mul(x, y)); + int128 abs_x_abs_y = mul(abs_x, abs_y); + + // Failure if all significant digits are lost + require(significant_digits_lost_in_mult(abs_x, abs_y) == false); + + // Assume a tolerance of two bits of precision + assert(equal_within_precision(abs_xy, abs_x_abs_y, 2)); + } + + // Test the subadditivity property + // | x + y | <= |x| + |y| + function abs_test_subadditivity(int128 x, int128 y) public pure { + int128 abs_x = abs(x); + int128 abs_y = abs(y); + int128 abs_xy = abs(add(x, y)); + + assert(abs_xy <= add(abs_x, abs_y)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test the zero-case | 0 | = 0 + function abs_test_zero() public view { + int128 abs_zero; + + try this.abs(ZERO_FP) { + // If it doesn't revert, the value must be zero + abs_zero = this.abs(ZERO_FP); + assert(abs_zero == ZERO_FP); + } catch { + // Unexpected, the function must not revert here + assert(false); + } + } + + // Test the maximum value + function abs_test_maximum() public view { + int128 abs_max; + + try this.abs(MAX_64x64) { + // If it doesn't revert, the value must be MAX_64x64 + abs_max = this.abs(MAX_64x64); + assert(abs_max == MAX_64x64); + } catch {} + } + + // Test the minimum value + function abs_test_minimum() public view { + int128 abs_min; + + try this.abs(MIN_64x64) { + // If it doesn't revert, the value must be the negative of MIN_64x64 + abs_min = this.abs(MIN_64x64); + assert(abs_min == neg(MIN_64x64)); + } catch {} + } + + /* ================================================================ + + TESTS FOR FUNCTION inv() + + ================================================================ */ + + /* ================================================================ + Tests for mathematical properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test that the inverse of the inverse is close enough to the + // original number + function inv_test_double_inverse(int128 x) public view { + require(x != ZERO_FP); + + int128 double_inv_x = inv(inv(x)); + + // The maximum loss of precision will be 2 * log2(x) bits rounded up + uint256 loss = 2 * toUInt(log_2(x)) + 2; + + assert(equal_within_precision(x, double_inv_x, loss)); + } + + // Test equivalence with division + function inv_test_division(int128 x) public view { + require(x != ZERO_FP); + + int128 inv_x = inv(x); + int128 div_1_x = div(ONE_FP, x); + + assert(inv_x == div_1_x); + } + + // Test the anticommutativity of the division + // x / y == 1 / (y / x) + function inv_test_division_noncommutativity( + int128 x, + int128 y + ) public view { + require(x != ZERO_FP && y != ZERO_FP); + + int128 x_y = div(x, y); + int128 y_x = div(y, x); + + require( + significant_bits_after_mult(x, inv(y)) > REQUIRED_SIGNIFICANT_BITS + ); + require( + significant_bits_after_mult(y, inv(x)) > REQUIRED_SIGNIFICANT_BITS + ); + assert(equal_within_tolerance(x_y, inv(y_x), ONE_TENTH_FP)); + } + + // Test the multiplication of inverses + // 1/(x * y) == 1/x * 1/y + function inv_test_multiplication(int128 x, int128 y) public view { + require(x != ZERO_FP && y != ZERO_FP); + + int128 inv_x = inv(x); + int128 inv_y = inv(y); + int128 inv_x_times_inv_y = mul(inv_x, inv_y); + + int128 x_y = mul(x, y); + int128 inv_x_y = inv(x_y); + + require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + require( + significant_bits_after_mult(inv_x, inv_y) > + REQUIRED_SIGNIFICANT_BITS + ); + + // The maximum loss of precision is given by the formula: + // 2 * | log_2(x) - log_2(y) | + 1 + uint256 loss = 2 * toUInt(abs(log_2(x) - log_2(y))) + 1; + + assert(equal_within_precision(inv_x_y, inv_x_times_inv_y, loss)); + } + + // Test identity property + // Intermediate result should have at least REQUIRED_SIGNIFICANT_BITS + function inv_test_identity(int128 x) public view { + require(x != ZERO_FP); + + int128 inv_x = inv(x); + int128 identity = mul(inv_x, x); + + require( + significant_bits_after_mult(x, inv_x) > REQUIRED_SIGNIFICANT_BITS + ); + + // They should agree with a tolerance of one tenth of a percent + assert(equal_within_tolerance(identity, ONE_FP, ONE_TENTH_FP)); + } + + // Test that the absolute value of the result is in range zero-one + // if x is greater than one, else, the absolute value of the result + // must be greater than one + function inv_test_values(int128 x) public view { + require(x != ZERO_FP); + + int128 abs_inv_x = abs(inv(x)); + + if (abs(x) >= ONE_FP) { + assert(abs_inv_x <= ONE_FP); + } else { + assert(abs_inv_x > ONE_FP); + } + } + + // Test that the result has the same sign as the argument + function inv_test_sign(int128 x) public view { + require(x != ZERO_FP); + + int128 inv_x = inv(x); + + if (x > ZERO_FP) { + assert(inv_x > ZERO_FP); + } else { + assert(inv_x < ZERO_FP); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test the zero-case, should revert + function inv_test_zero() public view { + try this.inv(ZERO_FP) { + // Unexpected, the function must revert + assert(false); + } catch {} + } + + // Test the maximum value case, should not revert, and be close to zero + function inv_test_maximum() public view { + int128 inv_maximum; + + try this.inv(MAX_64x64) { + inv_maximum = this.inv(MAX_64x64); + assert(equal_within_precision(inv_maximum, ZERO_FP, 10)); + } catch { + // Unexpected, the function must not revert + assert(false); + } + } + + // Test the minimum value case, should not revert, and be close to zero + function inv_test_minimum() public view { + int128 inv_minimum; + + try this.inv(MAX_64x64) { + inv_minimum = this.inv(MAX_64x64); + assert(equal_within_precision(abs(inv_minimum), ZERO_FP, 10)); + } catch { + // Unexpected, the function must not revert + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION avg() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test that the result is between the two operands + // avg(x, y) >= min(x, y) && avg(x, y) <= max(x, y) + function avg_test_values_in_range(int128 x, int128 y) public pure { + int128 avg_xy = avg(x, y); + + if (x >= y) { + assert(avg_xy >= y && avg_xy <= x); + } else { + assert(avg_xy >= x && avg_xy <= y); + } + } + + // Test that the average of the same number is itself + // avg(x, x) == x + function avg_test_one_value(int128 x) public pure { + int128 avg_x = avg(x, x); + + assert(avg_x == x); + } + + // Test that the order of operands is irrelevant + // avg(x, y) == avg(y, x) + function avg_test_operand_order(int128 x, int128 y) public pure { + int128 avg_xy = avg(x, y); + int128 avg_yx = avg(y, x); + + assert(avg_xy == avg_yx); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for the maximum value + function avg_test_maximum() public view { + int128 result; + + // This may revert due to overflow depending on implementation + // If it doesn't revert, the result must be MAX_64x64 + try this.avg(MAX_64x64, MAX_64x64) { + result = this.avg(MAX_64x64, MAX_64x64); + assert(result == MAX_64x64); + } catch {} + } + + // Test for the minimum value + function avg_test_minimum() public view { + int128 result; + + // This may revert due to overflow depending on implementation + // If it doesn't revert, the result must be MIN_64x64 + try this.avg(MIN_64x64, MIN_64x64) { + result = this.avg(MIN_64x64, MIN_64x64); + assert(result == MIN_64x64); + } catch {} + } + + /* ================================================================ + + TESTS FOR FUNCTION gavg() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test that the result is between the two operands + // gavg(x, y) >= min(x, y) && gavg(x, y) <= max(x, y) + function gavg_test_values_in_range(int128 x, int128 y) public view { + int128 gavg_xy = gavg(x, y); + + if (x == ZERO_FP || y == ZERO_FP) { + assert(gavg_xy == ZERO_FP); + } else { + if (abs(x) >= abs(y)) { + assert(gavg_xy >= abs(y) && gavg_xy <= abs(x)); + } else { + assert(gavg_xy >= abs(x) && gavg_xy <= abs(y)); + } + } + } + + // Test that the average of the same number is itself + // gavg(x, x) == | x | + function gavg_test_one_value(int128 x) public pure { + int128 gavg_x = gavg(x, x); + + assert(gavg_x == abs(x)); + } + + // Test that the order of operands is irrelevant + // gavg(x, y) == gavg(y, x) + function gavg_test_operand_order(int128 x, int128 y) public pure { + int128 gavg_xy = gavg(x, y); + int128 gavg_yx = gavg(y, x); + + assert(gavg_xy == gavg_yx); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for the maximum value + function gavg_test_maximum() public view { + int128 result; + + // This may revert due to overflow depending on implementation + // If it doesn't revert, the result must be MAX_64x64 + try this.gavg(MAX_64x64, MAX_64x64) { + result = this.gavg(MAX_64x64, MAX_64x64); + assert(result == MAX_64x64); + } catch {} + } + + // Test for the minimum value + function gavg_test_minimum() public view { + int128 result; + + // This may revert due to overflow depending on implementation + // If it doesn't revert, the result must be MIN_64x64 + try this.gavg(MIN_64x64, MIN_64x64) { + result = this.gavg(MIN_64x64, MIN_64x64); + assert(result == MIN_64x64); + } catch {} + } + + /* ================================================================ + + TESTS FOR FUNCTION pow() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for zero exponent + // x ** 0 == 1 + function pow_test_zero_exponent(int128 x) public view { + int128 x_pow_0 = pow(x, 0); + + assert(x_pow_0 == ONE_FP); + } + + // Test for zero base + // 0 ** x == 0 (for positive x) + function pow_test_zero_base(uint256 x) public view { + require(x != 0); + + int128 zero_pow_x = pow(ZERO_FP, x); + + assert(zero_pow_x == ZERO_FP); + } + + // Test for exponent one + // x ** 1 == x + function pow_test_one_exponent(int128 x) public pure { + int128 x_pow_1 = pow(x, 1); + + assert(x_pow_1 == x); + } + + // Test for base one + // 1 ** x == 1 + function pow_test_base_one(uint256 x) public view { + int128 one_pow_x = pow(ONE_FP, x); + + assert(one_pow_x == ONE_FP); + } + + // Test for product of powers of the same base + // x ** a * x ** b == x ** (a + b) + function pow_test_product_same_base( + int128 x, + uint256 a, + uint256 b + ) public view { + require(x != ZERO_FP); + + int128 x_a = pow(x, a); + int128 x_b = pow(x, b); + int128 x_ab = pow(x, a + b); + + assert(equal_within_precision(mul(x_a, x_b), x_ab, 2)); + } + + // Test for power of an exponentiation + // (x ** a) ** b == x ** (a * b) + function pow_test_power_of_an_exponentiation( + int128 x, + uint256 a, + uint256 b + ) public view { + require(x != ZERO_FP); + + int128 x_a = pow(x, a); + int128 x_a_b = pow(x_a, b); + int128 x_ab = pow(x, a * b); + + assert(equal_within_precision(x_a_b, x_ab, 2)); + } + + // Test for power of a product + // (x * y) ** a == x ** a * y ** a + function pow_test_product_same_base( + int128 x, + int128 y, + uint256 a + ) public view { + require(x != ZERO_FP && y != ZERO_FP); + require(a > 2 ** 32); // to avoid massive loss of precision + + int128 x_y = mul(x, y); + int128 xy_a = pow(x_y, a); + + int128 x_a = pow(x, a); + int128 y_a = pow(y, a); + + assert(equal_within_precision(mul(x_a, y_a), xy_a, 2)); + } + + // Test for result being greater than or lower than the argument, depending on + // its absolute value and the value of the exponent + function pow_test_values(int128 x, uint256 a) public view { + require(x != ZERO_FP); + + int128 x_a = pow(x, a); + + if (abs(x) >= ONE_FP) { + assert(abs(x_a) >= ONE_FP); + } + + if (abs(x) <= ONE_FP) { + assert(abs(x_a) <= ONE_FP); + } + } + + // Test for result sign: if the exponent is even, sign is positive + // if the exponent is odd, preserves the sign of the base + function pow_test_sign(int128 x, uint256 a) public view { + require(x != ZERO_FP && a != 0); + + int128 x_a = pow(x, a); + + // This prevents the case where a small negative number gets + // rounded down to zero and thus changes sign + require(x_a != ZERO_FP); + + // If the exponent is even + if (a % 2 == 0) { + assert(x_a == abs(x_a)); + } else { + // x_a preserves x sign + if (x < ZERO_FP) { + assert(x_a < ZERO_FP); + } else { + assert(x_a > ZERO_FP); + } + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for maximum base and exponent > 1 + function pow_test_maximum_base(uint256 a) public view { + require(a > 1); + + try this.pow(MAX_64x64, a) { + // Unexpected, should revert because of overflow + assert(false); + } catch { + // Expected revert + } + } + + // Test for abs(base) < 1 and high exponent + function pow_test_high_exponent(int128 x, uint256 a) public view { + require(abs(x) < ONE_FP && a > 2 ** 64); + + int128 result = pow(x, a); + + assert(result == ZERO_FP); + } + + /* ================================================================ + + TESTS FOR FUNCTION sqrt() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for the inverse operation + // sqrt(x) * sqrt(x) == x + function sqrt_test_inverse_mul(int128 x) public view { + require(x >= ZERO_FP); + + int128 sqrt_x = sqrt(x); + int128 sqrt_x_squared = mul(sqrt_x, sqrt_x); + + // Precision loss is at most half the bits of the operand + assert( + equal_within_precision( + sqrt_x_squared, + x, + (toUInt(log_2(x)) >> 1) + 2 + ) + ); + } + + // Test for the inverse operation + // sqrt(x) ** 2 == x + function sqrt_test_inverse_pow(int128 x) public view { + require(x >= ZERO_FP); + + int128 sqrt_x = sqrt(x); + int128 sqrt_x_squared = pow(sqrt_x, 2); + + // Precision loss is at most half the bits of the operand + assert( + equal_within_precision( + sqrt_x_squared, + x, + (toUInt(log_2(x)) >> 1) + 2 + ) + ); + } + + // Test for distributive property respect to the multiplication + // sqrt(x) * sqrt(y) == sqrt(x * y) + function sqrt_test_distributive(int128 x, int128 y) public view { + require(x >= ZERO_FP && y >= ZERO_FP); + + int128 sqrt_x = sqrt(x); + int128 sqrt_y = sqrt(y); + int128 sqrt_x_sqrt_y = mul(sqrt_x, sqrt_y); + int128 sqrt_xy = sqrt(mul(x, y)); + + // Ensure we have enough significant digits for the result to be meaningful + require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + require( + significant_bits_after_mult(sqrt_x, sqrt_y) > + REQUIRED_SIGNIFICANT_BITS + ); + + // Allow an error of up to one tenth of a percent + assert(equal_within_tolerance(sqrt_x_sqrt_y, sqrt_xy, ONE_TENTH_FP)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case + function sqrt_test_zero() public view { + assert(sqrt(ZERO_FP) == ZERO_FP); + } + + // Test for maximum value + function sqrt_test_maximum() public view { + try this.sqrt(MAX_64x64) { + // Expected behaviour, MAX_64x64 is positive, and operation + // should not revert as the result is in range + } catch { + // Unexpected, should not revert + assert(false); + } + } + + // Test for minimum value + function sqrt_test_minimum() public view { + try this.sqrt(MIN_64x64) { + // Unexpected, should revert. MIN_64x64 is negative. + assert(false); + } catch { + // Expected behaviour, revert + } + } + + // Test for negative operands + function sqrt_test_negative(int128 x) public view { + require(x < ZERO_FP); + + try this.sqrt(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected behaviour, revert + } + } + + /* ================================================================ + + TESTS FOR FUNCTION log2() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for distributive property respect to multiplication + // log2(x * y) = log2(x) + log2(y) + function log2_test_distributive_mul(int128 x, int128 y) public view { + int128 log2_x = log_2(x); + int128 log2_y = log_2(y); + int128 log2_x_log2_y = add(log2_x, log2_y); + + int128 xy = mul(x, y); + int128 log2_xy = log_2(xy); + + // Ensure we have enough significant digits for the result to be meaningful + require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + + // The maximum loss of precision is given by the formula: + // | log_2(x) + log_2(y) | + uint256 loss = toUInt(abs(log_2(x) + log_2(y))); + + assert(equal_within_precision(log2_x_log2_y, log2_xy, loss)); + } + + // Test for logarithm of a power + // log2(x ** y) = y * log2(x) + function log2_test_power(int128 x, uint256 y) public pure { + int128 x_y = pow(x, y); + int128 log2_x_y = log_2(x_y); + + uint256 y_log2_x = mulu(log_2(x), y); + + assert(y_log2_x == toUInt(log2_x_y)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case, should revert + function log2_test_zero() public view { + try this.log_2(ZERO_FP) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, log(0) is not defined + } + } + + // Test for maximum value case, should return a positive number + function log2_test_maximum() public view { + int128 result; + + try this.log_2(MAX_64x64) { + // Expected, should not revert and the result must be > 0 + result = this.log_2(MAX_64x64); + assert(result > ZERO_FP); + } catch { + // Unexpected + assert(false); + } + } + + // Test for negative values, should revert as log2 is not defined + function log2_test_negative(int128 x) public view { + require(x < ZERO_FP); + + try this.log_2(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected + } + } + + /* ================================================================ + + TESTS FOR FUNCTION ln() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for distributive property respect to multiplication + // ln(x * y) = ln(x) + ln(y) + function ln_test_distributive_mul(int128 x, int128 y) public view { + require(x > ZERO_FP && y > ZERO_FP); + + int128 ln_x = ln(x); + int128 ln_y = ln(y); + int128 ln_x_ln_y = add(ln_x, ln_y); + + int128 xy = mul(x, y); + int128 ln_xy = ln(xy); + + require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + + // The maximum loss of precision is given by the formula: + // | log_2(x) + log_2(y) | + uint256 loss = toUInt(abs(log_2(x) + log_2(y))); + + assert(equal_within_precision(ln_x_ln_y, ln_xy, loss)); + } + + // Test for logarithm of a power + // ln(x ** y) = y * ln(x) + function ln_test_power(int128 x, uint256 y) public pure { + int128 x_y = pow(x, y); + int128 ln_x_y = ln(x_y); + + uint256 y_ln_x = mulu(ln(x), y); + + assert(y_ln_x == toUInt(ln_x_y)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case, should revert + function ln_test_zero() public view { + try this.ln(ZERO_FP) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, log(0) is not defined + } + } + + // Test for maximum value case, should return a positive number + function ln_test_maximum() public view { + int128 result; + + try this.ln(MAX_64x64) { + // Expected, should not revert and the result must be > 0 + result = this.ln(MAX_64x64); + assert(result > ZERO_FP); + } catch { + // Unexpected + assert(false); + } + } + + // Test for negative values, should revert as ln is not defined + function ln_test_negative(int128 x) public view { + require(x < ZERO_FP); + + try this.ln(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected + } + } + + /* ================================================================ + + TESTS FOR FUNCTION exp2() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for equality with pow(2, x) for integer x + // pow(2, x) == exp_2(x) + function exp2_test_equivalence_pow(uint256 x) public view { + int128 exp2_x = exp_2(fromUInt(x)); + int128 pow_2_x = pow(TWO_FP, x); + + assert(exp2_x == pow_2_x); + } + + // Test for inverse function + // If y = log_2(x) then exp_2(y) == x + function exp2_test_inverse(int128 x) public view { + int128 log2_x = log_2(x); + int128 exp2_x = exp_2(log2_x); + + uint256 bits = 50; + + if (log2_x < ZERO_FP) { + bits = uint256(int256(bits) + int256(log2_x)); + } + + assert(equal_most_significant_bits_within_precision(x, exp2_x, bits)); + } + + // Test for negative exponent + // exp_2(-x) == inv( exp_2(x) ) + function exp2_test_negative_exponent(int128 x) public view { + require(x < ZERO_FP && x != MIN_64x64); + + int128 exp2_x = exp_2(x); + int128 exp2_minus_x = exp_2(-x); + + // Result should be within 4 bits precision for the worst case + assert(equal_within_precision(exp2_x, inv(exp2_minus_x), 4)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case + // exp_2(0) == 1 + function exp2_test_zero() public view { + int128 exp_zero = exp_2(ZERO_FP); + assert(exp_zero == ONE_FP); + } + + // Test for maximum value. This should overflow as it won't fit + // in the data type + function exp2_test_maximum() public view { + try this.exp_2(MAX_64x64) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert + } + } + + // Test for minimum value. This should return zero since + // 2 ** -x == 1 / 2 ** x that tends to zero as x increases + function exp2_test_minimum() public view { + int128 result; + + try this.exp_2(MIN_64x64) { + // Expected, should not revert, check that value is zero + result = exp_2(MIN_64x64); + assert(result == ZERO_FP); + } catch { + // Unexpected revert + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION exp() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for inverse function + // If y = ln(x) then exp(y) == x + function exp_test_inverse(int128 x) public view { + int128 ln_x = ln(x); + int128 exp_x = exp(ln_x); + int128 log2_x = log_2(x); + + uint256 bits = 48; + + if (log2_x < ZERO_FP) { + bits = uint256(int256(bits) + int256(log2_x)); + } + + assert(equal_most_significant_bits_within_precision(x, exp_x, bits)); + } + + // Test for negative exponent + // exp(-x) == inv( exp(x) ) + function exp_test_negative_exponent(int128 x) public view { + require(x < ZERO_FP && x != MIN_64x64); + + int128 exp_x = exp(x); + int128 exp_minus_x = exp(-x); + + // Result should be within 4 bits precision for the worst case + assert(equal_within_precision(exp_x, inv(exp_minus_x), 4)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case + // exp(0) == 1 + function exp_test_zero() public view { + int128 exp_zero = exp(ZERO_FP); + assert(exp_zero == ONE_FP); + } + + // Test for maximum value. This should overflow as it won't fit + // in the data type + function exp_test_maximum() public view { + try this.exp(MAX_64x64) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert + } + } + + // Test for minimum value. This should return zero since + // e ** -x == 1 / e ** x that tends to zero as x increases + function exp_test_minimum() public view { + int128 result; + + try this.exp(MIN_64x64) { + // Expected, should not revert, check that value is zero + result = exp(MIN_64x64); + assert(result == ZERO_FP); + } catch { + // Unexpected revert + assert(false); + } + } +} diff --git a/contracts/Math/PRBMath/PRBMathUD60x18PropertyTests.sol b/contracts/Math/PRBMath/PRBMathUD60x18PropertyTests.sol new file mode 100644 index 0000000..81b9572 --- /dev/null +++ b/contracts/Math/PRBMath/PRBMathUD60x18PropertyTests.sol @@ -0,0 +1,1850 @@ +pragma solidity ^0.8.19; + +import { UD60x18 } from "@prb/math/UD60x18.sol"; + +contract CryticPRBMathUD60x18Properties { + /* ================================================================ + 64x64 fixed-point constants used for testing specific values. + This assumes that ABDK library's fromInt(x) works as expected. + ================================================================ */ + int128 internal ZERO_FP = ABDKMath64x64.fromInt(0); + int128 internal ONE_FP = ABDKMath64x64.fromInt(1); + int128 internal MINUS_ONE_FP = ABDKMath64x64.fromInt(-1); + int128 internal TWO_FP = ABDKMath64x64.fromInt(2); + int128 internal THREE_FP = ABDKMath64x64.fromInt(3); + int128 internal EIGHT_FP = ABDKMath64x64.fromInt(8); + int128 internal THOUSAND_FP = ABDKMath64x64.fromInt(1000); + int128 internal MINUS_SIXTY_FOUR_FP = ABDKMath64x64.fromInt(-64); + int128 internal EPSILON = 1; + int128 internal ONE_TENTH_FP = + ABDKMath64x64.div(ABDKMath64x64.fromInt(1), ABDKMath64x64.fromInt(10)); + + /* ================================================================ + Constants used for precision loss calculations + ================================================================ */ + uint256 internal REQUIRED_SIGNIFICANT_BITS = 10; + + /* ================================================================ + Integer representations maximum values. + These constants are used for testing edge cases or limits for + possible values. + ================================================================ */ + int128 private constant MIN_64x64 = -0x80000000000000000000000000000000; + int128 private constant MAX_64x64 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + int256 private constant MAX_256 = + 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + int256 private constant MIN_256 = + -0x8000000000000000000000000000000000000000000000000000000000000000; + uint256 private constant MAX_U256 = + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + + /* ================================================================ + Events used for debugging or showing information. + ================================================================ */ + event Value(string reason, int128 val); + event LogErr(bytes error); + + /* ================================================================ + Helper functions. + ================================================================ */ + + // These functions allows to compare a and b for equality, discarding + // the last precision_bits bits. + // An absolute value function is implemented inline in order to not use + // the implementation from the library under test. + function equal_within_precision( + int128 a, + int128 b, + uint256 precision_bits + ) public pure returns (bool) { + int128 max = (a > b) ? a : b; + int128 min = (a > b) ? b : a; + int128 r = (max - min) >> precision_bits; + + return (r == 0); + } + + function equal_within_precision_u( + uint256 a, + uint256 b, + uint256 precision_bits + ) public pure returns (bool) { + uint256 max = (a > b) ? a : b; + uint256 min = (a > b) ? b : a; + uint256 r = (max - min) >> precision_bits; + + return (r == 0); + } + + // This function determines if the relative error between a and b is less + // than error_percent % (expressed as a 64x64 value) + // Uses functions from the library under test! + function equal_within_tolerance( + int128 a, + int128 b, + int128 error_percent + ) public pure returns (bool) { + int128 tol_value = abs(mul(a, div(error_percent, fromUInt(100)))); + + return (abs(sub(b, a)) <= tol_value); + } + + // Check that there are remaining significant digits after a multiplication + // Uses functions from the library under test! + function significant_digits_lost_in_mult( + int128 a, + int128 b + ) public pure returns (bool) { + int128 x = a >= 0 ? a : -a; + int128 y = b >= 0 ? b : -b; + + int128 lx = toInt(log_2(x)); + int128 ly = toInt(log_2(y)); + + return (lx + ly - 1 <= -64); + } + + // Return how many significant bits will remain after multiplying a and b + // Uses functions from the library under test! + function significant_bits_after_mult( + int128 a, + int128 b + ) public pure returns (uint256) { + int128 x = a >= 0 ? a : -a; + int128 y = b >= 0 ? b : -b; + + int128 lx = toInt(log_2(x)); + int128 ly = toInt(log_2(y)); + int256 prec = lx + ly - 1; + + if (prec < -64) return 0; + else return (64 + uint256(prec)); + } + + // Return the i most significant bits from |n|. If n has less than i significant bits, return |n| + // Uses functions from the library under test! + function most_significant_bits( + int128 n, + uint256 i + ) public pure returns (uint256) { + // Create a mask consisting of i bits set to 1 + uint256 mask = (2 ** i) - 1; + + // Get the position of the MSB set to 1 of n + uint256 pos = uint64(toInt(log_2(n)) + 64 + 1); + + // Get the positive value of n + uint256 value = (n > 0) ? uint128(n) : uint128(-n); + + // Shift the mask to match the rightmost 1-set bit + if (pos > i) { + mask <<= (pos - i); + } + + return (value & mask); + } + + // Returns true if the n most significant bits of a and b are almost equal + // Uses functions from the library under test! + function equal_most_significant_bits_within_precision( + int128 a, + int128 b, + uint256 bits + ) public pure returns (bool) { + // Get the number of bits in a and b + // Since log(x) returns in the interval [-64, 63), add 64 to be in the interval [0, 127) + uint256 a_bits = uint256(int256(toInt(log_2(a)) + 64)); + uint256 b_bits = uint256(int256(toInt(log_2(b)) + 64)); + + // a and b lengths may differ in 1 bit, so the shift should take into account the longest + uint256 shift_bits = (a_bits > b_bits) + ? (a_bits - bits) + : (b_bits - bits); + + // Get the _bits_ most significant bits of a and b + uint256 a_msb = most_significant_bits(a, bits) >> shift_bits; + uint256 b_msb = most_significant_bits(b, bits) >> shift_bits; + + // See if they are equal within 1 bit precision + // This could be modified to get the precision as a parameter to the function + return equal_within_precision_u(a_msb, b_msb, 1); + } + + /* ================================================================ + Library wrappers. + These functions allow calling the ABDKMath64x64 library. + ================================================================ */ + function debug(string calldata x, int128 y) public { + emit Value(x, ABDKMath64x64.toInt(y)); + } + + function fromInt(int256 x) public pure returns (int128) { + return ABDKMath64x64.fromInt(x); + } + + function toInt(int128 x) public pure returns (int64) { + return ABDKMath64x64.toInt(x); + } + + function fromUInt(uint256 x) public pure returns (int128) { + return ABDKMath64x64.fromUInt(x); + } + + function toUInt(int128 x) public pure returns (uint64) { + return ABDKMath64x64.toUInt(x); + } + + function add(int128 x, int128 y) public pure returns (int128) { + return ABDKMath64x64.add(x, y); + } + + function sub(int128 x, int128 y) public pure returns (int128) { + return ABDKMath64x64.sub(x, y); + } + + function mul(int128 x, int128 y) public pure returns (int128) { + return ABDKMath64x64.mul(x, y); + } + + function mulu(int128 x, uint256 y) public pure returns (uint256) { + return ABDKMath64x64.mulu(x, y); + } + + function div(int128 x, int128 y) public pure returns (int128) { + return ABDKMath64x64.div(x, y); + } + + function neg(int128 x) public pure returns (int128) { + return ABDKMath64x64.neg(x); + } + + function abs(int128 x) public pure returns (int128) { + return ABDKMath64x64.abs(x); + } + + function inv(int128 x) public pure returns (int128) { + return ABDKMath64x64.inv(x); + } + + function avg(int128 x, int128 y) public pure returns (int128) { + return ABDKMath64x64.avg(x, y); + } + + function gavg(int128 x, int128 y) public pure returns (int128) { + return ABDKMath64x64.gavg(x, y); + } + + function pow(int128 x, uint256 y) public pure returns (int128) { + return ABDKMath64x64.pow(x, y); + } + + function sqrt(int128 x) public pure returns (int128) { + return ABDKMath64x64.sqrt(x); + } + + function log_2(int128 x) public pure returns (int128) { + return ABDKMath64x64.log_2(x); + } + + function ln(int128 x) public pure returns (int128) { + return ABDKMath64x64.ln(x); + } + + function exp_2(int128 x) public pure returns (int128) { + return ABDKMath64x64.exp_2(x); + } + + function exp(int128 x) public pure returns (int128) { + return ABDKMath64x64.exp(x); + } + + /* ================================================================ + Start of tests + ================================================================ */ + + /* ================================================================ + + TESTS FOR FUNCTION add() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for commutative property + // x + y == y + x + function add_test_commutative(int128 x, int128 y) public pure { + int128 x_y = add(x, y); + int128 y_x = add(y, x); + + assert(x_y == y_x); + } + + // Test for associative property + // (x + y) + z == x + (y + z) + function add_test_associative(int128 x, int128 y, int128 z) public pure { + int128 x_y = add(x, y); + int128 y_z = add(y, z); + int128 xy_z = add(x_y, z); + int128 x_yz = add(x, y_z); + + assert(xy_z == x_yz); + } + + // Test for identity operation + // x + 0 == x (equivalent to x + (-x) == 0) + function add_test_identity(int128 x) public view { + int128 x_0 = add(x, ZERO_FP); + + assert(x_0 == x); + assert(add(x, neg(x)) == ZERO_FP); + } + + // Test that the result increases or decreases depending + // on the value to be added + function add_test_values(int128 x, int128 y) public view { + int128 x_y = add(x, y); + + if (y >= ZERO_FP) { + assert(x_y >= x); + } else { + assert(x_y < x); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These should make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // The result of the addition must be between the maximum + // and minimum allowed values for 64x64 + function add_test_range(int128 x, int128 y) public view { + int128 result; + try this.add(x, y) { + result = this.add(x, y); + assert(result <= MAX_64x64 && result >= MIN_64x64); + } catch { + // If it reverts, just ignore + } + } + + // Adding zero to the maximum value shouldn't revert, as it is valid + // Moreover, the result must be MAX_64x64 + function add_test_maximum_value() public view { + int128 result; + try this.add(MAX_64x64, ZERO_FP) { + // Expected behaviour, does not revert + result = this.add(MAX_64x64, ZERO_FP); + assert(result == MAX_64x64); + } catch { + assert(false); + } + } + + // Adding one to the maximum value should revert, as it is out of range + function add_test_maximum_value_plus_one() public view { + try this.add(MAX_64x64, ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + // Adding zero to the minimum value shouldn't revert, as it is valid + // Moreover, the result must be MIN_64x64 + function add_test_minimum_value() public view { + int128 result; + try this.add(MIN_64x64, ZERO_FP) { + // Expected behaviour, does not revert + result = this.add(MIN_64x64, ZERO_FP); + assert(result == MIN_64x64); + } catch { + assert(false); + } + } + + // Adding minus one to the maximum value should revert, as it is out of range + function add_test_minimum_value_plus_negative_one() public view { + try this.add(MIN_64x64, MINUS_ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + /* ================================================================ + + TESTS FOR FUNCTION sub() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test equivalence to addition + // x - y == x + (-y) + function sub_test_equivalence_to_addition(int128 x, int128 y) public pure { + int128 minus_y = neg(y); + int128 addition = add(x, minus_y); + int128 subtraction = sub(x, y); + + assert(addition == subtraction); + } + + // Test for non-commutative property + // x - y == -(y - x) + function sub_test_non_commutative(int128 x, int128 y) public pure { + int128 x_y = sub(x, y); + int128 y_x = sub(y, x); + + assert(x_y == neg(y_x)); + } + + // Test for identity operation + // x - 0 == x (equivalent to x - x == 0) + function sub_test_identity(int128 x) public view { + int128 x_0 = sub(x, ZERO_FP); + + assert(x_0 == x); + assert(sub(x, x) == ZERO_FP); + } + + // Test for neutrality over addition and subtraction + // (x - y) + y == (x + y) - y == x + function sub_test_neutrality(int128 x, int128 y) public pure { + int128 x_minus_y = sub(x, y); + int128 x_plus_y = add(x, y); + + int128 x_minus_y_plus_y = add(x_minus_y, y); + int128 x_plus_y_minus_y = sub(x_plus_y, y); + + assert(x_minus_y_plus_y == x_plus_y_minus_y); + assert(x_minus_y_plus_y == x); + } + + // Test that the result increases or decreases depending + // on the value to be subtracted + function sub_test_values(int128 x, int128 y) public view { + int128 x_y = sub(x, y); + + if (y >= ZERO_FP) { + assert(x_y <= x); + } else { + assert(x_y > x); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // The result of the subtraction must be between the maximum + // and minimum allowed values for 64x64 + function sub_test_range(int128 x, int128 y) public view { + int128 result; + try this.sub(x, y) { + result = this.sub(x, y); + assert(result <= MAX_64x64 && result >= MIN_64x64); + } catch { + // If it reverts, just ignore + } + } + + // Subtracting zero from the maximum value shouldn't revert, as it is valid + // Moreover, the result must be MAX_64x64 + function sub_test_maximum_value() public view { + int128 result; + try this.sub(MAX_64x64, ZERO_FP) { + // Expected behaviour, does not revert + result = this.sub(MAX_64x64, ZERO_FP); + assert(result == MAX_64x64); + } catch { + assert(false); + } + } + + // Subtracting minus one from the maximum value should revert, + // as it is out of range + function sub_test_maximum_value_minus_neg_one() public view { + try this.sub(MAX_64x64, MINUS_ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + // Subtracting zero from the minimum value shouldn't revert, as it is valid + // Moreover, the result must be MIN_64x64 + function sub_test_minimum_value() public view { + int128 result; + try this.sub(MIN_64x64, ZERO_FP) { + // Expected behaviour, does not revert + result = this.sub(MIN_64x64, ZERO_FP); + assert(result == MIN_64x64); + } catch { + assert(false); + } + } + + // Subtracting one from the minimum value should revert, as it is out of range + function sub_test_minimum_value_minus_one() public view { + try this.sub(MIN_64x64, ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + /* ================================================================ + + TESTS FOR FUNCTION mul() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for commutative property + // x * y == y * x + function mul_test_commutative(int128 x, int128 y) public pure { + int128 x_y = mul(x, y); + int128 y_x = mul(y, x); + + assert(x_y == y_x); + } + + // Test for associative property + // (x * y) * z == x * (y * z) + function mul_test_associative(int128 x, int128 y, int128 z) public view { + int128 x_y = mul(x, y); + int128 y_z = mul(y, z); + int128 xy_z = mul(x_y, z); + int128 x_yz = mul(x, y_z); + + // Failure if all significant digits are lost + require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + require(significant_bits_after_mult(y, z) > REQUIRED_SIGNIFICANT_BITS); + require( + significant_bits_after_mult(x_y, z) > REQUIRED_SIGNIFICANT_BITS + ); + require( + significant_bits_after_mult(x, y_z) > REQUIRED_SIGNIFICANT_BITS + ); + + assert(equal_within_tolerance(xy_z, x_yz, ONE_TENTH_FP)); + } + + // Test for distributive property + // x * (y + z) == x * y + x * z + function mul_test_distributive(int128 x, int128 y, int128 z) public view { + int128 y_plus_z = add(y, z); + int128 x_times_y_plus_z = mul(x, y_plus_z); + + int128 x_times_y = mul(x, y); + int128 x_times_z = mul(x, z); + + // Failure if all significant digits are lost + require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + require(significant_bits_after_mult(x, z) > REQUIRED_SIGNIFICANT_BITS); + require( + significant_bits_after_mult(x, y_plus_z) > REQUIRED_SIGNIFICANT_BITS + ); + + assert( + equal_within_tolerance( + add(x_times_y, x_times_z), + x_times_y_plus_z, + ONE_TENTH_FP + ) + ); + } + + // Test for identity operation + // x * 1 == x (also check that x * 0 == 0) + function mul_test_identity(int128 x) public view { + int128 x_1 = mul(x, ONE_FP); + int128 x_0 = mul(x, ZERO_FP); + + assert(x_0 == ZERO_FP); + assert(x_1 == x); + } + + // Test that the result increases or decreases depending + // on the value to be added + function mul_test_values(int128 x, int128 y) public view { + require(x != ZERO_FP && y != ZERO_FP); + + int128 x_y = mul(x, y); + + require(significant_digits_lost_in_mult(x, y) == false); + + if (x >= ZERO_FP) { + if (y >= ONE_FP) { + assert(x_y >= x); + } else { + assert(x_y <= x); + } + } else { + if (y >= ONE_FP) { + assert(x_y <= x); + } else { + assert(x_y >= x); + } + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // The result of the multiplication must be between the maximum + // and minimum allowed values for 64x64 + function mul_test_range(int128 x, int128 y) public view { + int128 result; + try this.mul(x, y) { + result = this.mul(x, y); + assert(result <= MAX_64x64 && result >= MIN_64x64); + } catch { + // If it reverts, just ignore + } + } + + // Multiplying the maximum value times one shouldn't revert, as it is valid + // Moreover, the result must be MAX_64x64 + function mul_test_maximum_value() public view { + int128 result; + try this.mul(MAX_64x64, ONE_FP) { + // Expected behaviour, does not revert + result = this.mul(MAX_64x64, ONE_FP); + assert(result == MAX_64x64); + } catch { + assert(false); + } + } + + // Multiplying the minimum value times one shouldn't revert, as it is valid + // Moreover, the result must be MIN_64x64 + function mul_test_minimum_value() public view { + int128 result; + try this.mul(MIN_64x64, ONE_FP) { + // Expected behaviour, does not revert + result = this.mul(MIN_64x64, ONE_FP); + assert(result == MIN_64x64); + } catch { + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION div() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for identity property + // x / 1 == x (equivalent to x / x == 1) + // Moreover, x/x should not revert unless x == 0 + function div_test_division_identity(int128 x) public view { + int128 div_1 = div(x, ONE_FP); + assert(x == div_1); + + int128 div_x; + + try this.div(x, x) { + // This should always equal one + div_x = div(x, x); + assert(div_x == ONE_FP); + } catch { + // The only allowed case to revert is if x == 0 + assert(x == ZERO_FP); + } + } + + // Test for negative divisor + // x / -y == -(x / y) + function div_test_negative_divisor(int128 x, int128 y) public view { + require(y < ZERO_FP); + + int128 x_y = div(x, y); + int128 x_minus_y = div(x, neg(y)); + + assert(x_y == neg(x_minus_y)); + } + + // Test for division with 0 as numerator + // 0 / x = 0 + function div_test_division_num_zero(int128 x) public view { + require(x != ZERO_FP); + + int128 div_0 = div(ZERO_FP, x); + + assert(ZERO_FP == div_0); + } + + // Test that the absolute value of the result increases or + // decreases depending on the denominator's absolute value + function div_test_values(int128 x, int128 y) public view { + require(y != ZERO_FP); + + int128 x_y = abs(div(x, y)); + + if (abs(y) >= ONE_FP) { + assert(x_y <= abs(x)); + } else { + assert(x_y >= abs(x)); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for division by zero + function div_test_div_by_zero(int128 x) public view { + try this.div(x, ZERO_FP) { + // Unexpected, this should revert + assert(false); + } catch { + // Expected revert + } + } + + // Test for division by a large value, the result should be less than one + function div_test_maximum_denominator(int128 x) public view { + int128 div_large = div(x, MAX_64x64); + + assert(abs(div_large) <= ONE_FP); + } + + // Test for division of a large value + // This should revert if |x| < 1 as it would return a value higher than max + function div_test_maximum_numerator(int128 x) public view { + int128 div_large; + + try this.div(MAX_64x64, x) { + // If it didn't revert, then |x| >= 1 + div_large = div(MAX_64x64, x); + + assert(abs(x) >= ONE_FP); + } catch { + // Expected revert as result is higher than max + } + } + + // Test for values in range + function div_test_range(int128 x, int128 y) public view { + int128 result; + + try this.div(x, y) { + // If it returns a value, it must be in range + result = div(x, y); + assert(result <= MAX_64x64 && result >= MIN_64x64); + } catch { + // Otherwise, it should revert + } + } + + /* ================================================================ + + TESTS FOR FUNCTION neg() + + ================================================================ */ + + /* ================================================================ + Tests for mathematical properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for the double negation + // -(-x) == x + function neg_test_double_negation(int128 x) public pure { + int128 double_neg = neg(neg(x)); + + assert(x == double_neg); + } + + // Test for the identity operation + // x + (-x) == 0 + function neg_test_identity(int128 x) public view { + int128 neg_x = neg(x); + + assert(add(x, neg_x) == ZERO_FP); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for the zero-case + // -0 == 0 + function neg_test_zero() public view { + int128 neg_x = neg(ZERO_FP); + + assert(neg_x == ZERO_FP); + } + + // Test for the maximum value case + // Since this is implementation-dependant, we will actually test with MAX_64x64-EPS + function neg_test_maximum() public view { + try this.neg(sub(MAX_64x64, EPSILON)) { + // Expected behaviour, does not revert + } catch { + assert(false); + } + } + + // Test for the minimum value case + // Since this is implementation-dependant, we will actually test with MIN_64x64+EPS + function neg_test_minimum() public view { + try this.neg(add(MIN_64x64, EPSILON)) { + // Expected behaviour, does not revert + } catch { + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION abs() + + ================================================================ */ + + /* ================================================================ + Tests for mathematical properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test that the absolute value is always positive + function abs_test_positive(int128 x) public view { + int128 abs_x = abs(x); + + assert(abs_x >= ZERO_FP); + } + + // Test that the absolute value of a number equals the + // absolute value of the negative of the same number + function abs_test_negative(int128 x) public pure { + int128 abs_x = abs(x); + int128 abs_minus_x = abs(neg(x)); + + assert(abs_x == abs_minus_x); + } + + // Test the multiplicativeness property + // | x * y | == |x| * |y| + function abs_test_multiplicativeness(int128 x, int128 y) public pure { + int128 abs_x = abs(x); + int128 abs_y = abs(y); + int128 abs_xy = abs(mul(x, y)); + int128 abs_x_abs_y = mul(abs_x, abs_y); + + // Failure if all significant digits are lost + require(significant_digits_lost_in_mult(abs_x, abs_y) == false); + + // Assume a tolerance of two bits of precision + assert(equal_within_precision(abs_xy, abs_x_abs_y, 2)); + } + + // Test the subadditivity property + // | x + y | <= |x| + |y| + function abs_test_subadditivity(int128 x, int128 y) public pure { + int128 abs_x = abs(x); + int128 abs_y = abs(y); + int128 abs_xy = abs(add(x, y)); + + assert(abs_xy <= add(abs_x, abs_y)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test the zero-case | 0 | = 0 + function abs_test_zero() public view { + int128 abs_zero; + + try this.abs(ZERO_FP) { + // If it doesn't revert, the value must be zero + abs_zero = this.abs(ZERO_FP); + assert(abs_zero == ZERO_FP); + } catch { + // Unexpected, the function must not revert here + assert(false); + } + } + + // Test the maximum value + function abs_test_maximum() public view { + int128 abs_max; + + try this.abs(MAX_64x64) { + // If it doesn't revert, the value must be MAX_64x64 + abs_max = this.abs(MAX_64x64); + assert(abs_max == MAX_64x64); + } catch {} + } + + // Test the minimum value + function abs_test_minimum() public view { + int128 abs_min; + + try this.abs(MIN_64x64) { + // If it doesn't revert, the value must be the negative of MIN_64x64 + abs_min = this.abs(MIN_64x64); + assert(abs_min == neg(MIN_64x64)); + } catch {} + } + + /* ================================================================ + + TESTS FOR FUNCTION inv() + + ================================================================ */ + + /* ================================================================ + Tests for mathematical properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test that the inverse of the inverse is close enough to the + // original number + function inv_test_double_inverse(int128 x) public view { + require(x != ZERO_FP); + + int128 double_inv_x = inv(inv(x)); + + // The maximum loss of precision will be 2 * log2(x) bits rounded up + uint256 loss = 2 * toUInt(log_2(x)) + 2; + + assert(equal_within_precision(x, double_inv_x, loss)); + } + + // Test equivalence with division + function inv_test_division(int128 x) public view { + require(x != ZERO_FP); + + int128 inv_x = inv(x); + int128 div_1_x = div(ONE_FP, x); + + assert(inv_x == div_1_x); + } + + // Test the anticommutativity of the division + // x / y == 1 / (y / x) + function inv_test_division_noncommutativity( + int128 x, + int128 y + ) public view { + require(x != ZERO_FP && y != ZERO_FP); + + int128 x_y = div(x, y); + int128 y_x = div(y, x); + + require( + significant_bits_after_mult(x, inv(y)) > REQUIRED_SIGNIFICANT_BITS + ); + require( + significant_bits_after_mult(y, inv(x)) > REQUIRED_SIGNIFICANT_BITS + ); + assert(equal_within_tolerance(x_y, inv(y_x), ONE_TENTH_FP)); + } + + // Test the multiplication of inverses + // 1/(x * y) == 1/x * 1/y + function inv_test_multiplication(int128 x, int128 y) public view { + require(x != ZERO_FP && y != ZERO_FP); + + int128 inv_x = inv(x); + int128 inv_y = inv(y); + int128 inv_x_times_inv_y = mul(inv_x, inv_y); + + int128 x_y = mul(x, y); + int128 inv_x_y = inv(x_y); + + require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + require( + significant_bits_after_mult(inv_x, inv_y) > + REQUIRED_SIGNIFICANT_BITS + ); + + // The maximum loss of precision is given by the formula: + // 2 * | log_2(x) - log_2(y) | + 1 + uint256 loss = 2 * toUInt(abs(log_2(x) - log_2(y))) + 1; + + assert(equal_within_precision(inv_x_y, inv_x_times_inv_y, loss)); + } + + // Test identity property + // Intermediate result should have at least REQUIRED_SIGNIFICANT_BITS + function inv_test_identity(int128 x) public view { + require(x != ZERO_FP); + + int128 inv_x = inv(x); + int128 identity = mul(inv_x, x); + + require( + significant_bits_after_mult(x, inv_x) > REQUIRED_SIGNIFICANT_BITS + ); + + // They should agree with a tolerance of one tenth of a percent + assert(equal_within_tolerance(identity, ONE_FP, ONE_TENTH_FP)); + } + + // Test that the absolute value of the result is in range zero-one + // if x is greater than one, else, the absolute value of the result + // must be greater than one + function inv_test_values(int128 x) public view { + require(x != ZERO_FP); + + int128 abs_inv_x = abs(inv(x)); + + if (abs(x) >= ONE_FP) { + assert(abs_inv_x <= ONE_FP); + } else { + assert(abs_inv_x > ONE_FP); + } + } + + // Test that the result has the same sign as the argument + function inv_test_sign(int128 x) public view { + require(x != ZERO_FP); + + int128 inv_x = inv(x); + + if (x > ZERO_FP) { + assert(inv_x > ZERO_FP); + } else { + assert(inv_x < ZERO_FP); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test the zero-case, should revert + function inv_test_zero() public view { + try this.inv(ZERO_FP) { + // Unexpected, the function must revert + assert(false); + } catch {} + } + + // Test the maximum value case, should not revert, and be close to zero + function inv_test_maximum() public view { + int128 inv_maximum; + + try this.inv(MAX_64x64) { + inv_maximum = this.inv(MAX_64x64); + assert(equal_within_precision(inv_maximum, ZERO_FP, 10)); + } catch { + // Unexpected, the function must not revert + assert(false); + } + } + + // Test the minimum value case, should not revert, and be close to zero + function inv_test_minimum() public view { + int128 inv_minimum; + + try this.inv(MAX_64x64) { + inv_minimum = this.inv(MAX_64x64); + assert(equal_within_precision(abs(inv_minimum), ZERO_FP, 10)); + } catch { + // Unexpected, the function must not revert + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION avg() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test that the result is between the two operands + // avg(x, y) >= min(x, y) && avg(x, y) <= max(x, y) + function avg_test_values_in_range(int128 x, int128 y) public pure { + int128 avg_xy = avg(x, y); + + if (x >= y) { + assert(avg_xy >= y && avg_xy <= x); + } else { + assert(avg_xy >= x && avg_xy <= y); + } + } + + // Test that the average of the same number is itself + // avg(x, x) == x + function avg_test_one_value(int128 x) public pure { + int128 avg_x = avg(x, x); + + assert(avg_x == x); + } + + // Test that the order of operands is irrelevant + // avg(x, y) == avg(y, x) + function avg_test_operand_order(int128 x, int128 y) public pure { + int128 avg_xy = avg(x, y); + int128 avg_yx = avg(y, x); + + assert(avg_xy == avg_yx); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for the maximum value + function avg_test_maximum() public view { + int128 result; + + // This may revert due to overflow depending on implementation + // If it doesn't revert, the result must be MAX_64x64 + try this.avg(MAX_64x64, MAX_64x64) { + result = this.avg(MAX_64x64, MAX_64x64); + assert(result == MAX_64x64); + } catch {} + } + + // Test for the minimum value + function avg_test_minimum() public view { + int128 result; + + // This may revert due to overflow depending on implementation + // If it doesn't revert, the result must be MIN_64x64 + try this.avg(MIN_64x64, MIN_64x64) { + result = this.avg(MIN_64x64, MIN_64x64); + assert(result == MIN_64x64); + } catch {} + } + + /* ================================================================ + + TESTS FOR FUNCTION gavg() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test that the result is between the two operands + // gavg(x, y) >= min(x, y) && gavg(x, y) <= max(x, y) + function gavg_test_values_in_range(int128 x, int128 y) public view { + int128 gavg_xy = gavg(x, y); + + if (x == ZERO_FP || y == ZERO_FP) { + assert(gavg_xy == ZERO_FP); + } else { + if (abs(x) >= abs(y)) { + assert(gavg_xy >= abs(y) && gavg_xy <= abs(x)); + } else { + assert(gavg_xy >= abs(x) && gavg_xy <= abs(y)); + } + } + } + + // Test that the average of the same number is itself + // gavg(x, x) == | x | + function gavg_test_one_value(int128 x) public pure { + int128 gavg_x = gavg(x, x); + + assert(gavg_x == abs(x)); + } + + // Test that the order of operands is irrelevant + // gavg(x, y) == gavg(y, x) + function gavg_test_operand_order(int128 x, int128 y) public pure { + int128 gavg_xy = gavg(x, y); + int128 gavg_yx = gavg(y, x); + + assert(gavg_xy == gavg_yx); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for the maximum value + function gavg_test_maximum() public view { + int128 result; + + // This may revert due to overflow depending on implementation + // If it doesn't revert, the result must be MAX_64x64 + try this.gavg(MAX_64x64, MAX_64x64) { + result = this.gavg(MAX_64x64, MAX_64x64); + assert(result == MAX_64x64); + } catch {} + } + + // Test for the minimum value + function gavg_test_minimum() public view { + int128 result; + + // This may revert due to overflow depending on implementation + // If it doesn't revert, the result must be MIN_64x64 + try this.gavg(MIN_64x64, MIN_64x64) { + result = this.gavg(MIN_64x64, MIN_64x64); + assert(result == MIN_64x64); + } catch {} + } + + /* ================================================================ + + TESTS FOR FUNCTION pow() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for zero exponent + // x ** 0 == 1 + function pow_test_zero_exponent(int128 x) public view { + int128 x_pow_0 = pow(x, 0); + + assert(x_pow_0 == ONE_FP); + } + + // Test for zero base + // 0 ** x == 0 (for positive x) + function pow_test_zero_base(uint256 x) public view { + require(x != 0); + + int128 zero_pow_x = pow(ZERO_FP, x); + + assert(zero_pow_x == ZERO_FP); + } + + // Test for exponent one + // x ** 1 == x + function pow_test_one_exponent(int128 x) public pure { + int128 x_pow_1 = pow(x, 1); + + assert(x_pow_1 == x); + } + + // Test for base one + // 1 ** x == 1 + function pow_test_base_one(uint256 x) public view { + int128 one_pow_x = pow(ONE_FP, x); + + assert(one_pow_x == ONE_FP); + } + + // Test for product of powers of the same base + // x ** a * x ** b == x ** (a + b) + function pow_test_product_same_base( + int128 x, + uint256 a, + uint256 b + ) public view { + require(x != ZERO_FP); + + int128 x_a = pow(x, a); + int128 x_b = pow(x, b); + int128 x_ab = pow(x, a + b); + + assert(equal_within_precision(mul(x_a, x_b), x_ab, 2)); + } + + // Test for power of an exponentiation + // (x ** a) ** b == x ** (a * b) + function pow_test_power_of_an_exponentiation( + int128 x, + uint256 a, + uint256 b + ) public view { + require(x != ZERO_FP); + + int128 x_a = pow(x, a); + int128 x_a_b = pow(x_a, b); + int128 x_ab = pow(x, a * b); + + assert(equal_within_precision(x_a_b, x_ab, 2)); + } + + // Test for power of a product + // (x * y) ** a == x ** a * y ** a + function pow_test_product_same_base( + int128 x, + int128 y, + uint256 a + ) public view { + require(x != ZERO_FP && y != ZERO_FP); + require(a > 2 ** 32); // to avoid massive loss of precision + + int128 x_y = mul(x, y); + int128 xy_a = pow(x_y, a); + + int128 x_a = pow(x, a); + int128 y_a = pow(y, a); + + assert(equal_within_precision(mul(x_a, y_a), xy_a, 2)); + } + + // Test for result being greater than or lower than the argument, depending on + // its absolute value and the value of the exponent + function pow_test_values(int128 x, uint256 a) public view { + require(x != ZERO_FP); + + int128 x_a = pow(x, a); + + if (abs(x) >= ONE_FP) { + assert(abs(x_a) >= ONE_FP); + } + + if (abs(x) <= ONE_FP) { + assert(abs(x_a) <= ONE_FP); + } + } + + // Test for result sign: if the exponent is even, sign is positive + // if the exponent is odd, preserves the sign of the base + function pow_test_sign(int128 x, uint256 a) public view { + require(x != ZERO_FP && a != 0); + + int128 x_a = pow(x, a); + + // This prevents the case where a small negative number gets + // rounded down to zero and thus changes sign + require(x_a != ZERO_FP); + + // If the exponent is even + if (a % 2 == 0) { + assert(x_a == abs(x_a)); + } else { + // x_a preserves x sign + if (x < ZERO_FP) { + assert(x_a < ZERO_FP); + } else { + assert(x_a > ZERO_FP); + } + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for maximum base and exponent > 1 + function pow_test_maximum_base(uint256 a) public view { + require(a > 1); + + try this.pow(MAX_64x64, a) { + // Unexpected, should revert because of overflow + assert(false); + } catch { + // Expected revert + } + } + + // Test for abs(base) < 1 and high exponent + function pow_test_high_exponent(int128 x, uint256 a) public view { + require(abs(x) < ONE_FP && a > 2 ** 64); + + int128 result = pow(x, a); + + assert(result == ZERO_FP); + } + + /* ================================================================ + + TESTS FOR FUNCTION sqrt() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for the inverse operation + // sqrt(x) * sqrt(x) == x + function sqrt_test_inverse_mul(int128 x) public view { + require(x >= ZERO_FP); + + int128 sqrt_x = sqrt(x); + int128 sqrt_x_squared = mul(sqrt_x, sqrt_x); + + // Precision loss is at most half the bits of the operand + assert( + equal_within_precision( + sqrt_x_squared, + x, + (toUInt(log_2(x)) >> 1) + 2 + ) + ); + } + + // Test for the inverse operation + // sqrt(x) ** 2 == x + function sqrt_test_inverse_pow(int128 x) public view { + require(x >= ZERO_FP); + + int128 sqrt_x = sqrt(x); + int128 sqrt_x_squared = pow(sqrt_x, 2); + + // Precision loss is at most half the bits of the operand + assert( + equal_within_precision( + sqrt_x_squared, + x, + (toUInt(log_2(x)) >> 1) + 2 + ) + ); + } + + // Test for distributive property respect to the multiplication + // sqrt(x) * sqrt(y) == sqrt(x * y) + function sqrt_test_distributive(int128 x, int128 y) public view { + require(x >= ZERO_FP && y >= ZERO_FP); + + int128 sqrt_x = sqrt(x); + int128 sqrt_y = sqrt(y); + int128 sqrt_x_sqrt_y = mul(sqrt_x, sqrt_y); + int128 sqrt_xy = sqrt(mul(x, y)); + + // Ensure we have enough significant digits for the result to be meaningful + require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + require( + significant_bits_after_mult(sqrt_x, sqrt_y) > + REQUIRED_SIGNIFICANT_BITS + ); + + // Allow an error of up to one tenth of a percent + assert(equal_within_tolerance(sqrt_x_sqrt_y, sqrt_xy, ONE_TENTH_FP)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case + function sqrt_test_zero() public view { + assert(sqrt(ZERO_FP) == ZERO_FP); + } + + // Test for maximum value + function sqrt_test_maximum() public view { + try this.sqrt(MAX_64x64) { + // Expected behaviour, MAX_64x64 is positive, and operation + // should not revert as the result is in range + } catch { + // Unexpected, should not revert + assert(false); + } + } + + // Test for minimum value + function sqrt_test_minimum() public view { + try this.sqrt(MIN_64x64) { + // Unexpected, should revert. MIN_64x64 is negative. + assert(false); + } catch { + // Expected behaviour, revert + } + } + + // Test for negative operands + function sqrt_test_negative(int128 x) public view { + require(x < ZERO_FP); + + try this.sqrt(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected behaviour, revert + } + } + + /* ================================================================ + + TESTS FOR FUNCTION log2() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for distributive property respect to multiplication + // log2(x * y) = log2(x) + log2(y) + function log2_test_distributive_mul(int128 x, int128 y) public view { + int128 log2_x = log_2(x); + int128 log2_y = log_2(y); + int128 log2_x_log2_y = add(log2_x, log2_y); + + int128 xy = mul(x, y); + int128 log2_xy = log_2(xy); + + // Ensure we have enough significant digits for the result to be meaningful + require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + + // The maximum loss of precision is given by the formula: + // | log_2(x) + log_2(y) | + uint256 loss = toUInt(abs(log_2(x) + log_2(y))); + + assert(equal_within_precision(log2_x_log2_y, log2_xy, loss)); + } + + // Test for logarithm of a power + // log2(x ** y) = y * log2(x) + function log2_test_power(int128 x, uint256 y) public pure { + int128 x_y = pow(x, y); + int128 log2_x_y = log_2(x_y); + + uint256 y_log2_x = mulu(log_2(x), y); + + assert(y_log2_x == toUInt(log2_x_y)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case, should revert + function log2_test_zero() public view { + try this.log_2(ZERO_FP) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, log(0) is not defined + } + } + + // Test for maximum value case, should return a positive number + function log2_test_maximum() public view { + int128 result; + + try this.log_2(MAX_64x64) { + // Expected, should not revert and the result must be > 0 + result = this.log_2(MAX_64x64); + assert(result > ZERO_FP); + } catch { + // Unexpected + assert(false); + } + } + + // Test for negative values, should revert as log2 is not defined + function log2_test_negative(int128 x) public view { + require(x < ZERO_FP); + + try this.log_2(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected + } + } + + /* ================================================================ + + TESTS FOR FUNCTION ln() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for distributive property respect to multiplication + // ln(x * y) = ln(x) + ln(y) + function ln_test_distributive_mul(int128 x, int128 y) public view { + require(x > ZERO_FP && y > ZERO_FP); + + int128 ln_x = ln(x); + int128 ln_y = ln(y); + int128 ln_x_ln_y = add(ln_x, ln_y); + + int128 xy = mul(x, y); + int128 ln_xy = ln(xy); + + require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + + // The maximum loss of precision is given by the formula: + // | log_2(x) + log_2(y) | + uint256 loss = toUInt(abs(log_2(x) + log_2(y))); + + assert(equal_within_precision(ln_x_ln_y, ln_xy, loss)); + } + + // Test for logarithm of a power + // ln(x ** y) = y * ln(x) + function ln_test_power(int128 x, uint256 y) public pure { + int128 x_y = pow(x, y); + int128 ln_x_y = ln(x_y); + + uint256 y_ln_x = mulu(ln(x), y); + + assert(y_ln_x == toUInt(ln_x_y)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case, should revert + function ln_test_zero() public view { + try this.ln(ZERO_FP) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, log(0) is not defined + } + } + + // Test for maximum value case, should return a positive number + function ln_test_maximum() public view { + int128 result; + + try this.ln(MAX_64x64) { + // Expected, should not revert and the result must be > 0 + result = this.ln(MAX_64x64); + assert(result > ZERO_FP); + } catch { + // Unexpected + assert(false); + } + } + + // Test for negative values, should revert as ln is not defined + function ln_test_negative(int128 x) public view { + require(x < ZERO_FP); + + try this.ln(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected + } + } + + /* ================================================================ + + TESTS FOR FUNCTION exp2() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for equality with pow(2, x) for integer x + // pow(2, x) == exp_2(x) + function exp2_test_equivalence_pow(uint256 x) public view { + int128 exp2_x = exp_2(fromUInt(x)); + int128 pow_2_x = pow(TWO_FP, x); + + assert(exp2_x == pow_2_x); + } + + // Test for inverse function + // If y = log_2(x) then exp_2(y) == x + function exp2_test_inverse(int128 x) public view { + int128 log2_x = log_2(x); + int128 exp2_x = exp_2(log2_x); + + uint256 bits = 50; + + if (log2_x < ZERO_FP) { + bits = uint256(int256(bits) + int256(log2_x)); + } + + assert(equal_most_significant_bits_within_precision(x, exp2_x, bits)); + } + + // Test for negative exponent + // exp_2(-x) == inv( exp_2(x) ) + function exp2_test_negative_exponent(int128 x) public view { + require(x < ZERO_FP && x != MIN_64x64); + + int128 exp2_x = exp_2(x); + int128 exp2_minus_x = exp_2(-x); + + // Result should be within 4 bits precision for the worst case + assert(equal_within_precision(exp2_x, inv(exp2_minus_x), 4)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case + // exp_2(0) == 1 + function exp2_test_zero() public view { + int128 exp_zero = exp_2(ZERO_FP); + assert(exp_zero == ONE_FP); + } + + // Test for maximum value. This should overflow as it won't fit + // in the data type + function exp2_test_maximum() public view { + try this.exp_2(MAX_64x64) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert + } + } + + // Test for minimum value. This should return zero since + // 2 ** -x == 1 / 2 ** x that tends to zero as x increases + function exp2_test_minimum() public view { + int128 result; + + try this.exp_2(MIN_64x64) { + // Expected, should not revert, check that value is zero + result = exp_2(MIN_64x64); + assert(result == ZERO_FP); + } catch { + // Unexpected revert + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION exp() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for inverse function + // If y = ln(x) then exp(y) == x + function exp_test_inverse(int128 x) public view { + int128 ln_x = ln(x); + int128 exp_x = exp(ln_x); + int128 log2_x = log_2(x); + + uint256 bits = 48; + + if (log2_x < ZERO_FP) { + bits = uint256(int256(bits) + int256(log2_x)); + } + + assert(equal_most_significant_bits_within_precision(x, exp_x, bits)); + } + + // Test for negative exponent + // exp(-x) == inv( exp(x) ) + function exp_test_negative_exponent(int128 x) public view { + require(x < ZERO_FP && x != MIN_64x64); + + int128 exp_x = exp(x); + int128 exp_minus_x = exp(-x); + + // Result should be within 4 bits precision for the worst case + assert(equal_within_precision(exp_x, inv(exp_minus_x), 4)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case + // exp(0) == 1 + function exp_test_zero() public view { + int128 exp_zero = exp(ZERO_FP); + assert(exp_zero == ONE_FP); + } + + // Test for maximum value. This should overflow as it won't fit + // in the data type + function exp_test_maximum() public view { + try this.exp(MAX_64x64) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert + } + } + + // Test for minimum value. This should return zero since + // e ** -x == 1 / e ** x that tends to zero as x increases + function exp_test_minimum() public view { + int128 result; + + try this.exp(MIN_64x64) { + // Expected, should not revert, check that value is zero + result = exp(MIN_64x64); + assert(result == ZERO_FP); + } catch { + // Unexpected revert + assert(false); + } + } +} diff --git a/package-lock.json b/package-lock.json index 836906c..e59ca87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@openzeppelin/contracts": "^4.7.3", + "@prb/math": "^4.0.0", "markdown-link-check": "^3.11.0", "prettier": "^2.8.7", "prettier-plugin-solidity": "^1.1.3", @@ -1027,6 +1028,11 @@ "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.8.0.tgz", "integrity": "sha512-AGuwhRRL+NaKx73WKRNzeCxOCOCxpaqF+kp8TJ89QzAipSwZy/NoflkWaL9bywXFRhIzXt8j38sfF7KBKCPWLw==" }, + "node_modules/@prb/math": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@prb/math/-/math-4.0.0.tgz", + "integrity": "sha512-zGOMIn3EMSxh2yo64uKly4xtwVvbUvsKAand4JFzDRFAPEzqBWJe1/Qf23o992ZR7xeA502L17rSTcOscfRO2Q==" + }, "node_modules/@scure/base": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", @@ -5081,6 +5087,11 @@ "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.8.0.tgz", "integrity": "sha512-AGuwhRRL+NaKx73WKRNzeCxOCOCxpaqF+kp8TJ89QzAipSwZy/NoflkWaL9bywXFRhIzXt8j38sfF7KBKCPWLw==" }, + "@prb/math": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@prb/math/-/math-4.0.0.tgz", + "integrity": "sha512-zGOMIn3EMSxh2yo64uKly4xtwVvbUvsKAand4JFzDRFAPEzqBWJe1/Qf23o992ZR7xeA502L17rSTcOscfRO2Q==" + }, "@scure/base": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", diff --git a/package.json b/package.json index 58777b9..7f5d8e8 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "homepage": "https://github.com/crytic/properties#readme", "dependencies": { "@openzeppelin/contracts": "^4.7.3", + "@prb/math": "^4.0.0", "markdown-link-check": "^3.11.0", "prettier": "^2.8.7", "prettier-plugin-solidity": "^1.1.3", From ba4db6908340b70fd4e99d5fbf0316210ed62b60 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Tue, 9 May 2023 19:38:32 +0200 Subject: [PATCH 02/32] converted ABDKMath properties to PBRMath properties --- .../PRBMath/PRBMathSD59x18PropertyTests.sol | 1256 +++++------ .../PRBMath/PRBMathUD60x18PropertyTests.sol | 1850 ----------------- 2 files changed, 564 insertions(+), 2542 deletions(-) delete mode 100644 contracts/Math/PRBMath/PRBMathUD60x18PropertyTests.sol diff --git a/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol index 766aa06..fa8b4b2 100644 --- a/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol @@ -1,169 +1,105 @@ pragma solidity ^0.8.19; import { SD59x18 } from "@prb/math/SD59x18.sol"; +import {add as helpersAdd, sub as helpersSub} from "@prb/math/sd59x18/Helpers.sol"; +import {mul as helpersMul, div as helpersDiv, abs as helpersAbs, ln as helpersLn, exp as helpersExp, exp2 as helpersExp2, log2 as helpersLog2, sqrt as helpersSqrt, pow as helpersPow, avg as helpersAvg, inv as helpersInv} from "@prb/math/sd59x18/Math.sol"; + +contract CryticPRBMath59x18Properties { -contract CryticPRBMathUD59x18Properties { /* ================================================================ 64x64 fixed-point constants used for testing specific values. This assumes that ABDK library's fromInt(x) works as expected. ================================================================ */ - int128 internal ZERO_FP = ABDKMath64x64.fromInt(0); - int128 internal ONE_FP = ABDKMath64x64.fromInt(1); - int128 internal MINUS_ONE_FP = ABDKMath64x64.fromInt(-1); - int128 internal TWO_FP = ABDKMath64x64.fromInt(2); - int128 internal THREE_FP = ABDKMath64x64.fromInt(3); - int128 internal EIGHT_FP = ABDKMath64x64.fromInt(8); - int128 internal THOUSAND_FP = ABDKMath64x64.fromInt(1000); - int128 internal MINUS_SIXTY_FOUR_FP = ABDKMath64x64.fromInt(-64); - int128 internal EPSILON = 1; - int128 internal ONE_TENTH_FP = - ABDKMath64x64.div(ABDKMath64x64.fromInt(1), ABDKMath64x64.fromInt(10)); + SD59x18 internal ZERO_FP = convert(0); + SD59x18 internal ONE_FP = convert(1); + SD59x18 internal MINUS_ONE_FP = convert(-1); + SD59x18 internal TWO_FP = convert(2); + SD59x18 internal THREE_FP = convert(3); + SD59x18 internal EIGHT_FP = convert(8); + SD59x18 internal THOUSAND_FP = convert(1000); + SD59x18 internal MINUS_SIXTY_FOUR_FP = convert(-64); + SD59x18 internal EPSILON = SD59x18.wrap(1); + SD59x18 internal ONE_TENTH_FP = convert(1); /* ================================================================ Constants used for precision loss calculations ================================================================ */ uint256 internal REQUIRED_SIGNIFICANT_BITS = 10; - /* ================================================================ - Integer representations maximum values. - These constants are used for testing edge cases or limits for - possible values. - ================================================================ */ - int128 private constant MIN_64x64 = -0x80000000000000000000000000000000; - int128 private constant MAX_64x64 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - int256 private constant MAX_256 = - 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - int256 private constant MIN_256 = - -0x8000000000000000000000000000000000000000000000000000000000000000; - uint256 private constant MAX_U256 = - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - /* ================================================================ Events used for debugging or showing information. ================================================================ */ - event Value(string reason, int128 val); + event Value(string reason, int256 val); event LogErr(bytes error); + /* ================================================================ Helper functions. ================================================================ */ // These functions allows to compare a and b for equality, discarding // the last precision_bits bits. - // An absolute value function is implemented inline in order to not use + // An absolute value function is implemented inline in order to not use // the implementation from the library under test. - function equal_within_precision( - int128 a, - int128 b, - uint256 precision_bits - ) public pure returns (bool) { - int128 max = (a > b) ? a : b; - int128 min = (a > b) ? b : a; - int128 r = (max - min) >> precision_bits; - - return (r == 0); + function equal_within_precision(SD59x18 a, SD59x18 b, uint256 precision_bits) public pure returns(bool) { + SD59x18 max = gt(a , b) ? a : b; + SD59x18 min = gt(a , b) ? b : a; + SD59x18 r = rshift(sub(max, min), precision_bits); + + return (eq(r, convert(0))); } - function equal_within_precision_u( - uint256 a, - uint256 b, - uint256 precision_bits - ) public pure returns (bool) { + function equal_within_precision_u(uint256 a, uint256 b, uint256 precision_bits) public pure returns(bool) { uint256 max = (a > b) ? a : b; uint256 min = (a > b) ? b : a; uint256 r = (max - min) >> precision_bits; - + return (r == 0); } // This function determines if the relative error between a and b is less - // than error_percent % (expressed as a 64x64 value) + // than error_percent % (expressed as a 59x18 value) // Uses functions from the library under test! - function equal_within_tolerance( - int128 a, - int128 b, - int128 error_percent - ) public pure returns (bool) { - int128 tol_value = abs(mul(a, div(error_percent, fromUInt(100)))); + function equal_within_tolerance(SD59x18 a, SD59x18 b, SD59x18 error_percent) public pure returns(bool) { + SD59x18 tol_value = abs(mul(a, div(error_percent, convert(100)))); - return (abs(sub(b, a)) <= tol_value); + return (lte(abs(sub(b, a)), tol_value)); } // Check that there are remaining significant digits after a multiplication // Uses functions from the library under test! - function significant_digits_lost_in_mult( - int128 a, - int128 b - ) public pure returns (bool) { - int128 x = a >= 0 ? a : -a; - int128 y = b >= 0 ? b : -b; - - int128 lx = toInt(log_2(x)); - int128 ly = toInt(log_2(y)); + function significant_digits_lost_in_mult(SD59x18 a, SD59x18 b) public pure returns (bool) { + int256 la = convert(floor(log10(abs(a)))); + int256 lb = convert(floor(log10(abs(b)))); - return (lx + ly - 1 <= -64); + return(la + lb < -18); } // Return how many significant bits will remain after multiplying a and b // Uses functions from the library under test! - function significant_bits_after_mult( - int128 a, - int128 b - ) public pure returns (uint256) { - int128 x = a >= 0 ? a : -a; - int128 y = b >= 0 ? b : -b; + function significant_digits_after_mult(SD59x18 a, SD59x18 b) public pure returns (uint256) { + int256 la = convert(floor(log10(abs(a)))); + int256 lb = convert(floor(log10(abs(b)))); + int256 prec = la + lb; - int128 lx = toInt(log_2(x)); - int128 ly = toInt(log_2(y)); - int256 prec = lx + ly - 1; - - if (prec < -64) return 0; - else return (64 + uint256(prec)); - } - - // Return the i most significant bits from |n|. If n has less than i significant bits, return |n| - // Uses functions from the library under test! - function most_significant_bits( - int128 n, - uint256 i - ) public pure returns (uint256) { - // Create a mask consisting of i bits set to 1 - uint256 mask = (2 ** i) - 1; - - // Get the position of the MSB set to 1 of n - uint256 pos = uint64(toInt(log_2(n)) + 64 + 1); - - // Get the positive value of n - uint256 value = (n > 0) ? uint128(n) : uint128(-n); - - // Shift the mask to match the rightmost 1-set bit - if (pos > i) { - mask <<= (pos - i); - } - - return (value & mask); + if (prec < -18) return 0; + else return(18 + prec); } - // Returns true if the n most significant bits of a and b are almost equal + // Returns true if the n most significant bits of a and b are almost equal // Uses functions from the library under test! - function equal_most_significant_bits_within_precision( - int128 a, - int128 b, - uint256 bits - ) public pure returns (bool) { + function equal_most_significant_digits_within_precision(SD59x18 a, SD59x18 b, uint256 bits) public pure returns (bool) { // Get the number of bits in a and b // Since log(x) returns in the interval [-64, 63), add 64 to be in the interval [0, 127) - uint256 a_bits = uint256(int256(toInt(log_2(a)) + 64)); - uint256 b_bits = uint256(int256(toInt(log_2(b)) + 64)); + uint256 a_bits = uint256(int256(convert(log2(a)) + 64)); + uint256 b_bits = uint256(int256(convert(log2(b)) + 64)); // a and b lengths may differ in 1 bit, so the shift should take into account the longest - uint256 shift_bits = (a_bits > b_bits) - ? (a_bits - bits) - : (b_bits - bits); + uint256 shift_bits = (a_bits > b_bits) ? (a_bits - bits) : (b_bits - bits); // Get the _bits_ most significant bits of a and b - uint256 a_msb = most_significant_bits(a, bits) >> shift_bits; - uint256 b_msb = most_significant_bits(b, bits) >> shift_bits; + uint256 a_msb = msb(a, bits) >> shift_bits; + uint256 b_msb = msb(b, bits) >> shift_bits; // See if they are equal within 1 bit precision // This could be modified to get the precision as a parameter to the function @@ -172,101 +108,76 @@ contract CryticPRBMathUD59x18Properties { /* ================================================================ Library wrappers. - These functions allow calling the ABDKMath64x64 library. + These functions allow calling the PRBMathSD59x18 library. ================================================================ */ - function debug(string calldata x, int128 y) public { - emit Value(x, ABDKMath64x64.toInt(y)); - } - - function fromInt(int256 x) public pure returns (int128) { - return ABDKMath64x64.fromInt(x); + function debug(string calldata x, int256 y) public { + emit Value(x, y); } - function toInt(int128 x) public pure returns (int64) { - return ABDKMath64x64.toInt(x); + // Wrapper for external try/catch calls + function add(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return helpersAdd(x,y); } - function fromUInt(uint256 x) public pure returns (int128) { - return ABDKMath64x64.fromUInt(x); + // Wrapper for external try/catch calls + function sub(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return helpersSub(x,y); } - function toUInt(int128 x) public pure returns (uint64) { - return ABDKMath64x64.toUInt(x); + // Wrapper for external try/catch calls + function mul(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return helpersMul(x,y); } - function add(int128 x, int128 y) public pure returns (int128) { - return ABDKMath64x64.add(x, y); + function div(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return helpersDiv(x,y); } - function sub(int128 x, int128 y) public pure returns (int128) { - return ABDKMath64x64.sub(x, y); + function neg(SD59x18 x) public pure returns (SD59x18) { + return SD59x18.wrap(-SD59x18.unwrap(x)); } - function mul(int128 x, int128 y) public pure returns (int128) { - return ABDKMath64x64.mul(x, y); + function abs(SD59x18 x) public pure returns (SD59x18) { + return helpersAbs(x); } - function mulu(int128 x, uint256 y) public pure returns (uint256) { - return ABDKMath64x64.mulu(x, y); + function ln(SD59x18 x) public pure returns (SD59x18) { + return helpersAbs(x); } - function div(int128 x, int128 y) public pure returns (int128) { - return ABDKMath64x64.div(x, y); + function exp(SD59x18 x) public pure returns (SD59x18) { + return helpersExp(x); } - function neg(int128 x) public pure returns (int128) { - return ABDKMath64x64.neg(x); + function exp2(SD59x18 x) public pure returns (SD59x18) { + return helpersExp2(x); } - function abs(int128 x) public pure returns (int128) { - return ABDKMath64x64.abs(x); + function log_2(SD59x18 x) public pure returns (SD59x18) { + return helpersLog2(x); } - function inv(int128 x) public pure returns (int128) { - return ABDKMath64x64.inv(x); + function sqrt(SD59x18 x) public pure returns (SD59x18) { + return helpersSqrt(x); } - function avg(int128 x, int128 y) public pure returns (int128) { - return ABDKMath64x64.avg(x, y); + function pow(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return helpersPow(x, y); } - function gavg(int128 x, int128 y) public pure returns (int128) { - return ABDKMath64x64.gavg(x, y); + function avg(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return helpersAvg(x, y); } - function pow(int128 x, uint256 y) public pure returns (int128) { - return ABDKMath64x64.pow(x, y); + function inv(SD59x18 x) public pure returns (SD59x18) { + return helpersInv(x); } - function sqrt(int128 x) public pure returns (int128) { - return ABDKMath64x64.sqrt(x); - } - - function log_2(int128 x) public pure returns (int128) { - return ABDKMath64x64.log_2(x); - } - - function ln(int128 x) public pure returns (int128) { - return ABDKMath64x64.ln(x); - } - - function exp_2(int128 x) public pure returns (int128) { - return ABDKMath64x64.exp_2(x); - } - - function exp(int128 x) public pure returns (int128) { - return ABDKMath64x64.exp(x); - } - - /* ================================================================ - Start of tests - ================================================================ */ - /* ================================================================ TESTS FOR FUNCTION add() - ================================================================ */ + ================================================================ */ /* ================================================================ Tests for arithmetic properties. @@ -276,45 +187,46 @@ contract CryticPRBMathUD59x18Properties { // Test for commutative property // x + y == y + x - function add_test_commutative(int128 x, int128 y) public pure { - int128 x_y = add(x, y); - int128 y_x = add(y, x); + function add_test_commutative(SD59x18 x, SD59x18 y) public pure { + SD59x18 x_y = x.add(y); + SD59x18 y_x = y.add(x); - assert(x_y == y_x); + assert(x_y.eq(y_x)); } // Test for associative property // (x + y) + z == x + (y + z) - function add_test_associative(int128 x, int128 y, int128 z) public pure { - int128 x_y = add(x, y); - int128 y_z = add(y, z); - int128 xy_z = add(x_y, z); - int128 x_yz = add(x, y_z); + function add_test_associative(SD59x18 x, SD59x18 y, SD59x18 z) public pure { + SD59x18 x_y = x.add(y); + SD59x18 y_z = y.add(z); + SD59x18 xy_z = x_y.add(z); + SD59x18 x_yz = x.add(y_z); - assert(xy_z == x_yz); + assert(xy_z.eq(x_yz)); } // Test for identity operation // x + 0 == x (equivalent to x + (-x) == 0) - function add_test_identity(int128 x) public view { - int128 x_0 = add(x, ZERO_FP); + function add_test_identity(SD59x18 x) public view { + SD59x18 x_0 = x.add(ZERO_FP); - assert(x_0 == x); - assert(add(x, neg(x)) == ZERO_FP); + assert(x.eq(x_0)); + assert(x.sub(x).eq(ZERO_FP)); } // Test that the result increases or decreases depending // on the value to be added - function add_test_values(int128 x, int128 y) public view { - int128 x_y = add(x, y); + function add_test_values(SD59x18 x, SD59x18 y) public view { + SD59x18 x_y = x.add(y); - if (y >= ZERO_FP) { - assert(x_y >= x); + if (y.gte(ZERO_FP)) { + assert(x_y.gte(x)); } else { - assert(x_y < x); + assert(x_y.lt(x)); } } + /* ================================================================ Tests for overflow and edge cases. These should make sure that the function reverts on overflow and @@ -323,24 +235,20 @@ contract CryticPRBMathUD59x18Properties { // The result of the addition must be between the maximum // and minimum allowed values for 64x64 - function add_test_range(int128 x, int128 y) public view { - int128 result; - try this.add(x, y) { - result = this.add(x, y); - assert(result <= MAX_64x64 && result >= MIN_64x64); + function add_test_range(SD59x18 x, SD59x18 y) public view { + try this.add(x, y) returns (SD59x18 result) { + assert(result.lte(MAX_SD59x18) && result.gte(MIN_SD59x18)); } catch { // If it reverts, just ignore } } // Adding zero to the maximum value shouldn't revert, as it is valid - // Moreover, the result must be MAX_64x64 + // Moreover, the result must be MAX_SD59x18 function add_test_maximum_value() public view { - int128 result; - try this.add(MAX_64x64, ZERO_FP) { + try this.add(MAX_SD59x18, ZERO_FP) returns (SD59x18 result) { // Expected behaviour, does not revert - result = this.add(MAX_64x64, ZERO_FP); - assert(result == MAX_64x64); + assert(result.eq(MAX_SD59x18)); } catch { assert(false); } @@ -348,7 +256,7 @@ contract CryticPRBMathUD59x18Properties { // Adding one to the maximum value should revert, as it is out of range function add_test_maximum_value_plus_one() public view { - try this.add(MAX_64x64, ONE_FP) { + try this.add(MAX_SD59x18, ONE_FP) { assert(false); } catch { // Expected behaviour, reverts @@ -356,13 +264,11 @@ contract CryticPRBMathUD59x18Properties { } // Adding zero to the minimum value shouldn't revert, as it is valid - // Moreover, the result must be MIN_64x64 + // Moreover, the result must be MIN_SD59x18 function add_test_minimum_value() public view { - int128 result; - try this.add(MIN_64x64, ZERO_FP) { + try this.add(MIN_SD59x18, ZERO_FP) returns (SD59x18 result) { // Expected behaviour, does not revert - result = this.add(MIN_64x64, ZERO_FP); - assert(result == MIN_64x64); + assert(result.eq(MIN_SD59x18)); } catch { assert(false); } @@ -370,18 +276,21 @@ contract CryticPRBMathUD59x18Properties { // Adding minus one to the maximum value should revert, as it is out of range function add_test_minimum_value_plus_negative_one() public view { - try this.add(MIN_64x64, MINUS_ONE_FP) { + try this.add(MIN_SD59x18, MINUS_ONE_FP) { assert(false); } catch { // Expected behaviour, reverts } } + + + /* ================================================================ TESTS FOR FUNCTION sub() - ================================================================ */ + ================================================================ */ /* ================================================================ Tests for arithmetic properties. @@ -391,54 +300,54 @@ contract CryticPRBMathUD59x18Properties { // Test equivalence to addition // x - y == x + (-y) - function sub_test_equivalence_to_addition(int128 x, int128 y) public pure { - int128 minus_y = neg(y); - int128 addition = add(x, minus_y); - int128 subtraction = sub(x, y); + function sub_test_equivalence_to_addition(SD59x18 x, SD59x18 y) public pure { + SD59x18 minus_y = neg(y); + SD59x18 addition = x.add(minus_y); + SD59x18 subtraction = x.sub(y); - assert(addition == subtraction); + assert(addition.eq(subtraction)); } // Test for non-commutative property // x - y == -(y - x) - function sub_test_non_commutative(int128 x, int128 y) public pure { - int128 x_y = sub(x, y); - int128 y_x = sub(y, x); - - assert(x_y == neg(y_x)); + function sub_test_non_commutative(SD59x18 x, SD59x18 y) public pure { + SD59x18 x_y = x.sub(y); + SD59x18 y_x = y.sub(x); + + assert(x_y.eq(neg(y_x))); } // Test for identity operation // x - 0 == x (equivalent to x - x == 0) - function sub_test_identity(int128 x) public view { - int128 x_0 = sub(x, ZERO_FP); + function sub_test_identity(SD59x18 x) public view { + SD59x18 x_0 = x.sub(ZERO_FP); - assert(x_0 == x); - assert(sub(x, x) == ZERO_FP); + assert(x_0.eq(x)); + assert(x.sub(x).eq(ZERO_FP)); } // Test for neutrality over addition and subtraction // (x - y) + y == (x + y) - y == x - function sub_test_neutrality(int128 x, int128 y) public pure { - int128 x_minus_y = sub(x, y); - int128 x_plus_y = add(x, y); + function sub_test_neutrality(SD59x18 x, SD59x18 y) public pure { + SD59x18 x_minus_y = x.sub(y); + SD59x18 x_plus_y = x.add(y); - int128 x_minus_y_plus_y = add(x_minus_y, y); - int128 x_plus_y_minus_y = sub(x_plus_y, y); - - assert(x_minus_y_plus_y == x_plus_y_minus_y); - assert(x_minus_y_plus_y == x); + SD59x18 x_minus_y_plus_y = x_minus_y.add(y); + SD59x18 x_plus_y_minus_y = x_plus_y.sub(y); + + assert(x_minus_y_plus_y.eq(x_plus_y_minus_y)); + assert(x_minus_y_plus_y.eq(x)); } // Test that the result increases or decreases depending // on the value to be subtracted - function sub_test_values(int128 x, int128 y) public view { - int128 x_y = sub(x, y); + function sub_test_values(SD59x18 x, SD59x18 y) public view { + SD59x18 x_y = x.sub(y); - if (y >= ZERO_FP) { - assert(x_y <= x); + if (y.gte(ZERO_FP)) { + assert(x_y.lte(x)); } else { - assert(x_y > x); + assert(x_y.gt(x)); } } @@ -449,34 +358,30 @@ contract CryticPRBMathUD59x18Properties { ================================================================ */ // The result of the subtraction must be between the maximum - // and minimum allowed values for 64x64 - function sub_test_range(int128 x, int128 y) public view { - int128 result; - try this.sub(x, y) { - result = this.sub(x, y); - assert(result <= MAX_64x64 && result >= MIN_64x64); + // and minimum allowed values for SD59x18 + function sub_test_range(SD59x18 x, SD59x18 y) public view { + try this.sub(x, y) returns (SD59x18 result) { + assert(result.lte(MAX_SD59x18) && result.gte(MIN_SD59x18)); } catch { // If it reverts, just ignore } } // Subtracting zero from the maximum value shouldn't revert, as it is valid - // Moreover, the result must be MAX_64x64 + // Moreover, the result must be MAX_SD59x18 function sub_test_maximum_value() public view { - int128 result; - try this.sub(MAX_64x64, ZERO_FP) { + try this.sub(MAX_SD59x18, ZERO_FP) returns (SD59x18 result) { // Expected behaviour, does not revert - result = this.sub(MAX_64x64, ZERO_FP); - assert(result == MAX_64x64); + assert(result.eq(MAX_SD59x18)); } catch { assert(false); } } - // Subtracting minus one from the maximum value should revert, + // Subtracting minus one from the maximum value should revert, // as it is out of range function sub_test_maximum_value_minus_neg_one() public view { - try this.sub(MAX_64x64, MINUS_ONE_FP) { + try this.sub(MAX_SD59x18, MINUS_ONE_FP) { assert(false); } catch { // Expected behaviour, reverts @@ -484,13 +389,11 @@ contract CryticPRBMathUD59x18Properties { } // Subtracting zero from the minimum value shouldn't revert, as it is valid - // Moreover, the result must be MIN_64x64 + // Moreover, the result must be MIN_SD59x18 function sub_test_minimum_value() public view { - int128 result; - try this.sub(MIN_64x64, ZERO_FP) { + try this.sub(MIN_SD59x18, ZERO_FP) returns (SD59x18 result) { // Expected behaviour, does not revert - result = this.sub(MIN_64x64, ZERO_FP); - assert(result == MIN_64x64); + assert(result.eq(MIN_SD59x18)); } catch { assert(false); } @@ -498,18 +401,20 @@ contract CryticPRBMathUD59x18Properties { // Subtracting one from the minimum value should revert, as it is out of range function sub_test_minimum_value_minus_one() public view { - try this.sub(MIN_64x64, ONE_FP) { + try this.sub(MIN_SD59x18, ONE_FP) { assert(false); } catch { // Expected behaviour, reverts } } + + /* ================================================================ TESTS FOR FUNCTION mul() - ================================================================ */ + ================================================================ */ /* ================================================================ Tests for arithmetic properties. @@ -519,92 +424,85 @@ contract CryticPRBMathUD59x18Properties { // Test for commutative property // x * y == y * x - function mul_test_commutative(int128 x, int128 y) public pure { - int128 x_y = mul(x, y); - int128 y_x = mul(y, x); + function mul_test_commutative(SD59x18 x, SD59x18 y) public pure { + SD59x18 x_y = x.mul(y); + SD59x18 y_x = y.mul(x); - assert(x_y == y_x); + assert(x_y.eq(y_x)); } // Test for associative property // (x * y) * z == x * (y * z) - function mul_test_associative(int128 x, int128 y, int128 z) public view { - int128 x_y = mul(x, y); - int128 y_z = mul(y, z); - int128 xy_z = mul(x_y, z); - int128 x_yz = mul(x, y_z); + function mul_test_associative(SD59x18 x, SD59x18 y, SD59x18 z) public view { + SD59x18 x_y = x.mul(y); + SD59x18 y_z = y.mul(z); + SD59x18 xy_z = x_y.mul(z); + SD59x18 x_yz = x.mul(y_z); + // todo check if this should not be used // Failure if all significant digits are lost - require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + /*require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); require(significant_bits_after_mult(y, z) > REQUIRED_SIGNIFICANT_BITS); - require( - significant_bits_after_mult(x_y, z) > REQUIRED_SIGNIFICANT_BITS - ); - require( - significant_bits_after_mult(x, y_z) > REQUIRED_SIGNIFICANT_BITS - ); + require(significant_bits_after_mult(x_y, z) > REQUIRED_SIGNIFICANT_BITS); + require(significant_bits_after_mult(x, y_z) > REQUIRED_SIGNIFICANT_BITS);*/ - assert(equal_within_tolerance(xy_z, x_yz, ONE_TENTH_FP)); + //assert(equal_within_tolerance(xy_z, x_yz, ONE_TENTH_FP)); + assert(xy_z.eq(x_yz)); } // Test for distributive property // x * (y + z) == x * y + x * z - function mul_test_distributive(int128 x, int128 y, int128 z) public view { - int128 y_plus_z = add(y, z); - int128 x_times_y_plus_z = mul(x, y_plus_z); + function mul_test_distributive(SD59x18 x, SD59x18 y, SD59x18 z) public view { + SD59x18 y_plus_z = y.add(z); + SD59x18 x_times_y_plus_z = x.mul(y_plus_z); - int128 x_times_y = mul(x, y); - int128 x_times_z = mul(x, z); + SD59x18 x_times_y = x.mul(y); + SD59x18 x_times_z = x.mul(z); + // todo check if this should not be used // Failure if all significant digits are lost - require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + /*require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); require(significant_bits_after_mult(x, z) > REQUIRED_SIGNIFICANT_BITS); - require( - significant_bits_after_mult(x, y_plus_z) > REQUIRED_SIGNIFICANT_BITS - ); + require(significant_bits_after_mult(x, y_plus_z) > REQUIRED_SIGNIFICANT_BITS); - assert( - equal_within_tolerance( - add(x_times_y, x_times_z), - x_times_y_plus_z, - ONE_TENTH_FP - ) - ); + assert(equal_within_tolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP));*/ + assert(x_times_y.add(x_times_z).eq(x_times_y_plus_z)); } // Test for identity operation // x * 1 == x (also check that x * 0 == 0) - function mul_test_identity(int128 x) public view { - int128 x_1 = mul(x, ONE_FP); - int128 x_0 = mul(x, ZERO_FP); + function mul_test_identity(SD59x18 x) public view { + SD59x18 x_1 = x.mul(ONE_FP); + SD59x18 x_0 = x.mul(ZERO_FP); - assert(x_0 == ZERO_FP); - assert(x_1 == x); + assert(x_0.eq(ZERO_FP)); + assert(x_1.eq(x)); } // Test that the result increases or decreases depending // on the value to be added - function mul_test_values(int128 x, int128 y) public view { - require(x != ZERO_FP && y != ZERO_FP); + function mul_test_values(SD59x18 x, SD59x18 y) public view { + require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); - int128 x_y = mul(x, y); + SD59x18 x_y = x.mul(y); - require(significant_digits_lost_in_mult(x, y) == false); + //require(significant_digits_lost_in_mult(x, y) == false); - if (x >= ZERO_FP) { - if (y >= ONE_FP) { - assert(x_y >= x); + if (x.gte(ZERO_FP)) { + if (y.gte(ONE_FP)) { + assert(x_y.gte(x)); } else { - assert(x_y <= x); + assert(x_y.lte(x)); } } else { - if (y >= ONE_FP) { - assert(x_y <= x); + if (y.gte(ONE_FP)) { + assert(x_y.lte(x)); } else { - assert(x_y >= x); + assert(x_y.gte(x)); } } } + /* ================================================================ Tests for overflow and edge cases. @@ -614,47 +512,42 @@ contract CryticPRBMathUD59x18Properties { // The result of the multiplication must be between the maximum // and minimum allowed values for 64x64 - function mul_test_range(int128 x, int128 y) public view { - int128 result; - try this.mul(x, y) { - result = this.mul(x, y); - assert(result <= MAX_64x64 && result >= MIN_64x64); + function mul_test_range(SD59x18 x, SD59x18 y) public view { + try this.mul(x, y) returns(SD59x18 result) { + assert(result.lte(MAX_SD59x18) && result.gte(MIN_SD59x18)); } catch { // If it reverts, just ignore } } // Multiplying the maximum value times one shouldn't revert, as it is valid - // Moreover, the result must be MAX_64x64 + // Moreover, the result must be MAX_SD59x18 function mul_test_maximum_value() public view { - int128 result; - try this.mul(MAX_64x64, ONE_FP) { + try this.mul(MAX_SD59x18, ONE_FP) returns (SD59x18 result) { // Expected behaviour, does not revert - result = this.mul(MAX_64x64, ONE_FP); - assert(result == MAX_64x64); + assert(result.eq(MAX_SD59x18)); } catch { assert(false); } } // Multiplying the minimum value times one shouldn't revert, as it is valid - // Moreover, the result must be MIN_64x64 + // Moreover, the result must be MIN_SD59x18 function mul_test_minimum_value() public view { - int128 result; - try this.mul(MIN_64x64, ONE_FP) { + try this.mul(MIN_SD59x18.add(ONE_FP), ONE_FP) returns (SD59x18 result) { // Expected behaviour, does not revert - result = this.mul(MIN_64x64, ONE_FP); - assert(result == MIN_64x64); + assert(result.eq(MIN_SD59x18)); } catch { assert(false); } } + /* ================================================================ TESTS FOR FUNCTION div() - ================================================================ */ + ================================================================ */ /* ================================================================ Tests for arithmetic properties. @@ -662,57 +555,64 @@ contract CryticPRBMathUD59x18Properties { with math rules and expected behaviour. ================================================================ */ + /// Requirements: + /// - Refer to the requirements in {Common.mulDiv}. + /// - None of the inputs can be `MIN_SD59x18`. + /// - The denominator must not be zero. + /// - The result must fit in SD59x18. + // Test for identity property // x / 1 == x (equivalent to x / x == 1) // Moreover, x/x should not revert unless x == 0 - function div_test_division_identity(int128 x) public view { - int128 div_1 = div(x, ONE_FP); - assert(x == div_1); + function div_test_division_identity(SD59x18 x) public view { + SD59x18 div_1 = div(x, ONE_FP); + assert(x.eq(div_1)); - int128 div_x; + SD59x18 div_x; try this.div(x, x) { // This should always equal one div_x = div(x, x); - assert(div_x == ONE_FP); + assert(div_x.eq(ONE_FP)); } catch { // The only allowed case to revert is if x == 0 - assert(x == ZERO_FP); + assert(x.eq(ZERO_FP)); } } + // Test for negative divisor // x / -y == -(x / y) - function div_test_negative_divisor(int128 x, int128 y) public view { - require(y < ZERO_FP); + function div_test_negative_divisor(SD59x18 x, SD59x18 y) public view { + require(y.lt(ZERO_FP)); - int128 x_y = div(x, y); - int128 x_minus_y = div(x, neg(y)); + SD59x18 x_y = div(x, y); + SD59x18 x_minus_y = div(x, neg(y)); - assert(x_y == neg(x_minus_y)); + assert(x_y.eq(neg(x_minus_y))); } // Test for division with 0 as numerator // 0 / x = 0 - function div_test_division_num_zero(int128 x) public view { - require(x != ZERO_FP); + function div_test_division_num_zero(SD59x18 x) public view { + require(x.neq(ZERO_FP)); - int128 div_0 = div(ZERO_FP, x); + SD59x18 div_0 = div(ZERO_FP, x); - assert(ZERO_FP == div_0); + assert(ZERO_FP.eq(div_0)); } // Test that the absolute value of the result increases or // decreases depending on the denominator's absolute value - function div_test_values(int128 x, int128 y) public view { - require(y != ZERO_FP); + function div_test_values(SD59x18 x, SD59x18 y) public view { + require(y.neq(ZERO_FP)); - int128 x_y = abs(div(x, y)); + SD59x18 x_y = abs(div(x, y)); - if (abs(y) >= ONE_FP) { - assert(x_y <= abs(x)); + if (abs(y).gte(ONE_FP)) { + assert(x_y.lte(abs(x))); } else { - assert(x_y >= abs(x)); + assert(x_y.gte(abs(x))); } } @@ -723,7 +623,7 @@ contract CryticPRBMathUD59x18Properties { ================================================================ */ // Test for division by zero - function div_test_div_by_zero(int128 x) public view { + function div_test_div_by_zero(SD59x18 x) public view { try this.div(x, ZERO_FP) { // Unexpected, this should revert assert(false); @@ -733,35 +633,35 @@ contract CryticPRBMathUD59x18Properties { } // Test for division by a large value, the result should be less than one - function div_test_maximum_denominator(int128 x) public view { - int128 div_large = div(x, MAX_64x64); + function div_test_maximum_denominator(SD59x18 x) public view { + SD59x18 div_large = div(x, MAX_SD59x18); - assert(abs(div_large) <= ONE_FP); + assert(abs(div_large).lte(ONE_FP)); } // Test for division of a large value // This should revert if |x| < 1 as it would return a value higher than max - function div_test_maximum_numerator(int128 x) public view { - int128 div_large; + function div_test_maximum_numerator(SD59x18 x) public view { + SD59x18 div_large; - try this.div(MAX_64x64, x) { + try this.div(MAX_SD59x18, x) { // If it didn't revert, then |x| >= 1 - div_large = div(MAX_64x64, x); + div_large = div(MAX_SD59x18, x); - assert(abs(x) >= ONE_FP); + assert(abs(x).gte(ONE_FP)); } catch { // Expected revert as result is higher than max } } // Test for values in range - function div_test_range(int128 x, int128 y) public view { - int128 result; + function div_test_range(SD59x18 x, SD59x18 y) public view { + SD59x18 result; try this.div(x, y) { // If it returns a value, it must be in range result = div(x, y); - assert(result <= MAX_64x64 && result >= MIN_64x64); + assert(result.lte(MAX_SD59x18) && result.gte(MIN_SD59x18)); } catch { // Otherwise, it should revert } @@ -771,7 +671,7 @@ contract CryticPRBMathUD59x18Properties { TESTS FOR FUNCTION neg() - ================================================================ */ + ================================================================ */ /* ================================================================ Tests for mathematical properties. @@ -781,18 +681,18 @@ contract CryticPRBMathUD59x18Properties { // Test for the double negation // -(-x) == x - function neg_test_double_negation(int128 x) public pure { - int128 double_neg = neg(neg(x)); + function neg_test_double_negation(SD59x18 x) public pure { + SD59x18 double_neg = neg(neg(x)); - assert(x == double_neg); + assert(x.eq(double_neg)); } // Test for the identity operation // x + (-x) == 0 - function neg_test_identity(int128 x) public view { - int128 neg_x = neg(x); + function neg_test_identity(SD59x18 x) public view { + SD59x18 neg_x = neg(x); - assert(add(x, neg_x) == ZERO_FP); + assert(add(x, neg_x).eq(ZERO_FP)); } /* ================================================================ @@ -806,23 +706,25 @@ contract CryticPRBMathUD59x18Properties { function neg_test_zero() public view { int128 neg_x = neg(ZERO_FP); - assert(neg_x == ZERO_FP); + assert(neg_x.eq(ZERO_FP)); } + // todo check what is used for SD59x18 // Test for the maximum value case // Since this is implementation-dependant, we will actually test with MAX_64x64-EPS function neg_test_maximum() public view { - try this.neg(sub(MAX_64x64, EPSILON)) { + try this.neg(sub(MAX_SD59x18, EPSILON)) { // Expected behaviour, does not revert } catch { assert(false); } } + // todo check what is used for SD59x18 // Test for the minimum value case // Since this is implementation-dependant, we will actually test with MIN_64x64+EPS function neg_test_minimum() public view { - try this.neg(add(MIN_64x64, EPSILON)) { + try this.neg(add(MIN_SD59x18, EPSILON)) { // Expected behaviour, does not revert } catch { assert(false); @@ -833,7 +735,7 @@ contract CryticPRBMathUD59x18Properties { TESTS FOR FUNCTION abs() - ================================================================ */ + ================================================================ */ /* ================================================================ Tests for mathematical properties. @@ -841,29 +743,32 @@ contract CryticPRBMathUD59x18Properties { with math rules and expected behaviour. ================================================================ */ + /// @dev Requirements: + /// - x must be greater than `MIN_SD59x18`. + // Test that the absolute value is always positive - function abs_test_positive(int128 x) public view { + function abs_test_positive(SD59x18 x) public view { int128 abs_x = abs(x); - assert(abs_x >= ZERO_FP); + assert(abs_x.gte(ZERO_FP)); } // Test that the absolute value of a number equals the // absolute value of the negative of the same number - function abs_test_negative(int128 x) public pure { - int128 abs_x = abs(x); - int128 abs_minus_x = abs(neg(x)); + function abs_test_negative(SD59x18 x) public pure { + SD59x18 abs_x = abs(x); + SD59x18 abs_minus_x = abs(neg(x)); - assert(abs_x == abs_minus_x); + assert(abs_x.eq(abs_minus_x)); } // Test the multiplicativeness property // | x * y | == |x| * |y| - function abs_test_multiplicativeness(int128 x, int128 y) public pure { - int128 abs_x = abs(x); - int128 abs_y = abs(y); - int128 abs_xy = abs(mul(x, y)); - int128 abs_x_abs_y = mul(abs_x, abs_y); + function abs_test_multiplicativeness(SD59x18 x, SD59x18 y) public pure { + SD59x18 abs_x = abs(x); + SD59x18 abs_y = abs(y); + SD59x18 abs_xy = abs(mul(x, y)); + SD59x18 abs_x_abs_y = mul(abs_x, abs_y); // Failure if all significant digits are lost require(significant_digits_lost_in_mult(abs_x, abs_y) == false); @@ -874,12 +779,12 @@ contract CryticPRBMathUD59x18Properties { // Test the subadditivity property // | x + y | <= |x| + |y| - function abs_test_subadditivity(int128 x, int128 y) public pure { - int128 abs_x = abs(x); - int128 abs_y = abs(y); - int128 abs_xy = abs(add(x, y)); + function abs_test_subadditivity(SD59x18 x, SD59x18 y) public pure { + SD59x18 abs_x = abs(x); + SD59x18 abs_y = abs(y); + SD59x18 abs_xy = abs(add(x, y)); - assert(abs_xy <= add(abs_x, abs_y)); + assert(abs_xy.lte(add(abs_x, abs_y))); } /* ================================================================ @@ -890,12 +795,12 @@ contract CryticPRBMathUD59x18Properties { // Test the zero-case | 0 | = 0 function abs_test_zero() public view { - int128 abs_zero; + SD59x18 abs_zero; try this.abs(ZERO_FP) { // If it doesn't revert, the value must be zero abs_zero = this.abs(ZERO_FP); - assert(abs_zero == ZERO_FP); + assert(abs_zero.eq(ZERO_FP)); } catch { // Unexpected, the function must not revert here assert(false); @@ -904,23 +809,23 @@ contract CryticPRBMathUD59x18Properties { // Test the maximum value function abs_test_maximum() public view { - int128 abs_max; + SD59x18 abs_max; - try this.abs(MAX_64x64) { - // If it doesn't revert, the value must be MAX_64x64 - abs_max = this.abs(MAX_64x64); - assert(abs_max == MAX_64x64); + try this.abs(MAX_SD59x18) { + // If it doesn't revert, the value must be MAX_SD59x18 + abs_max = this.abs(MAX_SD59x18); + assert(abs_max.eq(MAX_SD59x18)); } catch {} } // Test the minimum value function abs_test_minimum() public view { - int128 abs_min; + SD59x18 abs_min; - try this.abs(MIN_64x64) { - // If it doesn't revert, the value must be the negative of MIN_64x64 - abs_min = this.abs(MIN_64x64); - assert(abs_min == neg(MIN_64x64)); + try this.abs(MIN_SD59x18) { + // If it doesn't revert, the value must be the negative of MIN_SD59x18 + abs_min = this.abs(MIN_SD59x18); + assert(abs_min == neg(MIN_SD59x18)); } catch {} } @@ -928,7 +833,7 @@ contract CryticPRBMathUD59x18Properties { TESTS FOR FUNCTION inv() - ================================================================ */ + ================================================================ */ /* ================================================================ Tests for mathematical properties. @@ -936,39 +841,41 @@ contract CryticPRBMathUD59x18Properties { with math rules and expected behaviour. ================================================================ */ + /// - x must not be zero. + // Test that the inverse of the inverse is close enough to the // original number - function inv_test_double_inverse(int128 x) public view { - require(x != ZERO_FP); + function inv_test_double_inverse(SD59x18 x) public view { + require(x.neq(ZERO_FP)); - int128 double_inv_x = inv(inv(x)); + SD59x18 double_inv_x = inv(inv(x)); // The maximum loss of precision will be 2 * log2(x) bits rounded up - uint256 loss = 2 * toUInt(log_2(x)) + 2; + uint256 loss = 2 * SD59x18.intoUint256(log_2(x)) + 2; assert(equal_within_precision(x, double_inv_x, loss)); } // Test equivalence with division - function inv_test_division(int128 x) public view { - require(x != ZERO_FP); + function inv_test_division(SD59x18 x) public view { + require(x.neq(ZERO_FP)); - int128 inv_x = inv(x); - int128 div_1_x = div(ONE_FP, x); + SD59x18 inv_x = inv(x); + SD59x18 div_1_x = div(ONE_FP, x); - assert(inv_x == div_1_x); + assert(inv_x.eq(div_1_x)); } // Test the anticommutativity of the division // x / y == 1 / (y / x) function inv_test_division_noncommutativity( - int128 x, - int128 y + SD59x18 x, + SD59x18 y ) public view { - require(x != ZERO_FP && y != ZERO_FP); + require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); - int128 x_y = div(x, y); - int128 y_x = div(y, x); + SD59x18 x_y = div(x, y); + SD59x18 y_x = div(y, x); require( significant_bits_after_mult(x, inv(y)) > REQUIRED_SIGNIFICANT_BITS @@ -981,36 +888,35 @@ contract CryticPRBMathUD59x18Properties { // Test the multiplication of inverses // 1/(x * y) == 1/x * 1/y - function inv_test_multiplication(int128 x, int128 y) public view { - require(x != ZERO_FP && y != ZERO_FP); + function inv_test_multiplication(SD59x18 x, SD59x18 y) public view { + require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); - int128 inv_x = inv(x); - int128 inv_y = inv(y); - int128 inv_x_times_inv_y = mul(inv_x, inv_y); + SD59x18 inv_x = inv(x); + SD59x18 inv_y = inv(y); + SD59x18 inv_x_times_inv_y = mul(inv_x, inv_y); - int128 x_y = mul(x, y); - int128 inv_x_y = inv(x_y); + SD59x18 x_y = mul(x, y); + SD59x18 inv_x_y = inv(x_y); require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); require( - significant_bits_after_mult(inv_x, inv_y) > - REQUIRED_SIGNIFICANT_BITS + significant_bits_after_mult(inv_x, inv_y) > REQUIRED_SIGNIFICANT_BITS ); // The maximum loss of precision is given by the formula: // 2 * | log_2(x) - log_2(y) | + 1 - uint256 loss = 2 * toUInt(abs(log_2(x) - log_2(y))) + 1; + uint256 loss = 2 * SD59x18.intoUint256(abs(log_2(x) - log_2(y))) + 1; assert(equal_within_precision(inv_x_y, inv_x_times_inv_y, loss)); } // Test identity property // Intermediate result should have at least REQUIRED_SIGNIFICANT_BITS - function inv_test_identity(int128 x) public view { - require(x != ZERO_FP); + function inv_test_identity(SD59x18 x) public view { + require(x.neq(ZERO_FP)); - int128 inv_x = inv(x); - int128 identity = mul(inv_x, x); + SD59x18 inv_x = inv(x); + SD59x18 identity = mul(inv_x, x); require( significant_bits_after_mult(x, inv_x) > REQUIRED_SIGNIFICANT_BITS @@ -1023,28 +929,28 @@ contract CryticPRBMathUD59x18Properties { // Test that the absolute value of the result is in range zero-one // if x is greater than one, else, the absolute value of the result // must be greater than one - function inv_test_values(int128 x) public view { - require(x != ZERO_FP); + function inv_test_values(SD59x18 x) public view { + require(x.neq(ZERO_FP)); - int128 abs_inv_x = abs(inv(x)); + SD59x18 abs_inv_x = abs(inv(x)); - if (abs(x) >= ONE_FP) { - assert(abs_inv_x <= ONE_FP); + if (abs(x).gte(ONE_FP)) { + assert(abs_inv_x.lte(ONE_FP)); } else { - assert(abs_inv_x > ONE_FP); + assert(abs_inv_x.gt(ONE_FP)); } } // Test that the result has the same sign as the argument - function inv_test_sign(int128 x) public view { - require(x != ZERO_FP); + function inv_test_sign(SD59x18 x) public view { + require(x.neq(ZERO_FP)); - int128 inv_x = inv(x); + SD59x18 inv_x = inv(x); - if (x > ZERO_FP) { - assert(inv_x > ZERO_FP); + if (x.gt(ZERO_FP)) { + assert(inv_x.gt(ZERO_FP)); } else { - assert(inv_x < ZERO_FP); + assert(inv_x.lt(ZERO_FP)); } } @@ -1064,10 +970,10 @@ contract CryticPRBMathUD59x18Properties { // Test the maximum value case, should not revert, and be close to zero function inv_test_maximum() public view { - int128 inv_maximum; + SD59x18 inv_maximum; - try this.inv(MAX_64x64) { - inv_maximum = this.inv(MAX_64x64); + try this.inv(MAX_SD59x18) { + inv_maximum = this.inv(MAX_SD59x18); assert(equal_within_precision(inv_maximum, ZERO_FP, 10)); } catch { // Unexpected, the function must not revert @@ -1077,10 +983,10 @@ contract CryticPRBMathUD59x18Properties { // Test the minimum value case, should not revert, and be close to zero function inv_test_minimum() public view { - int128 inv_minimum; + SD59x18 inv_minimum; - try this.inv(MAX_64x64) { - inv_minimum = this.inv(MAX_64x64); + try this.inv(MIN_SD59x18) { + inv_minimum = this.inv(MIN_SD59x18); assert(equal_within_precision(abs(inv_minimum), ZERO_FP, 10)); } catch { // Unexpected, the function must not revert @@ -1088,11 +994,12 @@ contract CryticPRBMathUD59x18Properties { } } + /* ================================================================ TESTS FOR FUNCTION avg() - ================================================================ */ + ================================================================ */ /* ================================================================ Tests for arithmetic properties. @@ -1102,31 +1009,31 @@ contract CryticPRBMathUD59x18Properties { // Test that the result is between the two operands // avg(x, y) >= min(x, y) && avg(x, y) <= max(x, y) - function avg_test_values_in_range(int128 x, int128 y) public pure { - int128 avg_xy = avg(x, y); + function avg_test_values_in_range(SD59x18 x, SD59x18 y) public pure { + SD59x18 avg_xy = avg(x, y); - if (x >= y) { - assert(avg_xy >= y && avg_xy <= x); + if (x.gte(y)) { + assert(avg_xy.gte(y) && avg_xy.lte(x)); } else { - assert(avg_xy >= x && avg_xy <= y); + assert(avg_xy.gte(x) && avg_xy.lte(y)); } } // Test that the average of the same number is itself // avg(x, x) == x - function avg_test_one_value(int128 x) public pure { - int128 avg_x = avg(x, x); + function avg_test_one_value(SD59x18 x) public pure { + SD59x18 avg_x = avg(x, x); - assert(avg_x == x); + assert(avg_x.eq(x)); } // Test that the order of operands is irrelevant // avg(x, y) == avg(y, x) - function avg_test_operand_order(int128 x, int128 y) public pure { - int128 avg_xy = avg(x, y); - int128 avg_yx = avg(y, x); + function avg_test_operand_order(SD59x18 x, SD59x18 y) public pure { + SD59x18 avg_xy = avg(x, y); + SD59x18 avg_yx = avg(y, x); - assert(avg_xy == avg_yx); + assert(avg_xy.eq(avg_yx)); } /* ================================================================ @@ -1137,100 +1044,25 @@ contract CryticPRBMathUD59x18Properties { // Test for the maximum value function avg_test_maximum() public view { - int128 result; + SD59x18 result; // This may revert due to overflow depending on implementation - // If it doesn't revert, the result must be MAX_64x64 - try this.avg(MAX_64x64, MAX_64x64) { - result = this.avg(MAX_64x64, MAX_64x64); - assert(result == MAX_64x64); + // If it doesn't revert, the result must be MAX_SD59x18 + try this.avg(MAX_SD59x18, MAX_SD59x18) { + result = this.avg(MAX_SD59x18, MAX_SD59x18); + assert(result.eq(MAX_SD59x18)); } catch {} } // Test for the minimum value function avg_test_minimum() public view { - int128 result; - - // This may revert due to overflow depending on implementation - // If it doesn't revert, the result must be MIN_64x64 - try this.avg(MIN_64x64, MIN_64x64) { - result = this.avg(MIN_64x64, MIN_64x64); - assert(result == MIN_64x64); - } catch {} - } - - /* ================================================================ - - TESTS FOR FUNCTION gavg() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test that the result is between the two operands - // gavg(x, y) >= min(x, y) && gavg(x, y) <= max(x, y) - function gavg_test_values_in_range(int128 x, int128 y) public view { - int128 gavg_xy = gavg(x, y); - - if (x == ZERO_FP || y == ZERO_FP) { - assert(gavg_xy == ZERO_FP); - } else { - if (abs(x) >= abs(y)) { - assert(gavg_xy >= abs(y) && gavg_xy <= abs(x)); - } else { - assert(gavg_xy >= abs(x) && gavg_xy <= abs(y)); - } - } - } - - // Test that the average of the same number is itself - // gavg(x, x) == | x | - function gavg_test_one_value(int128 x) public pure { - int128 gavg_x = gavg(x, x); - - assert(gavg_x == abs(x)); - } - - // Test that the order of operands is irrelevant - // gavg(x, y) == gavg(y, x) - function gavg_test_operand_order(int128 x, int128 y) public pure { - int128 gavg_xy = gavg(x, y); - int128 gavg_yx = gavg(y, x); - - assert(gavg_xy == gavg_yx); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for the maximum value - function gavg_test_maximum() public view { - int128 result; + SD59x18 result; // This may revert due to overflow depending on implementation - // If it doesn't revert, the result must be MAX_64x64 - try this.gavg(MAX_64x64, MAX_64x64) { - result = this.gavg(MAX_64x64, MAX_64x64); - assert(result == MAX_64x64); - } catch {} - } - - // Test for the minimum value - function gavg_test_minimum() public view { - int128 result; - - // This may revert due to overflow depending on implementation - // If it doesn't revert, the result must be MIN_64x64 - try this.gavg(MIN_64x64, MIN_64x64) { - result = this.gavg(MIN_64x64, MIN_64x64); - assert(result == MIN_64x64); + // If it doesn't revert, the result must be MIN_SD59x18 + try this.avg(MIN_SD59x18, MIN_SD59x18) { + result = this.avg(MIN_SD59x18, MIN_SD59x18); + assert(result.eq(MIN_SD59x18)); } catch {} } @@ -1238,7 +1070,7 @@ contract CryticPRBMathUD59x18Properties { TESTS FOR FUNCTION pow() - ================================================================ */ + ================================================================ */ /* ================================================================ Tests for arithmetic properties. @@ -1246,52 +1078,64 @@ contract CryticPRBMathUD59x18Properties { with math rules and expected behaviour. ================================================================ */ + /// @dev Notes: + /// - Refer to the notes in {exp2}, {log2}, and {mul}. + /// - If x is less than -59_794705707972522261, the result is zero. + /// - Due to the lossy precision of the iterative approximation, the results are not perfectly accurate to the last decimal. + /// - Returns `UNIT` for 0^0. + /// + /// Requirements: + /// - None of the inputs can be `MIN_SD59x18`. + /// - x must be less than 192e18. + /// - x must be greater than zero. + /// - The result must fit in SD59x18. + // Test for zero exponent // x ** 0 == 1 - function pow_test_zero_exponent(int128 x) public view { - int128 x_pow_0 = pow(x, 0); + function pow_test_zero_exponent(SD59x18 x) public view { + SD59x18 x_pow_0 = pow(x, ZERO_FP); - assert(x_pow_0 == ONE_FP); + assert(x_pow_0.eq(ONE_FP)); } // Test for zero base // 0 ** x == 0 (for positive x) - function pow_test_zero_base(uint256 x) public view { - require(x != 0); + function pow_test_zero_base(SD59x18 x) public view { + require(x.neq(ZERO_FP)); - int128 zero_pow_x = pow(ZERO_FP, x); + SD59x18 zero_pow_x = pow(ZERO_FP, x); - assert(zero_pow_x == ZERO_FP); + assert(zero_pow_x.eq(ZERO_FP)); } // Test for exponent one // x ** 1 == x - function pow_test_one_exponent(int128 x) public pure { - int128 x_pow_1 = pow(x, 1); + function pow_test_one_exponent(SD59x18 x) public pure { + SD59x18 x_pow_1 = pow(x, ONE_FP); - assert(x_pow_1 == x); + assert(x_pow_1.eq(x)); } // Test for base one // 1 ** x == 1 - function pow_test_base_one(uint256 x) public view { - int128 one_pow_x = pow(ONE_FP, x); + function pow_test_base_one(SD59x18 x) public view { + SD59x18 one_pow_x = pow(ONE_FP, x); - assert(one_pow_x == ONE_FP); + assert(one_pow_x.eq(ONE_FP)); } // Test for product of powers of the same base // x ** a * x ** b == x ** (a + b) function pow_test_product_same_base( - int128 x, - uint256 a, - uint256 b + SD59x18 x, + SD59x18 a, + SD59x18 b ) public view { - require(x != ZERO_FP); + require(x.neq(ZERO_FP)); - int128 x_a = pow(x, a); - int128 x_b = pow(x, b); - int128 x_ab = pow(x, a + b); + SD59x18 x_a = pow(x, a); + SD59x18 x_b = pow(x, b); + SD59x18 x_ab = pow(x, a.add(b)); assert(equal_within_precision(mul(x_a, x_b), x_ab, 2)); } @@ -1299,15 +1143,15 @@ contract CryticPRBMathUD59x18Properties { // Test for power of an exponentiation // (x ** a) ** b == x ** (a * b) function pow_test_power_of_an_exponentiation( - int128 x, - uint256 a, - uint256 b + SD59x18 x, + SD59x18 a, + SD59x18 b ) public view { - require(x != ZERO_FP); + require(x.neq(ZERO_FP)); - int128 x_a = pow(x, a); - int128 x_a_b = pow(x_a, b); - int128 x_ab = pow(x, a * b); + SD59x18 x_a = pow(x, a); + SD59x18 x_a_b = pow(x_a, b); + SD59x18 x_ab = pow(x, a.mul(b)); assert(equal_within_precision(x_a_b, x_ab, 2)); } @@ -1315,12 +1159,13 @@ contract CryticPRBMathUD59x18Properties { // Test for power of a product // (x * y) ** a == x ** a * y ** a function pow_test_product_same_base( - int128 x, - int128 y, - uint256 a + SD59x18 x, + SD59x18 y, + SD59x18 a ) public view { - require(x != ZERO_FP && y != ZERO_FP); - require(a > 2 ** 32); // to avoid massive loss of precision + require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); + // todo this should probably be changed + require(a.gt(convert(2 ** 32))); // to avoid massive loss of precision int128 x_y = mul(x, y); int128 xy_a = pow(x_y, a); @@ -1333,40 +1178,41 @@ contract CryticPRBMathUD59x18Properties { // Test for result being greater than or lower than the argument, depending on // its absolute value and the value of the exponent - function pow_test_values(int128 x, uint256 a) public view { - require(x != ZERO_FP); + function pow_test_values(SD59x18 x, SD59x18 a) public view { + require(x.neq(ZERO_FP)); - int128 x_a = pow(x, a); + SD59x18 x_a = pow(x, a); - if (abs(x) >= ONE_FP) { - assert(abs(x_a) >= ONE_FP); + if (abs(x).gte(ONE_FP)) { + assert(abs(x_a).gte(ONE_FP)); } - if (abs(x) <= ONE_FP) { - assert(abs(x_a) <= ONE_FP); + if (abs(x).lte(ONE_FP)) { + assert(abs(x_a).lte(ONE_FP)); } } // Test for result sign: if the exponent is even, sign is positive // if the exponent is odd, preserves the sign of the base - function pow_test_sign(int128 x, uint256 a) public view { - require(x != ZERO_FP && a != 0); + function pow_test_sign(SD59x18 x, SD59x18 a) public view { + require(x.neq(ZERO_FP) && a.neq(ZERO_FP)); - int128 x_a = pow(x, a); + SD59x18 x_a = pow(x, a); // This prevents the case where a small negative number gets // rounded down to zero and thus changes sign - require(x_a != ZERO_FP); + require(x_a.neq(ZERO_FP)); + // todo should I unwrap here? // If the exponent is even - if (a % 2 == 0) { - assert(x_a == abs(x_a)); + if (a.mod(convert(2)).eq(ZERO_FP)) { + assert(x_a.eq(abs(x_a))); } else { // x_a preserves x sign - if (x < ZERO_FP) { - assert(x_a < ZERO_FP); + if (x.lt(ZERO_FP)) { + assert(x_a.lt(ZERO_FP)); } else { - assert(x_a > ZERO_FP); + assert(x_a.gt(ZERO_FP)); } } } @@ -1378,10 +1224,10 @@ contract CryticPRBMathUD59x18Properties { ================================================================ */ // Test for maximum base and exponent > 1 - function pow_test_maximum_base(uint256 a) public view { - require(a > 1); + function pow_test_maximum_base(SD59x18 a) public view { + require(a.gt(ONE_FP)); - try this.pow(MAX_64x64, a) { + try this.pow(MAX_SD59x18, a) { // Unexpected, should revert because of overflow assert(false); } catch { @@ -1390,19 +1236,19 @@ contract CryticPRBMathUD59x18Properties { } // Test for abs(base) < 1 and high exponent - function pow_test_high_exponent(int128 x, uint256 a) public view { - require(abs(x) < ONE_FP && a > 2 ** 64); + function pow_test_high_exponent(SD59x18 x, SD59x18 a) public view { + require(abs(x).lt(ONE_FP) && a.gt(convert(2 ** 64))); int128 result = pow(x, a); - assert(result == ZERO_FP); + assert(result.eq(ZERO_FP)); } /* ================================================================ TESTS FOR FUNCTION sqrt() - ================================================================ */ + ================================================================ */ /* ================================================================ Tests for arithmetic properties. @@ -1410,10 +1256,18 @@ contract CryticPRBMathUD59x18Properties { with math rules and expected behaviour. ================================================================ */ + /// Notes: + /// - Only the positive root is returned. + /// - The result is rounded toward zero. + /// + /// Requirements: + /// - x cannot be negative, since complex numbers are not supported. + /// - x must be less than `MAX_SD59x18 / UNIT`. + // Test for the inverse operation // sqrt(x) * sqrt(x) == x - function sqrt_test_inverse_mul(int128 x) public view { - require(x >= ZERO_FP); + function sqrt_test_inverse_mul(SD59x18 x) public view { + require(x.lte(ZERO_FP)); int128 sqrt_x = sqrt(x); int128 sqrt_x_squared = mul(sqrt_x, sqrt_x); @@ -1423,38 +1277,38 @@ contract CryticPRBMathUD59x18Properties { equal_within_precision( sqrt_x_squared, x, - (toUInt(log_2(x)) >> 1) + 2 + (SD59x18.intoUint256(log_2(x)) >> 1) + 2 ) ); } // Test for the inverse operation // sqrt(x) ** 2 == x - function sqrt_test_inverse_pow(int128 x) public view { - require(x >= ZERO_FP); + function sqrt_test_inverse_pow(SD59x18 x) public view { + require(x.gte(ZERO_FP)); - int128 sqrt_x = sqrt(x); - int128 sqrt_x_squared = pow(sqrt_x, 2); + SD59x18 sqrt_x = sqrt(x); + SD59x18 sqrt_x_squared = pow(sqrt_x, convert(2)); // Precision loss is at most half the bits of the operand assert( equal_within_precision( sqrt_x_squared, x, - (toUInt(log_2(x)) >> 1) + 2 + (SD59x18.intoUint256(log_2(x)) >> 1) + 2 ) ); } // Test for distributive property respect to the multiplication // sqrt(x) * sqrt(y) == sqrt(x * y) - function sqrt_test_distributive(int128 x, int128 y) public view { - require(x >= ZERO_FP && y >= ZERO_FP); + function sqrt_test_distributive(SD59x18 x, SD59x18 y) public view { + require(x.gte(ZERO_FP) && y.gte(ZERO_FP)); - int128 sqrt_x = sqrt(x); - int128 sqrt_y = sqrt(y); - int128 sqrt_x_sqrt_y = mul(sqrt_x, sqrt_y); - int128 sqrt_xy = sqrt(mul(x, y)); + SD59x18 sqrt_x = sqrt(x); + SD59x18 sqrt_y = sqrt(y); + SD59x18 sqrt_x_sqrt_y = mul(sqrt_x, sqrt_y); + SD59x18 sqrt_xy = sqrt(mul(x, y)); // Ensure we have enough significant digits for the result to be meaningful require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); @@ -1475,13 +1329,13 @@ contract CryticPRBMathUD59x18Properties { // Test for zero case function sqrt_test_zero() public view { - assert(sqrt(ZERO_FP) == ZERO_FP); + assert(sqrt(ZERO_FP).eq(ZERO_FP)); } // Test for maximum value function sqrt_test_maximum() public view { - try this.sqrt(MAX_64x64) { - // Expected behaviour, MAX_64x64 is positive, and operation + try this.sqrt(MAX_SD59x18) { + // Expected behaviour, MAX_SD59x18 is positive, and operation // should not revert as the result is in range } catch { // Unexpected, should not revert @@ -1491,8 +1345,8 @@ contract CryticPRBMathUD59x18Properties { // Test for minimum value function sqrt_test_minimum() public view { - try this.sqrt(MIN_64x64) { - // Unexpected, should revert. MIN_64x64 is negative. + try this.sqrt(MIN_SD59x18) { + // Unexpected, should revert. MIN_SD59x18 is negative. assert(false); } catch { // Expected behaviour, revert @@ -1500,8 +1354,8 @@ contract CryticPRBMathUD59x18Properties { } // Test for negative operands - function sqrt_test_negative(int128 x) public view { - require(x < ZERO_FP); + function sqrt_test_negative(SD59x18 x) public view { + require(x.lt(ZERO_FP)); try this.sqrt(x) { // Unexpected, should revert @@ -1515,7 +1369,7 @@ contract CryticPRBMathUD59x18Properties { TESTS FOR FUNCTION log2() - ================================================================ */ + ================================================================ */ /* ================================================================ Tests for arithmetic properties. @@ -1523,35 +1377,37 @@ contract CryticPRBMathUD59x18Properties { with math rules and expected behaviour. ================================================================ */ + /// Requirements: + /// - x must be greater than zero. + // Test for distributive property respect to multiplication // log2(x * y) = log2(x) + log2(y) - function log2_test_distributive_mul(int128 x, int128 y) public view { - int128 log2_x = log_2(x); - int128 log2_y = log_2(y); - int128 log2_x_log2_y = add(log2_x, log2_y); + function log2_test_distributive_mul(SD59x18 x, SD59x18 y) public view { + SD59x18 log2_x = log_2(x); + SD59x18 log2_y = log_2(y); + SD59x18 log2_x_log2_y = add(log2_x, log2_y); - int128 xy = mul(x, y); - int128 log2_xy = log_2(xy); + SD59x18 xy = mul(x, y); + SD59x18 log2_xy = log_2(xy); // Ensure we have enough significant digits for the result to be meaningful require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); // The maximum loss of precision is given by the formula: // | log_2(x) + log_2(y) | - uint256 loss = toUInt(abs(log_2(x) + log_2(y))); + uint256 loss = SD59x18.intoUint256(abs(log_2(x) + log_2(y))); assert(equal_within_precision(log2_x_log2_y, log2_xy, loss)); } // Test for logarithm of a power // log2(x ** y) = y * log2(x) - function log2_test_power(int128 x, uint256 y) public pure { - int128 x_y = pow(x, y); - int128 log2_x_y = log_2(x_y); + function log2_test_power(SD59x18 x, SD59x18 y) public pure { + SD59x18 x_y = pow(x, y); + SD59x18 log2_x_y = log_2(x_y); + SD59x18 y_log2_x = mul(log_2(x), y); - uint256 y_log2_x = mulu(log_2(x), y); - - assert(y_log2_x == toUInt(log2_x_y)); + assert(y_log2_x.eq(log2_x_y)); } /* ================================================================ @@ -1572,12 +1428,12 @@ contract CryticPRBMathUD59x18Properties { // Test for maximum value case, should return a positive number function log2_test_maximum() public view { - int128 result; + SD59x18 result; - try this.log_2(MAX_64x64) { + try this.log_2(MAX_SD59x18) { // Expected, should not revert and the result must be > 0 - result = this.log_2(MAX_64x64); - assert(result > ZERO_FP); + result = this.log_2(MAX_SD59x18); + assert(result.gt(ZERO_FP)); } catch { // Unexpected assert(false); @@ -1585,8 +1441,8 @@ contract CryticPRBMathUD59x18Properties { } // Test for negative values, should revert as log2 is not defined - function log2_test_negative(int128 x) public view { - require(x < ZERO_FP); + function log2_test_negative(SD59x18 x) public view { + require(x.lt(ZERO_FP)); try this.log_2(x) { // Unexpected, should revert @@ -1600,7 +1456,7 @@ contract CryticPRBMathUD59x18Properties { TESTS FOR FUNCTION ln() - ================================================================ */ + ================================================================ */ /* ================================================================ Tests for arithmetic properties. @@ -1608,36 +1464,39 @@ contract CryticPRBMathUD59x18Properties { with math rules and expected behaviour. ================================================================ */ + /// Requirements: + /// - x must be greater than zero. + // Test for distributive property respect to multiplication // ln(x * y) = ln(x) + ln(y) - function ln_test_distributive_mul(int128 x, int128 y) public view { - require(x > ZERO_FP && y > ZERO_FP); + function ln_test_distributive_mul(SD59x18 x, SD59x18 y) public view { + require(x.gt(ZERO_FP) && y.gt(ZERO_FP)); - int128 ln_x = ln(x); - int128 ln_y = ln(y); - int128 ln_x_ln_y = add(ln_x, ln_y); + SD59x18 ln_x = ln(x); + SD59x18 ln_y = ln(y); + SD59x18 ln_x_ln_y = add(ln_x, ln_y); - int128 xy = mul(x, y); - int128 ln_xy = ln(xy); + SD59x18 xy = mul(x, y); + SD59x18 ln_xy = ln(xy); require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); // The maximum loss of precision is given by the formula: // | log_2(x) + log_2(y) | - uint256 loss = toUInt(abs(log_2(x) + log_2(y))); + uint256 loss = SD59x18.intoUint256(abs(log_2(x) + log_2(y))); assert(equal_within_precision(ln_x_ln_y, ln_xy, loss)); } // Test for logarithm of a power // ln(x ** y) = y * ln(x) - function ln_test_power(int128 x, uint256 y) public pure { - int128 x_y = pow(x, y); - int128 ln_x_y = ln(x_y); + function ln_test_power(SD59x18 x, SD59x18 y) public pure { + SD59x18 x_y = pow(x, y); + SD59x18 ln_x_y = ln(x_y); - uint256 y_ln_x = mulu(ln(x), y); + SD59x18 y_ln_x = mul(ln(x), y); - assert(y_ln_x == toUInt(ln_x_y)); + assert(y_ln_x.eq(ln_x_y)); } /* ================================================================ @@ -1658,12 +1517,12 @@ contract CryticPRBMathUD59x18Properties { // Test for maximum value case, should return a positive number function ln_test_maximum() public view { - int128 result; + SD59x18 result; - try this.ln(MAX_64x64) { + try this.ln(MAX_SD59x18) { // Expected, should not revert and the result must be > 0 - result = this.ln(MAX_64x64); - assert(result > ZERO_FP); + result = this.ln(MAX_SD59x18); + assert(result.gt(ZERO_FP)); } catch { // Unexpected assert(false); @@ -1671,8 +1530,8 @@ contract CryticPRBMathUD59x18Properties { } // Test for negative values, should revert as ln is not defined - function ln_test_negative(int128 x) public view { - require(x < ZERO_FP); + function ln_test_negative(SD59x18 x) public view { + require(x.lt(ZERO_FP)); try this.ln(x) { // Unexpected, should revert @@ -1686,7 +1545,7 @@ contract CryticPRBMathUD59x18Properties { TESTS FOR FUNCTION exp2() - ================================================================ */ + ================================================================ */ /* ================================================================ Tests for arithmetic properties. @@ -1694,25 +1553,33 @@ contract CryticPRBMathUD59x18Properties { with math rules and expected behaviour. ================================================================ */ + /// Notes: + /// - If x is less than -59_794705707972522261, the result is zero. + /// + /// Requirements: + /// - x must be less than 192e18. + /// - The result must fit in SD59x18. + // Test for equality with pow(2, x) for integer x // pow(2, x) == exp_2(x) function exp2_test_equivalence_pow(uint256 x) public view { - int128 exp2_x = exp_2(fromUInt(x)); - int128 pow_2_x = pow(TWO_FP, x); + SD59x18 exp2_x = exp_2(convert(x)); + SD59x18 pow_2_x = pow(TWO_FP, x); - assert(exp2_x == pow_2_x); + assert(exp2_x.eq(pow_2_x)); } // Test for inverse function // If y = log_2(x) then exp_2(y) == x - function exp2_test_inverse(int128 x) public view { - int128 log2_x = log_2(x); - int128 exp2_x = exp_2(log2_x); + function exp2_test_inverse(SD59x18 x) public view { + SD59x18 log2_x = log_2(x); + SD59x18 exp2_x = exp_2(log2_x); + // todo is this the correct number of bits? uint256 bits = 50; - if (log2_x < ZERO_FP) { - bits = uint256(int256(bits) + int256(log2_x)); + if (log2_x.lt(ZERO_FP)) { + bits = SD59x18.intoUint256(convert(bits).add(convert(log2_x))); } assert(equal_most_significant_bits_within_precision(x, exp2_x, bits)); @@ -1720,11 +1587,11 @@ contract CryticPRBMathUD59x18Properties { // Test for negative exponent // exp_2(-x) == inv( exp_2(x) ) - function exp2_test_negative_exponent(int128 x) public view { - require(x < ZERO_FP && x != MIN_64x64); + function exp2_test_negative_exponent(SD59x18 x) public view { + require(x.lt(ZERO_FP) && x.neq(MIN_SD59x18)); - int128 exp2_x = exp_2(x); - int128 exp2_minus_x = exp_2(-x); + SD59x18 exp2_x = exp_2(x); + SD59x18 exp2_minus_x = exp_2(neg(x)); // Result should be within 4 bits precision for the worst case assert(equal_within_precision(exp2_x, inv(exp2_minus_x), 4)); @@ -1739,14 +1606,14 @@ contract CryticPRBMathUD59x18Properties { // Test for zero case // exp_2(0) == 1 function exp2_test_zero() public view { - int128 exp_zero = exp_2(ZERO_FP); - assert(exp_zero == ONE_FP); + SD59x18 exp_zero = exp_2(ZERO_FP); + assert(exp_zero.eq(ONE_FP)); } // Test for maximum value. This should overflow as it won't fit // in the data type function exp2_test_maximum() public view { - try this.exp_2(MAX_64x64) { + try this.exp_2(MAX_SD59x18) { // Unexpected, should revert assert(false); } catch { @@ -1757,12 +1624,12 @@ contract CryticPRBMathUD59x18Properties { // Test for minimum value. This should return zero since // 2 ** -x == 1 / 2 ** x that tends to zero as x increases function exp2_test_minimum() public view { - int128 result; + SD59x18 result; - try this.exp_2(MIN_64x64) { + try this.exp_2(MIN_SD59x18) { // Expected, should not revert, check that value is zero - result = exp_2(MIN_64x64); - assert(result == ZERO_FP); + result = exp_2(MIN_SD59x18); + assert(result.eq(ZERO_FP)); } catch { // Unexpected revert assert(false); @@ -1773,7 +1640,7 @@ contract CryticPRBMathUD59x18Properties { TESTS FOR FUNCTION exp() - ================================================================ */ + ================================================================ */ /* ================================================================ Tests for arithmetic properties. @@ -1781,17 +1648,21 @@ contract CryticPRBMathUD59x18Properties { with math rules and expected behaviour. ================================================================ */ + /// Requirements: + /// - The result must fit in SD59x18.. + /// - x must be less than 133_084258667509499441. + // Test for inverse function // If y = ln(x) then exp(y) == x - function exp_test_inverse(int128 x) public view { - int128 ln_x = ln(x); - int128 exp_x = exp(ln_x); - int128 log2_x = log_2(x); + function exp_test_inverse(SD59x18 x) public view { + SD59x18 ln_x = ln(x); + SD59x18 exp_x = exp(ln_x); + SD59x18 log2_x = log_2(x); uint256 bits = 48; - if (log2_x < ZERO_FP) { - bits = uint256(int256(bits) + int256(log2_x)); + if (log2_x.lt(ZERO_FP)) { + bits = SD59x18.intoUint256(convert(bits).add(convert(log2_x))); } assert(equal_most_significant_bits_within_precision(x, exp_x, bits)); @@ -1799,11 +1670,11 @@ contract CryticPRBMathUD59x18Properties { // Test for negative exponent // exp(-x) == inv( exp(x) ) - function exp_test_negative_exponent(int128 x) public view { - require(x < ZERO_FP && x != MIN_64x64); + function exp_test_negative_exponent(SD59x18 x) public view { + require(x.lt(ZERO_FP) && x.neq(MIN_64x64)); - int128 exp_x = exp(x); - int128 exp_minus_x = exp(-x); + SD59x18 exp_x = exp(x); + SD59x18 exp_minus_x = exp(neg(x)); // Result should be within 4 bits precision for the worst case assert(equal_within_precision(exp_x, inv(exp_minus_x), 4)); @@ -1818,14 +1689,14 @@ contract CryticPRBMathUD59x18Properties { // Test for zero case // exp(0) == 1 function exp_test_zero() public view { - int128 exp_zero = exp(ZERO_FP); - assert(exp_zero == ONE_FP); + SD59x18 exp_zero = exp(ZERO_FP); + assert(exp_zero.eq(ONE_FP)); } // Test for maximum value. This should overflow as it won't fit // in the data type function exp_test_maximum() public view { - try this.exp(MAX_64x64) { + try this.exp(MAX_SD59x18) { // Unexpected, should revert assert(false); } catch { @@ -1836,15 +1707,16 @@ contract CryticPRBMathUD59x18Properties { // Test for minimum value. This should return zero since // e ** -x == 1 / e ** x that tends to zero as x increases function exp_test_minimum() public view { - int128 result; + SD59x18 result; - try this.exp(MIN_64x64) { + try this.exp(MIN_SD59x18) { // Expected, should not revert, check that value is zero - result = exp(MIN_64x64); - assert(result == ZERO_FP); + result = exp(MIN_SD59x18); + assert(result.eq(ZERO_FP)); } catch { // Unexpected revert assert(false); } } -} + +} \ No newline at end of file diff --git a/contracts/Math/PRBMath/PRBMathUD60x18PropertyTests.sol b/contracts/Math/PRBMath/PRBMathUD60x18PropertyTests.sol deleted file mode 100644 index 81b9572..0000000 --- a/contracts/Math/PRBMath/PRBMathUD60x18PropertyTests.sol +++ /dev/null @@ -1,1850 +0,0 @@ -pragma solidity ^0.8.19; - -import { UD60x18 } from "@prb/math/UD60x18.sol"; - -contract CryticPRBMathUD60x18Properties { - /* ================================================================ - 64x64 fixed-point constants used for testing specific values. - This assumes that ABDK library's fromInt(x) works as expected. - ================================================================ */ - int128 internal ZERO_FP = ABDKMath64x64.fromInt(0); - int128 internal ONE_FP = ABDKMath64x64.fromInt(1); - int128 internal MINUS_ONE_FP = ABDKMath64x64.fromInt(-1); - int128 internal TWO_FP = ABDKMath64x64.fromInt(2); - int128 internal THREE_FP = ABDKMath64x64.fromInt(3); - int128 internal EIGHT_FP = ABDKMath64x64.fromInt(8); - int128 internal THOUSAND_FP = ABDKMath64x64.fromInt(1000); - int128 internal MINUS_SIXTY_FOUR_FP = ABDKMath64x64.fromInt(-64); - int128 internal EPSILON = 1; - int128 internal ONE_TENTH_FP = - ABDKMath64x64.div(ABDKMath64x64.fromInt(1), ABDKMath64x64.fromInt(10)); - - /* ================================================================ - Constants used for precision loss calculations - ================================================================ */ - uint256 internal REQUIRED_SIGNIFICANT_BITS = 10; - - /* ================================================================ - Integer representations maximum values. - These constants are used for testing edge cases or limits for - possible values. - ================================================================ */ - int128 private constant MIN_64x64 = -0x80000000000000000000000000000000; - int128 private constant MAX_64x64 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - int256 private constant MAX_256 = - 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - int256 private constant MIN_256 = - -0x8000000000000000000000000000000000000000000000000000000000000000; - uint256 private constant MAX_U256 = - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - - /* ================================================================ - Events used for debugging or showing information. - ================================================================ */ - event Value(string reason, int128 val); - event LogErr(bytes error); - - /* ================================================================ - Helper functions. - ================================================================ */ - - // These functions allows to compare a and b for equality, discarding - // the last precision_bits bits. - // An absolute value function is implemented inline in order to not use - // the implementation from the library under test. - function equal_within_precision( - int128 a, - int128 b, - uint256 precision_bits - ) public pure returns (bool) { - int128 max = (a > b) ? a : b; - int128 min = (a > b) ? b : a; - int128 r = (max - min) >> precision_bits; - - return (r == 0); - } - - function equal_within_precision_u( - uint256 a, - uint256 b, - uint256 precision_bits - ) public pure returns (bool) { - uint256 max = (a > b) ? a : b; - uint256 min = (a > b) ? b : a; - uint256 r = (max - min) >> precision_bits; - - return (r == 0); - } - - // This function determines if the relative error between a and b is less - // than error_percent % (expressed as a 64x64 value) - // Uses functions from the library under test! - function equal_within_tolerance( - int128 a, - int128 b, - int128 error_percent - ) public pure returns (bool) { - int128 tol_value = abs(mul(a, div(error_percent, fromUInt(100)))); - - return (abs(sub(b, a)) <= tol_value); - } - - // Check that there are remaining significant digits after a multiplication - // Uses functions from the library under test! - function significant_digits_lost_in_mult( - int128 a, - int128 b - ) public pure returns (bool) { - int128 x = a >= 0 ? a : -a; - int128 y = b >= 0 ? b : -b; - - int128 lx = toInt(log_2(x)); - int128 ly = toInt(log_2(y)); - - return (lx + ly - 1 <= -64); - } - - // Return how many significant bits will remain after multiplying a and b - // Uses functions from the library under test! - function significant_bits_after_mult( - int128 a, - int128 b - ) public pure returns (uint256) { - int128 x = a >= 0 ? a : -a; - int128 y = b >= 0 ? b : -b; - - int128 lx = toInt(log_2(x)); - int128 ly = toInt(log_2(y)); - int256 prec = lx + ly - 1; - - if (prec < -64) return 0; - else return (64 + uint256(prec)); - } - - // Return the i most significant bits from |n|. If n has less than i significant bits, return |n| - // Uses functions from the library under test! - function most_significant_bits( - int128 n, - uint256 i - ) public pure returns (uint256) { - // Create a mask consisting of i bits set to 1 - uint256 mask = (2 ** i) - 1; - - // Get the position of the MSB set to 1 of n - uint256 pos = uint64(toInt(log_2(n)) + 64 + 1); - - // Get the positive value of n - uint256 value = (n > 0) ? uint128(n) : uint128(-n); - - // Shift the mask to match the rightmost 1-set bit - if (pos > i) { - mask <<= (pos - i); - } - - return (value & mask); - } - - // Returns true if the n most significant bits of a and b are almost equal - // Uses functions from the library under test! - function equal_most_significant_bits_within_precision( - int128 a, - int128 b, - uint256 bits - ) public pure returns (bool) { - // Get the number of bits in a and b - // Since log(x) returns in the interval [-64, 63), add 64 to be in the interval [0, 127) - uint256 a_bits = uint256(int256(toInt(log_2(a)) + 64)); - uint256 b_bits = uint256(int256(toInt(log_2(b)) + 64)); - - // a and b lengths may differ in 1 bit, so the shift should take into account the longest - uint256 shift_bits = (a_bits > b_bits) - ? (a_bits - bits) - : (b_bits - bits); - - // Get the _bits_ most significant bits of a and b - uint256 a_msb = most_significant_bits(a, bits) >> shift_bits; - uint256 b_msb = most_significant_bits(b, bits) >> shift_bits; - - // See if they are equal within 1 bit precision - // This could be modified to get the precision as a parameter to the function - return equal_within_precision_u(a_msb, b_msb, 1); - } - - /* ================================================================ - Library wrappers. - These functions allow calling the ABDKMath64x64 library. - ================================================================ */ - function debug(string calldata x, int128 y) public { - emit Value(x, ABDKMath64x64.toInt(y)); - } - - function fromInt(int256 x) public pure returns (int128) { - return ABDKMath64x64.fromInt(x); - } - - function toInt(int128 x) public pure returns (int64) { - return ABDKMath64x64.toInt(x); - } - - function fromUInt(uint256 x) public pure returns (int128) { - return ABDKMath64x64.fromUInt(x); - } - - function toUInt(int128 x) public pure returns (uint64) { - return ABDKMath64x64.toUInt(x); - } - - function add(int128 x, int128 y) public pure returns (int128) { - return ABDKMath64x64.add(x, y); - } - - function sub(int128 x, int128 y) public pure returns (int128) { - return ABDKMath64x64.sub(x, y); - } - - function mul(int128 x, int128 y) public pure returns (int128) { - return ABDKMath64x64.mul(x, y); - } - - function mulu(int128 x, uint256 y) public pure returns (uint256) { - return ABDKMath64x64.mulu(x, y); - } - - function div(int128 x, int128 y) public pure returns (int128) { - return ABDKMath64x64.div(x, y); - } - - function neg(int128 x) public pure returns (int128) { - return ABDKMath64x64.neg(x); - } - - function abs(int128 x) public pure returns (int128) { - return ABDKMath64x64.abs(x); - } - - function inv(int128 x) public pure returns (int128) { - return ABDKMath64x64.inv(x); - } - - function avg(int128 x, int128 y) public pure returns (int128) { - return ABDKMath64x64.avg(x, y); - } - - function gavg(int128 x, int128 y) public pure returns (int128) { - return ABDKMath64x64.gavg(x, y); - } - - function pow(int128 x, uint256 y) public pure returns (int128) { - return ABDKMath64x64.pow(x, y); - } - - function sqrt(int128 x) public pure returns (int128) { - return ABDKMath64x64.sqrt(x); - } - - function log_2(int128 x) public pure returns (int128) { - return ABDKMath64x64.log_2(x); - } - - function ln(int128 x) public pure returns (int128) { - return ABDKMath64x64.ln(x); - } - - function exp_2(int128 x) public pure returns (int128) { - return ABDKMath64x64.exp_2(x); - } - - function exp(int128 x) public pure returns (int128) { - return ABDKMath64x64.exp(x); - } - - /* ================================================================ - Start of tests - ================================================================ */ - - /* ================================================================ - - TESTS FOR FUNCTION add() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for commutative property - // x + y == y + x - function add_test_commutative(int128 x, int128 y) public pure { - int128 x_y = add(x, y); - int128 y_x = add(y, x); - - assert(x_y == y_x); - } - - // Test for associative property - // (x + y) + z == x + (y + z) - function add_test_associative(int128 x, int128 y, int128 z) public pure { - int128 x_y = add(x, y); - int128 y_z = add(y, z); - int128 xy_z = add(x_y, z); - int128 x_yz = add(x, y_z); - - assert(xy_z == x_yz); - } - - // Test for identity operation - // x + 0 == x (equivalent to x + (-x) == 0) - function add_test_identity(int128 x) public view { - int128 x_0 = add(x, ZERO_FP); - - assert(x_0 == x); - assert(add(x, neg(x)) == ZERO_FP); - } - - // Test that the result increases or decreases depending - // on the value to be added - function add_test_values(int128 x, int128 y) public view { - int128 x_y = add(x, y); - - if (y >= ZERO_FP) { - assert(x_y >= x); - } else { - assert(x_y < x); - } - } - - /* ================================================================ - Tests for overflow and edge cases. - These should make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // The result of the addition must be between the maximum - // and minimum allowed values for 64x64 - function add_test_range(int128 x, int128 y) public view { - int128 result; - try this.add(x, y) { - result = this.add(x, y); - assert(result <= MAX_64x64 && result >= MIN_64x64); - } catch { - // If it reverts, just ignore - } - } - - // Adding zero to the maximum value shouldn't revert, as it is valid - // Moreover, the result must be MAX_64x64 - function add_test_maximum_value() public view { - int128 result; - try this.add(MAX_64x64, ZERO_FP) { - // Expected behaviour, does not revert - result = this.add(MAX_64x64, ZERO_FP); - assert(result == MAX_64x64); - } catch { - assert(false); - } - } - - // Adding one to the maximum value should revert, as it is out of range - function add_test_maximum_value_plus_one() public view { - try this.add(MAX_64x64, ONE_FP) { - assert(false); - } catch { - // Expected behaviour, reverts - } - } - - // Adding zero to the minimum value shouldn't revert, as it is valid - // Moreover, the result must be MIN_64x64 - function add_test_minimum_value() public view { - int128 result; - try this.add(MIN_64x64, ZERO_FP) { - // Expected behaviour, does not revert - result = this.add(MIN_64x64, ZERO_FP); - assert(result == MIN_64x64); - } catch { - assert(false); - } - } - - // Adding minus one to the maximum value should revert, as it is out of range - function add_test_minimum_value_plus_negative_one() public view { - try this.add(MIN_64x64, MINUS_ONE_FP) { - assert(false); - } catch { - // Expected behaviour, reverts - } - } - - /* ================================================================ - - TESTS FOR FUNCTION sub() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test equivalence to addition - // x - y == x + (-y) - function sub_test_equivalence_to_addition(int128 x, int128 y) public pure { - int128 minus_y = neg(y); - int128 addition = add(x, minus_y); - int128 subtraction = sub(x, y); - - assert(addition == subtraction); - } - - // Test for non-commutative property - // x - y == -(y - x) - function sub_test_non_commutative(int128 x, int128 y) public pure { - int128 x_y = sub(x, y); - int128 y_x = sub(y, x); - - assert(x_y == neg(y_x)); - } - - // Test for identity operation - // x - 0 == x (equivalent to x - x == 0) - function sub_test_identity(int128 x) public view { - int128 x_0 = sub(x, ZERO_FP); - - assert(x_0 == x); - assert(sub(x, x) == ZERO_FP); - } - - // Test for neutrality over addition and subtraction - // (x - y) + y == (x + y) - y == x - function sub_test_neutrality(int128 x, int128 y) public pure { - int128 x_minus_y = sub(x, y); - int128 x_plus_y = add(x, y); - - int128 x_minus_y_plus_y = add(x_minus_y, y); - int128 x_plus_y_minus_y = sub(x_plus_y, y); - - assert(x_minus_y_plus_y == x_plus_y_minus_y); - assert(x_minus_y_plus_y == x); - } - - // Test that the result increases or decreases depending - // on the value to be subtracted - function sub_test_values(int128 x, int128 y) public view { - int128 x_y = sub(x, y); - - if (y >= ZERO_FP) { - assert(x_y <= x); - } else { - assert(x_y > x); - } - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // The result of the subtraction must be between the maximum - // and minimum allowed values for 64x64 - function sub_test_range(int128 x, int128 y) public view { - int128 result; - try this.sub(x, y) { - result = this.sub(x, y); - assert(result <= MAX_64x64 && result >= MIN_64x64); - } catch { - // If it reverts, just ignore - } - } - - // Subtracting zero from the maximum value shouldn't revert, as it is valid - // Moreover, the result must be MAX_64x64 - function sub_test_maximum_value() public view { - int128 result; - try this.sub(MAX_64x64, ZERO_FP) { - // Expected behaviour, does not revert - result = this.sub(MAX_64x64, ZERO_FP); - assert(result == MAX_64x64); - } catch { - assert(false); - } - } - - // Subtracting minus one from the maximum value should revert, - // as it is out of range - function sub_test_maximum_value_minus_neg_one() public view { - try this.sub(MAX_64x64, MINUS_ONE_FP) { - assert(false); - } catch { - // Expected behaviour, reverts - } - } - - // Subtracting zero from the minimum value shouldn't revert, as it is valid - // Moreover, the result must be MIN_64x64 - function sub_test_minimum_value() public view { - int128 result; - try this.sub(MIN_64x64, ZERO_FP) { - // Expected behaviour, does not revert - result = this.sub(MIN_64x64, ZERO_FP); - assert(result == MIN_64x64); - } catch { - assert(false); - } - } - - // Subtracting one from the minimum value should revert, as it is out of range - function sub_test_minimum_value_minus_one() public view { - try this.sub(MIN_64x64, ONE_FP) { - assert(false); - } catch { - // Expected behaviour, reverts - } - } - - /* ================================================================ - - TESTS FOR FUNCTION mul() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for commutative property - // x * y == y * x - function mul_test_commutative(int128 x, int128 y) public pure { - int128 x_y = mul(x, y); - int128 y_x = mul(y, x); - - assert(x_y == y_x); - } - - // Test for associative property - // (x * y) * z == x * (y * z) - function mul_test_associative(int128 x, int128 y, int128 z) public view { - int128 x_y = mul(x, y); - int128 y_z = mul(y, z); - int128 xy_z = mul(x_y, z); - int128 x_yz = mul(x, y_z); - - // Failure if all significant digits are lost - require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); - require(significant_bits_after_mult(y, z) > REQUIRED_SIGNIFICANT_BITS); - require( - significant_bits_after_mult(x_y, z) > REQUIRED_SIGNIFICANT_BITS - ); - require( - significant_bits_after_mult(x, y_z) > REQUIRED_SIGNIFICANT_BITS - ); - - assert(equal_within_tolerance(xy_z, x_yz, ONE_TENTH_FP)); - } - - // Test for distributive property - // x * (y + z) == x * y + x * z - function mul_test_distributive(int128 x, int128 y, int128 z) public view { - int128 y_plus_z = add(y, z); - int128 x_times_y_plus_z = mul(x, y_plus_z); - - int128 x_times_y = mul(x, y); - int128 x_times_z = mul(x, z); - - // Failure if all significant digits are lost - require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); - require(significant_bits_after_mult(x, z) > REQUIRED_SIGNIFICANT_BITS); - require( - significant_bits_after_mult(x, y_plus_z) > REQUIRED_SIGNIFICANT_BITS - ); - - assert( - equal_within_tolerance( - add(x_times_y, x_times_z), - x_times_y_plus_z, - ONE_TENTH_FP - ) - ); - } - - // Test for identity operation - // x * 1 == x (also check that x * 0 == 0) - function mul_test_identity(int128 x) public view { - int128 x_1 = mul(x, ONE_FP); - int128 x_0 = mul(x, ZERO_FP); - - assert(x_0 == ZERO_FP); - assert(x_1 == x); - } - - // Test that the result increases or decreases depending - // on the value to be added - function mul_test_values(int128 x, int128 y) public view { - require(x != ZERO_FP && y != ZERO_FP); - - int128 x_y = mul(x, y); - - require(significant_digits_lost_in_mult(x, y) == false); - - if (x >= ZERO_FP) { - if (y >= ONE_FP) { - assert(x_y >= x); - } else { - assert(x_y <= x); - } - } else { - if (y >= ONE_FP) { - assert(x_y <= x); - } else { - assert(x_y >= x); - } - } - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // The result of the multiplication must be between the maximum - // and minimum allowed values for 64x64 - function mul_test_range(int128 x, int128 y) public view { - int128 result; - try this.mul(x, y) { - result = this.mul(x, y); - assert(result <= MAX_64x64 && result >= MIN_64x64); - } catch { - // If it reverts, just ignore - } - } - - // Multiplying the maximum value times one shouldn't revert, as it is valid - // Moreover, the result must be MAX_64x64 - function mul_test_maximum_value() public view { - int128 result; - try this.mul(MAX_64x64, ONE_FP) { - // Expected behaviour, does not revert - result = this.mul(MAX_64x64, ONE_FP); - assert(result == MAX_64x64); - } catch { - assert(false); - } - } - - // Multiplying the minimum value times one shouldn't revert, as it is valid - // Moreover, the result must be MIN_64x64 - function mul_test_minimum_value() public view { - int128 result; - try this.mul(MIN_64x64, ONE_FP) { - // Expected behaviour, does not revert - result = this.mul(MIN_64x64, ONE_FP); - assert(result == MIN_64x64); - } catch { - assert(false); - } - } - - /* ================================================================ - - TESTS FOR FUNCTION div() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for identity property - // x / 1 == x (equivalent to x / x == 1) - // Moreover, x/x should not revert unless x == 0 - function div_test_division_identity(int128 x) public view { - int128 div_1 = div(x, ONE_FP); - assert(x == div_1); - - int128 div_x; - - try this.div(x, x) { - // This should always equal one - div_x = div(x, x); - assert(div_x == ONE_FP); - } catch { - // The only allowed case to revert is if x == 0 - assert(x == ZERO_FP); - } - } - - // Test for negative divisor - // x / -y == -(x / y) - function div_test_negative_divisor(int128 x, int128 y) public view { - require(y < ZERO_FP); - - int128 x_y = div(x, y); - int128 x_minus_y = div(x, neg(y)); - - assert(x_y == neg(x_minus_y)); - } - - // Test for division with 0 as numerator - // 0 / x = 0 - function div_test_division_num_zero(int128 x) public view { - require(x != ZERO_FP); - - int128 div_0 = div(ZERO_FP, x); - - assert(ZERO_FP == div_0); - } - - // Test that the absolute value of the result increases or - // decreases depending on the denominator's absolute value - function div_test_values(int128 x, int128 y) public view { - require(y != ZERO_FP); - - int128 x_y = abs(div(x, y)); - - if (abs(y) >= ONE_FP) { - assert(x_y <= abs(x)); - } else { - assert(x_y >= abs(x)); - } - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for division by zero - function div_test_div_by_zero(int128 x) public view { - try this.div(x, ZERO_FP) { - // Unexpected, this should revert - assert(false); - } catch { - // Expected revert - } - } - - // Test for division by a large value, the result should be less than one - function div_test_maximum_denominator(int128 x) public view { - int128 div_large = div(x, MAX_64x64); - - assert(abs(div_large) <= ONE_FP); - } - - // Test for division of a large value - // This should revert if |x| < 1 as it would return a value higher than max - function div_test_maximum_numerator(int128 x) public view { - int128 div_large; - - try this.div(MAX_64x64, x) { - // If it didn't revert, then |x| >= 1 - div_large = div(MAX_64x64, x); - - assert(abs(x) >= ONE_FP); - } catch { - // Expected revert as result is higher than max - } - } - - // Test for values in range - function div_test_range(int128 x, int128 y) public view { - int128 result; - - try this.div(x, y) { - // If it returns a value, it must be in range - result = div(x, y); - assert(result <= MAX_64x64 && result >= MIN_64x64); - } catch { - // Otherwise, it should revert - } - } - - /* ================================================================ - - TESTS FOR FUNCTION neg() - - ================================================================ */ - - /* ================================================================ - Tests for mathematical properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for the double negation - // -(-x) == x - function neg_test_double_negation(int128 x) public pure { - int128 double_neg = neg(neg(x)); - - assert(x == double_neg); - } - - // Test for the identity operation - // x + (-x) == 0 - function neg_test_identity(int128 x) public view { - int128 neg_x = neg(x); - - assert(add(x, neg_x) == ZERO_FP); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for the zero-case - // -0 == 0 - function neg_test_zero() public view { - int128 neg_x = neg(ZERO_FP); - - assert(neg_x == ZERO_FP); - } - - // Test for the maximum value case - // Since this is implementation-dependant, we will actually test with MAX_64x64-EPS - function neg_test_maximum() public view { - try this.neg(sub(MAX_64x64, EPSILON)) { - // Expected behaviour, does not revert - } catch { - assert(false); - } - } - - // Test for the minimum value case - // Since this is implementation-dependant, we will actually test with MIN_64x64+EPS - function neg_test_minimum() public view { - try this.neg(add(MIN_64x64, EPSILON)) { - // Expected behaviour, does not revert - } catch { - assert(false); - } - } - - /* ================================================================ - - TESTS FOR FUNCTION abs() - - ================================================================ */ - - /* ================================================================ - Tests for mathematical properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test that the absolute value is always positive - function abs_test_positive(int128 x) public view { - int128 abs_x = abs(x); - - assert(abs_x >= ZERO_FP); - } - - // Test that the absolute value of a number equals the - // absolute value of the negative of the same number - function abs_test_negative(int128 x) public pure { - int128 abs_x = abs(x); - int128 abs_minus_x = abs(neg(x)); - - assert(abs_x == abs_minus_x); - } - - // Test the multiplicativeness property - // | x * y | == |x| * |y| - function abs_test_multiplicativeness(int128 x, int128 y) public pure { - int128 abs_x = abs(x); - int128 abs_y = abs(y); - int128 abs_xy = abs(mul(x, y)); - int128 abs_x_abs_y = mul(abs_x, abs_y); - - // Failure if all significant digits are lost - require(significant_digits_lost_in_mult(abs_x, abs_y) == false); - - // Assume a tolerance of two bits of precision - assert(equal_within_precision(abs_xy, abs_x_abs_y, 2)); - } - - // Test the subadditivity property - // | x + y | <= |x| + |y| - function abs_test_subadditivity(int128 x, int128 y) public pure { - int128 abs_x = abs(x); - int128 abs_y = abs(y); - int128 abs_xy = abs(add(x, y)); - - assert(abs_xy <= add(abs_x, abs_y)); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test the zero-case | 0 | = 0 - function abs_test_zero() public view { - int128 abs_zero; - - try this.abs(ZERO_FP) { - // If it doesn't revert, the value must be zero - abs_zero = this.abs(ZERO_FP); - assert(abs_zero == ZERO_FP); - } catch { - // Unexpected, the function must not revert here - assert(false); - } - } - - // Test the maximum value - function abs_test_maximum() public view { - int128 abs_max; - - try this.abs(MAX_64x64) { - // If it doesn't revert, the value must be MAX_64x64 - abs_max = this.abs(MAX_64x64); - assert(abs_max == MAX_64x64); - } catch {} - } - - // Test the minimum value - function abs_test_minimum() public view { - int128 abs_min; - - try this.abs(MIN_64x64) { - // If it doesn't revert, the value must be the negative of MIN_64x64 - abs_min = this.abs(MIN_64x64); - assert(abs_min == neg(MIN_64x64)); - } catch {} - } - - /* ================================================================ - - TESTS FOR FUNCTION inv() - - ================================================================ */ - - /* ================================================================ - Tests for mathematical properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test that the inverse of the inverse is close enough to the - // original number - function inv_test_double_inverse(int128 x) public view { - require(x != ZERO_FP); - - int128 double_inv_x = inv(inv(x)); - - // The maximum loss of precision will be 2 * log2(x) bits rounded up - uint256 loss = 2 * toUInt(log_2(x)) + 2; - - assert(equal_within_precision(x, double_inv_x, loss)); - } - - // Test equivalence with division - function inv_test_division(int128 x) public view { - require(x != ZERO_FP); - - int128 inv_x = inv(x); - int128 div_1_x = div(ONE_FP, x); - - assert(inv_x == div_1_x); - } - - // Test the anticommutativity of the division - // x / y == 1 / (y / x) - function inv_test_division_noncommutativity( - int128 x, - int128 y - ) public view { - require(x != ZERO_FP && y != ZERO_FP); - - int128 x_y = div(x, y); - int128 y_x = div(y, x); - - require( - significant_bits_after_mult(x, inv(y)) > REQUIRED_SIGNIFICANT_BITS - ); - require( - significant_bits_after_mult(y, inv(x)) > REQUIRED_SIGNIFICANT_BITS - ); - assert(equal_within_tolerance(x_y, inv(y_x), ONE_TENTH_FP)); - } - - // Test the multiplication of inverses - // 1/(x * y) == 1/x * 1/y - function inv_test_multiplication(int128 x, int128 y) public view { - require(x != ZERO_FP && y != ZERO_FP); - - int128 inv_x = inv(x); - int128 inv_y = inv(y); - int128 inv_x_times_inv_y = mul(inv_x, inv_y); - - int128 x_y = mul(x, y); - int128 inv_x_y = inv(x_y); - - require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); - require( - significant_bits_after_mult(inv_x, inv_y) > - REQUIRED_SIGNIFICANT_BITS - ); - - // The maximum loss of precision is given by the formula: - // 2 * | log_2(x) - log_2(y) | + 1 - uint256 loss = 2 * toUInt(abs(log_2(x) - log_2(y))) + 1; - - assert(equal_within_precision(inv_x_y, inv_x_times_inv_y, loss)); - } - - // Test identity property - // Intermediate result should have at least REQUIRED_SIGNIFICANT_BITS - function inv_test_identity(int128 x) public view { - require(x != ZERO_FP); - - int128 inv_x = inv(x); - int128 identity = mul(inv_x, x); - - require( - significant_bits_after_mult(x, inv_x) > REQUIRED_SIGNIFICANT_BITS - ); - - // They should agree with a tolerance of one tenth of a percent - assert(equal_within_tolerance(identity, ONE_FP, ONE_TENTH_FP)); - } - - // Test that the absolute value of the result is in range zero-one - // if x is greater than one, else, the absolute value of the result - // must be greater than one - function inv_test_values(int128 x) public view { - require(x != ZERO_FP); - - int128 abs_inv_x = abs(inv(x)); - - if (abs(x) >= ONE_FP) { - assert(abs_inv_x <= ONE_FP); - } else { - assert(abs_inv_x > ONE_FP); - } - } - - // Test that the result has the same sign as the argument - function inv_test_sign(int128 x) public view { - require(x != ZERO_FP); - - int128 inv_x = inv(x); - - if (x > ZERO_FP) { - assert(inv_x > ZERO_FP); - } else { - assert(inv_x < ZERO_FP); - } - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test the zero-case, should revert - function inv_test_zero() public view { - try this.inv(ZERO_FP) { - // Unexpected, the function must revert - assert(false); - } catch {} - } - - // Test the maximum value case, should not revert, and be close to zero - function inv_test_maximum() public view { - int128 inv_maximum; - - try this.inv(MAX_64x64) { - inv_maximum = this.inv(MAX_64x64); - assert(equal_within_precision(inv_maximum, ZERO_FP, 10)); - } catch { - // Unexpected, the function must not revert - assert(false); - } - } - - // Test the minimum value case, should not revert, and be close to zero - function inv_test_minimum() public view { - int128 inv_minimum; - - try this.inv(MAX_64x64) { - inv_minimum = this.inv(MAX_64x64); - assert(equal_within_precision(abs(inv_minimum), ZERO_FP, 10)); - } catch { - // Unexpected, the function must not revert - assert(false); - } - } - - /* ================================================================ - - TESTS FOR FUNCTION avg() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test that the result is between the two operands - // avg(x, y) >= min(x, y) && avg(x, y) <= max(x, y) - function avg_test_values_in_range(int128 x, int128 y) public pure { - int128 avg_xy = avg(x, y); - - if (x >= y) { - assert(avg_xy >= y && avg_xy <= x); - } else { - assert(avg_xy >= x && avg_xy <= y); - } - } - - // Test that the average of the same number is itself - // avg(x, x) == x - function avg_test_one_value(int128 x) public pure { - int128 avg_x = avg(x, x); - - assert(avg_x == x); - } - - // Test that the order of operands is irrelevant - // avg(x, y) == avg(y, x) - function avg_test_operand_order(int128 x, int128 y) public pure { - int128 avg_xy = avg(x, y); - int128 avg_yx = avg(y, x); - - assert(avg_xy == avg_yx); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for the maximum value - function avg_test_maximum() public view { - int128 result; - - // This may revert due to overflow depending on implementation - // If it doesn't revert, the result must be MAX_64x64 - try this.avg(MAX_64x64, MAX_64x64) { - result = this.avg(MAX_64x64, MAX_64x64); - assert(result == MAX_64x64); - } catch {} - } - - // Test for the minimum value - function avg_test_minimum() public view { - int128 result; - - // This may revert due to overflow depending on implementation - // If it doesn't revert, the result must be MIN_64x64 - try this.avg(MIN_64x64, MIN_64x64) { - result = this.avg(MIN_64x64, MIN_64x64); - assert(result == MIN_64x64); - } catch {} - } - - /* ================================================================ - - TESTS FOR FUNCTION gavg() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test that the result is between the two operands - // gavg(x, y) >= min(x, y) && gavg(x, y) <= max(x, y) - function gavg_test_values_in_range(int128 x, int128 y) public view { - int128 gavg_xy = gavg(x, y); - - if (x == ZERO_FP || y == ZERO_FP) { - assert(gavg_xy == ZERO_FP); - } else { - if (abs(x) >= abs(y)) { - assert(gavg_xy >= abs(y) && gavg_xy <= abs(x)); - } else { - assert(gavg_xy >= abs(x) && gavg_xy <= abs(y)); - } - } - } - - // Test that the average of the same number is itself - // gavg(x, x) == | x | - function gavg_test_one_value(int128 x) public pure { - int128 gavg_x = gavg(x, x); - - assert(gavg_x == abs(x)); - } - - // Test that the order of operands is irrelevant - // gavg(x, y) == gavg(y, x) - function gavg_test_operand_order(int128 x, int128 y) public pure { - int128 gavg_xy = gavg(x, y); - int128 gavg_yx = gavg(y, x); - - assert(gavg_xy == gavg_yx); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for the maximum value - function gavg_test_maximum() public view { - int128 result; - - // This may revert due to overflow depending on implementation - // If it doesn't revert, the result must be MAX_64x64 - try this.gavg(MAX_64x64, MAX_64x64) { - result = this.gavg(MAX_64x64, MAX_64x64); - assert(result == MAX_64x64); - } catch {} - } - - // Test for the minimum value - function gavg_test_minimum() public view { - int128 result; - - // This may revert due to overflow depending on implementation - // If it doesn't revert, the result must be MIN_64x64 - try this.gavg(MIN_64x64, MIN_64x64) { - result = this.gavg(MIN_64x64, MIN_64x64); - assert(result == MIN_64x64); - } catch {} - } - - /* ================================================================ - - TESTS FOR FUNCTION pow() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for zero exponent - // x ** 0 == 1 - function pow_test_zero_exponent(int128 x) public view { - int128 x_pow_0 = pow(x, 0); - - assert(x_pow_0 == ONE_FP); - } - - // Test for zero base - // 0 ** x == 0 (for positive x) - function pow_test_zero_base(uint256 x) public view { - require(x != 0); - - int128 zero_pow_x = pow(ZERO_FP, x); - - assert(zero_pow_x == ZERO_FP); - } - - // Test for exponent one - // x ** 1 == x - function pow_test_one_exponent(int128 x) public pure { - int128 x_pow_1 = pow(x, 1); - - assert(x_pow_1 == x); - } - - // Test for base one - // 1 ** x == 1 - function pow_test_base_one(uint256 x) public view { - int128 one_pow_x = pow(ONE_FP, x); - - assert(one_pow_x == ONE_FP); - } - - // Test for product of powers of the same base - // x ** a * x ** b == x ** (a + b) - function pow_test_product_same_base( - int128 x, - uint256 a, - uint256 b - ) public view { - require(x != ZERO_FP); - - int128 x_a = pow(x, a); - int128 x_b = pow(x, b); - int128 x_ab = pow(x, a + b); - - assert(equal_within_precision(mul(x_a, x_b), x_ab, 2)); - } - - // Test for power of an exponentiation - // (x ** a) ** b == x ** (a * b) - function pow_test_power_of_an_exponentiation( - int128 x, - uint256 a, - uint256 b - ) public view { - require(x != ZERO_FP); - - int128 x_a = pow(x, a); - int128 x_a_b = pow(x_a, b); - int128 x_ab = pow(x, a * b); - - assert(equal_within_precision(x_a_b, x_ab, 2)); - } - - // Test for power of a product - // (x * y) ** a == x ** a * y ** a - function pow_test_product_same_base( - int128 x, - int128 y, - uint256 a - ) public view { - require(x != ZERO_FP && y != ZERO_FP); - require(a > 2 ** 32); // to avoid massive loss of precision - - int128 x_y = mul(x, y); - int128 xy_a = pow(x_y, a); - - int128 x_a = pow(x, a); - int128 y_a = pow(y, a); - - assert(equal_within_precision(mul(x_a, y_a), xy_a, 2)); - } - - // Test for result being greater than or lower than the argument, depending on - // its absolute value and the value of the exponent - function pow_test_values(int128 x, uint256 a) public view { - require(x != ZERO_FP); - - int128 x_a = pow(x, a); - - if (abs(x) >= ONE_FP) { - assert(abs(x_a) >= ONE_FP); - } - - if (abs(x) <= ONE_FP) { - assert(abs(x_a) <= ONE_FP); - } - } - - // Test for result sign: if the exponent is even, sign is positive - // if the exponent is odd, preserves the sign of the base - function pow_test_sign(int128 x, uint256 a) public view { - require(x != ZERO_FP && a != 0); - - int128 x_a = pow(x, a); - - // This prevents the case where a small negative number gets - // rounded down to zero and thus changes sign - require(x_a != ZERO_FP); - - // If the exponent is even - if (a % 2 == 0) { - assert(x_a == abs(x_a)); - } else { - // x_a preserves x sign - if (x < ZERO_FP) { - assert(x_a < ZERO_FP); - } else { - assert(x_a > ZERO_FP); - } - } - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for maximum base and exponent > 1 - function pow_test_maximum_base(uint256 a) public view { - require(a > 1); - - try this.pow(MAX_64x64, a) { - // Unexpected, should revert because of overflow - assert(false); - } catch { - // Expected revert - } - } - - // Test for abs(base) < 1 and high exponent - function pow_test_high_exponent(int128 x, uint256 a) public view { - require(abs(x) < ONE_FP && a > 2 ** 64); - - int128 result = pow(x, a); - - assert(result == ZERO_FP); - } - - /* ================================================================ - - TESTS FOR FUNCTION sqrt() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for the inverse operation - // sqrt(x) * sqrt(x) == x - function sqrt_test_inverse_mul(int128 x) public view { - require(x >= ZERO_FP); - - int128 sqrt_x = sqrt(x); - int128 sqrt_x_squared = mul(sqrt_x, sqrt_x); - - // Precision loss is at most half the bits of the operand - assert( - equal_within_precision( - sqrt_x_squared, - x, - (toUInt(log_2(x)) >> 1) + 2 - ) - ); - } - - // Test for the inverse operation - // sqrt(x) ** 2 == x - function sqrt_test_inverse_pow(int128 x) public view { - require(x >= ZERO_FP); - - int128 sqrt_x = sqrt(x); - int128 sqrt_x_squared = pow(sqrt_x, 2); - - // Precision loss is at most half the bits of the operand - assert( - equal_within_precision( - sqrt_x_squared, - x, - (toUInt(log_2(x)) >> 1) + 2 - ) - ); - } - - // Test for distributive property respect to the multiplication - // sqrt(x) * sqrt(y) == sqrt(x * y) - function sqrt_test_distributive(int128 x, int128 y) public view { - require(x >= ZERO_FP && y >= ZERO_FP); - - int128 sqrt_x = sqrt(x); - int128 sqrt_y = sqrt(y); - int128 sqrt_x_sqrt_y = mul(sqrt_x, sqrt_y); - int128 sqrt_xy = sqrt(mul(x, y)); - - // Ensure we have enough significant digits for the result to be meaningful - require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); - require( - significant_bits_after_mult(sqrt_x, sqrt_y) > - REQUIRED_SIGNIFICANT_BITS - ); - - // Allow an error of up to one tenth of a percent - assert(equal_within_tolerance(sqrt_x_sqrt_y, sqrt_xy, ONE_TENTH_FP)); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for zero case - function sqrt_test_zero() public view { - assert(sqrt(ZERO_FP) == ZERO_FP); - } - - // Test for maximum value - function sqrt_test_maximum() public view { - try this.sqrt(MAX_64x64) { - // Expected behaviour, MAX_64x64 is positive, and operation - // should not revert as the result is in range - } catch { - // Unexpected, should not revert - assert(false); - } - } - - // Test for minimum value - function sqrt_test_minimum() public view { - try this.sqrt(MIN_64x64) { - // Unexpected, should revert. MIN_64x64 is negative. - assert(false); - } catch { - // Expected behaviour, revert - } - } - - // Test for negative operands - function sqrt_test_negative(int128 x) public view { - require(x < ZERO_FP); - - try this.sqrt(x) { - // Unexpected, should revert - assert(false); - } catch { - // Expected behaviour, revert - } - } - - /* ================================================================ - - TESTS FOR FUNCTION log2() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for distributive property respect to multiplication - // log2(x * y) = log2(x) + log2(y) - function log2_test_distributive_mul(int128 x, int128 y) public view { - int128 log2_x = log_2(x); - int128 log2_y = log_2(y); - int128 log2_x_log2_y = add(log2_x, log2_y); - - int128 xy = mul(x, y); - int128 log2_xy = log_2(xy); - - // Ensure we have enough significant digits for the result to be meaningful - require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); - - // The maximum loss of precision is given by the formula: - // | log_2(x) + log_2(y) | - uint256 loss = toUInt(abs(log_2(x) + log_2(y))); - - assert(equal_within_precision(log2_x_log2_y, log2_xy, loss)); - } - - // Test for logarithm of a power - // log2(x ** y) = y * log2(x) - function log2_test_power(int128 x, uint256 y) public pure { - int128 x_y = pow(x, y); - int128 log2_x_y = log_2(x_y); - - uint256 y_log2_x = mulu(log_2(x), y); - - assert(y_log2_x == toUInt(log2_x_y)); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for zero case, should revert - function log2_test_zero() public view { - try this.log_2(ZERO_FP) { - // Unexpected, should revert - assert(false); - } catch { - // Expected revert, log(0) is not defined - } - } - - // Test for maximum value case, should return a positive number - function log2_test_maximum() public view { - int128 result; - - try this.log_2(MAX_64x64) { - // Expected, should not revert and the result must be > 0 - result = this.log_2(MAX_64x64); - assert(result > ZERO_FP); - } catch { - // Unexpected - assert(false); - } - } - - // Test for negative values, should revert as log2 is not defined - function log2_test_negative(int128 x) public view { - require(x < ZERO_FP); - - try this.log_2(x) { - // Unexpected, should revert - assert(false); - } catch { - // Expected - } - } - - /* ================================================================ - - TESTS FOR FUNCTION ln() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for distributive property respect to multiplication - // ln(x * y) = ln(x) + ln(y) - function ln_test_distributive_mul(int128 x, int128 y) public view { - require(x > ZERO_FP && y > ZERO_FP); - - int128 ln_x = ln(x); - int128 ln_y = ln(y); - int128 ln_x_ln_y = add(ln_x, ln_y); - - int128 xy = mul(x, y); - int128 ln_xy = ln(xy); - - require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); - - // The maximum loss of precision is given by the formula: - // | log_2(x) + log_2(y) | - uint256 loss = toUInt(abs(log_2(x) + log_2(y))); - - assert(equal_within_precision(ln_x_ln_y, ln_xy, loss)); - } - - // Test for logarithm of a power - // ln(x ** y) = y * ln(x) - function ln_test_power(int128 x, uint256 y) public pure { - int128 x_y = pow(x, y); - int128 ln_x_y = ln(x_y); - - uint256 y_ln_x = mulu(ln(x), y); - - assert(y_ln_x == toUInt(ln_x_y)); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for zero case, should revert - function ln_test_zero() public view { - try this.ln(ZERO_FP) { - // Unexpected, should revert - assert(false); - } catch { - // Expected revert, log(0) is not defined - } - } - - // Test for maximum value case, should return a positive number - function ln_test_maximum() public view { - int128 result; - - try this.ln(MAX_64x64) { - // Expected, should not revert and the result must be > 0 - result = this.ln(MAX_64x64); - assert(result > ZERO_FP); - } catch { - // Unexpected - assert(false); - } - } - - // Test for negative values, should revert as ln is not defined - function ln_test_negative(int128 x) public view { - require(x < ZERO_FP); - - try this.ln(x) { - // Unexpected, should revert - assert(false); - } catch { - // Expected - } - } - - /* ================================================================ - - TESTS FOR FUNCTION exp2() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for equality with pow(2, x) for integer x - // pow(2, x) == exp_2(x) - function exp2_test_equivalence_pow(uint256 x) public view { - int128 exp2_x = exp_2(fromUInt(x)); - int128 pow_2_x = pow(TWO_FP, x); - - assert(exp2_x == pow_2_x); - } - - // Test for inverse function - // If y = log_2(x) then exp_2(y) == x - function exp2_test_inverse(int128 x) public view { - int128 log2_x = log_2(x); - int128 exp2_x = exp_2(log2_x); - - uint256 bits = 50; - - if (log2_x < ZERO_FP) { - bits = uint256(int256(bits) + int256(log2_x)); - } - - assert(equal_most_significant_bits_within_precision(x, exp2_x, bits)); - } - - // Test for negative exponent - // exp_2(-x) == inv( exp_2(x) ) - function exp2_test_negative_exponent(int128 x) public view { - require(x < ZERO_FP && x != MIN_64x64); - - int128 exp2_x = exp_2(x); - int128 exp2_minus_x = exp_2(-x); - - // Result should be within 4 bits precision for the worst case - assert(equal_within_precision(exp2_x, inv(exp2_minus_x), 4)); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for zero case - // exp_2(0) == 1 - function exp2_test_zero() public view { - int128 exp_zero = exp_2(ZERO_FP); - assert(exp_zero == ONE_FP); - } - - // Test for maximum value. This should overflow as it won't fit - // in the data type - function exp2_test_maximum() public view { - try this.exp_2(MAX_64x64) { - // Unexpected, should revert - assert(false); - } catch { - // Expected revert - } - } - - // Test for minimum value. This should return zero since - // 2 ** -x == 1 / 2 ** x that tends to zero as x increases - function exp2_test_minimum() public view { - int128 result; - - try this.exp_2(MIN_64x64) { - // Expected, should not revert, check that value is zero - result = exp_2(MIN_64x64); - assert(result == ZERO_FP); - } catch { - // Unexpected revert - assert(false); - } - } - - /* ================================================================ - - TESTS FOR FUNCTION exp() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for inverse function - // If y = ln(x) then exp(y) == x - function exp_test_inverse(int128 x) public view { - int128 ln_x = ln(x); - int128 exp_x = exp(ln_x); - int128 log2_x = log_2(x); - - uint256 bits = 48; - - if (log2_x < ZERO_FP) { - bits = uint256(int256(bits) + int256(log2_x)); - } - - assert(equal_most_significant_bits_within_precision(x, exp_x, bits)); - } - - // Test for negative exponent - // exp(-x) == inv( exp(x) ) - function exp_test_negative_exponent(int128 x) public view { - require(x < ZERO_FP && x != MIN_64x64); - - int128 exp_x = exp(x); - int128 exp_minus_x = exp(-x); - - // Result should be within 4 bits precision for the worst case - assert(equal_within_precision(exp_x, inv(exp_minus_x), 4)); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for zero case - // exp(0) == 1 - function exp_test_zero() public view { - int128 exp_zero = exp(ZERO_FP); - assert(exp_zero == ONE_FP); - } - - // Test for maximum value. This should overflow as it won't fit - // in the data type - function exp_test_maximum() public view { - try this.exp(MAX_64x64) { - // Unexpected, should revert - assert(false); - } catch { - // Expected revert - } - } - - // Test for minimum value. This should return zero since - // e ** -x == 1 / e ** x that tends to zero as x increases - function exp_test_minimum() public view { - int128 result; - - try this.exp(MIN_64x64) { - // Expected, should not revert, check that value is zero - result = exp(MIN_64x64); - assert(result == ZERO_FP); - } catch { - // Unexpected revert - assert(false); - } - } -} From ac22a9eac100bd9461d3ae7183734956cb9a7c33 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Tue, 9 May 2023 19:40:56 +0200 Subject: [PATCH 03/32] updated comments --- .../Math/PRBMath/PRBMathSD59x18PropertyTests.sol | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol index fa8b4b2..6b2b589 100644 --- a/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol @@ -7,8 +7,8 @@ import {mul as helpersMul, div as helpersDiv, abs as helpersAbs, ln as helpersLn contract CryticPRBMath59x18Properties { /* ================================================================ - 64x64 fixed-point constants used for testing specific values. - This assumes that ABDK library's fromInt(x) works as expected. + 59x18 fixed-point constants used for testing specific values. + This assumes that PRBMath library's convert(x) works as expected. ================================================================ */ SD59x18 internal ZERO_FP = convert(0); SD59x18 internal ONE_FP = convert(1); @@ -234,7 +234,7 @@ contract CryticPRBMath59x18Properties { ================================================================ */ // The result of the addition must be between the maximum - // and minimum allowed values for 64x64 + // and minimum allowed values for SD59x18 function add_test_range(SD59x18 x, SD59x18 y) public view { try this.add(x, y) returns (SD59x18 result) { assert(result.lte(MAX_SD59x18) && result.gte(MIN_SD59x18)); @@ -511,7 +511,7 @@ contract CryticPRBMath59x18Properties { ================================================================ */ // The result of the multiplication must be between the maximum - // and minimum allowed values for 64x64 + // and minimum allowed values for SD59x18 function mul_test_range(SD59x18 x, SD59x18 y) public view { try this.mul(x, y) returns(SD59x18 result) { assert(result.lte(MAX_SD59x18) && result.gte(MIN_SD59x18)); @@ -711,7 +711,7 @@ contract CryticPRBMath59x18Properties { // todo check what is used for SD59x18 // Test for the maximum value case - // Since this is implementation-dependant, we will actually test with MAX_64x64-EPS + // Since this is implementation-dependant, we will actually test with MAX_SD59x18-EPS function neg_test_maximum() public view { try this.neg(sub(MAX_SD59x18, EPSILON)) { // Expected behaviour, does not revert @@ -722,7 +722,7 @@ contract CryticPRBMath59x18Properties { // todo check what is used for SD59x18 // Test for the minimum value case - // Since this is implementation-dependant, we will actually test with MIN_64x64+EPS + // Since this is implementation-dependant, we will actually test with MIN_SD59x18+EPS function neg_test_minimum() public view { try this.neg(add(MIN_SD59x18, EPSILON)) { // Expected behaviour, does not revert @@ -1671,7 +1671,7 @@ contract CryticPRBMath59x18Properties { // Test for negative exponent // exp(-x) == inv( exp(x) ) function exp_test_negative_exponent(SD59x18 x) public view { - require(x.lt(ZERO_FP) && x.neq(MIN_64x64)); + require(x.lt(ZERO_FP) && x.neq(MIN_SD59x18)); SD59x18 exp_x = exp(x); SD59x18 exp_minus_x = exp(neg(x)); From 530c5fc7ce2b5819a5e44ce7ce87b8d63c145d6e Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Wed, 10 May 2023 10:25:28 +0200 Subject: [PATCH 04/32] added comparison operators --- contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol index 6b2b589..96ca85c 100644 --- a/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol @@ -1,7 +1,8 @@ pragma solidity ^0.8.19; import { SD59x18 } from "@prb/math/SD59x18.sol"; -import {add as helpersAdd, sub as helpersSub} from "@prb/math/sd59x18/Helpers.sol"; +import {add as helpersAdd, sub as helpersSub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb/math/sd59x18/Helpers.sol"; +import {convert} from "@prb/math/sd59x18/Conversions.sol"; import {mul as helpersMul, div as helpersDiv, abs as helpersAbs, ln as helpersLn, exp as helpersExp, exp2 as helpersExp2, log2 as helpersLog2, sqrt as helpersSqrt, pow as helpersPow, avg as helpersAvg, inv as helpersInv} from "@prb/math/sd59x18/Math.sol"; contract CryticPRBMath59x18Properties { @@ -39,8 +40,7 @@ contract CryticPRBMath59x18Properties { // These functions allows to compare a and b for equality, discarding // the last precision_bits bits. - // An absolute value function is implemented inline in order to not use - // the implementation from the library under test. + // Uses functions from the library under test! function equal_within_precision(SD59x18 a, SD59x18 b, uint256 precision_bits) public pure returns(bool) { SD59x18 max = gt(a , b) ? a : b; SD59x18 min = gt(a , b) ? b : a; @@ -149,7 +149,7 @@ contract CryticPRBMath59x18Properties { return helpersExp(x); } - function exp2(SD59x18 x) public pure returns (SD59x18) { + function exp_2(SD59x18 x) public pure returns (SD59x18) { return helpersExp2(x); } From 8de26186f1d28b51933a13bd7d60fdb530e5ef53 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Wed, 10 May 2023 12:28:12 +0200 Subject: [PATCH 05/32] added echidna config to test PRB --- .../ABDKMath64x64PropertyTests.sol | 2 +- .../PRBMath/PRBMathSD59x18PropertyTests.sol | 155 ++++++++++++------ contracts/Math/PRBMath/echidna-config.yaml | 6 + hardhat.config.js | 87 +++++----- 4 files changed, 164 insertions(+), 86 deletions(-) create mode 100644 contracts/Math/PRBMath/echidna-config.yaml diff --git a/contracts/Math/ABDKMath64x64/ABDKMath64x64PropertyTests.sol b/contracts/Math/ABDKMath64x64/ABDKMath64x64PropertyTests.sol index 3246adc..5bbec4d 100644 --- a/contracts/Math/ABDKMath64x64/ABDKMath64x64PropertyTests.sol +++ b/contracts/Math/ABDKMath64x64/ABDKMath64x64PropertyTests.sol @@ -1314,7 +1314,7 @@ contract CryticABDKMath64x64Properties { // Test for power of a product // (x * y) ** a == x ** a * y ** a - function pow_test_product_same_base( + function pow_test_product_power( int128 x, int128 y, uint256 a diff --git a/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol index 96ca85c..0e9b95b 100644 --- a/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol @@ -1,9 +1,11 @@ pragma solidity ^0.8.19; -import { SD59x18 } from "@prb/math/SD59x18.sol"; -import {add as helpersAdd, sub as helpersSub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb/math/sd59x18/Helpers.sol"; -import {convert} from "@prb/math/sd59x18/Conversions.sol"; -import {mul as helpersMul, div as helpersDiv, abs as helpersAbs, ln as helpersLn, exp as helpersExp, exp2 as helpersExp2, log2 as helpersLog2, sqrt as helpersSqrt, pow as helpersPow, avg as helpersAvg, inv as helpersInv} from "@prb/math/sd59x18/Math.sol"; +import { SD59x18 } from "@prb/math/src/SD59x18.sol"; +import {add as helpersAdd, sub as helpersSub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb/math/src/sd59x18/Helpers.sol"; +import {convert} from "@prb/math/src/sd59x18/Conversions.sol"; +import {msb} from "@prb/math/src/Common.sol"; +import {intoUint128, intoUint256} from "@prb/math/src/sd59x18/Casting.sol"; +import {mul as helpersMul, div as helpersDiv, abs as helpersAbs, ln as helpersLn, exp as helpersExp, exp2 as helpersExp2, log2 as helpersLog2, sqrt as helpersSqrt, pow as helpersPow, avg as helpersAvg, inv as helpersInv, log10 as helpersLog10, floor as helpersFloor} from "@prb/math/src/sd59x18/Math.sol"; contract CryticPRBMath59x18Properties { @@ -27,6 +29,34 @@ contract CryticPRBMath59x18Properties { ================================================================ */ uint256 internal REQUIRED_SIGNIFICANT_BITS = 10; + /* ================================================================ + Integer representations maximum values. + These constants are used for testing edge cases or limits for + possible values. + ================================================================ */ + /// @dev The unit number, which gives the decimal precision of SD59x18. + int256 constant uUNIT = 1e18; + SD59x18 constant UNIT = SD59x18.wrap(1e18); + + /// @dev The minimum value an SD59x18 number can have. + int256 constant uMIN_SD59x18 = -57896044618658097711785492504343953926634992332820282019728_792003956564819968; + SD59x18 constant MIN_SD59x18 = SD59x18.wrap(uMIN_SD59x18); + + /// @dev The maximum value an SD59x18 number can have. + int256 constant uMAX_SD59x18 = 57896044618658097711785492504343953926634992332820282019728_792003956564819967; + SD59x18 constant MAX_SD59x18 = SD59x18.wrap(uMAX_SD59x18); + + /// @dev The maximum input permitted in {exp2}. + int256 constant uEXP2_MAX_INPUT = 192e18 - 1; + SD59x18 constant EXP2_MAX_INPUT = SD59x18.wrap(uEXP2_MAX_INPUT); + + /// @dev The maximum input permitted in {exp}. + int256 constant uEXP_MAX_INPUT = 133_084258667509499440; + SD59x18 constant EXP_MAX_INPUT = SD59x18.wrap(uEXP_MAX_INPUT); + + /// @dev Euler's number as an SD59x18 number. + SD59x18 constant E = SD59x18.wrap(2_718281828459045235); + /* ================================================================ Events used for debugging or showing information. ================================================================ */ @@ -69,8 +99,8 @@ contract CryticPRBMath59x18Properties { // Check that there are remaining significant digits after a multiplication // Uses functions from the library under test! function significant_digits_lost_in_mult(SD59x18 a, SD59x18 b) public pure returns (bool) { - int256 la = convert(floor(log10(abs(a)))); - int256 lb = convert(floor(log10(abs(b)))); + int256 la = convert(floor(log_10(abs(a)))); + int256 lb = convert(floor(log_10(abs(b)))); return(la + lb < -18); } @@ -78,34 +108,59 @@ contract CryticPRBMath59x18Properties { // Return how many significant bits will remain after multiplying a and b // Uses functions from the library under test! function significant_digits_after_mult(SD59x18 a, SD59x18 b) public pure returns (uint256) { - int256 la = convert(floor(log10(abs(a)))); - int256 lb = convert(floor(log10(abs(b)))); + int256 la = convert(floor(log_10(abs(a)))); + int256 lb = convert(floor(log_10(abs(b)))); int256 prec = la + lb; if (prec < -18) return 0; - else return(18 + prec); + else return(18 + uint256(prec)); } // Returns true if the n most significant bits of a and b are almost equal // Uses functions from the library under test! - function equal_most_significant_digits_within_precision(SD59x18 a, SD59x18 b, uint256 bits) public pure returns (bool) { + function equal_most_significant_digits_within_precision(SD59x18 a, SD59x18 b, uint256 bits) public view returns (bool) { // Get the number of bits in a and b // Since log(x) returns in the interval [-64, 63), add 64 to be in the interval [0, 127) - uint256 a_bits = uint256(int256(convert(log2(a)) + 64)); - uint256 b_bits = uint256(int256(convert(log2(b)) + 64)); + uint256 a_bits = uint256(int256(convert(log_2(a)) + 64)); + uint256 b_bits = uint256(int256(convert(log_2(b)) + 64)); // a and b lengths may differ in 1 bit, so the shift should take into account the longest uint256 shift_bits = (a_bits > b_bits) ? (a_bits - bits) : (b_bits - bits); // Get the _bits_ most significant bits of a and b - uint256 a_msb = msb(a, bits) >> shift_bits; - uint256 b_msb = msb(b, bits) >> shift_bits; + uint256 a_msb = most_significant_bits(a, bits) >> shift_bits; + uint256 b_msb = most_significant_bits(b, bits) >> shift_bits; // See if they are equal within 1 bit precision // This could be modified to get the precision as a parameter to the function return equal_within_precision_u(a_msb, b_msb, 1); } + // Return the i most significant bits from |n|. If n has less than i significant bits, return |n| + // Uses functions from the library under test! + function most_significant_bits( + SD59x18 n, + uint256 i + ) public view returns (uint256) { + if (n.eq(MIN_SD59x18)) return 0; + + // Create a mask consisting of i bits set to 1 + uint256 mask = (2 ** i) - 1; + + // Get the positive value of n + uint256 value = (n.gt(ZERO_FP)) ? intoUint256(n) : intoUint256(neg(n)); + + // Get the position of the MSB set to 1 of n + uint256 pos = msb(value); + + // Shift the mask to match the rightmost 1-set bit + if (pos > i) { + mask <<= (pos - i); + } + + return (value & mask); + } + /* ================================================================ Library wrappers. These functions allow calling the PRBMathSD59x18 library. @@ -173,6 +228,14 @@ contract CryticPRBMath59x18Properties { return helpersInv(x); } + function log_10(SD59x18 x) public pure returns (SD59x18) { + return helpersLog10(x); + } + + function floor(SD59x18 x) public pure returns (SD59x18) { + return helpersFloor(x); + } + /* ================================================================ TESTS FOR FUNCTION add() @@ -704,7 +767,7 @@ contract CryticPRBMath59x18Properties { // Test for the zero-case // -0 == 0 function neg_test_zero() public view { - int128 neg_x = neg(ZERO_FP); + SD59x18 neg_x = neg(ZERO_FP); assert(neg_x.eq(ZERO_FP)); } @@ -748,7 +811,7 @@ contract CryticPRBMath59x18Properties { // Test that the absolute value is always positive function abs_test_positive(SD59x18 x) public view { - int128 abs_x = abs(x); + SD59x18 abs_x = abs(x); assert(abs_x.gte(ZERO_FP)); } @@ -851,7 +914,7 @@ contract CryticPRBMath59x18Properties { SD59x18 double_inv_x = inv(inv(x)); // The maximum loss of precision will be 2 * log2(x) bits rounded up - uint256 loss = 2 * SD59x18.intoUint256(log_2(x)) + 2; + uint256 loss = 2 * intoUint256(log_2(x)) + 2; assert(equal_within_precision(x, double_inv_x, loss)); } @@ -878,10 +941,10 @@ contract CryticPRBMath59x18Properties { SD59x18 y_x = div(y, x); require( - significant_bits_after_mult(x, inv(y)) > REQUIRED_SIGNIFICANT_BITS + significant_digits_after_mult(x, inv(y)) > REQUIRED_SIGNIFICANT_BITS ); require( - significant_bits_after_mult(y, inv(x)) > REQUIRED_SIGNIFICANT_BITS + significant_digits_after_mult(y, inv(x)) > REQUIRED_SIGNIFICANT_BITS ); assert(equal_within_tolerance(x_y, inv(y_x), ONE_TENTH_FP)); } @@ -898,14 +961,14 @@ contract CryticPRBMath59x18Properties { SD59x18 x_y = mul(x, y); SD59x18 inv_x_y = inv(x_y); - require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); require( - significant_bits_after_mult(inv_x, inv_y) > REQUIRED_SIGNIFICANT_BITS + significant_digits_after_mult(inv_x, inv_y) > REQUIRED_SIGNIFICANT_BITS ); // The maximum loss of precision is given by the formula: // 2 * | log_2(x) - log_2(y) | + 1 - uint256 loss = 2 * SD59x18.intoUint256(abs(log_2(x) - log_2(y))) + 1; + uint256 loss = 2 * intoUint256(abs(log_2(x) - log_2(y))) + 1; assert(equal_within_precision(inv_x_y, inv_x_times_inv_y, loss)); } @@ -919,7 +982,7 @@ contract CryticPRBMath59x18Properties { SD59x18 identity = mul(inv_x, x); require( - significant_bits_after_mult(x, inv_x) > REQUIRED_SIGNIFICANT_BITS + significant_digits_after_mult(x, inv_x) > REQUIRED_SIGNIFICANT_BITS ); // They should agree with a tolerance of one tenth of a percent @@ -1110,7 +1173,7 @@ contract CryticPRBMath59x18Properties { // Test for exponent one // x ** 1 == x - function pow_test_one_exponent(SD59x18 x) public pure { + function pow_test_one_exponent(SD59x18 x) public view { SD59x18 x_pow_1 = pow(x, ONE_FP); assert(x_pow_1.eq(x)); @@ -1158,7 +1221,7 @@ contract CryticPRBMath59x18Properties { // Test for power of a product // (x * y) ** a == x ** a * y ** a - function pow_test_product_same_base( + function pow_test_product_power( SD59x18 x, SD59x18 y, SD59x18 a @@ -1167,11 +1230,11 @@ contract CryticPRBMath59x18Properties { // todo this should probably be changed require(a.gt(convert(2 ** 32))); // to avoid massive loss of precision - int128 x_y = mul(x, y); - int128 xy_a = pow(x_y, a); + SD59x18 x_y = mul(x, y); + SD59x18 xy_a = pow(x_y, a); - int128 x_a = pow(x, a); - int128 y_a = pow(y, a); + SD59x18 x_a = pow(x, a); + SD59x18 y_a = pow(y, a); assert(equal_within_precision(mul(x_a, y_a), xy_a, 2)); } @@ -1239,7 +1302,7 @@ contract CryticPRBMath59x18Properties { function pow_test_high_exponent(SD59x18 x, SD59x18 a) public view { require(abs(x).lt(ONE_FP) && a.gt(convert(2 ** 64))); - int128 result = pow(x, a); + SD59x18 result = pow(x, a); assert(result.eq(ZERO_FP)); } @@ -1269,15 +1332,15 @@ contract CryticPRBMath59x18Properties { function sqrt_test_inverse_mul(SD59x18 x) public view { require(x.lte(ZERO_FP)); - int128 sqrt_x = sqrt(x); - int128 sqrt_x_squared = mul(sqrt_x, sqrt_x); + SD59x18 sqrt_x = sqrt(x); + SD59x18 sqrt_x_squared = mul(sqrt_x, sqrt_x); // Precision loss is at most half the bits of the operand assert( equal_within_precision( sqrt_x_squared, x, - (SD59x18.intoUint256(log_2(x)) >> 1) + 2 + (intoUint256(log_2(x)) >> 1) + 2 ) ); } @@ -1295,7 +1358,7 @@ contract CryticPRBMath59x18Properties { equal_within_precision( sqrt_x_squared, x, - (SD59x18.intoUint256(log_2(x)) >> 1) + 2 + (intoUint256(log_2(x)) >> 1) + 2 ) ); } @@ -1311,9 +1374,9 @@ contract CryticPRBMath59x18Properties { SD59x18 sqrt_xy = sqrt(mul(x, y)); // Ensure we have enough significant digits for the result to be meaningful - require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); require( - significant_bits_after_mult(sqrt_x, sqrt_y) > + significant_digits_after_mult(sqrt_x, sqrt_y) > REQUIRED_SIGNIFICANT_BITS ); @@ -1391,11 +1454,11 @@ contract CryticPRBMath59x18Properties { SD59x18 log2_xy = log_2(xy); // Ensure we have enough significant digits for the result to be meaningful - require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); // The maximum loss of precision is given by the formula: // | log_2(x) + log_2(y) | - uint256 loss = SD59x18.intoUint256(abs(log_2(x) + log_2(y))); + uint256 loss = intoUint256(abs(log_2(x) + log_2(y))); assert(equal_within_precision(log2_x_log2_y, log2_xy, loss)); } @@ -1479,11 +1542,11 @@ contract CryticPRBMath59x18Properties { SD59x18 xy = mul(x, y); SD59x18 ln_xy = ln(xy); - require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); // The maximum loss of precision is given by the formula: // | log_2(x) + log_2(y) | - uint256 loss = SD59x18.intoUint256(abs(log_2(x) + log_2(y))); + uint256 loss = intoUint256(abs(log_2(x) + log_2(y))); assert(equal_within_precision(ln_x_ln_y, ln_xy, loss)); } @@ -1562,8 +1625,8 @@ contract CryticPRBMath59x18Properties { // Test for equality with pow(2, x) for integer x // pow(2, x) == exp_2(x) - function exp2_test_equivalence_pow(uint256 x) public view { - SD59x18 exp2_x = exp_2(convert(x)); + function exp2_test_equivalence_pow(SD59x18 x) public view { + SD59x18 exp2_x = exp_2(x); SD59x18 pow_2_x = pow(TWO_FP, x); assert(exp2_x.eq(pow_2_x)); @@ -1579,10 +1642,10 @@ contract CryticPRBMath59x18Properties { uint256 bits = 50; if (log2_x.lt(ZERO_FP)) { - bits = SD59x18.intoUint256(convert(bits).add(convert(log2_x))); + bits = intoUint256(convert(int256(bits)).add(log2_x)); } - assert(equal_most_significant_bits_within_precision(x, exp2_x, bits)); + assert(equal_most_significant_digits_within_precision(x, exp2_x, bits)); } // Test for negative exponent @@ -1662,10 +1725,10 @@ contract CryticPRBMath59x18Properties { uint256 bits = 48; if (log2_x.lt(ZERO_FP)) { - bits = SD59x18.intoUint256(convert(bits).add(convert(log2_x))); + bits = intoUint256(convert(int256(bits)).add(log2_x)); } - assert(equal_most_significant_bits_within_precision(x, exp_x, bits)); + assert(equal_most_significant_digits_within_precision(x, exp_x, bits)); } // Test for negative exponent diff --git a/contracts/Math/PRBMath/echidna-config.yaml b/contracts/Math/PRBMath/echidna-config.yaml new file mode 100644 index 0000000..0b938e7 --- /dev/null +++ b/contracts/Math/PRBMath/echidna-config.yaml @@ -0,0 +1,6 @@ +corpusDir: "echidna-corpus" +testMode: assertion +testLimit: 100000 +deployer: "0x10000" +sender: ["0x10000", "0x20000", "0x30000"] +cryticArgs: ["--compile-force-framework", "hardhat"] diff --git a/hardhat.config.js b/hardhat.config.js index a443bc8..8eca546 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -2,43 +2,52 @@ const urlOverride = process.env.ETH_PROVIDER_URL; const chainId = parseInt(process.env.CHAIN_ID ?? "31337", 10); module.exports = { - paths: { - artifacts: "./artifacts", - sources: "./contracts", - tests: "./tests", - }, - solidity: { - compilers: [ - { - version: "0.8.1", - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, - { - version: "0.8.17", - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, - ], - }, - defaultNetwork: "hardhat", - networks: { - hardhat: { - chainId, - loggingEnabled: false, - saveDeployments: false, - }, - localhost: { - chainId, - url: urlOverride || "http://localhost:8545", - }, - }, + paths: { + artifacts: "./artifacts", + sources: "./contracts", + tests: "./tests", + }, + solidity: { + compilers: [ + { + version: "0.8.1", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + { + version: "0.8.17", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + { + version: "0.8.19", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + ], + }, + defaultNetwork: "hardhat", + networks: { + hardhat: { + chainId, + loggingEnabled: false, + saveDeployments: false, + }, + localhost: { + chainId, + url: urlOverride || "http://localhost:8545", + }, + }, }; From 8c5da0a8207de6201c4af7939c865f5fcd559292 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Wed, 10 May 2023 13:26:33 +0200 Subject: [PATCH 06/32] submodule mistake --- .gitmodules | 6 ++++++ contracts/Math/PRBMath/prbmath-libraries-solidity | 1 + prb-math | 1 + 3 files changed, 8 insertions(+) create mode 160000 contracts/Math/PRBMath/prbmath-libraries-solidity create mode 160000 prb-math diff --git a/.gitmodules b/.gitmodules index ac2a919..c17a5e4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -18,3 +18,9 @@ path = lib/openzeppelin-contracts url = https://github.com/openzeppelin/openzeppelin-contracts branch = v4.8.0 +[submodule "contracts/Math/PRBMath/prbmath-libraries-solidity"] + path = contracts/Math/PRBMath/prbmath-libraries-solidity + url = https://github.com/PaulRBerg/prb-math +[submodule "prb-math"] + path = prb-math + url = https://github.com/PaulRBerg/prb-math diff --git a/contracts/Math/PRBMath/prbmath-libraries-solidity b/contracts/Math/PRBMath/prbmath-libraries-solidity new file mode 160000 index 0000000..9d82323 --- /dev/null +++ b/contracts/Math/PRBMath/prbmath-libraries-solidity @@ -0,0 +1 @@ +Subproject commit 9d82323facf7aa2f4230eebc57347e6f6024e1f1 diff --git a/prb-math b/prb-math new file mode 160000 index 0000000..9d82323 --- /dev/null +++ b/prb-math @@ -0,0 +1 @@ +Subproject commit 9d82323facf7aa2f4230eebc57347e6f6024e1f1 From 0c6000ec8930e5b1e106c4dbbd572cf46d7e52b8 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Wed, 10 May 2023 13:27:43 +0200 Subject: [PATCH 07/32] removed submodules --- .gitmodules | 6 ------ contracts/Math/PRBMath/prbmath-libraries-solidity | 1 - prb-math | 1 - 3 files changed, 8 deletions(-) delete mode 160000 contracts/Math/PRBMath/prbmath-libraries-solidity delete mode 160000 prb-math diff --git a/.gitmodules b/.gitmodules index c17a5e4..ac2a919 100644 --- a/.gitmodules +++ b/.gitmodules @@ -18,9 +18,3 @@ path = lib/openzeppelin-contracts url = https://github.com/openzeppelin/openzeppelin-contracts branch = v4.8.0 -[submodule "contracts/Math/PRBMath/prbmath-libraries-solidity"] - path = contracts/Math/PRBMath/prbmath-libraries-solidity - url = https://github.com/PaulRBerg/prb-math -[submodule "prb-math"] - path = prb-math - url = https://github.com/PaulRBerg/prb-math diff --git a/contracts/Math/PRBMath/prbmath-libraries-solidity b/contracts/Math/PRBMath/prbmath-libraries-solidity deleted file mode 160000 index 9d82323..0000000 --- a/contracts/Math/PRBMath/prbmath-libraries-solidity +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9d82323facf7aa2f4230eebc57347e6f6024e1f1 diff --git a/prb-math b/prb-math deleted file mode 160000 index 9d82323..0000000 --- a/prb-math +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9d82323facf7aa2f4230eebc57347e6f6024e1f1 From efcf1ff785650b3bc4b45a3517caf169ea79a9a5 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Wed, 10 May 2023 13:33:54 +0200 Subject: [PATCH 08/32] install forge prb dependency --- .gitmodules | 4 ++++ lib/prb-math | 1 + remappings.txt | 9 +++++++++ 3 files changed, 14 insertions(+) create mode 160000 lib/prb-math create mode 100644 remappings.txt diff --git a/.gitmodules b/.gitmodules index ac2a919..3ec3a4e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -18,3 +18,7 @@ path = lib/openzeppelin-contracts url = https://github.com/openzeppelin/openzeppelin-contracts branch = v4.8.0 +[submodule "lib/prb-math"] + path = lib/prb-math + url = https://github.com/PaulRBerg/prb-math + branch = v4 diff --git a/lib/prb-math b/lib/prb-math new file mode 160000 index 0000000..7ce3009 --- /dev/null +++ b/lib/prb-math @@ -0,0 +1 @@ +Subproject commit 7ce3009bbfa0d8e2d430b7a1a9ca46b6e706d90d diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..42051ce --- /dev/null +++ b/remappings.txt @@ -0,0 +1,9 @@ +@prb/test/=lib/prb-math/lib/prb-test/src/ +ERC4626/=lib/ERC4626/contracts/ +ds-test/=lib/forge-std/lib/ds-test/src/ +erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ +forge-std/=lib/forge-std/src/ +@openzeppelin/=lib/openzeppelin-contracts/ +@prb/math/=lib/prb-math/ +prb-test/=lib/prb-math/lib/prb-test/src/ +solmate/=lib/solmate/ From e7346627667d5adb05755b5877c8b61e3c9abb30 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Wed, 10 May 2023 14:49:32 +0200 Subject: [PATCH 09/32] removed aliasing --- .../PRBMath/PRBMathSD59x18PropertyTests.sol | 224 +++++++++--------- 1 file changed, 112 insertions(+), 112 deletions(-) diff --git a/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol index 0e9b95b..8e41edd 100644 --- a/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol @@ -1,11 +1,11 @@ pragma solidity ^0.8.19; import { SD59x18 } from "@prb/math/src/SD59x18.sol"; -import {add as helpersAdd, sub as helpersSub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb/math/src/sd59x18/Helpers.sol"; +import {add, sub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb/math/src/sd59x18/Helpers.sol"; import {convert} from "@prb/math/src/sd59x18/Conversions.sol"; import {msb} from "@prb/math/src/Common.sol"; import {intoUint128, intoUint256} from "@prb/math/src/sd59x18/Casting.sol"; -import {mul as helpersMul, div as helpersDiv, abs as helpersAbs, ln as helpersLn, exp as helpersExp, exp2 as helpersExp2, log2 as helpersLog2, sqrt as helpersSqrt, pow as helpersPow, avg as helpersAvg, inv as helpersInv, log10 as helpersLog10, floor as helpersFloor} from "@prb/math/src/sd59x18/Math.sol"; +import {mul, div, abs, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor} from "@prb/math/src/sd59x18/Math.sol"; contract CryticPRBMath59x18Properties { @@ -99,8 +99,8 @@ contract CryticPRBMath59x18Properties { // Check that there are remaining significant digits after a multiplication // Uses functions from the library under test! function significant_digits_lost_in_mult(SD59x18 a, SD59x18 b) public pure returns (bool) { - int256 la = convert(floor(log_10(abs(a)))); - int256 lb = convert(floor(log_10(abs(b)))); + int256 la = convert(floor(log10(abs(a)))); + int256 lb = convert(floor(log10(abs(b)))); return(la + lb < -18); } @@ -108,8 +108,8 @@ contract CryticPRBMath59x18Properties { // Return how many significant bits will remain after multiplying a and b // Uses functions from the library under test! function significant_digits_after_mult(SD59x18 a, SD59x18 b) public pure returns (uint256) { - int256 la = convert(floor(log_10(abs(a)))); - int256 lb = convert(floor(log_10(abs(b)))); + int256 la = convert(floor(log10(abs(a)))); + int256 lb = convert(floor(log10(abs(b)))); int256 prec = la + lb; if (prec < -18) return 0; @@ -121,8 +121,8 @@ contract CryticPRBMath59x18Properties { function equal_most_significant_digits_within_precision(SD59x18 a, SD59x18 b, uint256 bits) public view returns (bool) { // Get the number of bits in a and b // Since log(x) returns in the interval [-64, 63), add 64 to be in the interval [0, 127) - uint256 a_bits = uint256(int256(convert(log_2(a)) + 64)); - uint256 b_bits = uint256(int256(convert(log_2(b)) + 64)); + uint256 a_bits = uint256(int256(convert(log2(a)) + 64)); + uint256 b_bits = uint256(int256(convert(log2(b)) + 64)); // a and b lengths may differ in 1 bit, so the shift should take into account the longest uint256 shift_bits = (a_bits > b_bits) ? (a_bits - bits) : (b_bits - bits); @@ -170,70 +170,70 @@ contract CryticPRBMath59x18Properties { } // Wrapper for external try/catch calls - function add(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return helpersAdd(x,y); + function helpersAdd(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return add(x,y); } // Wrapper for external try/catch calls - function sub(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return helpersSub(x,y); + function helpersSub(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return sub(x,y); } // Wrapper for external try/catch calls - function mul(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return helpersMul(x,y); + function helpersMul(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return mul(x,y); } - function div(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return helpersDiv(x,y); + function helpersDiv(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return div(x,y); } function neg(SD59x18 x) public pure returns (SD59x18) { return SD59x18.wrap(-SD59x18.unwrap(x)); } - function abs(SD59x18 x) public pure returns (SD59x18) { - return helpersAbs(x); + function helpersAbs(SD59x18 x) public pure returns (SD59x18) { + return abs(x); } - function ln(SD59x18 x) public pure returns (SD59x18) { - return helpersAbs(x); + function helpersLn(SD59x18 x) public pure returns (SD59x18) { + return ln(x); } - function exp(SD59x18 x) public pure returns (SD59x18) { - return helpersExp(x); + function helpersExp(SD59x18 x) public pure returns (SD59x18) { + return exp(x); } - function exp_2(SD59x18 x) public pure returns (SD59x18) { - return helpersExp2(x); + function helpersExp2(SD59x18 x) public pure returns (SD59x18) { + return exp2(x); } - function log_2(SD59x18 x) public pure returns (SD59x18) { - return helpersLog2(x); + function helpersLog2(SD59x18 x) public pure returns (SD59x18) { + return log2(x); } - function sqrt(SD59x18 x) public pure returns (SD59x18) { - return helpersSqrt(x); + function helpersSqrt(SD59x18 x) public pure returns (SD59x18) { + return sqrt(x); } - function pow(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return helpersPow(x, y); + function helpersPow(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return pow(x, y); } - function avg(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return helpersAvg(x, y); + function helpersAvg(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return avg(x, y); } - function inv(SD59x18 x) public pure returns (SD59x18) { - return helpersInv(x); + function helpersInv(SD59x18 x) public pure returns (SD59x18) { + return inv(x); } - function log_10(SD59x18 x) public pure returns (SD59x18) { - return helpersLog10(x); + function helpersLog10(SD59x18 x) public pure returns (SD59x18) { + return log10(x); } - function floor(SD59x18 x) public pure returns (SD59x18) { - return helpersFloor(x); + function helpersFloor(SD59x18 x) public pure returns (SD59x18) { + return floor(x); } /* ================================================================ @@ -299,7 +299,7 @@ contract CryticPRBMath59x18Properties { // The result of the addition must be between the maximum // and minimum allowed values for SD59x18 function add_test_range(SD59x18 x, SD59x18 y) public view { - try this.add(x, y) returns (SD59x18 result) { + try this.helpersAdd(x, y) returns (SD59x18 result) { assert(result.lte(MAX_SD59x18) && result.gte(MIN_SD59x18)); } catch { // If it reverts, just ignore @@ -309,7 +309,7 @@ contract CryticPRBMath59x18Properties { // Adding zero to the maximum value shouldn't revert, as it is valid // Moreover, the result must be MAX_SD59x18 function add_test_maximum_value() public view { - try this.add(MAX_SD59x18, ZERO_FP) returns (SD59x18 result) { + try this.helpersAdd(MAX_SD59x18, ZERO_FP) returns (SD59x18 result) { // Expected behaviour, does not revert assert(result.eq(MAX_SD59x18)); } catch { @@ -319,7 +319,7 @@ contract CryticPRBMath59x18Properties { // Adding one to the maximum value should revert, as it is out of range function add_test_maximum_value_plus_one() public view { - try this.add(MAX_SD59x18, ONE_FP) { + try this.helpersAdd(MAX_SD59x18, ONE_FP) { assert(false); } catch { // Expected behaviour, reverts @@ -329,7 +329,7 @@ contract CryticPRBMath59x18Properties { // Adding zero to the minimum value shouldn't revert, as it is valid // Moreover, the result must be MIN_SD59x18 function add_test_minimum_value() public view { - try this.add(MIN_SD59x18, ZERO_FP) returns (SD59x18 result) { + try this.helpersAdd(MIN_SD59x18, ZERO_FP) returns (SD59x18 result) { // Expected behaviour, does not revert assert(result.eq(MIN_SD59x18)); } catch { @@ -339,7 +339,7 @@ contract CryticPRBMath59x18Properties { // Adding minus one to the maximum value should revert, as it is out of range function add_test_minimum_value_plus_negative_one() public view { - try this.add(MIN_SD59x18, MINUS_ONE_FP) { + try this.helpersAdd(MIN_SD59x18, MINUS_ONE_FP) { assert(false); } catch { // Expected behaviour, reverts @@ -423,7 +423,7 @@ contract CryticPRBMath59x18Properties { // The result of the subtraction must be between the maximum // and minimum allowed values for SD59x18 function sub_test_range(SD59x18 x, SD59x18 y) public view { - try this.sub(x, y) returns (SD59x18 result) { + try this.helpersSub(x, y) returns (SD59x18 result) { assert(result.lte(MAX_SD59x18) && result.gte(MIN_SD59x18)); } catch { // If it reverts, just ignore @@ -433,7 +433,7 @@ contract CryticPRBMath59x18Properties { // Subtracting zero from the maximum value shouldn't revert, as it is valid // Moreover, the result must be MAX_SD59x18 function sub_test_maximum_value() public view { - try this.sub(MAX_SD59x18, ZERO_FP) returns (SD59x18 result) { + try this.helpersSub(MAX_SD59x18, ZERO_FP) returns (SD59x18 result) { // Expected behaviour, does not revert assert(result.eq(MAX_SD59x18)); } catch { @@ -444,7 +444,7 @@ contract CryticPRBMath59x18Properties { // Subtracting minus one from the maximum value should revert, // as it is out of range function sub_test_maximum_value_minus_neg_one() public view { - try this.sub(MAX_SD59x18, MINUS_ONE_FP) { + try this.helpersSub(MAX_SD59x18, MINUS_ONE_FP) { assert(false); } catch { // Expected behaviour, reverts @@ -454,7 +454,7 @@ contract CryticPRBMath59x18Properties { // Subtracting zero from the minimum value shouldn't revert, as it is valid // Moreover, the result must be MIN_SD59x18 function sub_test_minimum_value() public view { - try this.sub(MIN_SD59x18, ZERO_FP) returns (SD59x18 result) { + try this.helpersSub(MIN_SD59x18, ZERO_FP) returns (SD59x18 result) { // Expected behaviour, does not revert assert(result.eq(MIN_SD59x18)); } catch { @@ -464,7 +464,7 @@ contract CryticPRBMath59x18Properties { // Subtracting one from the minimum value should revert, as it is out of range function sub_test_minimum_value_minus_one() public view { - try this.sub(MIN_SD59x18, ONE_FP) { + try this.helpersSub(MIN_SD59x18, ONE_FP) { assert(false); } catch { // Expected behaviour, reverts @@ -576,7 +576,7 @@ contract CryticPRBMath59x18Properties { // The result of the multiplication must be between the maximum // and minimum allowed values for SD59x18 function mul_test_range(SD59x18 x, SD59x18 y) public view { - try this.mul(x, y) returns(SD59x18 result) { + try this.helpersMul(x, y) returns(SD59x18 result) { assert(result.lte(MAX_SD59x18) && result.gte(MIN_SD59x18)); } catch { // If it reverts, just ignore @@ -586,7 +586,7 @@ contract CryticPRBMath59x18Properties { // Multiplying the maximum value times one shouldn't revert, as it is valid // Moreover, the result must be MAX_SD59x18 function mul_test_maximum_value() public view { - try this.mul(MAX_SD59x18, ONE_FP) returns (SD59x18 result) { + try this.helpersMul(MAX_SD59x18, ONE_FP) returns (SD59x18 result) { // Expected behaviour, does not revert assert(result.eq(MAX_SD59x18)); } catch { @@ -597,7 +597,7 @@ contract CryticPRBMath59x18Properties { // Multiplying the minimum value times one shouldn't revert, as it is valid // Moreover, the result must be MIN_SD59x18 function mul_test_minimum_value() public view { - try this.mul(MIN_SD59x18.add(ONE_FP), ONE_FP) returns (SD59x18 result) { + try this.helpersMul(MIN_SD59x18.add(ONE_FP), ONE_FP) returns (SD59x18 result) { // Expected behaviour, does not revert assert(result.eq(MIN_SD59x18)); } catch { @@ -633,7 +633,7 @@ contract CryticPRBMath59x18Properties { SD59x18 div_x; - try this.div(x, x) { + try this.helpersDiv(x, x) { // This should always equal one div_x = div(x, x); assert(div_x.eq(ONE_FP)); @@ -687,7 +687,7 @@ contract CryticPRBMath59x18Properties { // Test for division by zero function div_test_div_by_zero(SD59x18 x) public view { - try this.div(x, ZERO_FP) { + try this.helpersDiv(x, ZERO_FP) { // Unexpected, this should revert assert(false); } catch { @@ -707,7 +707,7 @@ contract CryticPRBMath59x18Properties { function div_test_maximum_numerator(SD59x18 x) public view { SD59x18 div_large; - try this.div(MAX_SD59x18, x) { + try this.helpersDiv(MAX_SD59x18, x) { // If it didn't revert, then |x| >= 1 div_large = div(MAX_SD59x18, x); @@ -721,7 +721,7 @@ contract CryticPRBMath59x18Properties { function div_test_range(SD59x18 x, SD59x18 y) public view { SD59x18 result; - try this.div(x, y) { + try this.helpersDiv(x, y) { // If it returns a value, it must be in range result = div(x, y); assert(result.lte(MAX_SD59x18) && result.gte(MIN_SD59x18)); @@ -860,9 +860,9 @@ contract CryticPRBMath59x18Properties { function abs_test_zero() public view { SD59x18 abs_zero; - try this.abs(ZERO_FP) { + try this.helpersAbs(ZERO_FP) { // If it doesn't revert, the value must be zero - abs_zero = this.abs(ZERO_FP); + abs_zero = this.helpersAbs(ZERO_FP); assert(abs_zero.eq(ZERO_FP)); } catch { // Unexpected, the function must not revert here @@ -874,9 +874,9 @@ contract CryticPRBMath59x18Properties { function abs_test_maximum() public view { SD59x18 abs_max; - try this.abs(MAX_SD59x18) { + try this.helpersAbs(MAX_SD59x18) { // If it doesn't revert, the value must be MAX_SD59x18 - abs_max = this.abs(MAX_SD59x18); + abs_max = this.helpersAbs(MAX_SD59x18); assert(abs_max.eq(MAX_SD59x18)); } catch {} } @@ -885,9 +885,9 @@ contract CryticPRBMath59x18Properties { function abs_test_minimum() public view { SD59x18 abs_min; - try this.abs(MIN_SD59x18) { + try this.helpersAbs(MIN_SD59x18) { // If it doesn't revert, the value must be the negative of MIN_SD59x18 - abs_min = this.abs(MIN_SD59x18); + abs_min = this.helpersAbs(MIN_SD59x18); assert(abs_min == neg(MIN_SD59x18)); } catch {} } @@ -914,7 +914,7 @@ contract CryticPRBMath59x18Properties { SD59x18 double_inv_x = inv(inv(x)); // The maximum loss of precision will be 2 * log2(x) bits rounded up - uint256 loss = 2 * intoUint256(log_2(x)) + 2; + uint256 loss = 2 * intoUint256(log2(x)) + 2; assert(equal_within_precision(x, double_inv_x, loss)); } @@ -967,8 +967,8 @@ contract CryticPRBMath59x18Properties { ); // The maximum loss of precision is given by the formula: - // 2 * | log_2(x) - log_2(y) | + 1 - uint256 loss = 2 * intoUint256(abs(log_2(x) - log_2(y))) + 1; + // 2 * | log2(x) - log2(y) | + 1 + uint256 loss = 2 * intoUint256(abs(log2(x) - log2(y))) + 1; assert(equal_within_precision(inv_x_y, inv_x_times_inv_y, loss)); } @@ -1025,7 +1025,7 @@ contract CryticPRBMath59x18Properties { // Test the zero-case, should revert function inv_test_zero() public view { - try this.inv(ZERO_FP) { + try this.helpersInv(ZERO_FP) { // Unexpected, the function must revert assert(false); } catch {} @@ -1035,8 +1035,8 @@ contract CryticPRBMath59x18Properties { function inv_test_maximum() public view { SD59x18 inv_maximum; - try this.inv(MAX_SD59x18) { - inv_maximum = this.inv(MAX_SD59x18); + try this.helpersInv(MAX_SD59x18) { + inv_maximum = this.helpersInv(MAX_SD59x18); assert(equal_within_precision(inv_maximum, ZERO_FP, 10)); } catch { // Unexpected, the function must not revert @@ -1048,8 +1048,8 @@ contract CryticPRBMath59x18Properties { function inv_test_minimum() public view { SD59x18 inv_minimum; - try this.inv(MIN_SD59x18) { - inv_minimum = this.inv(MIN_SD59x18); + try this.helpersInv(MIN_SD59x18) { + inv_minimum = this.helpersInv(MIN_SD59x18); assert(equal_within_precision(abs(inv_minimum), ZERO_FP, 10)); } catch { // Unexpected, the function must not revert @@ -1111,8 +1111,8 @@ contract CryticPRBMath59x18Properties { // This may revert due to overflow depending on implementation // If it doesn't revert, the result must be MAX_SD59x18 - try this.avg(MAX_SD59x18, MAX_SD59x18) { - result = this.avg(MAX_SD59x18, MAX_SD59x18); + try this.helpersAvg(MAX_SD59x18, MAX_SD59x18) { + result = this.helpersAvg(MAX_SD59x18, MAX_SD59x18); assert(result.eq(MAX_SD59x18)); } catch {} } @@ -1123,8 +1123,8 @@ contract CryticPRBMath59x18Properties { // This may revert due to overflow depending on implementation // If it doesn't revert, the result must be MIN_SD59x18 - try this.avg(MIN_SD59x18, MIN_SD59x18) { - result = this.avg(MIN_SD59x18, MIN_SD59x18); + try this.helpersAvg(MIN_SD59x18, MIN_SD59x18) { + result = this.helpersAvg(MIN_SD59x18, MIN_SD59x18); assert(result.eq(MIN_SD59x18)); } catch {} } @@ -1290,7 +1290,7 @@ contract CryticPRBMath59x18Properties { function pow_test_maximum_base(SD59x18 a) public view { require(a.gt(ONE_FP)); - try this.pow(MAX_SD59x18, a) { + try this.helpersPow(MAX_SD59x18, a) { // Unexpected, should revert because of overflow assert(false); } catch { @@ -1340,7 +1340,7 @@ contract CryticPRBMath59x18Properties { equal_within_precision( sqrt_x_squared, x, - (intoUint256(log_2(x)) >> 1) + 2 + (intoUint256(log2(x)) >> 1) + 2 ) ); } @@ -1358,7 +1358,7 @@ contract CryticPRBMath59x18Properties { equal_within_precision( sqrt_x_squared, x, - (intoUint256(log_2(x)) >> 1) + 2 + (intoUint256(log2(x)) >> 1) + 2 ) ); } @@ -1397,7 +1397,7 @@ contract CryticPRBMath59x18Properties { // Test for maximum value function sqrt_test_maximum() public view { - try this.sqrt(MAX_SD59x18) { + try this.helpersSqrt(MAX_SD59x18) { // Expected behaviour, MAX_SD59x18 is positive, and operation // should not revert as the result is in range } catch { @@ -1408,7 +1408,7 @@ contract CryticPRBMath59x18Properties { // Test for minimum value function sqrt_test_minimum() public view { - try this.sqrt(MIN_SD59x18) { + try this.helpersSqrt(MIN_SD59x18) { // Unexpected, should revert. MIN_SD59x18 is negative. assert(false); } catch { @@ -1420,7 +1420,7 @@ contract CryticPRBMath59x18Properties { function sqrt_test_negative(SD59x18 x) public view { require(x.lt(ZERO_FP)); - try this.sqrt(x) { + try this.helpersSqrt(x) { // Unexpected, should revert assert(false); } catch { @@ -1446,19 +1446,19 @@ contract CryticPRBMath59x18Properties { // Test for distributive property respect to multiplication // log2(x * y) = log2(x) + log2(y) function log2_test_distributive_mul(SD59x18 x, SD59x18 y) public view { - SD59x18 log2_x = log_2(x); - SD59x18 log2_y = log_2(y); + SD59x18 log2_x = log2(x); + SD59x18 log2_y = log2(y); SD59x18 log2_x_log2_y = add(log2_x, log2_y); SD59x18 xy = mul(x, y); - SD59x18 log2_xy = log_2(xy); + SD59x18 log2_xy = log2(xy); // Ensure we have enough significant digits for the result to be meaningful require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); // The maximum loss of precision is given by the formula: - // | log_2(x) + log_2(y) | - uint256 loss = intoUint256(abs(log_2(x) + log_2(y))); + // | log2(x) + log2(y) | + uint256 loss = intoUint256(abs(log2(x) + log2(y))); assert(equal_within_precision(log2_x_log2_y, log2_xy, loss)); } @@ -1467,8 +1467,8 @@ contract CryticPRBMath59x18Properties { // log2(x ** y) = y * log2(x) function log2_test_power(SD59x18 x, SD59x18 y) public pure { SD59x18 x_y = pow(x, y); - SD59x18 log2_x_y = log_2(x_y); - SD59x18 y_log2_x = mul(log_2(x), y); + SD59x18 log2_x_y = log2(x_y); + SD59x18 y_log2_x = mul(log2(x), y); assert(y_log2_x.eq(log2_x_y)); } @@ -1481,7 +1481,7 @@ contract CryticPRBMath59x18Properties { // Test for zero case, should revert function log2_test_zero() public view { - try this.log_2(ZERO_FP) { + try this.helpersLog2(ZERO_FP) { // Unexpected, should revert assert(false); } catch { @@ -1493,9 +1493,9 @@ contract CryticPRBMath59x18Properties { function log2_test_maximum() public view { SD59x18 result; - try this.log_2(MAX_SD59x18) { + try this.helpersLog2(MAX_SD59x18) { // Expected, should not revert and the result must be > 0 - result = this.log_2(MAX_SD59x18); + result = this.helpersLog2(MAX_SD59x18); assert(result.gt(ZERO_FP)); } catch { // Unexpected @@ -1507,7 +1507,7 @@ contract CryticPRBMath59x18Properties { function log2_test_negative(SD59x18 x) public view { require(x.lt(ZERO_FP)); - try this.log_2(x) { + try this.helpersLog2(x) { // Unexpected, should revert assert(false); } catch { @@ -1545,8 +1545,8 @@ contract CryticPRBMath59x18Properties { require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); // The maximum loss of precision is given by the formula: - // | log_2(x) + log_2(y) | - uint256 loss = intoUint256(abs(log_2(x) + log_2(y))); + // | log2(x) + log2(y) | + uint256 loss = intoUint256(abs(log2(x) + log2(y))); assert(equal_within_precision(ln_x_ln_y, ln_xy, loss)); } @@ -1570,7 +1570,7 @@ contract CryticPRBMath59x18Properties { // Test for zero case, should revert function ln_test_zero() public view { - try this.ln(ZERO_FP) { + try this.helpersLn(ZERO_FP) { // Unexpected, should revert assert(false); } catch { @@ -1582,9 +1582,9 @@ contract CryticPRBMath59x18Properties { function ln_test_maximum() public view { SD59x18 result; - try this.ln(MAX_SD59x18) { + try this.helpersLn(MAX_SD59x18) { // Expected, should not revert and the result must be > 0 - result = this.ln(MAX_SD59x18); + result = this.helpersLn(MAX_SD59x18); assert(result.gt(ZERO_FP)); } catch { // Unexpected @@ -1596,7 +1596,7 @@ contract CryticPRBMath59x18Properties { function ln_test_negative(SD59x18 x) public view { require(x.lt(ZERO_FP)); - try this.ln(x) { + try this.helpersLn(x) { // Unexpected, should revert assert(false); } catch { @@ -1624,19 +1624,19 @@ contract CryticPRBMath59x18Properties { /// - The result must fit in SD59x18. // Test for equality with pow(2, x) for integer x - // pow(2, x) == exp_2(x) + // pow(2, x) == exp2(x) function exp2_test_equivalence_pow(SD59x18 x) public view { - SD59x18 exp2_x = exp_2(x); + SD59x18 exp2_x = exp2(x); SD59x18 pow_2_x = pow(TWO_FP, x); assert(exp2_x.eq(pow_2_x)); } // Test for inverse function - // If y = log_2(x) then exp_2(y) == x + // If y = log2(x) then exp2(y) == x function exp2_test_inverse(SD59x18 x) public view { - SD59x18 log2_x = log_2(x); - SD59x18 exp2_x = exp_2(log2_x); + SD59x18 log2_x = log2(x); + SD59x18 exp2_x = exp2(log2_x); // todo is this the correct number of bits? uint256 bits = 50; @@ -1649,12 +1649,12 @@ contract CryticPRBMath59x18Properties { } // Test for negative exponent - // exp_2(-x) == inv( exp_2(x) ) + // exp2(-x) == inv( exp2(x) ) function exp2_test_negative_exponent(SD59x18 x) public view { require(x.lt(ZERO_FP) && x.neq(MIN_SD59x18)); - SD59x18 exp2_x = exp_2(x); - SD59x18 exp2_minus_x = exp_2(neg(x)); + SD59x18 exp2_x = exp2(x); + SD59x18 exp2_minus_x = exp2(neg(x)); // Result should be within 4 bits precision for the worst case assert(equal_within_precision(exp2_x, inv(exp2_minus_x), 4)); @@ -1667,16 +1667,16 @@ contract CryticPRBMath59x18Properties { ================================================================ */ // Test for zero case - // exp_2(0) == 1 + // exp2(0) == 1 function exp2_test_zero() public view { - SD59x18 exp_zero = exp_2(ZERO_FP); + SD59x18 exp_zero = exp2(ZERO_FP); assert(exp_zero.eq(ONE_FP)); } // Test for maximum value. This should overflow as it won't fit // in the data type function exp2_test_maximum() public view { - try this.exp_2(MAX_SD59x18) { + try this.helpersExp2(MAX_SD59x18) { // Unexpected, should revert assert(false); } catch { @@ -1689,9 +1689,9 @@ contract CryticPRBMath59x18Properties { function exp2_test_minimum() public view { SD59x18 result; - try this.exp_2(MIN_SD59x18) { + try this.helpersExp2(MIN_SD59x18) { // Expected, should not revert, check that value is zero - result = exp_2(MIN_SD59x18); + result = exp2(MIN_SD59x18); assert(result.eq(ZERO_FP)); } catch { // Unexpected revert @@ -1720,7 +1720,7 @@ contract CryticPRBMath59x18Properties { function exp_test_inverse(SD59x18 x) public view { SD59x18 ln_x = ln(x); SD59x18 exp_x = exp(ln_x); - SD59x18 log2_x = log_2(x); + SD59x18 log2_x = log2(x); uint256 bits = 48; @@ -1759,7 +1759,7 @@ contract CryticPRBMath59x18Properties { // Test for maximum value. This should overflow as it won't fit // in the data type function exp_test_maximum() public view { - try this.exp(MAX_SD59x18) { + try this.helpersExp(MAX_SD59x18) { // Unexpected, should revert assert(false); } catch { @@ -1772,7 +1772,7 @@ contract CryticPRBMath59x18Properties { function exp_test_minimum() public view { SD59x18 result; - try this.exp(MIN_SD59x18) { + try this.helpersExp(MIN_SD59x18) { // Expected, should not revert, check that value is zero result = exp(MIN_SD59x18); assert(result.eq(ZERO_FP)); From 1a480c919d5fe2aaa514292d5a0434e0b5580ff8 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Thu, 11 May 2023 10:19:21 +0200 Subject: [PATCH 10/32] precision modifications --- .gitmodules | 2 +- contracts/Math/PRBMath/echidna-config.yaml | 2 +- .../{ => v3}/PRBMathSD59x18PropertyTests.sol | 129 ++++++++++++++---- lib/prb-math | 2 +- 4 files changed, 109 insertions(+), 26 deletions(-) rename contracts/Math/PRBMath/{ => v3}/PRBMathSD59x18PropertyTests.sol (93%) diff --git a/.gitmodules b/.gitmodules index 3ec3a4e..4b5049e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -21,4 +21,4 @@ [submodule "lib/prb-math"] path = lib/prb-math url = https://github.com/PaulRBerg/prb-math - branch = v4 + branch = v3 diff --git a/contracts/Math/PRBMath/echidna-config.yaml b/contracts/Math/PRBMath/echidna-config.yaml index 0b938e7..31f2af4 100644 --- a/contracts/Math/PRBMath/echidna-config.yaml +++ b/contracts/Math/PRBMath/echidna-config.yaml @@ -3,4 +3,4 @@ testMode: assertion testLimit: 100000 deployer: "0x10000" sender: ["0x10000", "0x20000", "0x30000"] -cryticArgs: ["--compile-force-framework", "hardhat"] +cryticArgs: ["--compile-force-framework", "foundry"] diff --git a/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol similarity index 93% rename from contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol rename to contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol index 8e41edd..8867dce 100644 --- a/contracts/Math/PRBMath/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol @@ -7,7 +7,10 @@ import {msb} from "@prb/math/src/Common.sol"; import {intoUint128, intoUint256} from "@prb/math/src/sd59x18/Casting.sol"; import {mul, div, abs, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor} from "@prb/math/src/sd59x18/Math.sol"; -contract CryticPRBMath59x18Properties { +contract CryticPRBMath59x18Propertiesv3 { + + event AssertionFailed(SD59x18 result); + event AssertionFailed(SD59x18 result1, SD59x18 result2); /* ================================================================ 59x18 fixed-point constants used for testing specific values. @@ -57,6 +60,21 @@ contract CryticPRBMath59x18Properties { /// @dev Euler's number as an SD59x18 number. SD59x18 constant E = SD59x18.wrap(2_718281828459045235); + int256 constant uMAX_SQRT = uMAX_SD59x18 / uUNIT; + SD59x18 constant MAX_SQRT = SD59x18.wrap(uMAX_SQRT); + + /// @dev Half the UNIT number. + int256 constant uHALF_UNIT = 0.5e18; + SD59x18 constant HALF_UNIT = SD59x18.wrap(uHALF_UNIT); + + /// @dev log2(10) as an SD59x18 number. + int256 constant uLOG2_10 = 3_321928094887362347; + SD59x18 constant LOG2_10 = SD59x18.wrap(uLOG2_10); + + /// @dev log2(e) as an SD59x18 number. + int256 constant uLOG2_E = 1_442695040888963407; + SD59x18 constant LOG2_E = SD59x18.wrap(uLOG2_E); + /* ================================================================ Events used for debugging or showing information. ================================================================ */ @@ -161,6 +179,57 @@ contract CryticPRBMath59x18Properties { return (value & mask); } + /* function compute_max_log_error(SD59x18 x) public view returns (SD59x18 result) { + int256 xInt = SD59x18.unwrap(x); + + unchecked { + // This works because of: + // + // $$ + // log_2{x} = -log_2{\frac{1}{x}} + // $$ + int256 sign; + if (xInt >= uUNIT) { + sign = 1; + } else { + sign = -1; + // Do the fixed-point inversion inline to save gas. The numerator is UNIT * UNIT. + xInt = 1e36 / xInt; + } + + // Calculate the integer part of the logarithm and add it to the result and finally calculate $y = x * 2^(-n)$. + uint256 n = msb(uint256(xInt / uUNIT)); + + // This is $y = x * 2^{-n}$. + int256 y = xInt >> n; + + // If y is 1, the fractional part is zero. + if (y == uUNIT) { + return 0; + } + + // Calculate the fractional part via the iterative approximation. + // The "delta >>= 1" part is equivalent to "delta /= 2", but shifting bits is faster. + int256 DOUBLE_UNIT = 2e18; + int256 sum; + for (int256 delta = uHALF_UNIT; delta > 0; delta >>= 1) { + y = (y * y) / uUNIT; + + // Is $y^2 > 2$ and so in the range [2,4)? + if (y >= DOUBLE_UNIT) { + // Add the 2^{-m} factor to the logarithm. + sum += delta; + + // Corresponds to z/2 on Wikipedia. + y >>= 1; + } + } + + int256 maxError = 2 ** (-sum); + result = convert(maxError); + } + } */ + /* ================================================================ Library wrappers. These functions allow calling the PRBMathSD59x18 library. @@ -888,7 +957,7 @@ contract CryticPRBMath59x18Properties { try this.helpersAbs(MIN_SD59x18) { // If it doesn't revert, the value must be the negative of MIN_SD59x18 abs_min = this.helpersAbs(MIN_SD59x18); - assert(abs_min == neg(MIN_SD59x18)); + assert(abs_min.eq(neg(MIN_SD59x18))); } catch {} } @@ -968,7 +1037,7 @@ contract CryticPRBMath59x18Properties { // The maximum loss of precision is given by the formula: // 2 * | log2(x) - log2(y) | + 1 - uint256 loss = 2 * intoUint256(abs(log2(x) - log2(y))) + 1; + uint256 loss = 2 * intoUint256(abs(log2(x).sub(log2(y)))) + 1; assert(equal_within_precision(inv_x_y, inv_x_times_inv_y, loss)); } @@ -1004,16 +1073,17 @@ contract CryticPRBMath59x18Properties { } } - // Test that the result has the same sign as the argument - function inv_test_sign(SD59x18 x) public view { + // Test that the result has the same sign as the argument. + // Since inv() rounds towards zero, we are checking the zero case as well + function inv_test_sign(SD59x18 x) public { require(x.neq(ZERO_FP)); SD59x18 inv_x = inv(x); if (x.gt(ZERO_FP)) { - assert(inv_x.gt(ZERO_FP)); + assert(inv_x.gte(ZERO_FP)); } else { - assert(inv_x.lt(ZERO_FP)); + assert(inv_x.lte(ZERO_FP)); } } @@ -1141,17 +1211,26 @@ contract CryticPRBMath59x18Properties { with math rules and expected behaviour. ================================================================ */ - /// @dev Notes: - /// - Refer to the notes in {exp2}, {log2}, and {mul}. - /// - If x is less than -59_794705707972522261, the result is zero. - /// - Due to the lossy precision of the iterative approximation, the results are not perfectly accurate to the last decimal. - /// - Returns `UNIT` for 0^0. - /// - /// Requirements: - /// - None of the inputs can be `MIN_SD59x18`. - /// - x must be less than 192e18. - /// - x must be greater than zero. - /// - The result must fit in SD59x18. +/* pow.t.sol +├── when the base is zero +│ ├── when the exponent is zero +│ │ └── it should return the unit number +│ └── when the exponent is not zero +│ └── it should return zero +└── when the base is not zero + ├── when the base is the unit number + │ └── it should return the unit number + └── when the base is not the unit number + ├── when the exponent is zero + │ └── it should return the base + └── when the exponent is not zero + ├── when the exponent is the unit number + │ └── it should return the base + └── when the exponent is not the unit number + ├── when the exponent is negative + │ └── it should return the correct value + └── when the exponent is positive + └── it should return the correct value */ // Test for zero exponent // x ** 0 == 1 @@ -1243,6 +1322,7 @@ contract CryticPRBMath59x18Properties { // its absolute value and the value of the exponent function pow_test_values(SD59x18 x, SD59x18 a) public view { require(x.neq(ZERO_FP)); + require(x.neq(MIN_SD59x18) && a.neq(MIN_SD59x18)); SD59x18 x_a = pow(x, a); @@ -1397,7 +1477,7 @@ contract CryticPRBMath59x18Properties { // Test for maximum value function sqrt_test_maximum() public view { - try this.helpersSqrt(MAX_SD59x18) { + try this.helpersSqrt(MAX_SQRT) { // Expected behaviour, MAX_SD59x18 is positive, and operation // should not revert as the result is in range } catch { @@ -1458,7 +1538,7 @@ contract CryticPRBMath59x18Properties { // The maximum loss of precision is given by the formula: // | log2(x) + log2(y) | - uint256 loss = intoUint256(abs(log2(x) + log2(y))); + uint256 loss = intoUint256(abs(log2(x).add(log2(y)))); assert(equal_within_precision(log2_x_log2_y, log2_xy, loss)); } @@ -1546,20 +1626,23 @@ contract CryticPRBMath59x18Properties { // The maximum loss of precision is given by the formula: // | log2(x) + log2(y) | - uint256 loss = intoUint256(abs(log2(x) + log2(y))); + uint256 loss = intoUint256(abs(log2(x).add(log2(y)))); assert(equal_within_precision(ln_x_ln_y, ln_xy, loss)); } // Test for logarithm of a power // ln(x ** y) = y * ln(x) - function ln_test_power(SD59x18 x, SD59x18 y) public pure { + function ln_test_power(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP)); SD59x18 x_y = pow(x, y); SD59x18 ln_x_y = ln(x_y); SD59x18 y_ln_x = mul(ln(x), y); - assert(y_ln_x.eq(ln_x_y)); + uint256 loss = intoUint256(abs(log2(x).add(log2(y)))); + + assert(equal_within_precision(ln_x_y, y_ln_x, loss)); } /* ================================================================ diff --git a/lib/prb-math b/lib/prb-math index 7ce3009..1edf08d 160000 --- a/lib/prb-math +++ b/lib/prb-math @@ -1 +1 @@ -Subproject commit 7ce3009bbfa0d8e2d430b7a1a9ca46b6e706d90d +Subproject commit 1edf08dd73eb1ace0042459ba719b8ea4a55c0e0 From 1095586a0b85b5a89d736f4d11c07e4f3e964c0e Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Fri, 12 May 2023 14:03:28 +0200 Subject: [PATCH 11/32] draft 59x18 and 60x18 properties --- contracts/Math/PRBMath/README.md | 36 + .../v3/PRBMathSD59x18PropertyTests.sol | 551 ++++- .../v3/PRBMathUD60x18PropertyTests.sol | 1765 +++++++++++++++++ .../Math/{PRBMath => }/echidna-config.yaml | 0 package.json | 71 +- 5 files changed, 2284 insertions(+), 139 deletions(-) create mode 100644 contracts/Math/PRBMath/README.md create mode 100644 contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol rename contracts/Math/{PRBMath => }/echidna-config.yaml (100%) diff --git a/contracts/Math/PRBMath/README.md b/contracts/Math/PRBMath/README.md new file mode 100644 index 0000000..646b51b --- /dev/null +++ b/contracts/Math/PRBMath/README.md @@ -0,0 +1,36 @@ +# PRBMath test suite for Echidna + +## What is PRBMath? + +The Solidity smart contract programming language does not have any inbuilt feature for working with decimal numbers, so for contracts dealing with non-integer values, a third party solution is needed. PRBMath is a fixed-point arithmetic Solidity library that operates on signed 59x18-decimal fixed-point and unsigned 60.18-decimal fixed-point numbers. This library was developed by [Paul Razvan Berg](https://github.com/PaulRBerg "Paul Razvan Berg") and is [open source](https://github.com/PaulRBerg/prb-math "open source") under the MIT License. + +## Why are tests needed? + +Solidity libraries are used in smart contracts that at some point in time can hold important value in tokens or other assets. The security of those assets is directly related to the robustness and reliability of the smart contract source code. + +While testing does not guarantee the absence of errors, it helps the developers in assessing what the risky operations are, how they work, and ultimately how can they fail. Furthermore, having a working test suite with common and edge cases is useful to ensure that code does not behave unexpectedly, and that future versions of the library do not break compatibility. + +Echidna testing can be integrated into CI/CD pipelines, so bugs are caught early and the developers are notified about security risks in their contracts. + +## Who are these tests designed for? + +In principle, these tests are meant to be an entry level practice to learn how to use Echidna for assertion-based fuzz tests, targeting a stand-alone library. It is a self contained exercise that shows how to determine the contract invariants, create proper tests, and configure the relevant Echidna parameters for the campaign. + +Determining the invariants is a process that involves an intermediate-level comprehension of the library and the math properties behind the operations implemented. For example, the addition function has the `x+y == y+x` commutative property. This statement should always be true, no matter the values of `x` and `y`, therefore it should be a good invariant for the system. More complex operations can demand more complex invariants. + +The next step, creating the tests, means to implement Solidity functions that verify the previously defined invariants. Echidna is a fuzz tester, so it can quickly test different values for the arguments of a function. For example, the commutative property can be tested using a function that takes two parameters and performs the additions, as shown below: + +```solidity +// Test for commutative property +// x + y == y + x +function add_test_commutative(SD59x18 x, SD59x18 y) public pure { + SD59x18 x_y = x.add(y); + SD59x18 y_x = y.add(x); + + assert(x_y.eq(y_x)); +} +``` + +Finally, the fuzzer has to be instructed to perform the correct type of test, the number of test runs to be made, among other configuration parameters. Since the invariant is checked using an assertion, Echidna must be configured to try to find assertion violations. In this mode, different argument values are passed to `add_test_commutative()`, and the result of the `assert(x_y == y_x)` expression is evaluated for each call: if the assertion is false, the invariant was broken, and it is a sign that there can be an issue with the library implementation. + +However, even if this particular test suite is meant as an exercise, it can be used as a template to create tests for other fixed-point arithmetic libraries implementations. diff --git a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol index 8867dce..3d72f35 100644 --- a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol @@ -5,7 +5,7 @@ import {add, sub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb/math/src/sd59 import {convert} from "@prb/math/src/sd59x18/Conversions.sol"; import {msb} from "@prb/math/src/Common.sol"; import {intoUint128, intoUint256} from "@prb/math/src/sd59x18/Casting.sol"; -import {mul, div, abs, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor} from "@prb/math/src/sd59x18/Math.sol"; +import {mul, div, abs, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, powu} from "@prb/math/src/sd59x18/Math.sol"; contract CryticPRBMath59x18Propertiesv3 { @@ -25,12 +25,12 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 internal THOUSAND_FP = convert(1000); SD59x18 internal MINUS_SIXTY_FOUR_FP = convert(-64); SD59x18 internal EPSILON = SD59x18.wrap(1); - SD59x18 internal ONE_TENTH_FP = convert(1); + SD59x18 internal ONE_TENTH_FP = convert(1).div(convert(10)); /* ================================================================ Constants used for precision loss calculations ================================================================ */ - uint256 internal REQUIRED_SIGNIFICANT_BITS = 10; + uint256 internal REQUIRED_SIGNIFICANT_DIGITS = 10; /* ================================================================ Integer representations maximum values. @@ -63,6 +63,12 @@ contract CryticPRBMath59x18Propertiesv3 { int256 constant uMAX_SQRT = uMAX_SD59x18 / uUNIT; SD59x18 constant MAX_SQRT = SD59x18.wrap(uMAX_SQRT); + SD59x18 internal constant MAX_PERMITTED_EXP2 = SD59x18.wrap(192e18 - 1); + SD59x18 internal constant MIN_PERMITTED_EXP2 = SD59x18.wrap(-59_794705707972522261); + + SD59x18 internal constant MAX_PERMITTED_EXP = SD59x18.wrap(133_084258667509499440); + SD59x18 internal constant MIN_PERMITTED_EXP = SD59x18.wrap(-41_446531673892822322); + /// @dev Half the UNIT number. int256 constant uHALF_UNIT = 0.5e18; SD59x18 constant HALF_UNIT = SD59x18.wrap(uHALF_UNIT); @@ -86,6 +92,7 @@ contract CryticPRBMath59x18Propertiesv3 { Helper functions. ================================================================ */ + // @audit not checking for overflows // These functions allows to compare a and b for equality, discarding // the last precision_bits bits. // Uses functions from the library under test! @@ -97,6 +104,7 @@ contract CryticPRBMath59x18Propertiesv3 { return (eq(r, convert(0))); } + // @audit not checking for overflows function equal_within_precision_u(uint256 a, uint256 b, uint256 precision_bits) public pure returns(bool) { uint256 max = (a > b) ? a : b; uint256 min = (a > b) ? b : a; @@ -139,8 +147,8 @@ contract CryticPRBMath59x18Propertiesv3 { function equal_most_significant_digits_within_precision(SD59x18 a, SD59x18 b, uint256 bits) public view returns (bool) { // Get the number of bits in a and b // Since log(x) returns in the interval [-64, 63), add 64 to be in the interval [0, 127) - uint256 a_bits = uint256(int256(convert(log2(a)) + 64)); - uint256 b_bits = uint256(int256(convert(log2(b)) + 64)); + uint256 a_bits = uint256(int256(convert(log2(a)) + 18)); + uint256 b_bits = uint256(int256(convert(log2(b)) + 18)); // a and b lengths may differ in 1 bit, so the shift should take into account the longest uint256 shift_bits = (a_bits > b_bits) ? (a_bits - bits) : (b_bits - bits); @@ -289,6 +297,10 @@ contract CryticPRBMath59x18Propertiesv3 { return pow(x, y); } + function helpersPowu(SD59x18 x, uint256 y) public pure returns (SD59x18) { + return powu(x, y); + } + function helpersAvg(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { return avg(x, y); } @@ -317,6 +329,7 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ + // @audit-ok // Test for commutative property // x + y == y + x function add_test_commutative(SD59x18 x, SD59x18 y) public pure { @@ -326,6 +339,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x_y.eq(y_x)); } + // @audit-ok // Test for associative property // (x + y) + z == x + (y + z) function add_test_associative(SD59x18 x, SD59x18 y, SD59x18 z) public pure { @@ -337,6 +351,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(xy_z.eq(x_yz)); } + // @audit-ok // Test for identity operation // x + 0 == x (equivalent to x + (-x) == 0) function add_test_identity(SD59x18 x) public view { @@ -346,6 +361,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x.sub(x).eq(ZERO_FP)); } + // @audit-ok // Test that the result increases or decreases depending // on the value to be added function add_test_values(SD59x18 x, SD59x18 y) public view { @@ -365,6 +381,7 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ + // @audit-ok // The result of the addition must be between the maximum // and minimum allowed values for SD59x18 function add_test_range(SD59x18 x, SD59x18 y) public view { @@ -375,6 +392,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Adding zero to the maximum value shouldn't revert, as it is valid // Moreover, the result must be MAX_SD59x18 function add_test_maximum_value() public view { @@ -386,6 +404,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Adding one to the maximum value should revert, as it is out of range function add_test_maximum_value_plus_one() public view { try this.helpersAdd(MAX_SD59x18, ONE_FP) { @@ -395,6 +414,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Adding zero to the minimum value shouldn't revert, as it is valid // Moreover, the result must be MIN_SD59x18 function add_test_minimum_value() public view { @@ -406,6 +426,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Adding minus one to the maximum value should revert, as it is out of range function add_test_minimum_value_plus_negative_one() public view { try this.helpersAdd(MIN_SD59x18, MINUS_ONE_FP) { @@ -430,6 +451,7 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ + // @audit-ok // Test equivalence to addition // x - y == x + (-y) function sub_test_equivalence_to_addition(SD59x18 x, SD59x18 y) public pure { @@ -440,6 +462,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(addition.eq(subtraction)); } + // @audit-ok // Test for non-commutative property // x - y == -(y - x) function sub_test_non_commutative(SD59x18 x, SD59x18 y) public pure { @@ -449,6 +472,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x_y.eq(neg(y_x))); } + // @audit-ok // Test for identity operation // x - 0 == x (equivalent to x - x == 0) function sub_test_identity(SD59x18 x) public view { @@ -458,6 +482,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x.sub(x).eq(ZERO_FP)); } + // @audit-ok // Test for neutrality over addition and subtraction // (x - y) + y == (x + y) - y == x function sub_test_neutrality(SD59x18 x, SD59x18 y) public pure { @@ -471,6 +496,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x_minus_y_plus_y.eq(x)); } + // @audit-ok // Test that the result increases or decreases depending // on the value to be subtracted function sub_test_values(SD59x18 x, SD59x18 y) public view { @@ -489,6 +515,7 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ + // @audit-ok // The result of the subtraction must be between the maximum // and minimum allowed values for SD59x18 function sub_test_range(SD59x18 x, SD59x18 y) public view { @@ -499,6 +526,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Subtracting zero from the maximum value shouldn't revert, as it is valid // Moreover, the result must be MAX_SD59x18 function sub_test_maximum_value() public view { @@ -510,6 +538,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Subtracting minus one from the maximum value should revert, // as it is out of range function sub_test_maximum_value_minus_neg_one() public view { @@ -520,6 +549,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Subtracting zero from the minimum value shouldn't revert, as it is valid // Moreover, the result must be MIN_SD59x18 function sub_test_minimum_value() public view { @@ -531,6 +561,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Subtracting one from the minimum value should revert, as it is out of range function sub_test_minimum_value_minus_one() public view { try this.helpersSub(MIN_SD59x18, ONE_FP) { @@ -554,15 +585,18 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ + // @audit-ok // Test for commutative property // x * y == y * x function mul_test_commutative(SD59x18 x, SD59x18 y) public pure { + require(x.neq(MIN_SD59x18) && y.neq(MIN_SD59x18)); SD59x18 x_y = x.mul(y); SD59x18 y_x = y.mul(x); assert(x_y.eq(y_x)); } + // @audit - No guarantee of precision // Test for associative property // (x * y) * z == x * (y * z) function mul_test_associative(SD59x18 x, SD59x18 y, SD59x18 z) public view { @@ -573,15 +607,16 @@ contract CryticPRBMath59x18Propertiesv3 { // todo check if this should not be used // Failure if all significant digits are lost - /*require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); - require(significant_bits_after_mult(y, z) > REQUIRED_SIGNIFICANT_BITS); - require(significant_bits_after_mult(x_y, z) > REQUIRED_SIGNIFICANT_BITS); - require(significant_bits_after_mult(x, y_z) > REQUIRED_SIGNIFICANT_BITS);*/ + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + require(significant_digits_after_mult(y, z) > REQUIRED_SIGNIFICANT_DIGITS); + require(significant_digits_after_mult(x_y, z) > REQUIRED_SIGNIFICANT_DIGITS); + require(significant_digits_after_mult(x, y_z) > REQUIRED_SIGNIFICANT_DIGITS); - //assert(equal_within_tolerance(xy_z, x_yz, ONE_TENTH_FP)); - assert(xy_z.eq(x_yz)); + assert(equal_within_tolerance(xy_z, x_yz, ONE_TENTH_FP)); + //assert(xy_z.eq(x_yz)); } + // @audit - No guarantee of precision // Test for distributive property // x * (y + z) == x * y + x * z function mul_test_distributive(SD59x18 x, SD59x18 y, SD59x18 z) public view { @@ -593,14 +628,15 @@ contract CryticPRBMath59x18Propertiesv3 { // todo check if this should not be used // Failure if all significant digits are lost - /*require(significant_bits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); - require(significant_bits_after_mult(x, z) > REQUIRED_SIGNIFICANT_BITS); - require(significant_bits_after_mult(x, y_plus_z) > REQUIRED_SIGNIFICANT_BITS); + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + require(significant_digits_after_mult(x, z) > REQUIRED_SIGNIFICANT_DIGITS); + require(significant_digits_after_mult(x, y_plus_z) > REQUIRED_SIGNIFICANT_DIGITS); - assert(equal_within_tolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP));*/ - assert(x_times_y.add(x_times_z).eq(x_times_y_plus_z)); + assert(equal_within_tolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP)); + //assert(x_times_y.add(x_times_z).eq(x_times_y_plus_z)); } + // @audit-ok // Test for identity operation // x * 1 == x (also check that x * 0 == 0) function mul_test_identity(SD59x18 x) public view { @@ -611,6 +647,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x_1.eq(x)); } + // @audit - No guarantee of precision // Test that the result increases or decreases depending // on the value to be added function mul_test_values(SD59x18 x, SD59x18 y) public view { @@ -642,6 +679,7 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ + // @audit-ok // The result of the multiplication must be between the maximum // and minimum allowed values for SD59x18 function mul_test_range(SD59x18 x, SD59x18 y) public view { @@ -652,6 +690,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Multiplying the maximum value times one shouldn't revert, as it is valid // Moreover, the result must be MAX_SD59x18 function mul_test_maximum_value() public view { @@ -663,12 +702,13 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Multiplying the minimum value times one shouldn't revert, as it is valid // Moreover, the result must be MIN_SD59x18 function mul_test_minimum_value() public view { try this.helpersMul(MIN_SD59x18.add(ONE_FP), ONE_FP) returns (SD59x18 result) { // Expected behaviour, does not revert - assert(result.eq(MIN_SD59x18)); + assert(result.eq(MIN_SD59x18.add(ONE_FP))); } catch { assert(false); } @@ -687,12 +727,7 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - /// Requirements: - /// - Refer to the requirements in {Common.mulDiv}. - /// - None of the inputs can be `MIN_SD59x18`. - /// - The denominator must not be zero. - /// - The result must fit in SD59x18. - + // @audit-ok // Test for identity property // x / 1 == x (equivalent to x / x == 1) // Moreover, x/x should not revert unless x == 0 @@ -707,12 +742,15 @@ contract CryticPRBMath59x18Propertiesv3 { div_x = div(x, x); assert(div_x.eq(ONE_FP)); } catch { - // The only allowed case to revert is if x == 0 - assert(x.eq(ZERO_FP)); + // There are a couple of allowed cases for a revert: + // 1. x == 0 + // 2. x == MIN_SD59x18 + // 3. when the result overflows + assert(x.eq(ZERO_FP) || x.eq(MIN_SD59x18)); } } - + // @audit-ok // Test for negative divisor // x / -y == -(x / y) function div_test_negative_divisor(SD59x18 x, SD59x18 y) public view { @@ -724,6 +762,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x_y.eq(neg(x_minus_y))); } + // @audit-ok // Test for division with 0 as numerator // 0 / x = 0 function div_test_division_num_zero(SD59x18 x) public view { @@ -734,6 +773,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(ZERO_FP.eq(div_0)); } + // @audit-ok // Test that the absolute value of the result increases or // decreases depending on the denominator's absolute value function div_test_values(SD59x18 x, SD59x18 y) public view { @@ -754,6 +794,7 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ + // @audit-ok // Test for division by zero function div_test_div_by_zero(SD59x18 x) public view { try this.helpersDiv(x, ZERO_FP) { @@ -764,6 +805,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Test for division by a large value, the result should be less than one function div_test_maximum_denominator(SD59x18 x) public view { SD59x18 div_large = div(x, MAX_SD59x18); @@ -771,6 +813,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(abs(div_large).lte(ONE_FP)); } + // @audit-ok // Test for division of a large value // This should revert if |x| < 1 as it would return a value higher than max function div_test_maximum_numerator(SD59x18 x) public view { @@ -786,6 +829,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Test for values in range function div_test_range(SD59x18 x, SD59x18 y) public view { SD59x18 result; @@ -811,6 +855,7 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ + // @audit-ok // Test for the double negation // -(-x) == x function neg_test_double_negation(SD59x18 x) public pure { @@ -819,6 +864,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x.eq(double_neg)); } + // @audit-ok // Test for the identity operation // x + (-x) == 0 function neg_test_identity(SD59x18 x) public view { @@ -833,6 +879,7 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ + // @audit-ok // Test for the zero-case // -0 == 0 function neg_test_zero() public view { @@ -841,7 +888,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(neg_x.eq(ZERO_FP)); } - // todo check what is used for SD59x18 + // @audit todo check what is used for SD59x18 // Test for the maximum value case // Since this is implementation-dependant, we will actually test with MAX_SD59x18-EPS function neg_test_maximum() public view { @@ -852,7 +899,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // todo check what is used for SD59x18 + // @audit todo check what is used for SD59x18 // Test for the minimum value case // Since this is implementation-dependant, we will actually test with MIN_SD59x18+EPS function neg_test_minimum() public view { @@ -875,9 +922,7 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - /// @dev Requirements: - /// - x must be greater than `MIN_SD59x18`. - + // @audit-ok // Test that the absolute value is always positive function abs_test_positive(SD59x18 x) public view { SD59x18 abs_x = abs(x); @@ -885,6 +930,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(abs_x.gte(ZERO_FP)); } + // @audit-ok // Test that the absolute value of a number equals the // absolute value of the negative of the same number function abs_test_negative(SD59x18 x) public pure { @@ -894,6 +940,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(abs_x.eq(abs_minus_x)); } + // @audit check precision used // Test the multiplicativeness property // | x * y | == |x| * |y| function abs_test_multiplicativeness(SD59x18 x, SD59x18 y) public pure { @@ -909,6 +956,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_precision(abs_xy, abs_x_abs_y, 2)); } + // @audit-ok // Test the subadditivity property // | x + y | <= |x| + |y| function abs_test_subadditivity(SD59x18 x, SD59x18 y) public pure { @@ -925,6 +973,7 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ + // @audit-ok // Test the zero-case | 0 | = 0 function abs_test_zero() public view { SD59x18 abs_zero; @@ -939,6 +988,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Test the maximum value function abs_test_maximum() public view { SD59x18 abs_max; @@ -950,14 +1000,27 @@ contract CryticPRBMath59x18Propertiesv3 { } catch {} } + // @audit-ok // Test the minimum value - function abs_test_minimum() public view { + function abs_test_minimum_revert() public view { SD59x18 abs_min; try this.helpersAbs(MIN_SD59x18) { - // If it doesn't revert, the value must be the negative of MIN_SD59x18 - abs_min = this.helpersAbs(MIN_SD59x18); - assert(abs_min.eq(neg(MIN_SD59x18))); + // It should always revert for MIN_SD59x18 + assert(false); + } catch {} + } + + // @audit-ok + // Test the minimum value + function abs_test_minimum_allowed() public view { + SD59x18 abs_min; + SD59x18 input = MIN_SD59x18.add(ONE_FP); + + try this.helpersAbs(input) { + // If it doesn't revert, the value must be the negative of MIN_SD59x18 + 1 + abs_min = this.helpersAbs(input); + assert(abs_min.eq(neg(input))); } catch {} } @@ -973,8 +1036,7 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - /// - x must not be zero. - + // @audit-ok // Test that the inverse of the inverse is close enough to the // original number function inv_test_double_inverse(SD59x18 x) public view { @@ -988,6 +1050,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_precision(x, double_inv_x, loss)); } + // @audit-ok // Test equivalence with division function inv_test_division(SD59x18 x) public view { require(x.neq(ZERO_FP)); @@ -998,6 +1061,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(inv_x.eq(div_1_x)); } + // @audit check if loss is correct // Test the anticommutativity of the division // x / y == 1 / (y / x) function inv_test_division_noncommutativity( @@ -1010,14 +1074,15 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 y_x = div(y, x); require( - significant_digits_after_mult(x, inv(y)) > REQUIRED_SIGNIFICANT_BITS + significant_digits_after_mult(x, inv(y)) > REQUIRED_SIGNIFICANT_DIGITS ); require( - significant_digits_after_mult(y, inv(x)) > REQUIRED_SIGNIFICANT_BITS + significant_digits_after_mult(y, inv(x)) > REQUIRED_SIGNIFICANT_DIGITS ); assert(equal_within_tolerance(x_y, inv(y_x), ONE_TENTH_FP)); } + // @audit check if loss is correct // Test the multiplication of inverses // 1/(x * y) == 1/x * 1/y function inv_test_multiplication(SD59x18 x, SD59x18 y) public view { @@ -1030,9 +1095,9 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_y = mul(x, y); SD59x18 inv_x_y = inv(x_y); - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); require( - significant_digits_after_mult(inv_x, inv_y) > REQUIRED_SIGNIFICANT_BITS + significant_digits_after_mult(inv_x, inv_y) > REQUIRED_SIGNIFICANT_DIGITS ); // The maximum loss of precision is given by the formula: @@ -1042,8 +1107,9 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_precision(inv_x_y, inv_x_times_inv_y, loss)); } + // @audit-ok // Test identity property - // Intermediate result should have at least REQUIRED_SIGNIFICANT_BITS + // Intermediate result should have at least REQUIRED_SIGNIFICANT_DIGITS function inv_test_identity(SD59x18 x) public view { require(x.neq(ZERO_FP)); @@ -1051,13 +1117,14 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 identity = mul(inv_x, x); require( - significant_digits_after_mult(x, inv_x) > REQUIRED_SIGNIFICANT_BITS + significant_digits_after_mult(x, inv_x) > REQUIRED_SIGNIFICANT_DIGITS ); // They should agree with a tolerance of one tenth of a percent assert(equal_within_tolerance(identity, ONE_FP, ONE_TENTH_FP)); } + // @audit-ok // Test that the absolute value of the result is in range zero-one // if x is greater than one, else, the absolute value of the result // must be greater than one @@ -1073,6 +1140,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Test that the result has the same sign as the argument. // Since inv() rounds towards zero, we are checking the zero case as well function inv_test_sign(SD59x18 x) public { @@ -1093,6 +1161,7 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ + // @audit-ok // Test the zero-case, should revert function inv_test_zero() public view { try this.helpersInv(ZERO_FP) { @@ -1101,6 +1170,7 @@ contract CryticPRBMath59x18Propertiesv3 { } catch {} } + // @audit-ok // Test the maximum value case, should not revert, and be close to zero function inv_test_maximum() public view { SD59x18 inv_maximum; @@ -1114,6 +1184,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit Check precision // Test the minimum value case, should not revert, and be close to zero function inv_test_minimum() public view { SD59x18 inv_minimum; @@ -1140,6 +1211,7 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ + // @audit-ok // Test that the result is between the two operands // avg(x, y) >= min(x, y) && avg(x, y) <= max(x, y) function avg_test_values_in_range(SD59x18 x, SD59x18 y) public pure { @@ -1152,6 +1224,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Test that the average of the same number is itself // avg(x, x) == x function avg_test_one_value(SD59x18 x) public pure { @@ -1160,6 +1233,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(avg_x.eq(x)); } + // @audit-ok // Test that the order of operands is irrelevant // avg(x, y) == avg(y, x) function avg_test_operand_order(SD59x18 x, SD59x18 y) public pure { @@ -1175,6 +1249,7 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ + // @audit-ok // Test for the maximum value function avg_test_maximum() public view { SD59x18 result; @@ -1187,6 +1262,7 @@ contract CryticPRBMath59x18Propertiesv3 { } catch {} } + // @audit-ok // Test for the minimum value function avg_test_minimum() public view { SD59x18 result; @@ -1211,27 +1287,7 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ -/* pow.t.sol -├── when the base is zero -│ ├── when the exponent is zero -│ │ └── it should return the unit number -│ └── when the exponent is not zero -│ └── it should return zero -└── when the base is not zero - ├── when the base is the unit number - │ └── it should return the unit number - └── when the base is not the unit number - ├── when the exponent is zero - │ └── it should return the base - └── when the exponent is not zero - ├── when the exponent is the unit number - │ └── it should return the base - └── when the exponent is not the unit number - ├── when the exponent is negative - │ └── it should return the correct value - └── when the exponent is positive - └── it should return the correct value */ - + // @audit-ok // Test for zero exponent // x ** 0 == 1 function pow_test_zero_exponent(SD59x18 x) public view { @@ -1240,6 +1296,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x_pow_0.eq(ONE_FP)); } + // @audit-ok // Test for zero base // 0 ** x == 0 (for positive x) function pow_test_zero_base(SD59x18 x) public view { @@ -1250,6 +1307,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(zero_pow_x.eq(ZERO_FP)); } + // @audit-ok // Test for exponent one // x ** 1 == x function pow_test_one_exponent(SD59x18 x) public view { @@ -1258,6 +1316,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x_pow_1.eq(x)); } + // @audit-ok // Test for base one // 1 ** x == 1 function pow_test_base_one(SD59x18 x) public view { @@ -1266,6 +1325,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(one_pow_x.eq(ONE_FP)); } + // @audit - Fails // Test for product of powers of the same base // x ** a * x ** b == x ** (a + b) function pow_test_product_same_base( @@ -1279,9 +1339,10 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_b = pow(x, b); SD59x18 x_ab = pow(x, a.add(b)); - assert(equal_within_precision(mul(x_a, x_b), x_ab, 2)); + assert(equal_within_precision(mul(x_a, x_b), x_ab, 10)); } + // @audit - fails // Test for power of an exponentiation // (x ** a) ** b == x ** (a * b) function pow_test_power_of_an_exponentiation( @@ -1295,9 +1356,10 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_a_b = pow(x_a, b); SD59x18 x_ab = pow(x, a.mul(b)); - assert(equal_within_precision(x_a_b, x_ab, 2)); + assert(equal_within_precision(x_a_b, x_ab, 10)); } + // @audit check precision // Test for power of a product // (x * y) ** a == x ** a * y ** a function pow_test_product_power( @@ -1315,9 +1377,10 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_a = pow(x, a); SD59x18 y_a = pow(y, a); - assert(equal_within_precision(mul(x_a, y_a), xy_a, 2)); + assert(equal_within_precision(mul(x_a, y_a), xy_a, 10)); } + // @audit - fails // Test for result being greater than or lower than the argument, depending on // its absolute value and the value of the exponent function pow_test_values(SD59x18 x, SD59x18 a) public view { @@ -1335,6 +1398,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Test for result sign: if the exponent is even, sign is positive // if the exponent is odd, preserves the sign of the base function pow_test_sign(SD59x18 x, SD59x18 a) public view { @@ -1346,7 +1410,6 @@ contract CryticPRBMath59x18Propertiesv3 { // rounded down to zero and thus changes sign require(x_a.neq(ZERO_FP)); - // todo should I unwrap here? // If the exponent is even if (a.mod(convert(2)).eq(ZERO_FP)) { assert(x_a.eq(abs(x_a))); @@ -1366,6 +1429,8 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ + + // @audit-ok // Test for maximum base and exponent > 1 function pow_test_maximum_base(SD59x18 a) public view { require(a.gt(ONE_FP)); @@ -1378,9 +1443,10 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit Check 2**18 // Test for abs(base) < 1 and high exponent function pow_test_high_exponent(SD59x18 x, SD59x18 a) public view { - require(abs(x).lt(ONE_FP) && a.gt(convert(2 ** 64))); + require(abs(x).lt(ONE_FP) && a.gt(convert(2 ** 18))); SD59x18 result = pow(x, a); @@ -1399,18 +1465,11 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - /// Notes: - /// - Only the positive root is returned. - /// - The result is rounded toward zero. - /// - /// Requirements: - /// - x cannot be negative, since complex numbers are not supported. - /// - x must be less than `MAX_SD59x18 / UNIT`. - + // @audit-ok // Test for the inverse operation // sqrt(x) * sqrt(x) == x function sqrt_test_inverse_mul(SD59x18 x) public view { - require(x.lte(ZERO_FP)); + require(x.gte(ZERO_FP)); SD59x18 sqrt_x = sqrt(x); SD59x18 sqrt_x_squared = mul(sqrt_x, sqrt_x); @@ -1425,6 +1484,7 @@ contract CryticPRBMath59x18Propertiesv3 { ); } + // @audit-ok // Test for the inverse operation // sqrt(x) ** 2 == x function sqrt_test_inverse_pow(SD59x18 x) public view { @@ -1443,6 +1503,7 @@ contract CryticPRBMath59x18Propertiesv3 { ); } + // @audit check the precision // Test for distributive property respect to the multiplication // sqrt(x) * sqrt(y) == sqrt(x * y) function sqrt_test_distributive(SD59x18 x, SD59x18 y) public view { @@ -1454,10 +1515,10 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 sqrt_xy = sqrt(mul(x, y)); // Ensure we have enough significant digits for the result to be meaningful - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); require( significant_digits_after_mult(sqrt_x, sqrt_y) > - REQUIRED_SIGNIFICANT_BITS + REQUIRED_SIGNIFICANT_DIGITS ); // Allow an error of up to one tenth of a percent @@ -1470,15 +1531,17 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ + // @audit-ok // Test for zero case function sqrt_test_zero() public view { assert(sqrt(ZERO_FP).eq(ZERO_FP)); } + // @audit-ok // Test for maximum value function sqrt_test_maximum() public view { try this.helpersSqrt(MAX_SQRT) { - // Expected behaviour, MAX_SD59x18 is positive, and operation + // Expected behaviour, MAX_SQRT is positive, and operation // should not revert as the result is in range } catch { // Unexpected, should not revert @@ -1486,6 +1549,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Test for minimum value function sqrt_test_minimum() public view { try this.helpersSqrt(MIN_SD59x18) { @@ -1496,6 +1560,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Test for negative operands function sqrt_test_negative(SD59x18 x) public view { require(x.lt(ZERO_FP)); @@ -1520,9 +1585,7 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - /// Requirements: - /// - x must be greater than zero. - + // @audit check loss // Test for distributive property respect to multiplication // log2(x * y) = log2(x) + log2(y) function log2_test_distributive_mul(SD59x18 x, SD59x18 y) public view { @@ -1534,7 +1597,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 log2_xy = log2(xy); // Ensure we have enough significant digits for the result to be meaningful - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); // The maximum loss of precision is given by the formula: // | log2(x) + log2(y) | @@ -1543,14 +1606,15 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_precision(log2_x_log2_y, log2_xy, loss)); } + // @audit - fails // Test for logarithm of a power // log2(x ** y) = y * log2(x) - function log2_test_power(SD59x18 x, SD59x18 y) public pure { + function log2_test_power(SD59x18 x, SD59x18 y) public view { SD59x18 x_y = pow(x, y); SD59x18 log2_x_y = log2(x_y); SD59x18 y_log2_x = mul(log2(x), y); - assert(y_log2_x.eq(log2_x_y)); + assert(equal_within_precision(y_log2_x, log2_x_y, 4)); } /* ================================================================ @@ -1559,6 +1623,7 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ + // @audit-ok // Test for zero case, should revert function log2_test_zero() public view { try this.helpersLog2(ZERO_FP) { @@ -1569,6 +1634,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Test for maximum value case, should return a positive number function log2_test_maximum() public view { SD59x18 result; @@ -1583,6 +1649,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Test for negative values, should revert as log2 is not defined function log2_test_negative(SD59x18 x) public view { require(x.lt(ZERO_FP)); @@ -1607,9 +1674,7 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - /// Requirements: - /// - x must be greater than zero. - + // @audit check precision // Test for distributive property respect to multiplication // ln(x * y) = ln(x) + ln(y) function ln_test_distributive_mul(SD59x18 x, SD59x18 y) public view { @@ -1622,7 +1687,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 xy = mul(x, y); SD59x18 ln_xy = ln(xy); - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_BITS); + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); // The maximum loss of precision is given by the formula: // | log2(x) + log2(y) | @@ -1631,6 +1696,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_precision(ln_x_ln_y, ln_xy, loss)); } + // @audit check precision // Test for logarithm of a power // ln(x ** y) = y * ln(x) function ln_test_power(SD59x18 x, SD59x18 y) public { @@ -1640,9 +1706,10 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 y_ln_x = mul(ln(x), y); + // todo this isn't correct uint256 loss = intoUint256(abs(log2(x).add(log2(y)))); - assert(equal_within_precision(ln_x_y, y_ln_x, loss)); + assert(equal_within_precision(ln_x_y, y_ln_x, 4)); } /* ================================================================ @@ -1651,6 +1718,7 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ + // @audit-ok // Test for zero case, should revert function ln_test_zero() public view { try this.helpersLn(ZERO_FP) { @@ -1661,6 +1729,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Test for maximum value case, should return a positive number function ln_test_maximum() public view { SD59x18 result; @@ -1675,6 +1744,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Test for negative values, should revert as ln is not defined function ln_test_negative(SD59x18 x) public view { require(x.lt(ZERO_FP)); @@ -1699,13 +1769,7 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - /// Notes: - /// - If x is less than -59_794705707972522261, the result is zero. - /// - /// Requirements: - /// - x must be less than 192e18. - /// - The result must fit in SD59x18. - + // @audit-ok // Test for equality with pow(2, x) for integer x // pow(2, x) == exp2(x) function exp2_test_equivalence_pow(SD59x18 x) public view { @@ -1715,6 +1779,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(exp2_x.eq(pow_2_x)); } + // @audit - fails // Test for inverse function // If y = log2(x) then exp2(y) == x function exp2_test_inverse(SD59x18 x) public view { @@ -1722,7 +1787,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 exp2_x = exp2(log2_x); // todo is this the correct number of bits? - uint256 bits = 50; + uint256 bits = 18; if (log2_x.lt(ZERO_FP)) { bits = intoUint256(convert(int256(bits)).add(log2_x)); @@ -1731,6 +1796,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_most_significant_digits_within_precision(x, exp2_x, bits)); } + // @audit-ok // Test for negative exponent // exp2(-x) == inv( exp2(x) ) function exp2_test_negative_exponent(SD59x18 x) public view { @@ -1749,6 +1815,7 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ + // @audit-ok // Test for zero case // exp2(0) == 1 function exp2_test_zero() public view { @@ -1756,6 +1823,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(exp_zero.eq(ONE_FP)); } + // @audit-ok // Test for maximum value. This should overflow as it won't fit // in the data type function exp2_test_maximum() public view { @@ -1767,6 +1835,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Test for minimum value. This should return zero since // 2 ** -x == 1 / 2 ** x that tends to zero as x increases function exp2_test_minimum() public view { @@ -1794,10 +1863,7 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - /// Requirements: - /// - The result must fit in SD59x18.. - /// - x must be less than 133_084258667509499441. - + // @audit - fails // Test for inverse function // If y = ln(x) then exp(y) == x function exp_test_inverse(SD59x18 x) public view { @@ -1805,7 +1871,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 exp_x = exp(ln_x); SD59x18 log2_x = log2(x); - uint256 bits = 48; + uint256 bits = 18; if (log2_x.lt(ZERO_FP)) { bits = intoUint256(convert(int256(bits)).add(log2_x)); @@ -1814,6 +1880,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_most_significant_digits_within_precision(x, exp_x, bits)); } + // @audit check precision // Test for negative exponent // exp(-x) == inv( exp(x) ) function exp_test_negative_exponent(SD59x18 x) public view { @@ -1832,6 +1899,7 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ + // @audit-ok // Test for zero case // exp(0) == 1 function exp_test_zero() public view { @@ -1839,6 +1907,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(exp_zero.eq(ONE_FP)); } + // @audit-ok // Test for maximum value. This should overflow as it won't fit // in the data type function exp_test_maximum() public view { @@ -1850,6 +1919,7 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // @audit-ok // Test for minimum value. This should return zero since // e ** -x == 1 / e ** x that tends to zero as x increases function exp_test_minimum() public view { @@ -1865,4 +1935,275 @@ contract CryticPRBMath59x18Propertiesv3 { } } + /* ================================================================ + + TESTS FOR FUNCTION powu() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // @audit-ok + // Test for zero exponent + // x ** 0 == 1 + function powu_test_zero_exponent(SD59x18 x) public view { + SD59x18 x_pow_0 = powu(x, 0); + + assert(x_pow_0.eq(ONE_FP)); + } + + // @audit-ok + // Test for zero base + // 0 ** x == 0 (for positive x) + function powu_test_zero_base(uint256 x) public view { + require(x != 0); + + SD59x18 zero_pow_x = powu(ZERO_FP, x); + + assert(zero_pow_x.eq(ZERO_FP)); + } + + // @audit-ok + // Test for exponent one + // x ** 1 == x + function powu_test_one_exponent(SD59x18 x) public view { + SD59x18 x_pow_1 = powu(x, 1); + + assert(x_pow_1.eq(x)); + } + + // @audit-ok + // Test for base one + // 1 ** x == 1 + function powu_test_base_one(uint256 x) public view { + SD59x18 one_pow_x = powu(ONE_FP, x); + + assert(one_pow_x.eq(ONE_FP)); + } + + // @audit - Fails + // Test for product of powers of the same base + // x ** a * x ** b == x ** (a + b) + function powu_test_product_same_base( + SD59x18 x, + uint256 a, + uint256 b + ) public view { + require(x.neq(ZERO_FP)); + + SD59x18 x_a = powu(x, a); + SD59x18 x_b = powu(x, b); + SD59x18 x_ab = powu(x, a + b); + + assert(equal_within_precision(mul(x_a, x_b), x_ab, 10)); + } + + // @audit - fails + // Test for power of an exponentiation + // (x ** a) ** b == x ** (a * b) + function powu_test_power_of_an_exponentiation( + SD59x18 x, + uint256 a, + uint256 b + ) public view { + require(x.neq(ZERO_FP)); + + SD59x18 x_a = powu(x, a); + SD59x18 x_a_b = powu(x_a, b); + SD59x18 x_ab = powu(x, a * b); + + assert(equal_within_precision(x_a_b, x_ab, 10)); + } + + // @audit check precision + // Test for power of a product + // (x * y) ** a == x ** a * y ** a + function powu_test_product_power( + SD59x18 x, + SD59x18 y, + uint256 a + ) public view { + require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); + // todo this should probably be changed + require(a > 2 ** 32); // to avoid massive loss of precision + + SD59x18 x_y = mul(x, y); + SD59x18 xy_a = powu(x_y, a); + + SD59x18 x_a = powu(x, a); + SD59x18 y_a = powu(y, a); + + assert(equal_within_precision(mul(x_a, y_a), xy_a, 10)); + } + + // @audit - fails + // Test for result being greater than or lower than the argument, depending on + // its absolute value and the value of the exponent + function powu_test_values(SD59x18 x, uint256 a) public view { + require(x.neq(ZERO_FP)); + require(x.neq(MIN_SD59x18)); + + SD59x18 x_a = powu(x, a); + + if (abs(x).gte(ONE_FP)) { + assert(abs(x_a).gte(ONE_FP)); + } + + if (abs(x).lte(ONE_FP)) { + assert(abs(x_a).lte(ONE_FP)); + } + } + + // @audit-ok + // Test for result sign: if the exponent is even, sign is positive + // if the exponent is odd, preserves the sign of the base + function powu_test_sign(SD59x18 x, uint256 a) public view { + require(x.neq(ZERO_FP) && a != 0); + + SD59x18 x_a = powu(x, a); + + // This prevents the case where a small negative number gets + // rounded down to zero and thus changes sign + require(x_a.neq(ZERO_FP)); + + // If the exponent is even + if (a % 2 == 0) { + assert(x_a.eq(abs(x_a))); + } else { + // x_a preserves x sign + if (x.lt(ZERO_FP)) { + assert(x_a.lt(ZERO_FP)); + } else { + assert(x_a.gt(ZERO_FP)); + } + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + + // @audit-ok + // Test for maximum base and exponent > 1 + function powu_test_maximum_base(uint256 a) public view { + require(a > 1); + + try this.helpersPowu(MAX_SD59x18, a) { + // Unexpected, should revert because of overflow + assert(false); + } catch { + // Expected revert + } + } + + // @audit Check 2**64 + // Test for abs(base) < 1 and high exponent + function powu_test_high_exponent(SD59x18 x, uint256 a) public view { + require(abs(x).lt(ONE_FP) && a > 2 ** 18); + + SD59x18 result = powu(x, a); + + assert(result.eq(ZERO_FP)); + } + + /* ================================================================ + + TESTS FOR FUNCTION log10() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // @audit check loss + // Test for distributive property respect to multiplication + // log10(x * y) = log10(x) + log10(y) + function log10_test_distributive_mul(SD59x18 x, SD59x18 y) public view { + SD59x18 log10_x = log10(x); + SD59x18 log10_y = log10(y); + SD59x18 log10_x_log10_y = add(log10_x, log10_y); + + SD59x18 xy = mul(x, y); + SD59x18 log10_xy = log10(xy); + + // Ensure we have enough significant digits for the result to be meaningful + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + + // The maximum loss of precision is given by the formula: + // | log10(x) + log10(y) | + uint256 loss = intoUint256(abs(log10(x).add(log10(y)))); + + assert(equal_within_precision(log10_x_log10_y, log10_xy, loss)); + } + + // @audit - fails + // Test for logarithm of a power + // log10(x ** y) = y * log10(x) + function log10_test_power(SD59x18 x, SD59x18 y) public { + SD59x18 x_y = pow(x, y); + SD59x18 log10_x_y = log10(x_y); + SD59x18 y_log10_x = mul(log10(x), y); + + if(!equal_within_precision(log10_x_y, y_log10_x, 18)){ + emit AssertionFailed(log10_x_y, y_log10_x); + } + + assert(equal_within_precision(log10_x_y, y_log10_x, 10)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // @audit-ok + // Test for zero case, should revert + function log10_test_zero() public view { + try this.helpersLog10(ZERO_FP) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, log(0) is not defined + } + } + + // @audit-ok + // Test for maximum value case, should return a positive number + function log10_test_maximum() public view { + SD59x18 result; + + try this.helpersLog10(MAX_SD59x18) { + // Expected, should not revert and the result must be > 0 + result = this.helpersLog10(MAX_SD59x18); + assert(result.gt(ZERO_FP)); + } catch { + // Unexpected + assert(false); + } + } + + // @audit-ok + // Test for negative values, should revert as log10 is not defined + function log10_test_negative(SD59x18 x) public view { + require(x.lt(ZERO_FP)); + + try this.helpersLog10(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected + } + } + } \ No newline at end of file diff --git a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol new file mode 100644 index 0000000..49d7f70 --- /dev/null +++ b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol @@ -0,0 +1,1765 @@ +pragma solidity ^0.8.19; + +import { UD60x18 } from "@prb/math/src/UD60x18.sol"; +import {add, sub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb/math/src/ud60x18/Helpers.sol"; +import {convert} from "@prb/math/src/ud60x18/Conversions.sol"; +import {msb} from "@prb/math/src/Common.sol"; +import {intoUint128, intoUint256} from "@prb/math/src/ud60x18/Casting.sol"; +import {mul, div, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, powu} from "@prb/math/src/ud60x18/Math.sol"; + +contract CryticPRBMath60x18Propertiesv3 { + + event AssertionFailed(UD60x18 result); + event AssertionFailed(UD60x18 result1, UD60x18 result2); + + /* ================================================================ + 59x18 fixed-point constants used for testing specific values. + This assumes that PRBMath library's convert(x) works as expected. + ================================================================ */ + UD60x18 internal ZERO_FP = convert(0); + UD60x18 internal ONE_FP = convert(1); + UD60x18 internal TWO_FP = convert(2); + UD60x18 internal THREE_FP = convert(3); + UD60x18 internal EIGHT_FP = convert(8); + UD60x18 internal THOUSAND_FP = convert(1000); + UD60x18 internal EPSILON = UD60x18.wrap(1); + UD60x18 internal ONE_TENTH_FP = convert(1).div(convert(10)); + + /* ================================================================ + Constants used for precision loss calculations + ================================================================ */ + uint256 internal REQUIRED_SIGNIFICANT_DIGITS = 10; + + /* ================================================================ + Integer representations maximum values. + These constants are used for testing edge cases or limits for + possible values. + ================================================================ */ + /// @dev Euler's number as an UD60x18 number. + UD60x18 constant E = UD60x18.wrap(2_718281828459045235); + + /// @dev Half the UNIT number. + uint256 constant uHALF_UNIT = 0.5e18; + UD60x18 constant HALF_UNIT = UD60x18.wrap(uHALF_UNIT); + + /// @dev log2(10) as an UD60x18 number. + uint256 constant uLOG2_10 = 3_321928094887362347; + UD60x18 constant LOG2_10 = UD60x18.wrap(uLOG2_10); + + /// @dev log2(e) as an UD60x18 number. + uint256 constant uLOG2_E = 1_442695040888963407; + UD60x18 constant LOG2_E = UD60x18.wrap(uLOG2_E); + + /// @dev The maximum value an UD60x18 number can have. + uint256 constant uMAX_UD60x18 = 115792089237316195423570985008687907853269984665640564039457_584007913129639935; + UD60x18 constant MAX_UD60x18 = UD60x18.wrap(uMAX_UD60x18); + + /// @dev The maximum whole value an UD60x18 number can have. + uint256 constant uMAX_WHOLE_UD60x18 = 115792089237316195423570985008687907853269984665640564039457_000000000000000000; + UD60x18 constant MAX_WHOLE_UD60x18 = UD60x18.wrap(uMAX_WHOLE_UD60x18); + + /// @dev PI as an UD60x18 number. + UD60x18 constant PI = UD60x18.wrap(3_141592653589793238); + + /// @dev The unit amount that implies how many trailing decimals can be represented. + uint256 constant uUNIT = 1e18; + UD60x18 constant UNIT = UD60x18.wrap(uUNIT); + + /// @dev Zero as an UD60x18 number. + UD60x18 constant ZERO = UD60x18.wrap(0); + + UD60x18 internal constant MAX_PERMITTED_EXP2 = UD60x18.wrap(192e18 - 1); + UD60x18 internal constant MAX_PERMITTED_EXP = UD60x18.wrap(133_084258667509499440); + UD60x18 internal constant MAX_PERMITTED_EXPONENT_POW = UD60x18.wrap(2 ** 192 * 10 ** 18 - 1); + UD60x18 internal constant MAX_PERMITTED_SQRT = UD60x18.wrap(115792089237316195423570985008687907853269_984665640564039457); + + + + /* ================================================================ + Events used for debugging or showing information. + ================================================================ */ + event Value(string reason, int256 val); + event LogErr(bytes error); + + + /* ================================================================ + Helper functions. + ================================================================ */ + + // @audit not checking for overflows + // These functions allows to compare a and b for equality, discarding + // the last precision_bits bits. + // Uses functions from the library under test! + function equal_within_precision(UD60x18 a, UD60x18 b, uint256 precision_bits) public pure returns(bool) { + UD60x18 max = gt(a , b) ? a : b; + UD60x18 min = gt(a , b) ? b : a; + UD60x18 r = rshift(sub(max, min), precision_bits); + + return (eq(r, convert(0))); + } + + // @audit not checking for overflows + function equal_within_precision_u(uint256 a, uint256 b, uint256 precision_bits) public pure returns(bool) { + uint256 max = (a > b) ? a : b; + uint256 min = (a > b) ? b : a; + uint256 r = (max - min) >> precision_bits; + + return (r == 0); + } + + // This function determines if the relative error between a and b is less + // than error_percent % (expressed as a 59x18 value) + // Uses functions from the library under test! + function equal_within_tolerance(UD60x18 a, UD60x18 b, UD60x18 error_percent) public pure returns(bool) { + UD60x18 tol_value = mul(a, div(error_percent, convert(100))); + + return (lte(sub(b, a), tol_value)); + } + + // @audit precision can never be negative + // Check that there are remaining significant digits after a multiplication + // Uses functions from the library under test! + function significant_digits_lost_in_mult(UD60x18 a, UD60x18 b) public pure returns (bool) { + uint256 la = convert(floor(log10(a))); + uint256 lb = convert(floor(log10(b))); + + return(la + lb < 18); + } + + // @audit precision can never be negative + // Return how many significant bits will remain after multiplying a and b + // Uses functions from the library under test! + function significant_digits_after_mult(UD60x18 a, UD60x18 b) public pure returns (uint256) { + uint256 la = convert(floor(log10(a))); + uint256 lb = convert(floor(log10(b))); + uint256 prec = la + lb; + + if (prec < 18) return 0; + else return(18 + uint256(prec)); + } + + // Returns true if the n most significant bits of a and b are almost equal + // Uses functions from the library under test! + function equal_most_significant_digits_within_precision(UD60x18 a, UD60x18 b, uint256 bits) public view returns (bool) { + // Get the number of bits in a and b + // Since log(x) returns in the interval [-64, 63), add 64 to be in the interval [0, 127) + uint256 a_bits = uint256(int256(convert(log2(a)))); + uint256 b_bits = uint256(int256(convert(log2(b)))); + + // a and b lengths may differ in 1 bit, so the shift should take into account the longest + uint256 shift_bits = (a_bits > b_bits) ? (a_bits - bits) : (b_bits - bits); + + // Get the _bits_ most significant bits of a and b + uint256 a_msb = most_significant_bits(a, bits) >> shift_bits; + uint256 b_msb = most_significant_bits(b, bits) >> shift_bits; + + // See if they are equal within 1 bit precision + // This could be modified to get the precision as a parameter to the function + return equal_within_precision_u(a_msb, b_msb, 1); + } + + // Return the i most significant bits from |n|. If n has less than i significant bits, return |n| + // Uses functions from the library under test! + function most_significant_bits( + UD60x18 n, + uint256 i + ) public view returns (uint256) { + if (n.eq(ZERO_FP)) return 0; + + // Create a mask consisting of i bits set to 1 + uint256 mask = (2 ** i) - 1; + + // Get the positive value of n + uint256 value = intoUint256(n); + + // Get the position of the MSB set to 1 of n + uint256 pos = msb(value); + + // Shift the mask to match the rightmost 1-set bit + if (pos > i) { + mask <<= (pos - i); + } + + return (value & mask); + } + + /* function compute_max_log_error(UD60x18 x) public view returns (UD60x18 result) { + int256 xInt = UD60x18.unwrap(x); + + unchecked { + // This works because of: + // + // $$ + // log_2{x} = -log_2{\frac{1}{x}} + // $$ + int256 sign; + if (xInt >= uUNIT) { + sign = 1; + } else { + sign = -1; + // Do the fixed-point inversion inline to save gas. The numerator is UNIT * UNIT. + xInt = 1e36 / xInt; + } + + // Calculate the integer part of the logarithm and add it to the result and finally calculate $y = x * 2^(-n)$. + uint256 n = msb(uint256(xInt / uUNIT)); + + // This is $y = x * 2^{-n}$. + int256 y = xInt >> n; + + // If y is 1, the fractional part is zero. + if (y == uUNIT) { + return 0; + } + + // Calculate the fractional part via the iterative approximation. + // The "delta >>= 1" part is equivalent to "delta /= 2", but shifting bits is faster. + int256 DOUBLE_UNIT = 2e18; + int256 sum; + for (int256 delta = uHALF_UNIT; delta > 0; delta >>= 1) { + y = (y * y) / uUNIT; + + // Is $y^2 > 2$ and so in the range [2,4)? + if (y >= DOUBLE_UNIT) { + // Add the 2^{-m} factor to the logarithm. + sum += delta; + + // Corresponds to z/2 on Wikipedia. + y >>= 1; + } + } + + int256 maxError = 2 ** (-sum); + result = convert(maxError); + } + } */ + + /* ================================================================ + Library wrappers. + These functions allow calling the PRBMathUD60x18 library. + ================================================================ */ + function debug(string calldata x, int256 y) public { + emit Value(x, y); + } + + // Wrapper for external try/catch calls + function helpersAdd(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { + return add(x,y); + } + + // Wrapper for external try/catch calls + function helpersSub(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { + return sub(x,y); + } + + // Wrapper for external try/catch calls + function helpersMul(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { + return mul(x,y); + } + + function helpersDiv(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { + return div(x,y); + } + + function helpersLn(UD60x18 x) public pure returns (UD60x18) { + return ln(x); + } + + function helpersExp(UD60x18 x) public pure returns (UD60x18) { + return exp(x); + } + + function helpersExp2(UD60x18 x) public pure returns (UD60x18) { + return exp2(x); + } + + function helpersLog2(UD60x18 x) public pure returns (UD60x18) { + return log2(x); + } + + function helpersSqrt(UD60x18 x) public pure returns (UD60x18) { + return sqrt(x); + } + + function helpersPow(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { + return pow(x, y); + } + + function helpersPowu(UD60x18 x, uint256 y) public pure returns (UD60x18) { + return powu(x, y); + } + + function helpersAvg(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { + return avg(x, y); + } + + function helpersInv(UD60x18 x) public pure returns (UD60x18) { + return inv(x); + } + + function helpersLog10(UD60x18 x) public pure returns (UD60x18) { + return log10(x); + } + + function helpersFloor(UD60x18 x) public pure returns (UD60x18) { + return floor(x); + } + + /* ================================================================ + + TESTS FOR FUNCTION add() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // @audit-ok + // Test for commutative property + // x + y == y + x + function add_test_commutative(UD60x18 x, UD60x18 y) public pure { + UD60x18 x_y = x.add(y); + UD60x18 y_x = y.add(x); + + assert(x_y.eq(y_x)); + } + + // @audit-ok + // Test for associative property + // (x + y) + z == x + (y + z) + function add_test_associative(UD60x18 x, UD60x18 y, UD60x18 z) public pure { + UD60x18 x_y = x.add(y); + UD60x18 y_z = y.add(z); + UD60x18 xy_z = x_y.add(z); + UD60x18 x_yz = x.add(y_z); + + assert(xy_z.eq(x_yz)); + } + + // @audit-ok + // Test for identity operation + // x + 0 == x (equivalent to x + (-x) == 0) + function add_test_identity(UD60x18 x) public view { + UD60x18 x_0 = x.add(ZERO_FP); + + assert(x.eq(x_0)); + assert(x.sub(x).eq(ZERO_FP)); + } + + // @audit-ok + // Test that the result increases or decreases depending + // on the value to be added + function add_test_values(UD60x18 x, UD60x18 y) public view { + UD60x18 x_y = x.add(y); + + if (y.gte(ZERO_FP)) { + assert(x_y.gte(x)); + } else { + assert(x_y.lt(x)); + } + } + + + /* ================================================================ + Tests for overflow and edge cases. + These should make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // @audit-ok + // The result of the addition must be between the maximum + // and minimum allowed values for UD60x18 + function add_test_range(UD60x18 x, UD60x18 y) public view { + try this.helpersAdd(x, y) returns (UD60x18 result) { + assert(result.lte(MAX_UD60x18) && result.gte(ZERO_FP)); + } catch { + // If it reverts, just ignore + } + } + + // @audit-ok + // Adding zero to the maximum value shouldn't revert, as it is valid + // Moreover, the result must be MAX_UD60x18 + function add_test_maximum_value() public view { + try this.helpersAdd(MAX_UD60x18, ZERO_FP) returns (UD60x18 result) { + // Expected behaviour, does not revert + assert(result.eq(MAX_UD60x18)); + } catch { + assert(false); + } + } + + // @audit-ok + // Adding one to the maximum value should revert, as it is out of range + function add_test_maximum_value_plus_one() public view { + try this.helpersAdd(MAX_UD60x18, ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + // @audit-ok + // Adding zero to the minimum value shouldn't revert, as it is valid + // Moreover, the result must be MIN_UD60x18 + function add_test_minimum_value() public view { + try this.helpersAdd(ZERO_FP, ZERO_FP) returns (UD60x18 result) { + // Expected behaviour, does not revert + assert(result.eq(ZERO_FP)); + } catch { + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION sub() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // @audit-ok + // Test for identity operation + // x - 0 == x (equivalent to x - x == 0) + function sub_test_identity(UD60x18 x) public view { + UD60x18 x_0 = x.sub(ZERO_FP); + + assert(x_0.eq(x)); + assert(x.sub(x).eq(ZERO_FP)); + } + + // @audit-ok + // Test for neutrality over addition and subtraction + // (x - y) + y == (x + y) - y == x + function sub_test_neutrality(UD60x18 x, UD60x18 y) public pure { + UD60x18 x_minus_y = x.sub(y); + UD60x18 x_plus_y = x.add(y); + + UD60x18 x_minus_y_plus_y = x_minus_y.add(y); + UD60x18 x_plus_y_minus_y = x_plus_y.sub(y); + + assert(x_minus_y_plus_y.eq(x_plus_y_minus_y)); + assert(x_minus_y_plus_y.eq(x)); + } + + // @audit-ok + // Test that the result always decreases + function sub_test_values(UD60x18 x, UD60x18 y) public view { + UD60x18 x_y = x.sub(y); + + assert(x_y.lte(x)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // @audit-ok + // The result of the subtraction must be between the maximum + // and minimum allowed values for UD60x18 + function sub_test_range(UD60x18 x, UD60x18 y) public view { + try this.helpersSub(x, y) returns (UD60x18 result) { + assert(result.lte(MAX_UD60x18) && result.gte(ZERO_FP)); + } catch { + // If it reverts, just ignore + } + } + + // @audit-ok + // Subtracting zero from the maximum value shouldn't revert, as it is valid + // Moreover, the result must be MAX_UD60x18 + function sub_test_maximum_value() public view { + try this.helpersSub(MAX_UD60x18, ZERO_FP) returns (UD60x18 result) { + // Expected behaviour, does not revert + assert(result.eq(MAX_UD60x18)); + } catch { + assert(false); + } + } + + // @audit-ok + // Subtracting zero from the minimum value shouldn't revert, as it is valid + // Moreover, the result must be MIN_UD60x18 + function sub_test_minimum_value() public view { + try this.helpersSub(ZERO_FP, ZERO_FP) returns (UD60x18 result) { + // Expected behaviour, does not revert + assert(result.eq(ZERO_FP)); + } catch { + assert(false); + } + } + + // @audit-ok + // Subtracting one from the minimum value should revert, as it is out of range + function sub_test_minimum_value_minus_one() public view { + try this.helpersSub(ZERO_FP, ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + + + /* ================================================================ + + TESTS FOR FUNCTION mul() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // @audit-ok + // Test for commutative property + // x * y == y * x + function mul_test_commutative(UD60x18 x, UD60x18 y) public pure { + UD60x18 x_y = x.mul(y); + UD60x18 y_x = y.mul(x); + + assert(x_y.eq(y_x)); + } + + // @audit - No guarantee of precision + // Test for associative property + // (x * y) * z == x * (y * z) + function mul_test_associative(UD60x18 x, UD60x18 y, UD60x18 z) public view { + UD60x18 x_y = x.mul(y); + UD60x18 y_z = y.mul(z); + UD60x18 xy_z = x_y.mul(z); + UD60x18 x_yz = x.mul(y_z); + + // todo check if this should not be used + // Failure if all significant digits are lost + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + require(significant_digits_after_mult(y, z) > REQUIRED_SIGNIFICANT_DIGITS); + require(significant_digits_after_mult(x_y, z) > REQUIRED_SIGNIFICANT_DIGITS); + require(significant_digits_after_mult(x, y_z) > REQUIRED_SIGNIFICANT_DIGITS); + + assert(equal_within_tolerance(xy_z, x_yz, ONE_TENTH_FP)); + //assert(xy_z.eq(x_yz)); + } + + // @audit - No guarantee of precision + // Test for distributive property + // x * (y + z) == x * y + x * z + function mul_test_distributive(UD60x18 x, UD60x18 y, UD60x18 z) public view { + UD60x18 y_plus_z = y.add(z); + UD60x18 x_times_y_plus_z = x.mul(y_plus_z); + + UD60x18 x_times_y = x.mul(y); + UD60x18 x_times_z = x.mul(z); + + // todo check if this should not be used + // Failure if all significant digits are lost + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + require(significant_digits_after_mult(x, z) > REQUIRED_SIGNIFICANT_DIGITS); + require(significant_digits_after_mult(x, y_plus_z) > REQUIRED_SIGNIFICANT_DIGITS); + + assert(equal_within_tolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP)); + //assert(x_times_y.add(x_times_z).eq(x_times_y_plus_z)); + } + + // @audit-ok + // Test for identity operation + // x * 1 == x (also check that x * 0 == 0) + function mul_test_identity(UD60x18 x) public view { + UD60x18 x_1 = x.mul(ONE_FP); + UD60x18 x_0 = x.mul(ZERO_FP); + + assert(x_0.eq(ZERO_FP)); + assert(x_1.eq(x)); + } + + // @audit - No guarantee of precision + // Test that the result increases or decreases depending + // on the value to be added + function mul_test_values(UD60x18 x, UD60x18 y) public view { + UD60x18 x_y = x.mul(y); + + //require(significant_digits_lost_in_mult(x, y) == false); + + if (y.gte(ONE_FP)) { + assert(x_y.gte(x)); + } else { + assert(x_y.lte(x)); + } + } + + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // @audit-ok + // The result of the multiplication must be between the maximum + // and minimum allowed values for UD60x18 + function mul_test_range(UD60x18 x, UD60x18 y) public view { + try this.helpersMul(x, y) returns(UD60x18 result) { + assert(result.lte(MAX_UD60x18)); + } catch { + // If it reverts, just ignore + } + } + + // @audit-ok + // Multiplying the maximum value times one shouldn't revert, as it is valid + // Moreover, the result must be MAX_UD60x18 + function mul_test_maximum_value() public view { + try this.helpersMul(MAX_UD60x18, ONE_FP) returns (UD60x18 result) { + // Expected behaviour, does not revert + assert(result.eq(MAX_UD60x18)); + } catch { + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION div() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // @audit-ok + // Test for identity property + // x / 1 == x (equivalent to x / x == 1) + // Moreover, x/x should not revert unless x == 0 + function div_test_division_identity(UD60x18 x) public view { + UD60x18 div_1 = div(x, ONE_FP); + assert(x.eq(div_1)); + + UD60x18 div_x; + + try this.helpersDiv(x, x) { + // This should always equal one + div_x = div(x, x); + assert(div_x.eq(ONE_FP)); + } catch { + // Only valid case for revert is x == 0 + assert(x.eq(ZERO_FP)); + } + } + + // @audit-ok + // Test for division with 0 as numerator + // 0 / x = 0 + function div_test_division_num_zero(UD60x18 x) public view { + require(x.neq(ZERO_FP)); + + UD60x18 div_0 = div(ZERO_FP, x); + + assert(ZERO_FP.eq(div_0)); + } + + // @audit-ok + // Test that the value of the result increases or + // decreases depending on the denominator's value + function div_test_values(UD60x18 x, UD60x18 y) public view { + require(y.neq(ZERO_FP)); + + UD60x18 x_y = div(x, y); + + if (y.gte(ONE_FP)) { + assert(x_y.lte(x)); + } else { + assert(x_y.gte(x)); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // @audit-ok + // Test for division by zero + function div_test_div_by_zero(UD60x18 x) public view { + try this.helpersDiv(x, ZERO_FP) { + // Unexpected, this should revert + assert(false); + } catch { + // Expected revert + } + } + + // @audit-ok + // Test for division by a large value, the result should be less than one + function div_test_maximum_denominator(UD60x18 x) public view { + UD60x18 div_large = div(x, MAX_UD60x18); + + assert(div_large.lte(ONE_FP)); + } + + // @audit-ok + // Test for division of a large value + // This should revert if |x| < 1 as it would return a value higher than max + function div_test_maximum_numerator(UD60x18 x) public view { + UD60x18 div_large; + + try this.helpersDiv(MAX_UD60x18, x) { + // If it didn't revert, then |x| >= 1 + div_large = div(MAX_UD60x18, x); + + assert(x.gte(ONE_FP)); + } catch { + // Expected revert as result is higher than max + } + } + + // @audit-ok + // Test for values in range + function div_test_range(UD60x18 x, UD60x18 y) public view { + UD60x18 result; + + try this.helpersDiv(x, y) { + // If it returns a value, it must be in range + result = div(x, y); + assert(result.lte(MAX_UD60x18)); + } catch { + // Otherwise, it should revert + } + } + + /* ================================================================ + + TESTS FOR FUNCTION inv() + + ================================================================ */ + + /* ================================================================ + Tests for mathematical properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // @audit-ok + // Test that the inverse of the inverse is close enough to the + // original number + function inv_test_double_inverse(UD60x18 x) public view { + require(x.neq(ZERO_FP)); + + UD60x18 double_inv_x = inv(inv(x)); + + // The maximum loss of precision will be 2 * log2(x) bits rounded up + uint256 loss = 2 * intoUint256(log2(x)) + 2; + + assert(equal_within_precision(x, double_inv_x, loss)); + } + + // @audit-ok + // Test equivalence with division + function inv_test_division(UD60x18 x) public view { + require(x.neq(ZERO_FP)); + + UD60x18 inv_x = inv(x); + UD60x18 div_1_x = div(ONE_FP, x); + + assert(inv_x.eq(div_1_x)); + } + + // @audit check if loss is correct + // Test the anticommutativity of the division + // x / y == 1 / (y / x) + function inv_test_division_noncommutativity( + UD60x18 x, + UD60x18 y + ) public view { + require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); + + UD60x18 x_y = div(x, y); + UD60x18 y_x = div(y, x); + + require( + significant_digits_after_mult(x, inv(y)) > REQUIRED_SIGNIFICANT_DIGITS + ); + require( + significant_digits_after_mult(y, inv(x)) > REQUIRED_SIGNIFICANT_DIGITS + ); + assert(equal_within_tolerance(x_y, inv(y_x), ONE_TENTH_FP)); + } + + // @audit check if loss is correct + // Test the multiplication of inverses + // 1/(x * y) == 1/x * 1/y + function inv_test_multiplication(UD60x18 x, UD60x18 y) public view { + require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); + + UD60x18 inv_x = inv(x); + UD60x18 inv_y = inv(y); + UD60x18 inv_x_times_inv_y = mul(inv_x, inv_y); + + UD60x18 x_y = mul(x, y); + UD60x18 inv_x_y = inv(x_y); + + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + require( + significant_digits_after_mult(inv_x, inv_y) > REQUIRED_SIGNIFICANT_DIGITS + ); + + // The maximum loss of precision is given by the formula: + // 2 * | log2(x) - log2(y) | + 1 + uint256 loss = 2 * intoUint256(log2(x).sub(log2(y))) + 1; + + assert(equal_within_precision(inv_x_y, inv_x_times_inv_y, loss)); + } + + // @audit-ok + // Test identity property + // Intermediate result should have at least REQUIRED_SIGNIFICANT_DIGITS + function inv_test_identity(UD60x18 x) public view { + require(x.neq(ZERO_FP)); + + UD60x18 inv_x = inv(x); + UD60x18 identity = mul(inv_x, x); + + require( + significant_digits_after_mult(x, inv_x) > REQUIRED_SIGNIFICANT_DIGITS + ); + + // They should agree with a tolerance of one tenth of a percent + assert(equal_within_tolerance(identity, ONE_FP, ONE_TENTH_FP)); + } + + // @audit-ok + // Test that the value of the result is in range zero-one + // if x is greater than one, else, the value of the result + // must be greater than one + function inv_test_values(UD60x18 x) public view { + require(x.neq(ZERO_FP)); + + UD60x18 inv_x = inv(x); + + if (x.gte(ONE_FP)) { + assert(inv_x.lte(ONE_FP)); + } else { + assert(inv_x.gt(ONE_FP)); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // @audit-ok + // Test the zero-case, should revert + function inv_test_zero() public view { + try this.helpersInv(ZERO_FP) { + // Unexpected, the function must revert + assert(false); + } catch {} + } + + // @audit-ok + // Test the maximum value case, should not revert, and be close to zero + function inv_test_maximum() public view { + UD60x18 inv_maximum; + + try this.helpersInv(MAX_UD60x18) { + inv_maximum = this.helpersInv(MAX_UD60x18); + assert(equal_within_precision(inv_maximum, ZERO_FP, 10)); + } catch { + // Unexpected, the function must not revert + assert(false); + } + } + + // @audit Check precision + // Test the minimum value case, should not revert, and be close to zero + function inv_test_minimum() public view { + UD60x18 inv_minimum; + + try this.helpersInv(UD60x18.wrap(1)) { + inv_minimum = this.helpersInv(UD60x18.wrap(1)); + assert(equal_within_precision(inv_minimum, ZERO_FP, 10)); + } catch { + // Unexpected, the function must not revert + assert(false); + } + } + + + /* ================================================================ + + TESTS FOR FUNCTION avg() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // @audit-ok + // Test that the result is between the two operands + // avg(x, y) >= min(x, y) && avg(x, y) <= max(x, y) + function avg_test_values_in_range(UD60x18 x, UD60x18 y) public pure { + UD60x18 avg_xy = avg(x, y); + + if (x.gte(y)) { + assert(avg_xy.gte(y) && avg_xy.lte(x)); + } else { + assert(avg_xy.gte(x) && avg_xy.lte(y)); + } + } + + // @audit-ok + // Test that the average of the same number is itself + // avg(x, x) == x + function avg_test_one_value(UD60x18 x) public pure { + UD60x18 avg_x = avg(x, x); + + assert(avg_x.eq(x)); + } + + // @audit-ok + // Test that the order of operands is irrelevant + // avg(x, y) == avg(y, x) + function avg_test_operand_order(UD60x18 x, UD60x18 y) public pure { + UD60x18 avg_xy = avg(x, y); + UD60x18 avg_yx = avg(y, x); + + assert(avg_xy.eq(avg_yx)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // @audit-ok + // Test for the maximum value + function avg_test_maximum() public view { + UD60x18 result; + + // This may revert due to overflow depending on implementation + // If it doesn't revert, the result must be MAX_UD60x18 + try this.helpersAvg(MAX_UD60x18, MAX_UD60x18) { + result = this.helpersAvg(MAX_UD60x18, MAX_UD60x18); + assert(result.eq(MAX_UD60x18)); + } catch {} + } + + /* ================================================================ + + TESTS FOR FUNCTION pow() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // @audit-ok + // Test for zero exponent + // x ** 0 == 1 + function pow_test_zero_exponent(UD60x18 x) public view { + UD60x18 x_pow_0 = pow(x, ZERO_FP); + + assert(x_pow_0.eq(ONE_FP)); + } + + // @audit-ok + // Test for zero base + // 0 ** x == 0 (for positive x) + function pow_test_zero_base(UD60x18 x) public view { + require(x.neq(ZERO_FP)); + + UD60x18 zero_pow_x = pow(ZERO_FP, x); + + assert(zero_pow_x.eq(ZERO_FP)); + } + + // @audit-ok + // Test for exponent one + // x ** 1 == x + function pow_test_one_exponent(UD60x18 x) public view { + UD60x18 x_pow_1 = pow(x, ONE_FP); + + assert(x_pow_1.eq(x)); + } + + // @audit-ok + // Test for base one + // 1 ** x == 1 + function pow_test_base_one(UD60x18 x) public view { + UD60x18 one_pow_x = pow(ONE_FP, x); + + assert(one_pow_x.eq(ONE_FP)); + } + + // @audit - Fails + // Test for product of powers of the same base + // x ** a * x ** b == x ** (a + b) + function pow_test_product_same_base( + UD60x18 x, + UD60x18 a, + UD60x18 b + ) public view { + require(x.neq(ZERO_FP)); + + UD60x18 x_a = pow(x, a); + UD60x18 x_b = pow(x, b); + UD60x18 x_ab = pow(x, a.add(b)); + + assert(equal_within_precision(mul(x_a, x_b), x_ab, 10)); + } + + // @audit - fails + // Test for power of an exponentiation + // (x ** a) ** b == x ** (a * b) + function pow_test_power_of_an_exponentiation( + UD60x18 x, + UD60x18 a, + UD60x18 b + ) public view { + require(x.neq(ZERO_FP)); + + UD60x18 x_a = pow(x, a); + UD60x18 x_a_b = pow(x_a, b); + UD60x18 x_ab = pow(x, a.mul(b)); + + assert(equal_within_precision(x_a_b, x_ab, 10)); + } + + // @audit check precision + // Test for power of a product + // (x * y) ** a == x ** a * y ** a + function pow_test_product_power( + UD60x18 x, + UD60x18 y, + UD60x18 a + ) public view { + require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); + // todo this should probably be changed + require(a.gt(convert(2 ** 32))); // to avoid massive loss of precision + + UD60x18 x_y = mul(x, y); + UD60x18 xy_a = pow(x_y, a); + + UD60x18 x_a = pow(x, a); + UD60x18 y_a = pow(y, a); + + assert(equal_within_precision(mul(x_a, y_a), xy_a, 10)); + } + + // @audit - fails + // Test for result being greater than or lower than the argument, depending on + // its value and the value of the exponent + function pow_test_values(UD60x18 x, UD60x18 a) public view { + require(x.neq(ZERO_FP)); + + UD60x18 x_a = pow(x, a); + + if (x.gte(ONE_FP)) { + assert(x_a.gte(ONE_FP)); + } + + if (x.lte(ONE_FP)) { + assert(x_a.lte(ONE_FP)); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // @audit-ok + // Test for maximum base and exponent > 1 + function pow_test_maximum_base(UD60x18 a) public view { + require(a.gt(ONE_FP)); + + try this.helpersPow(MAX_UD60x18, a) { + // Unexpected, should revert because of overflow + assert(false); + } catch { + // Expected revert + } + } + + // Test for maximum exponent and base > 1 + function pow_test_maximum_exponent(UD60x18 x) public view { + require(x.gt(ONE_FP)); + + try this.helpersPow(x, MAX_PERMITTED_EXPONENT_POW) { + // Unexpected, should revert because of overflow + assert(false); + } catch { + // Expected revert + } + } + + // @audit Check 2**18 + // Test for base < 1 and high exponent + function pow_test_high_exponent(UD60x18 x, UD60x18 a) public view { + require(x.lt(ONE_FP) && a.gt(convert(2 ** 64))); + + UD60x18 result = pow(x, a); + + assert(result.eq(ZERO_FP)); + } + + /* ================================================================ + + TESTS FOR FUNCTION sqrt() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // @audit-ok + // Test for the inverse operation + // sqrt(x) * sqrt(x) == x + function sqrt_test_inverse_mul(UD60x18 x) public view { + UD60x18 sqrt_x = sqrt(x); + UD60x18 sqrt_x_squared = mul(sqrt_x, sqrt_x); + + // Precision loss is at most half the bits of the operand + assert( + equal_within_precision( + sqrt_x_squared, + x, + (intoUint256(log2(x)) >> 1) + 2 + ) + ); + } + + // @audit-ok + // Test for the inverse operation + // sqrt(x) ** 2 == x + function sqrt_test_inverse_pow(UD60x18 x) public view { + UD60x18 sqrt_x = sqrt(x); + UD60x18 sqrt_x_squared = pow(sqrt_x, convert(2)); + + // Precision loss is at most half the bits of the operand + assert( + equal_within_precision( + sqrt_x_squared, + x, + (intoUint256(log2(x)) >> 1) + 2 + ) + ); + } + + // @audit check the precision + // Test for distributive property respect to the multiplication + // sqrt(x) * sqrt(y) == sqrt(x * y) + function sqrt_test_distributive(UD60x18 x, UD60x18 y) public view { + UD60x18 sqrt_x = sqrt(x); + UD60x18 sqrt_y = sqrt(y); + UD60x18 sqrt_x_sqrt_y = mul(sqrt_x, sqrt_y); + UD60x18 sqrt_xy = sqrt(mul(x, y)); + + // Ensure we have enough significant digits for the result to be meaningful + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + require( + significant_digits_after_mult(sqrt_x, sqrt_y) > + REQUIRED_SIGNIFICANT_DIGITS + ); + + // Allow an error of up to one tenth of a percent + assert(equal_within_tolerance(sqrt_x_sqrt_y, sqrt_xy, ONE_TENTH_FP)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // @audit-ok + // Test for zero case + function sqrt_test_zero() public view { + assert(sqrt(ZERO_FP).eq(ZERO_FP)); + } + + // @audit-ok + // Test for maximum value + function sqrt_test_maximum() public view { + try this.helpersSqrt(MAX_PERMITTED_SQRT) { + // Expected behaviour, MAX_SQRT is positive, and operation + // should not revert as the result is in range + } catch { + // Unexpected, should not revert + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION log2() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // @audit check loss + // Test for distributive property respect to multiplication + // log2(x * y) = log2(x) + log2(y) + function log2_test_distributive_mul(UD60x18 x, UD60x18 y) public view { + require(x.gte(UNIT) && y.gte(UNIT)); + + UD60x18 log2_x = log2(x); + UD60x18 log2_y = log2(y); + UD60x18 log2_x_log2_y = add(log2_x, log2_y); + + UD60x18 xy = mul(x, y); + UD60x18 log2_xy = log2(xy); + + // Ensure we have enough significant digits for the result to be meaningful + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + + // The maximum loss of precision is given by the formula: + // | log2(x) + log2(y) | + uint256 loss = intoUint256(log2(x).add(log2(y))); + + assert(equal_within_precision(log2_x_log2_y, log2_xy, loss)); + } + + // @audit - fails + // Test for logarithm of a power + // log2(x ** y) = y * log2(x) + function log2_test_power(UD60x18 x, UD60x18 y) public view { + require(x.gte(UNIT) && y.gte(UNIT)); + + UD60x18 x_y = pow(x, y); + UD60x18 log2_x_y = log2(x_y); + UD60x18 y_log2_x = mul(log2(x), y); + + assert(equal_within_precision(y_log2_x, log2_x_y, 4)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // @audit-ok + // Test for zero case, should revert + function log2_test_zero() public view { + try this.helpersLog2(ZERO_FP) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, log(0) is not defined + } + } + + // @audit-ok + // Test for maximum value case, should return a positive number + function log2_test_maximum() public view { + UD60x18 result; + + try this.helpersLog2(MAX_UD60x18) { + // Expected, should not revert and the result must be > 0 + result = this.helpersLog2(MAX_UD60x18); + assert(result.gt(ZERO_FP)); + } catch { + // Unexpected + assert(false); + } + } + + // @audit-ok + // Test for values less than UNIT, should revert as result would be negative + function log2_test_less_than_unit(UD60x18 x) public view { + require(x.lt(UNIT)); + + try this.helpersLog2(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected + } + } + + /* ================================================================ + + TESTS FOR FUNCTION ln() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // @audit check precision + // Test for distributive property respect to multiplication + // ln(x * y) = ln(x) + ln(y) + function ln_test_distributive_mul(UD60x18 x, UD60x18 y) public view { + require(x.gte(UNIT) && y.gte(UNIT)); + + UD60x18 ln_x = ln(x); + UD60x18 ln_y = ln(y); + UD60x18 ln_x_ln_y = add(ln_x, ln_y); + + UD60x18 xy = mul(x, y); + UD60x18 ln_xy = ln(xy); + + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + + // The maximum loss of precision is given by the formula: + // | log2(x) + log2(y) | + uint256 loss = intoUint256(log2(x).add(log2(y))); + + assert(equal_within_precision(ln_x_ln_y, ln_xy, loss)); + } + + // @audit check precision + // Test for logarithm of a power + // ln(x ** y) = y * ln(x) + function ln_test_power(UD60x18 x, UD60x18 y) public { + require(x.gte(UNIT)); + UD60x18 x_y = pow(x, y); + UD60x18 ln_x_y = ln(x_y); + UD60x18 y_ln_x = mul(ln(x), y); + + assert(equal_within_precision(ln_x_y, y_ln_x, 4)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // @audit-ok + // Test for zero case, should revert + function ln_test_zero() public view { + try this.helpersLn(ZERO_FP) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, log(0) is not defined + } + } + + // @audit-ok + // Test for maximum value case, should return a positive number + function ln_test_maximum() public view { + UD60x18 result; + + try this.helpersLn(MAX_UD60x18) { + // Expected, should not revert and the result must be > 0 + result = this.helpersLn(MAX_UD60x18); + assert(result.gt(ZERO_FP)); + } catch { + // Unexpected + assert(false); + } + } + + // @audit-ok + // Test for values less than UNIT, should revert since result would be negative + function ln_test_less_than_unit(UD60x18 x) public view { + require(x.lt(UNIT)); + + try this.helpersLn(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected + } + } + + /* ================================================================ + + TESTS FOR FUNCTION exp2() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // @audit-ok + // Test for equality with pow(2, x) for integer x + // pow(2, x) == exp2(x) + function exp2_test_equivalence_pow(UD60x18 x) public view { + require(x.lte(convert(192))); + UD60x18 exp2_x = exp2(x); + UD60x18 pow_2_x = pow(TWO_FP, x); + + assert(exp2_x.eq(pow_2_x)); + } + + // @audit - fails + // Test for inverse function + // If y = log2(x) then exp2(y) == x + function exp2_test_inverse(UD60x18 x) public view { + UD60x18 log2_x = log2(x); + UD60x18 exp2_x = exp2(log2_x); + + // todo is this the correct number of bits? + uint256 bits = 18; + + assert(equal_most_significant_digits_within_precision(x, exp2_x, bits)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // @audit-ok + // Test for zero case + // exp2(0) == 1 + function exp2_test_zero() public view { + UD60x18 exp_zero = exp2(ZERO_FP); + assert(exp_zero.eq(ONE_FP)); + } + + // @audit-ok + // Test for maximum value. This should overflow as it won't fit + // in the data type + function exp2_test_maximum() public view { + try this.helpersExp2(convert(192)) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert + } + } + + /* ================================================================ + + TESTS FOR FUNCTION exp() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // @audit - fails + // Test for inverse function + // If y = ln(x) then exp(y) == x + function exp_test_inverse(UD60x18 x) public view { + UD60x18 ln_x = ln(x); + UD60x18 exp_x = exp(ln_x); + UD60x18 log2_x = log2(x); + + uint256 bits = 18; + + assert(equal_most_significant_digits_within_precision(x, exp_x, bits)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // @audit-ok + // Test for zero case + // exp(0) == 1 + function exp_test_zero() public view { + UD60x18 exp_zero = exp(ZERO_FP); + assert(exp_zero.eq(ONE_FP)); + } + + // @audit-ok + // Test for maximum value. This should overflow as it won't fit + // in the data type + function exp_test_maximum() public view { + try this.helpersExp(convert(192)) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert + } + } + + /* ================================================================ + + TESTS FOR FUNCTION powu() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // @audit-ok + // Test for zero exponent + // x ** 0 == 1 + function powu_test_zero_exponent(UD60x18 x) public view { + UD60x18 x_pow_0 = powu(x, 0); + + assert(x_pow_0.eq(ONE_FP)); + } + + // @audit-ok + // Test for zero base + // 0 ** x == 0 + function powu_test_zero_base(uint256 a) public view { + require(a != 0); + + UD60x18 zero_pow_a = powu(ZERO_FP, a); + + assert(zero_pow_a.eq(ZERO_FP)); + } + + // @audit-ok + // Test for exponent one + // x ** 1 == x + function powu_test_one_exponent(UD60x18 x) public view { + UD60x18 x_pow_1 = powu(x, 1); + + assert(x_pow_1.eq(x)); + } + + // @audit-ok + // Test for base one + // 1 ** x == 1 + function powu_test_base_one(uint256 a) public view { + UD60x18 one_pow_a = powu(ONE_FP, a); + + assert(one_pow_a.eq(ONE_FP)); + } + + // @audit - Fails + // Test for product of powers of the same base + // x ** a * x ** b == x ** (a + b) + function powu_test_product_same_base( + UD60x18 x, + uint256 a, + uint256 b + ) public view { + require(x.neq(ZERO_FP)); + + UD60x18 x_a = powu(x, a); + UD60x18 x_b = powu(x, b); + UD60x18 x_ab = powu(x, a + b); + + assert(equal_within_precision(mul(x_a, x_b), x_ab, 10)); + } + + // @audit - fails + // Test for power of an exponentiation + // (x ** a) ** b == x ** (a * b) + function powu_test_power_of_an_exponentiation( + UD60x18 x, + uint256 a, + uint256 b + ) public view { + require(x.neq(ZERO_FP)); + + UD60x18 x_a = powu(x, a); + UD60x18 x_a_b = powu(x_a, b); + UD60x18 x_ab = powu(x, a * b); + + assert(equal_within_precision(x_a_b, x_ab, 10)); + } + + // @audit check precision + // Test for power of a product + // (x * y) ** a == x ** a * y ** a + function powu_test_product_power( + UD60x18 x, + UD60x18 y, + uint256 a + ) public view { + require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); + // todo this should probably be changed + require(a > 2 ** 32); // to avoid massive loss of precision + + UD60x18 x_y = mul(x, y); + UD60x18 xy_a = powu(x_y, a); + + UD60x18 x_a = powu(x, a); + UD60x18 y_a = powu(y, a); + + assert(equal_within_precision(mul(x_a, y_a), xy_a, 10)); + } + + // @audit - fails + // Test for result being greater than or lower than the argument, depending on + // its value and the value of the exponent + function powu_test_values(UD60x18 x, uint256 a) public view { + require(x.neq(ZERO_FP)); + + UD60x18 x_a = powu(x, a); + + if (x.gte(ONE_FP)) { + assert(x_a.gte(ONE_FP)); + } + + if (x.lte(ONE_FP)) { + assert(x_a.lte(ONE_FP)); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + + // @audit-ok + // Test for maximum base and exponent > 1 + function powu_test_maximum_base(uint256 a) public view { + require(a > 1); + + try this.helpersPowu(MAX_UD60x18, a) { + // Unexpected, should revert because of overflow + assert(false); + } catch { + // Expected revert + } + } + + // @audit Check 2**64 + // Test for base < 1 and high exponent + function powu_test_high_exponent(UD60x18 x, uint256 a) public view { + require(x.lt(ONE_FP) && a > 2 ** 64); + + UD60x18 result = powu(x, a); + + assert(result.eq(ZERO_FP)); + } + + /* ================================================================ + + TESTS FOR FUNCTION log10() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // @audit check loss + // Test for distributive property respect to multiplication + // log10(x * y) = log10(x) + log10(y) + function log10_test_distributive_mul(UD60x18 x, UD60x18 y) public view { + require(x.gte(UNIT) && y.gte(UNIT)); + UD60x18 log10_x = log10(x); + UD60x18 log10_y = log10(y); + UD60x18 log10_x_log10_y = add(log10_x, log10_y); + + UD60x18 xy = mul(x, y); + UD60x18 log10_xy = log10(xy); + + // Ensure we have enough significant digits for the result to be meaningful + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + + // The maximum loss of precision is given by the formula: + // | log10(x) + log10(y) | + uint256 loss = intoUint256(log10(x).add(log10(y))); + + assert(equal_within_precision(log10_x_log10_y, log10_xy, loss)); + } + + // @audit - fails + // Test for logarithm of a power + // log10(x ** y) = y * log10(x) + function log10_test_power(UD60x18 x, UD60x18 y) public { + require(x.gte(UNIT) && y.gte(UNIT)); + UD60x18 x_y = pow(x, y); + UD60x18 log10_x_y = log10(x_y); + UD60x18 y_log10_x = mul(log10(x), y); + + if(!equal_within_precision(log10_x_y, y_log10_x, 18)){ + emit AssertionFailed(log10_x_y, y_log10_x); + } + + assert(equal_within_precision(log10_x_y, y_log10_x, 10)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // @audit-ok + // Test for zero case, should revert + function log10_test_zero() public view { + try this.helpersLog10(ZERO_FP) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, log(0) is not defined + } + } + + // @audit-ok + // Test for maximum value case, should return a positive number + function log10_test_maximum() public view { + UD60x18 result; + + try this.helpersLog10(MAX_UD60x18) { + // Expected, should not revert and the result must be > 0 + result = this.helpersLog10(MAX_UD60x18); + assert(result.gt(ZERO_FP)); + } catch { + // Unexpected + assert(false); + } + } + + // @audit-ok + // Test for values less than UNIT, should revert as result would be negative + function log10_test_less_than_unit(UD60x18 x) public view { + require(x.lt(UNIT)); + + try this.helpersLog10(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected + } + } + +} \ No newline at end of file diff --git a/contracts/Math/PRBMath/echidna-config.yaml b/contracts/Math/echidna-config.yaml similarity index 100% rename from contracts/Math/PRBMath/echidna-config.yaml rename to contracts/Math/echidna-config.yaml diff --git a/package.json b/package.json index 7f5d8e8..aeec3de 100644 --- a/package.json +++ b/package.json @@ -1,36 +1,39 @@ { - "name": "@crytic/properties", - "version": "0.0.1", - "description": "Pre-made invariants for fuzz testing smart-contracts", - "main": "index.js", - "scripts": { - "compile": "hardhat compile", - "test": "echo \"Error: no test specified\" && exit 1", - "format": "prettier --write . && npm run format-embedded-solidity", - "format-embedded-solidity": "prettier --write \"**/*.md\" --embedded-language-formatting=auto --plugin prettier-plugin-solidity --tab-width 4 --print-width 120 && prettier --write \"**/*.md\"", - "lint": "npm run lint-check-format && npm run lint-check-links", - "lint-check-format": "prettier --check .", - "lint-check-links": "find . -name '*.md' -not -path './node_modules/*' -print0 | xargs -0 -n1 markdown-link-check" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/crytic/properties.git" - }, - "author": "Trail of Bits", - "license": "MIT", - "bugs": { - "url": "https://github.com/crytic/properties/issues" - }, - "homepage": "https://github.com/crytic/properties#readme", - "dependencies": { - "@openzeppelin/contracts": "^4.7.3", - "@prb/math": "^4.0.0", - "markdown-link-check": "^3.11.0", - "prettier": "^2.8.7", - "prettier-plugin-solidity": "^1.1.3", - "solmate": "^6.6.1" - }, - "devDependencies": { - "hardhat": "^2.9.3" - } + "name": "@crytic/properties", + "version": "0.0.1", + "description": "Pre-made invariants for fuzz testing smart-contracts", + "main": "index.js", + "scripts": { + "compile": "hardhat compile", + "test": "echo \"Error: no test specified\" && exit 1", + "format": "prettier --write . && npm run format-embedded-solidity", + "format-embedded-solidity": "prettier --write \"**/*.md\" --embedded-language-formatting=auto --plugin prettier-plugin-solidity --tab-width 4 --print-width 120 && prettier --write \"**/*.md\"", + "lint": "npm run lint-check-format && npm run lint-check-links", + "lint-check-format": "prettier --check .", + "lint-check-links": "find . -name '*.md' -not -path './node_modules/*' -print0 | xargs -0 -n1 markdown-link-check", + "echidna-prb-ud": "echidna . --contract CryticPRBMath60x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml", + "echidna-prb-sd": "echidna . --contract CryticPRBMath59x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml", + "echidna-abdk": "echidna . --contract CryticABDKMath64x64Properties --config ./contracts/Math/echidna-config.yaml" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/crytic/properties.git" + }, + "author": "Trail of Bits", + "license": "MIT", + "bugs": { + "url": "https://github.com/crytic/properties/issues" + }, + "homepage": "https://github.com/crytic/properties#readme", + "dependencies": { + "@openzeppelin/contracts": "^4.7.3", + "@prb/math": "^4.0.0", + "markdown-link-check": "^3.11.0", + "prettier": "^2.8.7", + "prettier-plugin-solidity": "^1.1.3", + "solmate": "^6.6.1" + }, + "devDependencies": { + "hardhat": "^2.9.3" + } } From 866773eb3299c89b082859d028459d16be39a28d Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Fri, 12 May 2023 19:33:06 +0200 Subject: [PATCH 12/32] added helper to calculate precision loss, modified ud60x18 --- .../v3/PRBMathSD59x18PropertyTests.sol | 259 +++--------- .../v3/PRBMathUD60x18PropertyTests.sol | 373 +++++++----------- 2 files changed, 193 insertions(+), 439 deletions(-) diff --git a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol index 3d72f35..051b6be 100644 --- a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol @@ -9,8 +9,10 @@ import {mul, div, abs, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, p contract CryticPRBMath59x18Propertiesv3 { - event AssertionFailed(SD59x18 result); - event AssertionFailed(SD59x18 result1, SD59x18 result2); + event PropertyFailed(SD59x18 result); + event PropertyFailed(SD59x18 result1, SD59x18 result2); + event PropertyFailed(SD59x18 result1, SD59x18 result2, uint256 discardedDigits); + event TestLog(int256 num1, int256 num2, int256 result); /* ================================================================ 59x18 fixed-point constants used for testing specific values. @@ -144,22 +146,19 @@ contract CryticPRBMath59x18Propertiesv3 { // Returns true if the n most significant bits of a and b are almost equal // Uses functions from the library under test! - function equal_most_significant_digits_within_precision(SD59x18 a, SD59x18 b, uint256 bits) public view returns (bool) { - // Get the number of bits in a and b - // Since log(x) returns in the interval [-64, 63), add 64 to be in the interval [0, 127) - uint256 a_bits = uint256(int256(convert(log2(a)) + 18)); - uint256 b_bits = uint256(int256(convert(log2(b)) + 18)); + function equal_most_significant_digits_within_precision(SD59x18 a, SD59x18 b, int256 digits) public returns (bool) { + // Divide both number by digits to truncate the unimportant digits + int256 a_uint = SD59x18.unwrap(abs(a)); + int256 b_uint = SD59x18.unwrap(abs(b)); - // a and b lengths may differ in 1 bit, so the shift should take into account the longest - uint256 shift_bits = (a_bits > b_bits) ? (a_bits - bits) : (b_bits - bits); + int256 a_significant = a_uint / digits; + int256 b_significant = b_uint / digits; - // Get the _bits_ most significant bits of a and b - uint256 a_msb = most_significant_bits(a, bits) >> shift_bits; - uint256 b_msb = most_significant_bits(b, bits) >> shift_bits; + int256 larger = a_significant > b_significant ? a_significant : b_significant; + int256 smaller = a_significant > b_significant ? b_significant : a_significant; - // See if they are equal within 1 bit precision - // This could be modified to get the precision as a parameter to the function - return equal_within_precision_u(a_msb, b_msb, 1); + emit TestLog(larger, smaller, larger - smaller); + return ((larger - smaller) <= 1); } // Return the i most significant bits from |n|. If n has less than i significant bits, return |n| @@ -187,57 +186,6 @@ contract CryticPRBMath59x18Propertiesv3 { return (value & mask); } - /* function compute_max_log_error(SD59x18 x) public view returns (SD59x18 result) { - int256 xInt = SD59x18.unwrap(x); - - unchecked { - // This works because of: - // - // $$ - // log_2{x} = -log_2{\frac{1}{x}} - // $$ - int256 sign; - if (xInt >= uUNIT) { - sign = 1; - } else { - sign = -1; - // Do the fixed-point inversion inline to save gas. The numerator is UNIT * UNIT. - xInt = 1e36 / xInt; - } - - // Calculate the integer part of the logarithm and add it to the result and finally calculate $y = x * 2^(-n)$. - uint256 n = msb(uint256(xInt / uUNIT)); - - // This is $y = x * 2^{-n}$. - int256 y = xInt >> n; - - // If y is 1, the fractional part is zero. - if (y == uUNIT) { - return 0; - } - - // Calculate the fractional part via the iterative approximation. - // The "delta >>= 1" part is equivalent to "delta /= 2", but shifting bits is faster. - int256 DOUBLE_UNIT = 2e18; - int256 sum; - for (int256 delta = uHALF_UNIT; delta > 0; delta >>= 1) { - y = (y * y) / uUNIT; - - // Is $y^2 > 2$ and so in the range [2,4)? - if (y >= DOUBLE_UNIT) { - // Add the 2^{-m} factor to the logarithm. - sum += delta; - - // Corresponds to z/2 on Wikipedia. - y >>= 1; - } - } - - int256 maxError = 2 ** (-sum); - result = convert(maxError); - } - } */ - /* ================================================================ Library wrappers. These functions allow calling the PRBMathSD59x18 library. @@ -329,7 +277,6 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test for commutative property // x + y == y + x function add_test_commutative(SD59x18 x, SD59x18 y) public pure { @@ -339,7 +286,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x_y.eq(y_x)); } - // @audit-ok // Test for associative property // (x + y) + z == x + (y + z) function add_test_associative(SD59x18 x, SD59x18 y, SD59x18 z) public pure { @@ -351,7 +297,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(xy_z.eq(x_yz)); } - // @audit-ok // Test for identity operation // x + 0 == x (equivalent to x + (-x) == 0) function add_test_identity(SD59x18 x) public view { @@ -361,7 +306,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x.sub(x).eq(ZERO_FP)); } - // @audit-ok // Test that the result increases or decreases depending // on the value to be added function add_test_values(SD59x18 x, SD59x18 y) public view { @@ -374,14 +318,12 @@ contract CryticPRBMath59x18Propertiesv3 { } } - /* ================================================================ Tests for overflow and edge cases. These should make sure that the function reverts on overflow and behaves correctly on edge cases ================================================================ */ - // @audit-ok // The result of the addition must be between the maximum // and minimum allowed values for SD59x18 function add_test_range(SD59x18 x, SD59x18 y) public view { @@ -392,7 +334,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Adding zero to the maximum value shouldn't revert, as it is valid // Moreover, the result must be MAX_SD59x18 function add_test_maximum_value() public view { @@ -404,7 +345,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Adding one to the maximum value should revert, as it is out of range function add_test_maximum_value_plus_one() public view { try this.helpersAdd(MAX_SD59x18, ONE_FP) { @@ -414,7 +354,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Adding zero to the minimum value shouldn't revert, as it is valid // Moreover, the result must be MIN_SD59x18 function add_test_minimum_value() public view { @@ -426,7 +365,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Adding minus one to the maximum value should revert, as it is out of range function add_test_minimum_value_plus_negative_one() public view { try this.helpersAdd(MIN_SD59x18, MINUS_ONE_FP) { @@ -436,9 +374,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - - - /* ================================================================ TESTS FOR FUNCTION sub() @@ -451,7 +386,6 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test equivalence to addition // x - y == x + (-y) function sub_test_equivalence_to_addition(SD59x18 x, SD59x18 y) public pure { @@ -462,7 +396,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(addition.eq(subtraction)); } - // @audit-ok // Test for non-commutative property // x - y == -(y - x) function sub_test_non_commutative(SD59x18 x, SD59x18 y) public pure { @@ -472,7 +405,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x_y.eq(neg(y_x))); } - // @audit-ok // Test for identity operation // x - 0 == x (equivalent to x - x == 0) function sub_test_identity(SD59x18 x) public view { @@ -482,7 +414,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x.sub(x).eq(ZERO_FP)); } - // @audit-ok // Test for neutrality over addition and subtraction // (x - y) + y == (x + y) - y == x function sub_test_neutrality(SD59x18 x, SD59x18 y) public pure { @@ -496,7 +427,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x_minus_y_plus_y.eq(x)); } - // @audit-ok // Test that the result increases or decreases depending // on the value to be subtracted function sub_test_values(SD59x18 x, SD59x18 y) public view { @@ -515,7 +445,6 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // The result of the subtraction must be between the maximum // and minimum allowed values for SD59x18 function sub_test_range(SD59x18 x, SD59x18 y) public view { @@ -526,7 +455,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Subtracting zero from the maximum value shouldn't revert, as it is valid // Moreover, the result must be MAX_SD59x18 function sub_test_maximum_value() public view { @@ -538,7 +466,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Subtracting minus one from the maximum value should revert, // as it is out of range function sub_test_maximum_value_minus_neg_one() public view { @@ -549,7 +476,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Subtracting zero from the minimum value shouldn't revert, as it is valid // Moreover, the result must be MIN_SD59x18 function sub_test_minimum_value() public view { @@ -561,7 +487,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Subtracting one from the minimum value should revert, as it is out of range function sub_test_minimum_value_minus_one() public view { try this.helpersSub(MIN_SD59x18, ONE_FP) { @@ -571,8 +496,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - - /* ================================================================ TESTS FOR FUNCTION mul() @@ -585,7 +508,6 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test for commutative property // x * y == y * x function mul_test_commutative(SD59x18 x, SD59x18 y) public pure { @@ -605,7 +527,6 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 xy_z = x_y.mul(z); SD59x18 x_yz = x.mul(y_z); - // todo check if this should not be used // Failure if all significant digits are lost require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); require(significant_digits_after_mult(y, z) > REQUIRED_SIGNIFICANT_DIGITS); @@ -613,7 +534,6 @@ contract CryticPRBMath59x18Propertiesv3 { require(significant_digits_after_mult(x, y_z) > REQUIRED_SIGNIFICANT_DIGITS); assert(equal_within_tolerance(xy_z, x_yz, ONE_TENTH_FP)); - //assert(xy_z.eq(x_yz)); } // @audit - No guarantee of precision @@ -626,17 +546,15 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_times_y = x.mul(y); SD59x18 x_times_z = x.mul(z); - // todo check if this should not be used // Failure if all significant digits are lost require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); require(significant_digits_after_mult(x, z) > REQUIRED_SIGNIFICANT_DIGITS); require(significant_digits_after_mult(x, y_plus_z) > REQUIRED_SIGNIFICANT_DIGITS); assert(equal_within_tolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP)); - //assert(x_times_y.add(x_times_z).eq(x_times_y_plus_z)); } - // @audit-ok + // Test for identity operation // x * 1 == x (also check that x * 0 == 0) function mul_test_identity(SD59x18 x) public view { @@ -672,14 +590,12 @@ contract CryticPRBMath59x18Propertiesv3 { } } - /* ================================================================ Tests for overflow and edge cases. These will make sure that the function reverts on overflow and behaves correctly on edge cases ================================================================ */ - // @audit-ok // The result of the multiplication must be between the maximum // and minimum allowed values for SD59x18 function mul_test_range(SD59x18 x, SD59x18 y) public view { @@ -690,7 +606,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Multiplying the maximum value times one shouldn't revert, as it is valid // Moreover, the result must be MAX_SD59x18 function mul_test_maximum_value() public view { @@ -702,7 +617,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Multiplying the minimum value times one shouldn't revert, as it is valid // Moreover, the result must be MIN_SD59x18 function mul_test_minimum_value() public view { @@ -714,7 +628,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - /* ================================================================ TESTS FOR FUNCTION div() @@ -727,7 +640,6 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test for identity property // x / 1 == x (equivalent to x / x == 1) // Moreover, x/x should not revert unless x == 0 @@ -750,7 +662,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Test for negative divisor // x / -y == -(x / y) function div_test_negative_divisor(SD59x18 x, SD59x18 y) public view { @@ -762,7 +673,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x_y.eq(neg(x_minus_y))); } - // @audit-ok // Test for division with 0 as numerator // 0 / x = 0 function div_test_division_num_zero(SD59x18 x) public view { @@ -773,7 +683,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(ZERO_FP.eq(div_0)); } - // @audit-ok // Test that the absolute value of the result increases or // decreases depending on the denominator's absolute value function div_test_values(SD59x18 x, SD59x18 y) public view { @@ -794,7 +703,7 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok + // Test for division by zero function div_test_div_by_zero(SD59x18 x) public view { try this.helpersDiv(x, ZERO_FP) { @@ -805,7 +714,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Test for division by a large value, the result should be less than one function div_test_maximum_denominator(SD59x18 x) public view { SD59x18 div_large = div(x, MAX_SD59x18); @@ -813,7 +721,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(abs(div_large).lte(ONE_FP)); } - // @audit-ok // Test for division of a large value // This should revert if |x| < 1 as it would return a value higher than max function div_test_maximum_numerator(SD59x18 x) public view { @@ -829,7 +736,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Test for values in range function div_test_range(SD59x18 x, SD59x18 y) public view { SD59x18 result; @@ -855,7 +761,6 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test for the double negation // -(-x) == x function neg_test_double_negation(SD59x18 x) public pure { @@ -864,7 +769,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x.eq(double_neg)); } - // @audit-ok // Test for the identity operation // x + (-x) == 0 function neg_test_identity(SD59x18 x) public view { @@ -879,7 +783,6 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test for the zero-case // -0 == 0 function neg_test_zero() public view { @@ -922,7 +825,7 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok + // Test that the absolute value is always positive function abs_test_positive(SD59x18 x) public view { SD59x18 abs_x = abs(x); @@ -930,7 +833,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(abs_x.gte(ZERO_FP)); } - // @audit-ok // Test that the absolute value of a number equals the // absolute value of the negative of the same number function abs_test_negative(SD59x18 x) public pure { @@ -956,7 +858,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_precision(abs_xy, abs_x_abs_y, 2)); } - // @audit-ok // Test the subadditivity property // | x + y | <= |x| + |y| function abs_test_subadditivity(SD59x18 x, SD59x18 y) public pure { @@ -973,7 +874,6 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test the zero-case | 0 | = 0 function abs_test_zero() public view { SD59x18 abs_zero; @@ -988,7 +888,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Test the maximum value function abs_test_maximum() public view { SD59x18 abs_max; @@ -1000,7 +899,6 @@ contract CryticPRBMath59x18Propertiesv3 { } catch {} } - // @audit-ok // Test the minimum value function abs_test_minimum_revert() public view { SD59x18 abs_min; @@ -1011,7 +909,6 @@ contract CryticPRBMath59x18Propertiesv3 { } catch {} } - // @audit-ok // Test the minimum value function abs_test_minimum_allowed() public view { SD59x18 abs_min; @@ -1036,7 +933,6 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test that the inverse of the inverse is close enough to the // original number function inv_test_double_inverse(SD59x18 x) public view { @@ -1050,7 +946,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_precision(x, double_inv_x, loss)); } - // @audit-ok // Test equivalence with division function inv_test_division(SD59x18 x) public view { require(x.neq(ZERO_FP)); @@ -1107,7 +1002,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_precision(inv_x_y, inv_x_times_inv_y, loss)); } - // @audit-ok // Test identity property // Intermediate result should have at least REQUIRED_SIGNIFICANT_DIGITS function inv_test_identity(SD59x18 x) public view { @@ -1124,7 +1018,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_tolerance(identity, ONE_FP, ONE_TENTH_FP)); } - // @audit-ok // Test that the absolute value of the result is in range zero-one // if x is greater than one, else, the absolute value of the result // must be greater than one @@ -1140,7 +1033,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Test that the result has the same sign as the argument. // Since inv() rounds towards zero, we are checking the zero case as well function inv_test_sign(SD59x18 x) public { @@ -1161,7 +1053,6 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test the zero-case, should revert function inv_test_zero() public view { try this.helpersInv(ZERO_FP) { @@ -1170,7 +1061,6 @@ contract CryticPRBMath59x18Propertiesv3 { } catch {} } - // @audit-ok // Test the maximum value case, should not revert, and be close to zero function inv_test_maximum() public view { SD59x18 inv_maximum; @@ -1198,7 +1088,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - /* ================================================================ TESTS FOR FUNCTION avg() @@ -1211,7 +1100,6 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test that the result is between the two operands // avg(x, y) >= min(x, y) && avg(x, y) <= max(x, y) function avg_test_values_in_range(SD59x18 x, SD59x18 y) public pure { @@ -1224,7 +1112,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Test that the average of the same number is itself // avg(x, x) == x function avg_test_one_value(SD59x18 x) public pure { @@ -1233,7 +1120,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(avg_x.eq(x)); } - // @audit-ok // Test that the order of operands is irrelevant // avg(x, y) == avg(y, x) function avg_test_operand_order(SD59x18 x, SD59x18 y) public pure { @@ -1249,7 +1135,6 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test for the maximum value function avg_test_maximum() public view { SD59x18 result; @@ -1262,7 +1147,6 @@ contract CryticPRBMath59x18Propertiesv3 { } catch {} } - // @audit-ok // Test for the minimum value function avg_test_minimum() public view { SD59x18 result; @@ -1287,7 +1171,6 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test for zero exponent // x ** 0 == 1 function pow_test_zero_exponent(SD59x18 x) public view { @@ -1296,7 +1179,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x_pow_0.eq(ONE_FP)); } - // @audit-ok // Test for zero base // 0 ** x == 0 (for positive x) function pow_test_zero_base(SD59x18 x) public view { @@ -1307,7 +1189,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(zero_pow_x.eq(ZERO_FP)); } - // @audit-ok // Test for exponent one // x ** 1 == x function pow_test_one_exponent(SD59x18 x) public view { @@ -1316,7 +1197,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x_pow_1.eq(x)); } - // @audit-ok // Test for base one // 1 ** x == 1 function pow_test_base_one(SD59x18 x) public view { @@ -1332,14 +1212,18 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x, SD59x18 a, SD59x18 b - ) public view { + ) public { require(x.neq(ZERO_FP)); SD59x18 x_a = pow(x, a); SD59x18 x_b = pow(x, b); SD59x18 x_ab = pow(x, a.add(b)); - assert(equal_within_precision(mul(x_a, x_b), x_ab, 10)); + uint256 power = 9; + int256 digits = int256(10**power); + + emit PropertyFailed(mul(x_a, x_b), x_ab, power); + assert(equal_most_significant_digits_within_precision(mul(x_a, x_b), x_ab, digits)); } // @audit - fails @@ -1349,14 +1233,18 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x, SD59x18 a, SD59x18 b - ) public view { + ) public { require(x.neq(ZERO_FP)); SD59x18 x_a = pow(x, a); SD59x18 x_a_b = pow(x_a, b); SD59x18 x_ab = pow(x, a.mul(b)); - assert(equal_within_precision(x_a_b, x_ab, 10)); + uint256 power = 9; + int256 digits = int256(10**power); + + emit PropertyFailed(x_a_b, x_ab, power); + assert(equal_most_significant_digits_within_precision(x_a_b, x_ab, digits)); } // @audit check precision @@ -1380,25 +1268,26 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_precision(mul(x_a, y_a), xy_a, 10)); } - // @audit - fails + // @audit - fails, add some relative error // Test for result being greater than or lower than the argument, depending on // its absolute value and the value of the exponent - function pow_test_values(SD59x18 x, SD59x18 a) public view { + function pow_test_values(SD59x18 x, SD59x18 a) public { require(x.neq(ZERO_FP)); require(x.neq(MIN_SD59x18) && a.neq(MIN_SD59x18)); SD59x18 x_a = pow(x, a); if (abs(x).gte(ONE_FP)) { + emit PropertyFailed(x_a, ONE_FP); assert(abs(x_a).gte(ONE_FP)); } if (abs(x).lte(ONE_FP)) { + emit PropertyFailed(x_a, ONE_FP); assert(abs(x_a).lte(ONE_FP)); } } - // @audit-ok // Test for result sign: if the exponent is even, sign is positive // if the exponent is odd, preserves the sign of the base function pow_test_sign(SD59x18 x, SD59x18 a) public view { @@ -1429,8 +1318,6 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - - // @audit-ok // Test for maximum base and exponent > 1 function pow_test_maximum_base(SD59x18 a) public view { require(a.gt(ONE_FP)); @@ -1465,7 +1352,6 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test for the inverse operation // sqrt(x) * sqrt(x) == x function sqrt_test_inverse_mul(SD59x18 x) public view { @@ -1484,7 +1370,6 @@ contract CryticPRBMath59x18Propertiesv3 { ); } - // @audit-ok // Test for the inverse operation // sqrt(x) ** 2 == x function sqrt_test_inverse_pow(SD59x18 x) public view { @@ -1531,13 +1416,12 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok + // Test for zero case function sqrt_test_zero() public view { assert(sqrt(ZERO_FP).eq(ZERO_FP)); } - // @audit-ok // Test for maximum value function sqrt_test_maximum() public view { try this.helpersSqrt(MAX_SQRT) { @@ -1549,7 +1433,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Test for minimum value function sqrt_test_minimum() public view { try this.helpersSqrt(MIN_SD59x18) { @@ -1560,7 +1443,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Test for negative operands function sqrt_test_negative(SD59x18 x) public view { require(x.lt(ZERO_FP)); @@ -1609,12 +1491,16 @@ contract CryticPRBMath59x18Propertiesv3 { // @audit - fails // Test for logarithm of a power // log2(x ** y) = y * log2(x) - function log2_test_power(SD59x18 x, SD59x18 y) public view { + function log2_test_power(SD59x18 x, SD59x18 y) public { SD59x18 x_y = pow(x, y); SD59x18 log2_x_y = log2(x_y); SD59x18 y_log2_x = mul(log2(x), y); - assert(equal_within_precision(y_log2_x, log2_x_y, 4)); + uint256 power = 9; + int256 digits = int256(10**power); + + emit PropertyFailed(y_log2_x, log2_x_y, power); + assert(equal_most_significant_digits_within_precision(y_log2_x, log2_x_y, digits)); } /* ================================================================ @@ -1623,7 +1509,6 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test for zero case, should revert function log2_test_zero() public view { try this.helpersLog2(ZERO_FP) { @@ -1634,7 +1519,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Test for maximum value case, should return a positive number function log2_test_maximum() public view { SD59x18 result; @@ -1649,7 +1533,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Test for negative values, should revert as log2 is not defined function log2_test_negative(SD59x18 x) public view { require(x.lt(ZERO_FP)); @@ -1706,10 +1589,10 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 y_ln_x = mul(ln(x), y); - // todo this isn't correct - uint256 loss = intoUint256(abs(log2(x).add(log2(y)))); + require(significant_digits_after_mult(ln(x), y) > REQUIRED_SIGNIFICANT_DIGITS); - assert(equal_within_precision(ln_x_y, y_ln_x, 4)); + emit PropertyFailed(ln_x_y, y_ln_x); + assert(equal_within_tolerance(ln_x_y, y_ln_x, ONE_TENTH_FP)); } /* ================================================================ @@ -1718,7 +1601,6 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test for zero case, should revert function ln_test_zero() public view { try this.helpersLn(ZERO_FP) { @@ -1729,7 +1611,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Test for maximum value case, should return a positive number function ln_test_maximum() public view { SD59x18 result; @@ -1744,7 +1625,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Test for negative values, should revert as ln is not defined function ln_test_negative(SD59x18 x) public view { require(x.lt(ZERO_FP)); @@ -1769,7 +1649,6 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test for equality with pow(2, x) for integer x // pow(2, x) == exp2(x) function exp2_test_equivalence_pow(SD59x18 x) public view { @@ -1782,21 +1661,17 @@ contract CryticPRBMath59x18Propertiesv3 { // @audit - fails // Test for inverse function // If y = log2(x) then exp2(y) == x - function exp2_test_inverse(SD59x18 x) public view { + function exp2_test_inverse(SD59x18 x) public { SD59x18 log2_x = log2(x); SD59x18 exp2_x = exp2(log2_x); - // todo is this the correct number of bits? - uint256 bits = 18; + uint256 power = 30; + int256 digits = int256(10**power); - if (log2_x.lt(ZERO_FP)) { - bits = intoUint256(convert(int256(bits)).add(log2_x)); - } - - assert(equal_most_significant_digits_within_precision(x, exp2_x, bits)); + emit PropertyFailed(x, exp2_x, power); + assert(equal_most_significant_digits_within_precision(x, exp2_x, digits)); } - // @audit-ok // Test for negative exponent // exp2(-x) == inv( exp2(x) ) function exp2_test_negative_exponent(SD59x18 x) public view { @@ -1815,7 +1690,6 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test for zero case // exp2(0) == 1 function exp2_test_zero() public view { @@ -1823,7 +1697,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(exp_zero.eq(ONE_FP)); } - // @audit-ok // Test for maximum value. This should overflow as it won't fit // in the data type function exp2_test_maximum() public view { @@ -1835,7 +1708,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Test for minimum value. This should return zero since // 2 ** -x == 1 / 2 ** x that tends to zero as x increases function exp2_test_minimum() public view { @@ -1866,18 +1738,16 @@ contract CryticPRBMath59x18Propertiesv3 { // @audit - fails // Test for inverse function // If y = ln(x) then exp(y) == x - function exp_test_inverse(SD59x18 x) public view { + function exp_test_inverse(SD59x18 x) public { SD59x18 ln_x = ln(x); SD59x18 exp_x = exp(ln_x); - SD59x18 log2_x = log2(x); - - uint256 bits = 18; + SD59x18 log10_x = log10(x); - if (log2_x.lt(ZERO_FP)) { - bits = intoUint256(convert(int256(bits)).add(log2_x)); - } + uint256 power = 16; + int256 digits = int256(10**power); - assert(equal_most_significant_digits_within_precision(x, exp_x, bits)); + emit PropertyFailed(x, exp_x, power); + assert(equal_most_significant_digits_within_precision(x, exp_x, digits)); } // @audit check precision @@ -1899,7 +1769,6 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test for zero case // exp(0) == 1 function exp_test_zero() public view { @@ -1907,7 +1776,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(exp_zero.eq(ONE_FP)); } - // @audit-ok // Test for maximum value. This should overflow as it won't fit // in the data type function exp_test_maximum() public view { @@ -1919,7 +1787,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Test for minimum value. This should return zero since // e ** -x == 1 / e ** x that tends to zero as x increases function exp_test_minimum() public view { @@ -1947,7 +1814,6 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test for zero exponent // x ** 0 == 1 function powu_test_zero_exponent(SD59x18 x) public view { @@ -1956,7 +1822,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x_pow_0.eq(ONE_FP)); } - // @audit-ok // Test for zero base // 0 ** x == 0 (for positive x) function powu_test_zero_base(uint256 x) public view { @@ -1967,7 +1832,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(zero_pow_x.eq(ZERO_FP)); } - // @audit-ok // Test for exponent one // x ** 1 == x function powu_test_one_exponent(SD59x18 x) public view { @@ -1976,7 +1840,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x_pow_1.eq(x)); } - // @audit-ok // Test for base one // 1 ** x == 1 function powu_test_base_one(uint256 x) public view { @@ -2058,7 +1921,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Test for result sign: if the exponent is even, sign is positive // if the exponent is odd, preserves the sign of the base function powu_test_sign(SD59x18 x, uint256 a) public view { @@ -2089,8 +1951,6 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - - // @audit-ok // Test for maximum base and exponent > 1 function powu_test_maximum_base(uint256 a) public view { require(a > 1); @@ -2154,11 +2014,11 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 log10_x_y = log10(x_y); SD59x18 y_log10_x = mul(log10(x), y); - if(!equal_within_precision(log10_x_y, y_log10_x, 18)){ - emit AssertionFailed(log10_x_y, y_log10_x); - } + uint256 power = 9; + int256 digits = int256(10**power); - assert(equal_within_precision(log10_x_y, y_log10_x, 10)); + emit PropertyFailed(log10_x_y, y_log10_x, power); + assert(equal_most_significant_digits_within_precision(log10_x_y, y_log10_x, digits)); } /* ================================================================ @@ -2167,7 +2027,6 @@ contract CryticPRBMath59x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test for zero case, should revert function log10_test_zero() public view { try this.helpersLog10(ZERO_FP) { @@ -2178,7 +2037,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Test for maximum value case, should return a positive number function log10_test_maximum() public view { SD59x18 result; @@ -2193,7 +2051,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit-ok // Test for negative values, should revert as log10 is not defined function log10_test_negative(SD59x18 x) public view { require(x.lt(ZERO_FP)); diff --git a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol index 49d7f70..dd31927 100644 --- a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol @@ -9,8 +9,10 @@ import {mul, div, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, powu} contract CryticPRBMath60x18Propertiesv3 { - event AssertionFailed(UD60x18 result); - event AssertionFailed(UD60x18 result1, UD60x18 result2); + event PropertyFailed(UD60x18 result); + event PropertyFailed(UD60x18 result1, UD60x18 result2); + event PropertyFailed(UD60x18 result1, UD60x18 result2, uint256 discardedDigits); + event TestLog(uint256 num1, uint256 num2, uint256 result); /* ================================================================ 59x18 fixed-point constants used for testing specific values. @@ -73,15 +75,12 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 internal constant MAX_PERMITTED_EXPONENT_POW = UD60x18.wrap(2 ** 192 * 10 ** 18 - 1); UD60x18 internal constant MAX_PERMITTED_SQRT = UD60x18.wrap(115792089237316195423570985008687907853269_984665640564039457); - - /* ================================================================ Events used for debugging or showing information. ================================================================ */ event Value(string reason, int256 val); event LogErr(bytes error); - /* ================================================================ Helper functions. ================================================================ */ @@ -140,22 +139,19 @@ contract CryticPRBMath60x18Propertiesv3 { // Returns true if the n most significant bits of a and b are almost equal // Uses functions from the library under test! - function equal_most_significant_digits_within_precision(UD60x18 a, UD60x18 b, uint256 bits) public view returns (bool) { - // Get the number of bits in a and b - // Since log(x) returns in the interval [-64, 63), add 64 to be in the interval [0, 127) - uint256 a_bits = uint256(int256(convert(log2(a)))); - uint256 b_bits = uint256(int256(convert(log2(b)))); + function equal_most_significant_digits_within_precision(UD60x18 a, UD60x18 b, uint256 digits) public returns (bool) { + // Divide both number by digits to truncate the unimportant digits + uint256 a_uint = UD60x18.unwrap(a); + uint256 b_uint = UD60x18.unwrap(b); - // a and b lengths may differ in 1 bit, so the shift should take into account the longest - uint256 shift_bits = (a_bits > b_bits) ? (a_bits - bits) : (b_bits - bits); + uint256 a_significant = a_uint / digits; + uint256 b_significant = b_uint / digits; - // Get the _bits_ most significant bits of a and b - uint256 a_msb = most_significant_bits(a, bits) >> shift_bits; - uint256 b_msb = most_significant_bits(b, bits) >> shift_bits; - - // See if they are equal within 1 bit precision - // This could be modified to get the precision as a parameter to the function - return equal_within_precision_u(a_msb, b_msb, 1); + uint256 larger = a_significant > b_significant ? a_significant : b_significant; + uint256 smaller = a_significant > b_significant ? b_significant : a_significant; + + emit TestLog(larger, smaller, larger - smaller); + return ((larger - smaller) <= 1); } // Return the i most significant bits from |n|. If n has less than i significant bits, return |n| @@ -163,7 +159,7 @@ contract CryticPRBMath60x18Propertiesv3 { function most_significant_bits( UD60x18 n, uint256 i - ) public view returns (uint256) { + ) public returns (uint256) { if (n.eq(ZERO_FP)) return 0; // Create a mask consisting of i bits set to 1 @@ -183,57 +179,6 @@ contract CryticPRBMath60x18Propertiesv3 { return (value & mask); } - /* function compute_max_log_error(UD60x18 x) public view returns (UD60x18 result) { - int256 xInt = UD60x18.unwrap(x); - - unchecked { - // This works because of: - // - // $$ - // log_2{x} = -log_2{\frac{1}{x}} - // $$ - int256 sign; - if (xInt >= uUNIT) { - sign = 1; - } else { - sign = -1; - // Do the fixed-point inversion inline to save gas. The numerator is UNIT * UNIT. - xInt = 1e36 / xInt; - } - - // Calculate the integer part of the logarithm and add it to the result and finally calculate $y = x * 2^(-n)$. - uint256 n = msb(uint256(xInt / uUNIT)); - - // This is $y = x * 2^{-n}$. - int256 y = xInt >> n; - - // If y is 1, the fractional part is zero. - if (y == uUNIT) { - return 0; - } - - // Calculate the fractional part via the iterative approximation. - // The "delta >>= 1" part is equivalent to "delta /= 2", but shifting bits is faster. - int256 DOUBLE_UNIT = 2e18; - int256 sum; - for (int256 delta = uHALF_UNIT; delta > 0; delta >>= 1) { - y = (y * y) / uUNIT; - - // Is $y^2 > 2$ and so in the range [2,4)? - if (y >= DOUBLE_UNIT) { - // Add the 2^{-m} factor to the logarithm. - sum += delta; - - // Corresponds to z/2 on Wikipedia. - y >>= 1; - } - } - - int256 maxError = 2 ** (-sum); - result = convert(maxError); - } - } */ - /* ================================================================ Library wrappers. These functions allow calling the PRBMathUD60x18 library. @@ -317,7 +262,6 @@ contract CryticPRBMath60x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test for commutative property // x + y == y + x function add_test_commutative(UD60x18 x, UD60x18 y) public pure { @@ -327,7 +271,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(x_y.eq(y_x)); } - // @audit-ok // Test for associative property // (x + y) + z == x + (y + z) function add_test_associative(UD60x18 x, UD60x18 y, UD60x18 z) public pure { @@ -339,20 +282,18 @@ contract CryticPRBMath60x18Propertiesv3 { assert(xy_z.eq(x_yz)); } - // @audit-ok // Test for identity operation // x + 0 == x (equivalent to x + (-x) == 0) - function add_test_identity(UD60x18 x) public view { + function add_test_identity(UD60x18 x) public { UD60x18 x_0 = x.add(ZERO_FP); assert(x.eq(x_0)); assert(x.sub(x).eq(ZERO_FP)); } - // @audit-ok // Test that the result increases or decreases depending // on the value to be added - function add_test_values(UD60x18 x, UD60x18 y) public view { + function add_test_values(UD60x18 x, UD60x18 y) public { UD60x18 x_y = x.add(y); if (y.gte(ZERO_FP)) { @@ -362,17 +303,15 @@ contract CryticPRBMath60x18Propertiesv3 { } } - /* ================================================================ Tests for overflow and edge cases. These should make sure that the function reverts on overflow and behaves correctly on edge cases ================================================================ */ - // @audit-ok // The result of the addition must be between the maximum // and minimum allowed values for UD60x18 - function add_test_range(UD60x18 x, UD60x18 y) public view { + function add_test_range(UD60x18 x, UD60x18 y) public { try this.helpersAdd(x, y) returns (UD60x18 result) { assert(result.lte(MAX_UD60x18) && result.gte(ZERO_FP)); } catch { @@ -380,10 +319,9 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit-ok // Adding zero to the maximum value shouldn't revert, as it is valid // Moreover, the result must be MAX_UD60x18 - function add_test_maximum_value() public view { + function add_test_maximum_value() public { try this.helpersAdd(MAX_UD60x18, ZERO_FP) returns (UD60x18 result) { // Expected behaviour, does not revert assert(result.eq(MAX_UD60x18)); @@ -392,9 +330,8 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit-ok // Adding one to the maximum value should revert, as it is out of range - function add_test_maximum_value_plus_one() public view { + function add_test_maximum_value_plus_one() public { try this.helpersAdd(MAX_UD60x18, ONE_FP) { assert(false); } catch { @@ -402,10 +339,9 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit-ok // Adding zero to the minimum value shouldn't revert, as it is valid // Moreover, the result must be MIN_UD60x18 - function add_test_minimum_value() public view { + function add_test_minimum_value() public { try this.helpersAdd(ZERO_FP, ZERO_FP) returns (UD60x18 result) { // Expected behaviour, does not revert assert(result.eq(ZERO_FP)); @@ -426,17 +362,15 @@ contract CryticPRBMath60x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test for identity operation // x - 0 == x (equivalent to x - x == 0) - function sub_test_identity(UD60x18 x) public view { + function sub_test_identity(UD60x18 x) public { UD60x18 x_0 = x.sub(ZERO_FP); assert(x_0.eq(x)); assert(x.sub(x).eq(ZERO_FP)); } - // @audit-ok // Test for neutrality over addition and subtraction // (x - y) + y == (x + y) - y == x function sub_test_neutrality(UD60x18 x, UD60x18 y) public pure { @@ -450,9 +384,8 @@ contract CryticPRBMath60x18Propertiesv3 { assert(x_minus_y_plus_y.eq(x)); } - // @audit-ok // Test that the result always decreases - function sub_test_values(UD60x18 x, UD60x18 y) public view { + function sub_test_values(UD60x18 x, UD60x18 y) public { UD60x18 x_y = x.sub(y); assert(x_y.lte(x)); @@ -464,10 +397,9 @@ contract CryticPRBMath60x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // The result of the subtraction must be between the maximum // and minimum allowed values for UD60x18 - function sub_test_range(UD60x18 x, UD60x18 y) public view { + function sub_test_range(UD60x18 x, UD60x18 y) public { try this.helpersSub(x, y) returns (UD60x18 result) { assert(result.lte(MAX_UD60x18) && result.gte(ZERO_FP)); } catch { @@ -475,10 +407,9 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit-ok // Subtracting zero from the maximum value shouldn't revert, as it is valid // Moreover, the result must be MAX_UD60x18 - function sub_test_maximum_value() public view { + function sub_test_maximum_value() public { try this.helpersSub(MAX_UD60x18, ZERO_FP) returns (UD60x18 result) { // Expected behaviour, does not revert assert(result.eq(MAX_UD60x18)); @@ -487,10 +418,9 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit-ok // Subtracting zero from the minimum value shouldn't revert, as it is valid // Moreover, the result must be MIN_UD60x18 - function sub_test_minimum_value() public view { + function sub_test_minimum_value() public { try this.helpersSub(ZERO_FP, ZERO_FP) returns (UD60x18 result) { // Expected behaviour, does not revert assert(result.eq(ZERO_FP)); @@ -499,9 +429,8 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit-ok // Subtracting one from the minimum value should revert, as it is out of range - function sub_test_minimum_value_minus_one() public view { + function sub_test_minimum_value_minus_one() public { try this.helpersSub(ZERO_FP, ONE_FP) { assert(false); } catch { @@ -509,8 +438,6 @@ contract CryticPRBMath60x18Propertiesv3 { } } - - /* ================================================================ TESTS FOR FUNCTION mul() @@ -523,7 +450,6 @@ contract CryticPRBMath60x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test for commutative property // x * y == y * x function mul_test_commutative(UD60x18 x, UD60x18 y) public pure { @@ -536,7 +462,7 @@ contract CryticPRBMath60x18Propertiesv3 { // @audit - No guarantee of precision // Test for associative property // (x * y) * z == x * (y * z) - function mul_test_associative(UD60x18 x, UD60x18 y, UD60x18 z) public view { + function mul_test_associative(UD60x18 x, UD60x18 y, UD60x18 z) public { UD60x18 x_y = x.mul(y); UD60x18 y_z = y.mul(z); UD60x18 xy_z = x_y.mul(z); @@ -556,7 +482,7 @@ contract CryticPRBMath60x18Propertiesv3 { // @audit - No guarantee of precision // Test for distributive property // x * (y + z) == x * y + x * z - function mul_test_distributive(UD60x18 x, UD60x18 y, UD60x18 z) public view { + function mul_test_distributive(UD60x18 x, UD60x18 y, UD60x18 z) public { UD60x18 y_plus_z = y.add(z); UD60x18 x_times_y_plus_z = x.mul(y_plus_z); @@ -573,10 +499,9 @@ contract CryticPRBMath60x18Propertiesv3 { //assert(x_times_y.add(x_times_z).eq(x_times_y_plus_z)); } - // @audit-ok // Test for identity operation // x * 1 == x (also check that x * 0 == 0) - function mul_test_identity(UD60x18 x) public view { + function mul_test_identity(UD60x18 x) public { UD60x18 x_1 = x.mul(ONE_FP); UD60x18 x_0 = x.mul(ZERO_FP); @@ -587,7 +512,7 @@ contract CryticPRBMath60x18Propertiesv3 { // @audit - No guarantee of precision // Test that the result increases or decreases depending // on the value to be added - function mul_test_values(UD60x18 x, UD60x18 y) public view { + function mul_test_values(UD60x18 x, UD60x18 y) public { UD60x18 x_y = x.mul(y); //require(significant_digits_lost_in_mult(x, y) == false); @@ -599,17 +524,15 @@ contract CryticPRBMath60x18Propertiesv3 { } } - /* ================================================================ Tests for overflow and edge cases. These will make sure that the function reverts on overflow and behaves correctly on edge cases ================================================================ */ - // @audit-ok // The result of the multiplication must be between the maximum // and minimum allowed values for UD60x18 - function mul_test_range(UD60x18 x, UD60x18 y) public view { + function mul_test_range(UD60x18 x, UD60x18 y) public { try this.helpersMul(x, y) returns(UD60x18 result) { assert(result.lte(MAX_UD60x18)); } catch { @@ -617,10 +540,9 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit-ok // Multiplying the maximum value times one shouldn't revert, as it is valid // Moreover, the result must be MAX_UD60x18 - function mul_test_maximum_value() public view { + function mul_test_maximum_value() public { try this.helpersMul(MAX_UD60x18, ONE_FP) returns (UD60x18 result) { // Expected behaviour, does not revert assert(result.eq(MAX_UD60x18)); @@ -641,11 +563,10 @@ contract CryticPRBMath60x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test for identity property // x / 1 == x (equivalent to x / x == 1) // Moreover, x/x should not revert unless x == 0 - function div_test_division_identity(UD60x18 x) public view { + function div_test_division_identity(UD60x18 x) public { UD60x18 div_1 = div(x, ONE_FP); assert(x.eq(div_1)); @@ -661,10 +582,9 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit-ok // Test for division with 0 as numerator // 0 / x = 0 - function div_test_division_num_zero(UD60x18 x) public view { + function div_test_division_num_zero(UD60x18 x) public { require(x.neq(ZERO_FP)); UD60x18 div_0 = div(ZERO_FP, x); @@ -672,10 +592,9 @@ contract CryticPRBMath60x18Propertiesv3 { assert(ZERO_FP.eq(div_0)); } - // @audit-ok // Test that the value of the result increases or // decreases depending on the denominator's value - function div_test_values(UD60x18 x, UD60x18 y) public view { + function div_test_values(UD60x18 x, UD60x18 y) public { require(y.neq(ZERO_FP)); UD60x18 x_y = div(x, y); @@ -693,9 +612,8 @@ contract CryticPRBMath60x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test for division by zero - function div_test_div_by_zero(UD60x18 x) public view { + function div_test_div_by_zero(UD60x18 x) public { try this.helpersDiv(x, ZERO_FP) { // Unexpected, this should revert assert(false); @@ -704,18 +622,16 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit-ok // Test for division by a large value, the result should be less than one - function div_test_maximum_denominator(UD60x18 x) public view { + function div_test_maximum_denominator(UD60x18 x) public { UD60x18 div_large = div(x, MAX_UD60x18); assert(div_large.lte(ONE_FP)); } - // @audit-ok // Test for division of a large value // This should revert if |x| < 1 as it would return a value higher than max - function div_test_maximum_numerator(UD60x18 x) public view { + function div_test_maximum_numerator(UD60x18 x) public { UD60x18 div_large; try this.helpersDiv(MAX_UD60x18, x) { @@ -728,9 +644,8 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit-ok // Test for values in range - function div_test_range(UD60x18 x, UD60x18 y) public view { + function div_test_range(UD60x18 x, UD60x18 y) public { UD60x18 result; try this.helpersDiv(x, y) { @@ -754,10 +669,9 @@ contract CryticPRBMath60x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test that the inverse of the inverse is close enough to the // original number - function inv_test_double_inverse(UD60x18 x) public view { + function inv_test_double_inverse(UD60x18 x) public { require(x.neq(ZERO_FP)); UD60x18 double_inv_x = inv(inv(x)); @@ -768,9 +682,8 @@ contract CryticPRBMath60x18Propertiesv3 { assert(equal_within_precision(x, double_inv_x, loss)); } - // @audit-ok // Test equivalence with division - function inv_test_division(UD60x18 x) public view { + function inv_test_division(UD60x18 x) public { require(x.neq(ZERO_FP)); UD60x18 inv_x = inv(x); @@ -785,7 +698,7 @@ contract CryticPRBMath60x18Propertiesv3 { function inv_test_division_noncommutativity( UD60x18 x, UD60x18 y - ) public view { + ) public { require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); UD60x18 x_y = div(x, y); @@ -803,7 +716,7 @@ contract CryticPRBMath60x18Propertiesv3 { // @audit check if loss is correct // Test the multiplication of inverses // 1/(x * y) == 1/x * 1/y - function inv_test_multiplication(UD60x18 x, UD60x18 y) public view { + function inv_test_multiplication(UD60x18 x, UD60x18 y) public { require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); UD60x18 inv_x = inv(x); @@ -825,10 +738,9 @@ contract CryticPRBMath60x18Propertiesv3 { assert(equal_within_precision(inv_x_y, inv_x_times_inv_y, loss)); } - // @audit-ok // Test identity property // Intermediate result should have at least REQUIRED_SIGNIFICANT_DIGITS - function inv_test_identity(UD60x18 x) public view { + function inv_test_identity(UD60x18 x) public { require(x.neq(ZERO_FP)); UD60x18 inv_x = inv(x); @@ -842,11 +754,10 @@ contract CryticPRBMath60x18Propertiesv3 { assert(equal_within_tolerance(identity, ONE_FP, ONE_TENTH_FP)); } - // @audit-ok // Test that the value of the result is in range zero-one // if x is greater than one, else, the value of the result // must be greater than one - function inv_test_values(UD60x18 x) public view { + function inv_test_values(UD60x18 x) public { require(x.neq(ZERO_FP)); UD60x18 inv_x = inv(x); @@ -864,18 +775,16 @@ contract CryticPRBMath60x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test the zero-case, should revert - function inv_test_zero() public view { + function inv_test_zero() public { try this.helpersInv(ZERO_FP) { // Unexpected, the function must revert assert(false); } catch {} } - // @audit-ok // Test the maximum value case, should not revert, and be close to zero - function inv_test_maximum() public view { + function inv_test_maximum() public { UD60x18 inv_maximum; try this.helpersInv(MAX_UD60x18) { @@ -889,7 +798,7 @@ contract CryticPRBMath60x18Propertiesv3 { // @audit Check precision // Test the minimum value case, should not revert, and be close to zero - function inv_test_minimum() public view { + function inv_test_minimum() public { UD60x18 inv_minimum; try this.helpersInv(UD60x18.wrap(1)) { @@ -901,7 +810,6 @@ contract CryticPRBMath60x18Propertiesv3 { } } - /* ================================================================ TESTS FOR FUNCTION avg() @@ -914,7 +822,6 @@ contract CryticPRBMath60x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test that the result is between the two operands // avg(x, y) >= min(x, y) && avg(x, y) <= max(x, y) function avg_test_values_in_range(UD60x18 x, UD60x18 y) public pure { @@ -927,7 +834,6 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit-ok // Test that the average of the same number is itself // avg(x, x) == x function avg_test_one_value(UD60x18 x) public pure { @@ -936,7 +842,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(avg_x.eq(x)); } - // @audit-ok // Test that the order of operands is irrelevant // avg(x, y) == avg(y, x) function avg_test_operand_order(UD60x18 x, UD60x18 y) public pure { @@ -952,9 +857,8 @@ contract CryticPRBMath60x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test for the maximum value - function avg_test_maximum() public view { + function avg_test_maximum() public { UD60x18 result; // This may revert due to overflow depending on implementation @@ -977,19 +881,17 @@ contract CryticPRBMath60x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test for zero exponent // x ** 0 == 1 - function pow_test_zero_exponent(UD60x18 x) public view { + function pow_test_zero_exponent(UD60x18 x) public { UD60x18 x_pow_0 = pow(x, ZERO_FP); assert(x_pow_0.eq(ONE_FP)); } - // @audit-ok // Test for zero base // 0 ** x == 0 (for positive x) - function pow_test_zero_base(UD60x18 x) public view { + function pow_test_zero_base(UD60x18 x) public { require(x.neq(ZERO_FP)); UD60x18 zero_pow_x = pow(ZERO_FP, x); @@ -997,19 +899,17 @@ contract CryticPRBMath60x18Propertiesv3 { assert(zero_pow_x.eq(ZERO_FP)); } - // @audit-ok // Test for exponent one // x ** 1 == x - function pow_test_one_exponent(UD60x18 x) public view { + function pow_test_one_exponent(UD60x18 x) public { UD60x18 x_pow_1 = pow(x, ONE_FP); assert(x_pow_1.eq(x)); } - // @audit-ok // Test for base one // 1 ** x == 1 - function pow_test_base_one(UD60x18 x) public view { + function pow_test_base_one(UD60x18 x) public { UD60x18 one_pow_x = pow(ONE_FP, x); assert(one_pow_x.eq(ONE_FP)); @@ -1022,14 +922,18 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x, UD60x18 a, UD60x18 b - ) public view { + ) public { require(x.neq(ZERO_FP)); UD60x18 x_a = pow(x, a); UD60x18 x_b = pow(x, b); UD60x18 x_ab = pow(x, a.add(b)); - assert(equal_within_precision(mul(x_a, x_b), x_ab, 10)); + uint256 power = 9; + uint256 digits = 10**power; + + emit PropertyFailed(mul(x_a, x_b), x_ab, power); + assert(equal_most_significant_digits_within_precision(mul(x_a, x_b), x_ab, digits)); } // @audit - fails @@ -1039,14 +943,18 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x, UD60x18 a, UD60x18 b - ) public view { + ) public { require(x.neq(ZERO_FP)); UD60x18 x_a = pow(x, a); UD60x18 x_a_b = pow(x_a, b); UD60x18 x_ab = pow(x, a.mul(b)); - assert(equal_within_precision(x_a_b, x_ab, 10)); + uint256 power = 9; + uint256 digits = 10**power; + + emit PropertyFailed(x_a_b, x_ab, power); + assert(equal_most_significant_digits_within_precision(x_a_b, x_ab, digits)); } // @audit check precision @@ -1056,9 +964,8 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x, UD60x18 y, UD60x18 a - ) public view { + ) public { require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); - // todo this should probably be changed require(a.gt(convert(2 ** 32))); // to avoid massive loss of precision UD60x18 x_y = mul(x, y); @@ -1067,13 +974,17 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_a = pow(x, a); UD60x18 y_a = pow(y, a); - assert(equal_within_precision(mul(x_a, y_a), xy_a, 10)); + uint256 power = 9; + uint256 digits = 10**power; + + emit PropertyFailed(mul(x_a, y_a), xy_a, power); + assert(equal_most_significant_digits_within_precision(mul(x_a, y_a), xy_a, digits)); } // @audit - fails // Test for result being greater than or lower than the argument, depending on // its value and the value of the exponent - function pow_test_values(UD60x18 x, UD60x18 a) public view { + function pow_test_values(UD60x18 x, UD60x18 a) public { require(x.neq(ZERO_FP)); UD60x18 x_a = pow(x, a); @@ -1093,9 +1004,8 @@ contract CryticPRBMath60x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test for maximum base and exponent > 1 - function pow_test_maximum_base(UD60x18 a) public view { + function pow_test_maximum_base(UD60x18 a) public { require(a.gt(ONE_FP)); try this.helpersPow(MAX_UD60x18, a) { @@ -1107,7 +1017,7 @@ contract CryticPRBMath60x18Propertiesv3 { } // Test for maximum exponent and base > 1 - function pow_test_maximum_exponent(UD60x18 x) public view { + function pow_test_maximum_exponent(UD60x18 x) public { require(x.gt(ONE_FP)); try this.helpersPow(x, MAX_PERMITTED_EXPONENT_POW) { @@ -1120,7 +1030,7 @@ contract CryticPRBMath60x18Propertiesv3 { // @audit Check 2**18 // Test for base < 1 and high exponent - function pow_test_high_exponent(UD60x18 x, UD60x18 a) public view { + function pow_test_high_exponent(UD60x18 x, UD60x18 a) public { require(x.lt(ONE_FP) && a.gt(convert(2 ** 64))); UD60x18 result = pow(x, a); @@ -1140,10 +1050,9 @@ contract CryticPRBMath60x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test for the inverse operation // sqrt(x) * sqrt(x) == x - function sqrt_test_inverse_mul(UD60x18 x) public view { + function sqrt_test_inverse_mul(UD60x18 x) public { UD60x18 sqrt_x = sqrt(x); UD60x18 sqrt_x_squared = mul(sqrt_x, sqrt_x); @@ -1157,10 +1066,9 @@ contract CryticPRBMath60x18Propertiesv3 { ); } - // @audit-ok // Test for the inverse operation // sqrt(x) ** 2 == x - function sqrt_test_inverse_pow(UD60x18 x) public view { + function sqrt_test_inverse_pow(UD60x18 x) public { UD60x18 sqrt_x = sqrt(x); UD60x18 sqrt_x_squared = pow(sqrt_x, convert(2)); @@ -1177,7 +1085,7 @@ contract CryticPRBMath60x18Propertiesv3 { // @audit check the precision // Test for distributive property respect to the multiplication // sqrt(x) * sqrt(y) == sqrt(x * y) - function sqrt_test_distributive(UD60x18 x, UD60x18 y) public view { + function sqrt_test_distributive(UD60x18 x, UD60x18 y) public { UD60x18 sqrt_x = sqrt(x); UD60x18 sqrt_y = sqrt(y); UD60x18 sqrt_x_sqrt_y = mul(sqrt_x, sqrt_y); @@ -1200,15 +1108,13 @@ contract CryticPRBMath60x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test for zero case - function sqrt_test_zero() public view { + function sqrt_test_zero() public { assert(sqrt(ZERO_FP).eq(ZERO_FP)); } - // @audit-ok // Test for maximum value - function sqrt_test_maximum() public view { + function sqrt_test_maximum() public { try this.helpersSqrt(MAX_PERMITTED_SQRT) { // Expected behaviour, MAX_SQRT is positive, and operation // should not revert as the result is in range @@ -1233,7 +1139,7 @@ contract CryticPRBMath60x18Propertiesv3 { // @audit check loss // Test for distributive property respect to multiplication // log2(x * y) = log2(x) + log2(y) - function log2_test_distributive_mul(UD60x18 x, UD60x18 y) public view { + function log2_test_distributive_mul(UD60x18 x, UD60x18 y) public { require(x.gte(UNIT) && y.gte(UNIT)); UD60x18 log2_x = log2(x); @@ -1256,14 +1162,19 @@ contract CryticPRBMath60x18Propertiesv3 { // @audit - fails // Test for logarithm of a power // log2(x ** y) = y * log2(x) - function log2_test_power(UD60x18 x, UD60x18 y) public view { + function log2_test_power(UD60x18 x, UD60x18 y) public { require(x.gte(UNIT) && y.gte(UNIT)); UD60x18 x_y = pow(x, y); UD60x18 log2_x_y = log2(x_y); UD60x18 y_log2_x = mul(log2(x), y); - assert(equal_within_precision(y_log2_x, log2_x_y, 4)); + uint256 power = 9; + uint256 digits = 10**power; + + emit PropertyFailed(y_log2_x, log2_x_y, power); + + assert(equal_most_significant_digits_within_precision(y_log2_x, log2_x_y, digits)); } /* ================================================================ @@ -1272,9 +1183,8 @@ contract CryticPRBMath60x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test for zero case, should revert - function log2_test_zero() public view { + function log2_test_zero() public { try this.helpersLog2(ZERO_FP) { // Unexpected, should revert assert(false); @@ -1283,9 +1193,8 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit-ok // Test for maximum value case, should return a positive number - function log2_test_maximum() public view { + function log2_test_maximum() public { UD60x18 result; try this.helpersLog2(MAX_UD60x18) { @@ -1298,9 +1207,8 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit-ok // Test for values less than UNIT, should revert as result would be negative - function log2_test_less_than_unit(UD60x18 x) public view { + function log2_test_less_than_unit(UD60x18 x) public { require(x.lt(UNIT)); try this.helpersLog2(x) { @@ -1326,7 +1234,7 @@ contract CryticPRBMath60x18Propertiesv3 { // @audit check precision // Test for distributive property respect to multiplication // ln(x * y) = ln(x) + ln(y) - function ln_test_distributive_mul(UD60x18 x, UD60x18 y) public view { + function ln_test_distributive_mul(UD60x18 x, UD60x18 y) public { require(x.gte(UNIT) && y.gte(UNIT)); UD60x18 ln_x = ln(x); @@ -1354,7 +1262,11 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 ln_x_y = ln(x_y); UD60x18 y_ln_x = mul(ln(x), y); - assert(equal_within_precision(ln_x_y, y_ln_x, 4)); + uint256 power = 9; + uint256 digits = 10**power; + + emit PropertyFailed(ln_x_y, y_ln_x, power); + assert(equal_most_significant_digits_within_precision(ln_x_y, y_ln_x, digits)); } /* ================================================================ @@ -1363,9 +1275,8 @@ contract CryticPRBMath60x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test for zero case, should revert - function ln_test_zero() public view { + function ln_test_zero() public { try this.helpersLn(ZERO_FP) { // Unexpected, should revert assert(false); @@ -1374,9 +1285,8 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit-ok // Test for maximum value case, should return a positive number - function ln_test_maximum() public view { + function ln_test_maximum() public { UD60x18 result; try this.helpersLn(MAX_UD60x18) { @@ -1389,9 +1299,8 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit-ok // Test for values less than UNIT, should revert since result would be negative - function ln_test_less_than_unit(UD60x18 x) public view { + function ln_test_less_than_unit(UD60x18 x) public { require(x.lt(UNIT)); try this.helpersLn(x) { @@ -1414,10 +1323,9 @@ contract CryticPRBMath60x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test for equality with pow(2, x) for integer x // pow(2, x) == exp2(x) - function exp2_test_equivalence_pow(UD60x18 x) public view { + function exp2_test_equivalence_pow(UD60x18 x) public { require(x.lte(convert(192))); UD60x18 exp2_x = exp2(x); UD60x18 pow_2_x = pow(TWO_FP, x); @@ -1428,14 +1336,15 @@ contract CryticPRBMath60x18Propertiesv3 { // @audit - fails // Test for inverse function // If y = log2(x) then exp2(y) == x - function exp2_test_inverse(UD60x18 x) public view { + function exp2_test_inverse(UD60x18 x) public { UD60x18 log2_x = log2(x); UD60x18 exp2_x = exp2(log2_x); - // todo is this the correct number of bits? - uint256 bits = 18; + uint256 power = 30; + uint256 digits = 10**power; - assert(equal_most_significant_digits_within_precision(x, exp2_x, bits)); + emit PropertyFailed(x, exp2_x, power); + assert(equal_most_significant_digits_within_precision(x, exp2_x, digits)); } /* ================================================================ @@ -1444,18 +1353,16 @@ contract CryticPRBMath60x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test for zero case // exp2(0) == 1 - function exp2_test_zero() public view { + function exp2_test_zero() public { UD60x18 exp_zero = exp2(ZERO_FP); assert(exp_zero.eq(ONE_FP)); } - // @audit-ok // Test for maximum value. This should overflow as it won't fit // in the data type - function exp2_test_maximum() public view { + function exp2_test_maximum() public { try this.helpersExp2(convert(192)) { // Unexpected, should revert assert(false); @@ -1479,14 +1386,16 @@ contract CryticPRBMath60x18Propertiesv3 { // @audit - fails // Test for inverse function // If y = ln(x) then exp(y) == x - function exp_test_inverse(UD60x18 x) public view { + function exp_test_inverse(UD60x18 x) public { UD60x18 ln_x = ln(x); UD60x18 exp_x = exp(ln_x); UD60x18 log2_x = log2(x); - uint256 bits = 18; + uint256 power = 16; + uint256 digits = 10**power; - assert(equal_most_significant_digits_within_precision(x, exp_x, bits)); + emit PropertyFailed(x, exp_x, power); + assert(equal_most_significant_digits_within_precision(x, exp_x, digits)); } /* ================================================================ @@ -1495,18 +1404,16 @@ contract CryticPRBMath60x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test for zero case // exp(0) == 1 - function exp_test_zero() public view { + function exp_test_zero() public { UD60x18 exp_zero = exp(ZERO_FP); assert(exp_zero.eq(ONE_FP)); } - // @audit-ok // Test for maximum value. This should overflow as it won't fit // in the data type - function exp_test_maximum() public view { + function exp_test_maximum() public { try this.helpersExp(convert(192)) { // Unexpected, should revert assert(false); @@ -1527,19 +1434,17 @@ contract CryticPRBMath60x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit-ok // Test for zero exponent // x ** 0 == 1 - function powu_test_zero_exponent(UD60x18 x) public view { + function powu_test_zero_exponent(UD60x18 x) public { UD60x18 x_pow_0 = powu(x, 0); assert(x_pow_0.eq(ONE_FP)); } - // @audit-ok // Test for zero base // 0 ** x == 0 - function powu_test_zero_base(uint256 a) public view { + function powu_test_zero_base(uint256 a) public { require(a != 0); UD60x18 zero_pow_a = powu(ZERO_FP, a); @@ -1547,19 +1452,17 @@ contract CryticPRBMath60x18Propertiesv3 { assert(zero_pow_a.eq(ZERO_FP)); } - // @audit-ok // Test for exponent one // x ** 1 == x - function powu_test_one_exponent(UD60x18 x) public view { + function powu_test_one_exponent(UD60x18 x) public { UD60x18 x_pow_1 = powu(x, 1); assert(x_pow_1.eq(x)); } - // @audit-ok // Test for base one // 1 ** x == 1 - function powu_test_base_one(uint256 a) public view { + function powu_test_base_one(uint256 a) public { UD60x18 one_pow_a = powu(ONE_FP, a); assert(one_pow_a.eq(ONE_FP)); @@ -1572,7 +1475,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x, uint256 a, uint256 b - ) public view { + ) public { require(x.neq(ZERO_FP)); UD60x18 x_a = powu(x, a); @@ -1589,7 +1492,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x, uint256 a, uint256 b - ) public view { + ) public { require(x.neq(ZERO_FP)); UD60x18 x_a = powu(x, a); @@ -1606,7 +1509,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x, UD60x18 y, uint256 a - ) public view { + ) public { require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); // todo this should probably be changed require(a > 2 ** 32); // to avoid massive loss of precision @@ -1623,7 +1526,7 @@ contract CryticPRBMath60x18Propertiesv3 { // @audit - fails // Test for result being greater than or lower than the argument, depending on // its value and the value of the exponent - function powu_test_values(UD60x18 x, uint256 a) public view { + function powu_test_values(UD60x18 x, uint256 a) public { require(x.neq(ZERO_FP)); UD60x18 x_a = powu(x, a); @@ -1643,10 +1546,8 @@ contract CryticPRBMath60x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - - // @audit-ok // Test for maximum base and exponent > 1 - function powu_test_maximum_base(uint256 a) public view { + function powu_test_maximum_base(uint256 a) public { require(a > 1); try this.helpersPowu(MAX_UD60x18, a) { @@ -1659,7 +1560,7 @@ contract CryticPRBMath60x18Propertiesv3 { // @audit Check 2**64 // Test for base < 1 and high exponent - function powu_test_high_exponent(UD60x18 x, uint256 a) public view { + function powu_test_high_exponent(UD60x18 x, uint256 a) public { require(x.lt(ONE_FP) && a > 2 ** 64); UD60x18 result = powu(x, a); @@ -1682,7 +1583,7 @@ contract CryticPRBMath60x18Propertiesv3 { // @audit check loss // Test for distributive property respect to multiplication // log10(x * y) = log10(x) + log10(y) - function log10_test_distributive_mul(UD60x18 x, UD60x18 y) public view { + function log10_test_distributive_mul(UD60x18 x, UD60x18 y) public { require(x.gte(UNIT) && y.gte(UNIT)); UD60x18 log10_x = log10(x); UD60x18 log10_y = log10(y); @@ -1710,11 +1611,11 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 log10_x_y = log10(x_y); UD60x18 y_log10_x = mul(log10(x), y); - if(!equal_within_precision(log10_x_y, y_log10_x, 18)){ - emit AssertionFailed(log10_x_y, y_log10_x); - } + uint256 power = 9; + uint256 digits = 10**power; - assert(equal_within_precision(log10_x_y, y_log10_x, 10)); + emit PropertyFailed(log10_x_y, y_log10_x, power); + assert(equal_most_significant_digits_within_precision(log10_x_y, y_log10_x, digits)); } /* ================================================================ @@ -1723,9 +1624,8 @@ contract CryticPRBMath60x18Propertiesv3 { behaves correctly on edge cases ================================================================ */ - // @audit-ok // Test for zero case, should revert - function log10_test_zero() public view { + function log10_test_zero() public { try this.helpersLog10(ZERO_FP) { // Unexpected, should revert assert(false); @@ -1734,9 +1634,8 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit-ok // Test for maximum value case, should return a positive number - function log10_test_maximum() public view { + function log10_test_maximum() public { UD60x18 result; try this.helpersLog10(MAX_UD60x18) { @@ -1749,9 +1648,8 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit-ok // Test for values less than UNIT, should revert as result would be negative - function log10_test_less_than_unit(UD60x18 x) public view { + function log10_test_less_than_unit(UD60x18 x) public { require(x.lt(UNIT)); try this.helpersLog10(x) { @@ -1761,5 +1659,4 @@ contract CryticPRBMath60x18Propertiesv3 { // Expected } } - } \ No newline at end of file From fd38c7e54f5118d00d2133ac738286b82517acab Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Mon, 15 May 2023 10:30:02 +0200 Subject: [PATCH 13/32] revert formatting changes --- .../ABDKMath64x64PropertyTests.sol | 2 +- hardhat.config.js | 96 +++++++++---------- package.json | 73 +++++++------- 3 files changed, 85 insertions(+), 86 deletions(-) diff --git a/contracts/Math/ABDKMath64x64/ABDKMath64x64PropertyTests.sol b/contracts/Math/ABDKMath64x64/ABDKMath64x64PropertyTests.sol index 5bbec4d..3246adc 100644 --- a/contracts/Math/ABDKMath64x64/ABDKMath64x64PropertyTests.sol +++ b/contracts/Math/ABDKMath64x64/ABDKMath64x64PropertyTests.sol @@ -1314,7 +1314,7 @@ contract CryticABDKMath64x64Properties { // Test for power of a product // (x * y) ** a == x ** a * y ** a - function pow_test_product_power( + function pow_test_product_same_base( int128 x, int128 y, uint256 a diff --git a/hardhat.config.js b/hardhat.config.js index 8eca546..8244d9d 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -2,52 +2,52 @@ const urlOverride = process.env.ETH_PROVIDER_URL; const chainId = parseInt(process.env.CHAIN_ID ?? "31337", 10); module.exports = { - paths: { - artifacts: "./artifacts", - sources: "./contracts", - tests: "./tests", - }, - solidity: { - compilers: [ - { - version: "0.8.1", - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, - { - version: "0.8.17", - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, - { - version: "0.8.19", - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, - ], - }, - defaultNetwork: "hardhat", - networks: { - hardhat: { - chainId, - loggingEnabled: false, - saveDeployments: false, - }, - localhost: { - chainId, - url: urlOverride || "http://localhost:8545", - }, - }, + paths: { + artifacts: "./artifacts", + sources: "./contracts", + tests: "./tests", + }, + solidity: { + compilers: [ + { + version: "0.8.1", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + { + version: "0.8.17", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + { + version: "0.8.19", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + ], + }, + defaultNetwork: "hardhat", + networks: { + hardhat: { + chainId, + loggingEnabled: false, + saveDeployments: false, + }, + localhost: { + chainId, + url: urlOverride || "http://localhost:8545", + }, + }, }; diff --git a/package.json b/package.json index aeec3de..17e68b0 100644 --- a/package.json +++ b/package.json @@ -1,39 +1,38 @@ { - "name": "@crytic/properties", - "version": "0.0.1", - "description": "Pre-made invariants for fuzz testing smart-contracts", - "main": "index.js", - "scripts": { - "compile": "hardhat compile", - "test": "echo \"Error: no test specified\" && exit 1", - "format": "prettier --write . && npm run format-embedded-solidity", - "format-embedded-solidity": "prettier --write \"**/*.md\" --embedded-language-formatting=auto --plugin prettier-plugin-solidity --tab-width 4 --print-width 120 && prettier --write \"**/*.md\"", - "lint": "npm run lint-check-format && npm run lint-check-links", - "lint-check-format": "prettier --check .", - "lint-check-links": "find . -name '*.md' -not -path './node_modules/*' -print0 | xargs -0 -n1 markdown-link-check", - "echidna-prb-ud": "echidna . --contract CryticPRBMath60x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml", - "echidna-prb-sd": "echidna . --contract CryticPRBMath59x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml", - "echidna-abdk": "echidna . --contract CryticABDKMath64x64Properties --config ./contracts/Math/echidna-config.yaml" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/crytic/properties.git" - }, - "author": "Trail of Bits", - "license": "MIT", - "bugs": { - "url": "https://github.com/crytic/properties/issues" - }, - "homepage": "https://github.com/crytic/properties#readme", - "dependencies": { - "@openzeppelin/contracts": "^4.7.3", - "@prb/math": "^4.0.0", - "markdown-link-check": "^3.11.0", - "prettier": "^2.8.7", - "prettier-plugin-solidity": "^1.1.3", - "solmate": "^6.6.1" - }, - "devDependencies": { - "hardhat": "^2.9.3" - } + "name": "@crytic/properties", + "version": "0.0.1", + "description": "Pre-made invariants for fuzz testing smart-contracts", + "main": "index.js", + "scripts": { + "compile": "hardhat compile", + "test": "echo \"Error: no test specified\" && exit 1", + "format": "prettier --write . && npm run format-embedded-solidity", + "format-embedded-solidity": "prettier --write \"**/*.md\" --embedded-language-formatting=auto --plugin prettier-plugin-solidity --tab-width 4 --print-width 120 && prettier --write \"**/*.md\"", + "lint": "npm run lint-check-format && npm run lint-check-links", + "lint-check-format": "prettier --check .", + "lint-check-links": "find . -name '*.md' -not -path './node_modules/*' -print0 | xargs -0 -n1 markdown-link-check", + "echidna-prb-ud": "echidna . --contract CryticPRBMath60x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml", + "echidna-prb-sd": "echidna . --contract CryticPRBMath59x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml", + "echidna-abdk": "echidna . --contract CryticABDKMath64x64Properties --config ./contracts/Math/echidna-config.yaml" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/crytic/properties.git" + }, + "author": "Trail of Bits", + "license": "MIT", + "bugs": { + "url": "https://github.com/crytic/properties/issues" + }, + "homepage": "https://github.com/crytic/properties#readme", + "dependencies": { + "@openzeppelin/contracts": "^4.7.3", + "markdown-link-check": "^3.11.0", + "prettier": "^2.8.7", + "prettier-plugin-solidity": "^1.1.3", + "solmate": "^6.6.1" + }, + "devDependencies": { + "hardhat": "^2.9.3" + } } From 413b76f3d5f5df1323541318e81330906ee59c67 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Mon, 15 May 2023 10:31:34 +0200 Subject: [PATCH 14/32] revert formatting changes --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 17e68b0..238548d 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,9 @@ "lint": "npm run lint-check-format && npm run lint-check-links", "lint-check-format": "prettier --check .", "lint-check-links": "find . -name '*.md' -not -path './node_modules/*' -print0 | xargs -0 -n1 markdown-link-check", - "echidna-prb-ud": "echidna . --contract CryticPRBMath60x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml", - "echidna-prb-sd": "echidna . --contract CryticPRBMath59x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml", - "echidna-abdk": "echidna . --contract CryticABDKMath64x64Properties --config ./contracts/Math/echidna-config.yaml" + "echidna-prb-ud": "echidna . --contract CryticPRBMath60x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml", + "echidna-prb-sd": "echidna . --contract CryticPRBMath59x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml", + "echidna-abdk": "echidna . --contract CryticABDKMath64x64Properties --config ./contracts/Math/echidna-config.yaml" }, "repository": { "type": "git", From 2c51fed4122aac2d2d98feb46cde334af3eea7b5 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Mon, 15 May 2023 11:30:42 +0200 Subject: [PATCH 15/32] fix foundry remappings bug --- remappings.txt | 2 +- tests/ERC20/foundry/remappings.txt | 3 ++- tests/ERC4626/foundry/remappings.txt | 3 ++- tests/ERC721/foundry/remappings.txt | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/remappings.txt b/remappings.txt index 42051ce..eac4464 100644 --- a/remappings.txt +++ b/remappings.txt @@ -6,4 +6,4 @@ forge-std/=lib/forge-std/src/ @openzeppelin/=lib/openzeppelin-contracts/ @prb/math/=lib/prb-math/ prb-test/=lib/prb-math/lib/prb-test/src/ -solmate/=lib/solmate/ +solmate/=lib/solmate/src/ diff --git a/tests/ERC20/foundry/remappings.txt b/tests/ERC20/foundry/remappings.txt index 473e7d7..ccc1909 100644 --- a/tests/ERC20/foundry/remappings.txt +++ b/tests/ERC20/foundry/remappings.txt @@ -1,4 +1,5 @@ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ properties/=../../../contracts/ -@openzeppelin/=../../../lib/openzeppelin-contracts/ \ No newline at end of file +@openzeppelin/=../../../lib/openzeppelin-contracts/ +src/=src/ \ No newline at end of file diff --git a/tests/ERC4626/foundry/remappings.txt b/tests/ERC4626/foundry/remappings.txt index 25aeca5..b1f11f9 100644 --- a/tests/ERC4626/foundry/remappings.txt +++ b/tests/ERC4626/foundry/remappings.txt @@ -1,3 +1,4 @@ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ -properties/=../../../contracts/ \ No newline at end of file +properties/=../../../contracts/ +src/=src/ \ No newline at end of file diff --git a/tests/ERC721/foundry/remappings.txt b/tests/ERC721/foundry/remappings.txt index 473e7d7..ccc1909 100644 --- a/tests/ERC721/foundry/remappings.txt +++ b/tests/ERC721/foundry/remappings.txt @@ -1,4 +1,5 @@ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ properties/=../../../contracts/ -@openzeppelin/=../../../lib/openzeppelin-contracts/ \ No newline at end of file +@openzeppelin/=../../../lib/openzeppelin-contracts/ +src/=src/ \ No newline at end of file From fea861e8658691e4b93fb4da034b9c81de9d08f0 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Mon, 15 May 2023 11:40:48 +0200 Subject: [PATCH 16/32] fix solmate remapping --- remappings.txt | 2 +- tests/ERC4626/foundry/remappings.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/remappings.txt b/remappings.txt index eac4464..42051ce 100644 --- a/remappings.txt +++ b/remappings.txt @@ -6,4 +6,4 @@ forge-std/=lib/forge-std/src/ @openzeppelin/=lib/openzeppelin-contracts/ @prb/math/=lib/prb-math/ prb-test/=lib/prb-math/lib/prb-test/src/ -solmate/=lib/solmate/src/ +solmate/=lib/solmate/ diff --git a/tests/ERC4626/foundry/remappings.txt b/tests/ERC4626/foundry/remappings.txt index b1f11f9..c3ae406 100644 --- a/tests/ERC4626/foundry/remappings.txt +++ b/tests/ERC4626/foundry/remappings.txt @@ -1,4 +1,5 @@ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ properties/=../../../contracts/ +solmate/=../../../lib/solmate/src/ src/=src/ \ No newline at end of file From 3800c1a42e1bfbfe68ab2229a3f65773a39cd2aa Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Mon, 15 May 2023 11:45:40 +0200 Subject: [PATCH 17/32] separate corpus for math libraries --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 238548d..bce45c2 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,9 @@ "lint": "npm run lint-check-format && npm run lint-check-links", "lint-check-format": "prettier --check .", "lint-check-links": "find . -name '*.md' -not -path './node_modules/*' -print0 | xargs -0 -n1 markdown-link-check", - "echidna-prb-ud": "echidna . --contract CryticPRBMath60x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml", - "echidna-prb-sd": "echidna . --contract CryticPRBMath59x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml", - "echidna-abdk": "echidna . --contract CryticABDKMath64x64Properties --config ./contracts/Math/echidna-config.yaml" + "echidna-prb-ud": "echidna . --contract CryticPRBMath60x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-prb-ud", + "echidna-prb-sd": "echidna . --contract CryticPRBMath59x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-prb-sd", + "echidna-abdk": "echidna . --contract CryticABDKMath64x64Properties --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-abdk-64" }, "repository": { "type": "git", From 527ddf7aae8425d2c6cce6f5a1792cb87e1f4bbd Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Tue, 16 May 2023 11:54:53 +0200 Subject: [PATCH 18/32] improve coverage, change bounds --- .../v3/PRBMathSD59x18PropertyTests.sol | 423 ++++++++++-------- .../v3/PRBMathUD60x18PropertyTests.sol | 161 +++---- 2 files changed, 283 insertions(+), 301 deletions(-) diff --git a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol index 051b6be..c966444 100644 --- a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol @@ -12,6 +12,7 @@ contract CryticPRBMath59x18Propertiesv3 { event PropertyFailed(SD59x18 result); event PropertyFailed(SD59x18 result1, SD59x18 result2); event PropertyFailed(SD59x18 result1, SD59x18 result2, uint256 discardedDigits); + event PropertyFailed(SD59x18 result1, SD59x18 result2, SD59x18 discardedDigits); event TestLog(int256 num1, int256 num2, int256 result); /* ================================================================ @@ -70,7 +71,8 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 internal constant MAX_PERMITTED_EXP = SD59x18.wrap(133_084258667509499440); SD59x18 internal constant MIN_PERMITTED_EXP = SD59x18.wrap(-41_446531673892822322); - + + SD59x18 internal constant MAX_PERMITTED_POW = SD59x18.wrap(2 ** 192 * 10 ** 18 - 1); /// @dev Half the UNIT number. int256 constant uHALF_UNIT = 0.5e18; SD59x18 constant HALF_UNIT = SD59x18.wrap(uHALF_UNIT); @@ -118,9 +120,11 @@ contract CryticPRBMath59x18Propertiesv3 { // This function determines if the relative error between a and b is less // than error_percent % (expressed as a 59x18 value) // Uses functions from the library under test! - function equal_within_tolerance(SD59x18 a, SD59x18 b, SD59x18 error_percent) public pure returns(bool) { + function equal_within_tolerance(SD59x18 a, SD59x18 b, SD59x18 error_percent) public returns(bool) { SD59x18 tol_value = abs(mul(a, div(error_percent, convert(100)))); + require(tol_value.neq(ZERO_FP)); + emit PropertyFailed(a, b, tol_value); return (lte(abs(sub(b, a)), tol_value)); } @@ -161,31 +165,6 @@ contract CryticPRBMath59x18Propertiesv3 { return ((larger - smaller) <= 1); } - // Return the i most significant bits from |n|. If n has less than i significant bits, return |n| - // Uses functions from the library under test! - function most_significant_bits( - SD59x18 n, - uint256 i - ) public view returns (uint256) { - if (n.eq(MIN_SD59x18)) return 0; - - // Create a mask consisting of i bits set to 1 - uint256 mask = (2 ** i) - 1; - - // Get the positive value of n - uint256 value = (n.gt(ZERO_FP)) ? intoUint256(n) : intoUint256(neg(n)); - - // Get the position of the MSB set to 1 of n - uint256 pos = msb(value); - - // Shift the mask to match the rightmost 1-set bit - if (pos > i) { - mask <<= (pos - i); - } - - return (value & mask); - } - /* ================================================================ Library wrappers. These functions allow calling the PRBMathSD59x18 library. @@ -265,6 +244,12 @@ contract CryticPRBMath59x18Propertiesv3 { return floor(x); } + /* ================================================================ + Bounding helpers + ================================================================ */ + + + /* ================================================================ TESTS FOR FUNCTION add() @@ -299,7 +284,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for identity operation // x + 0 == x (equivalent to x + (-x) == 0) - function add_test_identity(SD59x18 x) public view { + function add_test_identity(SD59x18 x) public { SD59x18 x_0 = x.add(ZERO_FP); assert(x.eq(x_0)); @@ -308,7 +293,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Test that the result increases or decreases depending // on the value to be added - function add_test_values(SD59x18 x, SD59x18 y) public view { + function add_test_values(SD59x18 x, SD59x18 y) public { SD59x18 x_y = x.add(y); if (y.gte(ZERO_FP)) { @@ -326,7 +311,7 @@ contract CryticPRBMath59x18Propertiesv3 { // The result of the addition must be between the maximum // and minimum allowed values for SD59x18 - function add_test_range(SD59x18 x, SD59x18 y) public view { + function add_test_range(SD59x18 x, SD59x18 y) public { try this.helpersAdd(x, y) returns (SD59x18 result) { assert(result.lte(MAX_SD59x18) && result.gte(MIN_SD59x18)); } catch { @@ -336,7 +321,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Adding zero to the maximum value shouldn't revert, as it is valid // Moreover, the result must be MAX_SD59x18 - function add_test_maximum_value() public view { + function add_test_maximum_value() public { try this.helpersAdd(MAX_SD59x18, ZERO_FP) returns (SD59x18 result) { // Expected behaviour, does not revert assert(result.eq(MAX_SD59x18)); @@ -346,7 +331,7 @@ contract CryticPRBMath59x18Propertiesv3 { } // Adding one to the maximum value should revert, as it is out of range - function add_test_maximum_value_plus_one() public view { + function add_test_maximum_value_plus_one() public { try this.helpersAdd(MAX_SD59x18, ONE_FP) { assert(false); } catch { @@ -356,7 +341,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Adding zero to the minimum value shouldn't revert, as it is valid // Moreover, the result must be MIN_SD59x18 - function add_test_minimum_value() public view { + function add_test_minimum_value() public { try this.helpersAdd(MIN_SD59x18, ZERO_FP) returns (SD59x18 result) { // Expected behaviour, does not revert assert(result.eq(MIN_SD59x18)); @@ -366,7 +351,7 @@ contract CryticPRBMath59x18Propertiesv3 { } // Adding minus one to the maximum value should revert, as it is out of range - function add_test_minimum_value_plus_negative_one() public view { + function add_test_minimum_value_plus_negative_one() public { try this.helpersAdd(MIN_SD59x18, MINUS_ONE_FP) { assert(false); } catch { @@ -407,7 +392,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for identity operation // x - 0 == x (equivalent to x - x == 0) - function sub_test_identity(SD59x18 x) public view { + function sub_test_identity(SD59x18 x) public { SD59x18 x_0 = x.sub(ZERO_FP); assert(x_0.eq(x)); @@ -429,7 +414,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Test that the result increases or decreases depending // on the value to be subtracted - function sub_test_values(SD59x18 x, SD59x18 y) public view { + function sub_test_values(SD59x18 x, SD59x18 y) public { SD59x18 x_y = x.sub(y); if (y.gte(ZERO_FP)) { @@ -447,7 +432,7 @@ contract CryticPRBMath59x18Propertiesv3 { // The result of the subtraction must be between the maximum // and minimum allowed values for SD59x18 - function sub_test_range(SD59x18 x, SD59x18 y) public view { + function sub_test_range(SD59x18 x, SD59x18 y) public { try this.helpersSub(x, y) returns (SD59x18 result) { assert(result.lte(MAX_SD59x18) && result.gte(MIN_SD59x18)); } catch { @@ -457,7 +442,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Subtracting zero from the maximum value shouldn't revert, as it is valid // Moreover, the result must be MAX_SD59x18 - function sub_test_maximum_value() public view { + function sub_test_maximum_value() public { try this.helpersSub(MAX_SD59x18, ZERO_FP) returns (SD59x18 result) { // Expected behaviour, does not revert assert(result.eq(MAX_SD59x18)); @@ -468,7 +453,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Subtracting minus one from the maximum value should revert, // as it is out of range - function sub_test_maximum_value_minus_neg_one() public view { + function sub_test_maximum_value_minus_neg_one() public { try this.helpersSub(MAX_SD59x18, MINUS_ONE_FP) { assert(false); } catch { @@ -478,7 +463,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Subtracting zero from the minimum value shouldn't revert, as it is valid // Moreover, the result must be MIN_SD59x18 - function sub_test_minimum_value() public view { + function sub_test_minimum_value() public { try this.helpersSub(MIN_SD59x18, ZERO_FP) returns (SD59x18 result) { // Expected behaviour, does not revert assert(result.eq(MIN_SD59x18)); @@ -488,7 +473,7 @@ contract CryticPRBMath59x18Propertiesv3 { } // Subtracting one from the minimum value should revert, as it is out of range - function sub_test_minimum_value_minus_one() public view { + function sub_test_minimum_value_minus_one() public { try this.helpersSub(MIN_SD59x18, ONE_FP) { assert(false); } catch { @@ -511,7 +496,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for commutative property // x * y == y * x function mul_test_commutative(SD59x18 x, SD59x18 y) public pure { - require(x.neq(MIN_SD59x18) && y.neq(MIN_SD59x18)); + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); SD59x18 x_y = x.mul(y); SD59x18 y_x = y.mul(x); @@ -521,35 +506,29 @@ contract CryticPRBMath59x18Propertiesv3 { // @audit - No guarantee of precision // Test for associative property // (x * y) * z == x * (y * z) - function mul_test_associative(SD59x18 x, SD59x18 y, SD59x18 z) public view { + function mul_test_associative(SD59x18 x, SD59x18 y, SD59x18 z) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18) && z.gt(MIN_SD59x18)); SD59x18 x_y = x.mul(y); SD59x18 y_z = y.mul(z); SD59x18 xy_z = x_y.mul(z); SD59x18 x_yz = x.mul(y_z); - // Failure if all significant digits are lost - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); - require(significant_digits_after_mult(y, z) > REQUIRED_SIGNIFICANT_DIGITS); - require(significant_digits_after_mult(x_y, z) > REQUIRED_SIGNIFICANT_DIGITS); - require(significant_digits_after_mult(x, y_z) > REQUIRED_SIGNIFICANT_DIGITS); - + require(xy_z.neq(ZERO_FP) && x_yz.neq(ZERO_FP)); assert(equal_within_tolerance(xy_z, x_yz, ONE_TENTH_FP)); } // @audit - No guarantee of precision // Test for distributive property // x * (y + z) == x * y + x * z - function mul_test_distributive(SD59x18 x, SD59x18 y, SD59x18 z) public view { + function mul_test_distributive(SD59x18 x, SD59x18 y, SD59x18 z) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18) && z.gt(MIN_SD59x18)); SD59x18 y_plus_z = y.add(z); SD59x18 x_times_y_plus_z = x.mul(y_plus_z); SD59x18 x_times_y = x.mul(y); SD59x18 x_times_z = x.mul(z); - // Failure if all significant digits are lost - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); - require(significant_digits_after_mult(x, z) > REQUIRED_SIGNIFICANT_DIGITS); - require(significant_digits_after_mult(x, y_plus_z) > REQUIRED_SIGNIFICANT_DIGITS); + require(add(x_times_y, x_times_z).neq(ZERO_FP) && x_times_y_plus_z.neq(ZERO_FP)); assert(equal_within_tolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP)); } @@ -557,7 +536,8 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for identity operation // x * 1 == x (also check that x * 0 == 0) - function mul_test_identity(SD59x18 x) public view { + function mul_test_identity(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); SD59x18 x_1 = x.mul(ONE_FP); SD59x18 x_0 = x.mul(ZERO_FP); @@ -565,28 +545,32 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x_1.eq(x)); } - // @audit - No guarantee of precision - // Test that the result increases or decreases depending - // on the value to be added - function mul_test_values(SD59x18 x, SD59x18 y) public view { - require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); + + // If x is positive and y is >= 1, the result should be larger than or equal to x + // If x is positive and y is < 1, the result should be smaller than x + function mul_test_x_positive(SD59x18 x, SD59x18 y) public { + require(x.gte(ZERO_FP)); SD59x18 x_y = x.mul(y); - //require(significant_digits_lost_in_mult(x, y) == false); + if (y.gte(ONE_FP)) { + assert(x_y.gte(x)); + } else { + assert(x_y.lte(x)); + } + } + + // If x is negative and y is >= 1, the result should be smaller than or equal to x + // If x is negative and y is < 1, the result should be larger than or equal to x + function mul_test_x_negative(SD59x18 x, SD59x18 y) public { + require(x.lte(ZERO_FP) && y.neq(ZERO_FP)); - if (x.gte(ZERO_FP)) { - if (y.gte(ONE_FP)) { - assert(x_y.gte(x)); - } else { - assert(x_y.lte(x)); - } + SD59x18 x_y = x.mul(y); + + if (y.gte(ONE_FP)) { + assert(x_y.lte(x)); } else { - if (y.gte(ONE_FP)) { - assert(x_y.lte(x)); - } else { - assert(x_y.gte(x)); - } + assert(x_y.gte(x)); } } @@ -598,7 +582,9 @@ contract CryticPRBMath59x18Propertiesv3 { // The result of the multiplication must be between the maximum // and minimum allowed values for SD59x18 - function mul_test_range(SD59x18 x, SD59x18 y) public view { + function mul_test_range(SD59x18 x, SD59x18 y) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + try this.helpersMul(x, y) returns(SD59x18 result) { assert(result.lte(MAX_SD59x18) && result.gte(MIN_SD59x18)); } catch { @@ -608,7 +594,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Multiplying the maximum value times one shouldn't revert, as it is valid // Moreover, the result must be MAX_SD59x18 - function mul_test_maximum_value() public view { + function mul_test_maximum_value() public { try this.helpersMul(MAX_SD59x18, ONE_FP) returns (SD59x18 result) { // Expected behaviour, does not revert assert(result.eq(MAX_SD59x18)); @@ -619,7 +605,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Multiplying the minimum value times one shouldn't revert, as it is valid // Moreover, the result must be MIN_SD59x18 - function mul_test_minimum_value() public view { + function mul_test_minimum_value() public { try this.helpersMul(MIN_SD59x18.add(ONE_FP), ONE_FP) returns (SD59x18 result) { // Expected behaviour, does not revert assert(result.eq(MIN_SD59x18.add(ONE_FP))); @@ -643,7 +629,8 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for identity property // x / 1 == x (equivalent to x / x == 1) // Moreover, x/x should not revert unless x == 0 - function div_test_division_identity(SD59x18 x) public view { + function div_test_division_identity(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); SD59x18 div_1 = div(x, ONE_FP); assert(x.eq(div_1)); @@ -664,7 +651,8 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for negative divisor // x / -y == -(x / y) - function div_test_negative_divisor(SD59x18 x, SD59x18 y) public view { + function div_test_negative_divisor(SD59x18 x, SD59x18 y) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); require(y.lt(ZERO_FP)); SD59x18 x_y = div(x, y); @@ -675,7 +663,8 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for division with 0 as numerator // 0 / x = 0 - function div_test_division_num_zero(SD59x18 x) public view { + function div_test_division_num_zero(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); require(x.neq(ZERO_FP)); SD59x18 div_0 = div(ZERO_FP, x); @@ -685,7 +674,8 @@ contract CryticPRBMath59x18Propertiesv3 { // Test that the absolute value of the result increases or // decreases depending on the denominator's absolute value - function div_test_values(SD59x18 x, SD59x18 y) public view { + function div_test_values(SD59x18 x, SD59x18 y) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); require(y.neq(ZERO_FP)); SD59x18 x_y = abs(div(x, y)); @@ -705,7 +695,8 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for division by zero - function div_test_div_by_zero(SD59x18 x) public view { + function div_test_div_by_zero(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); try this.helpersDiv(x, ZERO_FP) { // Unexpected, this should revert assert(false); @@ -715,7 +706,8 @@ contract CryticPRBMath59x18Propertiesv3 { } // Test for division by a large value, the result should be less than one - function div_test_maximum_denominator(SD59x18 x) public view { + function div_test_maximum_denominator(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); SD59x18 div_large = div(x, MAX_SD59x18); assert(abs(div_large).lte(ONE_FP)); @@ -723,21 +715,22 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for division of a large value // This should revert if |x| < 1 as it would return a value higher than max - function div_test_maximum_numerator(SD59x18 x) public view { + function div_test_maximum_numerator(SD59x18 y) public { SD59x18 div_large; - try this.helpersDiv(MAX_SD59x18, x) { + try this.helpersDiv(MAX_SD59x18, y) { // If it didn't revert, then |x| >= 1 - div_large = div(MAX_SD59x18, x); + div_large = div(MAX_SD59x18, y); - assert(abs(x).gte(ONE_FP)); + assert(abs(y).gte(ONE_FP)); } catch { // Expected revert as result is higher than max } } // Test for values in range - function div_test_range(SD59x18 x, SD59x18 y) public view { + function div_test_range(SD59x18 x, SD59x18 y) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); SD59x18 result; try this.helpersDiv(x, y) { @@ -771,7 +764,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for the identity operation // x + (-x) == 0 - function neg_test_identity(SD59x18 x) public view { + function neg_test_identity(SD59x18 x) public { SD59x18 neg_x = neg(x); assert(add(x, neg_x).eq(ZERO_FP)); @@ -785,7 +778,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for the zero-case // -0 == 0 - function neg_test_zero() public view { + function neg_test_zero() public { SD59x18 neg_x = neg(ZERO_FP); assert(neg_x.eq(ZERO_FP)); @@ -794,7 +787,7 @@ contract CryticPRBMath59x18Propertiesv3 { // @audit todo check what is used for SD59x18 // Test for the maximum value case // Since this is implementation-dependant, we will actually test with MAX_SD59x18-EPS - function neg_test_maximum() public view { + function neg_test_maximum() public { try this.neg(sub(MAX_SD59x18, EPSILON)) { // Expected behaviour, does not revert } catch { @@ -805,7 +798,7 @@ contract CryticPRBMath59x18Propertiesv3 { // @audit todo check what is used for SD59x18 // Test for the minimum value case // Since this is implementation-dependant, we will actually test with MIN_SD59x18+EPS - function neg_test_minimum() public view { + function neg_test_minimum() public { try this.neg(add(MIN_SD59x18, EPSILON)) { // Expected behaviour, does not revert } catch { @@ -827,7 +820,8 @@ contract CryticPRBMath59x18Propertiesv3 { // Test that the absolute value is always positive - function abs_test_positive(SD59x18 x) public view { + function abs_test_positive(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); SD59x18 abs_x = abs(x); assert(abs_x.gte(ZERO_FP)); @@ -836,6 +830,8 @@ contract CryticPRBMath59x18Propertiesv3 { // Test that the absolute value of a number equals the // absolute value of the negative of the same number function abs_test_negative(SD59x18 x) public pure { + require(x.gt(MIN_SD59x18)); + SD59x18 abs_x = abs(x); SD59x18 abs_minus_x = abs(neg(x)); @@ -846,6 +842,8 @@ contract CryticPRBMath59x18Propertiesv3 { // Test the multiplicativeness property // | x * y | == |x| * |y| function abs_test_multiplicativeness(SD59x18 x, SD59x18 y) public pure { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + SD59x18 abs_x = abs(x); SD59x18 abs_y = abs(y); SD59x18 abs_xy = abs(mul(x, y)); @@ -861,6 +859,8 @@ contract CryticPRBMath59x18Propertiesv3 { // Test the subadditivity property // | x + y | <= |x| + |y| function abs_test_subadditivity(SD59x18 x, SD59x18 y) public pure { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + SD59x18 abs_x = abs(x); SD59x18 abs_y = abs(y); SD59x18 abs_xy = abs(add(x, y)); @@ -875,7 +875,7 @@ contract CryticPRBMath59x18Propertiesv3 { ================================================================ */ // Test the zero-case | 0 | = 0 - function abs_test_zero() public view { + function abs_test_zero() public { SD59x18 abs_zero; try this.helpersAbs(ZERO_FP) { @@ -889,7 +889,7 @@ contract CryticPRBMath59x18Propertiesv3 { } // Test the maximum value - function abs_test_maximum() public view { + function abs_test_maximum() public { SD59x18 abs_max; try this.helpersAbs(MAX_SD59x18) { @@ -900,7 +900,7 @@ contract CryticPRBMath59x18Propertiesv3 { } // Test the minimum value - function abs_test_minimum_revert() public view { + function abs_test_minimum_revert() public { SD59x18 abs_min; try this.helpersAbs(MIN_SD59x18) { @@ -910,9 +910,9 @@ contract CryticPRBMath59x18Propertiesv3 { } // Test the minimum value - function abs_test_minimum_allowed() public view { + function abs_test_minimum_allowed() public { SD59x18 abs_min; - SD59x18 input = MIN_SD59x18.add(ONE_FP); + SD59x18 input = MIN_SD59x18.add(SD59x18.wrap(1)); try this.helpersAbs(input) { // If it doesn't revert, the value must be the negative of MIN_SD59x18 + 1 @@ -935,7 +935,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Test that the inverse of the inverse is close enough to the // original number - function inv_test_double_inverse(SD59x18 x) public view { + function inv_test_double_inverse(SD59x18 x) public { require(x.neq(ZERO_FP)); SD59x18 double_inv_x = inv(inv(x)); @@ -947,7 +947,7 @@ contract CryticPRBMath59x18Propertiesv3 { } // Test equivalence with division - function inv_test_division(SD59x18 x) public view { + function inv_test_division(SD59x18 x) public { require(x.neq(ZERO_FP)); SD59x18 inv_x = inv(x); @@ -962,25 +962,25 @@ contract CryticPRBMath59x18Propertiesv3 { function inv_test_division_noncommutativity( SD59x18 x, SD59x18 y - ) public view { + ) public { require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); SD59x18 x_y = div(x, y); SD59x18 y_x = div(y, x); - require( +/* require( significant_digits_after_mult(x, inv(y)) > REQUIRED_SIGNIFICANT_DIGITS ); require( significant_digits_after_mult(y, inv(x)) > REQUIRED_SIGNIFICANT_DIGITS - ); + ); */ assert(equal_within_tolerance(x_y, inv(y_x), ONE_TENTH_FP)); } // @audit check if loss is correct // Test the multiplication of inverses // 1/(x * y) == 1/x * 1/y - function inv_test_multiplication(SD59x18 x, SD59x18 y) public view { + function inv_test_multiplication(SD59x18 x, SD59x18 y) public { require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); SD59x18 inv_x = inv(x); @@ -990,10 +990,10 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_y = mul(x, y); SD59x18 inv_x_y = inv(x_y); - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + /* require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); require( significant_digits_after_mult(inv_x, inv_y) > REQUIRED_SIGNIFICANT_DIGITS - ); + ); */ // The maximum loss of precision is given by the formula: // 2 * | log2(x) - log2(y) | + 1 @@ -1004,16 +1004,12 @@ contract CryticPRBMath59x18Propertiesv3 { // Test identity property // Intermediate result should have at least REQUIRED_SIGNIFICANT_DIGITS - function inv_test_identity(SD59x18 x) public view { + function inv_test_identity(SD59x18 x) public { require(x.neq(ZERO_FP)); SD59x18 inv_x = inv(x); SD59x18 identity = mul(inv_x, x); - require( - significant_digits_after_mult(x, inv_x) > REQUIRED_SIGNIFICANT_DIGITS - ); - // They should agree with a tolerance of one tenth of a percent assert(equal_within_tolerance(identity, ONE_FP, ONE_TENTH_FP)); } @@ -1021,7 +1017,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Test that the absolute value of the result is in range zero-one // if x is greater than one, else, the absolute value of the result // must be greater than one - function inv_test_values(SD59x18 x) public view { + function inv_test_values(SD59x18 x) public { require(x.neq(ZERO_FP)); SD59x18 abs_inv_x = abs(inv(x)); @@ -1054,7 +1050,7 @@ contract CryticPRBMath59x18Propertiesv3 { ================================================================ */ // Test the zero-case, should revert - function inv_test_zero() public view { + function inv_test_zero() public { try this.helpersInv(ZERO_FP) { // Unexpected, the function must revert assert(false); @@ -1062,7 +1058,7 @@ contract CryticPRBMath59x18Propertiesv3 { } // Test the maximum value case, should not revert, and be close to zero - function inv_test_maximum() public view { + function inv_test_maximum() public { SD59x18 inv_maximum; try this.helpersInv(MAX_SD59x18) { @@ -1076,7 +1072,7 @@ contract CryticPRBMath59x18Propertiesv3 { // @audit Check precision // Test the minimum value case, should not revert, and be close to zero - function inv_test_minimum() public view { + function inv_test_minimum() public { SD59x18 inv_minimum; try this.helpersInv(MIN_SD59x18) { @@ -1136,7 +1132,7 @@ contract CryticPRBMath59x18Propertiesv3 { ================================================================ */ // Test for the maximum value - function avg_test_maximum() public view { + function avg_test_maximum() public { SD59x18 result; // This may revert due to overflow depending on implementation @@ -1148,7 +1144,7 @@ contract CryticPRBMath59x18Propertiesv3 { } // Test for the minimum value - function avg_test_minimum() public view { + function avg_test_minimum() public { SD59x18 result; // This may revert due to overflow depending on implementation @@ -1173,7 +1169,8 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for zero exponent // x ** 0 == 1 - function pow_test_zero_exponent(SD59x18 x) public view { + function pow_test_zero_exponent(SD59x18 x) public { + require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); SD59x18 x_pow_0 = pow(x, ZERO_FP); assert(x_pow_0.eq(ONE_FP)); @@ -1181,17 +1178,17 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for zero base // 0 ** x == 0 (for positive x) - function pow_test_zero_base(SD59x18 x) public view { - require(x.neq(ZERO_FP)); + function pow_test_zero_base(SD59x18 a) public { - SD59x18 zero_pow_x = pow(ZERO_FP, x); + SD59x18 zero_pow_a = pow(ZERO_FP, a); - assert(zero_pow_x.eq(ZERO_FP)); + assert(zero_pow_a.eq(ZERO_FP)); } // Test for exponent one // x ** 1 == x - function pow_test_one_exponent(SD59x18 x) public view { + function pow_test_one_exponent(SD59x18 x) public { + require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); SD59x18 x_pow_1 = pow(x, ONE_FP); assert(x_pow_1.eq(x)); @@ -1199,10 +1196,10 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for base one // 1 ** x == 1 - function pow_test_base_one(SD59x18 x) public view { - SD59x18 one_pow_x = pow(ONE_FP, x); + function pow_test_base_one(SD59x18 a) public { + SD59x18 one_pow_a = pow(ONE_FP, a); - assert(one_pow_x.eq(ONE_FP)); + assert(one_pow_a.eq(ONE_FP)); } // @audit - Fails @@ -1213,7 +1210,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 a, SD59x18 b ) public { - require(x.neq(ZERO_FP)); + require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); SD59x18 x_a = pow(x, a); SD59x18 x_b = pow(x, b); @@ -1234,7 +1231,8 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 a, SD59x18 b ) public { - require(x.neq(ZERO_FP)); + require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); + require(a.mul(b).neq(ZERO_FP)); SD59x18 x_a = pow(x, a); SD59x18 x_a_b = pow(x_a, b); @@ -1254,8 +1252,9 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x, SD59x18 y, SD59x18 a - ) public view { - require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); + ) public { + require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); + require(y.gte(ZERO_FP) && y.lte(MAX_PERMITTED_POW)); // todo this should probably be changed require(a.gt(convert(2 ** 32))); // to avoid massive loss of precision @@ -1272,8 +1271,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for result being greater than or lower than the argument, depending on // its absolute value and the value of the exponent function pow_test_values(SD59x18 x, SD59x18 a) public { - require(x.neq(ZERO_FP)); - require(x.neq(MIN_SD59x18) && a.neq(MIN_SD59x18)); + require(x.gte(ZERO_FP)); SD59x18 x_a = pow(x, a); @@ -1290,8 +1288,8 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for result sign: if the exponent is even, sign is positive // if the exponent is odd, preserves the sign of the base - function pow_test_sign(SD59x18 x, SD59x18 a) public view { - require(x.neq(ZERO_FP) && a.neq(ZERO_FP)); + function pow_test_sign(SD59x18 x, SD59x18 a) public { + require(x.gte(ZERO_FP)); SD59x18 x_a = pow(x, a); @@ -1319,7 +1317,7 @@ contract CryticPRBMath59x18Propertiesv3 { ================================================================ */ // Test for maximum base and exponent > 1 - function pow_test_maximum_base(SD59x18 a) public view { + function pow_test_maximum_base(SD59x18 a) public { require(a.gt(ONE_FP)); try this.helpersPow(MAX_SD59x18, a) { @@ -1332,8 +1330,8 @@ contract CryticPRBMath59x18Propertiesv3 { // @audit Check 2**18 // Test for abs(base) < 1 and high exponent - function pow_test_high_exponent(SD59x18 x, SD59x18 a) public view { - require(abs(x).lt(ONE_FP) && a.gt(convert(2 ** 18))); + function pow_test_high_exponent(SD59x18 x, SD59x18 a) public { + require(abs(x).lt(ONE_FP) && a.gt(convert(2 ** 32))); SD59x18 result = pow(x, a); @@ -1354,7 +1352,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for the inverse operation // sqrt(x) * sqrt(x) == x - function sqrt_test_inverse_mul(SD59x18 x) public view { + function sqrt_test_inverse_mul(SD59x18 x) public { require(x.gte(ZERO_FP)); SD59x18 sqrt_x = sqrt(x); @@ -1372,7 +1370,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for the inverse operation // sqrt(x) ** 2 == x - function sqrt_test_inverse_pow(SD59x18 x) public view { + function sqrt_test_inverse_pow(SD59x18 x) public { require(x.gte(ZERO_FP)); SD59x18 sqrt_x = sqrt(x); @@ -1391,7 +1389,7 @@ contract CryticPRBMath59x18Propertiesv3 { // @audit check the precision // Test for distributive property respect to the multiplication // sqrt(x) * sqrt(y) == sqrt(x * y) - function sqrt_test_distributive(SD59x18 x, SD59x18 y) public view { + function sqrt_test_distributive(SD59x18 x, SD59x18 y) public { require(x.gte(ZERO_FP) && y.gte(ZERO_FP)); SD59x18 sqrt_x = sqrt(x); @@ -1418,12 +1416,12 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for zero case - function sqrt_test_zero() public view { + function sqrt_test_zero() public { assert(sqrt(ZERO_FP).eq(ZERO_FP)); } // Test for maximum value - function sqrt_test_maximum() public view { + function sqrt_test_maximum() public { try this.helpersSqrt(MAX_SQRT) { // Expected behaviour, MAX_SQRT is positive, and operation // should not revert as the result is in range @@ -1434,7 +1432,7 @@ contract CryticPRBMath59x18Propertiesv3 { } // Test for minimum value - function sqrt_test_minimum() public view { + function sqrt_test_minimum() public { try this.helpersSqrt(MIN_SD59x18) { // Unexpected, should revert. MIN_SD59x18 is negative. assert(false); @@ -1444,7 +1442,7 @@ contract CryticPRBMath59x18Propertiesv3 { } // Test for negative operands - function sqrt_test_negative(SD59x18 x) public view { + function sqrt_test_negative(SD59x18 x) public { require(x.lt(ZERO_FP)); try this.helpersSqrt(x) { @@ -1470,7 +1468,8 @@ contract CryticPRBMath59x18Propertiesv3 { // @audit check loss // Test for distributive property respect to multiplication // log2(x * y) = log2(x) + log2(y) - function log2_test_distributive_mul(SD59x18 x, SD59x18 y) public view { + function log2_test_distributive_mul(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP) && y.gt(ZERO_FP)); SD59x18 log2_x = log2(x); SD59x18 log2_y = log2(y); SD59x18 log2_x_log2_y = add(log2_x, log2_y); @@ -1492,15 +1491,13 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for logarithm of a power // log2(x ** y) = y * log2(x) function log2_test_power(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP)); SD59x18 x_y = pow(x, y); + require(x_y.gt(ZERO_FP)); SD59x18 log2_x_y = log2(x_y); SD59x18 y_log2_x = mul(log2(x), y); - uint256 power = 9; - int256 digits = int256(10**power); - - emit PropertyFailed(y_log2_x, log2_x_y, power); - assert(equal_most_significant_digits_within_precision(y_log2_x, log2_x_y, digits)); + assert(equal_within_tolerance(y_log2_x, log2_x_y, ONE_FP)); } /* ================================================================ @@ -1510,7 +1507,7 @@ contract CryticPRBMath59x18Propertiesv3 { ================================================================ */ // Test for zero case, should revert - function log2_test_zero() public view { + function log2_test_zero() public { try this.helpersLog2(ZERO_FP) { // Unexpected, should revert assert(false); @@ -1520,7 +1517,7 @@ contract CryticPRBMath59x18Propertiesv3 { } // Test for maximum value case, should return a positive number - function log2_test_maximum() public view { + function log2_test_maximum() public { SD59x18 result; try this.helpersLog2(MAX_SD59x18) { @@ -1534,7 +1531,7 @@ contract CryticPRBMath59x18Propertiesv3 { } // Test for negative values, should revert as log2 is not defined - function log2_test_negative(SD59x18 x) public view { + function log2_test_negative(SD59x18 x) public { require(x.lt(ZERO_FP)); try this.helpersLog2(x) { @@ -1560,7 +1557,7 @@ contract CryticPRBMath59x18Propertiesv3 { // @audit check precision // Test for distributive property respect to multiplication // ln(x * y) = ln(x) + ln(y) - function ln_test_distributive_mul(SD59x18 x, SD59x18 y) public view { + function ln_test_distributive_mul(SD59x18 x, SD59x18 y) public { require(x.gt(ZERO_FP) && y.gt(ZERO_FP)); SD59x18 ln_x = ln(x); @@ -1602,7 +1599,7 @@ contract CryticPRBMath59x18Propertiesv3 { ================================================================ */ // Test for zero case, should revert - function ln_test_zero() public view { + function ln_test_zero() public { try this.helpersLn(ZERO_FP) { // Unexpected, should revert assert(false); @@ -1612,7 +1609,7 @@ contract CryticPRBMath59x18Propertiesv3 { } // Test for maximum value case, should return a positive number - function ln_test_maximum() public view { + function ln_test_maximum() public { SD59x18 result; try this.helpersLn(MAX_SD59x18) { @@ -1626,7 +1623,7 @@ contract CryticPRBMath59x18Propertiesv3 { } // Test for negative values, should revert as ln is not defined - function ln_test_negative(SD59x18 x) public view { + function ln_test_negative(SD59x18 x) public { require(x.lt(ZERO_FP)); try this.helpersLn(x) { @@ -1651,7 +1648,8 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for equality with pow(2, x) for integer x // pow(2, x) == exp2(x) - function exp2_test_equivalence_pow(SD59x18 x) public view { + function exp2_test_equivalence_pow(SD59x18 x) public { + require(x.lte(MAX_PERMITTED_EXP2)); SD59x18 exp2_x = exp2(x); SD59x18 pow_2_x = pow(TWO_FP, x); @@ -1662,6 +1660,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for inverse function // If y = log2(x) then exp2(y) == x function exp2_test_inverse(SD59x18 x) public { + require(x.lte(MAX_PERMITTED_EXP2) && x.gt(ZERO_FP)); SD59x18 log2_x = log2(x); SD59x18 exp2_x = exp2(log2_x); @@ -1674,14 +1673,18 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for negative exponent // exp2(-x) == inv( exp2(x) ) - function exp2_test_negative_exponent(SD59x18 x) public view { + function exp2_test_negative_exponent(SD59x18 x) public { require(x.lt(ZERO_FP) && x.neq(MIN_SD59x18)); SD59x18 exp2_x = exp2(x); SD59x18 exp2_minus_x = exp2(neg(x)); - // Result should be within 4 bits precision for the worst case - assert(equal_within_precision(exp2_x, inv(exp2_minus_x), 4)); + uint256 power = 2; + int256 digits = int256(10**power); + + emit PropertyFailed(exp2_x, inv(exp2_minus_x), power); + // Result should be within 2 digits precision for the worst case + assert(equal_most_significant_digits_within_precision(exp2_x, inv(exp2_minus_x), digits)); } /* ================================================================ @@ -1692,14 +1695,14 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for zero case // exp2(0) == 1 - function exp2_test_zero() public view { + function exp2_test_zero() public { SD59x18 exp_zero = exp2(ZERO_FP); assert(exp_zero.eq(ONE_FP)); } // Test for maximum value. This should overflow as it won't fit // in the data type - function exp2_test_maximum() public view { + function exp2_test_maximum() public { try this.helpersExp2(MAX_SD59x18) { // Unexpected, should revert assert(false); @@ -1708,9 +1711,20 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // Test for maximum value. This should overflow as it won't fit + // in the data type + function exp2_test_maximum_permitted() public { + try this.helpersExp2(MAX_PERMITTED_EXP2) { + // Should always pass + } catch { + // Should never revert + assert(false); + } + } + // Test for minimum value. This should return zero since // 2 ** -x == 1 / 2 ** x that tends to zero as x increases - function exp2_test_minimum() public view { + function exp2_test_minimum() public { SD59x18 result; try this.helpersExp2(MIN_SD59x18) { @@ -1739,6 +1753,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for inverse function // If y = ln(x) then exp(y) == x function exp_test_inverse(SD59x18 x) public { + require(x.lte(MAX_PERMITTED_EXP)); SD59x18 ln_x = ln(x); SD59x18 exp_x = exp(ln_x); SD59x18 log10_x = log10(x); @@ -1753,7 +1768,7 @@ contract CryticPRBMath59x18Propertiesv3 { // @audit check precision // Test for negative exponent // exp(-x) == inv( exp(x) ) - function exp_test_negative_exponent(SD59x18 x) public view { + function exp_test_negative_exponent(SD59x18 x) public { require(x.lt(ZERO_FP) && x.neq(MIN_SD59x18)); SD59x18 exp_x = exp(x); @@ -1771,14 +1786,14 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for zero case // exp(0) == 1 - function exp_test_zero() public view { + function exp_test_zero() public { SD59x18 exp_zero = exp(ZERO_FP); assert(exp_zero.eq(ONE_FP)); } // Test for maximum value. This should overflow as it won't fit // in the data type - function exp_test_maximum() public view { + function exp_test_maximum() public { try this.helpersExp(MAX_SD59x18) { // Unexpected, should revert assert(false); @@ -1787,9 +1802,20 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // Test for maximum value. This should overflow as it won't fit + // in the data type + function exp_test_maximum_permitted() public { + try this.helpersExp(MAX_PERMITTED_EXP) { + // Expected to always succeed + } catch { + // Unexpected, should revert + assert(false); + } + } + // Test for minimum value. This should return zero since // e ** -x == 1 / e ** x that tends to zero as x increases - function exp_test_minimum() public view { + function exp_test_minimum() public { SD59x18 result; try this.helpersExp(MIN_SD59x18) { @@ -1816,25 +1842,29 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for zero exponent // x ** 0 == 1 - function powu_test_zero_exponent(SD59x18 x) public view { + function powu_test_zero_exponent(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + require(x.lte(MAX_SD59x18)); SD59x18 x_pow_0 = powu(x, 0); assert(x_pow_0.eq(ONE_FP)); } // Test for zero base - // 0 ** x == 0 (for positive x) - function powu_test_zero_base(uint256 x) public view { - require(x != 0); + // 0 ** y == 0 (for positive y) + function powu_test_zero_base(uint256 y) public { + require(y != 0); - SD59x18 zero_pow_x = powu(ZERO_FP, x); + SD59x18 zero_pow_y = powu(ZERO_FP, y); - assert(zero_pow_x.eq(ZERO_FP)); + assert(zero_pow_y.eq(ZERO_FP)); } // Test for exponent one // x ** 1 == x - function powu_test_one_exponent(SD59x18 x) public view { + function powu_test_one_exponent(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + require(x.lte(MAX_SD59x18)); SD59x18 x_pow_1 = powu(x, 1); assert(x_pow_1.eq(x)); @@ -1842,10 +1872,10 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for base one // 1 ** x == 1 - function powu_test_base_one(uint256 x) public view { - SD59x18 one_pow_x = powu(ONE_FP, x); + function powu_test_base_one(uint256 y) public { + SD59x18 one_pow_y = powu(ONE_FP, y); - assert(one_pow_x.eq(ONE_FP)); + assert(one_pow_y.eq(ONE_FP)); } // @audit - Fails @@ -1855,8 +1885,9 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x, uint256 a, uint256 b - ) public view { - require(x.neq(ZERO_FP)); + ) public { + require(x.gt(MIN_SD59x18)); + require(x.lte(MAX_SD59x18)); SD59x18 x_a = powu(x, a); SD59x18 x_b = powu(x, b); @@ -1872,8 +1903,9 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x, uint256 a, uint256 b - ) public view { - require(x.neq(ZERO_FP)); + ) public { + require(x.gt(MIN_SD59x18)); + require(x.lte(MAX_SD59x18)); SD59x18 x_a = powu(x, a); SD59x18 x_a_b = powu(x_a, b); @@ -1889,8 +1921,9 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x, SD59x18 y, uint256 a - ) public view { - require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); + ) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + require(x.lte(MAX_SD59x18) && y.lte(MAX_SD59x18)); // todo this should probably be changed require(a > 2 ** 32); // to avoid massive loss of precision @@ -1906,7 +1939,7 @@ contract CryticPRBMath59x18Propertiesv3 { // @audit - fails // Test for result being greater than or lower than the argument, depending on // its absolute value and the value of the exponent - function powu_test_values(SD59x18 x, uint256 a) public view { + function powu_test_values(SD59x18 x, uint256 a) public { require(x.neq(ZERO_FP)); require(x.neq(MIN_SD59x18)); @@ -1923,7 +1956,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for result sign: if the exponent is even, sign is positive // if the exponent is odd, preserves the sign of the base - function powu_test_sign(SD59x18 x, uint256 a) public view { + function powu_test_sign(SD59x18 x, uint256 a) public { require(x.neq(ZERO_FP) && a != 0); SD59x18 x_a = powu(x, a); @@ -1952,7 +1985,7 @@ contract CryticPRBMath59x18Propertiesv3 { ================================================================ */ // Test for maximum base and exponent > 1 - function powu_test_maximum_base(uint256 a) public view { + function powu_test_maximum_base(uint256 a) public { require(a > 1); try this.helpersPowu(MAX_SD59x18, a) { @@ -1965,7 +1998,7 @@ contract CryticPRBMath59x18Propertiesv3 { // @audit Check 2**64 // Test for abs(base) < 1 and high exponent - function powu_test_high_exponent(SD59x18 x, uint256 a) public view { + function powu_test_high_exponent(SD59x18 x, uint256 a) public { require(abs(x).lt(ONE_FP) && a > 2 ** 18); SD59x18 result = powu(x, a); @@ -1988,7 +2021,8 @@ contract CryticPRBMath59x18Propertiesv3 { // @audit check loss // Test for distributive property respect to multiplication // log10(x * y) = log10(x) + log10(y) - function log10_test_distributive_mul(SD59x18 x, SD59x18 y) public view { + function log10_test_distributive_mul(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP) && y.gt(ZERO_FP)); SD59x18 log10_x = log10(x); SD59x18 log10_y = log10(y); SD59x18 log10_x_log10_y = add(log10_x, log10_y); @@ -2010,15 +2044,12 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for logarithm of a power // log10(x ** y) = y * log10(x) function log10_test_power(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP) && y.gt(ZERO_FP)); SD59x18 x_y = pow(x, y); SD59x18 log10_x_y = log10(x_y); SD59x18 y_log10_x = mul(log10(x), y); - - uint256 power = 9; - int256 digits = int256(10**power); - - emit PropertyFailed(log10_x_y, y_log10_x, power); - assert(equal_most_significant_digits_within_precision(log10_x_y, y_log10_x, digits)); + + assert(equal_within_tolerance(log10_x_y, y_log10_x, ONE_FP)); } /* ================================================================ @@ -2028,7 +2059,7 @@ contract CryticPRBMath59x18Propertiesv3 { ================================================================ */ // Test for zero case, should revert - function log10_test_zero() public view { + function log10_test_zero() public { try this.helpersLog10(ZERO_FP) { // Unexpected, should revert assert(false); @@ -2038,7 +2069,7 @@ contract CryticPRBMath59x18Propertiesv3 { } // Test for maximum value case, should return a positive number - function log10_test_maximum() public view { + function log10_test_maximum() public { SD59x18 result; try this.helpersLog10(MAX_SD59x18) { @@ -2052,7 +2083,7 @@ contract CryticPRBMath59x18Propertiesv3 { } // Test for negative values, should revert as log10 is not defined - function log10_test_negative(SD59x18 x) public view { + function log10_test_negative(SD59x18 x) public { require(x.lt(ZERO_FP)); try this.helpersLog10(x) { diff --git a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol index dd31927..ed734bc 100644 --- a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol @@ -12,6 +12,7 @@ contract CryticPRBMath60x18Propertiesv3 { event PropertyFailed(UD60x18 result); event PropertyFailed(UD60x18 result1, UD60x18 result2); event PropertyFailed(UD60x18 result1, UD60x18 result2, uint256 discardedDigits); + event PropertyFailed(UD60x18 result1, UD60x18 result2, UD60x18 percentage); event TestLog(uint256 num1, uint256 num2, uint256 result); /* ================================================================ @@ -72,7 +73,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 internal constant MAX_PERMITTED_EXP2 = UD60x18.wrap(192e18 - 1); UD60x18 internal constant MAX_PERMITTED_EXP = UD60x18.wrap(133_084258667509499440); - UD60x18 internal constant MAX_PERMITTED_EXPONENT_POW = UD60x18.wrap(2 ** 192 * 10 ** 18 - 1); + UD60x18 internal constant MAX_PERMITTED_POW = UD60x18.wrap(2 ** 192 * 10 ** 18 - 1); UD60x18 internal constant MAX_PERMITTED_SQRT = UD60x18.wrap(115792089237316195423570985008687907853269_984665640564039457); /* ================================================================ @@ -89,11 +90,12 @@ contract CryticPRBMath60x18Propertiesv3 { // These functions allows to compare a and b for equality, discarding // the last precision_bits bits. // Uses functions from the library under test! - function equal_within_precision(UD60x18 a, UD60x18 b, uint256 precision_bits) public pure returns(bool) { + function equal_within_precision(UD60x18 a, UD60x18 b, uint256 precision_bits) public returns(bool) { UD60x18 max = gt(a , b) ? a : b; UD60x18 min = gt(a , b) ? b : a; UD60x18 r = rshift(sub(max, min), precision_bits); + emit PropertyFailed(a, b, precision_bits); return (eq(r, convert(0))); } @@ -109,9 +111,11 @@ contract CryticPRBMath60x18Propertiesv3 { // This function determines if the relative error between a and b is less // than error_percent % (expressed as a 59x18 value) // Uses functions from the library under test! - function equal_within_tolerance(UD60x18 a, UD60x18 b, UD60x18 error_percent) public pure returns(bool) { + function equal_within_tolerance(UD60x18 a, UD60x18 b, UD60x18 error_percent) public returns(bool) { UD60x18 tol_value = mul(a, div(error_percent, convert(100))); + require(tol_value.neq(ZERO_FP)); + emit PropertyFailed(a, b, tol_value); return (lte(sub(b, a), tol_value)); } @@ -154,31 +158,6 @@ contract CryticPRBMath60x18Propertiesv3 { return ((larger - smaller) <= 1); } - // Return the i most significant bits from |n|. If n has less than i significant bits, return |n| - // Uses functions from the library under test! - function most_significant_bits( - UD60x18 n, - uint256 i - ) public returns (uint256) { - if (n.eq(ZERO_FP)) return 0; - - // Create a mask consisting of i bits set to 1 - uint256 mask = (2 ** i) - 1; - - // Get the positive value of n - uint256 value = intoUint256(n); - - // Get the position of the MSB set to 1 of n - uint256 pos = msb(value); - - // Shift the mask to match the rightmost 1-set bit - if (pos > i) { - mask <<= (pos - i); - } - - return (value & mask); - } - /* ================================================================ Library wrappers. These functions allow calling the PRBMathUD60x18 library. @@ -296,11 +275,7 @@ contract CryticPRBMath60x18Propertiesv3 { function add_test_values(UD60x18 x, UD60x18 y) public { UD60x18 x_y = x.add(y); - if (y.gte(ZERO_FP)) { - assert(x_y.gte(x)); - } else { - assert(x_y.lt(x)); - } + assert(x_y.gte(x)); } /* ================================================================ @@ -468,15 +443,8 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 xy_z = x_y.mul(z); UD60x18 x_yz = x.mul(y_z); - // todo check if this should not be used - // Failure if all significant digits are lost - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); - require(significant_digits_after_mult(y, z) > REQUIRED_SIGNIFICANT_DIGITS); - require(significant_digits_after_mult(x_y, z) > REQUIRED_SIGNIFICANT_DIGITS); - require(significant_digits_after_mult(x, y_z) > REQUIRED_SIGNIFICANT_DIGITS); - + require(xy_z.neq(ZERO_FP) && x_yz.neq(ZERO_FP)); assert(equal_within_tolerance(xy_z, x_yz, ONE_TENTH_FP)); - //assert(xy_z.eq(x_yz)); } // @audit - No guarantee of precision @@ -489,14 +457,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_times_y = x.mul(y); UD60x18 x_times_z = x.mul(z); - // todo check if this should not be used - // Failure if all significant digits are lost - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); - require(significant_digits_after_mult(x, z) > REQUIRED_SIGNIFICANT_DIGITS); - require(significant_digits_after_mult(x, y_plus_z) > REQUIRED_SIGNIFICANT_DIGITS); - assert(equal_within_tolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP)); - //assert(x_times_y.add(x_times_z).eq(x_times_y_plus_z)); } // Test for identity operation @@ -515,8 +476,6 @@ contract CryticPRBMath60x18Propertiesv3 { function mul_test_values(UD60x18 x, UD60x18 y) public { UD60x18 x_y = x.mul(y); - //require(significant_digits_lost_in_mult(x, y) == false); - if (y.gte(ONE_FP)) { assert(x_y.gte(x)); } else { @@ -584,10 +543,10 @@ contract CryticPRBMath60x18Propertiesv3 { // Test for division with 0 as numerator // 0 / x = 0 - function div_test_division_num_zero(UD60x18 x) public { - require(x.neq(ZERO_FP)); + function div_test_division_num_zero(UD60x18 y) public { + require(y.neq(ZERO_FP)); - UD60x18 div_0 = div(ZERO_FP, x); + UD60x18 div_0 = div(ZERO_FP, y); assert(ZERO_FP.eq(div_0)); } @@ -630,15 +589,16 @@ contract CryticPRBMath60x18Propertiesv3 { } // Test for division of a large value - // This should revert if |x| < 1 as it would return a value higher than max - function div_test_maximum_numerator(UD60x18 x) public { + // This should revert if |y| < 1 as it would return a value higher than max + function div_test_maximum_numerator(UD60x18 y) public { + require(y.neq(ZERO_FP)); UD60x18 div_large; - try this.helpersDiv(MAX_UD60x18, x) { - // If it didn't revert, then |x| >= 1 - div_large = div(MAX_UD60x18, x); + try this.helpersDiv(MAX_UD60x18, y) { + // If it didn't revert, then |y| >= 1 + div_large = div(MAX_UD60x18, y); - assert(x.gte(ONE_FP)); + assert(y.gte(ONE_FP)); } catch { // Expected revert as result is higher than max } @@ -646,6 +606,7 @@ contract CryticPRBMath60x18Propertiesv3 { // Test for values in range function div_test_range(UD60x18 x, UD60x18 y) public { + require(y.neq(ZERO_FP)); UD60x18 result; try this.helpersDiv(x, y) { @@ -704,12 +665,6 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_y = div(x, y); UD60x18 y_x = div(y, x); - require( - significant_digits_after_mult(x, inv(y)) > REQUIRED_SIGNIFICANT_DIGITS - ); - require( - significant_digits_after_mult(y, inv(x)) > REQUIRED_SIGNIFICANT_DIGITS - ); assert(equal_within_tolerance(x_y, inv(y_x), ONE_TENTH_FP)); } @@ -726,11 +681,6 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_y = mul(x, y); UD60x18 inv_x_y = inv(x_y); - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); - require( - significant_digits_after_mult(inv_x, inv_y) > REQUIRED_SIGNIFICANT_DIGITS - ); - // The maximum loss of precision is given by the formula: // 2 * | log2(x) - log2(y) | + 1 uint256 loss = 2 * intoUint256(log2(x).sub(log2(y))) + 1; @@ -738,7 +688,7 @@ contract CryticPRBMath60x18Propertiesv3 { assert(equal_within_precision(inv_x_y, inv_x_times_inv_y, loss)); } - // Test identity property + // Test multiplicative identity property // Intermediate result should have at least REQUIRED_SIGNIFICANT_DIGITS function inv_test_identity(UD60x18 x) public { require(x.neq(ZERO_FP)); @@ -746,9 +696,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 inv_x = inv(x); UD60x18 identity = mul(inv_x, x); - require( - significant_digits_after_mult(x, inv_x) > REQUIRED_SIGNIFICANT_DIGITS - ); + require(inv_x.neq(ZERO_FP) && identity.neq(ZERO_FP)); // They should agree with a tolerance of one tenth of a percent assert(equal_within_tolerance(identity, ONE_FP, ONE_TENTH_FP)); @@ -796,14 +744,13 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit Check precision - // Test the minimum value case, should not revert, and be close to zero + // Test the minimum value case, should not revert, and be close to 1e36 function inv_test_minimum() public { UD60x18 inv_minimum; try this.helpersInv(UD60x18.wrap(1)) { inv_minimum = this.helpersInv(UD60x18.wrap(1)); - assert(equal_within_precision(inv_minimum, ZERO_FP, 10)); + assert(equal_within_precision(inv_minimum, UD60x18.wrap(1e36), 10)); } catch { // Unexpected, the function must not revert assert(false); @@ -884,35 +831,38 @@ contract CryticPRBMath60x18Propertiesv3 { // Test for zero exponent // x ** 0 == 1 function pow_test_zero_exponent(UD60x18 x) public { + require(x.lte(MAX_PERMITTED_POW)); UD60x18 x_pow_0 = pow(x, ZERO_FP); assert(x_pow_0.eq(ONE_FP)); } // Test for zero base - // 0 ** x == 0 (for positive x) - function pow_test_zero_base(UD60x18 x) public { - require(x.neq(ZERO_FP)); + // 0 ** y == 0 (for positive y) + function pow_test_zero_base(UD60x18 y) public { + require(y.lte(MAX_PERMITTED_POW)); + require(y.neq(ZERO_FP)); - UD60x18 zero_pow_x = pow(ZERO_FP, x); + UD60x18 zero_pow_y = pow(ZERO_FP, y); - assert(zero_pow_x.eq(ZERO_FP)); + assert(zero_pow_y.eq(ZERO_FP)); } // Test for exponent one // x ** 1 == x function pow_test_one_exponent(UD60x18 x) public { + require(x.lte(MAX_PERMITTED_POW)); UD60x18 x_pow_1 = pow(x, ONE_FP); assert(x_pow_1.eq(x)); } // Test for base one - // 1 ** x == 1 - function pow_test_base_one(UD60x18 x) public { - UD60x18 one_pow_x = pow(ONE_FP, x); + // 1 ** y == 1 + function pow_test_base_one(UD60x18 y) public { + UD60x18 one_pow_y = pow(ONE_FP, y); - assert(one_pow_x.eq(ONE_FP)); + assert(one_pow_y.eq(ONE_FP)); } // @audit - Fails @@ -924,6 +874,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 b ) public { require(x.neq(ZERO_FP)); + require(x.lte(MAX_PERMITTED_POW)); UD60x18 x_a = pow(x, a); UD60x18 x_b = pow(x, b); @@ -945,6 +896,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 b ) public { require(x.neq(ZERO_FP)); + require(x.lte(MAX_PERMITTED_POW)); UD60x18 x_a = pow(x, a); UD60x18 x_a_b = pow(x_a, b); @@ -965,8 +917,9 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 y, UD60x18 a ) public { + require(x.lte(MAX_PERMITTED_POW)); require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); - require(a.gt(convert(2 ** 32))); // to avoid massive loss of precision + require(a.gt(UD60x18.wrap(1e9))); // to avoid massive loss of precision UD60x18 x_y = mul(x, y); UD60x18 xy_a = pow(x_y, a); @@ -986,6 +939,7 @@ contract CryticPRBMath60x18Propertiesv3 { // its value and the value of the exponent function pow_test_values(UD60x18 x, UD60x18 a) public { require(x.neq(ZERO_FP)); + require(x.lte(MAX_PERMITTED_POW)); UD60x18 x_a = pow(x, a); @@ -1019,8 +973,9 @@ contract CryticPRBMath60x18Propertiesv3 { // Test for maximum exponent and base > 1 function pow_test_maximum_exponent(UD60x18 x) public { require(x.gt(ONE_FP)); + require(x.lte(MAX_PERMITTED_POW)); - try this.helpersPow(x, MAX_PERMITTED_EXPONENT_POW) { + try this.helpersPow(x, MAX_PERMITTED_POW) { // Unexpected, should revert because of overflow assert(false); } catch { @@ -1028,7 +983,6 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit Check 2**18 // Test for base < 1 and high exponent function pow_test_high_exponent(UD60x18 x, UD60x18 a) public { require(x.lt(ONE_FP) && a.gt(convert(2 ** 64))); @@ -1053,6 +1007,7 @@ contract CryticPRBMath60x18Propertiesv3 { // Test for the inverse operation // sqrt(x) * sqrt(x) == x function sqrt_test_inverse_mul(UD60x18 x) public { + require(x.lte(MAX_PERMITTED_SQRT)); UD60x18 sqrt_x = sqrt(x); UD60x18 sqrt_x_squared = mul(sqrt_x, sqrt_x); @@ -1069,6 +1024,7 @@ contract CryticPRBMath60x18Propertiesv3 { // Test for the inverse operation // sqrt(x) ** 2 == x function sqrt_test_inverse_pow(UD60x18 x) public { + require(x.lte(MAX_PERMITTED_SQRT)); UD60x18 sqrt_x = sqrt(x); UD60x18 sqrt_x_squared = pow(sqrt_x, convert(2)); @@ -1086,18 +1042,12 @@ contract CryticPRBMath60x18Propertiesv3 { // Test for distributive property respect to the multiplication // sqrt(x) * sqrt(y) == sqrt(x * y) function sqrt_test_distributive(UD60x18 x, UD60x18 y) public { + require(x.lte(MAX_PERMITTED_SQRT) && y.lte(MAX_PERMITTED_SQRT)); UD60x18 sqrt_x = sqrt(x); UD60x18 sqrt_y = sqrt(y); UD60x18 sqrt_x_sqrt_y = mul(sqrt_x, sqrt_y); UD60x18 sqrt_xy = sqrt(mul(x, y)); - // Ensure we have enough significant digits for the result to be meaningful - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); - require( - significant_digits_after_mult(sqrt_x, sqrt_y) > - REQUIRED_SIGNIFICANT_DIGITS - ); - // Allow an error of up to one tenth of a percent assert(equal_within_tolerance(sqrt_x_sqrt_y, sqrt_xy, ONE_TENTH_FP)); } @@ -1149,9 +1099,6 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 xy = mul(x, y); UD60x18 log2_xy = log2(xy); - // Ensure we have enough significant digits for the result to be meaningful - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); - // The maximum loss of precision is given by the formula: // | log2(x) + log2(y) | uint256 loss = intoUint256(log2(x).add(log2(y))); @@ -1326,7 +1273,7 @@ contract CryticPRBMath60x18Propertiesv3 { // Test for equality with pow(2, x) for integer x // pow(2, x) == exp2(x) function exp2_test_equivalence_pow(UD60x18 x) public { - require(x.lte(convert(192))); + require(x.lte(MAX_PERMITTED_EXP2)); UD60x18 exp2_x = exp2(x); UD60x18 pow_2_x = pow(TWO_FP, x); @@ -1337,14 +1284,16 @@ contract CryticPRBMath60x18Propertiesv3 { // Test for inverse function // If y = log2(x) then exp2(y) == x function exp2_test_inverse(UD60x18 x) public { + require(x.gte(UNIT)); UD60x18 log2_x = log2(x); + require(log2_x.lte(MAX_PERMITTED_EXP2)); UD60x18 exp2_x = exp2(log2_x); - uint256 power = 30; + /* uint256 power = 30; uint256 digits = 10**power; - emit PropertyFailed(x, exp2_x, power); - assert(equal_most_significant_digits_within_precision(x, exp2_x, digits)); + emit PropertyFailed(x, exp2_x, power); */ + assert(equal_within_tolerance(x, exp2_x, ONE_TENTH_FP)); } /* ================================================================ @@ -1387,8 +1336,10 @@ contract CryticPRBMath60x18Propertiesv3 { // Test for inverse function // If y = ln(x) then exp(y) == x function exp_test_inverse(UD60x18 x) public { + require(x.gte(UNIT)); UD60x18 ln_x = ln(x); UD60x18 exp_x = exp(ln_x); + require(exp_x.lte(MAX_PERMITTED_EXP)); UD60x18 log2_x = log2(x); uint256 power = 16; @@ -1511,8 +1462,8 @@ contract CryticPRBMath60x18Propertiesv3 { uint256 a ) public { require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); - // todo this should probably be changed - require(a > 2 ** 32); // to avoid massive loss of precision + + require(a > 1e9); // to avoid massive loss of precision UD60x18 x_y = mul(x, y); UD60x18 xy_a = powu(x_y, a); From fc9daf57b9dee7eb29b5f6467fd53d7fa0d76966 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Tue, 16 May 2023 12:12:07 +0200 Subject: [PATCH 19/32] clean up comments --- .../v3/PRBMathSD59x18PropertyTests.sol | 57 ++++--------------- .../v3/PRBMathUD60x18PropertyTests.sol | 36 ++---------- 2 files changed, 16 insertions(+), 77 deletions(-) diff --git a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol index c966444..5f87519 100644 --- a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol @@ -96,7 +96,6 @@ contract CryticPRBMath59x18Propertiesv3 { Helper functions. ================================================================ */ - // @audit not checking for overflows // These functions allows to compare a and b for equality, discarding // the last precision_bits bits. // Uses functions from the library under test! @@ -108,7 +107,6 @@ contract CryticPRBMath59x18Propertiesv3 { return (eq(r, convert(0))); } - // @audit not checking for overflows function equal_within_precision_u(uint256 a, uint256 b, uint256 precision_bits) public pure returns(bool) { uint256 max = (a > b) ? a : b; uint256 min = (a > b) ? b : a; @@ -503,7 +501,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(x_y.eq(y_x)); } - // @audit - No guarantee of precision // Test for associative property // (x * y) * z == x * (y * z) function mul_test_associative(SD59x18 x, SD59x18 y, SD59x18 z) public { @@ -517,7 +514,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_tolerance(xy_z, x_yz, ONE_TENTH_FP)); } - // @audit - No guarantee of precision // Test for distributive property // x * (y + z) == x * y + x * z function mul_test_distributive(SD59x18 x, SD59x18 y, SD59x18 z) public { @@ -628,12 +624,16 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for identity property // x / 1 == x (equivalent to x / x == 1) - // Moreover, x/x should not revert unless x == 0 - function div_test_division_identity(SD59x18 x) public { + function div_test_division_identity_x_div_1(SD59x18 x) public { require(x.gt(MIN_SD59x18)); SD59x18 div_1 = div(x, ONE_FP); + assert(x.eq(div_1)); + } + // Test for identity property + // x/x should not revert unless x == 0 || x == MIN_SD59x18 + function div_test_division_identity_x_div_x(SD59x18 x) public { SD59x18 div_x; try this.helpersDiv(x, x) { @@ -784,7 +784,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(neg_x.eq(ZERO_FP)); } - // @audit todo check what is used for SD59x18 // Test for the maximum value case // Since this is implementation-dependant, we will actually test with MAX_SD59x18-EPS function neg_test_maximum() public { @@ -795,7 +794,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit todo check what is used for SD59x18 // Test for the minimum value case // Since this is implementation-dependant, we will actually test with MIN_SD59x18+EPS function neg_test_minimum() public { @@ -838,7 +836,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(abs_x.eq(abs_minus_x)); } - // @audit check precision used // Test the multiplicativeness property // | x * y | == |x| * |y| function abs_test_multiplicativeness(SD59x18 x, SD59x18 y) public pure { @@ -956,7 +953,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(inv_x.eq(div_1_x)); } - // @audit check if loss is correct // Test the anticommutativity of the division // x / y == 1 / (y / x) function inv_test_division_noncommutativity( @@ -968,16 +964,9 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_y = div(x, y); SD59x18 y_x = div(y, x); -/* require( - significant_digits_after_mult(x, inv(y)) > REQUIRED_SIGNIFICANT_DIGITS - ); - require( - significant_digits_after_mult(y, inv(x)) > REQUIRED_SIGNIFICANT_DIGITS - ); */ assert(equal_within_tolerance(x_y, inv(y_x), ONE_TENTH_FP)); } - // @audit check if loss is correct // Test the multiplication of inverses // 1/(x * y) == 1/x * 1/y function inv_test_multiplication(SD59x18 x, SD59x18 y) public { @@ -990,11 +979,6 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_y = mul(x, y); SD59x18 inv_x_y = inv(x_y); - /* require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); - require( - significant_digits_after_mult(inv_x, inv_y) > REQUIRED_SIGNIFICANT_DIGITS - ); */ - // The maximum loss of precision is given by the formula: // 2 * | log2(x) - log2(y) | + 1 uint256 loss = 2 * intoUint256(abs(log2(x).sub(log2(y)))) + 1; @@ -1003,7 +987,6 @@ contract CryticPRBMath59x18Propertiesv3 { } // Test identity property - // Intermediate result should have at least REQUIRED_SIGNIFICANT_DIGITS function inv_test_identity(SD59x18 x) public { require(x.neq(ZERO_FP)); @@ -1070,7 +1053,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit Check precision // Test the minimum value case, should not revert, and be close to zero function inv_test_minimum() public { SD59x18 inv_minimum; @@ -1202,7 +1184,7 @@ contract CryticPRBMath59x18Propertiesv3 { assert(one_pow_a.eq(ONE_FP)); } - // @audit - Fails + // Test for product of powers of the same base // x ** a * x ** b == x ** (a + b) function pow_test_product_same_base( @@ -1223,7 +1205,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_most_significant_digits_within_precision(mul(x_a, x_b), x_ab, digits)); } - // @audit - fails // Test for power of an exponentiation // (x ** a) ** b == x ** (a * b) function pow_test_power_of_an_exponentiation( @@ -1245,7 +1226,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_most_significant_digits_within_precision(x_a_b, x_ab, digits)); } - // @audit check precision // Test for power of a product // (x * y) ** a == x ** a * y ** a function pow_test_product_power( @@ -1255,7 +1235,7 @@ contract CryticPRBMath59x18Propertiesv3 { ) public { require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); require(y.gte(ZERO_FP) && y.lte(MAX_PERMITTED_POW)); - // todo this should probably be changed + require(a.gt(convert(2 ** 32))); // to avoid massive loss of precision SD59x18 x_y = mul(x, y); @@ -1267,7 +1247,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_precision(mul(x_a, y_a), xy_a, 10)); } - // @audit - fails, add some relative error // Test for result being greater than or lower than the argument, depending on // its absolute value and the value of the exponent function pow_test_values(SD59x18 x, SD59x18 a) public { @@ -1328,7 +1307,6 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit Check 2**18 // Test for abs(base) < 1 and high exponent function pow_test_high_exponent(SD59x18 x, SD59x18 a) public { require(abs(x).lt(ONE_FP) && a.gt(convert(2 ** 32))); @@ -1386,7 +1364,6 @@ contract CryticPRBMath59x18Propertiesv3 { ); } - // @audit check the precision // Test for distributive property respect to the multiplication // sqrt(x) * sqrt(y) == sqrt(x * y) function sqrt_test_distributive(SD59x18 x, SD59x18 y) public { @@ -1465,7 +1442,6 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit check loss // Test for distributive property respect to multiplication // log2(x * y) = log2(x) + log2(y) function log2_test_distributive_mul(SD59x18 x, SD59x18 y) public { @@ -1487,7 +1463,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_precision(log2_x_log2_y, log2_xy, loss)); } - // @audit - fails // Test for logarithm of a power // log2(x ** y) = y * log2(x) function log2_test_power(SD59x18 x, SD59x18 y) public { @@ -1554,7 +1529,6 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit check precision // Test for distributive property respect to multiplication // ln(x * y) = ln(x) + ln(y) function ln_test_distributive_mul(SD59x18 x, SD59x18 y) public { @@ -1576,7 +1550,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_precision(ln_x_ln_y, ln_xy, loss)); } - // @audit check precision // Test for logarithm of a power // ln(x ** y) = y * ln(x) function ln_test_power(SD59x18 x, SD59x18 y) public { @@ -1656,7 +1629,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(exp2_x.eq(pow_2_x)); } - // @audit - fails // Test for inverse function // If y = log2(x) then exp2(y) == x function exp2_test_inverse(SD59x18 x) public { @@ -1749,7 +1721,6 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit - fails // Test for inverse function // If y = ln(x) then exp(y) == x function exp_test_inverse(SD59x18 x) public { @@ -1765,7 +1736,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_most_significant_digits_within_precision(x, exp_x, digits)); } - // @audit check precision // Test for negative exponent // exp(-x) == inv( exp(x) ) function exp_test_negative_exponent(SD59x18 x) public { @@ -1878,7 +1848,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(one_pow_y.eq(ONE_FP)); } - // @audit - Fails // Test for product of powers of the same base // x ** a * x ** b == x ** (a + b) function powu_test_product_same_base( @@ -1896,7 +1865,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_precision(mul(x_a, x_b), x_ab, 10)); } - // @audit - fails // Test for power of an exponentiation // (x ** a) ** b == x ** (a * b) function powu_test_power_of_an_exponentiation( @@ -1914,7 +1882,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_precision(x_a_b, x_ab, 10)); } - // @audit check precision // Test for power of a product // (x * y) ** a == x ** a * y ** a function powu_test_product_power( @@ -1924,7 +1891,7 @@ contract CryticPRBMath59x18Propertiesv3 { ) public { require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); require(x.lte(MAX_SD59x18) && y.lte(MAX_SD59x18)); - // todo this should probably be changed + require(a > 2 ** 32); // to avoid massive loss of precision SD59x18 x_y = mul(x, y); @@ -1936,7 +1903,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_precision(mul(x_a, y_a), xy_a, 10)); } - // @audit - fails // Test for result being greater than or lower than the argument, depending on // its absolute value and the value of the exponent function powu_test_values(SD59x18 x, uint256 a) public { @@ -1996,10 +1962,9 @@ contract CryticPRBMath59x18Propertiesv3 { } } - // @audit Check 2**64 // Test for abs(base) < 1 and high exponent function powu_test_high_exponent(SD59x18 x, uint256 a) public { - require(abs(x).lt(ONE_FP) && a > 2 ** 18); + require(abs(x).lt(ONE_FP) && a > 2 ** 32); SD59x18 result = powu(x, a); @@ -2018,7 +1983,6 @@ contract CryticPRBMath59x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit check loss // Test for distributive property respect to multiplication // log10(x * y) = log10(x) + log10(y) function log10_test_distributive_mul(SD59x18 x, SD59x18 y) public { @@ -2040,7 +2004,6 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_precision(log10_x_log10_y, log10_xy, loss)); } - // @audit - fails // Test for logarithm of a power // log10(x ** y) = y * log10(x) function log10_test_power(SD59x18 x, SD59x18 y) public { diff --git a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol index ed734bc..bf1adc5 100644 --- a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol @@ -86,7 +86,6 @@ contract CryticPRBMath60x18Propertiesv3 { Helper functions. ================================================================ */ - // @audit not checking for overflows // These functions allows to compare a and b for equality, discarding // the last precision_bits bits. // Uses functions from the library under test! @@ -99,7 +98,6 @@ contract CryticPRBMath60x18Propertiesv3 { return (eq(r, convert(0))); } - // @audit not checking for overflows function equal_within_precision_u(uint256 a, uint256 b, uint256 precision_bits) public pure returns(bool) { uint256 max = (a > b) ? a : b; uint256 min = (a > b) ? b : a; @@ -119,7 +117,6 @@ contract CryticPRBMath60x18Propertiesv3 { return (lte(sub(b, a), tol_value)); } - // @audit precision can never be negative // Check that there are remaining significant digits after a multiplication // Uses functions from the library under test! function significant_digits_lost_in_mult(UD60x18 a, UD60x18 b) public pure returns (bool) { @@ -129,7 +126,6 @@ contract CryticPRBMath60x18Propertiesv3 { return(la + lb < 18); } - // @audit precision can never be negative // Return how many significant bits will remain after multiplying a and b // Uses functions from the library under test! function significant_digits_after_mult(UD60x18 a, UD60x18 b) public pure returns (uint256) { @@ -434,7 +430,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(x_y.eq(y_x)); } - // @audit - No guarantee of precision // Test for associative property // (x * y) * z == x * (y * z) function mul_test_associative(UD60x18 x, UD60x18 y, UD60x18 z) public { @@ -447,7 +442,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(equal_within_tolerance(xy_z, x_yz, ONE_TENTH_FP)); } - // @audit - No guarantee of precision // Test for distributive property // x * (y + z) == x * y + x * z function mul_test_distributive(UD60x18 x, UD60x18 y, UD60x18 z) public { @@ -470,7 +464,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(x_1.eq(x)); } - // @audit - No guarantee of precision // Test that the result increases or decreases depending // on the value to be added function mul_test_values(UD60x18 x, UD60x18 y) public { @@ -524,11 +517,15 @@ contract CryticPRBMath60x18Propertiesv3 { // Test for identity property // x / 1 == x (equivalent to x / x == 1) - // Moreover, x/x should not revert unless x == 0 - function div_test_division_identity(UD60x18 x) public { + function div_test_division_identity_x_div_1(UD60x18 x) public { UD60x18 div_1 = div(x, ONE_FP); + assert(x.eq(div_1)); + } + // Test for identity property + // x/x should not revert unless x == 0 + function div_test_division_identity_x_div_x(UD60x18 x) public { UD60x18 div_x; try this.helpersDiv(x, x) { @@ -653,7 +650,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(inv_x.eq(div_1_x)); } - // @audit check if loss is correct // Test the anticommutativity of the division // x / y == 1 / (y / x) function inv_test_division_noncommutativity( @@ -668,7 +664,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(equal_within_tolerance(x_y, inv(y_x), ONE_TENTH_FP)); } - // @audit check if loss is correct // Test the multiplication of inverses // 1/(x * y) == 1/x * 1/y function inv_test_multiplication(UD60x18 x, UD60x18 y) public { @@ -689,7 +684,6 @@ contract CryticPRBMath60x18Propertiesv3 { } // Test multiplicative identity property - // Intermediate result should have at least REQUIRED_SIGNIFICANT_DIGITS function inv_test_identity(UD60x18 x) public { require(x.neq(ZERO_FP)); @@ -865,7 +859,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(one_pow_y.eq(ONE_FP)); } - // @audit - Fails // Test for product of powers of the same base // x ** a * x ** b == x ** (a + b) function pow_test_product_same_base( @@ -887,7 +880,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(equal_most_significant_digits_within_precision(mul(x_a, x_b), x_ab, digits)); } - // @audit - fails // Test for power of an exponentiation // (x ** a) ** b == x ** (a * b) function pow_test_power_of_an_exponentiation( @@ -909,7 +901,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(equal_most_significant_digits_within_precision(x_a_b, x_ab, digits)); } - // @audit check precision // Test for power of a product // (x * y) ** a == x ** a * y ** a function pow_test_product_power( @@ -934,7 +925,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(equal_most_significant_digits_within_precision(mul(x_a, y_a), xy_a, digits)); } - // @audit - fails // Test for result being greater than or lower than the argument, depending on // its value and the value of the exponent function pow_test_values(UD60x18 x, UD60x18 a) public { @@ -1038,7 +1028,6 @@ contract CryticPRBMath60x18Propertiesv3 { ); } - // @audit check the precision // Test for distributive property respect to the multiplication // sqrt(x) * sqrt(y) == sqrt(x * y) function sqrt_test_distributive(UD60x18 x, UD60x18 y) public { @@ -1086,7 +1075,6 @@ contract CryticPRBMath60x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit check loss // Test for distributive property respect to multiplication // log2(x * y) = log2(x) + log2(y) function log2_test_distributive_mul(UD60x18 x, UD60x18 y) public { @@ -1106,7 +1094,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(equal_within_precision(log2_x_log2_y, log2_xy, loss)); } - // @audit - fails // Test for logarithm of a power // log2(x ** y) = y * log2(x) function log2_test_power(UD60x18 x, UD60x18 y) public { @@ -1178,7 +1165,6 @@ contract CryticPRBMath60x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit check precision // Test for distributive property respect to multiplication // ln(x * y) = ln(x) + ln(y) function ln_test_distributive_mul(UD60x18 x, UD60x18 y) public { @@ -1200,7 +1186,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(equal_within_precision(ln_x_ln_y, ln_xy, loss)); } - // @audit check precision // Test for logarithm of a power // ln(x ** y) = y * ln(x) function ln_test_power(UD60x18 x, UD60x18 y) public { @@ -1280,7 +1265,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(exp2_x.eq(pow_2_x)); } - // @audit - fails // Test for inverse function // If y = log2(x) then exp2(y) == x function exp2_test_inverse(UD60x18 x) public { @@ -1332,7 +1316,6 @@ contract CryticPRBMath60x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit - fails // Test for inverse function // If y = ln(x) then exp(y) == x function exp_test_inverse(UD60x18 x) public { @@ -1419,7 +1402,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(one_pow_a.eq(ONE_FP)); } - // @audit - Fails // Test for product of powers of the same base // x ** a * x ** b == x ** (a + b) function powu_test_product_same_base( @@ -1436,7 +1418,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(equal_within_precision(mul(x_a, x_b), x_ab, 10)); } - // @audit - fails // Test for power of an exponentiation // (x ** a) ** b == x ** (a * b) function powu_test_power_of_an_exponentiation( @@ -1453,7 +1434,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(equal_within_precision(x_a_b, x_ab, 10)); } - // @audit check precision // Test for power of a product // (x * y) ** a == x ** a * y ** a function powu_test_product_power( @@ -1474,7 +1454,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(equal_within_precision(mul(x_a, y_a), xy_a, 10)); } - // @audit - fails // Test for result being greater than or lower than the argument, depending on // its value and the value of the exponent function powu_test_values(UD60x18 x, uint256 a) public { @@ -1509,7 +1488,6 @@ contract CryticPRBMath60x18Propertiesv3 { } } - // @audit Check 2**64 // Test for base < 1 and high exponent function powu_test_high_exponent(UD60x18 x, uint256 a) public { require(x.lt(ONE_FP) && a > 2 ** 64); @@ -1531,7 +1509,6 @@ contract CryticPRBMath60x18Propertiesv3 { with math rules and expected behaviour. ================================================================ */ - // @audit check loss // Test for distributive property respect to multiplication // log10(x * y) = log10(x) + log10(y) function log10_test_distributive_mul(UD60x18 x, UD60x18 y) public { @@ -1553,7 +1530,6 @@ contract CryticPRBMath60x18Propertiesv3 { assert(equal_within_precision(log10_x_log10_y, log10_xy, loss)); } - // @audit - fails // Test for logarithm of a power // log10(x ** y) = y * log10(x) function log10_test_power(UD60x18 x, UD60x18 y) public { From 9b51148a6d785cad248ee60ec3745aa2ad3c4bae Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Tue, 16 May 2023 12:31:36 +0200 Subject: [PATCH 20/32] split up and fix some invariants --- .../PRBMath/v3/PRBMathSD59x18PropertyTests.sol | 16 ++++++++++++---- .../PRBMath/v3/PRBMathUD60x18PropertyTests.sol | 4 +++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol index 5f87519..d816c36 100644 --- a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol @@ -893,7 +893,9 @@ contract CryticPRBMath59x18Propertiesv3 { // If it doesn't revert, the value must be MAX_SD59x18 abs_max = this.helpersAbs(MAX_SD59x18); assert(abs_max.eq(MAX_SD59x18)); - } catch {} + } catch { + assert(false); + } } // Test the minimum value @@ -915,7 +917,9 @@ contract CryticPRBMath59x18Propertiesv3 { // If it doesn't revert, the value must be the negative of MIN_SD59x18 + 1 abs_min = this.helpersAbs(input); assert(abs_min.eq(neg(input))); - } catch {} + } catch { + assert(false); + } } /* ================================================================ @@ -1122,7 +1126,9 @@ contract CryticPRBMath59x18Propertiesv3 { try this.helpersAvg(MAX_SD59x18, MAX_SD59x18) { result = this.helpersAvg(MAX_SD59x18, MAX_SD59x18); assert(result.eq(MAX_SD59x18)); - } catch {} + } catch { + assert(false); + } } // Test for the minimum value @@ -1134,7 +1140,9 @@ contract CryticPRBMath59x18Propertiesv3 { try this.helpersAvg(MIN_SD59x18, MIN_SD59x18) { result = this.helpersAvg(MIN_SD59x18, MIN_SD59x18); assert(result.eq(MIN_SD59x18)); - } catch {} + } catch { + assert(false); + } } /* ================================================================ diff --git a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol index bf1adc5..b7fce65 100644 --- a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol @@ -807,7 +807,9 @@ contract CryticPRBMath60x18Propertiesv3 { try this.helpersAvg(MAX_UD60x18, MAX_UD60x18) { result = this.helpersAvg(MAX_UD60x18, MAX_UD60x18); assert(result.eq(MAX_UD60x18)); - } catch {} + } catch { + assert(false); + } } /* ================================================================ From 5371d5ecee1bdc4897680953ba7cd6cfc16b7136 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Tue, 16 May 2023 12:39:00 +0200 Subject: [PATCH 21/32] remove unused helpers --- .../PRBMath/v3/PRBMathSD59x18PropertyTests.sol | 14 -------------- .../PRBMath/v3/PRBMathUD60x18PropertyTests.sol | 17 ----------------- 2 files changed, 31 deletions(-) diff --git a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol index d816c36..8435614 100644 --- a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol @@ -107,14 +107,6 @@ contract CryticPRBMath59x18Propertiesv3 { return (eq(r, convert(0))); } - function equal_within_precision_u(uint256 a, uint256 b, uint256 precision_bits) public pure returns(bool) { - uint256 max = (a > b) ? a : b; - uint256 min = (a > b) ? b : a; - uint256 r = (max - min) >> precision_bits; - - return (r == 0); - } - // This function determines if the relative error between a and b is less // than error_percent % (expressed as a 59x18 value) // Uses functions from the library under test! @@ -242,12 +234,6 @@ contract CryticPRBMath59x18Propertiesv3 { return floor(x); } - /* ================================================================ - Bounding helpers - ================================================================ */ - - - /* ================================================================ TESTS FOR FUNCTION add() diff --git a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol index b7fce65..8130161 100644 --- a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol @@ -98,14 +98,6 @@ contract CryticPRBMath60x18Propertiesv3 { return (eq(r, convert(0))); } - function equal_within_precision_u(uint256 a, uint256 b, uint256 precision_bits) public pure returns(bool) { - uint256 max = (a > b) ? a : b; - uint256 min = (a > b) ? b : a; - uint256 r = (max - min) >> precision_bits; - - return (r == 0); - } - // This function determines if the relative error between a and b is less // than error_percent % (expressed as a 59x18 value) // Uses functions from the library under test! @@ -117,15 +109,6 @@ contract CryticPRBMath60x18Propertiesv3 { return (lte(sub(b, a), tol_value)); } - // Check that there are remaining significant digits after a multiplication - // Uses functions from the library under test! - function significant_digits_lost_in_mult(UD60x18 a, UD60x18 b) public pure returns (bool) { - uint256 la = convert(floor(log10(a))); - uint256 lb = convert(floor(log10(b))); - - return(la + lb < 18); - } - // Return how many significant bits will remain after multiplying a and b // Uses functions from the library under test! function significant_digits_after_mult(UD60x18 a, UD60x18 b) public pure returns (uint256) { From fd3aa14c2e0ec37a643992162dbefdcc36b41eb1 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Wed, 17 May 2023 19:54:38 +0200 Subject: [PATCH 22/32] gm properties --- .../v3/PRBMathSD59x18PropertyTests.sol | 180 ++++++++++++++++-- .../v3/PRBMathUD60x18PropertyTests.sol | 86 ++++++++- 2 files changed, 238 insertions(+), 28 deletions(-) diff --git a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol index 8435614..705afa0 100644 --- a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol @@ -5,16 +5,10 @@ import {add, sub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb/math/src/sd59 import {convert} from "@prb/math/src/sd59x18/Conversions.sol"; import {msb} from "@prb/math/src/Common.sol"; import {intoUint128, intoUint256} from "@prb/math/src/sd59x18/Casting.sol"; -import {mul, div, abs, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, powu} from "@prb/math/src/sd59x18/Math.sol"; +import {mul, div, abs, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, powu, gm} from "@prb/math/src/sd59x18/Math.sol"; contract CryticPRBMath59x18Propertiesv3 { - event PropertyFailed(SD59x18 result); - event PropertyFailed(SD59x18 result1, SD59x18 result2); - event PropertyFailed(SD59x18 result1, SD59x18 result2, uint256 discardedDigits); - event PropertyFailed(SD59x18 result1, SD59x18 result2, SD59x18 discardedDigits); - event TestLog(int256 num1, int256 num2, int256 result); - /* ================================================================ 59x18 fixed-point constants used for testing specific values. This assumes that PRBMath library's convert(x) works as expected. @@ -34,6 +28,7 @@ contract CryticPRBMath59x18Propertiesv3 { Constants used for precision loss calculations ================================================================ */ uint256 internal REQUIRED_SIGNIFICANT_DIGITS = 10; + SD59x18 internal LOG2_PRECISION_LOSS = SD59x18.wrap(1); /* ================================================================ Integer representations maximum values. @@ -88,8 +83,13 @@ contract CryticPRBMath59x18Propertiesv3 { /* ================================================================ Events used for debugging or showing information. ================================================================ */ - event Value(string reason, int256 val); + event Value(string reason, SD59x18 val); event LogErr(bytes error); + event PropertyFailed(SD59x18 result); + event PropertyFailed(SD59x18 result1, SD59x18 result2); + event PropertyFailed(SD59x18 result1, SD59x18 result2, uint256 discardedDigits); + event PropertyFailed(SD59x18 result1, SD59x18 result2, SD59x18 discardedDigits); + event TestLog(int256 num1, int256 num2, int256 result); /* ================================================================ @@ -135,7 +135,7 @@ contract CryticPRBMath59x18Propertiesv3 { int256 prec = la + lb; if (prec < -18) return 0; - else return(18 + uint256(prec)); + else return(59 + uint256(prec)); } // Returns true if the n most significant bits of a and b are almost equal @@ -159,7 +159,7 @@ contract CryticPRBMath59x18Propertiesv3 { Library wrappers. These functions allow calling the PRBMathSD59x18 library. ================================================================ */ - function debug(string calldata x, int256 y) public { + function debug(string calldata x, SD59x18 y) public { emit Value(x, y); } @@ -234,6 +234,10 @@ contract CryticPRBMath59x18Propertiesv3 { return floor(x); } + function helpersGm(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return gm(x,y); + } + /* ================================================================ TESTS FOR FUNCTION add() @@ -497,7 +501,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_yz = x.mul(y_z); require(xy_z.neq(ZERO_FP) && x_yz.neq(ZERO_FP)); - assert(equal_within_tolerance(xy_z, x_yz, ONE_TENTH_FP)); + assert(equal_within_tolerance(xy_z, x_yz, ONE_FP)); } // Test for distributive property @@ -954,7 +958,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_y = div(x, y); SD59x18 y_x = div(y, x); - assert(equal_within_tolerance(x_y, inv(y_x), ONE_TENTH_FP)); + assert(equal_within_tolerance(x_y, inv(y_x), ONE_FP)); } // Test the multiplication of inverses @@ -1153,14 +1157,22 @@ contract CryticPRBMath59x18Propertiesv3 { } // Test for zero base - // 0 ** x == 0 (for positive x) - function pow_test_zero_base(SD59x18 a) public { - + // 0 ** a == 0 (for positive a) + function pow_test_zero_base_non_zero_exponent(SD59x18 a) public { + require(a.gt(ZERO_FP)); SD59x18 zero_pow_a = pow(ZERO_FP, a); assert(zero_pow_a.eq(ZERO_FP)); } + // Test for zero base + // 0 ** 0 == 1 + function pow_test_zero_base_zero_exponent() public { + SD59x18 zero_pow_a = pow(ZERO_FP, ZERO_FP); + + assert(zero_pow_a.eq(ONE_FP)); + } + // Test for exponent one // x ** 1 == x function pow_test_one_exponent(SD59x18 x) public { @@ -1171,7 +1183,7 @@ contract CryticPRBMath59x18Propertiesv3 { } // Test for base one - // 1 ** x == 1 + // 1 ** a == 1 function pow_test_base_one(SD59x18 a) public { SD59x18 one_pow_a = pow(ONE_FP, a); @@ -1187,6 +1199,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 b ) public { require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); + require(a.gte(MIN_PERMITTED_EXP2) && b.gte(MIN_PERMITTED_EXP2)); SD59x18 x_a = pow(x, a); SD59x18 x_b = pow(x, b); @@ -1208,6 +1221,7 @@ contract CryticPRBMath59x18Propertiesv3 { ) public { require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); require(a.mul(b).neq(ZERO_FP)); + require(a.gte(MIN_PERMITTED_EXP2) && b.gte(MIN_PERMITTED_EXP2)); SD59x18 x_a = pow(x, a); SD59x18 x_a_b = pow(x_a, b); @@ -1243,8 +1257,8 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for result being greater than or lower than the argument, depending on // its absolute value and the value of the exponent - function pow_test_values(SD59x18 x, SD59x18 a) public { - require(x.gte(ZERO_FP)); + function pow_test_positive_exponent(SD59x18 x, SD59x18 a) public { + require(x.gte(ZERO_FP) && a.gte(ZERO_FP)); SD59x18 x_a = pow(x, a); @@ -1259,6 +1273,24 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // Test for result being greater than or lower than the argument, depending on + // its absolute value and the value of the exponent + function pow_test_negative_exponent(SD59x18 x, SD59x18 a) public { + require(x.gte(ZERO_FP) && a.lte(ZERO_FP)); + + SD59x18 x_a = pow(x, a); + + if (abs(x).gte(ONE_FP)) { + emit PropertyFailed(x_a, ONE_FP); + assert(abs(x_a).lte(ONE_FP)); + } + + if (abs(x).lte(ONE_FP)) { + emit PropertyFailed(x_a, ONE_FP); + assert(abs(x_a).gte(ONE_FP)); + } + } + // Test for result sign: if the exponent is even, sign is positive // if the exponent is odd, preserves the sign of the base function pow_test_sign(SD59x18 x, SD59x18 a) public { @@ -1283,6 +1315,15 @@ contract CryticPRBMath59x18Propertiesv3 { } } + // pow(2, a) == exp2(a) + function pow_test_exp2_equivalence(SD59x18 a) public { + SD59x18 pow_result = pow(convert(2), a); + SD59x18 exp2_result = exp2(a); + + emit PropertyFailed(pow_result, exp2_result); + assert(pow_result.eq(exp2_result)); + } + /* ================================================================ Tests for overflow and edge cases. These will make sure that the function reverts on overflow and @@ -1379,6 +1420,17 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_tolerance(sqrt_x_sqrt_y, sqrt_xy, ONE_TENTH_FP)); } + // Test that sqrt is strictly increasing + function sqrt_test_is_increasing(SD59x18 x, SD59x18 y) public { + require(x.gte(ZERO_FP) && x.lte(MAX_SQRT.sub(SD59x18.wrap(1)))); + require(y.gt(x) && y.lte(MAX_SQRT)); + + SD59x18 sqrt_x = sqrt(x); + SD59x18 sqrt_y = sqrt(y); + + assert(sqrt_y.gte(sqrt_x)); + } + /* ================================================================ Tests for overflow and edge cases. These will make sure that the function reverts on overflow and @@ -1742,6 +1794,17 @@ contract CryticPRBMath59x18Propertiesv3 { assert(equal_within_precision(exp_x, inv(exp_minus_x), 4)); } + // Test that exp strictly increases + function exp_test_strictly_increasing(SD59x18 x, SD59x18 y) public { + require(x.lte(MAX_PERMITTED_EXP)); + require(y.gt(x) && y.lte(MAX_PERMITTED_EXP)); + + SD59x18 exp_x = exp(x); + SD59x18 exp_y = exp(y); + + assert(exp_y.gte(exp_x)); + } + /* ================================================================ Tests for overflow and edge cases. These will make sure that the function reverts on overflow and @@ -2051,4 +2114,85 @@ contract CryticPRBMath59x18Propertiesv3 { } } + /* ================================================================ + + TESTS FOR FUNCTION gm() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // The product of the values should be equal to the geometric mean + // raised to the power of N (numbers in the set) + function gm_test_product(SD59x18 x, SD59x18 y) public { + bool x_sign = x.gt(ZERO_FP); + bool y_sign = y.gt(ZERO_FP); + require(x_sign = y_sign); + + SD59x18 x_mul_y = x.mul(y); + SD59x18 gm_squared = pow(gm(x,y), TWO_FP); + + emit PropertyFailed(x_mul_y, gm_squared); + assert(equal_within_tolerance(x_mul_y, gm_squared, ONE_TENTH_FP)); + } + + // The geometric mean for a set of positive numbers is less than the + // arithmetic mean of that set, as long as the values of the set are not equal + function gm_test_positive_set_avg(SD59x18 x, SD59x18 y) public { + require(x.gte(ZERO_FP) && y.gte(ZERO_FP) && x.neq(y)); + + SD59x18 gm_x_y = gm(x, y); + SD59x18 avg_x_y = avg(x, y); + + emit PropertyFailed(gm_x_y, avg_x_y); + assert(gm_x_y.lt(avg_x_y)); + } + + // The geometric mean of a set of positive equal numbers should be + // equal to the arithmetic mean + function gm_test_positive_equal_set_avg(SD59x18 x) public { + require(x.gte(ZERO_FP)); + + SD59x18 gm_x = gm(x, x); + SD59x18 avg_x = avg(x, x); + + emit PropertyFailed(gm_x, avg_x); + assert(gm_x.eq(avg_x)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case, should return 0 + function gm_test_zero(SD59x18 x) public { + require(x.gte(ZERO_FP)); + + try this.helpersGm(x, ZERO_FP) { + SD59x18 result = gm(x, ZERO_FP); + assert(result.eq(ZERO_FP)); + } catch { + // Unexpected, should not revert + assert(false); + } + } + + // Test for single negative input + function gm_test_negative(SD59x18 x, SD59x18 y) public { + require(x.gte(ZERO_FP) && y.lt(ZERO_FP)); + + try this.helpersGm(x, y) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, gm of a negative product is not defined + } + } + } \ No newline at end of file diff --git a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol index 8130161..c823fb2 100644 --- a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol @@ -5,16 +5,10 @@ import {add, sub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb/math/src/ud60 import {convert} from "@prb/math/src/ud60x18/Conversions.sol"; import {msb} from "@prb/math/src/Common.sol"; import {intoUint128, intoUint256} from "@prb/math/src/ud60x18/Casting.sol"; -import {mul, div, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, powu} from "@prb/math/src/ud60x18/Math.sol"; +import {mul, div, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, powu, gm} from "@prb/math/src/ud60x18/Math.sol"; contract CryticPRBMath60x18Propertiesv3 { - event PropertyFailed(UD60x18 result); - event PropertyFailed(UD60x18 result1, UD60x18 result2); - event PropertyFailed(UD60x18 result1, UD60x18 result2, uint256 discardedDigits); - event PropertyFailed(UD60x18 result1, UD60x18 result2, UD60x18 percentage); - event TestLog(uint256 num1, uint256 num2, uint256 result); - /* ================================================================ 59x18 fixed-point constants used for testing specific values. This assumes that PRBMath library's convert(x) works as expected. @@ -79,8 +73,13 @@ contract CryticPRBMath60x18Propertiesv3 { /* ================================================================ Events used for debugging or showing information. ================================================================ */ - event Value(string reason, int256 val); + event Value(string reason, UD60x18 val); event LogErr(bytes error); + event PropertyFailed(UD60x18 result); + event PropertyFailed(UD60x18 result1, UD60x18 result2); + event PropertyFailed(UD60x18 result1, UD60x18 result2, uint256 discardedDigits); + event PropertyFailed(UD60x18 result1, UD60x18 result2, UD60x18 percentage); + event TestLog(uint256 num1, uint256 num2, uint256 result); /* ================================================================ Helper functions. @@ -117,7 +116,7 @@ contract CryticPRBMath60x18Propertiesv3 { uint256 prec = la + lb; if (prec < 18) return 0; - else return(18 + uint256(prec)); + else return(60 + uint256(prec)); } // Returns true if the n most significant bits of a and b are almost equal @@ -141,7 +140,7 @@ contract CryticPRBMath60x18Propertiesv3 { Library wrappers. These functions allow calling the PRBMathUD60x18 library. ================================================================ */ - function debug(string calldata x, int256 y) public { + function debug(string calldata x, UD60x18 y) public { emit Value(x, y); } @@ -208,6 +207,10 @@ contract CryticPRBMath60x18Propertiesv3 { return floor(x); } + function helpersGm(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { + return gm(x, y); + } + /* ================================================================ TESTS FOR FUNCTION add() @@ -1571,4 +1574,67 @@ contract CryticPRBMath60x18Propertiesv3 { // Expected } } + + /* ================================================================ + + TESTS FOR FUNCTION gm() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // The product of the values should be equal to the geometric mean + // raised to the power of N (numbers in the set) + function gm_test_product(UD60x18 x, UD60x18 y) public { + UD60x18 x_mul_y = x.mul(y); + UD60x18 gm_squared = pow(gm(x,y), TWO_FP); + + emit PropertyFailed(x_mul_y, gm_squared); + assert(equal_within_tolerance(x_mul_y, gm_squared, ONE_TENTH_FP)); + } + + // The geometric mean for a set of positive numbers is less than the + // arithmetic mean of that set, as long as the values of the set are not equal + function gm_test_positive_set_avg(UD60x18 x, UD60x18 y) public { + require(x.neq(y)); + + UD60x18 gm_x_y = gm(x, y); + UD60x18 avg_x_y = avg(x, y); + + emit PropertyFailed(gm_x_y, avg_x_y); + assert(gm_x_y.lt(avg_x_y)); + } + + // The geometric mean of a set of positive equal numbers should be + // equal to the arithmetic mean + function gm_test_positive_equal_set_avg(UD60x18 x) public { + UD60x18 gm_x = gm(x, x); + UD60x18 avg_x = avg(x, x); + + emit PropertyFailed(gm_x, avg_x); + assert(gm_x.eq(avg_x)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case, should return 0 + function gm_test_zero(UD60x18 x) public { + require(x.gte(ZERO_FP)); + + try this.helpersGm(x, ZERO_FP) { + UD60x18 result = gm(x, ZERO_FP); + assert(result.eq(ZERO_FP)); + } catch { + // Unexpected, should not revert + assert(false); + } + } } \ No newline at end of file From faf008ebf200cff60a5afb8eeeab33208e956e08 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Thu, 18 May 2023 12:45:34 +0200 Subject: [PATCH 23/32] updating readme and properties list --- PROPERTIES.md | 241 +++++++++++++++++++++++++++++++++++++++++++++++++- README.md | 41 +++++++++ 2 files changed, 281 insertions(+), 1 deletion(-) diff --git a/PROPERTIES.md b/PROPERTIES.md index a51acbc..d384af4 100644 --- a/PROPERTIES.md +++ b/PROPERTIES.md @@ -1,6 +1,6 @@ # Introduction -This file lists all the currently implemented Echidna property tests for ERC20, ERC4626 and ABDKMath64x64. For each property, there is a permalink to the file implementing it in the repository and a small description of the invariant tested. +This file lists all the currently implemented Echidna property tests for ERC20, ERC4626, ABDKMath64x64, PRBMath SD59x18 and PRBMath UD60x18. For each property, there is a permalink to the file implementing it in the repository and a small description of the invariant tested. ## Table of contents @@ -18,6 +18,8 @@ This file lists all the currently implemented Echidna property tests for ERC20, - [Tests for mintable tokens](#tests-for-mintable-tokens-1) - [ERC4626](#erc4626) - [ABDKMath64x64](#abdkmath64x64) + - [PRBMath SD59x18](#prbmath-sd59x18) + - [PRBMath UD60x18](#prbmath-ud60x18) ## ERC20 @@ -260,3 +262,240 @@ This file lists all the currently implemented Echidna property tests for ERC20, | ABDKMATH-104 | [exp_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/ABDKMath64x64/ABDKMath64x64PropertyTests.sol#L1818) | Natural exponentiation edge case: exponent zero result should be one. | | ABDKMATH-105 | [exp_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/ABDKMath64x64/ABDKMath64x64PropertyTests.sol#L1825) | Natural exponentiation edge case: exponent maximum value should revert. | | ABDKMATH-106 | [exp_test_minimum](https://github.com/crytic/properties/blob/main/contracts/Math/ABDKMath64x64/ABDKMath64x64PropertyTests.sol#L1836) | Natural exponentiation edge case: exponent minimum value result should be zero. | + +## PRBMath SD59x18 + +| ID | Name | Invariant tested | +| ------------| ----------------------------------------------------------------------------------------------------------------------------------------------------------| ------------------------------------------------------------------------------------------------------ | +| SD59x18-001 | [add_test_commutative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L255) | Commutative property for addition. | +| SD59x18-002 | [add_test_associative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L264) | Associative property for addition. | +| SD59x18-003 | [add_test_identity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L275) | Identity operation for addition. | +| SD59x18-004 | [add_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L284) | Addition result should increase or decrease depending on operands signs. | +| SD59x18-005 | [add_test_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L302) | Addition result should be in the valid 59x18-arithmetic range. | +| SD59x18-006 | [add_test_maximum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L312) | Addition edge case: maximum value plus zero should be maximum value. | +| SD59x18-007 | [add_test_maximum_value_plus_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L322) | Addition edge case: maximum value plus one should revert (out of range). | +| SD59x18-008 | [add_test_minimum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L332) | Addition edge case: minimum value plus zero should be minimum value. | +| SD59x18-009 | [add_test_minimum_value_plus_negative_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L342) | Addition edge case: minimum value plus minus one should revert (out of range). | +| SD59x18-010 | [sub_test_equivalence_to_addition](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L364) | Subtraction should be equal to addition with opposite sign. | +| SD59x18-011 | [sub_test_non_commutative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L374) | Anti-commutative property for subtraction. | +| SD59x18-012 | [sub_test_identity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L383) | Identity operation for subtraction. | +| SD59x18-013 | [sub_test_neutrality](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L392) | Adding and subtracting the same value should not affect original value. | +| SD59x18-014 | [sub_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L405) | Subtraction result should increase or decrease depending on operands signs. | +| SD59x18-015 | [sub_test_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L423) | Subtraction result should be in the valid 59x18-arithmetic range. | +| SD59x18-016 | [sub_test_maximum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L433) | Subtraction edge case: maximum value minus zero should be maximum value. | +| SD59x18-017 | [sub_test_maximum_value_minus_neg_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L444) | Subtraction edge case: maximum value minus negative one should revert (out of range). | +| SD59x18-018 | [sub_test_minimum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L454) | Subtraction edge case: minimum value minus zero should be minimum value. | +| SD59x18-019 | [sub_test_minimum_value_minus_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L464) | Subtraction edge case: minimum value minus one should revert (out of range). | +| SD59x18-020 | [mul_test_commutative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L486) | Commutative property for multiplication. | +| SD59x18-021 | [mul_test_associative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L496) | Associative property for multiplication. | +| SD59x18-022 | [mul_test_distributive](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L509) | Distributive property for multiplication. | +| SD59x18-023 | [mul_test_identity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L525) | Identity operation for multiplication. | +| SD59x18-024 | [mul_test_x_positive](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L537) | Multiplication result should increase or decrease depending on operands signs. | +| SD59x18-025 | [mul_test_x_negative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L551) | Multiplication result should increase or decrease depending on operands signs. | +| SD59x18-026 | [mul_test_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L571) | Multiplication result should be in the valid 59x18-arithmetic range. | +| SD59x18-027 | [mul_test_maximum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L583) | Multiplication edge case: maximum value times one should be maximum value | +| SD59x18-028 | [mul_test_minimum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L594) | Multiplication edge case: minimum value times one should be minimum value | +| SD59x18-029 | [div_test_division_identity_x_div_1](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L617) | Identity operation for division. x / 1 == x | +| SD59x18-030 | [div_test_division_identity_x_div_x](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L626) | Identity operation for division. x / x == 1 | +| SD59x18-031 | [div_test_negative_divisor](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L644) | Division result sign should change according to divisor sign. | +| SD59x18-032 | [div_test_division_num_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L656) | Division with zero numerator should be zero. | +| SD59x18-033 | [div_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L667) | Division result should increase or decrease (in absolute value) depending on divisor's absolute value. | +| SD59x18-034 | [div_test_div_by_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L688) | Division edge case: Divisor zero should revert. | +| SD59x18-035 | [div_test_maximum_denominator](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L699) | Division edge case: Division result by a large number should be less than one. | +| SD59x18-036 | [div_test_maximum_numerator](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L708) | Division edge case: Division result of maximum value should revert if divisor is less than one. | +| SD59x18-037 | [div_test_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L722) | Division result should be in the valid 64x64-arithmetic range. | +| SD59x18-038 | [neg_test_double_negation](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L749) | Double sign negation should be equal to the original operand. | +| SD59x18-039 | [neg_test_identity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L757) | Identity operation for sign negation. | +| SD59x18-040 | [neg_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L771) | Negation edge case: Negation of zero should be zero. | +| SD59x18-041 | [neg_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L779) | Negation edge case: Negation of maximum value minus epsilon should not revert. | +| SD59x18-042 | [neg_test_minimum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L789) | Negation edge case: Negation of minimum value plus epsilon should not revert. | +| SD59x18-043 | [abs_test_positive](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L811) | Absolute value should be always positive. | +| SD59x18-044 | [abs_test_negative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L820) | Absolute value of a number and its negation should be equal. | +| SD59x18-045 | [abs_test_multiplicativeness](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L831) | Multiplicativeness property for absolute value. | +| SD59x18-046 | [abs_test_subadditivity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L848) | Subadditivity property for absolute value. | +| SD59x18-047 | [abs_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L865) | Absolute value edge case: absolute value of zero is zero. | +| SD59x18-048 | [abs_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L879) | Absolute value edge case: absolute value of maximum value is maximum value. | +| SD59x18-049 | [abs_test_minimum_revert](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L892) | Absolute value edge case: absolute value of minimum value should revert | +| SD59x18-050 | [abs_test_minimum_allowed](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L902) | Absolute value edge case: absolute value of minimum permitted value is the negation of minimum value. | +| SD59x18-051 | [inv_test_double_inverse](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L929) | Result of double inverse should be _close enough_ to the original operand. | +| SD59x18-052 | [inv_test_division](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L941) | Inverse should be equivalent to division. | +| SD59x18-053 | [inv_test_division_noncommutativity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L952) | Anticommutative property for inverse operation. | +| SD59x18-054 | [inv_test_multiplication](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L966) | Multiplication of inverses should be equal to inverse of multiplication. | +| SD59x18-055 | [inv_test_identity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L984) | Identity property for inverse. | +| SD59x18-056 | [inv_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L997) | Inverse result should be in range (0, 1) if operand is greater than one. | +| SD59x18-057 | [inv_test_sign](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1011) | Inverse result should keep operand's sign. | +| SD59x18-058 | [inv_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1030) | Inverse edge case: Inverse of zero should revert. | +| SD59x18-059 | [inv_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1038) | Inverse edge case: Inverse of maximum value should be close to zero. | +| SD59x18-060 | [inv_test_minimum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1051) | Inverse edge case: Inverse of minimum value should be close to zero. | +| SD59x18-061 | [avg_test_values_in_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1077) | Average result should be between operands. | +| SD59x18-062 | [avg_test_one_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1089) | Average of the same number twice is the number itself. | +| SD59x18-063 | [avg_test_operand_order](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1097) | Average result does not depend on the order of operands. | +| SD59x18-064 | [avg_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1111) | Average edge case: Average of maximum value twice is the maximum value. | +| SD59x18-065 | [avg_test_minimum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1125) | Average edge case: Average of minimum value twice is the minimum value. | +| SD59x18-066 | [pow_test_zero_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1152) | Power of zero should be one. | +| SD59x18-067 | [pow_test_zero_base_non_zero_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1161) | Zero to the power of any number should be zero. | +| SD59x18-068 | [pow_test_zero_base_zero_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1170) | Zero to the power of zero should be one | +| SD59x18-069 | [pow_test_one_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1178) | Power of one should be equal to the operand. | +| SD59x18-070 | [pow_test_base_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1187) | One to the power of any number should be one. | +| SD59x18-071 | [pow_test_product_same_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1196) | Product of powers of the same base property | +| SD59x18-072 | [pow_test_power_of_an_exponentiation](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1217) | Power of an exponentiation property | +| SD59x18-073 | [pow_test_product_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1239) | Distributive property for power of a product | +| SD59x18-074 | [pow_test_positive_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1260) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | +| SD59x18-075 | [pow_test_negative_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1278) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | +| SD59x18-076 | [pow_test_sign](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1296) | Power result sign should change according to the exponent sign. | +| SD59x18-077 | [pow_test_exp2_equivalence](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1319) | Base-2 exponentiation should be equal to power. | +| SD59x18-078 | [pow_test_maximum_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1334) | Power edge case: Power of the maximum value should revert if exponent > 1. | +| SD59x18-079 | [pow_test_high_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1346) | Power edge case: Result should be zero if base is small and exponent is large. | +| SD59x18-080 | [sqrt_test_inverse_mul](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1368) | Square root inverse as multiplication. | +| SD59x18-081 | [sqrt_test_inverse_pow](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1386) | Square root inverse as power. | +| SD59x18-082 | [sqrt_test_distributive](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1404) | Square root distributive property respect to multiplication. | +| SD59x18-083 | [sqrt_test_is_increasing](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1424) | Square root should be strictly increasing for any x | +| SD59x18-084 | [sqrt_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1442) | Square root edge case: square root of zero should be zero. | +| SD59x18-085 | [sqrt_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1447) | Square root edge case: square root of maximum value should not revert. | +| SD59x18-086 | [sqrt_test_minimum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1458) | Square root edge case: square root of minimum value should revert (negative). | +| SD59x18-087 | [sqrt_test_negative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1468) | Square root edge case: square root of a negative value should revert. | +| SD59x18-088 | [log2_test_distributive_mul](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1493) | Base-2 logarithm distributive property respect to multiplication. | +| SD59x18-089 | [log2_test_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1514) | Base-2 logarithm of a power property. | +| SD59x18-090 | [log2_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1531) | Base-2 logarithm edge case: Logarithm of zero should revert. | +| SD59x18-091 | [log2_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1541) | Base-2 logarithm edge case: Logarithm of maximum value should not revert. | +| SD59x18-092 | [log2_test_negative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1555) | Base-2 logarithm edge case: Logarithm of a negative value should revert. | +| SD59x18-093 | [ln_test_distributive_mul](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1580) | Natural logarithm distributive property respect to multiplication. | +| SD59x18-094 | [ln_test_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1601) | Natural logarithm of a power property. | +| SD59x18-095 | [ln_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1621) | Natural logarithm edge case: Logarithm of zero should revert. | +| SD59x18-096 | [ln_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1631) | Natural logarithm edge case: Logarithm of maximum value should not revert. | +| SD59x18-097 | [ln_test_negative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1645) | Natural logarithm edge case: Logarithm of a negative value should revert. | +| SD59x18-098 | [exp2_test_equivalence_pow](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1670) | Base-2 exponentiation should be equal to power. | +| SD59x18-099 | [exp2_test_inverse](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1680) | Base-2 exponentiation inverse function. | +| SD59x18-100 | [exp2_test_negative_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1694) | Base-2 exponentiation with negative exponent should equal the inverse. | +| SD59x18-101 | [exp2_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1716) | Base-2 exponentiation edge case: exponent zero result should be one. | +| SD59x18-102 | [exp2_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1723) | Base-2 exponentiation edge case: exponent maximum value should revert. | +| SD59x18-103 | [exp2_test_maximum_permitted](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1734) | Base-2 exponentiation edge case: exponent maximum permitted value should not revert. | +| SD59x18-104 | [exp2_test_minimum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1745) | Base-2 exponentiation edge case: exponent minimum value result should be zero. | +| SD59x18-105 | [exp_test_inverse](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1772) | Natural exponentiation inverse function. | +| SD59x18-106 | [exp_test_negative_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1787) | Natural exponentiation with negative exponent should equal the inverse. | +| SD59x18-107 | [exp_test_strictly_increasing](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1798) | Natural exponentiation should be strictly increasing for any x | +| SD59x18-108 | [exp_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1816) | Natural exponentiation edge case: exponent zero result should be one. | +| SD59x18-109 | [exp_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1823) | Natural exponentiation edge case: exponent maximum value should revert. | +| SD59x18-110 | [exp_test_maximum_permitted](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1834) | Natural exponentiation edge case: exponent maximum value should revert. | +| SD59x18-111 | [exp_test_minimum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1845) | Natural exponentiation edge case: exponent minimum value result should be zero. | +| SD59x18-112 | [powu_test_zero_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1872) | Power of zero should be one. | +| SD59x18-113 | [powu_test_zero_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1882) | Zero to the power of any number should be zero. | +| SD59x18-114 | [powu_test_one_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1892) | Power of one should be equal to the operand. | +| SD59x18-115 | [powu_test_base_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1902) | One to the power of any number should be one. | +| SD59x18-116 | [powu_test_product_same_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1910) | Product of powers of the same base property | +| SD59x18-117 | [powu_test_power_of_an_exponentiation](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1927) | Power of an exponentiation property | +| SD59x18-118 | [powu_test_product_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1944) | Distributive property for power of a product | +| SD59x18-119 | [powu_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1965) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | +| SD59x18-120 | [powu_test_sign](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1982) | Power result sign should change according to the exponent sign. | +| SD59x18-121 | [powu_test_maximum_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2011) | Power edge case: Power of the maximum value should revert if exponent > 1. | +| SD59x18-122 | [powu_test_high_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2023) | Power edge case: Result should be zero if base is small and exponent is large. | +| SD59x18-123 | [log10_test_distributive_mul](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2045) | Base-10 logarithm distributive property respect to multiplication. | +| SD59x18-124 | [log10_test_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2066) | Base-10 logarithm of a power property. | +| SD59x18-125 | [log10_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2082) | Base-10 logarithm edge case: Logarithm of zero should revert. | +| SD59x18-126 | [log10_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2092) | Base-10 logarithm edge case: Logarithm of maximum value should not revert. | +| SD59x18-127 | [log10_test_negative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2106) | Base-10 logarithm edge case: Logarithm of a negative value should revert. | +| SD59x18-128 | [gm_test_product](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2131) | Product of the values should be equal to the geometric mean raised to the power of N | +| SD59x18-129 | [gm_test_positive_set_avg](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2145) | The geometric mean of a set of positive values should be less than the arithmetic mean | +| SD59x18-130 | [gm_test_positive_equal_set_avg](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2157) | The geometric mean of a set of equal positive values should be equal to the arithmetic mean | +| SD59x18-131 | [gm_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2174) | GM edge case: if a set contains zero, the result is zero | +| SD59x18-132 | [gm_test_negative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2187) | GM edge case: The geometric mean is not defined when the set contains an odd number of negative values | + +## PRBMath UD60x18 + +| ID | Name | Invariant tested | +| ------------| ----------------------------------------------------------------------------------------------------------------------------------------------------------| ------------------------------------------------------------------------------------------------------ | +| UD60x18-001 | [add_test_commutative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L228) | Commutative property for addition. | +| UD60x18-002 | [add_test_associative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L237) | Associative property for addition. | +| UD60x18-003 | [add_test_identity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L248) | Identity operation for addition. | +| UD60x18-004 | [add_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L257) | Addition result should increase or decrease depending on operands signs. | +| UD60x18-005 | [add_test_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L271) | Addition result should be in the valid 60x18-arithmetic range. | +| UD60x18-006 | [add_test_maximum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L281) | Addition edge case: maximum value plus zero should be maximum value. | +| UD60x18-007 | [add_test_maximum_value_plus_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L291) | Addition edge case: maximum value plus one should revert (out of range). | +| UD60x18-008 | [add_test_minimum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L301) | Addition edge case: minimum value plus zero should be minimum value. | +| UD60x18-009 | [sub_test_identity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L324) | Identity operation for subtraction. | +| UD60x18-010 | [sub_test_neutrality](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L333) | Adding and subtracting the same value should not affect original value. | +| UD60x18-011 | [sub_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L345) | Subtraction result should increase or decrease depending on operands signs. | +| UD60x18-012 | [sub_test_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L359) | Subtraction result should be in the valid 60x18-arithmetic range. | +| UD60x18-013 | [sub_test_maximum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L369) | Subtraction edge case: maximum value minus zero should be maximum value. | +| UD60x18-014 | [sub_test_minimum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L380) | Subtraction edge case: minimum value minus zero should be minimum value. | +| UD60x18-015 | [sub_test_minimum_value_minus_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L390) | Subtraction edge case: minimum value minus one should revert (out of range). | +| UD60x18-016 | [mul_test_commutative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L412) | Commutative property for multiplication. | +| UD60x18-017 | [mul_test_associative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L421) | Associative property for multiplication. | +| UD60x18-018 | [mul_test_distributive](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L433) | Distributive property for multiplication. | +| UD60x18-019 | [mul_test_identity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L445) | Identity operation for multiplication. | +| UD60x18-020 | [mul_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L455) | Multiplication result should increase or decrease depending on operands signs. | +| UD60x18-021 | [mul_test_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L473) | Multiplication result should be in the valid 59x18-arithmetic range. | +| UD60x18-022 | [mul_test_maximum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L483) | Multiplication edge case: maximum value times one should be maximum value | +| UD60x18-023 | [div_test_division_identity_x_div_1](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L506) | Identity operation for division. x / 1 == x | +| UD60x18-024 | [div_test_division_identity_x_div_x](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L514) | Identity operation for division. x / x == 1 | +| UD60x18-025 | [div_test_division_num_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L529) | Division with zero numerator should be zero. | +| UD60x18-026 | [div_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L539) | Division result should increase or decrease (in absolute value) depending on divisor's absolute value. | +| UD60x18-027 | [div_test_div_by_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L558) | Division edge case: Divisor zero should revert. | +| UD60x18-028 | [div_test_maximum_denominator](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L568) | Division edge case: Division result by a large number should be less than one. | +| UD60x18-029 | [div_test_maximum_numerator](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L576) | Division edge case: Division result of maximum value should revert if divisor is less than one. | +| UD60x18-030 | [div_test_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L591) | Division result should be in the valid 60x18-arithmetic range. | +| UD60x18-031 | [inv_test_double_inverse](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L618) | Result of double inverse should be _close enough_ to the original operand. | +| UD60x18-032 | [inv_test_division](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L630) | Inverse should be equivalent to division. | +| UD60x18-033 | [inv_test_division_noncommutativity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L641) | Anticommutative property for inverse operation. | +| UD60x18-034 | [inv_test_multiplication](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L655) | Multiplication of inverses should be equal to inverse of multiplication. | +| UD60x18-035 | [inv_test_identity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L673) | Identity property for inverse. | +| UD60x18-036 | [inv_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L688) | Inverse result should be in range (0, 1) if operand is greater than one. | +| UD60x18-037 | [inv_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L707) | Inverse edge case: Inverse of zero should revert. | +| UD60x18-038 | [inv_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L715) | Inverse edge case: Inverse of maximum value should be close to zero. | +| UD60x18-039 | [inv_test_minimum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L728) | Inverse edge case: Inverse of minimum value should be close to zero. | +| UD60x18-040 | [avg_test_values_in_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L754) | Average result should be between operands. | +| UD60x18-041 | [avg_test_one_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L766) | Average of the same number twice is the number itself. | +| UD60x18-042 | [avg_test_operand_order](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L774) | Average result does not depend on the order of operands. | +| UD60x18-043 | [avg_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L788) | Average edge case: Average of maximum value twice is the maximum value. | +| UD60x18-044 | [pow_test_zero_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L815) | Power of zero should be one. | +| UD60x18-045 | [pow_test_zero_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L824) | Zero to the power of any number should be zero. | +| UD60x18-046 | [pow_test_one_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L835) | Power of one should be equal to the operand. | +| UD60x18-047 | [pow_test_base_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L844) | One to the power of any number should be one. | +| UD60x18-048 | [pow_test_product_same_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L852) | Product of powers of the same base property | +| UD60x18-049 | [pow_test_power_of_an_exponentiation](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L873) | Power of an exponentiation property | +| UD60x18-050 | [pow_test_product_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L894) | Distributive property for power of a product | +| UD60x18-051 | [pow_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L918) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | +| UD60x18-052 | [pow_test_maximum_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L940) | Power edge case: Power of the maximum value should revert if exponent > 1. | +| UD60x18-053 | [pow_test_maximum_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L952) | Power edge case: Power of the maximum value should revert if exponent > 1. | +| UD60x18-054 | [pow_test_high_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L965) | Power edge case: Result should be zero if base is small and exponent is large. | +| UD60x18-055 | [sqrt_test_inverse_mul](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L987) | Square root inverse as multiplication. | +| UD60x18-056 | [sqrt_test_inverse_pow](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1004) | Square root inverse as power. | +| UD60x18-057 | [sqrt_test_distributive](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1021) | Square root distributive property respect to multiplication. | +| UD60x18-058 | [sqrt_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1039) | Square root edge case: square root of zero should be zero. | +| UD60x18-059 | [sqrt_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1044) | Square root edge case: square root of maximum value should not revert. | +| UD60x18-060 | [log2_test_distributive_mul](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1068) | Base-2 logarithm distributive property respect to multiplication. | +| UD60x18-061 | [log2_test_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1087) | Base-2 logarithm of a power property. | +| UD60x18-062 | [log2_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1109) | Base-2 logarithm edge case: Logarithm of zero should revert. | +| UD60x18-063 | [log2_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1119) | Base-2 logarithm edge case: Logarithm of maximum value should not revert. | +| UD60x18-064 | [log2_test_less_than_unit](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1133) | Base-2 logarithm edge case: Logarithm of a negative value should revert. | +| UD60x18-065 | [ln_test_distributive_mul](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1158) | Natural logarithm distributive property respect to multiplication. | +| UD60x18-066 | [ln_test_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1179) | Natural logarithm of a power property. | +| UD60x18-067 | [ln_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1199) | Natural logarithm edge case: Logarithm of zero should revert. | +| UD60x18-068 | [ln_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1209) | Natural logarithm edge case: Logarithm of maximum value should not revert. | +| UD60x18-069 | [ln_test_less_than_unit](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1223) | Natural logarithm edge case: Logarithm of a negative value should revert. | +| UD60x18-070 | [exp2_test_equivalence_pow](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1248) | Base-2 exponentiation should be equal to power. | +| UD60x18-071 | [exp2_test_inverse](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1258) | Base-2 exponentiation inverse function. | +| UD60x18-072 | [exp2_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1279) | Base-2 exponentiation edge case: exponent zero result should be one. | +| UD60x18-073 | [exp2_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1286) | Base-2 exponentiation edge case: exponent maximum value should revert. | +| UD60x18-075 | [exp_test_inverse](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1309) | Natural exponentiation inverse function. | +| UD60x18-076 | [exp_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1331) | Natural exponentiation edge case: exponent zero result should be one. | +| UD60x18-077 | [exp_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1338) | Natural exponentiation edge case: exponent maximum value should revert. | +| UD60x18-078 | [powu_test_zero_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1361) | Power of zero should be one. | +| UD60x18-079 | [powu_test_zero_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1369) | Zero to the power of any number should be zero. | +| UD60x18-080 | [powu_test_one_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1379) | Power of one should be equal to the operand. | +| UD60x18-081 | [powu_test_base_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1387) | One to the power of any number should be one. | +| UD60x18-082 | [powu_test_product_same_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1395) | Product of powers of the same base property | +| UD60x18-083 | [powu_test_power_of_an_exponentiation](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1411) | Power of an exponentiation property | +| UD60x18-084 | [powu_test_product_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1427) | Distributive property for power of a product | +| UD60x18-085 | [powu_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1447) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | +| UD60x18-086 | [powu_test_maximum_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1468) | Power edge case: Power of the maximum value should revert if exponent > 1. | +| UD60x18-087 | [powu_test_high_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1480) | Power edge case: Result should be zero if base is small and exponent is large. | +| UD60x18-088 | [log10_test_distributive_mul](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1502) | Base-10 logarithm distributive property respect to multiplication. | +| UD60x18-089 | [log10_test_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1523) | Base-10 logarithm of a power property. | +| UD60x18-090 | [log10_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1543) | Base-10 logarithm edge case: Logarithm of zero should revert. | +| UD60x18-091 | [log10_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1553) | Base-10 logarithm edge case: Logarithm of maximum value should not revert. | +| UD60x18-092 | [log10_test_less_than_unit](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1567) | Base-10 logarithm edge case: Logarithm of a negative value should revert. | +| UD60x18-093 | [gm_test_product](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1592) | Product of the values should be equal to the geometric mean raised to the power of N | +| UD60x18-094 | [gm_test_positive_set_avg](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1602) | The geometric mean of a set of positive values should be less than the arithmetic mean | +| UD60x18-095 | [gm_test_positive_equal_set_avg](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1614) | The geometric mean of a set of equal positive values should be equal to the arithmetic mean | +| UD60x18-096 | [gm_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1629) | GM edge case: if a set contains zero, the result is zero | \ No newline at end of file diff --git a/README.md b/README.md index 003a6f8..62e2536 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ - [ERC721 Tests](#erc721-tests) - [ERC4626 Tests](#erc4626-tests) - [ABDKMath64x64 tests](#abdkmath64x64-tests) + - [PRBMath tests](#prbmath-tests) - [Additional resources](#additional-resources) - [Helper functions](#helper-functions) - [Usage examples](#usage-examples) @@ -26,6 +27,7 @@ This repository contains 168 code properties for: - [ERC721](https://ethereum.org/en/developers/docs/standards/tokens/erc-721/) token: mintable, burnable, and transferable invariants ([19 properties](PROPERTIES.md#erc721)). - [ERC4626](https://ethereum.org/en/developers/docs/standards/tokens/erc-4626/) vaults: strict specification and additional security invariants ([37 properties](PROPERTIES.md#erc4626)). - [ABDKMath64x64](https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.md) fixed-point library invariants ([106 properties](PROPERTIES.md#abdkmath64x64)). +- [PRBMath](https://github.com/PaulRBerg/prb-math/blob/main/README.md) fixed-point library invariants ([132 properties](PROPERTIES.md#prbmath-sd59x18)) for SD59x18, and ([96 properties](PROPERTIES.md#prbmath-ud60x18)) for UD60x18. The goals of these properties are to: @@ -47,6 +49,7 @@ The properties can be used through unit tests or through fuzzing with [Echidna]( - [ERC20 tests](#erc20-tests) - [ERC4626 test](#erc4626-tests) - [ABDKMath64x64 tests](#abdkmath64x64-tests) + - [PRBMath tests](#prbmath-tests) ### ERC20 tests @@ -414,6 +417,44 @@ contract CryticABDKMath64x64Harness is CryticABDKMath64x64PropertyTests { Run the test suite using `echidna-test . --contract CryticABDKMath64x64Harness --seq-len 1 --test-mode assertion --corpus-dir tests/echidna-corpus` and inspect the coverage report in `tests/echidna-corpus` when it finishes. +### PRBMath tests + +The Solidity smart contract programming language does not have any inbuilt feature for working with decimal numbers, so for contracts dealing with non-integer values, a third party solution is needed. [PRBMath](https://github.com/PaulRBerg/prb-math) is Solidity library for advanced fixed-point math that operates with signed 59.18-decimal fixed-point and unsigned 60.18-decimal fixed-point numbers + +SD59x18 library implements [19 arithmetic operations](https://github.com/PaulRBerg/prb-math/blob/main/README.md#mathematical-functions "19 arithmetic operations") using fixed-point numbers and [11 conversion functions](https://github.com/PaulRBerg/prb-math/blob/main/README.md#mathematical-functions "6 conversion functions") between integer types and fixed-point types. + +We provide a number of tests related with fundamental mathematical properties of the floating point numbers. To include these tests into your repository, follow these steps: + +1. [Integration](#integration-4) +2. [Run](#run-4) + + +#### Integration + +Create a new Solidity file containing the `PRBMathSD59x18Harness` or `PRBMath60x18Harness` contract: + +```Solidity +pragma solidity ^0.8.0; +import "@crytic/properties/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol; + +contract CryticPRBMath59x18Harness is CryticPRBMath59x18Propertiesv3 { + /* Any additional test can be added here */ +} +``` + +```Solidity +pragma solidity ^0.8.0; +import "@crytic/properties/contracts/Math/PRBMath/v3/PRBMathSD60x18PropertyTests.sol; + +contract CryticPRBMath60x18Harness is CryticPRBMath60x18Propertiesv3 { + /* Any additional test can be added here */ +} +``` + +#### Run + +Run the test suite using `echidna-test . --contract CryticPRBMath59x18Harness --seq-len 1 --test-mode assertion --corpus-dir tests/echidna-corpus` and inspect the coverage report in `tests/echidna-corpus` when it finishes. + ## Additional resources - [Building secure contracts](https://secure-contracts.com/program-analysis/index.html) From 6c505541ac5fb26c0778835e87dec0990cdf1767 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Thu, 18 May 2023 19:01:44 +0200 Subject: [PATCH 24/32] added submodule --- .gitmodules | 3 +++ contracts/Math/PRBMath/v4/prb-math-library | 1 + 2 files changed, 4 insertions(+) create mode 160000 contracts/Math/PRBMath/v4/prb-math-library diff --git a/.gitmodules b/.gitmodules index 4b5049e..383681a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ path = lib/prb-math url = https://github.com/PaulRBerg/prb-math branch = v3 +[submodule "contracts/Math/PRBMath/v4/prb-math-library"] + path = contracts/Math/PRBMath/v4/prb-math-library + url = https://github.com/PaulRBerg/prb-math diff --git a/contracts/Math/PRBMath/v4/prb-math-library b/contracts/Math/PRBMath/v4/prb-math-library new file mode 160000 index 0000000..97170d0 --- /dev/null +++ b/contracts/Math/PRBMath/v4/prb-math-library @@ -0,0 +1 @@ +Subproject commit 97170d0f52de76edfb5fe63c5810a8705c3b0ea7 From 5dc2fce6c478d12955d358d49fb9c1454ffa8020 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Thu, 18 May 2023 19:04:23 +0200 Subject: [PATCH 25/32] bug with submodule --- .gitmodules | 3 --- contracts/Math/PRBMath/v4/prb-math-library | 1 - 2 files changed, 4 deletions(-) delete mode 160000 contracts/Math/PRBMath/v4/prb-math-library diff --git a/.gitmodules b/.gitmodules index 383681a..4b5049e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,6 +22,3 @@ path = lib/prb-math url = https://github.com/PaulRBerg/prb-math branch = v3 -[submodule "contracts/Math/PRBMath/v4/prb-math-library"] - path = contracts/Math/PRBMath/v4/prb-math-library - url = https://github.com/PaulRBerg/prb-math diff --git a/contracts/Math/PRBMath/v4/prb-math-library b/contracts/Math/PRBMath/v4/prb-math-library deleted file mode 160000 index 97170d0..0000000 --- a/contracts/Math/PRBMath/v4/prb-math-library +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 97170d0f52de76edfb5fe63c5810a8705c3b0ea7 From cdff7fc399aa022e44ff373ed95e963388d80b57 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Fri, 19 May 2023 17:14:45 +0200 Subject: [PATCH 26/32] added slither config to support v4, created assert helpers, updated checks on some properties --- .gitmodules | 4 + .../v3/PRBMathSD59x18PropertyTests.sol | 2675 +++++++++++++++-- .../v3/PRBMathUD60x18PropertyTests.sol | 415 ++- .../PRBMath/v3/utils/AssertionHelperSD.sol | 316 ++ .../PRBMath/v3/utils/AssertionHelperUD.sol | 313 ++ .../v4/PRBMathSD59x18PropertyTests.sol | 2211 ++++++++++++++ .../v4/PRBMathUD60x18PropertyTests.sol | 1635 ++++++++++ .../PRBMath/v4/utils/AssertionHelperSD.sol | 316 ++ .../PRBMath/v4/utils/AssertionHelperUD.sol | 313 ++ lib/prb-math | 2 +- lib/prb-math-v3 | 1 + package.json | 6 +- remappings.txt | 4 +- slither.config.json | 1 + 14 files changed, 7767 insertions(+), 445 deletions(-) create mode 100644 contracts/Math/PRBMath/v3/utils/AssertionHelperSD.sol create mode 100644 contracts/Math/PRBMath/v3/utils/AssertionHelperUD.sol create mode 100644 contracts/Math/PRBMath/v4/PRBMathSD59x18PropertyTests.sol create mode 100644 contracts/Math/PRBMath/v4/PRBMathUD60x18PropertyTests.sol create mode 100644 contracts/Math/PRBMath/v4/utils/AssertionHelperSD.sol create mode 100644 contracts/Math/PRBMath/v4/utils/AssertionHelperUD.sol create mode 160000 lib/prb-math-v3 create mode 100644 slither.config.json diff --git a/.gitmodules b/.gitmodules index 4b5049e..631b9eb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -21,4 +21,8 @@ [submodule "lib/prb-math"] path = lib/prb-math url = https://github.com/PaulRBerg/prb-math + branch = v4 +[submodule "lib/prb-math-v3"] + path = lib/prb-math-v3 + url = https://github.com/PaulRBerg/prb-math branch = v3 diff --git a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol index 705afa0..dfb937f 100644 --- a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol @@ -1,13 +1,14 @@ pragma solidity ^0.8.19; -import { SD59x18 } from "@prb/math/src/SD59x18.sol"; -import {add, sub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb/math/src/sd59x18/Helpers.sol"; -import {convert} from "@prb/math/src/sd59x18/Conversions.sol"; -import {msb} from "@prb/math/src/Common.sol"; -import {intoUint128, intoUint256} from "@prb/math/src/sd59x18/Casting.sol"; -import {mul, div, abs, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, powu, gm} from "@prb/math/src/sd59x18/Math.sol"; +import { SD59x18 } from "@prb-math-v3/SD59x18.sol"; +import {add, sub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb-math-v3/sd59x18/Helpers.sol"; +import {convert} from "@prb-math-v3/sd59x18/Conversions.sol"; +import {msb} from "@prb-math-v3/Common.sol"; +import {intoUint128, intoUint256} from "@prb-math-v3/sd59x18/Casting.sol"; +import {mul, div, abs, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, powu, gm} from "@prb-math-v3/sd59x18/Math.sol"; +import "./utils/AssertionHelperSD.sol"; -contract CryticPRBMath59x18Propertiesv3 { +contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { /* ================================================================ 59x18 fixed-point constants used for testing specific values. @@ -85,42 +86,2215 @@ contract CryticPRBMath59x18Propertiesv3 { ================================================================ */ event Value(string reason, SD59x18 val); event LogErr(bytes error); - event PropertyFailed(SD59x18 result); - event PropertyFailed(SD59x18 result1, SD59x18 result2); - event PropertyFailed(SD59x18 result1, SD59x18 result2, uint256 discardedDigits); - event PropertyFailed(SD59x18 result1, SD59x18 result2, SD59x18 discardedDigits); - event TestLog(int256 num1, int256 num2, int256 result); /* ================================================================ Helper functions. ================================================================ */ - // These functions allows to compare a and b for equality, discarding - // the last precision_bits bits. + // Check that there are remaining significant digits after a multiplication + // Uses functions from the library under test! + function significant_digits_are_lost_in_mult(SD59x18 a, SD59x18 b) public pure returns (bool) { + int256 la = convert(floor(log10(abs(a)))); + int256 lb = convert(floor(log10(abs(b)))); + + return(la + lb < -18); + } + + // Return how many significant bits will remain after multiplying a and b + // Uses functions from the library under test! + function significant_digits_after_mult(SD59x18 a, SD59x18 b) public pure returns (uint256) { + int256 la = convert(floor(log10(abs(a)))); + int256 lb = convert(floor(log10(abs(b)))); + int256 prec = la + lb; + + if (prec < -18) return 0; + else return(59 + uint256(prec)); + } + + // Return how many significant bits will be lost after multiplying a and b // Uses functions from the library under test! - function equal_within_precision(SD59x18 a, SD59x18 b, uint256 precision_bits) public pure returns(bool) { - SD59x18 max = gt(a , b) ? a : b; - SD59x18 min = gt(a , b) ? b : a; - SD59x18 r = rshift(sub(max, min), precision_bits); + function significant_digits_lost_in_mult(SD59x18 a, SD59x18 b) public pure returns (uint256) { + int256 la = convert(floor(log10(abs(a)))); + int256 lb = convert(floor(log10(abs(b)))); + // digits left + int256 prec = la + lb; + + if (la > 0 && lb > 0) { + return 0; + } else { + return la < lb ? uint256(-la) : uint256(-lb); + } + } + + /* ================================================================ + Library wrappers. + These functions allow calling the PRBMathSD59x18 library. + ================================================================ */ + function debug(string calldata x, SD59x18 y) public { + emit Value(x, y); + } + + // Wrapper for external try/catch calls + function helpersAdd(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return add(x,y); + } + + // Wrapper for external try/catch calls + function helpersSub(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return sub(x,y); + } + + // Wrapper for external try/catch calls + function helpersMul(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return mul(x,y); + } + + function helpersDiv(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return div(x,y); + } + + function neg(SD59x18 x) public pure returns (SD59x18) { + return SD59x18.wrap(-SD59x18.unwrap(x)); + } + + function helpersAbs(SD59x18 x) public pure returns (SD59x18) { + return abs(x); + } + + function helpersLn(SD59x18 x) public pure returns (SD59x18) { + return ln(x); + } + + function helpersExp(SD59x18 x) public pure returns (SD59x18) { + return exp(x); + } + + function helpersExp2(SD59x18 x) public pure returns (SD59x18) { + return exp2(x); + } + + function helpersLog2(SD59x18 x) public pure returns (SD59x18) { + return log2(x); + } + + function helpersSqrt(SD59x18 x) public pure returns (SD59x18) { + return sqrt(x); + } + + function helpersPow(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return pow(x, y); + } + + function helpersPowu(SD59x18 x, uint256 y) public pure returns (SD59x18) { + return powu(x, y); + } + + function helpersAvg(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return avg(x, y); + } + + function helpersInv(SD59x18 x) public pure returns (SD59x18) { + return inv(x); + } + + function helpersLog10(SD59x18 x) public pure returns (SD59x18) { + return log10(x); + } + + function helpersFloor(SD59x18 x) public pure returns (SD59x18) { + return floor(x); + } + + function helpersGm(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return gm(x,y); + } + + /* ================================================================ + + TESTS FOR FUNCTION add() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for commutative property + // x + y == y + x + function add_test_commutative(SD59x18 x, SD59x18 y) public { + SD59x18 x_y = x.add(y); + SD59x18 y_x = y.add(x); + + assertEq(x_y, y_x); + } + + // Test for associative property + // (x + y) + z == x + (y + z) + function add_test_associative(SD59x18 x, SD59x18 y, SD59x18 z) public { + SD59x18 x_y = x.add(y); + SD59x18 y_z = y.add(z); + SD59x18 xy_z = x_y.add(z); + SD59x18 x_yz = x.add(y_z); + + assertEq(xy_z, x_yz); + } + + // Test for identity operation + // x + 0 == x (equivalent to x + (-x) == 0) + function add_test_identity(SD59x18 x) public { + SD59x18 x_0 = x.add(ZERO_FP); + + assertEq(x, x_0); + assertEq(x.sub(x), ZERO_FP); + } + + // Test that the result increases or decreases depending + // on the value to be added + function add_test_values(SD59x18 x, SD59x18 y) public { + SD59x18 x_y = x.add(y); + + if (y.gte(ZERO_FP)) { + assertGte(x_y, x); + } else { + assertLt(x_y, x); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These should make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // The result of the addition must be between the maximum + // and minimum allowed values for SD59x18 + function add_test_range(SD59x18 x, SD59x18 y) public { + try this.helpersAdd(x, y) returns (SD59x18 result) { + assertLte(result, MAX_SD59x18); + assertGte(result, MIN_SD59x18); + } catch { + // If it reverts, just ignore + } + } + + // Adding zero to the maximum value shouldn't revert, as it is valid + // Moreover, the result must be MAX_SD59x18 + function add_test_maximum_value() public { + try this.helpersAdd(MAX_SD59x18, ZERO_FP) returns (SD59x18 result) { + // Expected behaviour, does not revert + assertEq(result, MAX_SD59x18); + } catch { + assert(false); + } + } + + // Adding one to the maximum value should revert, as it is out of range + function add_test_maximum_value_plus_one() public { + try this.helpersAdd(MAX_SD59x18, ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + // Adding zero to the minimum value shouldn't revert, as it is valid + // Moreover, the result must be MIN_SD59x18 + function add_test_minimum_value() public { + try this.helpersAdd(MIN_SD59x18, ZERO_FP) returns (SD59x18 result) { + // Expected behaviour, does not revert + assertEq(result, MIN_SD59x18); + } catch { + assert(false); + } + } + + // Adding minus one to the maximum value should revert, as it is out of range + function add_test_minimum_value_plus_negative_one() public { + try this.helpersAdd(MIN_SD59x18, MINUS_ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + /* ================================================================ + + TESTS FOR FUNCTION sub() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test equivalence to addition + // x - y == x + (-y) + function sub_test_equivalence_to_addition(SD59x18 x, SD59x18 y) public { + SD59x18 minus_y = neg(y); + SD59x18 addition = x.add(minus_y); + SD59x18 subtraction = x.sub(y); + + assertEq(addition, subtraction); + } + + // Test for non-commutative property + // x - y == -(y - x) + function sub_test_non_commutative(SD59x18 x, SD59x18 y) public { + SD59x18 x_y = x.sub(y); + SD59x18 y_x = y.sub(x); + + assertEq(x_y, neg(y_x)); + } + + // Test for identity operation + // x - 0 == x (equivalent to x - x == 0) + function sub_test_identity(SD59x18 x) public { + SD59x18 x_0 = x.sub(ZERO_FP); + + assertEq(x_0, x); + assertEq(x.sub(x), ZERO_FP); + } + + // Test for neutrality over addition and subtraction + // (x - y) + y == (x + y) - y == x + function sub_test_neutrality(SD59x18 x, SD59x18 y) public { + SD59x18 x_minus_y = x.sub(y); + SD59x18 x_plus_y = x.add(y); + + SD59x18 x_minus_y_plus_y = x_minus_y.add(y); + SD59x18 x_plus_y_minus_y = x_plus_y.sub(y); + + assertEq(x_minus_y_plus_y, x_plus_y_minus_y); + assertEq(x_minus_y_plus_y, x); + } + + // Test that the result increases or decreases depending + // on the value to be subtracted + function sub_test_values(SD59x18 x, SD59x18 y) public { + SD59x18 x_y = x.sub(y); + + if (y.gte(ZERO_FP)) { + assertLte(x_y, x); + } else { + assertGt(x_y, x); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // The result of the subtraction must be between the maximum + // and minimum allowed values for SD59x18 + function sub_test_range(SD59x18 x, SD59x18 y) public { + try this.helpersSub(x, y) returns (SD59x18 result) { + assertLte(result, MAX_SD59x18); + assertGte(result, MIN_SD59x18); + } catch { + // If it reverts, just ignore + } + } + + // Subtracting zero from the maximum value shouldn't revert, as it is valid + // Moreover, the result must be MAX_SD59x18 + function sub_test_maximum_value() public { + try this.helpersSub(MAX_SD59x18, ZERO_FP) returns (SD59x18 result) { + // Expected behaviour, does not revert + assertEq(result, MAX_SD59x18); + } catch { + assert(false); + } + } + + // Subtracting minus one from the maximum value should revert, + // as it is out of range + function sub_test_maximum_value_minus_neg_one() public { + try this.helpersSub(MAX_SD59x18, MINUS_ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + // Subtracting zero from the minimum value shouldn't revert, as it is valid + // Moreover, the result must be MIN_SD59x18 + function sub_test_minimum_value() public { + try this.helpersSub(MIN_SD59x18, ZERO_FP) returns (SD59x18 result) { + // Expected behaviour, does not revert + assertEq(result, MIN_SD59x18); + } catch { + assert(false); + } + } + + // Subtracting one from the minimum value should revert, as it is out of range + function sub_test_minimum_value_minus_one() public { + try this.helpersSub(MIN_SD59x18, ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + /* ================================================================ + + TESTS FOR FUNCTION mul() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for commutative property + // x * y == y * x + function mul_test_commutative(SD59x18 x, SD59x18 y) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + SD59x18 x_y = x.mul(y); + SD59x18 y_x = y.mul(x); + + assertEq(x_y, y_x); + } + + // Test for associative property + // (x * y) * z == x * (y * z) + function mul_test_associative(SD59x18 x, SD59x18 y, SD59x18 z) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18) && z.gt(MIN_SD59x18)); + SD59x18 x_y = x.mul(y); + SD59x18 y_z = y.mul(z); + SD59x18 xy_z = x_y.mul(z); + SD59x18 x_yz = x.mul(y_z); + + require(xy_z.neq(ZERO_FP) && x_yz.neq(ZERO_FP)); + + uint256 digitsLost = significant_digits_lost_in_mult(x, y); + digitsLost += significant_digits_lost_in_mult(x, z); + + assertEqWithinDecimalPrecision(xy_z, x_yz, digitsLost); + } + + // Test for distributive property + // x * (y + z) == x * y + x * z + function mul_test_distributive(SD59x18 x, SD59x18 y, SD59x18 z) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18) && z.gt(MIN_SD59x18)); + SD59x18 y_plus_z = y.add(z); + SD59x18 x_times_y_plus_z = x.mul(y_plus_z); + + SD59x18 x_times_y = x.mul(y); + SD59x18 x_times_z = x.mul(z); + + require(add(x_times_y, x_times_z).neq(ZERO_FP) && x_times_y_plus_z.neq(ZERO_FP)); + assertEqWithinTolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP, "0.1%"); + } + + + // Test for identity operation + // x * 1 == x (also check that x * 0 == 0) + function mul_test_identity(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + SD59x18 x_1 = x.mul(ONE_FP); + SD59x18 x_0 = x.mul(ZERO_FP); + + assertEq(x_0, ZERO_FP); + assertEq(x_1, x); + } + + + // If x is positive and y is >= 1, the result should be larger than or equal to x + // If x is positive and y is < 1, the result should be smaller than x + function mul_test_x_positive(SD59x18 x, SD59x18 y) public { + require(x.gte(ZERO_FP)); + + SD59x18 x_y = x.mul(y); + + if (y.gte(ONE_FP)) { + assertGte(x_y, x); + } else { + assertLte(x_y, x); + } + } + + // If x is negative and y is >= 1, the result should be smaller than or equal to x + // If x is negative and y is < 1, the result should be larger than or equal to x + function mul_test_x_negative(SD59x18 x, SD59x18 y) public { + require(x.lte(ZERO_FP) && y.neq(ZERO_FP)); + + SD59x18 x_y = x.mul(y); + + if (y.gte(ONE_FP)) { + assertLte(x_y, x); + } else { + assertGte(x_y, x); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // The result of the multiplication must be between the maximum + // and minimum allowed values for SD59x18 + function mul_test_range(SD59x18 x, SD59x18 y) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + + try this.helpersMul(x, y) returns(SD59x18 result) { + assertLte(result, MAX_SD59x18); + assertGte(result, MIN_SD59x18); + } catch { + // If it reverts, just ignore + } + } + + // Multiplying the maximum value times one shouldn't revert, as it is valid + // Moreover, the result must be MAX_SD59x18 + function mul_test_maximum_value() public { + try this.helpersMul(MAX_SD59x18, ONE_FP) returns (SD59x18 result) { + // Expected behaviour, does not revert + assertEq(result, MAX_SD59x18); + } catch { + assert(false); + } + } + + // Multiplying the minimum value times one shouldn't revert, as it is valid + // Moreover, the result must be MIN_SD59x18 + function mul_test_minimum_value() public { + try this.helpersMul(MIN_SD59x18.add(ONE_FP), ONE_FP) returns (SD59x18 result) { + // Expected behaviour, does not revert + assertEq(result, MIN_SD59x18.add(ONE_FP)); + } catch { + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION div() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for identity property + // x / 1 == x (equivalent to x / x == 1) + function div_test_division_identity_x_div_1(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + SD59x18 div_1 = div(x, ONE_FP); + + assertEq(x, div_1); + } + + // Test for identity property + // x/x should not revert unless x == 0 || x == MIN_SD59x18 + function div_test_division_identity_x_div_x(SD59x18 x) public { + SD59x18 div_x; + + try this.helpersDiv(x, x) { + // This should always equal one + div_x = div(x, x); + assertEq(div_x, ONE_FP); + } catch { + // There are a couple of allowed cases for a revert: + // 1. x == 0 + // 2. x == MIN_SD59x18 + // 3. when the result overflows + assert(x.eq(ZERO_FP) || x.eq(MIN_SD59x18)); + } + } + + // Test for negative divisor + // x / -y == -(x / y) + function div_test_negative_divisor(SD59x18 x, SD59x18 y) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + require(y.lt(ZERO_FP)); + + SD59x18 x_y = div(x, y); + SD59x18 x_minus_y = div(x, neg(y)); + + assertEq(x_y, neg(x_minus_y)); + } + + // Test for division with 0 as numerator + // 0 / x = 0 + function div_test_division_num_zero(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + require(x.neq(ZERO_FP)); + + SD59x18 div_0 = div(ZERO_FP, x); + + assertEq(ZERO_FP, div_0); + } + + // Test that the absolute value of the result increases or + // decreases depending on the denominator's absolute value + function div_test_values(SD59x18 x, SD59x18 y) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + require(y.neq(ZERO_FP)); + + SD59x18 x_y = abs(div(x, y)); + + if (abs(y).gte(ONE_FP)) { + assertLte(x_y, abs(x)); + } else { + assertGte(x_y, abs(x)); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + + // Test for division by zero + function div_test_div_by_zero(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + try this.helpersDiv(x, ZERO_FP) { + // Unexpected, this should revert + assert(false); + } catch { + // Expected revert + } + } + + // Test for division by a large value, the result should be less than one + function div_test_maximum_denominator(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + SD59x18 div_large = div(x, MAX_SD59x18); + + assertLte(abs(div_large), ONE_FP); + } + + // Test for division of a large value + // This should revert if |x| < 1 as it would return a value higher than max + function div_test_maximum_numerator(SD59x18 y) public { + SD59x18 div_large; + + try this.helpersDiv(MAX_SD59x18, y) { + // If it didn't revert, then |x| >= 1 + div_large = div(MAX_SD59x18, y); + + assertGte(abs(y), ONE_FP); + } catch { + // Expected revert as result is higher than max + } + } + + // Test for values in range + function div_test_range(SD59x18 x, SD59x18 y) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + SD59x18 result; + + try this.helpersDiv(x, y) { + // If it returns a value, it must be in range + result = div(x, y); + assertLte(result, MAX_SD59x18); + assertGte(result, MIN_SD59x18); + } catch { + // Otherwise, it should revert + } + } + + /* ================================================================ + + TESTS FOR FUNCTION neg() + + ================================================================ */ + + /* ================================================================ + Tests for mathematical properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for the double negation + // -(-x) == x + function neg_test_double_negation(SD59x18 x) public { + SD59x18 double_neg = neg(neg(x)); + + assertEq(x, double_neg); + } + + // Test for the identity operation + // x + (-x) == 0 + function neg_test_identity(SD59x18 x) public { + SD59x18 neg_x = neg(x); + + assertEq(add(x, neg_x), ZERO_FP); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for the zero-case + // -0 == 0 + function neg_test_zero() public { + SD59x18 neg_x = neg(ZERO_FP); + + assertEq(neg_x, ZERO_FP); + } + + // Test for the maximum value case + // Since this is implementation-dependant, we will actually test with MAX_SD59x18-EPS + function neg_test_maximum() public { + try this.neg(sub(MAX_SD59x18, EPSILON)) { + // Expected behaviour, does not revert + } catch { + assert(false); + } + } + + // Test for the minimum value case + // Since this is implementation-dependant, we will actually test with MIN_SD59x18+EPS + function neg_test_minimum() public { + try this.neg(add(MIN_SD59x18, EPSILON)) { + // Expected behaviour, does not revert + } catch { + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION abs() + + ================================================================ */ + + /* ================================================================ + Tests for mathematical properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + + // Test that the absolute value is always positive + function abs_test_positive(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + SD59x18 abs_x = abs(x); + + assertGte(abs_x, ZERO_FP); + } + + // Test that the absolute value of a number equals the + // absolute value of the negative of the same number + function abs_test_negative(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + + SD59x18 abs_x = abs(x); + SD59x18 abs_minus_x = abs(neg(x)); + + assertEq(abs_x, abs_minus_x); + } + + // Test the multiplicativeness property + // | x * y | == |x| * |y| + function abs_test_multiplicativeness(SD59x18 x, SD59x18 y) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + + SD59x18 abs_x = abs(x); + SD59x18 abs_y = abs(y); + SD59x18 abs_xy = abs(mul(x, y)); + SD59x18 abs_x_abs_y = mul(abs_x, abs_y); + + // Failure if all significant digits are lost + require(significant_digits_are_lost_in_mult(abs_x, abs_y) == false); + + // Assume a tolerance of two bits of precision + assertEqWithinBitPrecision(abs_xy, abs_x_abs_y, 2); + } + + // Test the subadditivity property + // | x + y | <= |x| + |y| + function abs_test_subadditivity(SD59x18 x, SD59x18 y) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + + SD59x18 abs_x = abs(x); + SD59x18 abs_y = abs(y); + SD59x18 abs_xy = abs(add(x, y)); + + assertLte(abs_xy, add(abs_x, abs_y)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test the zero-case | 0 | = 0 + function abs_test_zero() public { + SD59x18 abs_zero; + + try this.helpersAbs(ZERO_FP) { + // If it doesn't revert, the value must be zero + abs_zero = this.helpersAbs(ZERO_FP); + assertEq(abs_zero, ZERO_FP); + } catch { + // Unexpected, the function must not revert here + assert(false); + } + } + + // Test the maximum value + function abs_test_maximum() public { + SD59x18 abs_max; + + try this.helpersAbs(MAX_SD59x18) { + // If it doesn't revert, the value must be MAX_SD59x18 + abs_max = this.helpersAbs(MAX_SD59x18); + assertEq(abs_max, MAX_SD59x18); + } catch { + assert(false); + } + } + + // Test the minimum value + function abs_test_minimum_revert() public { + SD59x18 abs_min; + + try this.helpersAbs(MIN_SD59x18) { + // It should always revert for MIN_SD59x18 + assert(false); + } catch {} + } + + // Test the minimum value + function abs_test_minimum_allowed() public { + SD59x18 abs_min; + SD59x18 input = MIN_SD59x18.add(SD59x18.wrap(1)); + + try this.helpersAbs(input) { + // If it doesn't revert, the value must be the negative of MIN_SD59x18 + 1 + abs_min = this.helpersAbs(input); + assertEq(abs_min, neg(input)); + } catch { + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION inv() + + ================================================================ */ + + /* ================================================================ + Tests for mathematical properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test that the inverse of the inverse is close enough to the + // original number + function inv_test_double_inverse(SD59x18 x) public { + require(x.neq(ZERO_FP)); + + SD59x18 double_inv_x = inv(inv(x)); + + // The maximum loss of precision will be 2 * log2(x) bits rounded up + uint256 loss = 2 * significant_digits_lost_in_mult(x, x)+ 2; + + assertEqWithinDecimalPrecision(x, double_inv_x, loss); + } + + // Test equivalence with division + function inv_test_division(SD59x18 x) public { + require(x.neq(ZERO_FP)); + + SD59x18 inv_x = inv(x); + SD59x18 div_1_x = div(ONE_FP, x); + + assertEq(inv_x, div_1_x); + } + + // Test the anticommutativity of the division + // x / y == 1 / (y / x) + function inv_test_division_noncommutativity( + SD59x18 x, + SD59x18 y + ) public { + require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); + + SD59x18 x_y = div(x, y); + SD59x18 y_x = div(y, x); + + assertEqWithinTolerance(x_y, inv(y_x), ONE_TENTH_FP, "0.1%"); + } + + // Test the multiplication of inverses + // 1/(x * y) == 1/x * 1/y + function inv_test_multiplication(SD59x18 x, SD59x18 y) public { + require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); + + SD59x18 inv_x = inv(x); + SD59x18 inv_y = inv(y); + SD59x18 inv_x_times_inv_y = mul(inv_x, inv_y); + + SD59x18 x_y = mul(x, y); + SD59x18 inv_x_y = inv(x_y); + + // The maximum loss of precision is given by the formula: + // 2 * | log2(x) - log2(y) | + 1 + uint256 loss = 2 * significant_digits_lost_in_mult(x, y) + 1; + + assertEqWithinDecimalPrecision(inv_x_y, inv_x_times_inv_y, loss); + } + + // Test identity property + function inv_test_identity(SD59x18 x) public { + require(x.neq(ZERO_FP)); + + SD59x18 inv_x = inv(x); + SD59x18 identity = mul(inv_x, x); + + // They should agree with a tolerance of one tenth of a percent + assertEqWithinTolerance(identity, ONE_FP, ONE_TENTH_FP, "0.1%"); + } + + // Test that the absolute value of the result is in range zero-one + // if x is greater than one, else, the absolute value of the result + // must be greater than one + function inv_test_values(SD59x18 x) public { + require(x.neq(ZERO_FP)); + + SD59x18 abs_inv_x = abs(inv(x)); + + if (abs(x).gte(ONE_FP)) { + assertLte(abs_inv_x, ONE_FP); + } else { + assertGt(abs_inv_x, ONE_FP); + } + } + + // Test that the result has the same sign as the argument. + // Since inv() rounds towards zero, we are checking the zero case as well + function inv_test_sign(SD59x18 x) public { + require(x.neq(ZERO_FP)); + + SD59x18 inv_x = inv(x); + + if (x.gt(ZERO_FP)) { + assertGte(inv_x, ZERO_FP); + } else { + assertLte(inv_x, ZERO_FP); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test the zero-case, should revert + function inv_test_zero() public { + try this.helpersInv(ZERO_FP) { + // Unexpected, the function must revert + assert(false); + } catch {} + } + + // Test the maximum value case, should not revert, and be close to zero + function inv_test_maximum() public { + SD59x18 inv_maximum; + + try this.helpersInv(MAX_SD59x18) { + inv_maximum = this.helpersInv(MAX_SD59x18); + assertEqWithinBitPrecision(inv_maximum, ZERO_FP, 10); + } catch { + // Unexpected, the function must not revert + assert(false); + } + } + + // Test the minimum value case, should not revert, and be close to zero + function inv_test_minimum() public { + SD59x18 inv_minimum; + + try this.helpersInv(MIN_SD59x18) { + inv_minimum = this.helpersInv(MIN_SD59x18); + assertEqWithinBitPrecision(abs(inv_minimum), ZERO_FP, 10); + } catch { + // Unexpected, the function must not revert + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION avg() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test that the result is between the two operands + // avg(x, y) >= min(x, y) && avg(x, y) <= max(x, y) + function avg_test_values_in_range(SD59x18 x, SD59x18 y) public { + SD59x18 avg_xy = avg(x, y); + + if (x.gte(y)) { + assertGte(avg_xy, y); + assertLte(avg_xy, x); + } else { + assertGte(avg_xy, x); + assertLte(avg_xy, y); + } + } + + // Test that the average of the same number is itself + // avg(x, x) == x + function avg_test_one_value(SD59x18 x) public { + SD59x18 avg_x = avg(x, x); + + assertEq(avg_x, x); + } + + // Test that the order of operands is irrelevant + // avg(x, y) == avg(y, x) + function avg_test_operand_order(SD59x18 x, SD59x18 y) public { + SD59x18 avg_xy = avg(x, y); + SD59x18 avg_yx = avg(y, x); + + assertEq(avg_xy, avg_yx); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for the maximum value + function avg_test_maximum() public { + SD59x18 result; + + // This may revert due to overflow depending on implementation + // If it doesn't revert, the result must be MAX_SD59x18 + try this.helpersAvg(MAX_SD59x18, MAX_SD59x18) { + result = this.helpersAvg(MAX_SD59x18, MAX_SD59x18); + assertEq(result, MAX_SD59x18); + } catch { + assert(false); + } + } + + // Test for the minimum value + function avg_test_minimum() public { + SD59x18 result; + + // This may revert due to overflow depending on implementation + // If it doesn't revert, the result must be MIN_SD59x18 + try this.helpersAvg(MIN_SD59x18, MIN_SD59x18) { + result = this.helpersAvg(MIN_SD59x18, MIN_SD59x18); + assertEq(result, MIN_SD59x18); + } catch { + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION pow() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for zero exponent + // x ** 0 == 1 + function pow_test_zero_exponent(SD59x18 x) public { + require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); + SD59x18 x_pow_0 = pow(x, ZERO_FP); + + assertEq(x_pow_0, ONE_FP); + } + + // Test for zero base + // 0 ** a == 0 (for positive a) + function pow_test_zero_base_non_zero_exponent(SD59x18 a) public { + require(a.gt(ZERO_FP)); + SD59x18 zero_pow_a = pow(ZERO_FP, a); + + assertEq(zero_pow_a, ZERO_FP); + } + + // Test for zero base + // 0 ** 0 == 1 + function pow_test_zero_base_zero_exponent() public { + SD59x18 zero_pow_a = pow(ZERO_FP, ZERO_FP); + + assertEq(zero_pow_a, ONE_FP); + } + + // Test for exponent one + // x ** 1 == x + function pow_test_one_exponent(SD59x18 x) public { + require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); + SD59x18 x_pow_1 = pow(x, ONE_FP); + + assertEq(x_pow_1, x); + } + + // Test for base one + // 1 ** a == 1 + function pow_test_base_one(SD59x18 a) public { + SD59x18 one_pow_a = pow(ONE_FP, a); + + assertEq(one_pow_a, ONE_FP); + } + + + // Test for product of powers of the same base + // x ** a * x ** b == x ** (a + b) + function pow_test_product_same_base( + SD59x18 x, + SD59x18 a, + SD59x18 b + ) public { + require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); + require(a.gte(MIN_PERMITTED_EXP2) && b.gte(MIN_PERMITTED_EXP2)); + + SD59x18 x_a = pow(x, a); + SD59x18 x_b = pow(x, b); + SD59x18 x_ab = pow(x, a.add(b)); + + assertEqWithinDecimalPrecision(mul(x_a, x_b), x_ab, 9); + } + + // Test for power of an exponentiation + // (x ** a) ** b == x ** (a * b) + function pow_test_power_of_an_exponentiation( + SD59x18 x, + SD59x18 a, + SD59x18 b + ) public { + require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); + require(a.mul(b).neq(ZERO_FP)); + require(a.gte(MIN_PERMITTED_EXP2) && b.gte(MIN_PERMITTED_EXP2)); + + SD59x18 x_a = pow(x, a); + SD59x18 x_a_b = pow(x_a, b); + SD59x18 x_ab = pow(x, a.mul(b)); + + assertEqWithinDecimalPrecision(x_a_b, x_ab, 9); + } + + // Test for power of a product + // (x * y) ** a == x ** a * y ** a + function pow_test_product_power( + SD59x18 x, + SD59x18 y, + SD59x18 a + ) public { + require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); + require(y.gte(ZERO_FP) && y.lte(MAX_PERMITTED_POW)); + + require(a.gt(convert(2 ** 32))); // to avoid massive loss of precision + + SD59x18 x_y = mul(x, y); + SD59x18 xy_a = pow(x_y, a); + + SD59x18 x_a = pow(x, a); + SD59x18 y_a = pow(y, a); + + assertEqWithinBitPrecision(mul(x_a, y_a), xy_a, 10); + } + + // Test for result being greater than or lower than the argument, depending on + // its absolute value and the value of the exponent + function pow_test_positive_exponent(SD59x18 x, SD59x18 a) public { + require(x.gte(ZERO_FP) && a.gte(ZERO_FP)); + + SD59x18 x_a = pow(x, a); + + if (abs(x).gte(ONE_FP)) { + assertGte(abs(x_a), ONE_FP); + } + + if (abs(x).lte(ONE_FP)) { + assertLte(abs(x_a), ONE_FP); + } + } + + // Test for result being greater than or lower than the argument, depending on + // its absolute value and the value of the exponent + function pow_test_negative_exponent(SD59x18 x, SD59x18 a) public { + require(x.gte(ZERO_FP) && a.lte(ZERO_FP)); + + SD59x18 x_a = pow(x, a); + + if (abs(x).gte(ONE_FP)) { + assertLte(abs(x_a), ONE_FP); + } + + if (abs(x).lte(ONE_FP)) { + assertGte(abs(x_a), ONE_FP); + } + } + + // Test for result sign: if the exponent is even, sign is positive + // if the exponent is odd, preserves the sign of the base + function pow_test_sign(SD59x18 x, SD59x18 a) public { + require(x.gte(ZERO_FP)); + + SD59x18 x_a = pow(x, a); + + // This prevents the case where a small negative number gets + // rounded down to zero and thus changes sign + require(x_a.neq(ZERO_FP)); + + // If the exponent is even + if (a.mod(convert(2)).eq(ZERO_FP)) { + assertEq(x_a, abs(x_a)); + } else { + // x_a preserves x sign + if (x.lt(ZERO_FP)) { + assertLt(x_a, ZERO_FP); + } else { + assertGt(x_a, ZERO_FP); + } + } + } + + // pow(2, a) == exp2(a) + function pow_test_exp2_equivalence(SD59x18 a) public { + SD59x18 pow_result = pow(convert(2), a); + SD59x18 exp2_result = exp2(a); + + assertEq(pow_result, exp2_result); + } + + // Power is strictly increasing + // x > y && a >= 0 --> pow(x, a) >= pow(y, a) + function pow_test_strictly_increasing(SD59x18 x, SD59x18 y, SD59x18 a) public { + require(x.gt(y) && x.lte(MAX_PERMITTED_POW)); + require(a.gte(ZERO_FP)); + + SD59x18 x_a = pow(x, a); + SD59x18 y_a = pow(y, a); + + assertGte(x_a, y_a); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for maximum base and exponent > 1 + function pow_test_maximum_base(SD59x18 a) public { + require(a.gt(ONE_FP)); + + try this.helpersPow(MAX_SD59x18, a) { + // Unexpected, should revert because of overflow + assert(false); + } catch { + // Expected revert + } + } + + // Test for abs(base) < 1 and high exponent + function pow_test_high_exponent(SD59x18 x, SD59x18 a) public { + require(abs(x).lt(ONE_FP) && a.gt(convert(2 ** 32))); + + SD59x18 result = pow(x, a); + + assertEq(result, ZERO_FP); + } + + /* ================================================================ + + TESTS FOR FUNCTION sqrt() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for the inverse operation + // sqrt(x) * sqrt(x) == x + function sqrt_test_inverse_mul(SD59x18 x) public { + require(x.gte(ZERO_FP)); + + SD59x18 sqrt_x = sqrt(x); + SD59x18 sqrt_x_squared = mul(sqrt_x, sqrt_x); + + // Precision loss is at most half the bits of the operand + assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); + } + + // Test for the inverse operation + // sqrt(x) ** 2 == x + function sqrt_test_inverse_pow(SD59x18 x) public { + require(x.gte(ZERO_FP)); + + SD59x18 sqrt_x = sqrt(x); + SD59x18 sqrt_x_squared = pow(sqrt_x, convert(2)); + + // Precision loss is at most half the bits of the operand + assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); + } + + // Test for distributive property respect to the multiplication + // sqrt(x) * sqrt(y) == sqrt(x * y) + function sqrt_test_distributive(SD59x18 x, SD59x18 y) public { + require(x.gte(ZERO_FP) && y.gte(ZERO_FP)); + + SD59x18 sqrt_x = sqrt(x); + SD59x18 sqrt_y = sqrt(y); + SD59x18 sqrt_x_sqrt_y = mul(sqrt_x, sqrt_y); + SD59x18 sqrt_xy = sqrt(mul(x, y)); + + // Ensure we have enough significant digits for the result to be meaningful + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + require( + significant_digits_after_mult(sqrt_x, sqrt_y) > + REQUIRED_SIGNIFICANT_DIGITS + ); + + // Allow an error of up to one tenth of a percent + assertEqWithinTolerance(sqrt_x_sqrt_y, sqrt_xy, ONE_TENTH_FP, "0.1%"); + } + + // Test that sqrt is strictly increasing + // x >= 0 && y > x --> sqrt(y) >= sqrt(x) + function sqrt_test_strictly_increasing(SD59x18 x, SD59x18 y) public { + require(x.gte(ZERO_FP) && x.lte(MAX_SQRT.sub(SD59x18.wrap(1)))); + require(y.gt(x) && y.lte(MAX_SQRT)); + + SD59x18 sqrt_x = sqrt(x); + SD59x18 sqrt_y = sqrt(y); + + assertGte(sqrt_y, sqrt_x); + } + + // Square root of perfect square should be equal to x + // sqrt(x * x) == x + function sqrt_test_square(SD59x18 x) public { + require(x.gt(ONE_FP)); + + SD59x18 square_x = x.mul(x); + SD59x18 sqrt_square_x = sqrt(square_x); + + assertEq(sqrt_square_x, x); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + + // Test for zero case + function sqrt_test_zero() public { + assertEq(sqrt(ZERO_FP), ZERO_FP); + } + + // Test for maximum value + function sqrt_test_maximum() public { + try this.helpersSqrt(MAX_SQRT) { + // Expected behaviour, MAX_SQRT is positive, and operation + // should not revert as the result is in range + } catch { + // Unexpected, should not revert + assert(false); + } + } + + // Test for minimum value + function sqrt_test_minimum() public { + try this.helpersSqrt(MIN_SD59x18) { + // Unexpected, should revert. MIN_SD59x18 is negative. + assert(false); + } catch { + // Expected behaviour, revert + } + } + + // Test for negative operands + function sqrt_test_negative(SD59x18 x) public { + require(x.lt(ZERO_FP)); + + try this.helpersSqrt(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected behaviour, revert + } + } + + /* ================================================================ + + TESTS FOR FUNCTION log2() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for distributive property respect to multiplication + // log2(x * y) = log2(x) + log2(y) + function log2_test_distributive_mul(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP) && y.gt(ZERO_FP)); + SD59x18 log2_x = log2(x); + SD59x18 log2_y = log2(y); + SD59x18 log2_x_log2_y = add(log2_x, log2_y); + + SD59x18 xy = mul(x, y); + SD59x18 log2_xy = log2(xy); + + // Ensure we have enough significant digits for the result to be meaningful + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + + // The maximum loss of precision is given by the formula: + // | log2(x) + log2(y) | + uint256 loss = significant_digits_lost_in_mult(x, y) + 2; + + assertEqWithinDecimalPrecision(log2_x_log2_y, log2_xy, loss); + } + + // Test for logarithm of a power + // log2(x ** y) = y * log2(x) + function log2_test_power(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP)); + SD59x18 x_y = pow(x, y); + require(x_y.gt(ZERO_FP)); + SD59x18 log2_x_y = log2(x_y); + SD59x18 y_log2_x = mul(log2(x), y); + + assertEqWithinTolerance(y_log2_x, log2_x_y, ONE_TENTH_FP, "0.1%"); + } + + // Base 2 logarithm is strictly increasing + // y > 0 && x > y --> log2(x) > log2(y) + function log2_test_strictly_increasing(SD59x18 x, SD59x18 y) public { + require(y.gt(ZERO_FP) && x.gt(y)); + + SD59x18 log2_x = log2(x); + SD59x18 log2_y = log2(y); + + assertGte(log2_x, log2_y); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case, should revert + function log2_test_zero() public { + try this.helpersLog2(ZERO_FP) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, log(0) is not defined + } + } + + // Test for maximum value case, should return a positive number + function log2_test_maximum() public { + SD59x18 result; + + try this.helpersLog2(MAX_SD59x18) { + // Expected, should not revert and the result must be > 0 + result = this.helpersLog2(MAX_SD59x18); + assertGt(result, ZERO_FP); + } catch { + // Unexpected + assert(false); + } + } + + // Test for negative values, should revert as log2 is not defined + function log2_test_negative(SD59x18 x) public { + require(x.lt(ZERO_FP)); + + try this.helpersLog2(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected + } + } + + /* ================================================================ + + TESTS FOR FUNCTION ln() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for distributive property respect to multiplication + // ln(x * y) = ln(x) + ln(y) + function ln_test_distributive_mul(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP) && y.gt(ZERO_FP)); + + SD59x18 ln_x = ln(x); + SD59x18 ln_y = ln(y); + SD59x18 ln_x_ln_y = add(ln_x, ln_y); + + SD59x18 xy = mul(x, y); + SD59x18 ln_xy = ln(xy); + + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + + // The maximum loss of precision is given by the formula: + // | log2(x) + log2(y) | + uint256 loss = significant_digits_lost_in_mult(x, y) + 2; + + assertEqWithinDecimalPrecision(ln_x_ln_y, ln_xy, loss); + } + + // Test for logarithm of a power + // ln(x ** y) = y * ln(x) + function ln_test_power(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP)); + SD59x18 x_y = pow(x, y); + SD59x18 ln_x_y = ln(x_y); + + SD59x18 y_ln_x = mul(ln(x), y); + + require(significant_digits_after_mult(ln(x), y) > REQUIRED_SIGNIFICANT_DIGITS); + + assertEqWithinTolerance(ln_x_y, y_ln_x, ONE_TENTH_FP, "0.1%"); + } + + // Natural logarithm is strictly increasing + // y > 0 && x > y --> log2(x) > log2(y) + function ln_test_strictly_increasing(SD59x18 x, SD59x18 y) public { + require(y.gt(ZERO_FP) && x.gt(y)); + + SD59x18 log2_x = ln(x); + SD59x18 log2_y = ln(y); + + assertGte(log2_x, log2_y); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case, should revert + function ln_test_zero() public { + try this.helpersLn(ZERO_FP) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, log(0) is not defined + } + } + + // Test for maximum value case, should return a positive number + function ln_test_maximum() public { + SD59x18 result; + + try this.helpersLn(MAX_SD59x18) { + // Expected, should not revert and the result must be > 0 + result = this.helpersLn(MAX_SD59x18); + assertGt(result, ZERO_FP); + } catch { + // Unexpected + assert(false); + } + } + + // Test for negative values, should revert as ln is not defined + function ln_test_negative(SD59x18 x) public { + require(x.lt(ZERO_FP)); + + try this.helpersLn(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected + } + } + + /* ================================================================ + + TESTS FOR FUNCTION exp2() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for equality with pow(2, x) for integer x + // pow(2, x) == exp2(x) + function exp2_test_equivalence_pow(SD59x18 x) public { + require(x.lte(MAX_PERMITTED_EXP2)); + SD59x18 exp2_x = exp2(x); + SD59x18 pow_2_x = pow(TWO_FP, x); + + assertEq(exp2_x, pow_2_x); + } + + // Test for inverse function + // If y = log2(x) then exp2(y) == x + function exp2_test_inverse(SD59x18 x) public { + require(x.lte(MAX_PERMITTED_EXP2) && x.gt(ZERO_FP)); + SD59x18 log2_x = log2(x); + SD59x18 exp2_x = exp2(log2_x); + + assertEqWithinDecimalPrecision(x, exp2_x, 9); + } + + // Test for negative exponent + // exp2(-x) == inv( exp2(x) ) + function exp2_test_negative_exponent(SD59x18 x) public { + require(x.lt(ZERO_FP) && x.neq(MIN_SD59x18)); + + SD59x18 exp2_x = exp2(x); + SD59x18 exp2_minus_x = exp2(neg(x)); + + assertEqWithinDecimalPrecision(exp2_x, inv(exp2_minus_x), 2); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case + // exp2(0) == 1 + function exp2_test_zero() public { + SD59x18 exp_zero = exp2(ZERO_FP); + assertEq(exp_zero, ONE_FP); + } + + // Test for maximum value. This should overflow as it won't fit + // in the data type + function exp2_test_maximum() public { + try this.helpersExp2(MAX_SD59x18) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert + } + } + + // Test for maximum value. This should overflow as it won't fit + // in the data type + function exp2_test_maximum_permitted() public { + try this.helpersExp2(MAX_PERMITTED_EXP2) { + // Should always pass + } catch { + // Should never revert + assert(false); + } + } + + // Test for minimum value. This should return zero since + // 2 ** -x == 1 / 2 ** x that tends to zero as x increases + function exp2_test_minimum() public { + SD59x18 result; + + try this.helpersExp2(MIN_SD59x18) { + // Expected, should not revert, check that value is zero + result = exp2(MIN_SD59x18); + assertEq(result, ZERO_FP); + } catch { + // Unexpected revert + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION exp() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for inverse function + // If y = ln(x) then exp(y) == x + function exp_test_inverse(SD59x18 x) public { + require(x.lte(MAX_PERMITTED_EXP)); + SD59x18 ln_x = ln(x); + SD59x18 exp_x = exp(ln_x); + SD59x18 log10_x = log10(x); + + assertEqWithinDecimalPrecision(x, exp_x, 9); + } + + // Test for negative exponent + // exp(-x) == inv( exp(x) ) + function exp_test_negative_exponent(SD59x18 x) public { + require(x.lt(ZERO_FP) && x.neq(MIN_SD59x18)); + + SD59x18 exp_x = exp(x); + SD59x18 exp_minus_x = exp(neg(x)); + + // Result should be within 4 bits precision for the worst case + assertEqWithinBitPrecision(exp_x, inv(exp_minus_x), 4); + } + + // Test that exp strictly increases + function exp_test_strictly_increasing(SD59x18 x, SD59x18 y) public { + require(x.lte(MAX_PERMITTED_EXP)); + require(y.gt(x) && y.lte(MAX_PERMITTED_EXP)); + + SD59x18 exp_x = exp(x); + SD59x18 exp_y = exp(y); + + assertGte(exp_y, exp_x); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case + // exp(0) == 1 + function exp_test_zero() public { + SD59x18 exp_zero = exp(ZERO_FP); + assertEq(exp_zero, ONE_FP); + } + + // Test for maximum value. This should overflow as it won't fit + // in the data type + function exp_test_maximum() public { + try this.helpersExp(MAX_SD59x18) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert + } + } + + // Test for maximum value. This should overflow as it won't fit + // in the data type + function exp_test_maximum_permitted() public { + try this.helpersExp(MAX_PERMITTED_EXP) { + // Expected to always succeed + } catch { + // Unexpected, should revert + assert(false); + } + } + + // Test for minimum value. This should return zero since + // e ** -x == 1 / e ** x that tends to zero as x increases + function exp_test_minimum() public { + SD59x18 result; + + try this.helpersExp(MIN_SD59x18) { + // Expected, should not revert, check that value is zero + result = exp(MIN_SD59x18); + assertEq(result, ZERO_FP); + } catch { + // Unexpected revert + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION powu() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for zero exponent + // x ** 0 == 1 + function powu_test_zero_exponent(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + require(x.lte(MAX_SD59x18)); + SD59x18 x_pow_0 = powu(x, 0); + + assertEq(x_pow_0, ONE_FP); + } + + // Test for zero base + // 0 ** y == 0 (for positive y) + function powu_test_zero_base(uint256 y) public { + require(y != 0); + + SD59x18 zero_pow_y = powu(ZERO_FP, y); + + assertEq(zero_pow_y, ZERO_FP); + } + + // Test for exponent one + // x ** 1 == x + function powu_test_one_exponent(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + require(x.lte(MAX_SD59x18)); + SD59x18 x_pow_1 = powu(x, 1); + + assertEq(x_pow_1, x); + } + + // Test for base one + // 1 ** x == 1 + function powu_test_base_one(uint256 y) public { + SD59x18 one_pow_y = powu(ONE_FP, y); + + assertEq(one_pow_y, ONE_FP); + } + + // Test for product of powers of the same base + // x ** a * x ** b == x ** (a + b) + function powu_test_product_same_base( + SD59x18 x, + uint256 a, + uint256 b + ) public { + require(x.gt(MIN_SD59x18)); + require(x.lte(MAX_SD59x18)); + + SD59x18 x_a = powu(x, a); + SD59x18 x_b = powu(x, b); + SD59x18 x_ab = powu(x, a + b); + + assertEqWithinBitPrecision(mul(x_a, x_b), x_ab, 10); + } + + // Test for power of an exponentiation + // (x ** a) ** b == x ** (a * b) + function powu_test_power_of_an_exponentiation( + SD59x18 x, + uint256 a, + uint256 b + ) public { + require(x.gt(MIN_SD59x18)); + require(x.lte(MAX_SD59x18)); + + SD59x18 x_a = powu(x, a); + SD59x18 x_a_b = powu(x_a, b); + SD59x18 x_ab = powu(x, a * b); + + assertEqWithinBitPrecision(x_a_b, x_ab, 10); + } + + // Test for power of a product + // (x * y) ** a == x ** a * y ** a + function powu_test_product_power( + SD59x18 x, + SD59x18 y, + uint256 a + ) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + require(x.lte(MAX_SD59x18) && y.lte(MAX_SD59x18)); + + require(a > 2 ** 32); // to avoid massive loss of precision + + SD59x18 x_y = mul(x, y); + SD59x18 xy_a = powu(x_y, a); + + SD59x18 x_a = powu(x, a); + SD59x18 y_a = powu(y, a); + + assertEqWithinBitPrecision(mul(x_a, y_a), xy_a, 10); + } + + // Test for result being greater than or lower than the argument, depending on + // its absolute value and the value of the exponent + function powu_test_values(SD59x18 x, uint256 a) public { + require(x.neq(ZERO_FP)); + require(x.neq(MIN_SD59x18)); + + SD59x18 x_a = powu(x, a); + + if (abs(x).gte(ONE_FP)) { + assertGte(abs(x_a), ONE_FP); + } + + if (abs(x).lte(ONE_FP)) { + assertLte(abs(x_a), ONE_FP); + } + } + + // Test for result sign: if the exponent is even, sign is positive + // if the exponent is odd, preserves the sign of the base + function powu_test_sign(SD59x18 x, uint256 a) public { + require(x.neq(ZERO_FP) && a != 0); + + SD59x18 x_a = powu(x, a); + + // This prevents the case where a small negative number gets + // rounded down to zero and thus changes sign + require(x_a.neq(ZERO_FP)); + + // If the exponent is even + if (a % 2 == 0) { + assertEq(x_a, abs(x_a)); + } else { + // x_a preserves x sign + if (x.lt(ZERO_FP)) { + assertLt(x_a, ZERO_FP); + } else { + assertGt(x_a, ZERO_FP); + } + } + } + + // Unsigned power is strictly increasing + // y > MIN && x > y && a > 0 && y != 0 --> powu(x, a) >= powu(y, a) + function powu_test_strictly_increasing( + SD59x18 x, + SD59x18 y, + uint256 a + ) public { + require(x.gt(y) && y.gt(MIN_SD59x18) && y.neq(ZERO_FP)); + require(x.lte(MAX_SD59x18)); + require(a > 0); + + SD59x18 x_a = powu(x, a); + SD59x18 y_a = powu(y, a); + + assertGte(x_a, y_a); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for maximum base and exponent > 1 + function powu_test_maximum_base(uint256 a) public { + require(a > 1); + + try this.helpersPowu(MAX_SD59x18, a) { + // Unexpected, should revert because of overflow + assert(false); + } catch { + // Expected revert + } + } + + // Test for abs(base) < 1 and high exponent + function powu_test_high_exponent(SD59x18 x, uint256 a) public { + require(abs(x).lt(ONE_FP) && a > 2 ** 32); + + SD59x18 result = powu(x, a); + + assertEq(result, ZERO_FP); + } + + /* ================================================================ + + TESTS FOR FUNCTION log10() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for distributive property respect to multiplication + // log10(x * y) = log10(x) + log10(y) + function log10_test_distributive_mul(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP) && y.gt(ZERO_FP)); + SD59x18 log10_x = log10(x); + SD59x18 log10_y = log10(y); + SD59x18 log10_x_log10_y = add(log10_x, log10_y); + + SD59x18 xy = mul(x, y); + SD59x18 log10_xy = log10(xy); + + // Ensure we have enough significant digits for the result to be meaningful + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + + // The maximum loss of precision is given by the formula: + // | log10(x) + log10(y) | + uint256 loss = significant_digits_lost_in_mult(x, y) + 2; + + assertEqWithinDecimalPrecision(log10_x_log10_y, log10_xy, loss); + } + + // Test for logarithm of a power + // log10(x ** y) = y * log10(x) + function log10_test_power(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP) && y.gt(ZERO_FP)); + SD59x18 x_y = pow(x, y); + SD59x18 log10_x_y = log10(x_y); + SD59x18 y_log10_x = mul(log10(x), y); + + assertEqWithinTolerance(log10_x_y, y_log10_x, ONE_TENTH_FP, "0.1%"); + } + + // Base 10 logarithm is strictly increasing + // x > y && y > 0 --> log10(x) > log10(y) + function log10_is_increasing(SD59x18 x, SD59x18 y) public { + require(y.gt(ZERO_FP) && x.gt(y)); - return (eq(r, convert(0))); + SD59x18 log2_x = log10(x); + SD59x18 log2_y = log10(y); + + assertGt(log2_x, log2_y); } - // This function determines if the relative error between a and b is less - // than error_percent % (expressed as a 59x18 value) - // Uses functions from the library under test! - function equal_within_tolerance(SD59x18 a, SD59x18 b, SD59x18 error_percent) public returns(bool) { - SD59x18 tol_value = abs(mul(a, div(error_percent, convert(100)))); + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case, should revert + function log10_test_zero() public { + try this.helpersLog10(ZERO_FP) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, log(0) is not defined + } + } + + // Test for maximum value case, should return a positive number + function log10_test_maximum() public { + SD59x18 result; + + try this.helpersLog10(MAX_SD59x18) { + // Expected, should not revert and the result must be > 0 + result = this.helpersLog10(MAX_SD59x18); + assertGt(result, ZERO_FP); + } catch { + // Unexpected + assert(false); + } + } + + // Test for negative values, should revert as log10 is not defined + function log10_test_negative(SD59x18 x) public { + require(x.lt(ZERO_FP)); + + try this.helpersLog10(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected + } + } + + /* ================================================================ + + TESTS FOR FUNCTION gm() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // The product of the values should be equal to the geometric mean + // raised to the power of N (numbers in the set) + function gm_test_product(SD59x18 x, SD59x18 y) public { + bool x_sign = x.gt(ZERO_FP); + bool y_sign = y.gt(ZERO_FP); + require(x_sign = y_sign); + + SD59x18 x_mul_y = x.mul(y); + SD59x18 gm_squared = pow(gm(x,y), TWO_FP); + + assertEqWithinTolerance(x_mul_y, gm_squared, ONE_TENTH_FP, "0.1%"); + } + + // The geometric mean for a set of positive numbers is less than the + // arithmetic mean of that set, as long as the values of the set are not equal + function gm_test_positive_set_avg(SD59x18 x, SD59x18 y) public { + require(x.gte(ZERO_FP) && y.gte(ZERO_FP) && x.neq(y)); + + SD59x18 gm_x_y = gm(x, y); + SD59x18 avg_x_y = avg(x, y); + + assertLte(gm_x_y, avg_x_y); + } + + // The geometric mean of a set of positive equal numbers should be + // equal to the arithmetic mean + function gm_test_positive_equal_set_avg(SD59x18 x) public { + require(x.gte(ZERO_FP)); + + SD59x18 gm_x = gm(x, x); + SD59x18 avg_x = avg(x, x); + + assertEq(gm_x, avg_x); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case, should return 0 + function gm_test_zero(SD59x18 x) public { + require(x.gte(ZERO_FP)); + + try this.helpersGm(x, ZERO_FP) { + SD59x18 result = gm(x, ZERO_FP); + assertEq(result, ZERO_FP); + } catch { + // Unexpected, should not revert + assert(false); + } + } - require(tol_value.neq(ZERO_FP)); - emit PropertyFailed(a, b, tol_value); - return (lte(abs(sub(b, a)), tol_value)); + // Test for single negative input + function gm_test_negative(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP) && y.lt(ZERO_FP)); + + try this.helpersGm(x, y) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, gm of a negative product is not defined + } } +}contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { + + /* ================================================================ + 59x18 fixed-point constants used for testing specific values. + This assumes that PRBMath library's convert(x) works as expected. + ================================================================ */ + SD59x18 internal ZERO_FP = convert(0); + SD59x18 internal ONE_FP = convert(1); + SD59x18 internal MINUS_ONE_FP = convert(-1); + SD59x18 internal TWO_FP = convert(2); + SD59x18 internal THREE_FP = convert(3); + SD59x18 internal EIGHT_FP = convert(8); + SD59x18 internal THOUSAND_FP = convert(1000); + SD59x18 internal MINUS_SIXTY_FOUR_FP = convert(-64); + SD59x18 internal EPSILON = SD59x18.wrap(1); + SD59x18 internal ONE_TENTH_FP = convert(1).div(convert(10)); + + /* ================================================================ + Constants used for precision loss calculations + ================================================================ */ + uint256 internal REQUIRED_SIGNIFICANT_DIGITS = 10; + SD59x18 internal LOG2_PRECISION_LOSS = SD59x18.wrap(1); + + /* ================================================================ + Integer representations maximum values. + These constants are used for testing edge cases or limits for + possible values. + ================================================================ */ + /// @dev The unit number, which gives the decimal precision of SD59x18. + int256 constant uUNIT = 1e18; + SD59x18 constant UNIT = SD59x18.wrap(1e18); + + /// @dev The minimum value an SD59x18 number can have. + int256 constant uMIN_SD59x18 = -57896044618658097711785492504343953926634992332820282019728_792003956564819968; + SD59x18 constant MIN_SD59x18 = SD59x18.wrap(uMIN_SD59x18); + + /// @dev The maximum value an SD59x18 number can have. + int256 constant uMAX_SD59x18 = 57896044618658097711785492504343953926634992332820282019728_792003956564819967; + SD59x18 constant MAX_SD59x18 = SD59x18.wrap(uMAX_SD59x18); + + /// @dev The maximum input permitted in {exp2}. + int256 constant uEXP2_MAX_INPUT = 192e18 - 1; + SD59x18 constant EXP2_MAX_INPUT = SD59x18.wrap(uEXP2_MAX_INPUT); + + /// @dev The maximum input permitted in {exp}. + int256 constant uEXP_MAX_INPUT = 133_084258667509499440; + SD59x18 constant EXP_MAX_INPUT = SD59x18.wrap(uEXP_MAX_INPUT); + + /// @dev Euler's number as an SD59x18 number. + SD59x18 constant E = SD59x18.wrap(2_718281828459045235); + + int256 constant uMAX_SQRT = uMAX_SD59x18 / uUNIT; + SD59x18 constant MAX_SQRT = SD59x18.wrap(uMAX_SQRT); + + SD59x18 internal constant MAX_PERMITTED_EXP2 = SD59x18.wrap(192e18 - 1); + SD59x18 internal constant MIN_PERMITTED_EXP2 = SD59x18.wrap(-59_794705707972522261); + + SD59x18 internal constant MAX_PERMITTED_EXP = SD59x18.wrap(133_084258667509499440); + SD59x18 internal constant MIN_PERMITTED_EXP = SD59x18.wrap(-41_446531673892822322); + + SD59x18 internal constant MAX_PERMITTED_POW = SD59x18.wrap(2 ** 192 * 10 ** 18 - 1); + /// @dev Half the UNIT number. + int256 constant uHALF_UNIT = 0.5e18; + SD59x18 constant HALF_UNIT = SD59x18.wrap(uHALF_UNIT); + + /// @dev log2(10) as an SD59x18 number. + int256 constant uLOG2_10 = 3_321928094887362347; + SD59x18 constant LOG2_10 = SD59x18.wrap(uLOG2_10); + + /// @dev log2(e) as an SD59x18 number. + int256 constant uLOG2_E = 1_442695040888963407; + SD59x18 constant LOG2_E = SD59x18.wrap(uLOG2_E); + + /* ================================================================ + Events used for debugging or showing information. + ================================================================ */ + event Value(string reason, SD59x18 val); + event LogErr(bytes error); + + + /* ================================================================ + Helper functions. + ================================================================ */ + // Check that there are remaining significant digits after a multiplication // Uses functions from the library under test! - function significant_digits_lost_in_mult(SD59x18 a, SD59x18 b) public pure returns (bool) { + function significant_digits_are_lost_in_mult(SD59x18 a, SD59x18 b) public pure returns (bool) { int256 la = convert(floor(log10(abs(a)))); int256 lb = convert(floor(log10(abs(b)))); @@ -138,21 +2312,19 @@ contract CryticPRBMath59x18Propertiesv3 { else return(59 + uint256(prec)); } - // Returns true if the n most significant bits of a and b are almost equal + // Return how many significant bits will be lost after multiplying a and b // Uses functions from the library under test! - function equal_most_significant_digits_within_precision(SD59x18 a, SD59x18 b, int256 digits) public returns (bool) { - // Divide both number by digits to truncate the unimportant digits - int256 a_uint = SD59x18.unwrap(abs(a)); - int256 b_uint = SD59x18.unwrap(abs(b)); - - int256 a_significant = a_uint / digits; - int256 b_significant = b_uint / digits; - - int256 larger = a_significant > b_significant ? a_significant : b_significant; - int256 smaller = a_significant > b_significant ? b_significant : a_significant; + function significant_digits_lost_in_mult(SD59x18 a, SD59x18 b) public pure returns (uint256) { + int256 la = convert(floor(log10(abs(a)))); + int256 lb = convert(floor(log10(abs(b)))); + // digits left + int256 prec = la + lb; - emit TestLog(larger, smaller, larger - smaller); - return ((larger - smaller) <= 1); + if (la > 0 && lb > 0) { + return 0; + } else { + return la < lb ? uint256(-la) : uint256(-lb); + } } /* ================================================================ @@ -252,22 +2424,22 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for commutative property // x + y == y + x - function add_test_commutative(SD59x18 x, SD59x18 y) public pure { + function add_test_commutative(SD59x18 x, SD59x18 y) public { SD59x18 x_y = x.add(y); SD59x18 y_x = y.add(x); - assert(x_y.eq(y_x)); + assertEq(x_y, y_x); } // Test for associative property // (x + y) + z == x + (y + z) - function add_test_associative(SD59x18 x, SD59x18 y, SD59x18 z) public pure { + function add_test_associative(SD59x18 x, SD59x18 y, SD59x18 z) public { SD59x18 x_y = x.add(y); SD59x18 y_z = y.add(z); SD59x18 xy_z = x_y.add(z); SD59x18 x_yz = x.add(y_z); - assert(xy_z.eq(x_yz)); + assertEq(xy_z, x_yz); } // Test for identity operation @@ -275,8 +2447,8 @@ contract CryticPRBMath59x18Propertiesv3 { function add_test_identity(SD59x18 x) public { SD59x18 x_0 = x.add(ZERO_FP); - assert(x.eq(x_0)); - assert(x.sub(x).eq(ZERO_FP)); + assertEq(x, x_0); + assertEq(x.sub(x), ZERO_FP); } // Test that the result increases or decreases depending @@ -285,9 +2457,9 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_y = x.add(y); if (y.gte(ZERO_FP)) { - assert(x_y.gte(x)); + assertGte(x_y, x); } else { - assert(x_y.lt(x)); + assertLt(x_y, x); } } @@ -301,7 +2473,8 @@ contract CryticPRBMath59x18Propertiesv3 { // and minimum allowed values for SD59x18 function add_test_range(SD59x18 x, SD59x18 y) public { try this.helpersAdd(x, y) returns (SD59x18 result) { - assert(result.lte(MAX_SD59x18) && result.gte(MIN_SD59x18)); + assertLte(result, MAX_SD59x18); + assertGte(result, MIN_SD59x18); } catch { // If it reverts, just ignore } @@ -312,7 +2485,7 @@ contract CryticPRBMath59x18Propertiesv3 { function add_test_maximum_value() public { try this.helpersAdd(MAX_SD59x18, ZERO_FP) returns (SD59x18 result) { // Expected behaviour, does not revert - assert(result.eq(MAX_SD59x18)); + assertEq(result, MAX_SD59x18); } catch { assert(false); } @@ -332,7 +2505,7 @@ contract CryticPRBMath59x18Propertiesv3 { function add_test_minimum_value() public { try this.helpersAdd(MIN_SD59x18, ZERO_FP) returns (SD59x18 result) { // Expected behaviour, does not revert - assert(result.eq(MIN_SD59x18)); + assertEq(result, MIN_SD59x18); } catch { assert(false); } @@ -361,21 +2534,21 @@ contract CryticPRBMath59x18Propertiesv3 { // Test equivalence to addition // x - y == x + (-y) - function sub_test_equivalence_to_addition(SD59x18 x, SD59x18 y) public pure { + function sub_test_equivalence_to_addition(SD59x18 x, SD59x18 y) public { SD59x18 minus_y = neg(y); SD59x18 addition = x.add(minus_y); SD59x18 subtraction = x.sub(y); - assert(addition.eq(subtraction)); + assertEq(addition, subtraction); } // Test for non-commutative property // x - y == -(y - x) - function sub_test_non_commutative(SD59x18 x, SD59x18 y) public pure { + function sub_test_non_commutative(SD59x18 x, SD59x18 y) public { SD59x18 x_y = x.sub(y); SD59x18 y_x = y.sub(x); - assert(x_y.eq(neg(y_x))); + assertEq(x_y, neg(y_x)); } // Test for identity operation @@ -383,21 +2556,21 @@ contract CryticPRBMath59x18Propertiesv3 { function sub_test_identity(SD59x18 x) public { SD59x18 x_0 = x.sub(ZERO_FP); - assert(x_0.eq(x)); - assert(x.sub(x).eq(ZERO_FP)); + assertEq(x_0, x); + assertEq(x.sub(x), ZERO_FP); } // Test for neutrality over addition and subtraction // (x - y) + y == (x + y) - y == x - function sub_test_neutrality(SD59x18 x, SD59x18 y) public pure { + function sub_test_neutrality(SD59x18 x, SD59x18 y) public { SD59x18 x_minus_y = x.sub(y); SD59x18 x_plus_y = x.add(y); SD59x18 x_minus_y_plus_y = x_minus_y.add(y); SD59x18 x_plus_y_minus_y = x_plus_y.sub(y); - assert(x_minus_y_plus_y.eq(x_plus_y_minus_y)); - assert(x_minus_y_plus_y.eq(x)); + assertEq(x_minus_y_plus_y, x_plus_y_minus_y); + assertEq(x_minus_y_plus_y, x); } // Test that the result increases or decreases depending @@ -406,9 +2579,9 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_y = x.sub(y); if (y.gte(ZERO_FP)) { - assert(x_y.lte(x)); + assertLte(x_y, x); } else { - assert(x_y.gt(x)); + assertGt(x_y, x); } } @@ -422,7 +2595,8 @@ contract CryticPRBMath59x18Propertiesv3 { // and minimum allowed values for SD59x18 function sub_test_range(SD59x18 x, SD59x18 y) public { try this.helpersSub(x, y) returns (SD59x18 result) { - assert(result.lte(MAX_SD59x18) && result.gte(MIN_SD59x18)); + assertLte(result, MAX_SD59x18); + assertGte(result, MIN_SD59x18); } catch { // If it reverts, just ignore } @@ -433,7 +2607,7 @@ contract CryticPRBMath59x18Propertiesv3 { function sub_test_maximum_value() public { try this.helpersSub(MAX_SD59x18, ZERO_FP) returns (SD59x18 result) { // Expected behaviour, does not revert - assert(result.eq(MAX_SD59x18)); + assertEq(result, MAX_SD59x18); } catch { assert(false); } @@ -454,7 +2628,7 @@ contract CryticPRBMath59x18Propertiesv3 { function sub_test_minimum_value() public { try this.helpersSub(MIN_SD59x18, ZERO_FP) returns (SD59x18 result) { // Expected behaviour, does not revert - assert(result.eq(MIN_SD59x18)); + assertEq(result, MIN_SD59x18); } catch { assert(false); } @@ -483,12 +2657,12 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for commutative property // x * y == y * x - function mul_test_commutative(SD59x18 x, SD59x18 y) public pure { + function mul_test_commutative(SD59x18 x, SD59x18 y) public { require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); SD59x18 x_y = x.mul(y); SD59x18 y_x = y.mul(x); - assert(x_y.eq(y_x)); + assertEq(x_y, y_x); } // Test for associative property @@ -501,7 +2675,11 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_yz = x.mul(y_z); require(xy_z.neq(ZERO_FP) && x_yz.neq(ZERO_FP)); - assert(equal_within_tolerance(xy_z, x_yz, ONE_FP)); + + uint256 digitsLost = significant_digits_lost_in_mult(x, y); + digitsLost += significant_digits_lost_in_mult(x, z); + + assertEqWithinDecimalPrecision(xy_z, x_yz, digitsLost); } // Test for distributive property @@ -515,8 +2693,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_times_z = x.mul(z); require(add(x_times_y, x_times_z).neq(ZERO_FP) && x_times_y_plus_z.neq(ZERO_FP)); - - assert(equal_within_tolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP)); + assertEqWithinTolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP, "0.1%"); } @@ -527,8 +2704,8 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_1 = x.mul(ONE_FP); SD59x18 x_0 = x.mul(ZERO_FP); - assert(x_0.eq(ZERO_FP)); - assert(x_1.eq(x)); + assertEq(x_0, ZERO_FP); + assertEq(x_1, x); } @@ -540,9 +2717,9 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_y = x.mul(y); if (y.gte(ONE_FP)) { - assert(x_y.gte(x)); + assertGte(x_y, x); } else { - assert(x_y.lte(x)); + assertLte(x_y, x); } } @@ -554,9 +2731,9 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_y = x.mul(y); if (y.gte(ONE_FP)) { - assert(x_y.lte(x)); + assertLte(x_y, x); } else { - assert(x_y.gte(x)); + assertGte(x_y, x); } } @@ -572,7 +2749,8 @@ contract CryticPRBMath59x18Propertiesv3 { require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); try this.helpersMul(x, y) returns(SD59x18 result) { - assert(result.lte(MAX_SD59x18) && result.gte(MIN_SD59x18)); + assertLte(result, MAX_SD59x18); + assertGte(result, MIN_SD59x18); } catch { // If it reverts, just ignore } @@ -583,7 +2761,7 @@ contract CryticPRBMath59x18Propertiesv3 { function mul_test_maximum_value() public { try this.helpersMul(MAX_SD59x18, ONE_FP) returns (SD59x18 result) { // Expected behaviour, does not revert - assert(result.eq(MAX_SD59x18)); + assertEq(result, MAX_SD59x18); } catch { assert(false); } @@ -594,7 +2772,7 @@ contract CryticPRBMath59x18Propertiesv3 { function mul_test_minimum_value() public { try this.helpersMul(MIN_SD59x18.add(ONE_FP), ONE_FP) returns (SD59x18 result) { // Expected behaviour, does not revert - assert(result.eq(MIN_SD59x18.add(ONE_FP))); + assertEq(result, MIN_SD59x18.add(ONE_FP)); } catch { assert(false); } @@ -618,7 +2796,7 @@ contract CryticPRBMath59x18Propertiesv3 { require(x.gt(MIN_SD59x18)); SD59x18 div_1 = div(x, ONE_FP); - assert(x.eq(div_1)); + assertEq(x, div_1); } // Test for identity property @@ -629,7 +2807,7 @@ contract CryticPRBMath59x18Propertiesv3 { try this.helpersDiv(x, x) { // This should always equal one div_x = div(x, x); - assert(div_x.eq(ONE_FP)); + assertEq(div_x, ONE_FP); } catch { // There are a couple of allowed cases for a revert: // 1. x == 0 @@ -648,7 +2826,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_y = div(x, y); SD59x18 x_minus_y = div(x, neg(y)); - assert(x_y.eq(neg(x_minus_y))); + assertEq(x_y, neg(x_minus_y)); } // Test for division with 0 as numerator @@ -659,7 +2837,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 div_0 = div(ZERO_FP, x); - assert(ZERO_FP.eq(div_0)); + assertEq(ZERO_FP, div_0); } // Test that the absolute value of the result increases or @@ -671,9 +2849,9 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_y = abs(div(x, y)); if (abs(y).gte(ONE_FP)) { - assert(x_y.lte(abs(x))); + assertLte(x_y, abs(x)); } else { - assert(x_y.gte(abs(x))); + assertGte(x_y, abs(x)); } } @@ -700,7 +2878,7 @@ contract CryticPRBMath59x18Propertiesv3 { require(x.gt(MIN_SD59x18)); SD59x18 div_large = div(x, MAX_SD59x18); - assert(abs(div_large).lte(ONE_FP)); + assertLte(abs(div_large), ONE_FP); } // Test for division of a large value @@ -712,7 +2890,7 @@ contract CryticPRBMath59x18Propertiesv3 { // If it didn't revert, then |x| >= 1 div_large = div(MAX_SD59x18, y); - assert(abs(y).gte(ONE_FP)); + assertGte(abs(y), ONE_FP); } catch { // Expected revert as result is higher than max } @@ -726,7 +2904,8 @@ contract CryticPRBMath59x18Propertiesv3 { try this.helpersDiv(x, y) { // If it returns a value, it must be in range result = div(x, y); - assert(result.lte(MAX_SD59x18) && result.gte(MIN_SD59x18)); + assertLte(result, MAX_SD59x18); + assertGte(result, MIN_SD59x18); } catch { // Otherwise, it should revert } @@ -746,10 +2925,10 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for the double negation // -(-x) == x - function neg_test_double_negation(SD59x18 x) public pure { + function neg_test_double_negation(SD59x18 x) public { SD59x18 double_neg = neg(neg(x)); - assert(x.eq(double_neg)); + assertEq(x, double_neg); } // Test for the identity operation @@ -757,7 +2936,7 @@ contract CryticPRBMath59x18Propertiesv3 { function neg_test_identity(SD59x18 x) public { SD59x18 neg_x = neg(x); - assert(add(x, neg_x).eq(ZERO_FP)); + assertEq(add(x, neg_x), ZERO_FP); } /* ================================================================ @@ -771,7 +2950,7 @@ contract CryticPRBMath59x18Propertiesv3 { function neg_test_zero() public { SD59x18 neg_x = neg(ZERO_FP); - assert(neg_x.eq(ZERO_FP)); + assertEq(neg_x, ZERO_FP); } // Test for the maximum value case @@ -812,23 +2991,23 @@ contract CryticPRBMath59x18Propertiesv3 { require(x.gt(MIN_SD59x18)); SD59x18 abs_x = abs(x); - assert(abs_x.gte(ZERO_FP)); + assertGte(abs_x, ZERO_FP); } // Test that the absolute value of a number equals the // absolute value of the negative of the same number - function abs_test_negative(SD59x18 x) public pure { + function abs_test_negative(SD59x18 x) public { require(x.gt(MIN_SD59x18)); SD59x18 abs_x = abs(x); SD59x18 abs_minus_x = abs(neg(x)); - assert(abs_x.eq(abs_minus_x)); + assertEq(abs_x, abs_minus_x); } // Test the multiplicativeness property // | x * y | == |x| * |y| - function abs_test_multiplicativeness(SD59x18 x, SD59x18 y) public pure { + function abs_test_multiplicativeness(SD59x18 x, SD59x18 y) public { require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); SD59x18 abs_x = abs(x); @@ -837,22 +3016,22 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 abs_x_abs_y = mul(abs_x, abs_y); // Failure if all significant digits are lost - require(significant_digits_lost_in_mult(abs_x, abs_y) == false); + require(significant_digits_are_lost_in_mult(abs_x, abs_y) == false); // Assume a tolerance of two bits of precision - assert(equal_within_precision(abs_xy, abs_x_abs_y, 2)); + assertEqWithinBitPrecision(abs_xy, abs_x_abs_y, 2); } // Test the subadditivity property // | x + y | <= |x| + |y| - function abs_test_subadditivity(SD59x18 x, SD59x18 y) public pure { + function abs_test_subadditivity(SD59x18 x, SD59x18 y) public { require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); SD59x18 abs_x = abs(x); SD59x18 abs_y = abs(y); SD59x18 abs_xy = abs(add(x, y)); - assert(abs_xy.lte(add(abs_x, abs_y))); + assertLte(abs_xy, add(abs_x, abs_y)); } /* ================================================================ @@ -868,7 +3047,7 @@ contract CryticPRBMath59x18Propertiesv3 { try this.helpersAbs(ZERO_FP) { // If it doesn't revert, the value must be zero abs_zero = this.helpersAbs(ZERO_FP); - assert(abs_zero.eq(ZERO_FP)); + assertEq(abs_zero, ZERO_FP); } catch { // Unexpected, the function must not revert here assert(false); @@ -882,7 +3061,7 @@ contract CryticPRBMath59x18Propertiesv3 { try this.helpersAbs(MAX_SD59x18) { // If it doesn't revert, the value must be MAX_SD59x18 abs_max = this.helpersAbs(MAX_SD59x18); - assert(abs_max.eq(MAX_SD59x18)); + assertEq(abs_max, MAX_SD59x18); } catch { assert(false); } @@ -906,7 +3085,7 @@ contract CryticPRBMath59x18Propertiesv3 { try this.helpersAbs(input) { // If it doesn't revert, the value must be the negative of MIN_SD59x18 + 1 abs_min = this.helpersAbs(input); - assert(abs_min.eq(neg(input))); + assertEq(abs_min, neg(input)); } catch { assert(false); } @@ -932,9 +3111,9 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 double_inv_x = inv(inv(x)); // The maximum loss of precision will be 2 * log2(x) bits rounded up - uint256 loss = 2 * intoUint256(log2(x)) + 2; + uint256 loss = 2 * significant_digits_lost_in_mult(x, x)+ 2; - assert(equal_within_precision(x, double_inv_x, loss)); + assertEqWithinDecimalPrecision(x, double_inv_x, loss); } // Test equivalence with division @@ -944,7 +3123,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 inv_x = inv(x); SD59x18 div_1_x = div(ONE_FP, x); - assert(inv_x.eq(div_1_x)); + assertEq(inv_x, div_1_x); } // Test the anticommutativity of the division @@ -958,7 +3137,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_y = div(x, y); SD59x18 y_x = div(y, x); - assert(equal_within_tolerance(x_y, inv(y_x), ONE_FP)); + assertEqWithinTolerance(x_y, inv(y_x), ONE_TENTH_FP, "0.1%"); } // Test the multiplication of inverses @@ -975,9 +3154,9 @@ contract CryticPRBMath59x18Propertiesv3 { // The maximum loss of precision is given by the formula: // 2 * | log2(x) - log2(y) | + 1 - uint256 loss = 2 * intoUint256(abs(log2(x).sub(log2(y)))) + 1; + uint256 loss = 2 * significant_digits_lost_in_mult(x, y) + 1; - assert(equal_within_precision(inv_x_y, inv_x_times_inv_y, loss)); + assertEqWithinDecimalPrecision(inv_x_y, inv_x_times_inv_y, loss); } // Test identity property @@ -988,7 +3167,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 identity = mul(inv_x, x); // They should agree with a tolerance of one tenth of a percent - assert(equal_within_tolerance(identity, ONE_FP, ONE_TENTH_FP)); + assertEqWithinTolerance(identity, ONE_FP, ONE_TENTH_FP, "0.1%"); } // Test that the absolute value of the result is in range zero-one @@ -1000,9 +3179,9 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 abs_inv_x = abs(inv(x)); if (abs(x).gte(ONE_FP)) { - assert(abs_inv_x.lte(ONE_FP)); + assertLte(abs_inv_x, ONE_FP); } else { - assert(abs_inv_x.gt(ONE_FP)); + assertGt(abs_inv_x, ONE_FP); } } @@ -1014,9 +3193,9 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 inv_x = inv(x); if (x.gt(ZERO_FP)) { - assert(inv_x.gte(ZERO_FP)); + assertGte(inv_x, ZERO_FP); } else { - assert(inv_x.lte(ZERO_FP)); + assertLte(inv_x, ZERO_FP); } } @@ -1040,7 +3219,7 @@ contract CryticPRBMath59x18Propertiesv3 { try this.helpersInv(MAX_SD59x18) { inv_maximum = this.helpersInv(MAX_SD59x18); - assert(equal_within_precision(inv_maximum, ZERO_FP, 10)); + assertEqWithinBitPrecision(inv_maximum, ZERO_FP, 10); } catch { // Unexpected, the function must not revert assert(false); @@ -1053,7 +3232,7 @@ contract CryticPRBMath59x18Propertiesv3 { try this.helpersInv(MIN_SD59x18) { inv_minimum = this.helpersInv(MIN_SD59x18); - assert(equal_within_precision(abs(inv_minimum), ZERO_FP, 10)); + assertEqWithinBitPrecision(abs(inv_minimum), ZERO_FP, 10); } catch { // Unexpected, the function must not revert assert(false); @@ -1074,31 +3253,33 @@ contract CryticPRBMath59x18Propertiesv3 { // Test that the result is between the two operands // avg(x, y) >= min(x, y) && avg(x, y) <= max(x, y) - function avg_test_values_in_range(SD59x18 x, SD59x18 y) public pure { + function avg_test_values_in_range(SD59x18 x, SD59x18 y) public { SD59x18 avg_xy = avg(x, y); if (x.gte(y)) { - assert(avg_xy.gte(y) && avg_xy.lte(x)); + assertGte(avg_xy, y); + assertLte(avg_xy, x); } else { - assert(avg_xy.gte(x) && avg_xy.lte(y)); + assertGte(avg_xy, x); + assertLte(avg_xy, y); } } // Test that the average of the same number is itself // avg(x, x) == x - function avg_test_one_value(SD59x18 x) public pure { + function avg_test_one_value(SD59x18 x) public { SD59x18 avg_x = avg(x, x); - assert(avg_x.eq(x)); + assertEq(avg_x, x); } // Test that the order of operands is irrelevant // avg(x, y) == avg(y, x) - function avg_test_operand_order(SD59x18 x, SD59x18 y) public pure { + function avg_test_operand_order(SD59x18 x, SD59x18 y) public { SD59x18 avg_xy = avg(x, y); SD59x18 avg_yx = avg(y, x); - assert(avg_xy.eq(avg_yx)); + assertEq(avg_xy, avg_yx); } /* ================================================================ @@ -1115,7 +3296,7 @@ contract CryticPRBMath59x18Propertiesv3 { // If it doesn't revert, the result must be MAX_SD59x18 try this.helpersAvg(MAX_SD59x18, MAX_SD59x18) { result = this.helpersAvg(MAX_SD59x18, MAX_SD59x18); - assert(result.eq(MAX_SD59x18)); + assertEq(result, MAX_SD59x18); } catch { assert(false); } @@ -1129,7 +3310,7 @@ contract CryticPRBMath59x18Propertiesv3 { // If it doesn't revert, the result must be MIN_SD59x18 try this.helpersAvg(MIN_SD59x18, MIN_SD59x18) { result = this.helpersAvg(MIN_SD59x18, MIN_SD59x18); - assert(result.eq(MIN_SD59x18)); + assertEq(result, MIN_SD59x18); } catch { assert(false); } @@ -1153,7 +3334,7 @@ contract CryticPRBMath59x18Propertiesv3 { require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); SD59x18 x_pow_0 = pow(x, ZERO_FP); - assert(x_pow_0.eq(ONE_FP)); + assertEq(x_pow_0, ONE_FP); } // Test for zero base @@ -1162,7 +3343,7 @@ contract CryticPRBMath59x18Propertiesv3 { require(a.gt(ZERO_FP)); SD59x18 zero_pow_a = pow(ZERO_FP, a); - assert(zero_pow_a.eq(ZERO_FP)); + assertEq(zero_pow_a, ZERO_FP); } // Test for zero base @@ -1170,7 +3351,7 @@ contract CryticPRBMath59x18Propertiesv3 { function pow_test_zero_base_zero_exponent() public { SD59x18 zero_pow_a = pow(ZERO_FP, ZERO_FP); - assert(zero_pow_a.eq(ONE_FP)); + assertEq(zero_pow_a, ONE_FP); } // Test for exponent one @@ -1179,7 +3360,7 @@ contract CryticPRBMath59x18Propertiesv3 { require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); SD59x18 x_pow_1 = pow(x, ONE_FP); - assert(x_pow_1.eq(x)); + assertEq(x_pow_1, x); } // Test for base one @@ -1187,7 +3368,7 @@ contract CryticPRBMath59x18Propertiesv3 { function pow_test_base_one(SD59x18 a) public { SD59x18 one_pow_a = pow(ONE_FP, a); - assert(one_pow_a.eq(ONE_FP)); + assertEq(one_pow_a, ONE_FP); } @@ -1205,11 +3386,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_b = pow(x, b); SD59x18 x_ab = pow(x, a.add(b)); - uint256 power = 9; - int256 digits = int256(10**power); - - emit PropertyFailed(mul(x_a, x_b), x_ab, power); - assert(equal_most_significant_digits_within_precision(mul(x_a, x_b), x_ab, digits)); + assertEqWithinDecimalPrecision(mul(x_a, x_b), x_ab, 9); } // Test for power of an exponentiation @@ -1227,11 +3404,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_a_b = pow(x_a, b); SD59x18 x_ab = pow(x, a.mul(b)); - uint256 power = 9; - int256 digits = int256(10**power); - - emit PropertyFailed(x_a_b, x_ab, power); - assert(equal_most_significant_digits_within_precision(x_a_b, x_ab, digits)); + assertEqWithinDecimalPrecision(x_a_b, x_ab, 9); } // Test for power of a product @@ -1252,7 +3425,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_a = pow(x, a); SD59x18 y_a = pow(y, a); - assert(equal_within_precision(mul(x_a, y_a), xy_a, 10)); + assertEqWithinBitPrecision(mul(x_a, y_a), xy_a, 10); } // Test for result being greater than or lower than the argument, depending on @@ -1263,13 +3436,11 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_a = pow(x, a); if (abs(x).gte(ONE_FP)) { - emit PropertyFailed(x_a, ONE_FP); - assert(abs(x_a).gte(ONE_FP)); + assertGte(abs(x_a), ONE_FP); } if (abs(x).lte(ONE_FP)) { - emit PropertyFailed(x_a, ONE_FP); - assert(abs(x_a).lte(ONE_FP)); + assertLte(abs(x_a), ONE_FP); } } @@ -1281,13 +3452,11 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_a = pow(x, a); if (abs(x).gte(ONE_FP)) { - emit PropertyFailed(x_a, ONE_FP); - assert(abs(x_a).lte(ONE_FP)); + assertLte(abs(x_a), ONE_FP); } if (abs(x).lte(ONE_FP)) { - emit PropertyFailed(x_a, ONE_FP); - assert(abs(x_a).gte(ONE_FP)); + assertGte(abs(x_a), ONE_FP); } } @@ -1304,13 +3473,13 @@ contract CryticPRBMath59x18Propertiesv3 { // If the exponent is even if (a.mod(convert(2)).eq(ZERO_FP)) { - assert(x_a.eq(abs(x_a))); + assertEq(x_a, abs(x_a)); } else { // x_a preserves x sign if (x.lt(ZERO_FP)) { - assert(x_a.lt(ZERO_FP)); + assertLt(x_a, ZERO_FP); } else { - assert(x_a.gt(ZERO_FP)); + assertGt(x_a, ZERO_FP); } } } @@ -1320,8 +3489,19 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 pow_result = pow(convert(2), a); SD59x18 exp2_result = exp2(a); - emit PropertyFailed(pow_result, exp2_result); - assert(pow_result.eq(exp2_result)); + assertEq(pow_result, exp2_result); + } + + // Power is strictly increasing + // x > y && a >= 0 --> pow(x, a) >= pow(y, a) + function pow_test_strictly_increasing(SD59x18 x, SD59x18 y, SD59x18 a) public { + require(x.gt(y) && x.lte(MAX_PERMITTED_POW)); + require(a.gte(ZERO_FP)); + + SD59x18 x_a = pow(x, a); + SD59x18 y_a = pow(y, a); + + assertGte(x_a, y_a); } /* ================================================================ @@ -1348,7 +3528,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 result = pow(x, a); - assert(result.eq(ZERO_FP)); + assertEq(result, ZERO_FP); } /* ================================================================ @@ -1372,13 +3552,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 sqrt_x_squared = mul(sqrt_x, sqrt_x); // Precision loss is at most half the bits of the operand - assert( - equal_within_precision( - sqrt_x_squared, - x, - (intoUint256(log2(x)) >> 1) + 2 - ) - ); + assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); } // Test for the inverse operation @@ -1390,13 +3564,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 sqrt_x_squared = pow(sqrt_x, convert(2)); // Precision loss is at most half the bits of the operand - assert( - equal_within_precision( - sqrt_x_squared, - x, - (intoUint256(log2(x)) >> 1) + 2 - ) - ); + assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); } // Test for distributive property respect to the multiplication @@ -1417,18 +3585,30 @@ contract CryticPRBMath59x18Propertiesv3 { ); // Allow an error of up to one tenth of a percent - assert(equal_within_tolerance(sqrt_x_sqrt_y, sqrt_xy, ONE_TENTH_FP)); + assertEqWithinTolerance(sqrt_x_sqrt_y, sqrt_xy, ONE_TENTH_FP, "0.1%"); } // Test that sqrt is strictly increasing - function sqrt_test_is_increasing(SD59x18 x, SD59x18 y) public { + // x >= 0 && y > x --> sqrt(y) >= sqrt(x) + function sqrt_test_strictly_increasing(SD59x18 x, SD59x18 y) public { require(x.gte(ZERO_FP) && x.lte(MAX_SQRT.sub(SD59x18.wrap(1)))); require(y.gt(x) && y.lte(MAX_SQRT)); SD59x18 sqrt_x = sqrt(x); SD59x18 sqrt_y = sqrt(y); - assert(sqrt_y.gte(sqrt_x)); + assertGte(sqrt_y, sqrt_x); + } + + // Square root of perfect square should be equal to x + // sqrt(x * x) == x + function sqrt_test_square(SD59x18 x) public { + require(x.gt(ONE_FP)); + + SD59x18 square_x = x.mul(x); + SD59x18 sqrt_square_x = sqrt(square_x); + + assertEq(sqrt_square_x, x); } /* ================================================================ @@ -1440,7 +3620,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for zero case function sqrt_test_zero() public { - assert(sqrt(ZERO_FP).eq(ZERO_FP)); + assertEq(sqrt(ZERO_FP), ZERO_FP); } // Test for maximum value @@ -1504,9 +3684,9 @@ contract CryticPRBMath59x18Propertiesv3 { // The maximum loss of precision is given by the formula: // | log2(x) + log2(y) | - uint256 loss = intoUint256(abs(log2(x).add(log2(y)))); + uint256 loss = significant_digits_lost_in_mult(x, y) + 2; - assert(equal_within_precision(log2_x_log2_y, log2_xy, loss)); + assertEqWithinDecimalPrecision(log2_x_log2_y, log2_xy, loss); } // Test for logarithm of a power @@ -1518,7 +3698,18 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 log2_x_y = log2(x_y); SD59x18 y_log2_x = mul(log2(x), y); - assert(equal_within_tolerance(y_log2_x, log2_x_y, ONE_FP)); + assertEqWithinTolerance(y_log2_x, log2_x_y, ONE_TENTH_FP, "0.1%"); + } + + // Base 2 logarithm is strictly increasing + // y > 0 && x > y --> log2(x) > log2(y) + function log2_test_strictly_increasing(SD59x18 x, SD59x18 y) public { + require(y.gt(ZERO_FP) && x.gt(y)); + + SD59x18 log2_x = log2(x); + SD59x18 log2_y = log2(y); + + assertGte(log2_x, log2_y); } /* ================================================================ @@ -1544,7 +3735,7 @@ contract CryticPRBMath59x18Propertiesv3 { try this.helpersLog2(MAX_SD59x18) { // Expected, should not revert and the result must be > 0 result = this.helpersLog2(MAX_SD59x18); - assert(result.gt(ZERO_FP)); + assertGt(result, ZERO_FP); } catch { // Unexpected assert(false); @@ -1591,9 +3782,9 @@ contract CryticPRBMath59x18Propertiesv3 { // The maximum loss of precision is given by the formula: // | log2(x) + log2(y) | - uint256 loss = intoUint256(abs(log2(x).add(log2(y)))); + uint256 loss = significant_digits_lost_in_mult(x, y) + 2; - assert(equal_within_precision(ln_x_ln_y, ln_xy, loss)); + assertEqWithinDecimalPrecision(ln_x_ln_y, ln_xy, loss); } // Test for logarithm of a power @@ -1607,8 +3798,18 @@ contract CryticPRBMath59x18Propertiesv3 { require(significant_digits_after_mult(ln(x), y) > REQUIRED_SIGNIFICANT_DIGITS); - emit PropertyFailed(ln_x_y, y_ln_x); - assert(equal_within_tolerance(ln_x_y, y_ln_x, ONE_TENTH_FP)); + assertEqWithinTolerance(ln_x_y, y_ln_x, ONE_TENTH_FP, "0.1%"); + } + + // Natural logarithm is strictly increasing + // y > 0 && x > y --> log2(x) > log2(y) + function ln_test_strictly_increasing(SD59x18 x, SD59x18 y) public { + require(y.gt(ZERO_FP) && x.gt(y)); + + SD59x18 log2_x = ln(x); + SD59x18 log2_y = ln(y); + + assertGte(log2_x, log2_y); } /* ================================================================ @@ -1634,7 +3835,7 @@ contract CryticPRBMath59x18Propertiesv3 { try this.helpersLn(MAX_SD59x18) { // Expected, should not revert and the result must be > 0 result = this.helpersLn(MAX_SD59x18); - assert(result.gt(ZERO_FP)); + assertGt(result, ZERO_FP); } catch { // Unexpected assert(false); @@ -1672,7 +3873,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 exp2_x = exp2(x); SD59x18 pow_2_x = pow(TWO_FP, x); - assert(exp2_x.eq(pow_2_x)); + assertEq(exp2_x, pow_2_x); } // Test for inverse function @@ -1682,11 +3883,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 log2_x = log2(x); SD59x18 exp2_x = exp2(log2_x); - uint256 power = 30; - int256 digits = int256(10**power); - - emit PropertyFailed(x, exp2_x, power); - assert(equal_most_significant_digits_within_precision(x, exp2_x, digits)); + assertEqWithinDecimalPrecision(x, exp2_x, 9); } // Test for negative exponent @@ -1697,12 +3894,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 exp2_x = exp2(x); SD59x18 exp2_minus_x = exp2(neg(x)); - uint256 power = 2; - int256 digits = int256(10**power); - - emit PropertyFailed(exp2_x, inv(exp2_minus_x), power); - // Result should be within 2 digits precision for the worst case - assert(equal_most_significant_digits_within_precision(exp2_x, inv(exp2_minus_x), digits)); + assertEqWithinDecimalPrecision(exp2_x, inv(exp2_minus_x), 2); } /* ================================================================ @@ -1715,7 +3907,7 @@ contract CryticPRBMath59x18Propertiesv3 { // exp2(0) == 1 function exp2_test_zero() public { SD59x18 exp_zero = exp2(ZERO_FP); - assert(exp_zero.eq(ONE_FP)); + assertEq(exp_zero, ONE_FP); } // Test for maximum value. This should overflow as it won't fit @@ -1748,7 +3940,7 @@ contract CryticPRBMath59x18Propertiesv3 { try this.helpersExp2(MIN_SD59x18) { // Expected, should not revert, check that value is zero result = exp2(MIN_SD59x18); - assert(result.eq(ZERO_FP)); + assertEq(result, ZERO_FP); } catch { // Unexpected revert assert(false); @@ -1775,11 +3967,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 exp_x = exp(ln_x); SD59x18 log10_x = log10(x); - uint256 power = 16; - int256 digits = int256(10**power); - - emit PropertyFailed(x, exp_x, power); - assert(equal_most_significant_digits_within_precision(x, exp_x, digits)); + assertEqWithinDecimalPrecision(x, exp_x, 9); } // Test for negative exponent @@ -1791,7 +3979,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 exp_minus_x = exp(neg(x)); // Result should be within 4 bits precision for the worst case - assert(equal_within_precision(exp_x, inv(exp_minus_x), 4)); + assertEqWithinBitPrecision(exp_x, inv(exp_minus_x), 4); } // Test that exp strictly increases @@ -1802,7 +3990,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 exp_x = exp(x); SD59x18 exp_y = exp(y); - assert(exp_y.gte(exp_x)); + assertGte(exp_y, exp_x); } /* ================================================================ @@ -1815,7 +4003,7 @@ contract CryticPRBMath59x18Propertiesv3 { // exp(0) == 1 function exp_test_zero() public { SD59x18 exp_zero = exp(ZERO_FP); - assert(exp_zero.eq(ONE_FP)); + assertEq(exp_zero, ONE_FP); } // Test for maximum value. This should overflow as it won't fit @@ -1848,7 +4036,7 @@ contract CryticPRBMath59x18Propertiesv3 { try this.helpersExp(MIN_SD59x18) { // Expected, should not revert, check that value is zero result = exp(MIN_SD59x18); - assert(result.eq(ZERO_FP)); + assertEq(result, ZERO_FP); } catch { // Unexpected revert assert(false); @@ -1874,7 +4062,7 @@ contract CryticPRBMath59x18Propertiesv3 { require(x.lte(MAX_SD59x18)); SD59x18 x_pow_0 = powu(x, 0); - assert(x_pow_0.eq(ONE_FP)); + assertEq(x_pow_0, ONE_FP); } // Test for zero base @@ -1884,7 +4072,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 zero_pow_y = powu(ZERO_FP, y); - assert(zero_pow_y.eq(ZERO_FP)); + assertEq(zero_pow_y, ZERO_FP); } // Test for exponent one @@ -1894,7 +4082,7 @@ contract CryticPRBMath59x18Propertiesv3 { require(x.lte(MAX_SD59x18)); SD59x18 x_pow_1 = powu(x, 1); - assert(x_pow_1.eq(x)); + assertEq(x_pow_1, x); } // Test for base one @@ -1902,7 +4090,7 @@ contract CryticPRBMath59x18Propertiesv3 { function powu_test_base_one(uint256 y) public { SD59x18 one_pow_y = powu(ONE_FP, y); - assert(one_pow_y.eq(ONE_FP)); + assertEq(one_pow_y, ONE_FP); } // Test for product of powers of the same base @@ -1919,7 +4107,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_b = powu(x, b); SD59x18 x_ab = powu(x, a + b); - assert(equal_within_precision(mul(x_a, x_b), x_ab, 10)); + assertEqWithinBitPrecision(mul(x_a, x_b), x_ab, 10); } // Test for power of an exponentiation @@ -1936,7 +4124,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_a_b = powu(x_a, b); SD59x18 x_ab = powu(x, a * b); - assert(equal_within_precision(x_a_b, x_ab, 10)); + assertEqWithinBitPrecision(x_a_b, x_ab, 10); } // Test for power of a product @@ -1957,7 +4145,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_a = powu(x, a); SD59x18 y_a = powu(y, a); - assert(equal_within_precision(mul(x_a, y_a), xy_a, 10)); + assertEqWithinBitPrecision(mul(x_a, y_a), xy_a, 10); } // Test for result being greater than or lower than the argument, depending on @@ -1969,11 +4157,11 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_a = powu(x, a); if (abs(x).gte(ONE_FP)) { - assert(abs(x_a).gte(ONE_FP)); + assertGte(abs(x_a), ONE_FP); } if (abs(x).lte(ONE_FP)) { - assert(abs(x_a).lte(ONE_FP)); + assertLte(abs(x_a), ONE_FP); } } @@ -1990,17 +4178,34 @@ contract CryticPRBMath59x18Propertiesv3 { // If the exponent is even if (a % 2 == 0) { - assert(x_a.eq(abs(x_a))); + assertEq(x_a, abs(x_a)); } else { // x_a preserves x sign if (x.lt(ZERO_FP)) { - assert(x_a.lt(ZERO_FP)); + assertLt(x_a, ZERO_FP); } else { - assert(x_a.gt(ZERO_FP)); + assertGt(x_a, ZERO_FP); } } } + // Unsigned power is strictly increasing + // y > MIN && x > y && a > 0 && y != 0 --> powu(x, a) >= powu(y, a) + function powu_test_strictly_increasing( + SD59x18 x, + SD59x18 y, + uint256 a + ) public { + require(x.gt(y) && y.gt(MIN_SD59x18) && y.neq(ZERO_FP)); + require(x.lte(MAX_SD59x18)); + require(a > 0); + + SD59x18 x_a = powu(x, a); + SD59x18 y_a = powu(y, a); + + assertGte(x_a, y_a); + } + /* ================================================================ Tests for overflow and edge cases. These will make sure that the function reverts on overflow and @@ -2025,7 +4230,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 result = powu(x, a); - assert(result.eq(ZERO_FP)); + assertEq(result, ZERO_FP); } /* ================================================================ @@ -2056,9 +4261,9 @@ contract CryticPRBMath59x18Propertiesv3 { // The maximum loss of precision is given by the formula: // | log10(x) + log10(y) | - uint256 loss = intoUint256(abs(log10(x).add(log10(y)))); + uint256 loss = significant_digits_lost_in_mult(x, y) + 2; - assert(equal_within_precision(log10_x_log10_y, log10_xy, loss)); + assertEqWithinDecimalPrecision(log10_x_log10_y, log10_xy, loss); } // Test for logarithm of a power @@ -2069,7 +4274,18 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 log10_x_y = log10(x_y); SD59x18 y_log10_x = mul(log10(x), y); - assert(equal_within_tolerance(log10_x_y, y_log10_x, ONE_FP)); + assertEqWithinTolerance(log10_x_y, y_log10_x, ONE_TENTH_FP, "0.1%"); + } + + // Base 10 logarithm is strictly increasing + // x > y && y > 0 --> log10(x) > log10(y) + function log10_is_increasing(SD59x18 x, SD59x18 y) public { + require(y.gt(ZERO_FP) && x.gt(y)); + + SD59x18 log2_x = log10(x); + SD59x18 log2_y = log10(y); + + assertGt(log2_x, log2_y); } /* ================================================================ @@ -2095,7 +4311,7 @@ contract CryticPRBMath59x18Propertiesv3 { try this.helpersLog10(MAX_SD59x18) { // Expected, should not revert and the result must be > 0 result = this.helpersLog10(MAX_SD59x18); - assert(result.gt(ZERO_FP)); + assertGt(result, ZERO_FP); } catch { // Unexpected assert(false); @@ -2136,8 +4352,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 x_mul_y = x.mul(y); SD59x18 gm_squared = pow(gm(x,y), TWO_FP); - emit PropertyFailed(x_mul_y, gm_squared); - assert(equal_within_tolerance(x_mul_y, gm_squared, ONE_TENTH_FP)); + assertEqWithinTolerance(x_mul_y, gm_squared, ONE_TENTH_FP, "0.1%"); } // The geometric mean for a set of positive numbers is less than the @@ -2148,8 +4363,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 gm_x_y = gm(x, y); SD59x18 avg_x_y = avg(x, y); - emit PropertyFailed(gm_x_y, avg_x_y); - assert(gm_x_y.lt(avg_x_y)); + assertLte(gm_x_y, avg_x_y); } // The geometric mean of a set of positive equal numbers should be @@ -2160,8 +4374,7 @@ contract CryticPRBMath59x18Propertiesv3 { SD59x18 gm_x = gm(x, x); SD59x18 avg_x = avg(x, x); - emit PropertyFailed(gm_x, avg_x); - assert(gm_x.eq(avg_x)); + assertEq(gm_x, avg_x); } /* ================================================================ @@ -2176,7 +4389,7 @@ contract CryticPRBMath59x18Propertiesv3 { try this.helpersGm(x, ZERO_FP) { SD59x18 result = gm(x, ZERO_FP); - assert(result.eq(ZERO_FP)); + assertEq(result, ZERO_FP); } catch { // Unexpected, should not revert assert(false); @@ -2185,7 +4398,7 @@ contract CryticPRBMath59x18Propertiesv3 { // Test for single negative input function gm_test_negative(SD59x18 x, SD59x18 y) public { - require(x.gte(ZERO_FP) && y.lt(ZERO_FP)); + require(x.gt(ZERO_FP) && y.lt(ZERO_FP)); try this.helpersGm(x, y) { // Unexpected, should revert diff --git a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol index c823fb2..3f70a83 100644 --- a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol @@ -1,13 +1,14 @@ pragma solidity ^0.8.19; -import { UD60x18 } from "@prb/math/src/UD60x18.sol"; -import {add, sub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb/math/src/ud60x18/Helpers.sol"; -import {convert} from "@prb/math/src/ud60x18/Conversions.sol"; -import {msb} from "@prb/math/src/Common.sol"; -import {intoUint128, intoUint256} from "@prb/math/src/ud60x18/Casting.sol"; -import {mul, div, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, powu, gm} from "@prb/math/src/ud60x18/Math.sol"; +import { UD60x18 } from "@prb-math-v3/UD60x18.sol"; +import {add, sub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb-math-v3/ud60x18/Helpers.sol"; +import {convert} from "@prb-math-v3/ud60x18/Conversions.sol"; +import {msb} from "@prb-math-v3/Common.sol"; +import {intoUint128, intoUint256} from "@prb-math-v3/ud60x18/Casting.sol"; +import {mul, div, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, powu, gm} from "@prb-math-v3/ud60x18/Math.sol"; +import "./utils/AssertionHelperUD.sol"; -contract CryticPRBMath60x18Propertiesv3 { +contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { /* ================================================================ 59x18 fixed-point constants used for testing specific values. @@ -75,66 +76,6 @@ contract CryticPRBMath60x18Propertiesv3 { ================================================================ */ event Value(string reason, UD60x18 val); event LogErr(bytes error); - event PropertyFailed(UD60x18 result); - event PropertyFailed(UD60x18 result1, UD60x18 result2); - event PropertyFailed(UD60x18 result1, UD60x18 result2, uint256 discardedDigits); - event PropertyFailed(UD60x18 result1, UD60x18 result2, UD60x18 percentage); - event TestLog(uint256 num1, uint256 num2, uint256 result); - - /* ================================================================ - Helper functions. - ================================================================ */ - - // These functions allows to compare a and b for equality, discarding - // the last precision_bits bits. - // Uses functions from the library under test! - function equal_within_precision(UD60x18 a, UD60x18 b, uint256 precision_bits) public returns(bool) { - UD60x18 max = gt(a , b) ? a : b; - UD60x18 min = gt(a , b) ? b : a; - UD60x18 r = rshift(sub(max, min), precision_bits); - - emit PropertyFailed(a, b, precision_bits); - return (eq(r, convert(0))); - } - - // This function determines if the relative error between a and b is less - // than error_percent % (expressed as a 59x18 value) - // Uses functions from the library under test! - function equal_within_tolerance(UD60x18 a, UD60x18 b, UD60x18 error_percent) public returns(bool) { - UD60x18 tol_value = mul(a, div(error_percent, convert(100))); - - require(tol_value.neq(ZERO_FP)); - emit PropertyFailed(a, b, tol_value); - return (lte(sub(b, a), tol_value)); - } - - // Return how many significant bits will remain after multiplying a and b - // Uses functions from the library under test! - function significant_digits_after_mult(UD60x18 a, UD60x18 b) public pure returns (uint256) { - uint256 la = convert(floor(log10(a))); - uint256 lb = convert(floor(log10(b))); - uint256 prec = la + lb; - - if (prec < 18) return 0; - else return(60 + uint256(prec)); - } - - // Returns true if the n most significant bits of a and b are almost equal - // Uses functions from the library under test! - function equal_most_significant_digits_within_precision(UD60x18 a, UD60x18 b, uint256 digits) public returns (bool) { - // Divide both number by digits to truncate the unimportant digits - uint256 a_uint = UD60x18.unwrap(a); - uint256 b_uint = UD60x18.unwrap(b); - - uint256 a_significant = a_uint / digits; - uint256 b_significant = b_uint / digits; - - uint256 larger = a_significant > b_significant ? a_significant : b_significant; - uint256 smaller = a_significant > b_significant ? b_significant : a_significant; - - emit TestLog(larger, smaller, larger - smaller); - return ((larger - smaller) <= 1); - } /* ================================================================ Library wrappers. @@ -225,22 +166,22 @@ contract CryticPRBMath60x18Propertiesv3 { // Test for commutative property // x + y == y + x - function add_test_commutative(UD60x18 x, UD60x18 y) public pure { + function add_test_commutative(UD60x18 x, UD60x18 y) public { UD60x18 x_y = x.add(y); UD60x18 y_x = y.add(x); - assert(x_y.eq(y_x)); + assertEq(x_y, y_x); } // Test for associative property // (x + y) + z == x + (y + z) - function add_test_associative(UD60x18 x, UD60x18 y, UD60x18 z) public pure { + function add_test_associative(UD60x18 x, UD60x18 y, UD60x18 z) public { UD60x18 x_y = x.add(y); UD60x18 y_z = y.add(z); UD60x18 xy_z = x_y.add(z); UD60x18 x_yz = x.add(y_z); - assert(xy_z.eq(x_yz)); + assertEq(xy_z, x_yz); } // Test for identity operation @@ -248,8 +189,8 @@ contract CryticPRBMath60x18Propertiesv3 { function add_test_identity(UD60x18 x) public { UD60x18 x_0 = x.add(ZERO_FP); - assert(x.eq(x_0)); - assert(x.sub(x).eq(ZERO_FP)); + assertEq(x, x_0); + assertEq(x.sub(x), ZERO_FP); } // Test that the result increases or decreases depending @@ -257,7 +198,7 @@ contract CryticPRBMath60x18Propertiesv3 { function add_test_values(UD60x18 x, UD60x18 y) public { UD60x18 x_y = x.add(y); - assert(x_y.gte(x)); + assertGte(x_y, x); } /* ================================================================ @@ -281,7 +222,7 @@ contract CryticPRBMath60x18Propertiesv3 { function add_test_maximum_value() public { try this.helpersAdd(MAX_UD60x18, ZERO_FP) returns (UD60x18 result) { // Expected behaviour, does not revert - assert(result.eq(MAX_UD60x18)); + assertEq(result, MAX_UD60x18); } catch { assert(false); } @@ -301,7 +242,7 @@ contract CryticPRBMath60x18Propertiesv3 { function add_test_minimum_value() public { try this.helpersAdd(ZERO_FP, ZERO_FP) returns (UD60x18 result) { // Expected behaviour, does not revert - assert(result.eq(ZERO_FP)); + assertEq(result, ZERO_FP); } catch { assert(false); } @@ -324,28 +265,28 @@ contract CryticPRBMath60x18Propertiesv3 { function sub_test_identity(UD60x18 x) public { UD60x18 x_0 = x.sub(ZERO_FP); - assert(x_0.eq(x)); - assert(x.sub(x).eq(ZERO_FP)); + assertEq(x_0, x); + assertEq(x.sub(x), ZERO_FP); } // Test for neutrality over addition and subtraction // (x - y) + y == (x + y) - y == x - function sub_test_neutrality(UD60x18 x, UD60x18 y) public pure { + function sub_test_neutrality(UD60x18 x, UD60x18 y) public { UD60x18 x_minus_y = x.sub(y); UD60x18 x_plus_y = x.add(y); UD60x18 x_minus_y_plus_y = x_minus_y.add(y); UD60x18 x_plus_y_minus_y = x_plus_y.sub(y); - assert(x_minus_y_plus_y.eq(x_plus_y_minus_y)); - assert(x_minus_y_plus_y.eq(x)); + assertEq(x_minus_y_plus_y, x_plus_y_minus_y); + assertEq(x_minus_y_plus_y, x); } // Test that the result always decreases function sub_test_values(UD60x18 x, UD60x18 y) public { UD60x18 x_y = x.sub(y); - assert(x_y.lte(x)); + assertLte(x_y, x); } /* ================================================================ @@ -369,7 +310,7 @@ contract CryticPRBMath60x18Propertiesv3 { function sub_test_maximum_value() public { try this.helpersSub(MAX_UD60x18, ZERO_FP) returns (UD60x18 result) { // Expected behaviour, does not revert - assert(result.eq(MAX_UD60x18)); + assertEq(result, MAX_UD60x18); } catch { assert(false); } @@ -380,7 +321,7 @@ contract CryticPRBMath60x18Propertiesv3 { function sub_test_minimum_value() public { try this.helpersSub(ZERO_FP, ZERO_FP) returns (UD60x18 result) { // Expected behaviour, does not revert - assert(result.eq(ZERO_FP)); + assertEq(result, ZERO_FP); } catch { assert(false); } @@ -409,11 +350,11 @@ contract CryticPRBMath60x18Propertiesv3 { // Test for commutative property // x * y == y * x - function mul_test_commutative(UD60x18 x, UD60x18 y) public pure { + function mul_test_commutative(UD60x18 x, UD60x18 y) public { UD60x18 x_y = x.mul(y); UD60x18 y_x = y.mul(x); - assert(x_y.eq(y_x)); + assertEq(x_y, y_x); } // Test for associative property @@ -425,7 +366,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_yz = x.mul(y_z); require(xy_z.neq(ZERO_FP) && x_yz.neq(ZERO_FP)); - assert(equal_within_tolerance(xy_z, x_yz, ONE_TENTH_FP)); + assertEqWithinTolerance(xy_z, x_yz, ONE_TENTH_FP, "0.1%"); } // Test for distributive property @@ -437,7 +378,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_times_y = x.mul(y); UD60x18 x_times_z = x.mul(z); - assert(equal_within_tolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP)); + assertEqWithinTolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP, "0.1%"); } // Test for identity operation @@ -446,8 +387,8 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_1 = x.mul(ONE_FP); UD60x18 x_0 = x.mul(ZERO_FP); - assert(x_0.eq(ZERO_FP)); - assert(x_1.eq(x)); + assertEq(x_0, ZERO_FP); + assertEq(x_1, x); } // Test that the result increases or decreases depending @@ -456,9 +397,9 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_y = x.mul(y); if (y.gte(ONE_FP)) { - assert(x_y.gte(x)); + assertGte(x_y, x); } else { - assert(x_y.lte(x)); + assertLte(x_y, x); } } @@ -472,7 +413,7 @@ contract CryticPRBMath60x18Propertiesv3 { // and minimum allowed values for UD60x18 function mul_test_range(UD60x18 x, UD60x18 y) public { try this.helpersMul(x, y) returns(UD60x18 result) { - assert(result.lte(MAX_UD60x18)); + assertLte(result, MAX_UD60x18); } catch { // If it reverts, just ignore } @@ -483,7 +424,7 @@ contract CryticPRBMath60x18Propertiesv3 { function mul_test_maximum_value() public { try this.helpersMul(MAX_UD60x18, ONE_FP) returns (UD60x18 result) { // Expected behaviour, does not revert - assert(result.eq(MAX_UD60x18)); + assertEq(result, MAX_UD60x18); } catch { assert(false); } @@ -506,7 +447,7 @@ contract CryticPRBMath60x18Propertiesv3 { function div_test_division_identity_x_div_1(UD60x18 x) public { UD60x18 div_1 = div(x, ONE_FP); - assert(x.eq(div_1)); + assertEq(x, div_1); } // Test for identity property @@ -517,10 +458,10 @@ contract CryticPRBMath60x18Propertiesv3 { try this.helpersDiv(x, x) { // This should always equal one div_x = div(x, x); - assert(div_x.eq(ONE_FP)); + assertEq(div_x, ONE_FP); } catch { // Only valid case for revert is x == 0 - assert(x.eq(ZERO_FP)); + assertEq(x, ZERO_FP); } } @@ -531,7 +472,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 div_0 = div(ZERO_FP, y); - assert(ZERO_FP.eq(div_0)); + assertEq(ZERO_FP, div_0); } // Test that the value of the result increases or @@ -542,9 +483,9 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_y = div(x, y); if (y.gte(ONE_FP)) { - assert(x_y.lte(x)); + assertLte(x_y, x); } else { - assert(x_y.gte(x)); + assertGte(x_y, x); } } @@ -568,7 +509,7 @@ contract CryticPRBMath60x18Propertiesv3 { function div_test_maximum_denominator(UD60x18 x) public { UD60x18 div_large = div(x, MAX_UD60x18); - assert(div_large.lte(ONE_FP)); + assertLte(div_large, ONE_FP); } // Test for division of a large value @@ -581,7 +522,7 @@ contract CryticPRBMath60x18Propertiesv3 { // If it didn't revert, then |y| >= 1 div_large = div(MAX_UD60x18, y); - assert(y.gte(ONE_FP)); + assertGte(y, ONE_FP); } catch { // Expected revert as result is higher than max } @@ -595,7 +536,7 @@ contract CryticPRBMath60x18Propertiesv3 { try this.helpersDiv(x, y) { // If it returns a value, it must be in range result = div(x, y); - assert(result.lte(MAX_UD60x18)); + assertLte(result, MAX_UD60x18); } catch { // Otherwise, it should revert } @@ -623,7 +564,7 @@ contract CryticPRBMath60x18Propertiesv3 { // The maximum loss of precision will be 2 * log2(x) bits rounded up uint256 loss = 2 * intoUint256(log2(x)) + 2; - assert(equal_within_precision(x, double_inv_x, loss)); + assertEqWithinBitPrecision(x, double_inv_x, loss); } // Test equivalence with division @@ -633,7 +574,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 inv_x = inv(x); UD60x18 div_1_x = div(ONE_FP, x); - assert(inv_x.eq(div_1_x)); + assertEq(inv_x, div_1_x); } // Test the anticommutativity of the division @@ -647,7 +588,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_y = div(x, y); UD60x18 y_x = div(y, x); - assert(equal_within_tolerance(x_y, inv(y_x), ONE_TENTH_FP)); + assertEqWithinTolerance(x_y, inv(y_x), ONE_TENTH_FP, "0.1%"); } // Test the multiplication of inverses @@ -666,7 +607,7 @@ contract CryticPRBMath60x18Propertiesv3 { // 2 * | log2(x) - log2(y) | + 1 uint256 loss = 2 * intoUint256(log2(x).sub(log2(y))) + 1; - assert(equal_within_precision(inv_x_y, inv_x_times_inv_y, loss)); + assertEqWithinBitPrecision(inv_x_y, inv_x_times_inv_y, loss); } // Test multiplicative identity property @@ -679,7 +620,7 @@ contract CryticPRBMath60x18Propertiesv3 { require(inv_x.neq(ZERO_FP) && identity.neq(ZERO_FP)); // They should agree with a tolerance of one tenth of a percent - assert(equal_within_tolerance(identity, ONE_FP, ONE_TENTH_FP)); + assertEqWithinTolerance(identity, ONE_FP, ONE_TENTH_FP, "0.1%"); } // Test that the value of the result is in range zero-one @@ -691,9 +632,9 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 inv_x = inv(x); if (x.gte(ONE_FP)) { - assert(inv_x.lte(ONE_FP)); + assertLte(inv_x, ONE_FP); } else { - assert(inv_x.gt(ONE_FP)); + assertGt(inv_x, ONE_FP); } } @@ -717,7 +658,7 @@ contract CryticPRBMath60x18Propertiesv3 { try this.helpersInv(MAX_UD60x18) { inv_maximum = this.helpersInv(MAX_UD60x18); - assert(equal_within_precision(inv_maximum, ZERO_FP, 10)); + assertEqWithinBitPrecision(inv_maximum, ZERO_FP, 10); } catch { // Unexpected, the function must not revert assert(false); @@ -730,7 +671,7 @@ contract CryticPRBMath60x18Propertiesv3 { try this.helpersInv(UD60x18.wrap(1)) { inv_minimum = this.helpersInv(UD60x18.wrap(1)); - assert(equal_within_precision(inv_minimum, UD60x18.wrap(1e36), 10)); + assertEqWithinBitPrecision(inv_minimum, UD60x18.wrap(1e36), 10); } catch { // Unexpected, the function must not revert assert(false); @@ -751,31 +692,33 @@ contract CryticPRBMath60x18Propertiesv3 { // Test that the result is between the two operands // avg(x, y) >= min(x, y) && avg(x, y) <= max(x, y) - function avg_test_values_in_range(UD60x18 x, UD60x18 y) public pure { + function avg_test_values_in_range(UD60x18 x, UD60x18 y) public { UD60x18 avg_xy = avg(x, y); if (x.gte(y)) { - assert(avg_xy.gte(y) && avg_xy.lte(x)); + assertGte(avg_xy, y); + assertLte(avg_xy, x); } else { - assert(avg_xy.gte(x) && avg_xy.lte(y)); + assertGte(avg_xy, x); + assertLte(avg_xy, y); } } // Test that the average of the same number is itself // avg(x, x) == x - function avg_test_one_value(UD60x18 x) public pure { + function avg_test_one_value(UD60x18 x) public { UD60x18 avg_x = avg(x, x); - assert(avg_x.eq(x)); + assertEq(avg_x, x); } // Test that the order of operands is irrelevant // avg(x, y) == avg(y, x) - function avg_test_operand_order(UD60x18 x, UD60x18 y) public pure { + function avg_test_operand_order(UD60x18 x, UD60x18 y) public { UD60x18 avg_xy = avg(x, y); UD60x18 avg_yx = avg(y, x); - assert(avg_xy.eq(avg_yx)); + assertEq(avg_xy, avg_yx); } /* ================================================================ @@ -792,7 +735,7 @@ contract CryticPRBMath60x18Propertiesv3 { // If it doesn't revert, the result must be MAX_UD60x18 try this.helpersAvg(MAX_UD60x18, MAX_UD60x18) { result = this.helpersAvg(MAX_UD60x18, MAX_UD60x18); - assert(result.eq(MAX_UD60x18)); + assertEq(result, MAX_UD60x18); } catch { assert(false); } @@ -816,7 +759,7 @@ contract CryticPRBMath60x18Propertiesv3 { require(x.lte(MAX_PERMITTED_POW)); UD60x18 x_pow_0 = pow(x, ZERO_FP); - assert(x_pow_0.eq(ONE_FP)); + assertEq(x_pow_0, ONE_FP); } // Test for zero base @@ -827,7 +770,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 zero_pow_y = pow(ZERO_FP, y); - assert(zero_pow_y.eq(ZERO_FP)); + assertEq(zero_pow_y, ZERO_FP); } // Test for exponent one @@ -836,7 +779,7 @@ contract CryticPRBMath60x18Propertiesv3 { require(x.lte(MAX_PERMITTED_POW)); UD60x18 x_pow_1 = pow(x, ONE_FP); - assert(x_pow_1.eq(x)); + assertEq(x_pow_1, x); } // Test for base one @@ -844,7 +787,7 @@ contract CryticPRBMath60x18Propertiesv3 { function pow_test_base_one(UD60x18 y) public { UD60x18 one_pow_y = pow(ONE_FP, y); - assert(one_pow_y.eq(ONE_FP)); + assertEq(one_pow_y, ONE_FP); } // Test for product of powers of the same base @@ -861,11 +804,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_b = pow(x, b); UD60x18 x_ab = pow(x, a.add(b)); - uint256 power = 9; - uint256 digits = 10**power; - - emit PropertyFailed(mul(x_a, x_b), x_ab, power); - assert(equal_most_significant_digits_within_precision(mul(x_a, x_b), x_ab, digits)); + assertEqWithinDecimalPrecision(mul(x_a, x_b), x_ab, 9); } // Test for power of an exponentiation @@ -882,11 +821,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_a_b = pow(x_a, b); UD60x18 x_ab = pow(x, a.mul(b)); - uint256 power = 9; - uint256 digits = 10**power; - - emit PropertyFailed(x_a_b, x_ab, power); - assert(equal_most_significant_digits_within_precision(x_a_b, x_ab, digits)); + assertEqWithinDecimalPrecision(x_a_b, x_ab, 9); } // Test for power of a product @@ -906,11 +841,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_a = pow(x, a); UD60x18 y_a = pow(y, a); - uint256 power = 9; - uint256 digits = 10**power; - - emit PropertyFailed(mul(x_a, y_a), xy_a, power); - assert(equal_most_significant_digits_within_precision(mul(x_a, y_a), xy_a, digits)); + assertEqWithinDecimalPrecision(mul(x_a, y_a), xy_a, 9); } // Test for result being greater than or lower than the argument, depending on @@ -922,14 +853,26 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_a = pow(x, a); if (x.gte(ONE_FP)) { - assert(x_a.gte(ONE_FP)); + assertGte(x_a, ONE_FP); } if (x.lte(ONE_FP)) { - assert(x_a.lte(ONE_FP)); + assertLte(x_a, ONE_FP); } } + // Power is strictly increasing + // x > y && a >= 0 --> pow(x) >= pow(y) + function pow_test_strictly_increasing(UD60x18 x, UD60x18 y, UD60x18 a) public { + require(x.gt(y) && x.lte(MAX_PERMITTED_POW)); + require(a.gte(ZERO_FP)); + + UD60x18 x_a = pow(x, a); + UD60x18 y_a = pow(y, a); + + assertGte(x_a, y_a); + } + /* ================================================================ Tests for overflow and edge cases. These will make sure that the function reverts on overflow and @@ -967,7 +910,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 result = pow(x, a); - assert(result.eq(ZERO_FP)); + assertEq(result, ZERO_FP); } /* ================================================================ @@ -990,13 +933,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 sqrt_x_squared = mul(sqrt_x, sqrt_x); // Precision loss is at most half the bits of the operand - assert( - equal_within_precision( - sqrt_x_squared, - x, - (intoUint256(log2(x)) >> 1) + 2 - ) - ); + assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); } // Test for the inverse operation @@ -1007,13 +944,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 sqrt_x_squared = pow(sqrt_x, convert(2)); // Precision loss is at most half the bits of the operand - assert( - equal_within_precision( - sqrt_x_squared, - x, - (intoUint256(log2(x)) >> 1) + 2 - ) - ); + assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); } // Test for distributive property respect to the multiplication @@ -1026,7 +957,29 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 sqrt_xy = sqrt(mul(x, y)); // Allow an error of up to one tenth of a percent - assert(equal_within_tolerance(sqrt_x_sqrt_y, sqrt_xy, ONE_TENTH_FP)); + assertEqWithinTolerance(sqrt_x_sqrt_y, sqrt_xy, ONE_TENTH_FP, "0.1%"); + } + + // Test that sqrt is strictly increasing + // x >= 0 && y > x --> sqrt(y) >= sqrt(x) + function sqrt_test_strictly_increasing(UD60x18 x, UD60x18 y) public { + require(y.gt(x)); + + UD60x18 sqrt_x = sqrt(x); + UD60x18 sqrt_y = sqrt(y); + + assertGte(sqrt_y, sqrt_x); + } + + // Square root of perfect square should be equal to x + // sqrt(x * x) == x + function sqrt_test_square(UD60x18 x) public { + require(x.gt(ONE_FP)); + + UD60x18 square_x = x.mul(x); + UD60x18 sqrt_square_x = sqrt(square_x); + + assertEq(sqrt_square_x, x); } /* ================================================================ @@ -1037,7 +990,7 @@ contract CryticPRBMath60x18Propertiesv3 { // Test for zero case function sqrt_test_zero() public { - assert(sqrt(ZERO_FP).eq(ZERO_FP)); + assertEq(sqrt(ZERO_FP), ZERO_FP); } // Test for maximum value @@ -1079,7 +1032,7 @@ contract CryticPRBMath60x18Propertiesv3 { // | log2(x) + log2(y) | uint256 loss = intoUint256(log2(x).add(log2(y))); - assert(equal_within_precision(log2_x_log2_y, log2_xy, loss)); + assertEqWithinBitPrecision(log2_x_log2_y, log2_xy, loss); } // Test for logarithm of a power @@ -1091,12 +1044,18 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 log2_x_y = log2(x_y); UD60x18 y_log2_x = mul(log2(x), y); - uint256 power = 9; - uint256 digits = 10**power; + assertEqWithinTolerance(y_log2_x, log2_x_y, ONE_TENTH_FP, "0.1%"); + } - emit PropertyFailed(y_log2_x, log2_x_y, power); + // Base 2 logarithm is strictly increasing + // y > 0 && x > y --> log2(x) > log2(y) + function log2_test_strictly_increasing(UD60x18 x, UD60x18 y) public { + require(y.gt(ZERO_FP) && x.gt(y)); + + UD60x18 log2_x = log2(x); + UD60x18 log2_y = log2(y); - assert(equal_most_significant_digits_within_precision(y_log2_x, log2_x_y, digits)); + assertGte(log2_x, log2_y); } /* ================================================================ @@ -1122,7 +1081,7 @@ contract CryticPRBMath60x18Propertiesv3 { try this.helpersLog2(MAX_UD60x18) { // Expected, should not revert and the result must be > 0 result = this.helpersLog2(MAX_UD60x18); - assert(result.gt(ZERO_FP)); + assertGt(result, ZERO_FP); } catch { // Unexpected assert(false); @@ -1165,13 +1124,10 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 xy = mul(x, y); UD60x18 ln_xy = ln(xy); - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); - // The maximum loss of precision is given by the formula: // | log2(x) + log2(y) | uint256 loss = intoUint256(log2(x).add(log2(y))); - - assert(equal_within_precision(ln_x_ln_y, ln_xy, loss)); + assertEqWithinBitPrecision(ln_x_ln_y, ln_xy, loss); } // Test for logarithm of a power @@ -1182,11 +1138,18 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 ln_x_y = ln(x_y); UD60x18 y_ln_x = mul(ln(x), y); - uint256 power = 9; - uint256 digits = 10**power; + assertEqWithinDecimalPrecision(ln_x_y, y_ln_x, 9); + } - emit PropertyFailed(ln_x_y, y_ln_x, power); - assert(equal_most_significant_digits_within_precision(ln_x_y, y_ln_x, digits)); + // Natural logarithm is strictly increasing + // y > 0 && x > y --> log2(x) > log2(y) + function ln_test_strictly_increasing(UD60x18 x, UD60x18 y) public { + require(y.gt(ZERO_FP) && x.gt(y)); + + UD60x18 ln_x = ln(x); + UD60x18 ln_y = ln(y); + + assertGte(ln_x, ln_y); } /* ================================================================ @@ -1212,7 +1175,7 @@ contract CryticPRBMath60x18Propertiesv3 { try this.helpersLn(MAX_UD60x18) { // Expected, should not revert and the result must be > 0 result = this.helpersLn(MAX_UD60x18); - assert(result.gt(ZERO_FP)); + assertGt(result, ZERO_FP); } catch { // Unexpected assert(false); @@ -1250,7 +1213,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 exp2_x = exp2(x); UD60x18 pow_2_x = pow(TWO_FP, x); - assert(exp2_x.eq(pow_2_x)); + assertEq(exp2_x, pow_2_x); } // Test for inverse function @@ -1261,11 +1224,18 @@ contract CryticPRBMath60x18Propertiesv3 { require(log2_x.lte(MAX_PERMITTED_EXP2)); UD60x18 exp2_x = exp2(log2_x); - /* uint256 power = 30; - uint256 digits = 10**power; + assertEqWithinTolerance(x, exp2_x, ONE_TENTH_FP, "0.1%"); + } + + // Test that exp2 strictly increases + // y > x && y < MAX --> exp2(y) > exp2(x) + function exp2_test_strictly_increasing(UD60x18 x, UD60x18 y) public { + require(y.gt(x) && y.lte(MAX_PERMITTED_EXP2)); + + UD60x18 exp_x = exp(x); + UD60x18 exp_y = exp(y); - emit PropertyFailed(x, exp2_x, power); */ - assert(equal_within_tolerance(x, exp2_x, ONE_TENTH_FP)); + assertGte(exp_y, exp_x); } /* ================================================================ @@ -1278,7 +1248,7 @@ contract CryticPRBMath60x18Propertiesv3 { // exp2(0) == 1 function exp2_test_zero() public { UD60x18 exp_zero = exp2(ZERO_FP); - assert(exp_zero.eq(ONE_FP)); + assertEq(exp_zero, ONE_FP); } // Test for maximum value. This should overflow as it won't fit @@ -1313,11 +1283,18 @@ contract CryticPRBMath60x18Propertiesv3 { require(exp_x.lte(MAX_PERMITTED_EXP)); UD60x18 log2_x = log2(x); - uint256 power = 16; - uint256 digits = 10**power; + assertEqWithinDecimalPrecision(x, exp_x, 9); + } + + // Test that exp strictly increases + // y <= MAX && y.gt(x) --> exp(y) >= exp(x) + function exp_test_strictly_increasing(UD60x18 x, UD60x18 y) public { + require(y.gt(x) && y.lte(MAX_PERMITTED_EXP)); - emit PropertyFailed(x, exp_x, power); - assert(equal_most_significant_digits_within_precision(x, exp_x, digits)); + UD60x18 exp_x = exp(x); + UD60x18 exp_y = exp(y); + + assertGte(exp_y, exp_x); } /* ================================================================ @@ -1330,7 +1307,7 @@ contract CryticPRBMath60x18Propertiesv3 { // exp(0) == 1 function exp_test_zero() public { UD60x18 exp_zero = exp(ZERO_FP); - assert(exp_zero.eq(ONE_FP)); + assertEq(exp_zero, ONE_FP); } // Test for maximum value. This should overflow as it won't fit @@ -1361,7 +1338,7 @@ contract CryticPRBMath60x18Propertiesv3 { function powu_test_zero_exponent(UD60x18 x) public { UD60x18 x_pow_0 = powu(x, 0); - assert(x_pow_0.eq(ONE_FP)); + assertEq(x_pow_0, ONE_FP); } // Test for zero base @@ -1371,7 +1348,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 zero_pow_a = powu(ZERO_FP, a); - assert(zero_pow_a.eq(ZERO_FP)); + assertEq(zero_pow_a, ZERO_FP); } // Test for exponent one @@ -1379,7 +1356,7 @@ contract CryticPRBMath60x18Propertiesv3 { function powu_test_one_exponent(UD60x18 x) public { UD60x18 x_pow_1 = powu(x, 1); - assert(x_pow_1.eq(x)); + assertEq(x_pow_1, x); } // Test for base one @@ -1387,7 +1364,7 @@ contract CryticPRBMath60x18Propertiesv3 { function powu_test_base_one(uint256 a) public { UD60x18 one_pow_a = powu(ONE_FP, a); - assert(one_pow_a.eq(ONE_FP)); + assertEq(one_pow_a, ONE_FP); } // Test for product of powers of the same base @@ -1403,7 +1380,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_b = powu(x, b); UD60x18 x_ab = powu(x, a + b); - assert(equal_within_precision(mul(x_a, x_b), x_ab, 10)); + assertEqWithinBitPrecision(mul(x_a, x_b), x_ab, 10); } // Test for power of an exponentiation @@ -1419,7 +1396,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_a_b = powu(x_a, b); UD60x18 x_ab = powu(x, a * b); - assert(equal_within_precision(x_a_b, x_ab, 10)); + assertEqWithinBitPrecision(x_a_b, x_ab, 10); } // Test for power of a product @@ -1439,7 +1416,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_a = powu(x, a); UD60x18 y_a = powu(y, a); - assert(equal_within_precision(mul(x_a, y_a), xy_a, 10)); + assertEqWithinBitPrecision(mul(x_a, y_a), xy_a, 10); } // Test for result being greater than or lower than the argument, depending on @@ -1450,14 +1427,31 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_a = powu(x, a); if (x.gte(ONE_FP)) { - assert(x_a.gte(ONE_FP)); + assertGte(x_a, ONE_FP); } if (x.lte(ONE_FP)) { - assert(x_a.lte(ONE_FP)); + assertLte(x_a, ONE_FP); } } + // Unsigned power is strictly increasing + // y > MIN && x > y && a > 0 --> powu(x, a) > powu(y, a) + function powu_test_strictly_increasing( + UD60x18 x, + UD60x18 y, + uint256 a + ) public { + require(x.gt(y) && y.gt(ZERO_FP)); + require(x.lte(MAX_UD60x18)); + require(a > 0); + + UD60x18 x_a = powu(x, a); + UD60x18 y_a = powu(y, a); + + assertGte(x_a, y_a); + } + /* ================================================================ Tests for overflow and edge cases. These will make sure that the function reverts on overflow and @@ -1482,7 +1476,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 result = powu(x, a); - assert(result.eq(ZERO_FP)); + assertEq(result, ZERO_FP); } /* ================================================================ @@ -1508,14 +1502,11 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 xy = mul(x, y); UD60x18 log10_xy = log10(xy); - // Ensure we have enough significant digits for the result to be meaningful - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); - // The maximum loss of precision is given by the formula: // | log10(x) + log10(y) | uint256 loss = intoUint256(log10(x).add(log10(y))); - assert(equal_within_precision(log10_x_log10_y, log10_xy, loss)); + assertEqWithinBitPrecision(log10_x_log10_y, log10_xy, loss); } // Test for logarithm of a power @@ -1526,11 +1517,18 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 log10_x_y = log10(x_y); UD60x18 y_log10_x = mul(log10(x), y); - uint256 power = 9; - uint256 digits = 10**power; + assertEqWithinTolerance(log10_x_y, y_log10_x, ONE_TENTH_FP, "0.1%"); + } + + // Base 10 logarithm is strictly increasing + // x > y && y > 0 --> log10(x) > log10(y) + function log10_test_strictly_increasing(UD60x18 x, UD60x18 y) public { + require(y.gt(ZERO_FP) && x.gt(y)); + + UD60x18 log10_x = log10(x); + UD60x18 log10_y = log10(y); - emit PropertyFailed(log10_x_y, y_log10_x, power); - assert(equal_most_significant_digits_within_precision(log10_x_y, y_log10_x, digits)); + assertGte(log10_x, log10_y); } /* ================================================================ @@ -1556,7 +1554,7 @@ contract CryticPRBMath60x18Propertiesv3 { try this.helpersLog10(MAX_UD60x18) { // Expected, should not revert and the result must be > 0 result = this.helpersLog10(MAX_UD60x18); - assert(result.gt(ZERO_FP)); + assertGt(result, ZERO_FP); } catch { // Unexpected assert(false); @@ -1593,8 +1591,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 x_mul_y = x.mul(y); UD60x18 gm_squared = pow(gm(x,y), TWO_FP); - emit PropertyFailed(x_mul_y, gm_squared); - assert(equal_within_tolerance(x_mul_y, gm_squared, ONE_TENTH_FP)); + assertEqWithinTolerance(x_mul_y, gm_squared, ONE_TENTH_FP, "0.1%"); } // The geometric mean for a set of positive numbers is less than the @@ -1605,8 +1602,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 gm_x_y = gm(x, y); UD60x18 avg_x_y = avg(x, y); - emit PropertyFailed(gm_x_y, avg_x_y); - assert(gm_x_y.lt(avg_x_y)); + assertLte(gm_x_y, avg_x_y); } // The geometric mean of a set of positive equal numbers should be @@ -1615,8 +1611,7 @@ contract CryticPRBMath60x18Propertiesv3 { UD60x18 gm_x = gm(x, x); UD60x18 avg_x = avg(x, x); - emit PropertyFailed(gm_x, avg_x); - assert(gm_x.eq(avg_x)); + assertEq(gm_x, avg_x); } /* ================================================================ @@ -1631,7 +1626,7 @@ contract CryticPRBMath60x18Propertiesv3 { try this.helpersGm(x, ZERO_FP) { UD60x18 result = gm(x, ZERO_FP); - assert(result.eq(ZERO_FP)); + assertEq(result, ZERO_FP); } catch { // Unexpected, should not revert assert(false); diff --git a/contracts/Math/PRBMath/v3/utils/AssertionHelperSD.sol b/contracts/Math/PRBMath/v3/utils/AssertionHelperSD.sol new file mode 100644 index 0000000..d015343 --- /dev/null +++ b/contracts/Math/PRBMath/v3/utils/AssertionHelperSD.sol @@ -0,0 +1,316 @@ +pragma solidity ^0.8.0; + +import { SD59x18 } from "@prb-math-v3/SD59x18.sol"; + +import { convert } from "@prb-math-v3/sd59x18/Conversions.sol"; +import { add, sub, eq, gt, gte, lt, lte, rshift } from "@prb-math-v3/sd59x18/Helpers.sol"; +import { mul, div, abs } from "@prb-math-v3/sd59x18/Math.sol"; + +abstract contract AssertionHelperSD { + event AssertEqFailure(string); + event AssertGtFailure(string); + event AssertGteFailure(string); + event AssertLtFailure(string); + event AssertLteFailure(string); + + function assertEq(SD59x18 a, SD59x18 b) internal { + if (!a.eq(b)) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " != ", + str_b, + ". No precision loss allowed." + ); + emit AssertEqFailure(string(assertMsg)); + assert(false); + } + } + + function assertEqWithinBitPrecision(SD59x18 a, SD59x18 b, uint256 precision_bits) internal { + SD59x18 max = gt(a , b) ? a : b; + SD59x18 min = gt(a , b) ? b : a; + SD59x18 r = rshift(sub(max, min), precision_bits); + + if (!eq(r, convert(0))) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + string memory str_bits = toString(precision_bits); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " != ", + str_b, + " within ", + str_bits, + " bits of precision" + ); + emit AssertEqFailure(string(assertMsg)); + assert(false); + } + } + + function assertEqWithinTolerance(SD59x18 a, SD59x18 b, SD59x18 error_percent, string memory str_percent) internal { + SD59x18 tol_value = mul(a, div(error_percent, convert(100))); + + require(tol_value.neq(convert(0))); + + if (!lte(sub(b, a), abs(tol_value))) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + string memory tolerance = toString(SD59x18.unwrap(abs(tol_value))); + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " != ", + str_b, + " within ", + str_percent, + " tolerance: ", + tolerance + ); + emit AssertEqFailure(string(assertMsg)); + assert(false); + } + } + + // Returns true if the n most significant bits of a and b are almost equal + // Uses functions from the library under test! + function assertEqWithinDecimalPrecision(SD59x18 a, SD59x18 b, uint256 digits) internal { + // Divide both number by digits to truncate the unimportant digits + int256 a_int = SD59x18.unwrap(a); + int256 b_int = SD59x18.unwrap(b); + + int256 denominator = int256(10 ** digits); + + int256 a_significant = a_int / denominator; + int256 b_significant = b_int / denominator; + + int256 larger = a_significant > b_significant ? a_significant : b_significant; + int256 smaller = a_significant > b_significant ? b_significant : a_significant; + + if (!((larger - smaller) <= 1)) { + string memory str_a = toString(a_int); + string memory str_b = toString(b_int); + string memory str_larger = toString(larger); + string memory str_smaller = toString(smaller); + string memory str_digits = toString(digits); + string memory difference = toString(larger - smaller); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " != ", + str_b, + " within ", + str_digits, + " digits of precision. Difference: ", + difference, + ", truncated input:", + str_larger, + " != ", + str_smaller + ); + emit AssertEqFailure(string(assertMsg)); + assert(false); + } + } + + function assertGt(SD59x18 a, SD59x18 b, string memory reason) internal { + if (!a.gt(b)) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " <= ", + str_b, + ", reason: ", + reason + ); + emit AssertGtFailure(string(assertMsg)); + assert(false); + } + } + + function assertGt(SD59x18 a, SD59x18 b) internal { + if (!a.gt(b)) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " <= ", + str_b + ); + emit AssertGtFailure(string(assertMsg)); + assert(false); + } + } + + function assertGte(SD59x18 a, SD59x18 b, string memory reason) internal { + if (!a.gte(b)) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " < ", + str_b, + ", reason: ", + reason + ); + emit AssertGteFailure(string(assertMsg)); + assert(false); + } + } + + function assertGte(SD59x18 a, SD59x18 b) internal { + if (!a.gte(b)) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " < ", + str_b + ); + emit AssertGteFailure(string(assertMsg)); + assert(false); + } + } + + function assertLt(SD59x18 a, SD59x18 b, string memory reason) internal { + if (!a.lt(b)) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " >= ", + str_b, + ", reason: ", + reason + ); + emit AssertLtFailure(string(assertMsg)); + assert(false); + } + } + + function assertLt(SD59x18 a, SD59x18 b) internal { + if (!a.lt(b)) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " >= ", + str_b + ); + emit AssertLtFailure(string(assertMsg)); + assert(false); + } + } + + function assertLte(SD59x18 a, SD59x18 b, string memory reason) internal { + if (!a.lte(b)) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " > ", + str_b, + ", reason: ", + reason + ); + emit AssertLteFailure(string(assertMsg)); + assert(false); + } + } + + function assertLte(SD59x18 a, SD59x18 b) internal { + if (!a.lte(b)) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " > ", + str_b + ); + emit AssertLteFailure(string(assertMsg)); + assert(false); + } + } + + function toString(int256 value) internal pure returns (string memory str) { + uint256 absValue = value >= 0 ? uint256(value) : uint256(-value); + str = toString(absValue); + + if (value < 0) { + str = string(abi.encodePacked("-", str)); + } + } + + function toString(uint256 value) internal pure returns (string memory str) { + /// @solidity memory-safe-assembly + assembly { + // The maximum value of a uint256 contains 78 digits (1 byte per digit), but we allocate 160 bytes + // to keep the free memory pointer word aligned. We'll need 1 word for the length, 1 word for the + // trailing zeros padding, and 3 other words for a max of 78 digits. In total: 5 * 32 = 160 bytes. + let newFreeMemoryPointer := add(mload(0x40), 160) + + // Update the free memory pointer to avoid overriding our string. + mstore(0x40, newFreeMemoryPointer) + + // Assign str to the end of the zone of newly allocated memory. + str := sub(newFreeMemoryPointer, 32) + + // Clean the last word of memory it may not be overwritten. + mstore(str, 0) + + // Cache the end of the memory to calculate the length later. + let end := str + + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + // prettier-ignore + for { let temp := value } 1 {} { + // Move the pointer 1 byte to the left. + str := sub(str, 1) + + // Write the character to the pointer. + // The ASCII index of the '0' character is 48. + mstore8(str, add(48, mod(temp, 10))) + + // Keep dividing temp until zero. + temp := div(temp, 10) + + // prettier-ignore + if iszero(temp) { break } + } + + // Compute and cache the final total length of the string. + let length := sub(end, str) + + // Move the pointer 32 bytes leftwards to make room for the length. + str := sub(str, 32) + + // Store the string's length at the start of memory allocated for our string. + mstore(str, length) + } + } +} \ No newline at end of file diff --git a/contracts/Math/PRBMath/v3/utils/AssertionHelperUD.sol b/contracts/Math/PRBMath/v3/utils/AssertionHelperUD.sol new file mode 100644 index 0000000..b4d1a0a --- /dev/null +++ b/contracts/Math/PRBMath/v3/utils/AssertionHelperUD.sol @@ -0,0 +1,313 @@ +pragma solidity ^0.8.0; + +import { UD60x18 } from "@prb-math-v3/UD60x18.sol"; + +import {convert} from "@prb-math-v3/ud60x18/Conversions.sol"; +import {add, sub, eq, gt, gte, lt, lte, rshift} from "@prb-math-v3/ud60x18/Helpers.sol"; +import {mul, div, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, powu, gm} from "@prb-math-v3/ud60x18/Math.sol"; + +abstract contract AssertionHelperUD { + event AssertEqFailure(string); + event AssertGtFailure(string); + event AssertGteFailure(string); + event AssertLtFailure(string); + event AssertLteFailure(string); + + function assertEq(UD60x18 a, UD60x18 b) internal { + if (!a.eq(b)) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " != ", + str_b, + ". No precision loss allowed." + ); + emit AssertEqFailure(string(assertMsg)); + assert(false); + } + } + + function assertEqWithinBitPrecision(UD60x18 a, UD60x18 b, uint256 precision_bits) internal { + UD60x18 max = gt(a , b) ? a : b; + UD60x18 min = gt(a , b) ? b : a; + UD60x18 r = rshift(sub(max, min), precision_bits); + + if (!eq(r, convert(0))) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + string memory str_bits = toString(precision_bits); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " != ", + str_b, + " within ", + str_bits, + " bits of precision" + ); + emit AssertEqFailure(string(assertMsg)); + assert(false); + } + } + + function assertEqWithinTolerance(UD60x18 a, UD60x18 b, UD60x18 error_percent, string memory str_percent) internal { + UD60x18 tol_value = mul(a, div(error_percent, convert(100))); + + require(tol_value.neq(convert(0))); + + if (!lte(sub(b, a), tol_value)) { + string memory ua = toString(UD60x18.unwrap(a)); + string memory ub = toString(UD60x18.unwrap(b)); + string memory tolerance = toString(UD60x18.unwrap(tol_value)); + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + ua, + " != ", + ub, + " within ", + str_percent, + " tolerance: ", + tolerance + ); + emit AssertEqFailure(string(assertMsg)); + assert(false); + } + } + + // Returns true if the n most significant bits of a and b are almost equal + // Uses functions from the library under test! + function assertEqWithinDecimalPrecision(UD60x18 a, UD60x18 b, uint256 digits) internal { + // Divide both number by digits to truncate the unimportant digits + uint256 a_uint = UD60x18.unwrap(a); + uint256 b_uint = UD60x18.unwrap(b); + + uint256 a_significant = a_uint / 10 ** digits; + uint256 b_significant = b_uint / 10 ** digits; + + uint256 larger = a_significant > b_significant ? a_significant : b_significant; + uint256 smaller = a_significant > b_significant ? b_significant : a_significant; + + if (!((larger - smaller) <= 1)) { + string memory str_a = toString(a_uint); + string memory str_b = toString(b_uint); + string memory str_digits = toString(digits); + string memory str_larger = toString(larger); + string memory str_smaller = toString(smaller); + string memory difference = toString(larger - smaller); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " != ", + str_b, + " within ", + str_digits, + " digits of precision. Difference: ", + difference, + ", truncated input:", + str_larger, + " != ", + str_smaller + ); + emit AssertEqFailure(string(assertMsg)); + assert(false); + } + } + + function assertGt(UD60x18 a, UD60x18 b, string memory reason) internal { + if (!a.gt(b)) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " <= ", + str_b, + ", reason: ", + reason + ); + emit AssertGtFailure(string(assertMsg)); + assert(false); + } + } + function assertGt(UD60x18 a, UD60x18 b) internal { + if (!a.gt(b)) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " <= ", + str_b + ); + emit AssertGtFailure(string(assertMsg)); + assert(false); + } + } + + function assertGte(UD60x18 a, UD60x18 b, string memory reason) internal { + if (!a.gte(b)) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " < ", + str_b, + ", reason: ", + reason + ); + emit AssertGteFailure(string(assertMsg)); + assert(false); + } + } + + function assertGte(UD60x18 a, UD60x18 b) internal { + if (!a.gte(b)) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " < ", + str_b + ); + emit AssertGteFailure(string(assertMsg)); + assert(false); + } + } + + function assertLt(UD60x18 a, UD60x18 b, string memory reason) internal { + if (!a.lt(b)) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " >= ", + str_b, + ", reason: ", + reason + ); + emit AssertLtFailure(string(assertMsg)); + assert(false); + } + } + + function assertLt(UD60x18 a, UD60x18 b) internal { + if (!a.lt(b)) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " >= ", + str_b + ); + emit AssertLtFailure(string(assertMsg)); + assert(false); + } + } + + function assertLte(UD60x18 a, UD60x18 b, string memory reason) internal { + if (!a.lte(b)) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " > ", + str_b, + ", reason: ", + reason + ); + emit AssertLteFailure(string(assertMsg)); + assert(false); + } + } + + function assertLte(UD60x18 a, UD60x18 b) internal { + if (!a.lte(b)) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " > ", + str_b + ); + emit AssertLteFailure(string(assertMsg)); + assert(false); + } + } + + function toString(int256 value) internal pure returns (string memory str) { + uint256 absValue = value >= 0 ? uint256(value) : uint256(-value); + str = toString(absValue); + + if (value < 0) { + str = string(abi.encodePacked("-", str)); + } + } + + function toString(uint256 value) internal pure returns (string memory str) { + /// @solidity memory-safe-assembly + assembly { + // The maximum value of a uint256 contains 78 digits (1 byte per digit), but we allocate 160 bytes + // to keep the free memory pointer word aligned. We'll need 1 word for the length, 1 word for the + // trailing zeros padding, and 3 other words for a max of 78 digits. In total: 5 * 32 = 160 bytes. + let newFreeMemoryPointer := add(mload(0x40), 160) + + // Update the free memory pointer to avoid overriding our string. + mstore(0x40, newFreeMemoryPointer) + + // Assign str to the end of the zone of newly allocated memory. + str := sub(newFreeMemoryPointer, 32) + + // Clean the last word of memory it may not be overwritten. + mstore(str, 0) + + // Cache the end of the memory to calculate the length later. + let end := str + + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + // prettier-ignore + for { let temp := value } 1 {} { + // Move the pointer 1 byte to the left. + str := sub(str, 1) + + // Write the character to the pointer. + // The ASCII index of the '0' character is 48. + mstore8(str, add(48, mod(temp, 10))) + + // Keep dividing temp until zero. + temp := div(temp, 10) + + // prettier-ignore + if iszero(temp) { break } + } + + // Compute and cache the final total length of the string. + let length := sub(end, str) + + // Move the pointer 32 bytes leftwards to make room for the length. + str := sub(str, 32) + + // Store the string's length at the start of memory allocated for our string. + mstore(str, length) + } + } +} \ No newline at end of file diff --git a/contracts/Math/PRBMath/v4/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/v4/PRBMathSD59x18PropertyTests.sol new file mode 100644 index 0000000..f0b26f4 --- /dev/null +++ b/contracts/Math/PRBMath/v4/PRBMathSD59x18PropertyTests.sol @@ -0,0 +1,2211 @@ +pragma solidity ^0.8.19; + +import { SD59x18 } from "@prb/math/SD59x18.sol"; +import {add, sub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb/math/sd59x18/Helpers.sol"; +import {convert} from "@prb/math/sd59x18/Conversions.sol"; +import {msb} from "@prb/math/Common.sol"; +import {intoUint128, intoUint256} from "@prb/math/sd59x18/Casting.sol"; +import {mul, div, abs, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, powu, gm} from "@prb/math/sd59x18/Math.sol"; +import "./utils/AssertionHelperSD.sol"; + +contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { + + /* ================================================================ + 59x18 fixed-point constants used for testing specific values. + This assumes that PRBMath library's convert(x) works as expected. + ================================================================ */ + SD59x18 internal ZERO_FP = convert(0); + SD59x18 internal ONE_FP = convert(1); + SD59x18 internal MINUS_ONE_FP = convert(-1); + SD59x18 internal TWO_FP = convert(2); + SD59x18 internal THREE_FP = convert(3); + SD59x18 internal EIGHT_FP = convert(8); + SD59x18 internal THOUSAND_FP = convert(1000); + SD59x18 internal MINUS_SIXTY_FOUR_FP = convert(-64); + SD59x18 internal EPSILON = SD59x18.wrap(1); + SD59x18 internal ONE_TENTH_FP = convert(1).div(convert(10)); + + /* ================================================================ + Constants used for precision loss calculations + ================================================================ */ + uint256 internal REQUIRED_SIGNIFICANT_DIGITS = 10; + SD59x18 internal LOG2_PRECISION_LOSS = SD59x18.wrap(1); + + /* ================================================================ + Integer representations maximum values. + These constants are used for testing edge cases or limits for + possible values. + ================================================================ */ + /// @dev The unit number, which gives the decimal precision of SD59x18. + int256 constant uUNIT = 1e18; + SD59x18 constant UNIT = SD59x18.wrap(1e18); + + /// @dev The minimum value an SD59x18 number can have. + int256 constant uMIN_SD59x18 = -57896044618658097711785492504343953926634992332820282019728_792003956564819968; + SD59x18 constant MIN_SD59x18 = SD59x18.wrap(uMIN_SD59x18); + + /// @dev The maximum value an SD59x18 number can have. + int256 constant uMAX_SD59x18 = 57896044618658097711785492504343953926634992332820282019728_792003956564819967; + SD59x18 constant MAX_SD59x18 = SD59x18.wrap(uMAX_SD59x18); + + /// @dev The maximum input permitted in {exp2}. + int256 constant uEXP2_MAX_INPUT = 192e18 - 1; + SD59x18 constant EXP2_MAX_INPUT = SD59x18.wrap(uEXP2_MAX_INPUT); + + /// @dev The maximum input permitted in {exp}. + int256 constant uEXP_MAX_INPUT = 133_084258667509499440; + SD59x18 constant EXP_MAX_INPUT = SD59x18.wrap(uEXP_MAX_INPUT); + + /// @dev Euler's number as an SD59x18 number. + SD59x18 constant E = SD59x18.wrap(2_718281828459045235); + + int256 constant uMAX_SQRT = uMAX_SD59x18 / uUNIT; + SD59x18 constant MAX_SQRT = SD59x18.wrap(uMAX_SQRT); + + SD59x18 internal constant MAX_PERMITTED_EXP2 = SD59x18.wrap(192e18 - 1); + SD59x18 internal constant MIN_PERMITTED_EXP2 = SD59x18.wrap(-59_794705707972522261); + + SD59x18 internal constant MAX_PERMITTED_EXP = SD59x18.wrap(133_084258667509499440); + SD59x18 internal constant MIN_PERMITTED_EXP = SD59x18.wrap(-41_446531673892822322); + + SD59x18 internal constant MAX_PERMITTED_POW = SD59x18.wrap(2 ** 192 * 10 ** 18 - 1); + /// @dev Half the UNIT number. + int256 constant uHALF_UNIT = 0.5e18; + SD59x18 constant HALF_UNIT = SD59x18.wrap(uHALF_UNIT); + + /// @dev log2(10) as an SD59x18 number. + int256 constant uLOG2_10 = 3_321928094887362347; + SD59x18 constant LOG2_10 = SD59x18.wrap(uLOG2_10); + + /// @dev log2(e) as an SD59x18 number. + int256 constant uLOG2_E = 1_442695040888963407; + SD59x18 constant LOG2_E = SD59x18.wrap(uLOG2_E); + + /* ================================================================ + Events used for debugging or showing information. + ================================================================ */ + event Value(string reason, SD59x18 val); + event LogErr(bytes error); + + + /* ================================================================ + Helper functions. + ================================================================ */ + + // Check that there are remaining significant digits after a multiplication + // Uses functions from the library under test! + function significant_digits_are_lost_in_mult(SD59x18 a, SD59x18 b) public pure returns (bool) { + int256 la = convert(floor(log10(abs(a)))); + int256 lb = convert(floor(log10(abs(b)))); + + return(la + lb < -18); + } + + // Return how many significant bits will remain after multiplying a and b + // Uses functions from the library under test! + function significant_digits_after_mult(SD59x18 a, SD59x18 b) public pure returns (uint256) { + int256 la = convert(floor(log10(abs(a)))); + int256 lb = convert(floor(log10(abs(b)))); + int256 prec = la + lb; + + if (prec < -18) return 0; + else return(59 + uint256(prec)); + } + + // Return how many significant bits will be lost after multiplying a and b + // Uses functions from the library under test! + function significant_digits_lost_in_mult(SD59x18 a, SD59x18 b) public pure returns (uint256) { + int256 la = convert(floor(log10(abs(a)))); + int256 lb = convert(floor(log10(abs(b)))); + // digits left + int256 prec = la + lb; + + if (la > 0 && lb > 0) { + return 0; + } else { + return la < lb ? uint256(-la) : uint256(-lb); + } + } + + /* ================================================================ + Library wrappers. + These functions allow calling the PRBMathSD59x18 library. + ================================================================ */ + function debug(string calldata x, SD59x18 y) public { + emit Value(x, y); + } + + // Wrapper for external try/catch calls + function helpersAdd(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return add(x,y); + } + + // Wrapper for external try/catch calls + function helpersSub(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return sub(x,y); + } + + // Wrapper for external try/catch calls + function helpersMul(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return mul(x,y); + } + + function helpersDiv(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return div(x,y); + } + + function neg(SD59x18 x) public pure returns (SD59x18) { + return SD59x18.wrap(-SD59x18.unwrap(x)); + } + + function helpersAbs(SD59x18 x) public pure returns (SD59x18) { + return abs(x); + } + + function helpersLn(SD59x18 x) public pure returns (SD59x18) { + return ln(x); + } + + function helpersExp(SD59x18 x) public pure returns (SD59x18) { + return exp(x); + } + + function helpersExp2(SD59x18 x) public pure returns (SD59x18) { + return exp2(x); + } + + function helpersLog2(SD59x18 x) public pure returns (SD59x18) { + return log2(x); + } + + function helpersSqrt(SD59x18 x) public pure returns (SD59x18) { + return sqrt(x); + } + + function helpersPow(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return pow(x, y); + } + + function helpersPowu(SD59x18 x, uint256 y) public pure returns (SD59x18) { + return powu(x, y); + } + + function helpersAvg(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return avg(x, y); + } + + function helpersInv(SD59x18 x) public pure returns (SD59x18) { + return inv(x); + } + + function helpersLog10(SD59x18 x) public pure returns (SD59x18) { + return log10(x); + } + + function helpersFloor(SD59x18 x) public pure returns (SD59x18) { + return floor(x); + } + + function helpersGm(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { + return gm(x,y); + } + + /* ================================================================ + + TESTS FOR FUNCTION add() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for commutative property + // x + y == y + x + function add_test_commutative(SD59x18 x, SD59x18 y) public { + SD59x18 x_y = x.add(y); + SD59x18 y_x = y.add(x); + + assertEq(x_y, y_x); + } + + // Test for associative property + // (x + y) + z == x + (y + z) + function add_test_associative(SD59x18 x, SD59x18 y, SD59x18 z) public { + SD59x18 x_y = x.add(y); + SD59x18 y_z = y.add(z); + SD59x18 xy_z = x_y.add(z); + SD59x18 x_yz = x.add(y_z); + + assertEq(xy_z, x_yz); + } + + // Test for identity operation + // x + 0 == x (equivalent to x + (-x) == 0) + function add_test_identity(SD59x18 x) public { + SD59x18 x_0 = x.add(ZERO_FP); + + assertEq(x, x_0); + assertEq(x.sub(x), ZERO_FP); + } + + // Test that the result increases or decreases depending + // on the value to be added + function add_test_values(SD59x18 x, SD59x18 y) public { + SD59x18 x_y = x.add(y); + + if (y.gte(ZERO_FP)) { + assertGte(x_y, x); + } else { + assertLt(x_y, x); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These should make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // The result of the addition must be between the maximum + // and minimum allowed values for SD59x18 + function add_test_range(SD59x18 x, SD59x18 y) public { + try this.helpersAdd(x, y) returns (SD59x18 result) { + assertLte(result, MAX_SD59x18); + assertGte(result, MIN_SD59x18); + } catch { + // If it reverts, just ignore + } + } + + // Adding zero to the maximum value shouldn't revert, as it is valid + // Moreover, the result must be MAX_SD59x18 + function add_test_maximum_value() public { + try this.helpersAdd(MAX_SD59x18, ZERO_FP) returns (SD59x18 result) { + // Expected behaviour, does not revert + assertEq(result, MAX_SD59x18); + } catch { + assert(false); + } + } + + // Adding one to the maximum value should revert, as it is out of range + function add_test_maximum_value_plus_one() public { + try this.helpersAdd(MAX_SD59x18, ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + // Adding zero to the minimum value shouldn't revert, as it is valid + // Moreover, the result must be MIN_SD59x18 + function add_test_minimum_value() public { + try this.helpersAdd(MIN_SD59x18, ZERO_FP) returns (SD59x18 result) { + // Expected behaviour, does not revert + assertEq(result, MIN_SD59x18); + } catch { + assert(false); + } + } + + // Adding minus one to the maximum value should revert, as it is out of range + function add_test_minimum_value_plus_negative_one() public { + try this.helpersAdd(MIN_SD59x18, MINUS_ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + /* ================================================================ + + TESTS FOR FUNCTION sub() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test equivalence to addition + // x - y == x + (-y) + function sub_test_equivalence_to_addition(SD59x18 x, SD59x18 y) public { + SD59x18 minus_y = neg(y); + SD59x18 addition = x.add(minus_y); + SD59x18 subtraction = x.sub(y); + + assertEq(addition, subtraction); + } + + // Test for non-commutative property + // x - y == -(y - x) + function sub_test_non_commutative(SD59x18 x, SD59x18 y) public { + SD59x18 x_y = x.sub(y); + SD59x18 y_x = y.sub(x); + + assertEq(x_y, neg(y_x)); + } + + // Test for identity operation + // x - 0 == x (equivalent to x - x == 0) + function sub_test_identity(SD59x18 x) public { + SD59x18 x_0 = x.sub(ZERO_FP); + + assertEq(x_0, x); + assertEq(x.sub(x), ZERO_FP); + } + + // Test for neutrality over addition and subtraction + // (x - y) + y == (x + y) - y == x + function sub_test_neutrality(SD59x18 x, SD59x18 y) public { + SD59x18 x_minus_y = x.sub(y); + SD59x18 x_plus_y = x.add(y); + + SD59x18 x_minus_y_plus_y = x_minus_y.add(y); + SD59x18 x_plus_y_minus_y = x_plus_y.sub(y); + + assertEq(x_minus_y_plus_y, x_plus_y_minus_y); + assertEq(x_minus_y_plus_y, x); + } + + // Test that the result increases or decreases depending + // on the value to be subtracted + function sub_test_values(SD59x18 x, SD59x18 y) public { + SD59x18 x_y = x.sub(y); + + if (y.gte(ZERO_FP)) { + assertLte(x_y, x); + } else { + assertGt(x_y, x); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // The result of the subtraction must be between the maximum + // and minimum allowed values for SD59x18 + function sub_test_range(SD59x18 x, SD59x18 y) public { + try this.helpersSub(x, y) returns (SD59x18 result) { + assertLte(result, MAX_SD59x18); + assertGte(result, MIN_SD59x18); + } catch { + // If it reverts, just ignore + } + } + + // Subtracting zero from the maximum value shouldn't revert, as it is valid + // Moreover, the result must be MAX_SD59x18 + function sub_test_maximum_value() public { + try this.helpersSub(MAX_SD59x18, ZERO_FP) returns (SD59x18 result) { + // Expected behaviour, does not revert + assertEq(result, MAX_SD59x18); + } catch { + assert(false); + } + } + + // Subtracting minus one from the maximum value should revert, + // as it is out of range + function sub_test_maximum_value_minus_neg_one() public { + try this.helpersSub(MAX_SD59x18, MINUS_ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + // Subtracting zero from the minimum value shouldn't revert, as it is valid + // Moreover, the result must be MIN_SD59x18 + function sub_test_minimum_value() public { + try this.helpersSub(MIN_SD59x18, ZERO_FP) returns (SD59x18 result) { + // Expected behaviour, does not revert + assertEq(result, MIN_SD59x18); + } catch { + assert(false); + } + } + + // Subtracting one from the minimum value should revert, as it is out of range + function sub_test_minimum_value_minus_one() public { + try this.helpersSub(MIN_SD59x18, ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + /* ================================================================ + + TESTS FOR FUNCTION mul() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for commutative property + // x * y == y * x + function mul_test_commutative(SD59x18 x, SD59x18 y) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + SD59x18 x_y = x.mul(y); + SD59x18 y_x = y.mul(x); + + assertEq(x_y, y_x); + } + + // Test for associative property + // (x * y) * z == x * (y * z) + function mul_test_associative(SD59x18 x, SD59x18 y, SD59x18 z) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18) && z.gt(MIN_SD59x18)); + SD59x18 x_y = x.mul(y); + SD59x18 y_z = y.mul(z); + SD59x18 xy_z = x_y.mul(z); + SD59x18 x_yz = x.mul(y_z); + + require(xy_z.neq(ZERO_FP) && x_yz.neq(ZERO_FP)); + + uint256 digitsLost = significant_digits_lost_in_mult(x, y); + digitsLost += significant_digits_lost_in_mult(x, z); + + assertEqWithinDecimalPrecision(xy_z, x_yz, digitsLost); + } + + // Test for distributive property + // x * (y + z) == x * y + x * z + function mul_test_distributive(SD59x18 x, SD59x18 y, SD59x18 z) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18) && z.gt(MIN_SD59x18)); + SD59x18 y_plus_z = y.add(z); + SD59x18 x_times_y_plus_z = x.mul(y_plus_z); + + SD59x18 x_times_y = x.mul(y); + SD59x18 x_times_z = x.mul(z); + + require(add(x_times_y, x_times_z).neq(ZERO_FP) && x_times_y_plus_z.neq(ZERO_FP)); + assertEqWithinTolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP, "0.1%"); + } + + + // Test for identity operation + // x * 1 == x (also check that x * 0 == 0) + function mul_test_identity(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + SD59x18 x_1 = x.mul(ONE_FP); + SD59x18 x_0 = x.mul(ZERO_FP); + + assertEq(x_0, ZERO_FP); + assertEq(x_1, x); + } + + + // If x is positive and y is >= 1, the result should be larger than or equal to x + // If x is positive and y is < 1, the result should be smaller than x + function mul_test_x_positive(SD59x18 x, SD59x18 y) public { + require(x.gte(ZERO_FP)); + + SD59x18 x_y = x.mul(y); + + if (y.gte(ONE_FP)) { + assertGte(x_y, x); + } else { + assertLte(x_y, x); + } + } + + // If x is negative and y is >= 1, the result should be smaller than or equal to x + // If x is negative and y is < 1, the result should be larger than or equal to x + function mul_test_x_negative(SD59x18 x, SD59x18 y) public { + require(x.lte(ZERO_FP) && y.neq(ZERO_FP)); + + SD59x18 x_y = x.mul(y); + + if (y.gte(ONE_FP)) { + assertLte(x_y, x); + } else { + assertGte(x_y, x); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // The result of the multiplication must be between the maximum + // and minimum allowed values for SD59x18 + function mul_test_range(SD59x18 x, SD59x18 y) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + + try this.helpersMul(x, y) returns(SD59x18 result) { + assertLte(result, MAX_SD59x18); + assertGte(result, MIN_SD59x18); + } catch { + // If it reverts, just ignore + } + } + + // Multiplying the maximum value times one shouldn't revert, as it is valid + // Moreover, the result must be MAX_SD59x18 + function mul_test_maximum_value() public { + try this.helpersMul(MAX_SD59x18, ONE_FP) returns (SD59x18 result) { + // Expected behaviour, does not revert + assertEq(result, MAX_SD59x18); + } catch { + assert(false); + } + } + + // Multiplying the minimum value times one shouldn't revert, as it is valid + // Moreover, the result must be MIN_SD59x18 + function mul_test_minimum_value() public { + try this.helpersMul(MIN_SD59x18.add(ONE_FP), ONE_FP) returns (SD59x18 result) { + // Expected behaviour, does not revert + assertEq(result, MIN_SD59x18.add(ONE_FP)); + } catch { + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION div() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for identity property + // x / 1 == x (equivalent to x / x == 1) + function div_test_division_identity_x_div_1(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + SD59x18 div_1 = div(x, ONE_FP); + + assertEq(x, div_1); + } + + // Test for identity property + // x/x should not revert unless x == 0 || x == MIN_SD59x18 + function div_test_division_identity_x_div_x(SD59x18 x) public { + SD59x18 div_x; + + try this.helpersDiv(x, x) { + // This should always equal one + div_x = div(x, x); + assertEq(div_x, ONE_FP); + } catch { + // There are a couple of allowed cases for a revert: + // 1. x == 0 + // 2. x == MIN_SD59x18 + // 3. when the result overflows + assert(x.eq(ZERO_FP) || x.eq(MIN_SD59x18)); + } + } + + // Test for negative divisor + // x / -y == -(x / y) + function div_test_negative_divisor(SD59x18 x, SD59x18 y) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + require(y.lt(ZERO_FP)); + + SD59x18 x_y = div(x, y); + SD59x18 x_minus_y = div(x, neg(y)); + + assertEq(x_y, neg(x_minus_y)); + } + + // Test for division with 0 as numerator + // 0 / x = 0 + function div_test_division_num_zero(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + require(x.neq(ZERO_FP)); + + SD59x18 div_0 = div(ZERO_FP, x); + + assertEq(ZERO_FP, div_0); + } + + // Test that the absolute value of the result increases or + // decreases depending on the denominator's absolute value + function div_test_values(SD59x18 x, SD59x18 y) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + require(y.neq(ZERO_FP)); + + SD59x18 x_y = abs(div(x, y)); + + if (abs(y).gte(ONE_FP)) { + assertLte(x_y, abs(x)); + } else { + assertGte(x_y, abs(x)); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + + // Test for division by zero + function div_test_div_by_zero(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + try this.helpersDiv(x, ZERO_FP) { + // Unexpected, this should revert + assert(false); + } catch { + // Expected revert + } + } + + // Test for division by a large value, the result should be less than one + function div_test_maximum_denominator(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + SD59x18 div_large = div(x, MAX_SD59x18); + + assertLte(abs(div_large), ONE_FP); + } + + // Test for division of a large value + // This should revert if |x| < 1 as it would return a value higher than max + function div_test_maximum_numerator(SD59x18 y) public { + SD59x18 div_large; + + try this.helpersDiv(MAX_SD59x18, y) { + // If it didn't revert, then |x| >= 1 + div_large = div(MAX_SD59x18, y); + + assertGte(abs(y), ONE_FP); + } catch { + // Expected revert as result is higher than max + } + } + + // Test for values in range + function div_test_range(SD59x18 x, SD59x18 y) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + SD59x18 result; + + try this.helpersDiv(x, y) { + // If it returns a value, it must be in range + result = div(x, y); + assertLte(result, MAX_SD59x18); + assertGte(result, MIN_SD59x18); + } catch { + // Otherwise, it should revert + } + } + + /* ================================================================ + + TESTS FOR FUNCTION neg() + + ================================================================ */ + + /* ================================================================ + Tests for mathematical properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for the double negation + // -(-x) == x + function neg_test_double_negation(SD59x18 x) public { + SD59x18 double_neg = neg(neg(x)); + + assertEq(x, double_neg); + } + + // Test for the identity operation + // x + (-x) == 0 + function neg_test_identity(SD59x18 x) public { + SD59x18 neg_x = neg(x); + + assertEq(add(x, neg_x), ZERO_FP); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for the zero-case + // -0 == 0 + function neg_test_zero() public { + SD59x18 neg_x = neg(ZERO_FP); + + assertEq(neg_x, ZERO_FP); + } + + // Test for the maximum value case + // Since this is implementation-dependant, we will actually test with MAX_SD59x18-EPS + function neg_test_maximum() public { + try this.neg(sub(MAX_SD59x18, EPSILON)) { + // Expected behaviour, does not revert + } catch { + assert(false); + } + } + + // Test for the minimum value case + // Since this is implementation-dependant, we will actually test with MIN_SD59x18+EPS + function neg_test_minimum() public { + try this.neg(add(MIN_SD59x18, EPSILON)) { + // Expected behaviour, does not revert + } catch { + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION abs() + + ================================================================ */ + + /* ================================================================ + Tests for mathematical properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + + // Test that the absolute value is always positive + function abs_test_positive(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + SD59x18 abs_x = abs(x); + + assertGte(abs_x, ZERO_FP); + } + + // Test that the absolute value of a number equals the + // absolute value of the negative of the same number + function abs_test_negative(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + + SD59x18 abs_x = abs(x); + SD59x18 abs_minus_x = abs(neg(x)); + + assertEq(abs_x, abs_minus_x); + } + + // Test the multiplicativeness property + // | x * y | == |x| * |y| + function abs_test_multiplicativeness(SD59x18 x, SD59x18 y) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + + SD59x18 abs_x = abs(x); + SD59x18 abs_y = abs(y); + SD59x18 abs_xy = abs(mul(x, y)); + SD59x18 abs_x_abs_y = mul(abs_x, abs_y); + + // Failure if all significant digits are lost + require(significant_digits_are_lost_in_mult(abs_x, abs_y) == false); + + // Assume a tolerance of two bits of precision + assertEqWithinBitPrecision(abs_xy, abs_x_abs_y, 2); + } + + // Test the subadditivity property + // | x + y | <= |x| + |y| + function abs_test_subadditivity(SD59x18 x, SD59x18 y) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + + SD59x18 abs_x = abs(x); + SD59x18 abs_y = abs(y); + SD59x18 abs_xy = abs(add(x, y)); + + assertLte(abs_xy, add(abs_x, abs_y)); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test the zero-case | 0 | = 0 + function abs_test_zero() public { + SD59x18 abs_zero; + + try this.helpersAbs(ZERO_FP) { + // If it doesn't revert, the value must be zero + abs_zero = this.helpersAbs(ZERO_FP); + assertEq(abs_zero, ZERO_FP); + } catch { + // Unexpected, the function must not revert here + assert(false); + } + } + + // Test the maximum value + function abs_test_maximum() public { + SD59x18 abs_max; + + try this.helpersAbs(MAX_SD59x18) { + // If it doesn't revert, the value must be MAX_SD59x18 + abs_max = this.helpersAbs(MAX_SD59x18); + assertEq(abs_max, MAX_SD59x18); + } catch { + assert(false); + } + } + + // Test the minimum value + function abs_test_minimum_revert() public { + SD59x18 abs_min; + + try this.helpersAbs(MIN_SD59x18) { + // It should always revert for MIN_SD59x18 + assert(false); + } catch {} + } + + // Test the minimum value + function abs_test_minimum_allowed() public { + SD59x18 abs_min; + SD59x18 input = MIN_SD59x18.add(SD59x18.wrap(1)); + + try this.helpersAbs(input) { + // If it doesn't revert, the value must be the negative of MIN_SD59x18 + 1 + abs_min = this.helpersAbs(input); + assertEq(abs_min, neg(input)); + } catch { + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION inv() + + ================================================================ */ + + /* ================================================================ + Tests for mathematical properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test that the inverse of the inverse is close enough to the + // original number + function inv_test_double_inverse(SD59x18 x) public { + require(x.neq(ZERO_FP)); + + SD59x18 double_inv_x = inv(inv(x)); + + // The maximum loss of precision will be 2 * log2(x) bits rounded up + uint256 loss = 2 * significant_digits_lost_in_mult(x, x)+ 2; + + assertEqWithinDecimalPrecision(x, double_inv_x, loss); + } + + // Test equivalence with division + function inv_test_division(SD59x18 x) public { + require(x.neq(ZERO_FP)); + + SD59x18 inv_x = inv(x); + SD59x18 div_1_x = div(ONE_FP, x); + + assertEq(inv_x, div_1_x); + } + + // Test the anticommutativity of the division + // x / y == 1 / (y / x) + function inv_test_division_noncommutativity( + SD59x18 x, + SD59x18 y + ) public { + require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); + + SD59x18 x_y = div(x, y); + SD59x18 y_x = div(y, x); + + assertEqWithinTolerance(x_y, inv(y_x), ONE_TENTH_FP, "0.1%"); + } + + // Test the multiplication of inverses + // 1/(x * y) == 1/x * 1/y + function inv_test_multiplication(SD59x18 x, SD59x18 y) public { + require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); + + SD59x18 inv_x = inv(x); + SD59x18 inv_y = inv(y); + SD59x18 inv_x_times_inv_y = mul(inv_x, inv_y); + + SD59x18 x_y = mul(x, y); + SD59x18 inv_x_y = inv(x_y); + + // The maximum loss of precision is given by the formula: + // 2 * | log2(x) - log2(y) | + 1 + uint256 loss = 2 * significant_digits_lost_in_mult(x, y) + 1; + + assertEqWithinDecimalPrecision(inv_x_y, inv_x_times_inv_y, loss); + } + + // Test identity property + function inv_test_identity(SD59x18 x) public { + require(x.neq(ZERO_FP)); + + SD59x18 inv_x = inv(x); + SD59x18 identity = mul(inv_x, x); + + // They should agree with a tolerance of one tenth of a percent + assertEqWithinTolerance(identity, ONE_FP, ONE_TENTH_FP, "0.1%"); + } + + // Test that the absolute value of the result is in range zero-one + // if x is greater than one, else, the absolute value of the result + // must be greater than one + function inv_test_values(SD59x18 x) public { + require(x.neq(ZERO_FP)); + + SD59x18 abs_inv_x = abs(inv(x)); + + if (abs(x).gte(ONE_FP)) { + assertLte(abs_inv_x, ONE_FP); + } else { + assertGt(abs_inv_x, ONE_FP); + } + } + + // Test that the result has the same sign as the argument. + // Since inv() rounds towards zero, we are checking the zero case as well + function inv_test_sign(SD59x18 x) public { + require(x.neq(ZERO_FP)); + + SD59x18 inv_x = inv(x); + + if (x.gt(ZERO_FP)) { + assertGte(inv_x, ZERO_FP); + } else { + assertLte(inv_x, ZERO_FP); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test the zero-case, should revert + function inv_test_zero() public { + try this.helpersInv(ZERO_FP) { + // Unexpected, the function must revert + assert(false); + } catch {} + } + + // Test the maximum value case, should not revert, and be close to zero + function inv_test_maximum() public { + SD59x18 inv_maximum; + + try this.helpersInv(MAX_SD59x18) { + inv_maximum = this.helpersInv(MAX_SD59x18); + assertEqWithinBitPrecision(inv_maximum, ZERO_FP, 10); + } catch { + // Unexpected, the function must not revert + assert(false); + } + } + + // Test the minimum value case, should not revert, and be close to zero + function inv_test_minimum() public { + SD59x18 inv_minimum; + + try this.helpersInv(MIN_SD59x18) { + inv_minimum = this.helpersInv(MIN_SD59x18); + assertEqWithinBitPrecision(abs(inv_minimum), ZERO_FP, 10); + } catch { + // Unexpected, the function must not revert + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION avg() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test that the result is between the two operands + // avg(x, y) >= min(x, y) && avg(x, y) <= max(x, y) + function avg_test_values_in_range(SD59x18 x, SD59x18 y) public { + SD59x18 avg_xy = avg(x, y); + + if (x.gte(y)) { + assertGte(avg_xy, y); + assertLte(avg_xy, x); + } else { + assertGte(avg_xy, x); + assertLte(avg_xy, y); + } + } + + // Test that the average of the same number is itself + // avg(x, x) == x + function avg_test_one_value(SD59x18 x) public { + SD59x18 avg_x = avg(x, x); + + assertEq(avg_x, x); + } + + // Test that the order of operands is irrelevant + // avg(x, y) == avg(y, x) + function avg_test_operand_order(SD59x18 x, SD59x18 y) public { + SD59x18 avg_xy = avg(x, y); + SD59x18 avg_yx = avg(y, x); + + assertEq(avg_xy, avg_yx); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for the maximum value + function avg_test_maximum() public { + SD59x18 result; + + // This may revert due to overflow depending on implementation + // If it doesn't revert, the result must be MAX_SD59x18 + try this.helpersAvg(MAX_SD59x18, MAX_SD59x18) { + result = this.helpersAvg(MAX_SD59x18, MAX_SD59x18); + assertEq(result, MAX_SD59x18); + } catch { + assert(false); + } + } + + // Test for the minimum value + function avg_test_minimum() public { + SD59x18 result; + + // This may revert due to overflow depending on implementation + // If it doesn't revert, the result must be MIN_SD59x18 + try this.helpersAvg(MIN_SD59x18, MIN_SD59x18) { + result = this.helpersAvg(MIN_SD59x18, MIN_SD59x18); + assertEq(result, MIN_SD59x18); + } catch { + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION pow() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for zero exponent + // x ** 0 == 1 + function pow_test_zero_exponent(SD59x18 x) public { + require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); + SD59x18 x_pow_0 = pow(x, ZERO_FP); + + assertEq(x_pow_0, ONE_FP); + } + + // Test for zero base + // 0 ** a == 0 (for positive a) + function pow_test_zero_base_non_zero_exponent(SD59x18 a) public { + require(a.gt(ZERO_FP)); + SD59x18 zero_pow_a = pow(ZERO_FP, a); + + assertEq(zero_pow_a, ZERO_FP); + } + + // Test for zero base + // 0 ** 0 == 1 + function pow_test_zero_base_zero_exponent() public { + SD59x18 zero_pow_a = pow(ZERO_FP, ZERO_FP); + + assertEq(zero_pow_a, ONE_FP); + } + + // Test for exponent one + // x ** 1 == x + function pow_test_one_exponent(SD59x18 x) public { + require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); + SD59x18 x_pow_1 = pow(x, ONE_FP); + + assertEq(x_pow_1, x); + } + + // Test for base one + // 1 ** a == 1 + function pow_test_base_one(SD59x18 a) public { + SD59x18 one_pow_a = pow(ONE_FP, a); + + assertEq(one_pow_a, ONE_FP); + } + + + // Test for product of powers of the same base + // x ** a * x ** b == x ** (a + b) + function pow_test_product_same_base( + SD59x18 x, + SD59x18 a, + SD59x18 b + ) public { + require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); + require(a.gte(MIN_PERMITTED_EXP2) && b.gte(MIN_PERMITTED_EXP2)); + + SD59x18 x_a = pow(x, a); + SD59x18 x_b = pow(x, b); + SD59x18 x_ab = pow(x, a.add(b)); + + assertEqWithinDecimalPrecision(mul(x_a, x_b), x_ab, 9); + } + + // Test for power of an exponentiation + // (x ** a) ** b == x ** (a * b) + function pow_test_power_of_an_exponentiation( + SD59x18 x, + SD59x18 a, + SD59x18 b + ) public { + require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); + require(a.mul(b).neq(ZERO_FP)); + require(a.gte(MIN_PERMITTED_EXP2) && b.gte(MIN_PERMITTED_EXP2)); + + SD59x18 x_a = pow(x, a); + SD59x18 x_a_b = pow(x_a, b); + SD59x18 x_ab = pow(x, a.mul(b)); + + assertEqWithinDecimalPrecision(x_a_b, x_ab, 9); + } + + // Test for power of a product + // (x * y) ** a == x ** a * y ** a + function pow_test_product_power( + SD59x18 x, + SD59x18 y, + SD59x18 a + ) public { + require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); + require(y.gte(ZERO_FP) && y.lte(MAX_PERMITTED_POW)); + + require(a.gt(convert(2 ** 32))); // to avoid massive loss of precision + + SD59x18 x_y = mul(x, y); + SD59x18 xy_a = pow(x_y, a); + + SD59x18 x_a = pow(x, a); + SD59x18 y_a = pow(y, a); + + assertEqWithinBitPrecision(mul(x_a, y_a), xy_a, 10); + } + + // Test for result being greater than or lower than the argument, depending on + // its absolute value and the value of the exponent + function pow_test_positive_exponent(SD59x18 x, SD59x18 a) public { + require(x.gte(ZERO_FP) && a.gte(ZERO_FP)); + + SD59x18 x_a = pow(x, a); + + if (abs(x).gte(ONE_FP)) { + assertGte(abs(x_a), ONE_FP); + } + + if (abs(x).lte(ONE_FP)) { + assertLte(abs(x_a), ONE_FP); + } + } + + // Test for result being greater than or lower than the argument, depending on + // its absolute value and the value of the exponent + function pow_test_negative_exponent(SD59x18 x, SD59x18 a) public { + require(x.gte(ZERO_FP) && a.lte(ZERO_FP)); + + SD59x18 x_a = pow(x, a); + + if (abs(x).gte(ONE_FP)) { + assertLte(abs(x_a), ONE_FP); + } + + if (abs(x).lte(ONE_FP)) { + assertGte(abs(x_a), ONE_FP); + } + } + + // Test for result sign: if the exponent is even, sign is positive + // if the exponent is odd, preserves the sign of the base + function pow_test_sign(SD59x18 x, SD59x18 a) public { + require(x.gte(ZERO_FP)); + + SD59x18 x_a = pow(x, a); + + // This prevents the case where a small negative number gets + // rounded down to zero and thus changes sign + require(x_a.neq(ZERO_FP)); + + // If the exponent is even + if (a.mod(convert(2)).eq(ZERO_FP)) { + assertEq(x_a, abs(x_a)); + } else { + // x_a preserves x sign + if (x.lt(ZERO_FP)) { + assertLt(x_a, ZERO_FP); + } else { + assertGt(x_a, ZERO_FP); + } + } + } + + // pow(2, a) == exp2(a) + function pow_test_exp2_equivalence(SD59x18 a) public { + SD59x18 pow_result = pow(convert(2), a); + SD59x18 exp2_result = exp2(a); + + assertEq(pow_result, exp2_result); + } + + // Power is strictly increasing + // x > y && a >= 0 --> pow(x, a) >= pow(y, a) + function pow_test_strictly_increasing(SD59x18 x, SD59x18 y, SD59x18 a) public { + require(x.gt(y) && x.lte(MAX_PERMITTED_POW)); + require(a.gte(ZERO_FP)); + + SD59x18 x_a = pow(x, a); + SD59x18 y_a = pow(y, a); + + assertGte(x_a, y_a); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for maximum base and exponent > 1 + function pow_test_maximum_base(SD59x18 a) public { + require(a.gt(ONE_FP)); + + try this.helpersPow(MAX_SD59x18, a) { + // Unexpected, should revert because of overflow + assert(false); + } catch { + // Expected revert + } + } + + // Test for abs(base) < 1 and high exponent + function pow_test_high_exponent(SD59x18 x, SD59x18 a) public { + require(abs(x).lt(ONE_FP) && a.gt(convert(2 ** 32))); + + SD59x18 result = pow(x, a); + + assertEq(result, ZERO_FP); + } + + /* ================================================================ + + TESTS FOR FUNCTION sqrt() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for the inverse operation + // sqrt(x) * sqrt(x) == x + function sqrt_test_inverse_mul(SD59x18 x) public { + require(x.gte(ZERO_FP)); + + SD59x18 sqrt_x = sqrt(x); + SD59x18 sqrt_x_squared = mul(sqrt_x, sqrt_x); + + // Precision loss is at most half the bits of the operand + assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); + } + + // Test for the inverse operation + // sqrt(x) ** 2 == x + function sqrt_test_inverse_pow(SD59x18 x) public { + require(x.gte(ZERO_FP)); + + SD59x18 sqrt_x = sqrt(x); + SD59x18 sqrt_x_squared = pow(sqrt_x, convert(2)); + + // Precision loss is at most half the bits of the operand + assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); + } + + // Test for distributive property respect to the multiplication + // sqrt(x) * sqrt(y) == sqrt(x * y) + function sqrt_test_distributive(SD59x18 x, SD59x18 y) public { + require(x.gte(ZERO_FP) && y.gte(ZERO_FP)); + + SD59x18 sqrt_x = sqrt(x); + SD59x18 sqrt_y = sqrt(y); + SD59x18 sqrt_x_sqrt_y = mul(sqrt_x, sqrt_y); + SD59x18 sqrt_xy = sqrt(mul(x, y)); + + // Ensure we have enough significant digits for the result to be meaningful + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + require( + significant_digits_after_mult(sqrt_x, sqrt_y) > + REQUIRED_SIGNIFICANT_DIGITS + ); + + // Allow an error of up to one tenth of a percent + assertEqWithinTolerance(sqrt_x_sqrt_y, sqrt_xy, ONE_TENTH_FP, "0.1%"); + } + + // Test that sqrt is strictly increasing + // x >= 0 && y > x --> sqrt(y) >= sqrt(x) + function sqrt_test_strictly_increasing(SD59x18 x, SD59x18 y) public { + require(x.gte(ZERO_FP) && x.lte(MAX_SQRT.sub(SD59x18.wrap(1)))); + require(y.gt(x) && y.lte(MAX_SQRT)); + + SD59x18 sqrt_x = sqrt(x); + SD59x18 sqrt_y = sqrt(y); + + assertGte(sqrt_y, sqrt_x); + } + + // Square root of perfect square should be equal to x + // sqrt(x * x) == x + function sqrt_test_square(SD59x18 x) public { + require(x.gt(ONE_FP)); + + SD59x18 square_x = x.mul(x); + SD59x18 sqrt_square_x = sqrt(square_x); + + assertEq(sqrt_square_x, x); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + + // Test for zero case + function sqrt_test_zero() public { + assertEq(sqrt(ZERO_FP), ZERO_FP); + } + + // Test for maximum value + function sqrt_test_maximum() public { + try this.helpersSqrt(MAX_SQRT) { + // Expected behaviour, MAX_SQRT is positive, and operation + // should not revert as the result is in range + } catch { + // Unexpected, should not revert + assert(false); + } + } + + // Test for minimum value + function sqrt_test_minimum() public { + try this.helpersSqrt(MIN_SD59x18) { + // Unexpected, should revert. MIN_SD59x18 is negative. + assert(false); + } catch { + // Expected behaviour, revert + } + } + + // Test for negative operands + function sqrt_test_negative(SD59x18 x) public { + require(x.lt(ZERO_FP)); + + try this.helpersSqrt(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected behaviour, revert + } + } + + /* ================================================================ + + TESTS FOR FUNCTION log2() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for distributive property respect to multiplication + // log2(x * y) = log2(x) + log2(y) + function log2_test_distributive_mul(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP) && y.gt(ZERO_FP)); + SD59x18 log2_x = log2(x); + SD59x18 log2_y = log2(y); + SD59x18 log2_x_log2_y = add(log2_x, log2_y); + + SD59x18 xy = mul(x, y); + SD59x18 log2_xy = log2(xy); + + // Ensure we have enough significant digits for the result to be meaningful + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + + // The maximum loss of precision is given by the formula: + // | log2(x) + log2(y) | + uint256 loss = significant_digits_lost_in_mult(x, y) + 2; + + assertEqWithinDecimalPrecision(log2_x_log2_y, log2_xy, loss); + } + + // Test for logarithm of a power + // log2(x ** y) = y * log2(x) + function log2_test_power(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP)); + SD59x18 x_y = pow(x, y); + require(x_y.gt(ZERO_FP)); + SD59x18 log2_x_y = log2(x_y); + SD59x18 y_log2_x = mul(log2(x), y); + + assertEqWithinTolerance(y_log2_x, log2_x_y, ONE_TENTH_FP, "0.1%"); + } + + // Base 2 logarithm is strictly increasing + // y > 0 && x > y --> log2(x) > log2(y) + function log2_test_strictly_increasing(SD59x18 x, SD59x18 y) public { + require(y.gt(ZERO_FP) && x.gt(y)); + + SD59x18 log2_x = log2(x); + SD59x18 log2_y = log2(y); + + assertGte(log2_x, log2_y); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case, should revert + function log2_test_zero() public { + try this.helpersLog2(ZERO_FP) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, log(0) is not defined + } + } + + // Test for maximum value case, should return a positive number + function log2_test_maximum() public { + SD59x18 result; + + try this.helpersLog2(MAX_SD59x18) { + // Expected, should not revert and the result must be > 0 + result = this.helpersLog2(MAX_SD59x18); + assertGt(result, ZERO_FP); + } catch { + // Unexpected + assert(false); + } + } + + // Test for negative values, should revert as log2 is not defined + function log2_test_negative(SD59x18 x) public { + require(x.lt(ZERO_FP)); + + try this.helpersLog2(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected + } + } + + /* ================================================================ + + TESTS FOR FUNCTION ln() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for distributive property respect to multiplication + // ln(x * y) = ln(x) + ln(y) + function ln_test_distributive_mul(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP) && y.gt(ZERO_FP)); + + SD59x18 ln_x = ln(x); + SD59x18 ln_y = ln(y); + SD59x18 ln_x_ln_y = add(ln_x, ln_y); + + SD59x18 xy = mul(x, y); + SD59x18 ln_xy = ln(xy); + + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + + // The maximum loss of precision is given by the formula: + // | log2(x) + log2(y) | + uint256 loss = significant_digits_lost_in_mult(x, y) + 2; + + assertEqWithinDecimalPrecision(ln_x_ln_y, ln_xy, loss); + } + + // Test for logarithm of a power + // ln(x ** y) = y * ln(x) + function ln_test_power(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP)); + SD59x18 x_y = pow(x, y); + SD59x18 ln_x_y = ln(x_y); + + SD59x18 y_ln_x = mul(ln(x), y); + + require(significant_digits_after_mult(ln(x), y) > REQUIRED_SIGNIFICANT_DIGITS); + + assertEqWithinTolerance(ln_x_y, y_ln_x, ONE_TENTH_FP, "0.1%"); + } + + // Natural logarithm is strictly increasing + // y > 0 && x > y --> log2(x) > log2(y) + function ln_test_strictly_increasing(SD59x18 x, SD59x18 y) public { + require(y.gt(ZERO_FP) && x.gt(y)); + + SD59x18 log2_x = ln(x); + SD59x18 log2_y = ln(y); + + assertGte(log2_x, log2_y); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case, should revert + function ln_test_zero() public { + try this.helpersLn(ZERO_FP) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, log(0) is not defined + } + } + + // Test for maximum value case, should return a positive number + function ln_test_maximum() public { + SD59x18 result; + + try this.helpersLn(MAX_SD59x18) { + // Expected, should not revert and the result must be > 0 + result = this.helpersLn(MAX_SD59x18); + assertGt(result, ZERO_FP); + } catch { + // Unexpected + assert(false); + } + } + + // Test for negative values, should revert as ln is not defined + function ln_test_negative(SD59x18 x) public { + require(x.lt(ZERO_FP)); + + try this.helpersLn(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected + } + } + + /* ================================================================ + + TESTS FOR FUNCTION exp2() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for equality with pow(2, x) for integer x + // pow(2, x) == exp2(x) + function exp2_test_equivalence_pow(SD59x18 x) public { + require(x.lte(MAX_PERMITTED_EXP2)); + SD59x18 exp2_x = exp2(x); + SD59x18 pow_2_x = pow(TWO_FP, x); + + assertEq(exp2_x, pow_2_x); + } + + // Test for inverse function + // If y = log2(x) then exp2(y) == x + function exp2_test_inverse(SD59x18 x) public { + require(x.lte(MAX_PERMITTED_EXP2) && x.gt(ZERO_FP)); + SD59x18 log2_x = log2(x); + SD59x18 exp2_x = exp2(log2_x); + + assertEqWithinDecimalPrecision(x, exp2_x, 9); + } + + // Test for negative exponent + // exp2(-x) == inv( exp2(x) ) + function exp2_test_negative_exponent(SD59x18 x) public { + require(x.lt(ZERO_FP) && x.neq(MIN_SD59x18)); + + SD59x18 exp2_x = exp2(x); + SD59x18 exp2_minus_x = exp2(neg(x)); + + assertEqWithinDecimalPrecision(exp2_x, inv(exp2_minus_x), 2); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case + // exp2(0) == 1 + function exp2_test_zero() public { + SD59x18 exp_zero = exp2(ZERO_FP); + assertEq(exp_zero, ONE_FP); + } + + // Test for maximum value. This should overflow as it won't fit + // in the data type + function exp2_test_maximum() public { + try this.helpersExp2(MAX_SD59x18) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert + } + } + + // Test for maximum value. This should overflow as it won't fit + // in the data type + function exp2_test_maximum_permitted() public { + try this.helpersExp2(MAX_PERMITTED_EXP2) { + // Should always pass + } catch { + // Should never revert + assert(false); + } + } + + // Test for minimum value. This should return zero since + // 2 ** -x == 1 / 2 ** x that tends to zero as x increases + function exp2_test_minimum() public { + SD59x18 result; + + try this.helpersExp2(MIN_SD59x18) { + // Expected, should not revert, check that value is zero + result = exp2(MIN_SD59x18); + assertEq(result, ZERO_FP); + } catch { + // Unexpected revert + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION exp() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for inverse function + // If y = ln(x) then exp(y) == x + function exp_test_inverse(SD59x18 x) public { + require(x.lte(MAX_PERMITTED_EXP)); + SD59x18 ln_x = ln(x); + SD59x18 exp_x = exp(ln_x); + SD59x18 log10_x = log10(x); + + assertEqWithinDecimalPrecision(x, exp_x, 9); + } + + // Test for negative exponent + // exp(-x) == inv( exp(x) ) + function exp_test_negative_exponent(SD59x18 x) public { + require(x.lt(ZERO_FP) && x.neq(MIN_SD59x18)); + + SD59x18 exp_x = exp(x); + SD59x18 exp_minus_x = exp(neg(x)); + + // Result should be within 4 bits precision for the worst case + assertEqWithinBitPrecision(exp_x, inv(exp_minus_x), 4); + } + + // Test that exp strictly increases + function exp_test_strictly_increasing(SD59x18 x, SD59x18 y) public { + require(x.lte(MAX_PERMITTED_EXP)); + require(y.gt(x) && y.lte(MAX_PERMITTED_EXP)); + + SD59x18 exp_x = exp(x); + SD59x18 exp_y = exp(y); + + assertGte(exp_y, exp_x); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case + // exp(0) == 1 + function exp_test_zero() public { + SD59x18 exp_zero = exp(ZERO_FP); + assertEq(exp_zero, ONE_FP); + } + + // Test for maximum value. This should overflow as it won't fit + // in the data type + function exp_test_maximum() public { + try this.helpersExp(MAX_SD59x18) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert + } + } + + // Test for maximum value. This should overflow as it won't fit + // in the data type + function exp_test_maximum_permitted() public { + try this.helpersExp(MAX_PERMITTED_EXP) { + // Expected to always succeed + } catch { + // Unexpected, should revert + assert(false); + } + } + + // Test for minimum value. This should return zero since + // e ** -x == 1 / e ** x that tends to zero as x increases + function exp_test_minimum() public { + SD59x18 result; + + try this.helpersExp(MIN_SD59x18) { + // Expected, should not revert, check that value is zero + result = exp(MIN_SD59x18); + assertEq(result, ZERO_FP); + } catch { + // Unexpected revert + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION powu() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for zero exponent + // x ** 0 == 1 + function powu_test_zero_exponent(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + require(x.lte(MAX_SD59x18)); + SD59x18 x_pow_0 = powu(x, 0); + + assertEq(x_pow_0, ONE_FP); + } + + // Test for zero base + // 0 ** y == 0 (for positive y) + function powu_test_zero_base(uint256 y) public { + require(y != 0); + + SD59x18 zero_pow_y = powu(ZERO_FP, y); + + assertEq(zero_pow_y, ZERO_FP); + } + + // Test for exponent one + // x ** 1 == x + function powu_test_one_exponent(SD59x18 x) public { + require(x.gt(MIN_SD59x18)); + require(x.lte(MAX_SD59x18)); + SD59x18 x_pow_1 = powu(x, 1); + + assertEq(x_pow_1, x); + } + + // Test for base one + // 1 ** x == 1 + function powu_test_base_one(uint256 y) public { + SD59x18 one_pow_y = powu(ONE_FP, y); + + assertEq(one_pow_y, ONE_FP); + } + + // Test for product of powers of the same base + // x ** a * x ** b == x ** (a + b) + function powu_test_product_same_base( + SD59x18 x, + uint256 a, + uint256 b + ) public { + require(x.gt(MIN_SD59x18)); + require(x.lte(MAX_SD59x18)); + + SD59x18 x_a = powu(x, a); + SD59x18 x_b = powu(x, b); + SD59x18 x_ab = powu(x, a + b); + + assertEqWithinBitPrecision(mul(x_a, x_b), x_ab, 10); + } + + // Test for power of an exponentiation + // (x ** a) ** b == x ** (a * b) + function powu_test_power_of_an_exponentiation( + SD59x18 x, + uint256 a, + uint256 b + ) public { + require(x.gt(MIN_SD59x18)); + require(x.lte(MAX_SD59x18)); + + SD59x18 x_a = powu(x, a); + SD59x18 x_a_b = powu(x_a, b); + SD59x18 x_ab = powu(x, a * b); + + assertEqWithinBitPrecision(x_a_b, x_ab, 10); + } + + // Test for power of a product + // (x * y) ** a == x ** a * y ** a + function powu_test_product_power( + SD59x18 x, + SD59x18 y, + uint256 a + ) public { + require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); + require(x.lte(MAX_SD59x18) && y.lte(MAX_SD59x18)); + + require(a > 2 ** 32); // to avoid massive loss of precision + + SD59x18 x_y = mul(x, y); + SD59x18 xy_a = powu(x_y, a); + + SD59x18 x_a = powu(x, a); + SD59x18 y_a = powu(y, a); + + assertEqWithinBitPrecision(mul(x_a, y_a), xy_a, 10); + } + + // Test for result being greater than or lower than the argument, depending on + // its absolute value and the value of the exponent + function powu_test_values(SD59x18 x, uint256 a) public { + require(x.neq(ZERO_FP)); + require(x.neq(MIN_SD59x18)); + + SD59x18 x_a = powu(x, a); + + if (abs(x).gte(ONE_FP)) { + assertGte(abs(x_a), ONE_FP); + } + + if (abs(x).lte(ONE_FP)) { + assertLte(abs(x_a), ONE_FP); + } + } + + // Test for result sign: if the exponent is even, sign is positive + // if the exponent is odd, preserves the sign of the base + function powu_test_sign(SD59x18 x, uint256 a) public { + require(x.neq(ZERO_FP) && a != 0); + + SD59x18 x_a = powu(x, a); + + // This prevents the case where a small negative number gets + // rounded down to zero and thus changes sign + require(x_a.neq(ZERO_FP)); + + // If the exponent is even + if (a % 2 == 0) { + assertEq(x_a, abs(x_a)); + } else { + // x_a preserves x sign + if (x.lt(ZERO_FP)) { + assertLt(x_a, ZERO_FP); + } else { + assertGt(x_a, ZERO_FP); + } + } + } + + // Unsigned power is strictly increasing + // y > MIN && x > y && a > 0 && y != 0 --> powu(x, a) >= powu(y, a) + function powu_test_strictly_increasing( + SD59x18 x, + SD59x18 y, + uint256 a + ) public { + require(x.gt(y) && y.gt(MIN_SD59x18) && y.neq(ZERO_FP)); + require(x.lte(MAX_SD59x18)); + require(a > 0); + + SD59x18 x_a = powu(x, a); + SD59x18 y_a = powu(y, a); + + assertGte(x_a, y_a); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for maximum base and exponent > 1 + function powu_test_maximum_base(uint256 a) public { + require(a > 1); + + try this.helpersPowu(MAX_SD59x18, a) { + // Unexpected, should revert because of overflow + assert(false); + } catch { + // Expected revert + } + } + + // Test for abs(base) < 1 and high exponent + function powu_test_high_exponent(SD59x18 x, uint256 a) public { + require(abs(x).lt(ONE_FP) && a > 2 ** 32); + + SD59x18 result = powu(x, a); + + assertEq(result, ZERO_FP); + } + + /* ================================================================ + + TESTS FOR FUNCTION log10() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for distributive property respect to multiplication + // log10(x * y) = log10(x) + log10(y) + function log10_test_distributive_mul(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP) && y.gt(ZERO_FP)); + SD59x18 log10_x = log10(x); + SD59x18 log10_y = log10(y); + SD59x18 log10_x_log10_y = add(log10_x, log10_y); + + SD59x18 xy = mul(x, y); + SD59x18 log10_xy = log10(xy); + + // Ensure we have enough significant digits for the result to be meaningful + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + + // The maximum loss of precision is given by the formula: + // | log10(x) + log10(y) | + uint256 loss = significant_digits_lost_in_mult(x, y) + 2; + + assertEqWithinDecimalPrecision(log10_x_log10_y, log10_xy, loss); + } + + // Test for logarithm of a power + // log10(x ** y) = y * log10(x) + function log10_test_power(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP) && y.gt(ZERO_FP)); + SD59x18 x_y = pow(x, y); + SD59x18 log10_x_y = log10(x_y); + SD59x18 y_log10_x = mul(log10(x), y); + + assertEqWithinTolerance(log10_x_y, y_log10_x, ONE_TENTH_FP, "0.1%"); + } + + // Base 10 logarithm is strictly increasing + // x > y && y > 0 --> log10(x) > log10(y) + function log10_is_increasing(SD59x18 x, SD59x18 y) public { + require(y.gt(ZERO_FP) && x.gt(y)); + + SD59x18 log2_x = log10(x); + SD59x18 log2_y = log10(y); + + assertGt(log2_x, log2_y); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case, should revert + function log10_test_zero() public { + try this.helpersLog10(ZERO_FP) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, log(0) is not defined + } + } + + // Test for maximum value case, should return a positive number + function log10_test_maximum() public { + SD59x18 result; + + try this.helpersLog10(MAX_SD59x18) { + // Expected, should not revert and the result must be > 0 + result = this.helpersLog10(MAX_SD59x18); + assertGt(result, ZERO_FP); + } catch { + // Unexpected + assert(false); + } + } + + // Test for negative values, should revert as log10 is not defined + function log10_test_negative(SD59x18 x) public { + require(x.lt(ZERO_FP)); + + try this.helpersLog10(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected + } + } + + /* ================================================================ + + TESTS FOR FUNCTION gm() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // The product of the values should be equal to the geometric mean + // raised to the power of N (numbers in the set) + function gm_test_product(SD59x18 x, SD59x18 y) public { + bool x_sign = x.gt(ZERO_FP); + bool y_sign = y.gt(ZERO_FP); + require(x_sign = y_sign); + + SD59x18 x_mul_y = x.mul(y); + SD59x18 gm_squared = pow(gm(x,y), TWO_FP); + + assertEqWithinTolerance(x_mul_y, gm_squared, ONE_TENTH_FP, "0.1%"); + } + + // The geometric mean for a set of positive numbers is less than the + // arithmetic mean of that set, as long as the values of the set are not equal + function gm_test_positive_set_avg(SD59x18 x, SD59x18 y) public { + require(x.gte(ZERO_FP) && y.gte(ZERO_FP) && x.neq(y)); + + SD59x18 gm_x_y = gm(x, y); + SD59x18 avg_x_y = avg(x, y); + + assertLte(gm_x_y, avg_x_y); + } + + // The geometric mean of a set of positive equal numbers should be + // equal to the arithmetic mean + function gm_test_positive_equal_set_avg(SD59x18 x) public { + require(x.gte(ZERO_FP)); + + SD59x18 gm_x = gm(x, x); + SD59x18 avg_x = avg(x, x); + + assertEq(gm_x, avg_x); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case, should return 0 + function gm_test_zero(SD59x18 x) public { + require(x.gte(ZERO_FP)); + + try this.helpersGm(x, ZERO_FP) { + SD59x18 result = gm(x, ZERO_FP); + assertEq(result, ZERO_FP); + } catch { + // Unexpected, should not revert + assert(false); + } + } + + // Test for single negative input + function gm_test_negative(SD59x18 x, SD59x18 y) public { + require(x.gt(ZERO_FP) && y.lt(ZERO_FP)); + + try this.helpersGm(x, y) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, gm of a negative product is not defined + } + } + +} \ No newline at end of file diff --git a/contracts/Math/PRBMath/v4/PRBMathUD60x18PropertyTests.sol b/contracts/Math/PRBMath/v4/PRBMathUD60x18PropertyTests.sol new file mode 100644 index 0000000..41f7385 --- /dev/null +++ b/contracts/Math/PRBMath/v4/PRBMathUD60x18PropertyTests.sol @@ -0,0 +1,1635 @@ +pragma solidity ^0.8.19; + +import { UD60x18 } from "@prb/math/UD60x18.sol"; +import {add, sub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb/math/ud60x18/Helpers.sol"; +import {convert} from "@prb/math/ud60x18/Conversions.sol"; +import {msb} from "@prb/math/Common.sol"; +import {intoUint128, intoUint256} from "@prb/math/ud60x18/Casting.sol"; +import {mul, div, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, powu, gm} from "@prb/math/ud60x18/Math.sol"; +import "./utils/AssertionHelperUD.sol"; + +contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { + + /* ================================================================ + 59x18 fixed-point constants used for testing specific values. + This assumes that PRBMath library's convert(x) works as expected. + ================================================================ */ + UD60x18 internal ZERO_FP = convert(0); + UD60x18 internal ONE_FP = convert(1); + UD60x18 internal TWO_FP = convert(2); + UD60x18 internal THREE_FP = convert(3); + UD60x18 internal EIGHT_FP = convert(8); + UD60x18 internal THOUSAND_FP = convert(1000); + UD60x18 internal EPSILON = UD60x18.wrap(1); + UD60x18 internal ONE_TENTH_FP = convert(1).div(convert(10)); + + /* ================================================================ + Constants used for precision loss calculations + ================================================================ */ + uint256 internal REQUIRED_SIGNIFICANT_DIGITS = 10; + + /* ================================================================ + Integer representations maximum values. + These constants are used for testing edge cases or limits for + possible values. + ================================================================ */ + /// @dev Euler's number as an UD60x18 number. + UD60x18 constant E = UD60x18.wrap(2_718281828459045235); + + /// @dev Half the UNIT number. + uint256 constant uHALF_UNIT = 0.5e18; + UD60x18 constant HALF_UNIT = UD60x18.wrap(uHALF_UNIT); + + /// @dev log2(10) as an UD60x18 number. + uint256 constant uLOG2_10 = 3_321928094887362347; + UD60x18 constant LOG2_10 = UD60x18.wrap(uLOG2_10); + + /// @dev log2(e) as an UD60x18 number. + uint256 constant uLOG2_E = 1_442695040888963407; + UD60x18 constant LOG2_E = UD60x18.wrap(uLOG2_E); + + /// @dev The maximum value an UD60x18 number can have. + uint256 constant uMAX_UD60x18 = 115792089237316195423570985008687907853269984665640564039457_584007913129639935; + UD60x18 constant MAX_UD60x18 = UD60x18.wrap(uMAX_UD60x18); + + /// @dev The maximum whole value an UD60x18 number can have. + uint256 constant uMAX_WHOLE_UD60x18 = 115792089237316195423570985008687907853269984665640564039457_000000000000000000; + UD60x18 constant MAX_WHOLE_UD60x18 = UD60x18.wrap(uMAX_WHOLE_UD60x18); + + /// @dev PI as an UD60x18 number. + UD60x18 constant PI = UD60x18.wrap(3_141592653589793238); + + /// @dev The unit amount that implies how many trailing decimals can be represented. + uint256 constant uUNIT = 1e18; + UD60x18 constant UNIT = UD60x18.wrap(uUNIT); + + /// @dev Zero as an UD60x18 number. + UD60x18 constant ZERO = UD60x18.wrap(0); + + UD60x18 internal constant MAX_PERMITTED_EXP2 = UD60x18.wrap(192e18 - 1); + UD60x18 internal constant MAX_PERMITTED_EXP = UD60x18.wrap(133_084258667509499440); + UD60x18 internal constant MAX_PERMITTED_POW = UD60x18.wrap(2 ** 192 * 10 ** 18 - 1); + UD60x18 internal constant MAX_PERMITTED_SQRT = UD60x18.wrap(115792089237316195423570985008687907853269_984665640564039457); + + /* ================================================================ + Events used for debugging or showing information. + ================================================================ */ + event Value(string reason, UD60x18 val); + event LogErr(bytes error); + + /* ================================================================ + Library wrappers. + These functions allow calling the PRBMathUD60x18 library. + ================================================================ */ + function debug(string calldata x, UD60x18 y) public { + emit Value(x, y); + } + + // Wrapper for external try/catch calls + function helpersAdd(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { + return add(x,y); + } + + // Wrapper for external try/catch calls + function helpersSub(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { + return sub(x,y); + } + + // Wrapper for external try/catch calls + function helpersMul(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { + return mul(x,y); + } + + function helpersDiv(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { + return div(x,y); + } + + function helpersLn(UD60x18 x) public pure returns (UD60x18) { + return ln(x); + } + + function helpersExp(UD60x18 x) public pure returns (UD60x18) { + return exp(x); + } + + function helpersExp2(UD60x18 x) public pure returns (UD60x18) { + return exp2(x); + } + + function helpersLog2(UD60x18 x) public pure returns (UD60x18) { + return log2(x); + } + + function helpersSqrt(UD60x18 x) public pure returns (UD60x18) { + return sqrt(x); + } + + function helpersPow(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { + return pow(x, y); + } + + function helpersPowu(UD60x18 x, uint256 y) public pure returns (UD60x18) { + return powu(x, y); + } + + function helpersAvg(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { + return avg(x, y); + } + + function helpersInv(UD60x18 x) public pure returns (UD60x18) { + return inv(x); + } + + function helpersLog10(UD60x18 x) public pure returns (UD60x18) { + return log10(x); + } + + function helpersFloor(UD60x18 x) public pure returns (UD60x18) { + return floor(x); + } + + function helpersGm(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { + return gm(x, y); + } + + /* ================================================================ + + TESTS FOR FUNCTION add() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for commutative property + // x + y == y + x + function add_test_commutative(UD60x18 x, UD60x18 y) public { + UD60x18 x_y = x.add(y); + UD60x18 y_x = y.add(x); + + assertEq(x_y, y_x); + } + + // Test for associative property + // (x + y) + z == x + (y + z) + function add_test_associative(UD60x18 x, UD60x18 y, UD60x18 z) public { + UD60x18 x_y = x.add(y); + UD60x18 y_z = y.add(z); + UD60x18 xy_z = x_y.add(z); + UD60x18 x_yz = x.add(y_z); + + assertEq(xy_z, x_yz); + } + + // Test for identity operation + // x + 0 == x (equivalent to x + (-x) == 0) + function add_test_identity(UD60x18 x) public { + UD60x18 x_0 = x.add(ZERO_FP); + + assertEq(x, x_0); + assertEq(x.sub(x), ZERO_FP); + } + + // Test that the result increases or decreases depending + // on the value to be added + function add_test_values(UD60x18 x, UD60x18 y) public { + UD60x18 x_y = x.add(y); + + assertGte(x_y, x); + } + + /* ================================================================ + Tests for overflow and edge cases. + These should make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // The result of the addition must be between the maximum + // and minimum allowed values for UD60x18 + function add_test_range(UD60x18 x, UD60x18 y) public { + try this.helpersAdd(x, y) returns (UD60x18 result) { + assert(result.lte(MAX_UD60x18) && result.gte(ZERO_FP)); + } catch { + // If it reverts, just ignore + } + } + + // Adding zero to the maximum value shouldn't revert, as it is valid + // Moreover, the result must be MAX_UD60x18 + function add_test_maximum_value() public { + try this.helpersAdd(MAX_UD60x18, ZERO_FP) returns (UD60x18 result) { + // Expected behaviour, does not revert + assertEq(result, MAX_UD60x18); + } catch { + assert(false); + } + } + + // Adding one to the maximum value should revert, as it is out of range + function add_test_maximum_value_plus_one() public { + try this.helpersAdd(MAX_UD60x18, ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + // Adding zero to the minimum value shouldn't revert, as it is valid + // Moreover, the result must be MIN_UD60x18 + function add_test_minimum_value() public { + try this.helpersAdd(ZERO_FP, ZERO_FP) returns (UD60x18 result) { + // Expected behaviour, does not revert + assertEq(result, ZERO_FP); + } catch { + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION sub() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for identity operation + // x - 0 == x (equivalent to x - x == 0) + function sub_test_identity(UD60x18 x) public { + UD60x18 x_0 = x.sub(ZERO_FP); + + assertEq(x_0, x); + assertEq(x.sub(x), ZERO_FP); + } + + // Test for neutrality over addition and subtraction + // (x - y) + y == (x + y) - y == x + function sub_test_neutrality(UD60x18 x, UD60x18 y) public { + UD60x18 x_minus_y = x.sub(y); + UD60x18 x_plus_y = x.add(y); + + UD60x18 x_minus_y_plus_y = x_minus_y.add(y); + UD60x18 x_plus_y_minus_y = x_plus_y.sub(y); + + assertEq(x_minus_y_plus_y, x_plus_y_minus_y); + assertEq(x_minus_y_plus_y, x); + } + + // Test that the result always decreases + function sub_test_values(UD60x18 x, UD60x18 y) public { + UD60x18 x_y = x.sub(y); + + assertLte(x_y, x); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // The result of the subtraction must be between the maximum + // and minimum allowed values for UD60x18 + function sub_test_range(UD60x18 x, UD60x18 y) public { + try this.helpersSub(x, y) returns (UD60x18 result) { + assert(result.lte(MAX_UD60x18) && result.gte(ZERO_FP)); + } catch { + // If it reverts, just ignore + } + } + + // Subtracting zero from the maximum value shouldn't revert, as it is valid + // Moreover, the result must be MAX_UD60x18 + function sub_test_maximum_value() public { + try this.helpersSub(MAX_UD60x18, ZERO_FP) returns (UD60x18 result) { + // Expected behaviour, does not revert + assertEq(result, MAX_UD60x18); + } catch { + assert(false); + } + } + + // Subtracting zero from the minimum value shouldn't revert, as it is valid + // Moreover, the result must be MIN_UD60x18 + function sub_test_minimum_value() public { + try this.helpersSub(ZERO_FP, ZERO_FP) returns (UD60x18 result) { + // Expected behaviour, does not revert + assertEq(result, ZERO_FP); + } catch { + assert(false); + } + } + + // Subtracting one from the minimum value should revert, as it is out of range + function sub_test_minimum_value_minus_one() public { + try this.helpersSub(ZERO_FP, ONE_FP) { + assert(false); + } catch { + // Expected behaviour, reverts + } + } + + /* ================================================================ + + TESTS FOR FUNCTION mul() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for commutative property + // x * y == y * x + function mul_test_commutative(UD60x18 x, UD60x18 y) public { + UD60x18 x_y = x.mul(y); + UD60x18 y_x = y.mul(x); + + assertEq(x_y, y_x); + } + + // Test for associative property + // (x * y) * z == x * (y * z) + function mul_test_associative(UD60x18 x, UD60x18 y, UD60x18 z) public { + UD60x18 x_y = x.mul(y); + UD60x18 y_z = y.mul(z); + UD60x18 xy_z = x_y.mul(z); + UD60x18 x_yz = x.mul(y_z); + + require(xy_z.neq(ZERO_FP) && x_yz.neq(ZERO_FP)); + assertEqWithinTolerance(xy_z, x_yz, ONE_TENTH_FP, "0.1%"); + } + + // Test for distributive property + // x * (y + z) == x * y + x * z + function mul_test_distributive(UD60x18 x, UD60x18 y, UD60x18 z) public { + UD60x18 y_plus_z = y.add(z); + UD60x18 x_times_y_plus_z = x.mul(y_plus_z); + + UD60x18 x_times_y = x.mul(y); + UD60x18 x_times_z = x.mul(z); + + assertEqWithinTolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP, "0.1%"); + } + + // Test for identity operation + // x * 1 == x (also check that x * 0 == 0) + function mul_test_identity(UD60x18 x) public { + UD60x18 x_1 = x.mul(ONE_FP); + UD60x18 x_0 = x.mul(ZERO_FP); + + assertEq(x_0, ZERO_FP); + assertEq(x_1, x); + } + + // Test that the result increases or decreases depending + // on the value to be added + function mul_test_values(UD60x18 x, UD60x18 y) public { + UD60x18 x_y = x.mul(y); + + if (y.gte(ONE_FP)) { + assertGte(x_y, x); + } else { + assertLte(x_y, x); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // The result of the multiplication must be between the maximum + // and minimum allowed values for UD60x18 + function mul_test_range(UD60x18 x, UD60x18 y) public { + try this.helpersMul(x, y) returns(UD60x18 result) { + assertLte(result, MAX_UD60x18); + } catch { + // If it reverts, just ignore + } + } + + // Multiplying the maximum value times one shouldn't revert, as it is valid + // Moreover, the result must be MAX_UD60x18 + function mul_test_maximum_value() public { + try this.helpersMul(MAX_UD60x18, ONE_FP) returns (UD60x18 result) { + // Expected behaviour, does not revert + assertEq(result, MAX_UD60x18); + } catch { + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION div() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for identity property + // x / 1 == x (equivalent to x / x == 1) + function div_test_division_identity_x_div_1(UD60x18 x) public { + UD60x18 div_1 = div(x, ONE_FP); + + assertEq(x, div_1); + } + + // Test for identity property + // x/x should not revert unless x == 0 + function div_test_division_identity_x_div_x(UD60x18 x) public { + UD60x18 div_x; + + try this.helpersDiv(x, x) { + // This should always equal one + div_x = div(x, x); + assertEq(div_x, ONE_FP); + } catch { + // Only valid case for revert is x == 0 + assertEq(x, ZERO_FP); + } + } + + // Test for division with 0 as numerator + // 0 / x = 0 + function div_test_division_num_zero(UD60x18 y) public { + require(y.neq(ZERO_FP)); + + UD60x18 div_0 = div(ZERO_FP, y); + + assertEq(ZERO_FP, div_0); + } + + // Test that the value of the result increases or + // decreases depending on the denominator's value + function div_test_values(UD60x18 x, UD60x18 y) public { + require(y.neq(ZERO_FP)); + + UD60x18 x_y = div(x, y); + + if (y.gte(ONE_FP)) { + assertLte(x_y, x); + } else { + assertGte(x_y, x); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for division by zero + function div_test_div_by_zero(UD60x18 x) public { + try this.helpersDiv(x, ZERO_FP) { + // Unexpected, this should revert + assert(false); + } catch { + // Expected revert + } + } + + // Test for division by a large value, the result should be less than one + function div_test_maximum_denominator(UD60x18 x) public { + UD60x18 div_large = div(x, MAX_UD60x18); + + assertLte(div_large, ONE_FP); + } + + // Test for division of a large value + // This should revert if |y| < 1 as it would return a value higher than max + function div_test_maximum_numerator(UD60x18 y) public { + require(y.neq(ZERO_FP)); + UD60x18 div_large; + + try this.helpersDiv(MAX_UD60x18, y) { + // If it didn't revert, then |y| >= 1 + div_large = div(MAX_UD60x18, y); + + assertGte(y, ONE_FP); + } catch { + // Expected revert as result is higher than max + } + } + + // Test for values in range + function div_test_range(UD60x18 x, UD60x18 y) public { + require(y.neq(ZERO_FP)); + UD60x18 result; + + try this.helpersDiv(x, y) { + // If it returns a value, it must be in range + result = div(x, y); + assertLte(result, MAX_UD60x18); + } catch { + // Otherwise, it should revert + } + } + + /* ================================================================ + + TESTS FOR FUNCTION inv() + + ================================================================ */ + + /* ================================================================ + Tests for mathematical properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test that the inverse of the inverse is close enough to the + // original number + function inv_test_double_inverse(UD60x18 x) public { + require(x.neq(ZERO_FP)); + + UD60x18 double_inv_x = inv(inv(x)); + + // The maximum loss of precision will be 2 * log2(x) bits rounded up + uint256 loss = 2 * intoUint256(log2(x)) + 2; + + assertEqWithinBitPrecision(x, double_inv_x, loss); + } + + // Test equivalence with division + function inv_test_division(UD60x18 x) public { + require(x.neq(ZERO_FP)); + + UD60x18 inv_x = inv(x); + UD60x18 div_1_x = div(ONE_FP, x); + + assertEq(inv_x, div_1_x); + } + + // Test the anticommutativity of the division + // x / y == 1 / (y / x) + function inv_test_division_noncommutativity( + UD60x18 x, + UD60x18 y + ) public { + require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); + + UD60x18 x_y = div(x, y); + UD60x18 y_x = div(y, x); + + assertEqWithinTolerance(x_y, inv(y_x), ONE_TENTH_FP, "0.1%"); + } + + // Test the multiplication of inverses + // 1/(x * y) == 1/x * 1/y + function inv_test_multiplication(UD60x18 x, UD60x18 y) public { + require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); + + UD60x18 inv_x = inv(x); + UD60x18 inv_y = inv(y); + UD60x18 inv_x_times_inv_y = mul(inv_x, inv_y); + + UD60x18 x_y = mul(x, y); + UD60x18 inv_x_y = inv(x_y); + + // The maximum loss of precision is given by the formula: + // 2 * | log2(x) - log2(y) | + 1 + uint256 loss = 2 * intoUint256(log2(x).sub(log2(y))) + 1; + + assertEqWithinBitPrecision(inv_x_y, inv_x_times_inv_y, loss); + } + + // Test multiplicative identity property + function inv_test_identity(UD60x18 x) public { + require(x.neq(ZERO_FP)); + + UD60x18 inv_x = inv(x); + UD60x18 identity = mul(inv_x, x); + + require(inv_x.neq(ZERO_FP) && identity.neq(ZERO_FP)); + + // They should agree with a tolerance of one tenth of a percent + assertEqWithinTolerance(identity, ONE_FP, ONE_TENTH_FP, "0.1%"); + } + + // Test that the value of the result is in range zero-one + // if x is greater than one, else, the value of the result + // must be greater than one + function inv_test_values(UD60x18 x) public { + require(x.neq(ZERO_FP)); + + UD60x18 inv_x = inv(x); + + if (x.gte(ONE_FP)) { + assertLte(inv_x, ONE_FP); + } else { + assertGt(inv_x, ONE_FP); + } + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test the zero-case, should revert + function inv_test_zero() public { + try this.helpersInv(ZERO_FP) { + // Unexpected, the function must revert + assert(false); + } catch {} + } + + // Test the maximum value case, should not revert, and be close to zero + function inv_test_maximum() public { + UD60x18 inv_maximum; + + try this.helpersInv(MAX_UD60x18) { + inv_maximum = this.helpersInv(MAX_UD60x18); + assertEqWithinBitPrecision(inv_maximum, ZERO_FP, 10); + } catch { + // Unexpected, the function must not revert + assert(false); + } + } + + // Test the minimum value case, should not revert, and be close to 1e36 + function inv_test_minimum() public { + UD60x18 inv_minimum; + + try this.helpersInv(UD60x18.wrap(1)) { + inv_minimum = this.helpersInv(UD60x18.wrap(1)); + assertEqWithinBitPrecision(inv_minimum, UD60x18.wrap(1e36), 10); + } catch { + // Unexpected, the function must not revert + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION avg() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test that the result is between the two operands + // avg(x, y) >= min(x, y) && avg(x, y) <= max(x, y) + function avg_test_values_in_range(UD60x18 x, UD60x18 y) public { + UD60x18 avg_xy = avg(x, y); + + if (x.gte(y)) { + assertGte(avg_xy, y); + assertLte(avg_xy, x); + } else { + assertGte(avg_xy, x); + assertLte(avg_xy, y); + } + } + + // Test that the average of the same number is itself + // avg(x, x) == x + function avg_test_one_value(UD60x18 x) public { + UD60x18 avg_x = avg(x, x); + + assertEq(avg_x, x); + } + + // Test that the order of operands is irrelevant + // avg(x, y) == avg(y, x) + function avg_test_operand_order(UD60x18 x, UD60x18 y) public { + UD60x18 avg_xy = avg(x, y); + UD60x18 avg_yx = avg(y, x); + + assertEq(avg_xy, avg_yx); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for the maximum value + function avg_test_maximum() public { + UD60x18 result; + + // This may revert due to overflow depending on implementation + // If it doesn't revert, the result must be MAX_UD60x18 + try this.helpersAvg(MAX_UD60x18, MAX_UD60x18) { + result = this.helpersAvg(MAX_UD60x18, MAX_UD60x18); + assertEq(result, MAX_UD60x18); + } catch { + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION pow() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for zero exponent + // x ** 0 == 1 + function pow_test_zero_exponent(UD60x18 x) public { + require(x.lte(MAX_PERMITTED_POW)); + UD60x18 x_pow_0 = pow(x, ZERO_FP); + + assertEq(x_pow_0, ONE_FP); + } + + // Test for zero base + // 0 ** y == 0 (for positive y) + function pow_test_zero_base(UD60x18 y) public { + require(y.lte(MAX_PERMITTED_POW)); + require(y.neq(ZERO_FP)); + + UD60x18 zero_pow_y = pow(ZERO_FP, y); + + assertEq(zero_pow_y, ZERO_FP); + } + + // Test for exponent one + // x ** 1 == x + function pow_test_one_exponent(UD60x18 x) public { + require(x.lte(MAX_PERMITTED_POW)); + UD60x18 x_pow_1 = pow(x, ONE_FP); + + assertEq(x_pow_1, x); + } + + // Test for base one + // 1 ** y == 1 + function pow_test_base_one(UD60x18 y) public { + UD60x18 one_pow_y = pow(ONE_FP, y); + + assertEq(one_pow_y, ONE_FP); + } + + // Test for product of powers of the same base + // x ** a * x ** b == x ** (a + b) + function pow_test_product_same_base( + UD60x18 x, + UD60x18 a, + UD60x18 b + ) public { + require(x.neq(ZERO_FP)); + require(x.lte(MAX_PERMITTED_POW)); + + UD60x18 x_a = pow(x, a); + UD60x18 x_b = pow(x, b); + UD60x18 x_ab = pow(x, a.add(b)); + + assertEqWithinDecimalPrecision(mul(x_a, x_b), x_ab, 9); + } + + // Test for power of an exponentiation + // (x ** a) ** b == x ** (a * b) + function pow_test_power_of_an_exponentiation( + UD60x18 x, + UD60x18 a, + UD60x18 b + ) public { + require(x.neq(ZERO_FP)); + require(x.lte(MAX_PERMITTED_POW)); + + UD60x18 x_a = pow(x, a); + UD60x18 x_a_b = pow(x_a, b); + UD60x18 x_ab = pow(x, a.mul(b)); + + assertEqWithinDecimalPrecision(x_a_b, x_ab, 9); + } + + // Test for power of a product + // (x * y) ** a == x ** a * y ** a + function pow_test_product_power( + UD60x18 x, + UD60x18 y, + UD60x18 a + ) public { + require(x.lte(MAX_PERMITTED_POW)); + require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); + require(a.gt(UD60x18.wrap(1e9))); // to avoid massive loss of precision + + UD60x18 x_y = mul(x, y); + UD60x18 xy_a = pow(x_y, a); + + UD60x18 x_a = pow(x, a); + UD60x18 y_a = pow(y, a); + + assertEqWithinDecimalPrecision(mul(x_a, y_a), xy_a, 9); + } + + // Test for result being greater than or lower than the argument, depending on + // its value and the value of the exponent + function pow_test_values(UD60x18 x, UD60x18 a) public { + require(x.neq(ZERO_FP)); + require(x.lte(MAX_PERMITTED_POW)); + + UD60x18 x_a = pow(x, a); + + if (x.gte(ONE_FP)) { + assertGte(x_a, ONE_FP); + } + + if (x.lte(ONE_FP)) { + assertLte(x_a, ONE_FP); + } + } + + // Power is strictly increasing + // x > y && a >= 0 --> pow(x) >= pow(y) + function pow_test_strictly_increasing(UD60x18 x, UD60x18 y, UD60x18 a) public { + require(x.gt(y) && x.lte(MAX_PERMITTED_POW)); + require(a.gte(ZERO_FP)); + + UD60x18 x_a = pow(x, a); + UD60x18 y_a = pow(y, a); + + assertGte(x_a, y_a); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for maximum base and exponent > 1 + function pow_test_maximum_base(UD60x18 a) public { + require(a.gt(ONE_FP)); + + try this.helpersPow(MAX_UD60x18, a) { + // Unexpected, should revert because of overflow + assert(false); + } catch { + // Expected revert + } + } + + // Test for maximum exponent and base > 1 + function pow_test_maximum_exponent(UD60x18 x) public { + require(x.gt(ONE_FP)); + require(x.lte(MAX_PERMITTED_POW)); + + try this.helpersPow(x, MAX_PERMITTED_POW) { + // Unexpected, should revert because of overflow + assert(false); + } catch { + // Expected revert + } + } + + // Test for base < 1 and high exponent + function pow_test_high_exponent(UD60x18 x, UD60x18 a) public { + require(x.lt(ONE_FP) && a.gt(convert(2 ** 64))); + + UD60x18 result = pow(x, a); + + assertEq(result, ZERO_FP); + } + + /* ================================================================ + + TESTS FOR FUNCTION sqrt() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for the inverse operation + // sqrt(x) * sqrt(x) == x + function sqrt_test_inverse_mul(UD60x18 x) public { + require(x.lte(MAX_PERMITTED_SQRT)); + UD60x18 sqrt_x = sqrt(x); + UD60x18 sqrt_x_squared = mul(sqrt_x, sqrt_x); + + // Precision loss is at most half the bits of the operand + assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); + } + + // Test for the inverse operation + // sqrt(x) ** 2 == x + function sqrt_test_inverse_pow(UD60x18 x) public { + require(x.lte(MAX_PERMITTED_SQRT)); + UD60x18 sqrt_x = sqrt(x); + UD60x18 sqrt_x_squared = pow(sqrt_x, convert(2)); + + // Precision loss is at most half the bits of the operand + assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); + } + + // Test for distributive property respect to the multiplication + // sqrt(x) * sqrt(y) == sqrt(x * y) + function sqrt_test_distributive(UD60x18 x, UD60x18 y) public { + require(x.lte(MAX_PERMITTED_SQRT) && y.lte(MAX_PERMITTED_SQRT)); + UD60x18 sqrt_x = sqrt(x); + UD60x18 sqrt_y = sqrt(y); + UD60x18 sqrt_x_sqrt_y = mul(sqrt_x, sqrt_y); + UD60x18 sqrt_xy = sqrt(mul(x, y)); + + // Allow an error of up to one tenth of a percent + assertEqWithinTolerance(sqrt_x_sqrt_y, sqrt_xy, ONE_TENTH_FP, "0.1%"); + } + + // Test that sqrt is strictly increasing + // x >= 0 && y > x --> sqrt(y) >= sqrt(x) + function sqrt_test_strictly_increasing(UD60x18 x, UD60x18 y) public { + require(y.gt(x)); + + UD60x18 sqrt_x = sqrt(x); + UD60x18 sqrt_y = sqrt(y); + + assertGte(sqrt_y, sqrt_x); + } + + // Square root of perfect square should be equal to x + // sqrt(x * x) == x + function sqrt_test_square(UD60x18 x) public { + require(x.gt(ONE_FP)); + + UD60x18 square_x = x.mul(x); + UD60x18 sqrt_square_x = sqrt(square_x); + + assertEq(sqrt_square_x, x); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case + function sqrt_test_zero() public { + assertEq(sqrt(ZERO_FP), ZERO_FP); + } + + // Test for maximum value + function sqrt_test_maximum() public { + try this.helpersSqrt(MAX_PERMITTED_SQRT) { + // Expected behaviour, MAX_SQRT is positive, and operation + // should not revert as the result is in range + } catch { + // Unexpected, should not revert + assert(false); + } + } + + /* ================================================================ + + TESTS FOR FUNCTION log2() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for distributive property respect to multiplication + // log2(x * y) = log2(x) + log2(y) + function log2_test_distributive_mul(UD60x18 x, UD60x18 y) public { + require(x.gte(UNIT) && y.gte(UNIT)); + + UD60x18 log2_x = log2(x); + UD60x18 log2_y = log2(y); + UD60x18 log2_x_log2_y = add(log2_x, log2_y); + + UD60x18 xy = mul(x, y); + UD60x18 log2_xy = log2(xy); + + // The maximum loss of precision is given by the formula: + // | log2(x) + log2(y) | + uint256 loss = intoUint256(log2(x).add(log2(y))); + + assertEqWithinBitPrecision(log2_x_log2_y, log2_xy, loss); + } + + // Test for logarithm of a power + // log2(x ** y) = y * log2(x) + function log2_test_power(UD60x18 x, UD60x18 y) public { + require(x.gte(UNIT) && y.gte(UNIT)); + + UD60x18 x_y = pow(x, y); + UD60x18 log2_x_y = log2(x_y); + UD60x18 y_log2_x = mul(log2(x), y); + + assertEqWithinTolerance(y_log2_x, log2_x_y, ONE_TENTH_FP, "0.1%"); + } + + // Base 2 logarithm is strictly increasing + // y > 0 && x > y --> log2(x) > log2(y) + function log2_test_strictly_increasing(UD60x18 x, UD60x18 y) public { + require(y.gt(ZERO_FP) && x.gt(y)); + + UD60x18 log2_x = log2(x); + UD60x18 log2_y = log2(y); + + assertGte(log2_x, log2_y); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case, should revert + function log2_test_zero() public { + try this.helpersLog2(ZERO_FP) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, log(0) is not defined + } + } + + // Test for maximum value case, should return a positive number + function log2_test_maximum() public { + UD60x18 result; + + try this.helpersLog2(MAX_UD60x18) { + // Expected, should not revert and the result must be > 0 + result = this.helpersLog2(MAX_UD60x18); + assertGt(result, ZERO_FP); + } catch { + // Unexpected + assert(false); + } + } + + // Test for values less than UNIT, should revert as result would be negative + function log2_test_less_than_unit(UD60x18 x) public { + require(x.lt(UNIT)); + + try this.helpersLog2(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected + } + } + + /* ================================================================ + + TESTS FOR FUNCTION ln() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for distributive property respect to multiplication + // ln(x * y) = ln(x) + ln(y) + function ln_test_distributive_mul(UD60x18 x, UD60x18 y) public { + require(x.gte(UNIT) && y.gte(UNIT)); + + UD60x18 ln_x = ln(x); + UD60x18 ln_y = ln(y); + UD60x18 ln_x_ln_y = add(ln_x, ln_y); + + UD60x18 xy = mul(x, y); + UD60x18 ln_xy = ln(xy); + + // The maximum loss of precision is given by the formula: + // | log2(x) + log2(y) | + uint256 loss = intoUint256(log2(x).add(log2(y))); + assertEqWithinBitPrecision(ln_x_ln_y, ln_xy, loss); + } + + // Test for logarithm of a power + // ln(x ** y) = y * ln(x) + function ln_test_power(UD60x18 x, UD60x18 y) public { + require(x.gte(UNIT)); + UD60x18 x_y = pow(x, y); + UD60x18 ln_x_y = ln(x_y); + UD60x18 y_ln_x = mul(ln(x), y); + + assertEqWithinDecimalPrecision(ln_x_y, y_ln_x, 9); + } + + // Natural logarithm is strictly increasing + // y > 0 && x > y --> log2(x) > log2(y) + function ln_test_strictly_increasing(UD60x18 x, UD60x18 y) public { + require(y.gt(ZERO_FP) && x.gt(y)); + + UD60x18 ln_x = ln(x); + UD60x18 ln_y = ln(y); + + assertGte(ln_x, ln_y); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case, should revert + function ln_test_zero() public { + try this.helpersLn(ZERO_FP) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, log(0) is not defined + } + } + + // Test for maximum value case, should return a positive number + function ln_test_maximum() public { + UD60x18 result; + + try this.helpersLn(MAX_UD60x18) { + // Expected, should not revert and the result must be > 0 + result = this.helpersLn(MAX_UD60x18); + assertGt(result, ZERO_FP); + } catch { + // Unexpected + assert(false); + } + } + + // Test for values less than UNIT, should revert since result would be negative + function ln_test_less_than_unit(UD60x18 x) public { + require(x.lt(UNIT)); + + try this.helpersLn(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected + } + } + + /* ================================================================ + + TESTS FOR FUNCTION exp2() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for equality with pow(2, x) for integer x + // pow(2, x) == exp2(x) + function exp2_test_equivalence_pow(UD60x18 x) public { + require(x.lte(MAX_PERMITTED_EXP2)); + UD60x18 exp2_x = exp2(x); + UD60x18 pow_2_x = pow(TWO_FP, x); + + assertEq(exp2_x, pow_2_x); + } + + // Test for inverse function + // If y = log2(x) then exp2(y) == x + function exp2_test_inverse(UD60x18 x) public { + require(x.gte(UNIT)); + UD60x18 log2_x = log2(x); + require(log2_x.lte(MAX_PERMITTED_EXP2)); + UD60x18 exp2_x = exp2(log2_x); + + assertEqWithinTolerance(x, exp2_x, ONE_TENTH_FP, "0.1%"); + } + + // Test that exp2 strictly increases + // y > x && y < MAX --> exp2(y) > exp2(x) + function exp2_test_strictly_increasing(UD60x18 x, UD60x18 y) public { + require(y.gt(x) && y.lte(MAX_PERMITTED_EXP2)); + + UD60x18 exp_x = exp(x); + UD60x18 exp_y = exp(y); + + assertGte(exp_y, exp_x); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case + // exp2(0) == 1 + function exp2_test_zero() public { + UD60x18 exp_zero = exp2(ZERO_FP); + assertEq(exp_zero, ONE_FP); + } + + // Test for maximum value. This should overflow as it won't fit + // in the data type + function exp2_test_maximum() public { + try this.helpersExp2(convert(192)) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert + } + } + + /* ================================================================ + + TESTS FOR FUNCTION exp() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for inverse function + // If y = ln(x) then exp(y) == x + function exp_test_inverse(UD60x18 x) public { + require(x.gte(UNIT)); + UD60x18 ln_x = ln(x); + UD60x18 exp_x = exp(ln_x); + require(exp_x.lte(MAX_PERMITTED_EXP)); + UD60x18 log2_x = log2(x); + + assertEqWithinDecimalPrecision(x, exp_x, 9); + } + + // Test that exp strictly increases + // y <= MAX && y.gt(x) --> exp(y) >= exp(x) + function exp_test_strictly_increasing(UD60x18 x, UD60x18 y) public { + require(y.gt(x) && y.lte(MAX_PERMITTED_EXP)); + + UD60x18 exp_x = exp(x); + UD60x18 exp_y = exp(y); + + assertGte(exp_y, exp_x); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case + // exp(0) == 1 + function exp_test_zero() public { + UD60x18 exp_zero = exp(ZERO_FP); + assertEq(exp_zero, ONE_FP); + } + + // Test for maximum value. This should overflow as it won't fit + // in the data type + function exp_test_maximum() public { + try this.helpersExp(convert(192)) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert + } + } + + /* ================================================================ + + TESTS FOR FUNCTION powu() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for zero exponent + // x ** 0 == 1 + function powu_test_zero_exponent(UD60x18 x) public { + UD60x18 x_pow_0 = powu(x, 0); + + assertEq(x_pow_0, ONE_FP); + } + + // Test for zero base + // 0 ** x == 0 + function powu_test_zero_base(uint256 a) public { + require(a != 0); + + UD60x18 zero_pow_a = powu(ZERO_FP, a); + + assertEq(zero_pow_a, ZERO_FP); + } + + // Test for exponent one + // x ** 1 == x + function powu_test_one_exponent(UD60x18 x) public { + UD60x18 x_pow_1 = powu(x, 1); + + assertEq(x_pow_1, x); + } + + // Test for base one + // 1 ** x == 1 + function powu_test_base_one(uint256 a) public { + UD60x18 one_pow_a = powu(ONE_FP, a); + + assertEq(one_pow_a, ONE_FP); + } + + // Test for product of powers of the same base + // x ** a * x ** b == x ** (a + b) + function powu_test_product_same_base( + UD60x18 x, + uint256 a, + uint256 b + ) public { + require(x.neq(ZERO_FP)); + + UD60x18 x_a = powu(x, a); + UD60x18 x_b = powu(x, b); + UD60x18 x_ab = powu(x, a + b); + + assertEqWithinBitPrecision(mul(x_a, x_b), x_ab, 10); + } + + // Test for power of an exponentiation + // (x ** a) ** b == x ** (a * b) + function powu_test_power_of_an_exponentiation( + UD60x18 x, + uint256 a, + uint256 b + ) public { + require(x.neq(ZERO_FP)); + + UD60x18 x_a = powu(x, a); + UD60x18 x_a_b = powu(x_a, b); + UD60x18 x_ab = powu(x, a * b); + + assertEqWithinBitPrecision(x_a_b, x_ab, 10); + } + + // Test for power of a product + // (x * y) ** a == x ** a * y ** a + function powu_test_product_power( + UD60x18 x, + UD60x18 y, + uint256 a + ) public { + require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); + + require(a > 1e9); // to avoid massive loss of precision + + UD60x18 x_y = mul(x, y); + UD60x18 xy_a = powu(x_y, a); + + UD60x18 x_a = powu(x, a); + UD60x18 y_a = powu(y, a); + + assertEqWithinBitPrecision(mul(x_a, y_a), xy_a, 10); + } + + // Test for result being greater than or lower than the argument, depending on + // its value and the value of the exponent + function powu_test_values(UD60x18 x, uint256 a) public { + require(x.neq(ZERO_FP)); + + UD60x18 x_a = powu(x, a); + + if (x.gte(ONE_FP)) { + assertGte(x_a, ONE_FP); + } + + if (x.lte(ONE_FP)) { + assertLte(x_a, ONE_FP); + } + } + + // Unsigned power is strictly increasing + // y > MIN && x > y && a > 0 --> powu(x, a) > powu(y, a) + function powu_test_strictly_increasing( + UD60x18 x, + UD60x18 y, + uint256 a + ) public { + require(x.gt(y) && y.gt(ZERO_FP)); + require(x.lte(MAX_UD60x18)); + require(a > 0); + + UD60x18 x_a = powu(x, a); + UD60x18 y_a = powu(y, a); + + assertGte(x_a, y_a); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for maximum base and exponent > 1 + function powu_test_maximum_base(uint256 a) public { + require(a > 1); + + try this.helpersPowu(MAX_UD60x18, a) { + // Unexpected, should revert because of overflow + assert(false); + } catch { + // Expected revert + } + } + + // Test for base < 1 and high exponent + function powu_test_high_exponent(UD60x18 x, uint256 a) public { + require(x.lt(ONE_FP) && a > 2 ** 64); + + UD60x18 result = powu(x, a); + + assertEq(result, ZERO_FP); + } + + /* ================================================================ + + TESTS FOR FUNCTION log10() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // Test for distributive property respect to multiplication + // log10(x * y) = log10(x) + log10(y) + function log10_test_distributive_mul(UD60x18 x, UD60x18 y) public { + require(x.gte(UNIT) && y.gte(UNIT)); + UD60x18 log10_x = log10(x); + UD60x18 log10_y = log10(y); + UD60x18 log10_x_log10_y = add(log10_x, log10_y); + + UD60x18 xy = mul(x, y); + UD60x18 log10_xy = log10(xy); + + // The maximum loss of precision is given by the formula: + // | log10(x) + log10(y) | + uint256 loss = intoUint256(log10(x).add(log10(y))); + + assertEqWithinBitPrecision(log10_x_log10_y, log10_xy, loss); + } + + // Test for logarithm of a power + // log10(x ** y) = y * log10(x) + function log10_test_power(UD60x18 x, UD60x18 y) public { + require(x.gte(UNIT) && y.gte(UNIT)); + UD60x18 x_y = pow(x, y); + UD60x18 log10_x_y = log10(x_y); + UD60x18 y_log10_x = mul(log10(x), y); + + assertEqWithinTolerance(log10_x_y, y_log10_x, ONE_TENTH_FP, "0.1%"); + } + + // Base 10 logarithm is strictly increasing + // x > y && y > 0 --> log10(x) > log10(y) + function log10_test_strictly_increasing(UD60x18 x, UD60x18 y) public { + require(y.gt(ZERO_FP) && x.gt(y)); + + UD60x18 log10_x = log10(x); + UD60x18 log10_y = log10(y); + + assertGte(log10_x, log10_y); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case, should revert + function log10_test_zero() public { + try this.helpersLog10(ZERO_FP) { + // Unexpected, should revert + assert(false); + } catch { + // Expected revert, log(0) is not defined + } + } + + // Test for maximum value case, should return a positive number + function log10_test_maximum() public { + UD60x18 result; + + try this.helpersLog10(MAX_UD60x18) { + // Expected, should not revert and the result must be > 0 + result = this.helpersLog10(MAX_UD60x18); + assertGt(result, ZERO_FP); + } catch { + // Unexpected + assert(false); + } + } + + // Test for values less than UNIT, should revert as result would be negative + function log10_test_less_than_unit(UD60x18 x) public { + require(x.lt(UNIT)); + + try this.helpersLog10(x) { + // Unexpected, should revert + assert(false); + } catch { + // Expected + } + } + + /* ================================================================ + + TESTS FOR FUNCTION gm() + + ================================================================ */ + + /* ================================================================ + Tests for arithmetic properties. + These should make sure that the implemented function complies + with math rules and expected behaviour. + ================================================================ */ + + // The product of the values should be equal to the geometric mean + // raised to the power of N (numbers in the set) + function gm_test_product(UD60x18 x, UD60x18 y) public { + UD60x18 x_mul_y = x.mul(y); + UD60x18 gm_squared = pow(gm(x,y), TWO_FP); + + assertEqWithinTolerance(x_mul_y, gm_squared, ONE_TENTH_FP, "0.1%"); + } + + // The geometric mean for a set of positive numbers is less than the + // arithmetic mean of that set, as long as the values of the set are not equal + function gm_test_positive_set_avg(UD60x18 x, UD60x18 y) public { + require(x.neq(y)); + + UD60x18 gm_x_y = gm(x, y); + UD60x18 avg_x_y = avg(x, y); + + assertLte(gm_x_y, avg_x_y); + } + + // The geometric mean of a set of positive equal numbers should be + // equal to the arithmetic mean + function gm_test_positive_equal_set_avg(UD60x18 x) public { + UD60x18 gm_x = gm(x, x); + UD60x18 avg_x = avg(x, x); + + assertEq(gm_x, avg_x); + } + + /* ================================================================ + Tests for overflow and edge cases. + These will make sure that the function reverts on overflow and + behaves correctly on edge cases + ================================================================ */ + + // Test for zero case, should return 0 + function gm_test_zero(UD60x18 x) public { + require(x.gte(ZERO_FP)); + + try this.helpersGm(x, ZERO_FP) { + UD60x18 result = gm(x, ZERO_FP); + assertEq(result, ZERO_FP); + } catch { + // Unexpected, should not revert + assert(false); + } + } +} \ No newline at end of file diff --git a/contracts/Math/PRBMath/v4/utils/AssertionHelperSD.sol b/contracts/Math/PRBMath/v4/utils/AssertionHelperSD.sol new file mode 100644 index 0000000..2ebe8ca --- /dev/null +++ b/contracts/Math/PRBMath/v4/utils/AssertionHelperSD.sol @@ -0,0 +1,316 @@ +pragma solidity ^0.8.0; + +import { SD59x18 } from "@prb/math/SD59x18.sol"; + +import { convert } from "@prb/math/sd59x18/Conversions.sol"; +import { add, sub, eq, gt, gte, lt, lte, rshift } from "@prb/math/sd59x18/Helpers.sol"; +import { mul, div, abs } from "@prb/math/sd59x18/Math.sol"; + +abstract contract AssertionHelperSD { + event AssertEqFailure(string); + event AssertGtFailure(string); + event AssertGteFailure(string); + event AssertLtFailure(string); + event AssertLteFailure(string); + + function assertEq(SD59x18 a, SD59x18 b) internal { + if (!a.eq(b)) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " != ", + str_b, + ". No precision loss allowed." + ); + emit AssertEqFailure(string(assertMsg)); + assert(false); + } + } + + function assertEqWithinBitPrecision(SD59x18 a, SD59x18 b, uint256 precision_bits) internal { + SD59x18 max = gt(a , b) ? a : b; + SD59x18 min = gt(a , b) ? b : a; + SD59x18 r = rshift(sub(max, min), precision_bits); + + if (!eq(r, convert(0))) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + string memory str_bits = toString(precision_bits); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " != ", + str_b, + " within ", + str_bits, + " bits of precision" + ); + emit AssertEqFailure(string(assertMsg)); + assert(false); + } + } + + function assertEqWithinTolerance(SD59x18 a, SD59x18 b, SD59x18 error_percent, string memory str_percent) internal { + SD59x18 tol_value = mul(a, div(error_percent, convert(100))); + + require(tol_value.neq(convert(0))); + + if (!lte(sub(b, a), abs(tol_value))) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + string memory tolerance = toString(SD59x18.unwrap(abs(tol_value))); + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " != ", + str_b, + " within ", + str_percent, + " tolerance: ", + tolerance + ); + emit AssertEqFailure(string(assertMsg)); + assert(false); + } + } + + // Returns true if the n most significant bits of a and b are almost equal + // Uses functions from the library under test! + function assertEqWithinDecimalPrecision(SD59x18 a, SD59x18 b, uint256 digits) internal { + // Divide both number by digits to truncate the unimportant digits + int256 a_int = SD59x18.unwrap(a); + int256 b_int = SD59x18.unwrap(b); + + int256 denominator = int256(10 ** digits); + + int256 a_significant = a_int / denominator; + int256 b_significant = b_int / denominator; + + int256 larger = a_significant > b_significant ? a_significant : b_significant; + int256 smaller = a_significant > b_significant ? b_significant : a_significant; + + if (!((larger - smaller) <= 1)) { + string memory str_a = toString(a_int); + string memory str_b = toString(b_int); + string memory str_larger = toString(larger); + string memory str_smaller = toString(smaller); + string memory str_digits = toString(digits); + string memory difference = toString(larger - smaller); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " != ", + str_b, + " within ", + str_digits, + " digits of precision. Difference: ", + difference, + ", truncated input:", + str_larger, + " != ", + str_smaller + ); + emit AssertEqFailure(string(assertMsg)); + assert(false); + } + } + + function assertGt(SD59x18 a, SD59x18 b, string memory reason) internal { + if (!a.gt(b)) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " <= ", + str_b, + ", reason: ", + reason + ); + emit AssertGtFailure(string(assertMsg)); + assert(false); + } + } + + function assertGt(SD59x18 a, SD59x18 b) internal { + if (!a.gt(b)) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " <= ", + str_b + ); + emit AssertGtFailure(string(assertMsg)); + assert(false); + } + } + + function assertGte(SD59x18 a, SD59x18 b, string memory reason) internal { + if (!a.gte(b)) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " < ", + str_b, + ", reason: ", + reason + ); + emit AssertGteFailure(string(assertMsg)); + assert(false); + } + } + + function assertGte(SD59x18 a, SD59x18 b) internal { + if (!a.gte(b)) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " < ", + str_b + ); + emit AssertGteFailure(string(assertMsg)); + assert(false); + } + } + + function assertLt(SD59x18 a, SD59x18 b, string memory reason) internal { + if (!a.lt(b)) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " >= ", + str_b, + ", reason: ", + reason + ); + emit AssertLtFailure(string(assertMsg)); + assert(false); + } + } + + function assertLt(SD59x18 a, SD59x18 b) internal { + if (!a.lt(b)) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " >= ", + str_b + ); + emit AssertLtFailure(string(assertMsg)); + assert(false); + } + } + + function assertLte(SD59x18 a, SD59x18 b, string memory reason) internal { + if (!a.lte(b)) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " > ", + str_b, + ", reason: ", + reason + ); + emit AssertLteFailure(string(assertMsg)); + assert(false); + } + } + + function assertLte(SD59x18 a, SD59x18 b) internal { + if (!a.lte(b)) { + string memory str_a = toString(SD59x18.unwrap(a)); + string memory str_b = toString(SD59x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " > ", + str_b + ); + emit AssertLteFailure(string(assertMsg)); + assert(false); + } + } + + function toString(int256 value) internal pure returns (string memory str) { + uint256 absValue = value >= 0 ? uint256(value) : uint256(-value); + str = toString(absValue); + + if (value < 0) { + str = string(abi.encodePacked("-", str)); + } + } + + function toString(uint256 value) internal pure returns (string memory str) { + /// @solidity memory-safe-assembly + assembly { + // The maximum value of a uint256 contains 78 digits (1 byte per digit), but we allocate 160 bytes + // to keep the free memory pointer word aligned. We'll need 1 word for the length, 1 word for the + // trailing zeros padding, and 3 other words for a max of 78 digits. In total: 5 * 32 = 160 bytes. + let newFreeMemoryPointer := add(mload(0x40), 160) + + // Update the free memory pointer to avoid overriding our string. + mstore(0x40, newFreeMemoryPointer) + + // Assign str to the end of the zone of newly allocated memory. + str := sub(newFreeMemoryPointer, 32) + + // Clean the last word of memory it may not be overwritten. + mstore(str, 0) + + // Cache the end of the memory to calculate the length later. + let end := str + + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + // prettier-ignore + for { let temp := value } 1 {} { + // Move the pointer 1 byte to the left. + str := sub(str, 1) + + // Write the character to the pointer. + // The ASCII index of the '0' character is 48. + mstore8(str, add(48, mod(temp, 10))) + + // Keep dividing temp until zero. + temp := div(temp, 10) + + // prettier-ignore + if iszero(temp) { break } + } + + // Compute and cache the final total length of the string. + let length := sub(end, str) + + // Move the pointer 32 bytes leftwards to make room for the length. + str := sub(str, 32) + + // Store the string's length at the start of memory allocated for our string. + mstore(str, length) + } + } +} \ No newline at end of file diff --git a/contracts/Math/PRBMath/v4/utils/AssertionHelperUD.sol b/contracts/Math/PRBMath/v4/utils/AssertionHelperUD.sol new file mode 100644 index 0000000..5f6f56a --- /dev/null +++ b/contracts/Math/PRBMath/v4/utils/AssertionHelperUD.sol @@ -0,0 +1,313 @@ +pragma solidity ^0.8.0; + +import { UD60x18 } from "@prb/math/UD60x18.sol"; + +import {convert} from "@prb/math/ud60x18/Conversions.sol"; +import {add, sub, eq, gt, gte, lt, lte, rshift} from "@prb/math/ud60x18/Helpers.sol"; +import {mul, div, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, powu, gm} from "@prb/math/ud60x18/Math.sol"; + +abstract contract AssertionHelperUD { + event AssertEqFailure(string); + event AssertGtFailure(string); + event AssertGteFailure(string); + event AssertLtFailure(string); + event AssertLteFailure(string); + + function assertEq(UD60x18 a, UD60x18 b) internal { + if (!a.eq(b)) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " != ", + str_b, + ". No precision loss allowed." + ); + emit AssertEqFailure(string(assertMsg)); + assert(false); + } + } + + function assertEqWithinBitPrecision(UD60x18 a, UD60x18 b, uint256 precision_bits) internal { + UD60x18 max = gt(a , b) ? a : b; + UD60x18 min = gt(a , b) ? b : a; + UD60x18 r = rshift(sub(max, min), precision_bits); + + if (!eq(r, convert(0))) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + string memory str_bits = toString(precision_bits); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " != ", + str_b, + " within ", + str_bits, + " bits of precision" + ); + emit AssertEqFailure(string(assertMsg)); + assert(false); + } + } + + function assertEqWithinTolerance(UD60x18 a, UD60x18 b, UD60x18 error_percent, string memory str_percent) internal { + UD60x18 tol_value = mul(a, div(error_percent, convert(100))); + + require(tol_value.neq(convert(0))); + + if (!lte(sub(b, a), tol_value)) { + string memory ua = toString(UD60x18.unwrap(a)); + string memory ub = toString(UD60x18.unwrap(b)); + string memory tolerance = toString(UD60x18.unwrap(tol_value)); + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + ua, + " != ", + ub, + " within ", + str_percent, + " tolerance: ", + tolerance + ); + emit AssertEqFailure(string(assertMsg)); + assert(false); + } + } + + // Returns true if the n most significant bits of a and b are almost equal + // Uses functions from the library under test! + function assertEqWithinDecimalPrecision(UD60x18 a, UD60x18 b, uint256 digits) internal { + // Divide both number by digits to truncate the unimportant digits + uint256 a_uint = UD60x18.unwrap(a); + uint256 b_uint = UD60x18.unwrap(b); + + uint256 a_significant = a_uint / 10 ** digits; + uint256 b_significant = b_uint / 10 ** digits; + + uint256 larger = a_significant > b_significant ? a_significant : b_significant; + uint256 smaller = a_significant > b_significant ? b_significant : a_significant; + + if (!((larger - smaller) <= 1)) { + string memory str_a = toString(a_uint); + string memory str_b = toString(b_uint); + string memory str_digits = toString(digits); + string memory str_larger = toString(larger); + string memory str_smaller = toString(smaller); + string memory difference = toString(larger - smaller); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " != ", + str_b, + " within ", + str_digits, + " digits of precision. Difference: ", + difference, + ", truncated input:", + str_larger, + " != ", + str_smaller + ); + emit AssertEqFailure(string(assertMsg)); + assert(false); + } + } + + function assertGt(UD60x18 a, UD60x18 b, string memory reason) internal { + if (!a.gt(b)) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " <= ", + str_b, + ", reason: ", + reason + ); + emit AssertGtFailure(string(assertMsg)); + assert(false); + } + } + function assertGt(UD60x18 a, UD60x18 b) internal { + if (!a.gt(b)) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " <= ", + str_b + ); + emit AssertGtFailure(string(assertMsg)); + assert(false); + } + } + + function assertGte(UD60x18 a, UD60x18 b, string memory reason) internal { + if (!a.gte(b)) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " < ", + str_b, + ", reason: ", + reason + ); + emit AssertGteFailure(string(assertMsg)); + assert(false); + } + } + + function assertGte(UD60x18 a, UD60x18 b) internal { + if (!a.gte(b)) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " < ", + str_b + ); + emit AssertGteFailure(string(assertMsg)); + assert(false); + } + } + + function assertLt(UD60x18 a, UD60x18 b, string memory reason) internal { + if (!a.lt(b)) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " >= ", + str_b, + ", reason: ", + reason + ); + emit AssertLtFailure(string(assertMsg)); + assert(false); + } + } + + function assertLt(UD60x18 a, UD60x18 b) internal { + if (!a.lt(b)) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " >= ", + str_b + ); + emit AssertLtFailure(string(assertMsg)); + assert(false); + } + } + + function assertLte(UD60x18 a, UD60x18 b, string memory reason) internal { + if (!a.lte(b)) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " > ", + str_b, + ", reason: ", + reason + ); + emit AssertLteFailure(string(assertMsg)); + assert(false); + } + } + + function assertLte(UD60x18 a, UD60x18 b) internal { + if (!a.lte(b)) { + string memory str_a = toString(UD60x18.unwrap(a)); + string memory str_b = toString(UD60x18.unwrap(b)); + + bytes memory assertMsg = abi.encodePacked( + "Invalid: ", + str_a, + " > ", + str_b + ); + emit AssertLteFailure(string(assertMsg)); + assert(false); + } + } + + function toString(int256 value) internal pure returns (string memory str) { + uint256 absValue = value >= 0 ? uint256(value) : uint256(-value); + str = toString(absValue); + + if (value < 0) { + str = string(abi.encodePacked("-", str)); + } + } + + function toString(uint256 value) internal pure returns (string memory str) { + /// @solidity memory-safe-assembly + assembly { + // The maximum value of a uint256 contains 78 digits (1 byte per digit), but we allocate 160 bytes + // to keep the free memory pointer word aligned. We'll need 1 word for the length, 1 word for the + // trailing zeros padding, and 3 other words for a max of 78 digits. In total: 5 * 32 = 160 bytes. + let newFreeMemoryPointer := add(mload(0x40), 160) + + // Update the free memory pointer to avoid overriding our string. + mstore(0x40, newFreeMemoryPointer) + + // Assign str to the end of the zone of newly allocated memory. + str := sub(newFreeMemoryPointer, 32) + + // Clean the last word of memory it may not be overwritten. + mstore(str, 0) + + // Cache the end of the memory to calculate the length later. + let end := str + + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + // prettier-ignore + for { let temp := value } 1 {} { + // Move the pointer 1 byte to the left. + str := sub(str, 1) + + // Write the character to the pointer. + // The ASCII index of the '0' character is 48. + mstore8(str, add(48, mod(temp, 10))) + + // Keep dividing temp until zero. + temp := div(temp, 10) + + // prettier-ignore + if iszero(temp) { break } + } + + // Compute and cache the final total length of the string. + let length := sub(end, str) + + // Move the pointer 32 bytes leftwards to make room for the length. + str := sub(str, 32) + + // Store the string's length at the start of memory allocated for our string. + mstore(str, length) + } + } +} \ No newline at end of file diff --git a/lib/prb-math b/lib/prb-math index 1edf08d..7ce3009 160000 --- a/lib/prb-math +++ b/lib/prb-math @@ -1 +1 @@ -Subproject commit 1edf08dd73eb1ace0042459ba719b8ea4a55c0e0 +Subproject commit 7ce3009bbfa0d8e2d430b7a1a9ca46b6e706d90d diff --git a/lib/prb-math-v3 b/lib/prb-math-v3 new file mode 160000 index 0000000..1edf08d --- /dev/null +++ b/lib/prb-math-v3 @@ -0,0 +1 @@ +Subproject commit 1edf08dd73eb1ace0042459ba719b8ea4a55c0e0 diff --git a/package.json b/package.json index bce45c2..16dd4f0 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,10 @@ "lint": "npm run lint-check-format && npm run lint-check-links", "lint-check-format": "prettier --check .", "lint-check-links": "find . -name '*.md' -not -path './node_modules/*' -print0 | xargs -0 -n1 markdown-link-check", - "echidna-prb-ud": "echidna . --contract CryticPRBMath60x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-prb-ud", - "echidna-prb-sd": "echidna . --contract CryticPRBMath59x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-prb-sd", + "echidna-prb-ud-v3": "echidna . --contract CryticPRBMath60x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-prb-ud-v3", + "echidna-prb-sd-v3": "echidna . --contract CryticPRBMath59x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-prb-sd-v3 --workers 4", + "echidna-prb-ud-v4": "echidna . --contract CryticPRBMath60x18Propertiesv4 --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-prb-ud-v4", + "echidna-prb-sd-v4": "echidna . --contract CryticPRBMath59x18Propertiesv4 --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-prb-sd-v4", "echidna-abdk": "echidna . --contract CryticABDKMath64x64Properties --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-abdk-64" }, "repository": { diff --git a/remappings.txt b/remappings.txt index 42051ce..b6fa815 100644 --- a/remappings.txt +++ b/remappings.txt @@ -4,6 +4,8 @@ ds-test/=lib/forge-std/lib/ds-test/src/ erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ forge-std/=lib/forge-std/src/ @openzeppelin/=lib/openzeppelin-contracts/ -@prb/math/=lib/prb-math/ +@prb-math-v3/=lib/prb-math-v3/src/ +@prb/math/=lib/prb-math/src/ prb-test/=lib/prb-math/lib/prb-test/src/ solmate/=lib/solmate/ +src/=lib/prb-math-v3/src/ diff --git a/slither.config.json b/slither.config.json new file mode 100644 index 0000000..ed7a465 --- /dev/null +++ b/slither.config.json @@ -0,0 +1 @@ +{ "no_fail": true } \ No newline at end of file From 9ccd809d8d7b3787a827feca1801b8306b951c6f Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Fri, 19 May 2023 17:15:47 +0200 Subject: [PATCH 27/32] update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 16dd4f0..d25d60f 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "lint-check-format": "prettier --check .", "lint-check-links": "find . -name '*.md' -not -path './node_modules/*' -print0 | xargs -0 -n1 markdown-link-check", "echidna-prb-ud-v3": "echidna . --contract CryticPRBMath60x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-prb-ud-v3", - "echidna-prb-sd-v3": "echidna . --contract CryticPRBMath59x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-prb-sd-v3 --workers 4", + "echidna-prb-sd-v3": "echidna . --contract CryticPRBMath59x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-prb-sd-v3", "echidna-prb-ud-v4": "echidna . --contract CryticPRBMath60x18Propertiesv4 --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-prb-ud-v4", "echidna-prb-sd-v4": "echidna . --contract CryticPRBMath59x18Propertiesv4 --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-prb-sd-v4", "echidna-abdk": "echidna . --contract CryticABDKMath64x64Properties --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-abdk-64" From c93b9c2c2a68b47dd1a8ecee8e86d7c7c064fd31 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Wed, 5 Jul 2023 12:21:02 +0200 Subject: [PATCH 28/32] update links, add link retry on error 429 --- CONTRIBUTING.md | 2 +- PROPERTIES.md | 454 ++++++++++++++++++++++++------------------------ README.md | 2 +- package.json | 4 +- 4 files changed, 231 insertions(+), 231 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d7bebae..4ee920a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ Bug reports and feature suggestions can be submitted to our issue tracker. For b ## Questions -Questions can be submitted to the issue tracker, but you may get a faster response if you ask in our [chat room](https://empireslacking.herokuapp.com/) (in the #ethereum channel). +Questions can be submitted to the issue tracker, but you may get a faster response if you ask in our [chat room](https://slack.empirehacking.nyc/) (in the #ethereum channel). ## Code diff --git a/PROPERTIES.md b/PROPERTIES.md index d384af4..9e55d78 100644 --- a/PROPERTIES.md +++ b/PROPERTIES.md @@ -267,235 +267,235 @@ This file lists all the currently implemented Echidna property tests for ERC20, | ID | Name | Invariant tested | | ------------| ----------------------------------------------------------------------------------------------------------------------------------------------------------| ------------------------------------------------------------------------------------------------------ | -| SD59x18-001 | [add_test_commutative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L255) | Commutative property for addition. | -| SD59x18-002 | [add_test_associative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L264) | Associative property for addition. | -| SD59x18-003 | [add_test_identity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L275) | Identity operation for addition. | -| SD59x18-004 | [add_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L284) | Addition result should increase or decrease depending on operands signs. | -| SD59x18-005 | [add_test_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L302) | Addition result should be in the valid 59x18-arithmetic range. | -| SD59x18-006 | [add_test_maximum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L312) | Addition edge case: maximum value plus zero should be maximum value. | -| SD59x18-007 | [add_test_maximum_value_plus_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L322) | Addition edge case: maximum value plus one should revert (out of range). | -| SD59x18-008 | [add_test_minimum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L332) | Addition edge case: minimum value plus zero should be minimum value. | -| SD59x18-009 | [add_test_minimum_value_plus_negative_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L342) | Addition edge case: minimum value plus minus one should revert (out of range). | -| SD59x18-010 | [sub_test_equivalence_to_addition](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L364) | Subtraction should be equal to addition with opposite sign. | -| SD59x18-011 | [sub_test_non_commutative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L374) | Anti-commutative property for subtraction. | -| SD59x18-012 | [sub_test_identity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L383) | Identity operation for subtraction. | -| SD59x18-013 | [sub_test_neutrality](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L392) | Adding and subtracting the same value should not affect original value. | -| SD59x18-014 | [sub_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L405) | Subtraction result should increase or decrease depending on operands signs. | -| SD59x18-015 | [sub_test_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L423) | Subtraction result should be in the valid 59x18-arithmetic range. | -| SD59x18-016 | [sub_test_maximum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L433) | Subtraction edge case: maximum value minus zero should be maximum value. | -| SD59x18-017 | [sub_test_maximum_value_minus_neg_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L444) | Subtraction edge case: maximum value minus negative one should revert (out of range). | -| SD59x18-018 | [sub_test_minimum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L454) | Subtraction edge case: minimum value minus zero should be minimum value. | -| SD59x18-019 | [sub_test_minimum_value_minus_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L464) | Subtraction edge case: minimum value minus one should revert (out of range). | -| SD59x18-020 | [mul_test_commutative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L486) | Commutative property for multiplication. | -| SD59x18-021 | [mul_test_associative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L496) | Associative property for multiplication. | -| SD59x18-022 | [mul_test_distributive](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L509) | Distributive property for multiplication. | -| SD59x18-023 | [mul_test_identity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L525) | Identity operation for multiplication. | -| SD59x18-024 | [mul_test_x_positive](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L537) | Multiplication result should increase or decrease depending on operands signs. | -| SD59x18-025 | [mul_test_x_negative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L551) | Multiplication result should increase or decrease depending on operands signs. | -| SD59x18-026 | [mul_test_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L571) | Multiplication result should be in the valid 59x18-arithmetic range. | -| SD59x18-027 | [mul_test_maximum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L583) | Multiplication edge case: maximum value times one should be maximum value | -| SD59x18-028 | [mul_test_minimum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L594) | Multiplication edge case: minimum value times one should be minimum value | -| SD59x18-029 | [div_test_division_identity_x_div_1](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L617) | Identity operation for division. x / 1 == x | -| SD59x18-030 | [div_test_division_identity_x_div_x](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L626) | Identity operation for division. x / x == 1 | -| SD59x18-031 | [div_test_negative_divisor](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L644) | Division result sign should change according to divisor sign. | -| SD59x18-032 | [div_test_division_num_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L656) | Division with zero numerator should be zero. | -| SD59x18-033 | [div_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L667) | Division result should increase or decrease (in absolute value) depending on divisor's absolute value. | -| SD59x18-034 | [div_test_div_by_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L688) | Division edge case: Divisor zero should revert. | -| SD59x18-035 | [div_test_maximum_denominator](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L699) | Division edge case: Division result by a large number should be less than one. | -| SD59x18-036 | [div_test_maximum_numerator](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L708) | Division edge case: Division result of maximum value should revert if divisor is less than one. | -| SD59x18-037 | [div_test_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L722) | Division result should be in the valid 64x64-arithmetic range. | -| SD59x18-038 | [neg_test_double_negation](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L749) | Double sign negation should be equal to the original operand. | -| SD59x18-039 | [neg_test_identity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L757) | Identity operation for sign negation. | -| SD59x18-040 | [neg_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L771) | Negation edge case: Negation of zero should be zero. | -| SD59x18-041 | [neg_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L779) | Negation edge case: Negation of maximum value minus epsilon should not revert. | -| SD59x18-042 | [neg_test_minimum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L789) | Negation edge case: Negation of minimum value plus epsilon should not revert. | -| SD59x18-043 | [abs_test_positive](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L811) | Absolute value should be always positive. | -| SD59x18-044 | [abs_test_negative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L820) | Absolute value of a number and its negation should be equal. | -| SD59x18-045 | [abs_test_multiplicativeness](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L831) | Multiplicativeness property for absolute value. | -| SD59x18-046 | [abs_test_subadditivity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L848) | Subadditivity property for absolute value. | -| SD59x18-047 | [abs_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L865) | Absolute value edge case: absolute value of zero is zero. | -| SD59x18-048 | [abs_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L879) | Absolute value edge case: absolute value of maximum value is maximum value. | -| SD59x18-049 | [abs_test_minimum_revert](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L892) | Absolute value edge case: absolute value of minimum value should revert | -| SD59x18-050 | [abs_test_minimum_allowed](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L902) | Absolute value edge case: absolute value of minimum permitted value is the negation of minimum value. | -| SD59x18-051 | [inv_test_double_inverse](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L929) | Result of double inverse should be _close enough_ to the original operand. | -| SD59x18-052 | [inv_test_division](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L941) | Inverse should be equivalent to division. | -| SD59x18-053 | [inv_test_division_noncommutativity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L952) | Anticommutative property for inverse operation. | -| SD59x18-054 | [inv_test_multiplication](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L966) | Multiplication of inverses should be equal to inverse of multiplication. | -| SD59x18-055 | [inv_test_identity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L984) | Identity property for inverse. | -| SD59x18-056 | [inv_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L997) | Inverse result should be in range (0, 1) if operand is greater than one. | -| SD59x18-057 | [inv_test_sign](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1011) | Inverse result should keep operand's sign. | -| SD59x18-058 | [inv_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1030) | Inverse edge case: Inverse of zero should revert. | -| SD59x18-059 | [inv_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1038) | Inverse edge case: Inverse of maximum value should be close to zero. | -| SD59x18-060 | [inv_test_minimum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1051) | Inverse edge case: Inverse of minimum value should be close to zero. | -| SD59x18-061 | [avg_test_values_in_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1077) | Average result should be between operands. | -| SD59x18-062 | [avg_test_one_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1089) | Average of the same number twice is the number itself. | -| SD59x18-063 | [avg_test_operand_order](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1097) | Average result does not depend on the order of operands. | -| SD59x18-064 | [avg_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1111) | Average edge case: Average of maximum value twice is the maximum value. | -| SD59x18-065 | [avg_test_minimum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1125) | Average edge case: Average of minimum value twice is the minimum value. | -| SD59x18-066 | [pow_test_zero_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1152) | Power of zero should be one. | -| SD59x18-067 | [pow_test_zero_base_non_zero_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1161) | Zero to the power of any number should be zero. | -| SD59x18-068 | [pow_test_zero_base_zero_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1170) | Zero to the power of zero should be one | -| SD59x18-069 | [pow_test_one_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1178) | Power of one should be equal to the operand. | -| SD59x18-070 | [pow_test_base_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1187) | One to the power of any number should be one. | -| SD59x18-071 | [pow_test_product_same_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1196) | Product of powers of the same base property | -| SD59x18-072 | [pow_test_power_of_an_exponentiation](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1217) | Power of an exponentiation property | -| SD59x18-073 | [pow_test_product_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1239) | Distributive property for power of a product | -| SD59x18-074 | [pow_test_positive_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1260) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | -| SD59x18-075 | [pow_test_negative_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1278) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | -| SD59x18-076 | [pow_test_sign](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1296) | Power result sign should change according to the exponent sign. | -| SD59x18-077 | [pow_test_exp2_equivalence](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1319) | Base-2 exponentiation should be equal to power. | -| SD59x18-078 | [pow_test_maximum_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1334) | Power edge case: Power of the maximum value should revert if exponent > 1. | -| SD59x18-079 | [pow_test_high_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1346) | Power edge case: Result should be zero if base is small and exponent is large. | -| SD59x18-080 | [sqrt_test_inverse_mul](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1368) | Square root inverse as multiplication. | -| SD59x18-081 | [sqrt_test_inverse_pow](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1386) | Square root inverse as power. | -| SD59x18-082 | [sqrt_test_distributive](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1404) | Square root distributive property respect to multiplication. | -| SD59x18-083 | [sqrt_test_is_increasing](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1424) | Square root should be strictly increasing for any x | -| SD59x18-084 | [sqrt_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1442) | Square root edge case: square root of zero should be zero. | -| SD59x18-085 | [sqrt_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1447) | Square root edge case: square root of maximum value should not revert. | -| SD59x18-086 | [sqrt_test_minimum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1458) | Square root edge case: square root of minimum value should revert (negative). | -| SD59x18-087 | [sqrt_test_negative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1468) | Square root edge case: square root of a negative value should revert. | -| SD59x18-088 | [log2_test_distributive_mul](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1493) | Base-2 logarithm distributive property respect to multiplication. | -| SD59x18-089 | [log2_test_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1514) | Base-2 logarithm of a power property. | -| SD59x18-090 | [log2_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1531) | Base-2 logarithm edge case: Logarithm of zero should revert. | -| SD59x18-091 | [log2_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1541) | Base-2 logarithm edge case: Logarithm of maximum value should not revert. | -| SD59x18-092 | [log2_test_negative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1555) | Base-2 logarithm edge case: Logarithm of a negative value should revert. | -| SD59x18-093 | [ln_test_distributive_mul](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1580) | Natural logarithm distributive property respect to multiplication. | -| SD59x18-094 | [ln_test_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1601) | Natural logarithm of a power property. | -| SD59x18-095 | [ln_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1621) | Natural logarithm edge case: Logarithm of zero should revert. | -| SD59x18-096 | [ln_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1631) | Natural logarithm edge case: Logarithm of maximum value should not revert. | -| SD59x18-097 | [ln_test_negative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1645) | Natural logarithm edge case: Logarithm of a negative value should revert. | -| SD59x18-098 | [exp2_test_equivalence_pow](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1670) | Base-2 exponentiation should be equal to power. | -| SD59x18-099 | [exp2_test_inverse](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1680) | Base-2 exponentiation inverse function. | -| SD59x18-100 | [exp2_test_negative_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1694) | Base-2 exponentiation with negative exponent should equal the inverse. | -| SD59x18-101 | [exp2_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1716) | Base-2 exponentiation edge case: exponent zero result should be one. | -| SD59x18-102 | [exp2_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1723) | Base-2 exponentiation edge case: exponent maximum value should revert. | -| SD59x18-103 | [exp2_test_maximum_permitted](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1734) | Base-2 exponentiation edge case: exponent maximum permitted value should not revert. | -| SD59x18-104 | [exp2_test_minimum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1745) | Base-2 exponentiation edge case: exponent minimum value result should be zero. | -| SD59x18-105 | [exp_test_inverse](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1772) | Natural exponentiation inverse function. | -| SD59x18-106 | [exp_test_negative_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1787) | Natural exponentiation with negative exponent should equal the inverse. | -| SD59x18-107 | [exp_test_strictly_increasing](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1798) | Natural exponentiation should be strictly increasing for any x | -| SD59x18-108 | [exp_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1816) | Natural exponentiation edge case: exponent zero result should be one. | -| SD59x18-109 | [exp_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1823) | Natural exponentiation edge case: exponent maximum value should revert. | -| SD59x18-110 | [exp_test_maximum_permitted](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1834) | Natural exponentiation edge case: exponent maximum value should revert. | -| SD59x18-111 | [exp_test_minimum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1845) | Natural exponentiation edge case: exponent minimum value result should be zero. | -| SD59x18-112 | [powu_test_zero_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1872) | Power of zero should be one. | -| SD59x18-113 | [powu_test_zero_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1882) | Zero to the power of any number should be zero. | -| SD59x18-114 | [powu_test_one_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1892) | Power of one should be equal to the operand. | -| SD59x18-115 | [powu_test_base_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1902) | One to the power of any number should be one. | -| SD59x18-116 | [powu_test_product_same_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1910) | Product of powers of the same base property | -| SD59x18-117 | [powu_test_power_of_an_exponentiation](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1927) | Power of an exponentiation property | -| SD59x18-118 | [powu_test_product_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1944) | Distributive property for power of a product | -| SD59x18-119 | [powu_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1965) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | -| SD59x18-120 | [powu_test_sign](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1982) | Power result sign should change according to the exponent sign. | -| SD59x18-121 | [powu_test_maximum_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2011) | Power edge case: Power of the maximum value should revert if exponent > 1. | -| SD59x18-122 | [powu_test_high_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2023) | Power edge case: Result should be zero if base is small and exponent is large. | -| SD59x18-123 | [log10_test_distributive_mul](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2045) | Base-10 logarithm distributive property respect to multiplication. | -| SD59x18-124 | [log10_test_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2066) | Base-10 logarithm of a power property. | -| SD59x18-125 | [log10_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2082) | Base-10 logarithm edge case: Logarithm of zero should revert. | -| SD59x18-126 | [log10_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2092) | Base-10 logarithm edge case: Logarithm of maximum value should not revert. | -| SD59x18-127 | [log10_test_negative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2106) | Base-10 logarithm edge case: Logarithm of a negative value should revert. | -| SD59x18-128 | [gm_test_product](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2131) | Product of the values should be equal to the geometric mean raised to the power of N | -| SD59x18-129 | [gm_test_positive_set_avg](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2145) | The geometric mean of a set of positive values should be less than the arithmetic mean | -| SD59x18-130 | [gm_test_positive_equal_set_avg](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2157) | The geometric mean of a set of equal positive values should be equal to the arithmetic mean | -| SD59x18-131 | [gm_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2174) | GM edge case: if a set contains zero, the result is zero | -| SD59x18-132 | [gm_test_negative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2187) | GM edge case: The geometric mean is not defined when the set contains an odd number of negative values | +| SD59x18-001 | [add_test_commutative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L255) | Commutative property for addition. | +| SD59x18-002 | [add_test_associative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L264) | Associative property for addition. | +| SD59x18-003 | [add_test_identity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L275) | Identity operation for addition. | +| SD59x18-004 | [add_test_values](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L284) | Addition result should increase or decrease depending on operands signs. | +| SD59x18-005 | [add_test_range](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L302) | Addition result should be in the valid 59x18-arithmetic range. | +| SD59x18-006 | [add_test_maximum_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L312) | Addition edge case: maximum value plus zero should be maximum value. | +| SD59x18-007 | [add_test_maximum_value_plus_one](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L322) | Addition edge case: maximum value plus one should revert (out of range). | +| SD59x18-008 | [add_test_minimum_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L332) | Addition edge case: minimum value plus zero should be minimum value. | +| SD59x18-009 | [add_test_minimum_value_plus_negative_one](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L342) | Addition edge case: minimum value plus minus one should revert (out of range). | +| SD59x18-010 | [sub_test_equivalence_to_addition](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L364) | Subtraction should be equal to addition with opposite sign. | +| SD59x18-011 | [sub_test_non_commutative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L374) | Anti-commutative property for subtraction. | +| SD59x18-012 | [sub_test_identity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L383) | Identity operation for subtraction. | +| SD59x18-013 | [sub_test_neutrality](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L392) | Adding and subtracting the same value should not affect original value. | +| SD59x18-014 | [sub_test_values](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L405) | Subtraction result should increase or decrease depending on operands signs. | +| SD59x18-015 | [sub_test_range](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L423) | Subtraction result should be in the valid 59x18-arithmetic range. | +| SD59x18-016 | [sub_test_maximum_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L433) | Subtraction edge case: maximum value minus zero should be maximum value. | +| SD59x18-017 | [sub_test_maximum_value_minus_neg_one](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L444) | Subtraction edge case: maximum value minus negative one should revert (out of range). | +| SD59x18-018 | [sub_test_minimum_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L454) | Subtraction edge case: minimum value minus zero should be minimum value. | +| SD59x18-019 | [sub_test_minimum_value_minus_one](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L464) | Subtraction edge case: minimum value minus one should revert (out of range). | +| SD59x18-020 | [mul_test_commutative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L486) | Commutative property for multiplication. | +| SD59x18-021 | [mul_test_associative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L496) | Associative property for multiplication. | +| SD59x18-022 | [mul_test_distributive](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L509) | Distributive property for multiplication. | +| SD59x18-023 | [mul_test_identity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L525) | Identity operation for multiplication. | +| SD59x18-024 | [mul_test_x_positive](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L537) | Multiplication result should increase or decrease depending on operands signs. | +| SD59x18-025 | [mul_test_x_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L551) | Multiplication result should increase or decrease depending on operands signs. | +| SD59x18-026 | [mul_test_range](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L571) | Multiplication result should be in the valid 59x18-arithmetic range. | +| SD59x18-027 | [mul_test_maximum_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L583) | Multiplication edge case: maximum value times one should be maximum value | +| SD59x18-028 | [mul_test_minimum_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L594) | Multiplication edge case: minimum value times one should be minimum value | +| SD59x18-029 | [div_test_division_identity_x_div_1](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L617) | Identity operation for division. x / 1 == x | +| SD59x18-030 | [div_test_division_identity_x_div_x](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L626) | Identity operation for division. x / x == 1 | +| SD59x18-031 | [div_test_negative_divisor](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L644) | Division result sign should change according to divisor sign. | +| SD59x18-032 | [div_test_division_num_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L656) | Division with zero numerator should be zero. | +| SD59x18-033 | [div_test_values](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L667) | Division result should increase or decrease (in absolute value) depending on divisor's absolute value. | +| SD59x18-034 | [div_test_div_by_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L688) | Division edge case: Divisor zero should revert. | +| SD59x18-035 | [div_test_maximum_denominator](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L699) | Division edge case: Division result by a large number should be less than one. | +| SD59x18-036 | [div_test_maximum_numerator](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L708) | Division edge case: Division result of maximum value should revert if divisor is less than one. | +| SD59x18-037 | [div_test_range](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L722) | Division result should be in the valid 64x64-arithmetic range. | +| SD59x18-038 | [neg_test_double_negation](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L749) | Double sign negation should be equal to the original operand. | +| SD59x18-039 | [neg_test_identity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L757) | Identity operation for sign negation. | +| SD59x18-040 | [neg_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L771) | Negation edge case: Negation of zero should be zero. | +| SD59x18-041 | [neg_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L779) | Negation edge case: Negation of maximum value minus epsilon should not revert. | +| SD59x18-042 | [neg_test_minimum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L789) | Negation edge case: Negation of minimum value plus epsilon should not revert. | +| SD59x18-043 | [abs_test_positive](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L811) | Absolute value should be always positive. | +| SD59x18-044 | [abs_test_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L820) | Absolute value of a number and its negation should be equal. | +| SD59x18-045 | [abs_test_multiplicativeness](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L831) | Multiplicativeness property for absolute value. | +| SD59x18-046 | [abs_test_subadditivity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L848) | Subadditivity property for absolute value. | +| SD59x18-047 | [abs_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L865) | Absolute value edge case: absolute value of zero is zero. | +| SD59x18-048 | [abs_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L879) | Absolute value edge case: absolute value of maximum value is maximum value. | +| SD59x18-049 | [abs_test_minimum_revert](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L892) | Absolute value edge case: absolute value of minimum value should revert | +| SD59x18-050 | [abs_test_minimum_allowed](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L902) | Absolute value edge case: absolute value of minimum permitted value is the negation of minimum value. | +| SD59x18-051 | [inv_test_double_inverse](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L929) | Result of double inverse should be _close enough_ to the original operand. | +| SD59x18-052 | [inv_test_division](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L941) | Inverse should be equivalent to division. | +| SD59x18-053 | [inv_test_division_noncommutativity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L952) | Anticommutative property for inverse operation. | +| SD59x18-054 | [inv_test_multiplication](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L966) | Multiplication of inverses should be equal to inverse of multiplication. | +| SD59x18-055 | [inv_test_identity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L984) | Identity property for inverse. | +| SD59x18-056 | [inv_test_values](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L997) | Inverse result should be in range (0, 1) if operand is greater than one. | +| SD59x18-057 | [inv_test_sign](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1011) | Inverse result should keep operand's sign. | +| SD59x18-058 | [inv_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1030) | Inverse edge case: Inverse of zero should revert. | +| SD59x18-059 | [inv_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1038) | Inverse edge case: Inverse of maximum value should be close to zero. | +| SD59x18-060 | [inv_test_minimum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1051) | Inverse edge case: Inverse of minimum value should be close to zero. | +| SD59x18-061 | [avg_test_values_in_range](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1077) | Average result should be between operands. | +| SD59x18-062 | [avg_test_one_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1089) | Average of the same number twice is the number itself. | +| SD59x18-063 | [avg_test_operand_order](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1097) | Average result does not depend on the order of operands. | +| SD59x18-064 | [avg_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1111) | Average edge case: Average of maximum value twice is the maximum value. | +| SD59x18-065 | [avg_test_minimum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1125) | Average edge case: Average of minimum value twice is the minimum value. | +| SD59x18-066 | [pow_test_zero_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1152) | Power of zero should be one. | +| SD59x18-067 | [pow_test_zero_base_non_zero_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1161) | Zero to the power of any number should be zero. | +| SD59x18-068 | [pow_test_zero_base_zero_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1170) | Zero to the power of zero should be one | +| SD59x18-069 | [pow_test_one_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1178) | Power of one should be equal to the operand. | +| SD59x18-070 | [pow_test_base_one](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1187) | One to the power of any number should be one. | +| SD59x18-071 | [pow_test_product_same_base](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1196) | Product of powers of the same base property | +| SD59x18-072 | [pow_test_power_of_an_exponentiation](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1217) | Power of an exponentiation property | +| SD59x18-073 | [pow_test_product_power](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1239) | Distributive property for power of a product | +| SD59x18-074 | [pow_test_positive_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1260) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | +| SD59x18-075 | [pow_test_negative_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1278) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | +| SD59x18-076 | [pow_test_sign](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1296) | Power result sign should change according to the exponent sign. | +| SD59x18-077 | [pow_test_exp2_equivalence](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1319) | Base-2 exponentiation should be equal to power. | +| SD59x18-078 | [pow_test_maximum_base](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1334) | Power edge case: Power of the maximum value should revert if exponent > 1. | +| SD59x18-079 | [pow_test_high_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1346) | Power edge case: Result should be zero if base is small and exponent is large. | +| SD59x18-080 | [sqrt_test_inverse_mul](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1368) | Square root inverse as multiplication. | +| SD59x18-081 | [sqrt_test_inverse_pow](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1386) | Square root inverse as power. | +| SD59x18-082 | [sqrt_test_distributive](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1404) | Square root distributive property respect to multiplication. | +| SD59x18-083 | [sqrt_test_is_increasing](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1424) | Square root should be strictly increasing for any x | +| SD59x18-084 | [sqrt_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1442) | Square root edge case: square root of zero should be zero. | +| SD59x18-085 | [sqrt_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1447) | Square root edge case: square root of maximum value should not revert. | +| SD59x18-086 | [sqrt_test_minimum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1458) | Square root edge case: square root of minimum value should revert (negative). | +| SD59x18-087 | [sqrt_test_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1468) | Square root edge case: square root of a negative value should revert. | +| SD59x18-088 | [log2_test_distributive_mul](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1493) | Base-2 logarithm distributive property respect to multiplication. | +| SD59x18-089 | [log2_test_power](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1514) | Base-2 logarithm of a power property. | +| SD59x18-090 | [log2_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1531) | Base-2 logarithm edge case: Logarithm of zero should revert. | +| SD59x18-091 | [log2_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1541) | Base-2 logarithm edge case: Logarithm of maximum value should not revert. | +| SD59x18-092 | [log2_test_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1555) | Base-2 logarithm edge case: Logarithm of a negative value should revert. | +| SD59x18-093 | [ln_test_distributive_mul](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1580) | Natural logarithm distributive property respect to multiplication. | +| SD59x18-094 | [ln_test_power](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1601) | Natural logarithm of a power property. | +| SD59x18-095 | [ln_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1621) | Natural logarithm edge case: Logarithm of zero should revert. | +| SD59x18-096 | [ln_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1631) | Natural logarithm edge case: Logarithm of maximum value should not revert. | +| SD59x18-097 | [ln_test_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1645) | Natural logarithm edge case: Logarithm of a negative value should revert. | +| SD59x18-098 | [exp2_test_equivalence_pow](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1670) | Base-2 exponentiation should be equal to power. | +| SD59x18-099 | [exp2_test_inverse](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1680) | Base-2 exponentiation inverse function. | +| SD59x18-100 | [exp2_test_negative_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1694) | Base-2 exponentiation with negative exponent should equal the inverse. | +| SD59x18-101 | [exp2_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1716) | Base-2 exponentiation edge case: exponent zero result should be one. | +| SD59x18-102 | [exp2_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1723) | Base-2 exponentiation edge case: exponent maximum value should revert. | +| SD59x18-103 | [exp2_test_maximum_permitted](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1734) | Base-2 exponentiation edge case: exponent maximum permitted value should not revert. | +| SD59x18-104 | [exp2_test_minimum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1745) | Base-2 exponentiation edge case: exponent minimum value result should be zero. | +| SD59x18-105 | [exp_test_inverse](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1772) | Natural exponentiation inverse function. | +| SD59x18-106 | [exp_test_negative_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1787) | Natural exponentiation with negative exponent should equal the inverse. | +| SD59x18-107 | [exp_test_strictly_increasing](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1798) | Natural exponentiation should be strictly increasing for any x | +| SD59x18-108 | [exp_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1816) | Natural exponentiation edge case: exponent zero result should be one. | +| SD59x18-109 | [exp_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1823) | Natural exponentiation edge case: exponent maximum value should revert. | +| SD59x18-110 | [exp_test_maximum_permitted](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1834) | Natural exponentiation edge case: exponent maximum value should revert. | +| SD59x18-111 | [exp_test_minimum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1845) | Natural exponentiation edge case: exponent minimum value result should be zero. | +| SD59x18-112 | [powu_test_zero_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1872) | Power of zero should be one. | +| SD59x18-113 | [powu_test_zero_base](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1882) | Zero to the power of any number should be zero. | +| SD59x18-114 | [powu_test_one_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1892) | Power of one should be equal to the operand. | +| SD59x18-115 | [powu_test_base_one](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1902) | One to the power of any number should be one. | +| SD59x18-116 | [powu_test_product_same_base](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1910) | Product of powers of the same base property | +| SD59x18-117 | [powu_test_power_of_an_exponentiation](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1927) | Power of an exponentiation property | +| SD59x18-118 | [powu_test_product_power](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1944) | Distributive property for power of a product | +| SD59x18-119 | [powu_test_values](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1965) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | +| SD59x18-120 | [powu_test_sign](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1982) | Power result sign should change according to the exponent sign. | +| SD59x18-121 | [powu_test_maximum_base](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2011) | Power edge case: Power of the maximum value should revert if exponent > 1. | +| SD59x18-122 | [powu_test_high_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2023) | Power edge case: Result should be zero if base is small and exponent is large. | +| SD59x18-123 | [log10_test_distributive_mul](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2045) | Base-10 logarithm distributive property respect to multiplication. | +| SD59x18-124 | [log10_test_power](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2066) | Base-10 logarithm of a power property. | +| SD59x18-125 | [log10_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2082) | Base-10 logarithm edge case: Logarithm of zero should revert. | +| SD59x18-126 | [log10_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2092) | Base-10 logarithm edge case: Logarithm of maximum value should not revert. | +| SD59x18-127 | [log10_test_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2106) | Base-10 logarithm edge case: Logarithm of a negative value should revert. | +| SD59x18-128 | [gm_test_product](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2131) | Product of the values should be equal to the geometric mean raised to the power of N | +| SD59x18-129 | [gm_test_positive_set_avg](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2145) | The geometric mean of a set of positive values should be less than the arithmetic mean | +| SD59x18-130 | [gm_test_positive_equal_set_avg](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2157) | The geometric mean of a set of equal positive values should be equal to the arithmetic mean | +| SD59x18-131 | [gm_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2174) | GM edge case: if a set contains zero, the result is zero | +| SD59x18-132 | [gm_test_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2187) | GM edge case: The geometric mean is not defined when the set contains an odd number of negative values | ## PRBMath UD60x18 | ID | Name | Invariant tested | | ------------| ----------------------------------------------------------------------------------------------------------------------------------------------------------| ------------------------------------------------------------------------------------------------------ | -| UD60x18-001 | [add_test_commutative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L228) | Commutative property for addition. | -| UD60x18-002 | [add_test_associative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L237) | Associative property for addition. | -| UD60x18-003 | [add_test_identity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L248) | Identity operation for addition. | -| UD60x18-004 | [add_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L257) | Addition result should increase or decrease depending on operands signs. | -| UD60x18-005 | [add_test_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L271) | Addition result should be in the valid 60x18-arithmetic range. | -| UD60x18-006 | [add_test_maximum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L281) | Addition edge case: maximum value plus zero should be maximum value. | -| UD60x18-007 | [add_test_maximum_value_plus_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L291) | Addition edge case: maximum value plus one should revert (out of range). | -| UD60x18-008 | [add_test_minimum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L301) | Addition edge case: minimum value plus zero should be minimum value. | -| UD60x18-009 | [sub_test_identity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L324) | Identity operation for subtraction. | -| UD60x18-010 | [sub_test_neutrality](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L333) | Adding and subtracting the same value should not affect original value. | -| UD60x18-011 | [sub_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L345) | Subtraction result should increase or decrease depending on operands signs. | -| UD60x18-012 | [sub_test_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L359) | Subtraction result should be in the valid 60x18-arithmetic range. | -| UD60x18-013 | [sub_test_maximum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L369) | Subtraction edge case: maximum value minus zero should be maximum value. | -| UD60x18-014 | [sub_test_minimum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L380) | Subtraction edge case: minimum value minus zero should be minimum value. | -| UD60x18-015 | [sub_test_minimum_value_minus_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L390) | Subtraction edge case: minimum value minus one should revert (out of range). | -| UD60x18-016 | [mul_test_commutative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L412) | Commutative property for multiplication. | -| UD60x18-017 | [mul_test_associative](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L421) | Associative property for multiplication. | -| UD60x18-018 | [mul_test_distributive](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L433) | Distributive property for multiplication. | -| UD60x18-019 | [mul_test_identity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L445) | Identity operation for multiplication. | -| UD60x18-020 | [mul_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L455) | Multiplication result should increase or decrease depending on operands signs. | -| UD60x18-021 | [mul_test_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L473) | Multiplication result should be in the valid 59x18-arithmetic range. | -| UD60x18-022 | [mul_test_maximum_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L483) | Multiplication edge case: maximum value times one should be maximum value | -| UD60x18-023 | [div_test_division_identity_x_div_1](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L506) | Identity operation for division. x / 1 == x | -| UD60x18-024 | [div_test_division_identity_x_div_x](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L514) | Identity operation for division. x / x == 1 | -| UD60x18-025 | [div_test_division_num_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L529) | Division with zero numerator should be zero. | -| UD60x18-026 | [div_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L539) | Division result should increase or decrease (in absolute value) depending on divisor's absolute value. | -| UD60x18-027 | [div_test_div_by_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L558) | Division edge case: Divisor zero should revert. | -| UD60x18-028 | [div_test_maximum_denominator](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L568) | Division edge case: Division result by a large number should be less than one. | -| UD60x18-029 | [div_test_maximum_numerator](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L576) | Division edge case: Division result of maximum value should revert if divisor is less than one. | -| UD60x18-030 | [div_test_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L591) | Division result should be in the valid 60x18-arithmetic range. | -| UD60x18-031 | [inv_test_double_inverse](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L618) | Result of double inverse should be _close enough_ to the original operand. | -| UD60x18-032 | [inv_test_division](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L630) | Inverse should be equivalent to division. | -| UD60x18-033 | [inv_test_division_noncommutativity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L641) | Anticommutative property for inverse operation. | -| UD60x18-034 | [inv_test_multiplication](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L655) | Multiplication of inverses should be equal to inverse of multiplication. | -| UD60x18-035 | [inv_test_identity](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L673) | Identity property for inverse. | -| UD60x18-036 | [inv_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L688) | Inverse result should be in range (0, 1) if operand is greater than one. | -| UD60x18-037 | [inv_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L707) | Inverse edge case: Inverse of zero should revert. | -| UD60x18-038 | [inv_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L715) | Inverse edge case: Inverse of maximum value should be close to zero. | -| UD60x18-039 | [inv_test_minimum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L728) | Inverse edge case: Inverse of minimum value should be close to zero. | -| UD60x18-040 | [avg_test_values_in_range](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L754) | Average result should be between operands. | -| UD60x18-041 | [avg_test_one_value](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L766) | Average of the same number twice is the number itself. | -| UD60x18-042 | [avg_test_operand_order](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L774) | Average result does not depend on the order of operands. | -| UD60x18-043 | [avg_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L788) | Average edge case: Average of maximum value twice is the maximum value. | -| UD60x18-044 | [pow_test_zero_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L815) | Power of zero should be one. | -| UD60x18-045 | [pow_test_zero_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L824) | Zero to the power of any number should be zero. | -| UD60x18-046 | [pow_test_one_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L835) | Power of one should be equal to the operand. | -| UD60x18-047 | [pow_test_base_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L844) | One to the power of any number should be one. | -| UD60x18-048 | [pow_test_product_same_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L852) | Product of powers of the same base property | -| UD60x18-049 | [pow_test_power_of_an_exponentiation](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L873) | Power of an exponentiation property | -| UD60x18-050 | [pow_test_product_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L894) | Distributive property for power of a product | -| UD60x18-051 | [pow_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L918) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | -| UD60x18-052 | [pow_test_maximum_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L940) | Power edge case: Power of the maximum value should revert if exponent > 1. | -| UD60x18-053 | [pow_test_maximum_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L952) | Power edge case: Power of the maximum value should revert if exponent > 1. | -| UD60x18-054 | [pow_test_high_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L965) | Power edge case: Result should be zero if base is small and exponent is large. | -| UD60x18-055 | [sqrt_test_inverse_mul](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L987) | Square root inverse as multiplication. | -| UD60x18-056 | [sqrt_test_inverse_pow](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1004) | Square root inverse as power. | -| UD60x18-057 | [sqrt_test_distributive](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1021) | Square root distributive property respect to multiplication. | -| UD60x18-058 | [sqrt_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1039) | Square root edge case: square root of zero should be zero. | -| UD60x18-059 | [sqrt_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1044) | Square root edge case: square root of maximum value should not revert. | -| UD60x18-060 | [log2_test_distributive_mul](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1068) | Base-2 logarithm distributive property respect to multiplication. | -| UD60x18-061 | [log2_test_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1087) | Base-2 logarithm of a power property. | -| UD60x18-062 | [log2_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1109) | Base-2 logarithm edge case: Logarithm of zero should revert. | -| UD60x18-063 | [log2_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1119) | Base-2 logarithm edge case: Logarithm of maximum value should not revert. | -| UD60x18-064 | [log2_test_less_than_unit](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1133) | Base-2 logarithm edge case: Logarithm of a negative value should revert. | -| UD60x18-065 | [ln_test_distributive_mul](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1158) | Natural logarithm distributive property respect to multiplication. | -| UD60x18-066 | [ln_test_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1179) | Natural logarithm of a power property. | -| UD60x18-067 | [ln_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1199) | Natural logarithm edge case: Logarithm of zero should revert. | -| UD60x18-068 | [ln_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1209) | Natural logarithm edge case: Logarithm of maximum value should not revert. | -| UD60x18-069 | [ln_test_less_than_unit](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1223) | Natural logarithm edge case: Logarithm of a negative value should revert. | -| UD60x18-070 | [exp2_test_equivalence_pow](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1248) | Base-2 exponentiation should be equal to power. | -| UD60x18-071 | [exp2_test_inverse](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1258) | Base-2 exponentiation inverse function. | -| UD60x18-072 | [exp2_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1279) | Base-2 exponentiation edge case: exponent zero result should be one. | -| UD60x18-073 | [exp2_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1286) | Base-2 exponentiation edge case: exponent maximum value should revert. | -| UD60x18-075 | [exp_test_inverse](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1309) | Natural exponentiation inverse function. | -| UD60x18-076 | [exp_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1331) | Natural exponentiation edge case: exponent zero result should be one. | -| UD60x18-077 | [exp_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1338) | Natural exponentiation edge case: exponent maximum value should revert. | -| UD60x18-078 | [powu_test_zero_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1361) | Power of zero should be one. | -| UD60x18-079 | [powu_test_zero_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1369) | Zero to the power of any number should be zero. | -| UD60x18-080 | [powu_test_one_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1379) | Power of one should be equal to the operand. | -| UD60x18-081 | [powu_test_base_one](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1387) | One to the power of any number should be one. | -| UD60x18-082 | [powu_test_product_same_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1395) | Product of powers of the same base property | -| UD60x18-083 | [powu_test_power_of_an_exponentiation](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1411) | Power of an exponentiation property | -| UD60x18-084 | [powu_test_product_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1427) | Distributive property for power of a product | -| UD60x18-085 | [powu_test_values](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1447) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | -| UD60x18-086 | [powu_test_maximum_base](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1468) | Power edge case: Power of the maximum value should revert if exponent > 1. | -| UD60x18-087 | [powu_test_high_exponent](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1480) | Power edge case: Result should be zero if base is small and exponent is large. | -| UD60x18-088 | [log10_test_distributive_mul](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1502) | Base-10 logarithm distributive property respect to multiplication. | -| UD60x18-089 | [log10_test_power](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1523) | Base-10 logarithm of a power property. | -| UD60x18-090 | [log10_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1543) | Base-10 logarithm edge case: Logarithm of zero should revert. | -| UD60x18-091 | [log10_test_maximum](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1553) | Base-10 logarithm edge case: Logarithm of maximum value should not revert. | -| UD60x18-092 | [log10_test_less_than_unit](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1567) | Base-10 logarithm edge case: Logarithm of a negative value should revert. | -| UD60x18-093 | [gm_test_product](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1592) | Product of the values should be equal to the geometric mean raised to the power of N | -| UD60x18-094 | [gm_test_positive_set_avg](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1602) | The geometric mean of a set of positive values should be less than the arithmetic mean | -| UD60x18-095 | [gm_test_positive_equal_set_avg](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1614) | The geometric mean of a set of equal positive values should be equal to the arithmetic mean | -| UD60x18-096 | [gm_test_zero](https://github.com/crytic/properties/blob/main/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1629) | GM edge case: if a set contains zero, the result is zero | \ No newline at end of file +| UD60x18-001 | [add_test_commutative](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L228) | Commutative property for addition. | +| UD60x18-002 | [add_test_associative](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L237) | Associative property for addition. | +| UD60x18-003 | [add_test_identity](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L248) | Identity operation for addition. | +| UD60x18-004 | [add_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L257) | Addition result should increase or decrease depending on operands signs. | +| UD60x18-005 | [add_test_range](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L271) | Addition result should be in the valid 60x18-arithmetic range. | +| UD60x18-006 | [add_test_maximum_value](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L281) | Addition edge case: maximum value plus zero should be maximum value. | +| UD60x18-007 | [add_test_maximum_value_plus_one](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L291) | Addition edge case: maximum value plus one should revert (out of range). | +| UD60x18-008 | [add_test_minimum_value](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L301) | Addition edge case: minimum value plus zero should be minimum value. | +| UD60x18-009 | [sub_test_identity](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L324) | Identity operation for subtraction. | +| UD60x18-010 | [sub_test_neutrality](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L333) | Adding and subtracting the same value should not affect original value. | +| UD60x18-011 | [sub_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L345) | Subtraction result should increase or decrease depending on operands signs. | +| UD60x18-012 | [sub_test_range](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L359) | Subtraction result should be in the valid 60x18-arithmetic range. | +| UD60x18-013 | [sub_test_maximum_value](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L369) | Subtraction edge case: maximum value minus zero should be maximum value. | +| UD60x18-014 | [sub_test_minimum_value](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L380) | Subtraction edge case: minimum value minus zero should be minimum value. | +| UD60x18-015 | [sub_test_minimum_value_minus_one](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L390) | Subtraction edge case: minimum value minus one should revert (out of range). | +| UD60x18-016 | [mul_test_commutative](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L412) | Commutative property for multiplication. | +| UD60x18-017 | [mul_test_associative](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L421) | Associative property for multiplication. | +| UD60x18-018 | [mul_test_distributive](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L433) | Distributive property for multiplication. | +| UD60x18-019 | [mul_test_identity](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L445) | Identity operation for multiplication. | +| UD60x18-020 | [mul_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L455) | Multiplication result should increase or decrease depending on operands signs. | +| UD60x18-021 | [mul_test_range](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L473) | Multiplication result should be in the valid 59x18-arithmetic range. | +| UD60x18-022 | [mul_test_maximum_value](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L483) | Multiplication edge case: maximum value times one should be maximum value | +| UD60x18-023 | [div_test_division_identity_x_div_1](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L506) | Identity operation for division. x / 1 == x | +| UD60x18-024 | [div_test_division_identity_x_div_x](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L514) | Identity operation for division. x / x == 1 | +| UD60x18-025 | [div_test_division_num_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L529) | Division with zero numerator should be zero. | +| UD60x18-026 | [div_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L539) | Division result should increase or decrease (in absolute value) depending on divisor's absolute value. | +| UD60x18-027 | [div_test_div_by_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L558) | Division edge case: Divisor zero should revert. | +| UD60x18-028 | [div_test_maximum_denominator](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L568) | Division edge case: Division result by a large number should be less than one. | +| UD60x18-029 | [div_test_maximum_numerator](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L576) | Division edge case: Division result of maximum value should revert if divisor is less than one. | +| UD60x18-030 | [div_test_range](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L591) | Division result should be in the valid 60x18-arithmetic range. | +| UD60x18-031 | [inv_test_double_inverse](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L618) | Result of double inverse should be _close enough_ to the original operand. | +| UD60x18-032 | [inv_test_division](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L630) | Inverse should be equivalent to division. | +| UD60x18-033 | [inv_test_division_noncommutativity](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L641) | Anticommutative property for inverse operation. | +| UD60x18-034 | [inv_test_multiplication](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L655) | Multiplication of inverses should be equal to inverse of multiplication. | +| UD60x18-035 | [inv_test_identity](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L673) | Identity property for inverse. | +| UD60x18-036 | [inv_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L688) | Inverse result should be in range (0, 1) if operand is greater than one. | +| UD60x18-037 | [inv_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L707) | Inverse edge case: Inverse of zero should revert. | +| UD60x18-038 | [inv_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L715) | Inverse edge case: Inverse of maximum value should be close to zero. | +| UD60x18-039 | [inv_test_minimum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L728) | Inverse edge case: Inverse of minimum value should be close to zero. | +| UD60x18-040 | [avg_test_values_in_range](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L754) | Average result should be between operands. | +| UD60x18-041 | [avg_test_one_value](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L766) | Average of the same number twice is the number itself. | +| UD60x18-042 | [avg_test_operand_order](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L774) | Average result does not depend on the order of operands. | +| UD60x18-043 | [avg_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L788) | Average edge case: Average of maximum value twice is the maximum value. | +| UD60x18-044 | [pow_test_zero_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L815) | Power of zero should be one. | +| UD60x18-045 | [pow_test_zero_base](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L824) | Zero to the power of any number should be zero. | +| UD60x18-046 | [pow_test_one_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L835) | Power of one should be equal to the operand. | +| UD60x18-047 | [pow_test_base_one](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L844) | One to the power of any number should be one. | +| UD60x18-048 | [pow_test_product_same_base](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L852) | Product of powers of the same base property | +| UD60x18-049 | [pow_test_power_of_an_exponentiation](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L873) | Power of an exponentiation property | +| UD60x18-050 | [pow_test_product_power](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L894) | Distributive property for power of a product | +| UD60x18-051 | [pow_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L918) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | +| UD60x18-052 | [pow_test_maximum_base](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L940) | Power edge case: Power of the maximum value should revert if exponent > 1. | +| UD60x18-053 | [pow_test_maximum_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L952) | Power edge case: Power of the maximum value should revert if exponent > 1. | +| UD60x18-054 | [pow_test_high_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L965) | Power edge case: Result should be zero if base is small and exponent is large. | +| UD60x18-055 | [sqrt_test_inverse_mul](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L987) | Square root inverse as multiplication. | +| UD60x18-056 | [sqrt_test_inverse_pow](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1004) | Square root inverse as power. | +| UD60x18-057 | [sqrt_test_distributive](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1021) | Square root distributive property respect to multiplication. | +| UD60x18-058 | [sqrt_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1039) | Square root edge case: square root of zero should be zero. | +| UD60x18-059 | [sqrt_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1044) | Square root edge case: square root of maximum value should not revert. | +| UD60x18-060 | [log2_test_distributive_mul](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1068) | Base-2 logarithm distributive property respect to multiplication. | +| UD60x18-061 | [log2_test_power](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1087) | Base-2 logarithm of a power property. | +| UD60x18-062 | [log2_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1109) | Base-2 logarithm edge case: Logarithm of zero should revert. | +| UD60x18-063 | [log2_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1119) | Base-2 logarithm edge case: Logarithm of maximum value should not revert. | +| UD60x18-064 | [log2_test_less_than_unit](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1133) | Base-2 logarithm edge case: Logarithm of a negative value should revert. | +| UD60x18-065 | [ln_test_distributive_mul](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1158) | Natural logarithm distributive property respect to multiplication. | +| UD60x18-066 | [ln_test_power](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1179) | Natural logarithm of a power property. | +| UD60x18-067 | [ln_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1199) | Natural logarithm edge case: Logarithm of zero should revert. | +| UD60x18-068 | [ln_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1209) | Natural logarithm edge case: Logarithm of maximum value should not revert. | +| UD60x18-069 | [ln_test_less_than_unit](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1223) | Natural logarithm edge case: Logarithm of a negative value should revert. | +| UD60x18-070 | [exp2_test_equivalence_pow](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1248) | Base-2 exponentiation should be equal to power. | +| UD60x18-071 | [exp2_test_inverse](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1258) | Base-2 exponentiation inverse function. | +| UD60x18-072 | [exp2_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1279) | Base-2 exponentiation edge case: exponent zero result should be one. | +| UD60x18-073 | [exp2_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1286) | Base-2 exponentiation edge case: exponent maximum value should revert. | +| UD60x18-075 | [exp_test_inverse](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1309) | Natural exponentiation inverse function. | +| UD60x18-076 | [exp_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1331) | Natural exponentiation edge case: exponent zero result should be one. | +| UD60x18-077 | [exp_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1338) | Natural exponentiation edge case: exponent maximum value should revert. | +| UD60x18-078 | [powu_test_zero_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1361) | Power of zero should be one. | +| UD60x18-079 | [powu_test_zero_base](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1369) | Zero to the power of any number should be zero. | +| UD60x18-080 | [powu_test_one_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1379) | Power of one should be equal to the operand. | +| UD60x18-081 | [powu_test_base_one](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1387) | One to the power of any number should be one. | +| UD60x18-082 | [powu_test_product_same_base](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1395) | Product of powers of the same base property | +| UD60x18-083 | [powu_test_power_of_an_exponentiation](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1411) | Power of an exponentiation property | +| UD60x18-084 | [powu_test_product_power](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1427) | Distributive property for power of a product | +| UD60x18-085 | [powu_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1447) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | +| UD60x18-086 | [powu_test_maximum_base](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1468) | Power edge case: Power of the maximum value should revert if exponent > 1. | +| UD60x18-087 | [powu_test_high_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1480) | Power edge case: Result should be zero if base is small and exponent is large. | +| UD60x18-088 | [log10_test_distributive_mul](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1502) | Base-10 logarithm distributive property respect to multiplication. | +| UD60x18-089 | [log10_test_power](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1523) | Base-10 logarithm of a power property. | +| UD60x18-090 | [log10_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1543) | Base-10 logarithm edge case: Logarithm of zero should revert. | +| UD60x18-091 | [log10_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1553) | Base-10 logarithm edge case: Logarithm of maximum value should not revert. | +| UD60x18-092 | [log10_test_less_than_unit](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1567) | Base-10 logarithm edge case: Logarithm of a negative value should revert. | +| UD60x18-093 | [gm_test_product](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1592) | Product of the values should be equal to the geometric mean raised to the power of N | +| UD60x18-094 | [gm_test_positive_set_avg](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1602) | The geometric mean of a set of positive values should be less than the arithmetic mean | +| UD60x18-095 | [gm_test_positive_equal_set_avg](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1614) | The geometric mean of a set of equal positive values should be equal to the arithmetic mean | +| UD60x18-096 | [gm_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1629) | GM edge case: if a set contains zero, the result is zero | \ No newline at end of file diff --git a/README.md b/README.md index 9027172..c371577 100644 --- a/README.md +++ b/README.md @@ -458,7 +458,7 @@ Run the test suite using `echidna-test . --contract CryticPRBMath59x18Harness -- ## Additional resources - [Building secure contracts](https://secure-contracts.com/program-analysis/index.html) -- Our [EmpireSlacking](https://empireslacking.herokuapp.com/) slack server, channel #ethereum +- Our [EmpireSlacking](https://slack.empirehacking.nyc/) slack server, channel #ethereum - Watch our [fuzzing workshop](https://www.youtube.com/watch?v=QofNQxW_K08&list=PLciHOL_J7Iwqdja9UH4ZzE8dP1IxtsBXI) # Helper functions diff --git a/package.json b/package.json index d25d60f..c1c454a 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "format-embedded-solidity": "prettier --write \"**/*.md\" --embedded-language-formatting=auto --plugin prettier-plugin-solidity --tab-width 4 --print-width 120 && prettier --write \"**/*.md\"", "lint": "npm run lint-check-format && npm run lint-check-links", "lint-check-format": "prettier --check .", - "lint-check-links": "find . -name '*.md' -not -path './node_modules/*' -print0 | xargs -0 -n1 markdown-link-check", + "lint-check-links": "find . -name '*.md' -not -path './node_modules/*' -print0 | xargs -0 -n1 markdown-link-check -r", "echidna-prb-ud-v3": "echidna . --contract CryticPRBMath60x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-prb-ud-v3", "echidna-prb-sd-v3": "echidna . --contract CryticPRBMath59x18Propertiesv3 --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-prb-sd-v3", "echidna-prb-ud-v4": "echidna . --contract CryticPRBMath60x18Propertiesv4 --config ./contracts/Math/echidna-config.yaml --corpus-dir corpus-prb-ud-v4", @@ -37,4 +37,4 @@ "devDependencies": { "hardhat": "^2.9.3" } -} +} \ No newline at end of file From ac203b43a098c29a4d8832de1b4a2fc17ac17fce Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Thu, 6 Jul 2023 17:01:54 +0200 Subject: [PATCH 29/32] changed helpers and some error bounds --- .../v3/PRBMathSD59x18PropertyTests.sol | 2265 +---------------- .../v3/PRBMathUD60x18PropertyTests.sol | 46 +- .../v4/PRBMathSD59x18PropertyTests.sol | 65 +- .../v4/PRBMathUD60x18PropertyTests.sol | 46 +- 4 files changed, 98 insertions(+), 2324 deletions(-) diff --git a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol index dfb937f..34976f5 100644 --- a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol @@ -28,7 +28,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { /* ================================================================ Constants used for precision loss calculations ================================================================ */ - uint256 internal REQUIRED_SIGNIFICANT_DIGITS = 10; + uint256 internal REQUIRED_SIGNIFICANT_DIGITS = 9; SD59x18 internal LOG2_PRECISION_LOSS = SD59x18.wrap(1); /* ================================================================ @@ -101,7 +101,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { return(la + lb < -18); } - // Return how many significant bits will remain after multiplying a and b + // Return how many significant digits will remain after multiplying a and b // Uses functions from the library under test! function significant_digits_after_mult(SD59x18 a, SD59x18 b) public pure returns (uint256) { int256 la = convert(floor(log10(abs(a)))); @@ -109,2224 +109,27 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { int256 prec = la + lb; if (prec < -18) return 0; - else return(59 + uint256(prec)); + else return(18 + absInt(prec)); } - // Return how many significant bits will be lost after multiplying a and b + // Return how many significant digits will be lost after multiplying a and b // Uses functions from the library under test! function significant_digits_lost_in_mult(SD59x18 a, SD59x18 b) public pure returns (uint256) { int256 la = convert(floor(log10(abs(a)))); int256 lb = convert(floor(log10(abs(b)))); - // digits left - int256 prec = la + lb; - - if (la > 0 && lb > 0) { - return 0; - } else { - return la < lb ? uint256(-la) : uint256(-lb); - } - } - - /* ================================================================ - Library wrappers. - These functions allow calling the PRBMathSD59x18 library. - ================================================================ */ - function debug(string calldata x, SD59x18 y) public { - emit Value(x, y); - } - - // Wrapper for external try/catch calls - function helpersAdd(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return add(x,y); - } - - // Wrapper for external try/catch calls - function helpersSub(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return sub(x,y); - } - - // Wrapper for external try/catch calls - function helpersMul(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return mul(x,y); - } - - function helpersDiv(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return div(x,y); - } - - function neg(SD59x18 x) public pure returns (SD59x18) { - return SD59x18.wrap(-SD59x18.unwrap(x)); - } - - function helpersAbs(SD59x18 x) public pure returns (SD59x18) { - return abs(x); - } - - function helpersLn(SD59x18 x) public pure returns (SD59x18) { - return ln(x); - } - - function helpersExp(SD59x18 x) public pure returns (SD59x18) { - return exp(x); - } - - function helpersExp2(SD59x18 x) public pure returns (SD59x18) { - return exp2(x); - } - - function helpersLog2(SD59x18 x) public pure returns (SD59x18) { - return log2(x); - } - - function helpersSqrt(SD59x18 x) public pure returns (SD59x18) { - return sqrt(x); - } - - function helpersPow(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return pow(x, y); - } - - function helpersPowu(SD59x18 x, uint256 y) public pure returns (SD59x18) { - return powu(x, y); - } - - function helpersAvg(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return avg(x, y); - } - - function helpersInv(SD59x18 x) public pure returns (SD59x18) { - return inv(x); - } - - function helpersLog10(SD59x18 x) public pure returns (SD59x18) { - return log10(x); - } - - function helpersFloor(SD59x18 x) public pure returns (SD59x18) { - return floor(x); - } - - function helpersGm(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return gm(x,y); - } - - /* ================================================================ - - TESTS FOR FUNCTION add() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for commutative property - // x + y == y + x - function add_test_commutative(SD59x18 x, SD59x18 y) public { - SD59x18 x_y = x.add(y); - SD59x18 y_x = y.add(x); - - assertEq(x_y, y_x); - } - - // Test for associative property - // (x + y) + z == x + (y + z) - function add_test_associative(SD59x18 x, SD59x18 y, SD59x18 z) public { - SD59x18 x_y = x.add(y); - SD59x18 y_z = y.add(z); - SD59x18 xy_z = x_y.add(z); - SD59x18 x_yz = x.add(y_z); - - assertEq(xy_z, x_yz); - } - - // Test for identity operation - // x + 0 == x (equivalent to x + (-x) == 0) - function add_test_identity(SD59x18 x) public { - SD59x18 x_0 = x.add(ZERO_FP); - - assertEq(x, x_0); - assertEq(x.sub(x), ZERO_FP); - } - - // Test that the result increases or decreases depending - // on the value to be added - function add_test_values(SD59x18 x, SD59x18 y) public { - SD59x18 x_y = x.add(y); - - if (y.gte(ZERO_FP)) { - assertGte(x_y, x); - } else { - assertLt(x_y, x); - } - } - - /* ================================================================ - Tests for overflow and edge cases. - These should make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // The result of the addition must be between the maximum - // and minimum allowed values for SD59x18 - function add_test_range(SD59x18 x, SD59x18 y) public { - try this.helpersAdd(x, y) returns (SD59x18 result) { - assertLte(result, MAX_SD59x18); - assertGte(result, MIN_SD59x18); - } catch { - // If it reverts, just ignore - } - } - - // Adding zero to the maximum value shouldn't revert, as it is valid - // Moreover, the result must be MAX_SD59x18 - function add_test_maximum_value() public { - try this.helpersAdd(MAX_SD59x18, ZERO_FP) returns (SD59x18 result) { - // Expected behaviour, does not revert - assertEq(result, MAX_SD59x18); - } catch { - assert(false); - } - } - - // Adding one to the maximum value should revert, as it is out of range - function add_test_maximum_value_plus_one() public { - try this.helpersAdd(MAX_SD59x18, ONE_FP) { - assert(false); - } catch { - // Expected behaviour, reverts - } - } - - // Adding zero to the minimum value shouldn't revert, as it is valid - // Moreover, the result must be MIN_SD59x18 - function add_test_minimum_value() public { - try this.helpersAdd(MIN_SD59x18, ZERO_FP) returns (SD59x18 result) { - // Expected behaviour, does not revert - assertEq(result, MIN_SD59x18); - } catch { - assert(false); - } - } - - // Adding minus one to the maximum value should revert, as it is out of range - function add_test_minimum_value_plus_negative_one() public { - try this.helpersAdd(MIN_SD59x18, MINUS_ONE_FP) { - assert(false); - } catch { - // Expected behaviour, reverts - } - } - - /* ================================================================ - - TESTS FOR FUNCTION sub() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test equivalence to addition - // x - y == x + (-y) - function sub_test_equivalence_to_addition(SD59x18 x, SD59x18 y) public { - SD59x18 minus_y = neg(y); - SD59x18 addition = x.add(minus_y); - SD59x18 subtraction = x.sub(y); - - assertEq(addition, subtraction); - } - - // Test for non-commutative property - // x - y == -(y - x) - function sub_test_non_commutative(SD59x18 x, SD59x18 y) public { - SD59x18 x_y = x.sub(y); - SD59x18 y_x = y.sub(x); - - assertEq(x_y, neg(y_x)); - } - - // Test for identity operation - // x - 0 == x (equivalent to x - x == 0) - function sub_test_identity(SD59x18 x) public { - SD59x18 x_0 = x.sub(ZERO_FP); - - assertEq(x_0, x); - assertEq(x.sub(x), ZERO_FP); - } - - // Test for neutrality over addition and subtraction - // (x - y) + y == (x + y) - y == x - function sub_test_neutrality(SD59x18 x, SD59x18 y) public { - SD59x18 x_minus_y = x.sub(y); - SD59x18 x_plus_y = x.add(y); - - SD59x18 x_minus_y_plus_y = x_minus_y.add(y); - SD59x18 x_plus_y_minus_y = x_plus_y.sub(y); - - assertEq(x_minus_y_plus_y, x_plus_y_minus_y); - assertEq(x_minus_y_plus_y, x); - } - - // Test that the result increases or decreases depending - // on the value to be subtracted - function sub_test_values(SD59x18 x, SD59x18 y) public { - SD59x18 x_y = x.sub(y); - - if (y.gte(ZERO_FP)) { - assertLte(x_y, x); - } else { - assertGt(x_y, x); - } - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // The result of the subtraction must be between the maximum - // and minimum allowed values for SD59x18 - function sub_test_range(SD59x18 x, SD59x18 y) public { - try this.helpersSub(x, y) returns (SD59x18 result) { - assertLte(result, MAX_SD59x18); - assertGte(result, MIN_SD59x18); - } catch { - // If it reverts, just ignore - } - } - - // Subtracting zero from the maximum value shouldn't revert, as it is valid - // Moreover, the result must be MAX_SD59x18 - function sub_test_maximum_value() public { - try this.helpersSub(MAX_SD59x18, ZERO_FP) returns (SD59x18 result) { - // Expected behaviour, does not revert - assertEq(result, MAX_SD59x18); - } catch { - assert(false); - } - } - - // Subtracting minus one from the maximum value should revert, - // as it is out of range - function sub_test_maximum_value_minus_neg_one() public { - try this.helpersSub(MAX_SD59x18, MINUS_ONE_FP) { - assert(false); - } catch { - // Expected behaviour, reverts - } - } - - // Subtracting zero from the minimum value shouldn't revert, as it is valid - // Moreover, the result must be MIN_SD59x18 - function sub_test_minimum_value() public { - try this.helpersSub(MIN_SD59x18, ZERO_FP) returns (SD59x18 result) { - // Expected behaviour, does not revert - assertEq(result, MIN_SD59x18); - } catch { - assert(false); - } - } - - // Subtracting one from the minimum value should revert, as it is out of range - function sub_test_minimum_value_minus_one() public { - try this.helpersSub(MIN_SD59x18, ONE_FP) { - assert(false); - } catch { - // Expected behaviour, reverts - } - } - - /* ================================================================ - - TESTS FOR FUNCTION mul() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for commutative property - // x * y == y * x - function mul_test_commutative(SD59x18 x, SD59x18 y) public { - require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); - SD59x18 x_y = x.mul(y); - SD59x18 y_x = y.mul(x); - - assertEq(x_y, y_x); - } - - // Test for associative property - // (x * y) * z == x * (y * z) - function mul_test_associative(SD59x18 x, SD59x18 y, SD59x18 z) public { - require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18) && z.gt(MIN_SD59x18)); - SD59x18 x_y = x.mul(y); - SD59x18 y_z = y.mul(z); - SD59x18 xy_z = x_y.mul(z); - SD59x18 x_yz = x.mul(y_z); - - require(xy_z.neq(ZERO_FP) && x_yz.neq(ZERO_FP)); - - uint256 digitsLost = significant_digits_lost_in_mult(x, y); - digitsLost += significant_digits_lost_in_mult(x, z); - - assertEqWithinDecimalPrecision(xy_z, x_yz, digitsLost); - } - - // Test for distributive property - // x * (y + z) == x * y + x * z - function mul_test_distributive(SD59x18 x, SD59x18 y, SD59x18 z) public { - require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18) && z.gt(MIN_SD59x18)); - SD59x18 y_plus_z = y.add(z); - SD59x18 x_times_y_plus_z = x.mul(y_plus_z); - - SD59x18 x_times_y = x.mul(y); - SD59x18 x_times_z = x.mul(z); - - require(add(x_times_y, x_times_z).neq(ZERO_FP) && x_times_y_plus_z.neq(ZERO_FP)); - assertEqWithinTolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP, "0.1%"); - } - - - // Test for identity operation - // x * 1 == x (also check that x * 0 == 0) - function mul_test_identity(SD59x18 x) public { - require(x.gt(MIN_SD59x18)); - SD59x18 x_1 = x.mul(ONE_FP); - SD59x18 x_0 = x.mul(ZERO_FP); - - assertEq(x_0, ZERO_FP); - assertEq(x_1, x); - } - - - // If x is positive and y is >= 1, the result should be larger than or equal to x - // If x is positive and y is < 1, the result should be smaller than x - function mul_test_x_positive(SD59x18 x, SD59x18 y) public { - require(x.gte(ZERO_FP)); - - SD59x18 x_y = x.mul(y); - - if (y.gte(ONE_FP)) { - assertGte(x_y, x); - } else { - assertLte(x_y, x); - } - } - - // If x is negative and y is >= 1, the result should be smaller than or equal to x - // If x is negative and y is < 1, the result should be larger than or equal to x - function mul_test_x_negative(SD59x18 x, SD59x18 y) public { - require(x.lte(ZERO_FP) && y.neq(ZERO_FP)); - - SD59x18 x_y = x.mul(y); - - if (y.gte(ONE_FP)) { - assertLte(x_y, x); - } else { - assertGte(x_y, x); - } - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // The result of the multiplication must be between the maximum - // and minimum allowed values for SD59x18 - function mul_test_range(SD59x18 x, SD59x18 y) public { - require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); - - try this.helpersMul(x, y) returns(SD59x18 result) { - assertLte(result, MAX_SD59x18); - assertGte(result, MIN_SD59x18); - } catch { - // If it reverts, just ignore - } - } - - // Multiplying the maximum value times one shouldn't revert, as it is valid - // Moreover, the result must be MAX_SD59x18 - function mul_test_maximum_value() public { - try this.helpersMul(MAX_SD59x18, ONE_FP) returns (SD59x18 result) { - // Expected behaviour, does not revert - assertEq(result, MAX_SD59x18); - } catch { - assert(false); - } - } - - // Multiplying the minimum value times one shouldn't revert, as it is valid - // Moreover, the result must be MIN_SD59x18 - function mul_test_minimum_value() public { - try this.helpersMul(MIN_SD59x18.add(ONE_FP), ONE_FP) returns (SD59x18 result) { - // Expected behaviour, does not revert - assertEq(result, MIN_SD59x18.add(ONE_FP)); - } catch { - assert(false); - } - } - - /* ================================================================ - - TESTS FOR FUNCTION div() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for identity property - // x / 1 == x (equivalent to x / x == 1) - function div_test_division_identity_x_div_1(SD59x18 x) public { - require(x.gt(MIN_SD59x18)); - SD59x18 div_1 = div(x, ONE_FP); - - assertEq(x, div_1); - } - - // Test for identity property - // x/x should not revert unless x == 0 || x == MIN_SD59x18 - function div_test_division_identity_x_div_x(SD59x18 x) public { - SD59x18 div_x; - - try this.helpersDiv(x, x) { - // This should always equal one - div_x = div(x, x); - assertEq(div_x, ONE_FP); - } catch { - // There are a couple of allowed cases for a revert: - // 1. x == 0 - // 2. x == MIN_SD59x18 - // 3. when the result overflows - assert(x.eq(ZERO_FP) || x.eq(MIN_SD59x18)); - } - } - - // Test for negative divisor - // x / -y == -(x / y) - function div_test_negative_divisor(SD59x18 x, SD59x18 y) public { - require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); - require(y.lt(ZERO_FP)); - - SD59x18 x_y = div(x, y); - SD59x18 x_minus_y = div(x, neg(y)); - - assertEq(x_y, neg(x_minus_y)); - } - - // Test for division with 0 as numerator - // 0 / x = 0 - function div_test_division_num_zero(SD59x18 x) public { - require(x.gt(MIN_SD59x18)); - require(x.neq(ZERO_FP)); - - SD59x18 div_0 = div(ZERO_FP, x); - - assertEq(ZERO_FP, div_0); - } - - // Test that the absolute value of the result increases or - // decreases depending on the denominator's absolute value - function div_test_values(SD59x18 x, SD59x18 y) public { - require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); - require(y.neq(ZERO_FP)); - - SD59x18 x_y = abs(div(x, y)); - - if (abs(y).gte(ONE_FP)) { - assertLte(x_y, abs(x)); - } else { - assertGte(x_y, abs(x)); - } - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - - // Test for division by zero - function div_test_div_by_zero(SD59x18 x) public { - require(x.gt(MIN_SD59x18)); - try this.helpersDiv(x, ZERO_FP) { - // Unexpected, this should revert - assert(false); - } catch { - // Expected revert - } - } - - // Test for division by a large value, the result should be less than one - function div_test_maximum_denominator(SD59x18 x) public { - require(x.gt(MIN_SD59x18)); - SD59x18 div_large = div(x, MAX_SD59x18); - - assertLte(abs(div_large), ONE_FP); - } - - // Test for division of a large value - // This should revert if |x| < 1 as it would return a value higher than max - function div_test_maximum_numerator(SD59x18 y) public { - SD59x18 div_large; - - try this.helpersDiv(MAX_SD59x18, y) { - // If it didn't revert, then |x| >= 1 - div_large = div(MAX_SD59x18, y); - - assertGte(abs(y), ONE_FP); - } catch { - // Expected revert as result is higher than max - } - } - - // Test for values in range - function div_test_range(SD59x18 x, SD59x18 y) public { - require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); - SD59x18 result; - - try this.helpersDiv(x, y) { - // If it returns a value, it must be in range - result = div(x, y); - assertLte(result, MAX_SD59x18); - assertGte(result, MIN_SD59x18); - } catch { - // Otherwise, it should revert - } - } - - /* ================================================================ - - TESTS FOR FUNCTION neg() - - ================================================================ */ - - /* ================================================================ - Tests for mathematical properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for the double negation - // -(-x) == x - function neg_test_double_negation(SD59x18 x) public { - SD59x18 double_neg = neg(neg(x)); - - assertEq(x, double_neg); - } - - // Test for the identity operation - // x + (-x) == 0 - function neg_test_identity(SD59x18 x) public { - SD59x18 neg_x = neg(x); - - assertEq(add(x, neg_x), ZERO_FP); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for the zero-case - // -0 == 0 - function neg_test_zero() public { - SD59x18 neg_x = neg(ZERO_FP); - - assertEq(neg_x, ZERO_FP); - } - - // Test for the maximum value case - // Since this is implementation-dependant, we will actually test with MAX_SD59x18-EPS - function neg_test_maximum() public { - try this.neg(sub(MAX_SD59x18, EPSILON)) { - // Expected behaviour, does not revert - } catch { - assert(false); - } - } - - // Test for the minimum value case - // Since this is implementation-dependant, we will actually test with MIN_SD59x18+EPS - function neg_test_minimum() public { - try this.neg(add(MIN_SD59x18, EPSILON)) { - // Expected behaviour, does not revert - } catch { - assert(false); - } - } - - /* ================================================================ - - TESTS FOR FUNCTION abs() - - ================================================================ */ - - /* ================================================================ - Tests for mathematical properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - - // Test that the absolute value is always positive - function abs_test_positive(SD59x18 x) public { - require(x.gt(MIN_SD59x18)); - SD59x18 abs_x = abs(x); - - assertGte(abs_x, ZERO_FP); - } - - // Test that the absolute value of a number equals the - // absolute value of the negative of the same number - function abs_test_negative(SD59x18 x) public { - require(x.gt(MIN_SD59x18)); - - SD59x18 abs_x = abs(x); - SD59x18 abs_minus_x = abs(neg(x)); - - assertEq(abs_x, abs_minus_x); - } - - // Test the multiplicativeness property - // | x * y | == |x| * |y| - function abs_test_multiplicativeness(SD59x18 x, SD59x18 y) public { - require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); - - SD59x18 abs_x = abs(x); - SD59x18 abs_y = abs(y); - SD59x18 abs_xy = abs(mul(x, y)); - SD59x18 abs_x_abs_y = mul(abs_x, abs_y); - - // Failure if all significant digits are lost - require(significant_digits_are_lost_in_mult(abs_x, abs_y) == false); - - // Assume a tolerance of two bits of precision - assertEqWithinBitPrecision(abs_xy, abs_x_abs_y, 2); - } - - // Test the subadditivity property - // | x + y | <= |x| + |y| - function abs_test_subadditivity(SD59x18 x, SD59x18 y) public { - require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); - - SD59x18 abs_x = abs(x); - SD59x18 abs_y = abs(y); - SD59x18 abs_xy = abs(add(x, y)); - - assertLte(abs_xy, add(abs_x, abs_y)); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test the zero-case | 0 | = 0 - function abs_test_zero() public { - SD59x18 abs_zero; - - try this.helpersAbs(ZERO_FP) { - // If it doesn't revert, the value must be zero - abs_zero = this.helpersAbs(ZERO_FP); - assertEq(abs_zero, ZERO_FP); - } catch { - // Unexpected, the function must not revert here - assert(false); - } - } - - // Test the maximum value - function abs_test_maximum() public { - SD59x18 abs_max; - - try this.helpersAbs(MAX_SD59x18) { - // If it doesn't revert, the value must be MAX_SD59x18 - abs_max = this.helpersAbs(MAX_SD59x18); - assertEq(abs_max, MAX_SD59x18); - } catch { - assert(false); - } - } - - // Test the minimum value - function abs_test_minimum_revert() public { - SD59x18 abs_min; - - try this.helpersAbs(MIN_SD59x18) { - // It should always revert for MIN_SD59x18 - assert(false); - } catch {} - } - - // Test the minimum value - function abs_test_minimum_allowed() public { - SD59x18 abs_min; - SD59x18 input = MIN_SD59x18.add(SD59x18.wrap(1)); - - try this.helpersAbs(input) { - // If it doesn't revert, the value must be the negative of MIN_SD59x18 + 1 - abs_min = this.helpersAbs(input); - assertEq(abs_min, neg(input)); - } catch { - assert(false); - } - } - - /* ================================================================ - - TESTS FOR FUNCTION inv() - - ================================================================ */ - - /* ================================================================ - Tests for mathematical properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test that the inverse of the inverse is close enough to the - // original number - function inv_test_double_inverse(SD59x18 x) public { - require(x.neq(ZERO_FP)); - - SD59x18 double_inv_x = inv(inv(x)); - - // The maximum loss of precision will be 2 * log2(x) bits rounded up - uint256 loss = 2 * significant_digits_lost_in_mult(x, x)+ 2; - - assertEqWithinDecimalPrecision(x, double_inv_x, loss); - } - - // Test equivalence with division - function inv_test_division(SD59x18 x) public { - require(x.neq(ZERO_FP)); - - SD59x18 inv_x = inv(x); - SD59x18 div_1_x = div(ONE_FP, x); - - assertEq(inv_x, div_1_x); - } - - // Test the anticommutativity of the division - // x / y == 1 / (y / x) - function inv_test_division_noncommutativity( - SD59x18 x, - SD59x18 y - ) public { - require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); - - SD59x18 x_y = div(x, y); - SD59x18 y_x = div(y, x); - - assertEqWithinTolerance(x_y, inv(y_x), ONE_TENTH_FP, "0.1%"); - } - - // Test the multiplication of inverses - // 1/(x * y) == 1/x * 1/y - function inv_test_multiplication(SD59x18 x, SD59x18 y) public { - require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); - - SD59x18 inv_x = inv(x); - SD59x18 inv_y = inv(y); - SD59x18 inv_x_times_inv_y = mul(inv_x, inv_y); - - SD59x18 x_y = mul(x, y); - SD59x18 inv_x_y = inv(x_y); - - // The maximum loss of precision is given by the formula: - // 2 * | log2(x) - log2(y) | + 1 - uint256 loss = 2 * significant_digits_lost_in_mult(x, y) + 1; - - assertEqWithinDecimalPrecision(inv_x_y, inv_x_times_inv_y, loss); - } - - // Test identity property - function inv_test_identity(SD59x18 x) public { - require(x.neq(ZERO_FP)); - - SD59x18 inv_x = inv(x); - SD59x18 identity = mul(inv_x, x); - - // They should agree with a tolerance of one tenth of a percent - assertEqWithinTolerance(identity, ONE_FP, ONE_TENTH_FP, "0.1%"); - } - - // Test that the absolute value of the result is in range zero-one - // if x is greater than one, else, the absolute value of the result - // must be greater than one - function inv_test_values(SD59x18 x) public { - require(x.neq(ZERO_FP)); - - SD59x18 abs_inv_x = abs(inv(x)); - - if (abs(x).gte(ONE_FP)) { - assertLte(abs_inv_x, ONE_FP); - } else { - assertGt(abs_inv_x, ONE_FP); - } - } - - // Test that the result has the same sign as the argument. - // Since inv() rounds towards zero, we are checking the zero case as well - function inv_test_sign(SD59x18 x) public { - require(x.neq(ZERO_FP)); - - SD59x18 inv_x = inv(x); - - if (x.gt(ZERO_FP)) { - assertGte(inv_x, ZERO_FP); - } else { - assertLte(inv_x, ZERO_FP); - } - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test the zero-case, should revert - function inv_test_zero() public { - try this.helpersInv(ZERO_FP) { - // Unexpected, the function must revert - assert(false); - } catch {} - } - - // Test the maximum value case, should not revert, and be close to zero - function inv_test_maximum() public { - SD59x18 inv_maximum; - - try this.helpersInv(MAX_SD59x18) { - inv_maximum = this.helpersInv(MAX_SD59x18); - assertEqWithinBitPrecision(inv_maximum, ZERO_FP, 10); - } catch { - // Unexpected, the function must not revert - assert(false); - } - } - - // Test the minimum value case, should not revert, and be close to zero - function inv_test_minimum() public { - SD59x18 inv_minimum; - - try this.helpersInv(MIN_SD59x18) { - inv_minimum = this.helpersInv(MIN_SD59x18); - assertEqWithinBitPrecision(abs(inv_minimum), ZERO_FP, 10); - } catch { - // Unexpected, the function must not revert - assert(false); - } - } - - /* ================================================================ - - TESTS FOR FUNCTION avg() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test that the result is between the two operands - // avg(x, y) >= min(x, y) && avg(x, y) <= max(x, y) - function avg_test_values_in_range(SD59x18 x, SD59x18 y) public { - SD59x18 avg_xy = avg(x, y); - - if (x.gte(y)) { - assertGte(avg_xy, y); - assertLte(avg_xy, x); - } else { - assertGte(avg_xy, x); - assertLte(avg_xy, y); - } - } - - // Test that the average of the same number is itself - // avg(x, x) == x - function avg_test_one_value(SD59x18 x) public { - SD59x18 avg_x = avg(x, x); - - assertEq(avg_x, x); - } - - // Test that the order of operands is irrelevant - // avg(x, y) == avg(y, x) - function avg_test_operand_order(SD59x18 x, SD59x18 y) public { - SD59x18 avg_xy = avg(x, y); - SD59x18 avg_yx = avg(y, x); - - assertEq(avg_xy, avg_yx); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for the maximum value - function avg_test_maximum() public { - SD59x18 result; - - // This may revert due to overflow depending on implementation - // If it doesn't revert, the result must be MAX_SD59x18 - try this.helpersAvg(MAX_SD59x18, MAX_SD59x18) { - result = this.helpersAvg(MAX_SD59x18, MAX_SD59x18); - assertEq(result, MAX_SD59x18); - } catch { - assert(false); - } - } - - // Test for the minimum value - function avg_test_minimum() public { - SD59x18 result; - - // This may revert due to overflow depending on implementation - // If it doesn't revert, the result must be MIN_SD59x18 - try this.helpersAvg(MIN_SD59x18, MIN_SD59x18) { - result = this.helpersAvg(MIN_SD59x18, MIN_SD59x18); - assertEq(result, MIN_SD59x18); - } catch { - assert(false); - } - } - - /* ================================================================ - - TESTS FOR FUNCTION pow() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for zero exponent - // x ** 0 == 1 - function pow_test_zero_exponent(SD59x18 x) public { - require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); - SD59x18 x_pow_0 = pow(x, ZERO_FP); - - assertEq(x_pow_0, ONE_FP); - } - - // Test for zero base - // 0 ** a == 0 (for positive a) - function pow_test_zero_base_non_zero_exponent(SD59x18 a) public { - require(a.gt(ZERO_FP)); - SD59x18 zero_pow_a = pow(ZERO_FP, a); - - assertEq(zero_pow_a, ZERO_FP); - } - - // Test for zero base - // 0 ** 0 == 1 - function pow_test_zero_base_zero_exponent() public { - SD59x18 zero_pow_a = pow(ZERO_FP, ZERO_FP); - - assertEq(zero_pow_a, ONE_FP); - } - - // Test for exponent one - // x ** 1 == x - function pow_test_one_exponent(SD59x18 x) public { - require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); - SD59x18 x_pow_1 = pow(x, ONE_FP); - - assertEq(x_pow_1, x); - } - - // Test for base one - // 1 ** a == 1 - function pow_test_base_one(SD59x18 a) public { - SD59x18 one_pow_a = pow(ONE_FP, a); - - assertEq(one_pow_a, ONE_FP); - } - - - // Test for product of powers of the same base - // x ** a * x ** b == x ** (a + b) - function pow_test_product_same_base( - SD59x18 x, - SD59x18 a, - SD59x18 b - ) public { - require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); - require(a.gte(MIN_PERMITTED_EXP2) && b.gte(MIN_PERMITTED_EXP2)); - - SD59x18 x_a = pow(x, a); - SD59x18 x_b = pow(x, b); - SD59x18 x_ab = pow(x, a.add(b)); - - assertEqWithinDecimalPrecision(mul(x_a, x_b), x_ab, 9); - } - - // Test for power of an exponentiation - // (x ** a) ** b == x ** (a * b) - function pow_test_power_of_an_exponentiation( - SD59x18 x, - SD59x18 a, - SD59x18 b - ) public { - require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); - require(a.mul(b).neq(ZERO_FP)); - require(a.gte(MIN_PERMITTED_EXP2) && b.gte(MIN_PERMITTED_EXP2)); - - SD59x18 x_a = pow(x, a); - SD59x18 x_a_b = pow(x_a, b); - SD59x18 x_ab = pow(x, a.mul(b)); - - assertEqWithinDecimalPrecision(x_a_b, x_ab, 9); - } - - // Test for power of a product - // (x * y) ** a == x ** a * y ** a - function pow_test_product_power( - SD59x18 x, - SD59x18 y, - SD59x18 a - ) public { - require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); - require(y.gte(ZERO_FP) && y.lte(MAX_PERMITTED_POW)); - - require(a.gt(convert(2 ** 32))); // to avoid massive loss of precision - - SD59x18 x_y = mul(x, y); - SD59x18 xy_a = pow(x_y, a); - - SD59x18 x_a = pow(x, a); - SD59x18 y_a = pow(y, a); - - assertEqWithinBitPrecision(mul(x_a, y_a), xy_a, 10); - } - - // Test for result being greater than or lower than the argument, depending on - // its absolute value and the value of the exponent - function pow_test_positive_exponent(SD59x18 x, SD59x18 a) public { - require(x.gte(ZERO_FP) && a.gte(ZERO_FP)); - - SD59x18 x_a = pow(x, a); - - if (abs(x).gte(ONE_FP)) { - assertGte(abs(x_a), ONE_FP); - } - - if (abs(x).lte(ONE_FP)) { - assertLte(abs(x_a), ONE_FP); - } - } - - // Test for result being greater than or lower than the argument, depending on - // its absolute value and the value of the exponent - function pow_test_negative_exponent(SD59x18 x, SD59x18 a) public { - require(x.gte(ZERO_FP) && a.lte(ZERO_FP)); - - SD59x18 x_a = pow(x, a); - - if (abs(x).gte(ONE_FP)) { - assertLte(abs(x_a), ONE_FP); - } - - if (abs(x).lte(ONE_FP)) { - assertGte(abs(x_a), ONE_FP); - } - } - - // Test for result sign: if the exponent is even, sign is positive - // if the exponent is odd, preserves the sign of the base - function pow_test_sign(SD59x18 x, SD59x18 a) public { - require(x.gte(ZERO_FP)); - - SD59x18 x_a = pow(x, a); - - // This prevents the case where a small negative number gets - // rounded down to zero and thus changes sign - require(x_a.neq(ZERO_FP)); - - // If the exponent is even - if (a.mod(convert(2)).eq(ZERO_FP)) { - assertEq(x_a, abs(x_a)); - } else { - // x_a preserves x sign - if (x.lt(ZERO_FP)) { - assertLt(x_a, ZERO_FP); - } else { - assertGt(x_a, ZERO_FP); - } - } - } - - // pow(2, a) == exp2(a) - function pow_test_exp2_equivalence(SD59x18 a) public { - SD59x18 pow_result = pow(convert(2), a); - SD59x18 exp2_result = exp2(a); - - assertEq(pow_result, exp2_result); - } - - // Power is strictly increasing - // x > y && a >= 0 --> pow(x, a) >= pow(y, a) - function pow_test_strictly_increasing(SD59x18 x, SD59x18 y, SD59x18 a) public { - require(x.gt(y) && x.lte(MAX_PERMITTED_POW)); - require(a.gte(ZERO_FP)); - - SD59x18 x_a = pow(x, a); - SD59x18 y_a = pow(y, a); - - assertGte(x_a, y_a); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for maximum base and exponent > 1 - function pow_test_maximum_base(SD59x18 a) public { - require(a.gt(ONE_FP)); - - try this.helpersPow(MAX_SD59x18, a) { - // Unexpected, should revert because of overflow - assert(false); - } catch { - // Expected revert - } - } - - // Test for abs(base) < 1 and high exponent - function pow_test_high_exponent(SD59x18 x, SD59x18 a) public { - require(abs(x).lt(ONE_FP) && a.gt(convert(2 ** 32))); - - SD59x18 result = pow(x, a); - - assertEq(result, ZERO_FP); - } - - /* ================================================================ - - TESTS FOR FUNCTION sqrt() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for the inverse operation - // sqrt(x) * sqrt(x) == x - function sqrt_test_inverse_mul(SD59x18 x) public { - require(x.gte(ZERO_FP)); - - SD59x18 sqrt_x = sqrt(x); - SD59x18 sqrt_x_squared = mul(sqrt_x, sqrt_x); - - // Precision loss is at most half the bits of the operand - assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); - } - - // Test for the inverse operation - // sqrt(x) ** 2 == x - function sqrt_test_inverse_pow(SD59x18 x) public { - require(x.gte(ZERO_FP)); - - SD59x18 sqrt_x = sqrt(x); - SD59x18 sqrt_x_squared = pow(sqrt_x, convert(2)); - - // Precision loss is at most half the bits of the operand - assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); - } - - // Test for distributive property respect to the multiplication - // sqrt(x) * sqrt(y) == sqrt(x * y) - function sqrt_test_distributive(SD59x18 x, SD59x18 y) public { - require(x.gte(ZERO_FP) && y.gte(ZERO_FP)); - - SD59x18 sqrt_x = sqrt(x); - SD59x18 sqrt_y = sqrt(y); - SD59x18 sqrt_x_sqrt_y = mul(sqrt_x, sqrt_y); - SD59x18 sqrt_xy = sqrt(mul(x, y)); - - // Ensure we have enough significant digits for the result to be meaningful - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); - require( - significant_digits_after_mult(sqrt_x, sqrt_y) > - REQUIRED_SIGNIFICANT_DIGITS - ); - - // Allow an error of up to one tenth of a percent - assertEqWithinTolerance(sqrt_x_sqrt_y, sqrt_xy, ONE_TENTH_FP, "0.1%"); - } - - // Test that sqrt is strictly increasing - // x >= 0 && y > x --> sqrt(y) >= sqrt(x) - function sqrt_test_strictly_increasing(SD59x18 x, SD59x18 y) public { - require(x.gte(ZERO_FP) && x.lte(MAX_SQRT.sub(SD59x18.wrap(1)))); - require(y.gt(x) && y.lte(MAX_SQRT)); - - SD59x18 sqrt_x = sqrt(x); - SD59x18 sqrt_y = sqrt(y); - - assertGte(sqrt_y, sqrt_x); - } - - // Square root of perfect square should be equal to x - // sqrt(x * x) == x - function sqrt_test_square(SD59x18 x) public { - require(x.gt(ONE_FP)); - - SD59x18 square_x = x.mul(x); - SD59x18 sqrt_square_x = sqrt(square_x); - - assertEq(sqrt_square_x, x); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - - // Test for zero case - function sqrt_test_zero() public { - assertEq(sqrt(ZERO_FP), ZERO_FP); - } - - // Test for maximum value - function sqrt_test_maximum() public { - try this.helpersSqrt(MAX_SQRT) { - // Expected behaviour, MAX_SQRT is positive, and operation - // should not revert as the result is in range - } catch { - // Unexpected, should not revert - assert(false); - } - } - - // Test for minimum value - function sqrt_test_minimum() public { - try this.helpersSqrt(MIN_SD59x18) { - // Unexpected, should revert. MIN_SD59x18 is negative. - assert(false); - } catch { - // Expected behaviour, revert - } - } - - // Test for negative operands - function sqrt_test_negative(SD59x18 x) public { - require(x.lt(ZERO_FP)); - - try this.helpersSqrt(x) { - // Unexpected, should revert - assert(false); - } catch { - // Expected behaviour, revert - } - } - - /* ================================================================ - - TESTS FOR FUNCTION log2() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for distributive property respect to multiplication - // log2(x * y) = log2(x) + log2(y) - function log2_test_distributive_mul(SD59x18 x, SD59x18 y) public { - require(x.gt(ZERO_FP) && y.gt(ZERO_FP)); - SD59x18 log2_x = log2(x); - SD59x18 log2_y = log2(y); - SD59x18 log2_x_log2_y = add(log2_x, log2_y); - - SD59x18 xy = mul(x, y); - SD59x18 log2_xy = log2(xy); - - // Ensure we have enough significant digits for the result to be meaningful - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); - - // The maximum loss of precision is given by the formula: - // | log2(x) + log2(y) | - uint256 loss = significant_digits_lost_in_mult(x, y) + 2; - - assertEqWithinDecimalPrecision(log2_x_log2_y, log2_xy, loss); - } - - // Test for logarithm of a power - // log2(x ** y) = y * log2(x) - function log2_test_power(SD59x18 x, SD59x18 y) public { - require(x.gt(ZERO_FP)); - SD59x18 x_y = pow(x, y); - require(x_y.gt(ZERO_FP)); - SD59x18 log2_x_y = log2(x_y); - SD59x18 y_log2_x = mul(log2(x), y); - - assertEqWithinTolerance(y_log2_x, log2_x_y, ONE_TENTH_FP, "0.1%"); - } - - // Base 2 logarithm is strictly increasing - // y > 0 && x > y --> log2(x) > log2(y) - function log2_test_strictly_increasing(SD59x18 x, SD59x18 y) public { - require(y.gt(ZERO_FP) && x.gt(y)); - - SD59x18 log2_x = log2(x); - SD59x18 log2_y = log2(y); - - assertGte(log2_x, log2_y); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for zero case, should revert - function log2_test_zero() public { - try this.helpersLog2(ZERO_FP) { - // Unexpected, should revert - assert(false); - } catch { - // Expected revert, log(0) is not defined - } - } - - // Test for maximum value case, should return a positive number - function log2_test_maximum() public { - SD59x18 result; - - try this.helpersLog2(MAX_SD59x18) { - // Expected, should not revert and the result must be > 0 - result = this.helpersLog2(MAX_SD59x18); - assertGt(result, ZERO_FP); - } catch { - // Unexpected - assert(false); - } - } - - // Test for negative values, should revert as log2 is not defined - function log2_test_negative(SD59x18 x) public { - require(x.lt(ZERO_FP)); - - try this.helpersLog2(x) { - // Unexpected, should revert - assert(false); - } catch { - // Expected - } - } - - /* ================================================================ - - TESTS FOR FUNCTION ln() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for distributive property respect to multiplication - // ln(x * y) = ln(x) + ln(y) - function ln_test_distributive_mul(SD59x18 x, SD59x18 y) public { - require(x.gt(ZERO_FP) && y.gt(ZERO_FP)); - - SD59x18 ln_x = ln(x); - SD59x18 ln_y = ln(y); - SD59x18 ln_x_ln_y = add(ln_x, ln_y); - - SD59x18 xy = mul(x, y); - SD59x18 ln_xy = ln(xy); - - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); - - // The maximum loss of precision is given by the formula: - // | log2(x) + log2(y) | - uint256 loss = significant_digits_lost_in_mult(x, y) + 2; - - assertEqWithinDecimalPrecision(ln_x_ln_y, ln_xy, loss); - } - - // Test for logarithm of a power - // ln(x ** y) = y * ln(x) - function ln_test_power(SD59x18 x, SD59x18 y) public { - require(x.gt(ZERO_FP)); - SD59x18 x_y = pow(x, y); - SD59x18 ln_x_y = ln(x_y); - - SD59x18 y_ln_x = mul(ln(x), y); - - require(significant_digits_after_mult(ln(x), y) > REQUIRED_SIGNIFICANT_DIGITS); - - assertEqWithinTolerance(ln_x_y, y_ln_x, ONE_TENTH_FP, "0.1%"); - } - - // Natural logarithm is strictly increasing - // y > 0 && x > y --> log2(x) > log2(y) - function ln_test_strictly_increasing(SD59x18 x, SD59x18 y) public { - require(y.gt(ZERO_FP) && x.gt(y)); - - SD59x18 log2_x = ln(x); - SD59x18 log2_y = ln(y); - - assertGte(log2_x, log2_y); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for zero case, should revert - function ln_test_zero() public { - try this.helpersLn(ZERO_FP) { - // Unexpected, should revert - assert(false); - } catch { - // Expected revert, log(0) is not defined - } - } - - // Test for maximum value case, should return a positive number - function ln_test_maximum() public { - SD59x18 result; - - try this.helpersLn(MAX_SD59x18) { - // Expected, should not revert and the result must be > 0 - result = this.helpersLn(MAX_SD59x18); - assertGt(result, ZERO_FP); - } catch { - // Unexpected - assert(false); - } - } - - // Test for negative values, should revert as ln is not defined - function ln_test_negative(SD59x18 x) public { - require(x.lt(ZERO_FP)); - - try this.helpersLn(x) { - // Unexpected, should revert - assert(false); - } catch { - // Expected - } - } - - /* ================================================================ - - TESTS FOR FUNCTION exp2() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for equality with pow(2, x) for integer x - // pow(2, x) == exp2(x) - function exp2_test_equivalence_pow(SD59x18 x) public { - require(x.lte(MAX_PERMITTED_EXP2)); - SD59x18 exp2_x = exp2(x); - SD59x18 pow_2_x = pow(TWO_FP, x); - - assertEq(exp2_x, pow_2_x); - } - - // Test for inverse function - // If y = log2(x) then exp2(y) == x - function exp2_test_inverse(SD59x18 x) public { - require(x.lte(MAX_PERMITTED_EXP2) && x.gt(ZERO_FP)); - SD59x18 log2_x = log2(x); - SD59x18 exp2_x = exp2(log2_x); - - assertEqWithinDecimalPrecision(x, exp2_x, 9); - } - - // Test for negative exponent - // exp2(-x) == inv( exp2(x) ) - function exp2_test_negative_exponent(SD59x18 x) public { - require(x.lt(ZERO_FP) && x.neq(MIN_SD59x18)); - - SD59x18 exp2_x = exp2(x); - SD59x18 exp2_minus_x = exp2(neg(x)); - - assertEqWithinDecimalPrecision(exp2_x, inv(exp2_minus_x), 2); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for zero case - // exp2(0) == 1 - function exp2_test_zero() public { - SD59x18 exp_zero = exp2(ZERO_FP); - assertEq(exp_zero, ONE_FP); - } - - // Test for maximum value. This should overflow as it won't fit - // in the data type - function exp2_test_maximum() public { - try this.helpersExp2(MAX_SD59x18) { - // Unexpected, should revert - assert(false); - } catch { - // Expected revert - } - } - - // Test for maximum value. This should overflow as it won't fit - // in the data type - function exp2_test_maximum_permitted() public { - try this.helpersExp2(MAX_PERMITTED_EXP2) { - // Should always pass - } catch { - // Should never revert - assert(false); - } - } - - // Test for minimum value. This should return zero since - // 2 ** -x == 1 / 2 ** x that tends to zero as x increases - function exp2_test_minimum() public { - SD59x18 result; - - try this.helpersExp2(MIN_SD59x18) { - // Expected, should not revert, check that value is zero - result = exp2(MIN_SD59x18); - assertEq(result, ZERO_FP); - } catch { - // Unexpected revert - assert(false); - } - } - - /* ================================================================ - - TESTS FOR FUNCTION exp() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for inverse function - // If y = ln(x) then exp(y) == x - function exp_test_inverse(SD59x18 x) public { - require(x.lte(MAX_PERMITTED_EXP)); - SD59x18 ln_x = ln(x); - SD59x18 exp_x = exp(ln_x); - SD59x18 log10_x = log10(x); - - assertEqWithinDecimalPrecision(x, exp_x, 9); - } - - // Test for negative exponent - // exp(-x) == inv( exp(x) ) - function exp_test_negative_exponent(SD59x18 x) public { - require(x.lt(ZERO_FP) && x.neq(MIN_SD59x18)); - - SD59x18 exp_x = exp(x); - SD59x18 exp_minus_x = exp(neg(x)); - - // Result should be within 4 bits precision for the worst case - assertEqWithinBitPrecision(exp_x, inv(exp_minus_x), 4); - } - - // Test that exp strictly increases - function exp_test_strictly_increasing(SD59x18 x, SD59x18 y) public { - require(x.lte(MAX_PERMITTED_EXP)); - require(y.gt(x) && y.lte(MAX_PERMITTED_EXP)); - - SD59x18 exp_x = exp(x); - SD59x18 exp_y = exp(y); - - assertGte(exp_y, exp_x); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for zero case - // exp(0) == 1 - function exp_test_zero() public { - SD59x18 exp_zero = exp(ZERO_FP); - assertEq(exp_zero, ONE_FP); - } - - // Test for maximum value. This should overflow as it won't fit - // in the data type - function exp_test_maximum() public { - try this.helpersExp(MAX_SD59x18) { - // Unexpected, should revert - assert(false); - } catch { - // Expected revert - } - } - - // Test for maximum value. This should overflow as it won't fit - // in the data type - function exp_test_maximum_permitted() public { - try this.helpersExp(MAX_PERMITTED_EXP) { - // Expected to always succeed - } catch { - // Unexpected, should revert - assert(false); - } - } - - // Test for minimum value. This should return zero since - // e ** -x == 1 / e ** x that tends to zero as x increases - function exp_test_minimum() public { - SD59x18 result; - - try this.helpersExp(MIN_SD59x18) { - // Expected, should not revert, check that value is zero - result = exp(MIN_SD59x18); - assertEq(result, ZERO_FP); - } catch { - // Unexpected revert - assert(false); - } - } - - /* ================================================================ - - TESTS FOR FUNCTION powu() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for zero exponent - // x ** 0 == 1 - function powu_test_zero_exponent(SD59x18 x) public { - require(x.gt(MIN_SD59x18)); - require(x.lte(MAX_SD59x18)); - SD59x18 x_pow_0 = powu(x, 0); - - assertEq(x_pow_0, ONE_FP); - } - - // Test for zero base - // 0 ** y == 0 (for positive y) - function powu_test_zero_base(uint256 y) public { - require(y != 0); - - SD59x18 zero_pow_y = powu(ZERO_FP, y); - - assertEq(zero_pow_y, ZERO_FP); - } - - // Test for exponent one - // x ** 1 == x - function powu_test_one_exponent(SD59x18 x) public { - require(x.gt(MIN_SD59x18)); - require(x.lte(MAX_SD59x18)); - SD59x18 x_pow_1 = powu(x, 1); - - assertEq(x_pow_1, x); - } - - // Test for base one - // 1 ** x == 1 - function powu_test_base_one(uint256 y) public { - SD59x18 one_pow_y = powu(ONE_FP, y); - - assertEq(one_pow_y, ONE_FP); - } - - // Test for product of powers of the same base - // x ** a * x ** b == x ** (a + b) - function powu_test_product_same_base( - SD59x18 x, - uint256 a, - uint256 b - ) public { - require(x.gt(MIN_SD59x18)); - require(x.lte(MAX_SD59x18)); - - SD59x18 x_a = powu(x, a); - SD59x18 x_b = powu(x, b); - SD59x18 x_ab = powu(x, a + b); - - assertEqWithinBitPrecision(mul(x_a, x_b), x_ab, 10); - } - - // Test for power of an exponentiation - // (x ** a) ** b == x ** (a * b) - function powu_test_power_of_an_exponentiation( - SD59x18 x, - uint256 a, - uint256 b - ) public { - require(x.gt(MIN_SD59x18)); - require(x.lte(MAX_SD59x18)); - - SD59x18 x_a = powu(x, a); - SD59x18 x_a_b = powu(x_a, b); - SD59x18 x_ab = powu(x, a * b); - - assertEqWithinBitPrecision(x_a_b, x_ab, 10); - } - - // Test for power of a product - // (x * y) ** a == x ** a * y ** a - function powu_test_product_power( - SD59x18 x, - SD59x18 y, - uint256 a - ) public { - require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); - require(x.lte(MAX_SD59x18) && y.lte(MAX_SD59x18)); - - require(a > 2 ** 32); // to avoid massive loss of precision - - SD59x18 x_y = mul(x, y); - SD59x18 xy_a = powu(x_y, a); - - SD59x18 x_a = powu(x, a); - SD59x18 y_a = powu(y, a); - - assertEqWithinBitPrecision(mul(x_a, y_a), xy_a, 10); - } - - // Test for result being greater than or lower than the argument, depending on - // its absolute value and the value of the exponent - function powu_test_values(SD59x18 x, uint256 a) public { - require(x.neq(ZERO_FP)); - require(x.neq(MIN_SD59x18)); - - SD59x18 x_a = powu(x, a); - - if (abs(x).gte(ONE_FP)) { - assertGte(abs(x_a), ONE_FP); - } - - if (abs(x).lte(ONE_FP)) { - assertLte(abs(x_a), ONE_FP); - } - } - - // Test for result sign: if the exponent is even, sign is positive - // if the exponent is odd, preserves the sign of the base - function powu_test_sign(SD59x18 x, uint256 a) public { - require(x.neq(ZERO_FP) && a != 0); - - SD59x18 x_a = powu(x, a); - - // This prevents the case where a small negative number gets - // rounded down to zero and thus changes sign - require(x_a.neq(ZERO_FP)); - - // If the exponent is even - if (a % 2 == 0) { - assertEq(x_a, abs(x_a)); - } else { - // x_a preserves x sign - if (x.lt(ZERO_FP)) { - assertLt(x_a, ZERO_FP); - } else { - assertGt(x_a, ZERO_FP); - } - } - } - - // Unsigned power is strictly increasing - // y > MIN && x > y && a > 0 && y != 0 --> powu(x, a) >= powu(y, a) - function powu_test_strictly_increasing( - SD59x18 x, - SD59x18 y, - uint256 a - ) public { - require(x.gt(y) && y.gt(MIN_SD59x18) && y.neq(ZERO_FP)); - require(x.lte(MAX_SD59x18)); - require(a > 0); - - SD59x18 x_a = powu(x, a); - SD59x18 y_a = powu(y, a); - - assertGte(x_a, y_a); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for maximum base and exponent > 1 - function powu_test_maximum_base(uint256 a) public { - require(a > 1); - - try this.helpersPowu(MAX_SD59x18, a) { - // Unexpected, should revert because of overflow - assert(false); - } catch { - // Expected revert - } - } - - // Test for abs(base) < 1 and high exponent - function powu_test_high_exponent(SD59x18 x, uint256 a) public { - require(abs(x).lt(ONE_FP) && a > 2 ** 32); - - SD59x18 result = powu(x, a); - - assertEq(result, ZERO_FP); - } - - /* ================================================================ - - TESTS FOR FUNCTION log10() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // Test for distributive property respect to multiplication - // log10(x * y) = log10(x) + log10(y) - function log10_test_distributive_mul(SD59x18 x, SD59x18 y) public { - require(x.gt(ZERO_FP) && y.gt(ZERO_FP)); - SD59x18 log10_x = log10(x); - SD59x18 log10_y = log10(y); - SD59x18 log10_x_log10_y = add(log10_x, log10_y); - - SD59x18 xy = mul(x, y); - SD59x18 log10_xy = log10(xy); - - // Ensure we have enough significant digits for the result to be meaningful - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); - - // The maximum loss of precision is given by the formula: - // | log10(x) + log10(y) | - uint256 loss = significant_digits_lost_in_mult(x, y) + 2; - - assertEqWithinDecimalPrecision(log10_x_log10_y, log10_xy, loss); - } - - // Test for logarithm of a power - // log10(x ** y) = y * log10(x) - function log10_test_power(SD59x18 x, SD59x18 y) public { - require(x.gt(ZERO_FP) && y.gt(ZERO_FP)); - SD59x18 x_y = pow(x, y); - SD59x18 log10_x_y = log10(x_y); - SD59x18 y_log10_x = mul(log10(x), y); - - assertEqWithinTolerance(log10_x_y, y_log10_x, ONE_TENTH_FP, "0.1%"); - } - - // Base 10 logarithm is strictly increasing - // x > y && y > 0 --> log10(x) > log10(y) - function log10_is_increasing(SD59x18 x, SD59x18 y) public { - require(y.gt(ZERO_FP) && x.gt(y)); - - SD59x18 log2_x = log10(x); - SD59x18 log2_y = log10(y); - - assertGt(log2_x, log2_y); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for zero case, should revert - function log10_test_zero() public { - try this.helpersLog10(ZERO_FP) { - // Unexpected, should revert - assert(false); - } catch { - // Expected revert, log(0) is not defined - } - } - - // Test for maximum value case, should return a positive number - function log10_test_maximum() public { - SD59x18 result; - - try this.helpersLog10(MAX_SD59x18) { - // Expected, should not revert and the result must be > 0 - result = this.helpersLog10(MAX_SD59x18); - assertGt(result, ZERO_FP); - } catch { - // Unexpected - assert(false); - } - } - - // Test for negative values, should revert as log10 is not defined - function log10_test_negative(SD59x18 x) public { - require(x.lt(ZERO_FP)); - - try this.helpersLog10(x) { - // Unexpected, should revert - assert(false); - } catch { - // Expected - } - } - - /* ================================================================ - - TESTS FOR FUNCTION gm() - - ================================================================ */ - - /* ================================================================ - Tests for arithmetic properties. - These should make sure that the implemented function complies - with math rules and expected behaviour. - ================================================================ */ - - // The product of the values should be equal to the geometric mean - // raised to the power of N (numbers in the set) - function gm_test_product(SD59x18 x, SD59x18 y) public { - bool x_sign = x.gt(ZERO_FP); - bool y_sign = y.gt(ZERO_FP); - require(x_sign = y_sign); - - SD59x18 x_mul_y = x.mul(y); - SD59x18 gm_squared = pow(gm(x,y), TWO_FP); - - assertEqWithinTolerance(x_mul_y, gm_squared, ONE_TENTH_FP, "0.1%"); - } - - // The geometric mean for a set of positive numbers is less than the - // arithmetic mean of that set, as long as the values of the set are not equal - function gm_test_positive_set_avg(SD59x18 x, SD59x18 y) public { - require(x.gte(ZERO_FP) && y.gte(ZERO_FP) && x.neq(y)); - - SD59x18 gm_x_y = gm(x, y); - SD59x18 avg_x_y = avg(x, y); - - assertLte(gm_x_y, avg_x_y); - } - - // The geometric mean of a set of positive equal numbers should be - // equal to the arithmetic mean - function gm_test_positive_equal_set_avg(SD59x18 x) public { - require(x.gte(ZERO_FP)); - - SD59x18 gm_x = gm(x, x); - SD59x18 avg_x = avg(x, x); - - assertEq(gm_x, avg_x); - } - - /* ================================================================ - Tests for overflow and edge cases. - These will make sure that the function reverts on overflow and - behaves correctly on edge cases - ================================================================ */ - - // Test for zero case, should return 0 - function gm_test_zero(SD59x18 x) public { - require(x.gte(ZERO_FP)); - - try this.helpersGm(x, ZERO_FP) { - SD59x18 result = gm(x, ZERO_FP); - assertEq(result, ZERO_FP); - } catch { - // Unexpected, should not revert - assert(false); - } - } - - // Test for single negative input - function gm_test_negative(SD59x18 x, SD59x18 y) public { - require(x.gt(ZERO_FP) && y.lt(ZERO_FP)); - - try this.helpersGm(x, y) { - // Unexpected, should revert - assert(false); - } catch { - // Expected revert, gm of a negative product is not defined - } - } - -}contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { - - /* ================================================================ - 59x18 fixed-point constants used for testing specific values. - This assumes that PRBMath library's convert(x) works as expected. - ================================================================ */ - SD59x18 internal ZERO_FP = convert(0); - SD59x18 internal ONE_FP = convert(1); - SD59x18 internal MINUS_ONE_FP = convert(-1); - SD59x18 internal TWO_FP = convert(2); - SD59x18 internal THREE_FP = convert(3); - SD59x18 internal EIGHT_FP = convert(8); - SD59x18 internal THOUSAND_FP = convert(1000); - SD59x18 internal MINUS_SIXTY_FOUR_FP = convert(-64); - SD59x18 internal EPSILON = SD59x18.wrap(1); - SD59x18 internal ONE_TENTH_FP = convert(1).div(convert(10)); - - /* ================================================================ - Constants used for precision loss calculations - ================================================================ */ - uint256 internal REQUIRED_SIGNIFICANT_DIGITS = 10; - SD59x18 internal LOG2_PRECISION_LOSS = SD59x18.wrap(1); - - /* ================================================================ - Integer representations maximum values. - These constants are used for testing edge cases or limits for - possible values. - ================================================================ */ - /// @dev The unit number, which gives the decimal precision of SD59x18. - int256 constant uUNIT = 1e18; - SD59x18 constant UNIT = SD59x18.wrap(1e18); - - /// @dev The minimum value an SD59x18 number can have. - int256 constant uMIN_SD59x18 = -57896044618658097711785492504343953926634992332820282019728_792003956564819968; - SD59x18 constant MIN_SD59x18 = SD59x18.wrap(uMIN_SD59x18); - - /// @dev The maximum value an SD59x18 number can have. - int256 constant uMAX_SD59x18 = 57896044618658097711785492504343953926634992332820282019728_792003956564819967; - SD59x18 constant MAX_SD59x18 = SD59x18.wrap(uMAX_SD59x18); - - /// @dev The maximum input permitted in {exp2}. - int256 constant uEXP2_MAX_INPUT = 192e18 - 1; - SD59x18 constant EXP2_MAX_INPUT = SD59x18.wrap(uEXP2_MAX_INPUT); - - /// @dev The maximum input permitted in {exp}. - int256 constant uEXP_MAX_INPUT = 133_084258667509499440; - SD59x18 constant EXP_MAX_INPUT = SD59x18.wrap(uEXP_MAX_INPUT); - - /// @dev Euler's number as an SD59x18 number. - SD59x18 constant E = SD59x18.wrap(2_718281828459045235); - - int256 constant uMAX_SQRT = uMAX_SD59x18 / uUNIT; - SD59x18 constant MAX_SQRT = SD59x18.wrap(uMAX_SQRT); - - SD59x18 internal constant MAX_PERMITTED_EXP2 = SD59x18.wrap(192e18 - 1); - SD59x18 internal constant MIN_PERMITTED_EXP2 = SD59x18.wrap(-59_794705707972522261); - - SD59x18 internal constant MAX_PERMITTED_EXP = SD59x18.wrap(133_084258667509499440); - SD59x18 internal constant MIN_PERMITTED_EXP = SD59x18.wrap(-41_446531673892822322); - - SD59x18 internal constant MAX_PERMITTED_POW = SD59x18.wrap(2 ** 192 * 10 ** 18 - 1); - /// @dev Half the UNIT number. - int256 constant uHALF_UNIT = 0.5e18; - SD59x18 constant HALF_UNIT = SD59x18.wrap(uHALF_UNIT); - - /// @dev log2(10) as an SD59x18 number. - int256 constant uLOG2_10 = 3_321928094887362347; - SD59x18 constant LOG2_10 = SD59x18.wrap(uLOG2_10); - - /// @dev log2(e) as an SD59x18 number. - int256 constant uLOG2_E = 1_442695040888963407; - SD59x18 constant LOG2_E = SD59x18.wrap(uLOG2_E); - - /* ================================================================ - Events used for debugging or showing information. - ================================================================ */ - event Value(string reason, SD59x18 val); - event LogErr(bytes error); - - - /* ================================================================ - Helper functions. - ================================================================ */ - - // Check that there are remaining significant digits after a multiplication - // Uses functions from the library under test! - function significant_digits_are_lost_in_mult(SD59x18 a, SD59x18 b) public pure returns (bool) { - int256 la = convert(floor(log10(abs(a)))); - int256 lb = convert(floor(log10(abs(b)))); - - return(la + lb < -18); - } - - // Return how many significant bits will remain after multiplying a and b - // Uses functions from the library under test! - function significant_digits_after_mult(SD59x18 a, SD59x18 b) public pure returns (uint256) { - int256 la = convert(floor(log10(abs(a)))); - int256 lb = convert(floor(log10(abs(b)))); - int256 prec = la + lb; - - if (prec < -18) return 0; - else return(59 + uint256(prec)); - } - - // Return how many significant bits will be lost after multiplying a and b - // Uses functions from the library under test! - function significant_digits_lost_in_mult(SD59x18 a, SD59x18 b) public pure returns (uint256) { - int256 la = convert(floor(log10(abs(a)))); - int256 lb = convert(floor(log10(abs(b)))); - // digits left - int256 prec = la + lb; if (la > 0 && lb > 0) { return 0; } else { - return la < lb ? uint256(-la) : uint256(-lb); + return absInt(la) < absInt(lb) ? uint256(-la) : uint256(-lb); } } + // Return the absolute value of the input + function absInt(int256 a) public pure returns (uint256) { + return a >= 0 ? uint256(a) : uint256(-a); + } + /* ================================================================ Library wrappers. These functions allow calling the PRBMathSD59x18 library. @@ -2676,6 +479,12 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { require(xy_z.neq(ZERO_FP) && x_yz.neq(ZERO_FP)); + // Checks that at least 9 digits of precision are left after multiplication + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + require(significant_digits_after_mult(y, z) > REQUIRED_SIGNIFICANT_DIGITS); + require(significant_digits_after_mult(x_y, z) > REQUIRED_SIGNIFICANT_DIGITS); + require(significant_digits_after_mult(x, y_z) > REQUIRED_SIGNIFICANT_DIGITS); + uint256 digitsLost = significant_digits_lost_in_mult(x, y); digitsLost += significant_digits_lost_in_mult(x, z); @@ -3107,11 +916,12 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { // original number function inv_test_double_inverse(SD59x18 x) public { require(x.neq(ZERO_FP)); + require(inv(x).neq(ZERO_FP)); SD59x18 double_inv_x = inv(inv(x)); - // The maximum loss of precision will be 2 * log2(x) bits rounded up - uint256 loss = 2 * significant_digits_lost_in_mult(x, x)+ 2; + // The maximum loss of precision will be 2 * log10(x) digits rounded up + uint256 loss = 2 * significant_digits_lost_in_mult(x, inv(x))+ 2; assertEqWithinDecimalPrecision(x, double_inv_x, loss); } @@ -3137,7 +947,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 x_y = div(x, y); SD59x18 y_x = div(y, x); - assertEqWithinTolerance(x_y, inv(y_x), ONE_TENTH_FP, "0.1%"); + assertEqWithinTolerance(x_y, inv(y_x), ONE_FP, "1%"); } // Test the multiplication of inverses @@ -3152,11 +962,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 x_y = mul(x, y); SD59x18 inv_x_y = inv(x_y); - // The maximum loss of precision is given by the formula: - // 2 * | log2(x) - log2(y) | + 1 - uint256 loss = 2 * significant_digits_lost_in_mult(x, y) + 1; - - assertEqWithinDecimalPrecision(inv_x_y, inv_x_times_inv_y, loss); + assertEqWithinTolerance(inv_x_y, inv_x_times_inv_y, ONE_FP, "1%"); } // Test identity property @@ -3166,8 +972,8 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 inv_x = inv(x); SD59x18 identity = mul(inv_x, x); - // They should agree with a tolerance of one tenth of a percent - assertEqWithinTolerance(identity, ONE_FP, ONE_TENTH_FP, "0.1%"); + // They should agree with a tolerance of one percent + assertEqWithinTolerance(identity, ONE_FP, ONE_FP, "1%"); } // Test that the absolute value of the result is in range zero-one @@ -3403,6 +1209,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 x_a = pow(x, a); SD59x18 x_a_b = pow(x_a, b); SD59x18 x_ab = pow(x, a.mul(b)); + require(x_a_b.neq(ZERO_FP) && x_ab.neq(ZERO_FP)); assertEqWithinDecimalPrecision(x_a_b, x_ab, 9); } @@ -3608,7 +1415,9 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 square_x = x.mul(x); SD59x18 sqrt_square_x = sqrt(square_x); - assertEq(sqrt_square_x, x); + uint256 loss = significant_digits_lost_in_mult(x, x); + + assertEqWithinDecimalPrecision(sqrt_square_x, x, loss); } /* ================================================================ @@ -3683,7 +1492,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); // The maximum loss of precision is given by the formula: - // | log2(x) + log2(y) | + // | log10(x) + log10(y) | uint256 loss = significant_digits_lost_in_mult(x, y) + 2; assertEqWithinDecimalPrecision(log2_x_log2_y, log2_xy, loss); @@ -3698,7 +1507,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 log2_x_y = log2(x_y); SD59x18 y_log2_x = mul(log2(x), y); - assertEqWithinTolerance(y_log2_x, log2_x_y, ONE_TENTH_FP, "0.1%"); + assertEqWithinTolerance(y_log2_x, log2_x_y, ONE_FP, "1%"); } // Base 2 logarithm is strictly increasing @@ -3798,7 +1607,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { require(significant_digits_after_mult(ln(x), y) > REQUIRED_SIGNIFICANT_DIGITS); - assertEqWithinTolerance(ln_x_y, y_ln_x, ONE_TENTH_FP, "0.1%"); + assertEqWithinTolerance(ln_x_y, y_ln_x, ONE_FP, "1%"); } // Natural logarithm is strictly increasing @@ -4107,7 +1916,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 x_b = powu(x, b); SD59x18 x_ab = powu(x, a + b); - assertEqWithinBitPrecision(mul(x_a, x_b), x_ab, 10); + assertEqWithinDecimalPrecision(mul(x_a, x_b), x_ab, 10); } // Test for power of an exponentiation @@ -4124,7 +1933,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 x_a_b = powu(x_a, b); SD59x18 x_ab = powu(x, a * b); - assertEqWithinBitPrecision(x_a_b, x_ab, 10); + assertEqWithinDecimalPrecision(x_a_b, x_ab, 10); } // Test for power of a product @@ -4145,7 +1954,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 x_a = powu(x, a); SD59x18 y_a = powu(y, a); - assertEqWithinBitPrecision(mul(x_a, y_a), xy_a, 10); + assertEqWithinDecimalPrecision(mul(x_a, y_a), xy_a, 10); } // Test for result being greater than or lower than the argument, depending on @@ -4259,11 +2068,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { // Ensure we have enough significant digits for the result to be meaningful require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); - // The maximum loss of precision is given by the formula: - // | log10(x) + log10(y) | - uint256 loss = significant_digits_lost_in_mult(x, y) + 2; - - assertEqWithinDecimalPrecision(log10_x_log10_y, log10_xy, loss); + assertEqWithinTolerance(log10_x_log10_y, log10_xy, ONE_FP, "1%"); } // Test for logarithm of a power @@ -4274,7 +2079,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 log10_x_y = log10(x_y); SD59x18 y_log10_x = mul(log10(x), y); - assertEqWithinTolerance(log10_x_y, y_log10_x, ONE_TENTH_FP, "0.1%"); + assertEqWithinTolerance(log10_x_y, y_log10_x, ONE_FP, "1%"); } // Base 10 logarithm is strictly increasing diff --git a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol index 3f70a83..6acf564 100644 --- a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol @@ -4,7 +4,7 @@ import { UD60x18 } from "@prb-math-v3/UD60x18.sol"; import {add, sub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb-math-v3/ud60x18/Helpers.sol"; import {convert} from "@prb-math-v3/ud60x18/Conversions.sol"; import {msb} from "@prb-math-v3/Common.sol"; -import {intoUint128, intoUint256} from "@prb-math-v3/ud60x18/Casting.sol"; +import {intoUint128, intoUint256, intoSD59x18} from "@prb-math-v3/ud60x18/Casting.sol"; import {mul, div, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, powu, gm} from "@prb-math-v3/ud60x18/Math.sol"; import "./utils/AssertionHelperUD.sol"; @@ -561,10 +561,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { UD60x18 double_inv_x = inv(inv(x)); - // The maximum loss of precision will be 2 * log2(x) bits rounded up - uint256 loss = 2 * intoUint256(log2(x)) + 2; - - assertEqWithinBitPrecision(x, double_inv_x, loss); + assertEqWithinTolerance(x, double_inv_x, ONE_FP, "1%"); } // Test equivalence with division @@ -588,7 +585,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { UD60x18 x_y = div(x, y); UD60x18 y_x = div(y, x); - assertEqWithinTolerance(x_y, inv(y_x), ONE_TENTH_FP, "0.1%"); + assertEqWithinTolerance(x_y, inv(y_x), ONE_FP, "1%"); } // Test the multiplication of inverses @@ -603,11 +600,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { UD60x18 x_y = mul(x, y); UD60x18 inv_x_y = inv(x_y); - // The maximum loss of precision is given by the formula: - // 2 * | log2(x) - log2(y) | + 1 - uint256 loss = 2 * intoUint256(log2(x).sub(log2(y))) + 1; - - assertEqWithinBitPrecision(inv_x_y, inv_x_times_inv_y, loss); + assertEqWithinTolerance(inv_x_y, inv_x_times_inv_y, ONE_FP, "1%"); } // Test multiplicative identity property @@ -619,8 +612,8 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { require(inv_x.neq(ZERO_FP) && identity.neq(ZERO_FP)); - // They should agree with a tolerance of one tenth of a percent - assertEqWithinTolerance(identity, ONE_FP, ONE_TENTH_FP, "0.1%"); + // They should agree with a tolerance of one percent + assertEqWithinTolerance(identity, ONE_FP, ONE_FP, "1%"); } // Test that the value of the result is in range zero-one @@ -979,7 +972,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { UD60x18 square_x = x.mul(x); UD60x18 sqrt_square_x = sqrt(square_x); - assertEq(sqrt_square_x, x); + assertEqWithinDecimalPrecision(sqrt_square_x, x, 2); } /* ================================================================ @@ -1028,11 +1021,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { UD60x18 xy = mul(x, y); UD60x18 log2_xy = log2(xy); - // The maximum loss of precision is given by the formula: - // | log2(x) + log2(y) | - uint256 loss = intoUint256(log2(x).add(log2(y))); - - assertEqWithinBitPrecision(log2_x_log2_y, log2_xy, loss); + assertEqWithinTolerance(log2_x_log2_y, log2_xy, ONE_FP, "1%"); } // Test for logarithm of a power @@ -1044,7 +1033,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { UD60x18 log2_x_y = log2(x_y); UD60x18 y_log2_x = mul(log2(x), y); - assertEqWithinTolerance(y_log2_x, log2_x_y, ONE_TENTH_FP, "0.1%"); + assertEqWithinTolerance(y_log2_x, log2_x_y, ONE_FP, "1%"); } // Base 2 logarithm is strictly increasing @@ -1124,10 +1113,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { UD60x18 xy = mul(x, y); UD60x18 ln_xy = ln(xy); - // The maximum loss of precision is given by the formula: - // | log2(x) + log2(y) | - uint256 loss = intoUint256(log2(x).add(log2(y))); - assertEqWithinBitPrecision(ln_x_ln_y, ln_xy, loss); + assertEqWithinTolerance(ln_xy, ln_x_ln_y, ONE_FP, "1%"); } // Test for logarithm of a power @@ -1138,7 +1124,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { UD60x18 ln_x_y = ln(x_y); UD60x18 y_ln_x = mul(ln(x), y); - assertEqWithinDecimalPrecision(ln_x_y, y_ln_x, 9); + assertEqWithinTolerance(ln_x_y, y_ln_x, ONE_FP, "1%"); } // Natural logarithm is strictly increasing @@ -1502,11 +1488,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { UD60x18 xy = mul(x, y); UD60x18 log10_xy = log10(xy); - // The maximum loss of precision is given by the formula: - // | log10(x) + log10(y) | - uint256 loss = intoUint256(log10(x).add(log10(y))); - - assertEqWithinBitPrecision(log10_x_log10_y, log10_xy, loss); + assertEqWithinTolerance(log10_x_log10_y, log10_xy, ONE_FP, "1%"); } // Test for logarithm of a power @@ -1517,7 +1499,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { UD60x18 log10_x_y = log10(x_y); UD60x18 y_log10_x = mul(log10(x), y); - assertEqWithinTolerance(log10_x_y, y_log10_x, ONE_TENTH_FP, "0.1%"); + assertEqWithinTolerance(log10_x_y, y_log10_x, ONE_FP, "1%"); } // Base 10 logarithm is strictly increasing @@ -1528,7 +1510,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { UD60x18 log10_x = log10(x); UD60x18 log10_y = log10(y); - assertGte(log10_x, log10_y); + assertGt(log10_x, log10_y); } /* ================================================================ diff --git a/contracts/Math/PRBMath/v4/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/v4/PRBMathSD59x18PropertyTests.sol index f0b26f4..876a875 100644 --- a/contracts/Math/PRBMath/v4/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v4/PRBMathSD59x18PropertyTests.sol @@ -28,7 +28,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { /* ================================================================ Constants used for precision loss calculations ================================================================ */ - uint256 internal REQUIRED_SIGNIFICANT_DIGITS = 10; + uint256 internal REQUIRED_SIGNIFICANT_DIGITS = 9; SD59x18 internal LOG2_PRECISION_LOSS = SD59x18.wrap(1); /* ================================================================ @@ -101,7 +101,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { return(la + lb < -18); } - // Return how many significant bits will remain after multiplying a and b + // Return how many significant digits will remain after multiplying a and b // Uses functions from the library under test! function significant_digits_after_mult(SD59x18 a, SD59x18 b) public pure returns (uint256) { int256 la = convert(floor(log10(abs(a)))); @@ -109,24 +109,27 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { int256 prec = la + lb; if (prec < -18) return 0; - else return(59 + uint256(prec)); + else return(18 + absInt(prec)); } - // Return how many significant bits will be lost after multiplying a and b + // Return how many significant digits will be lost after multiplying a and b // Uses functions from the library under test! function significant_digits_lost_in_mult(SD59x18 a, SD59x18 b) public pure returns (uint256) { int256 la = convert(floor(log10(abs(a)))); int256 lb = convert(floor(log10(abs(b)))); - // digits left - int256 prec = la + lb; if (la > 0 && lb > 0) { return 0; } else { - return la < lb ? uint256(-la) : uint256(-lb); + return absInt(la) < absInt(lb) ? uint256(-la) : uint256(-lb); } } + // Return the absolute value of the input + function absInt(int256 a) public pure returns (uint256) { + return a >= 0 ? uint256(a) : uint256(-a); + } + /* ================================================================ Library wrappers. These functions allow calling the PRBMathSD59x18 library. @@ -476,6 +479,12 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { require(xy_z.neq(ZERO_FP) && x_yz.neq(ZERO_FP)); + // Checks that at least 9 digits of precision are left after multiplication + require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + require(significant_digits_after_mult(y, z) > REQUIRED_SIGNIFICANT_DIGITS); + require(significant_digits_after_mult(x_y, z) > REQUIRED_SIGNIFICANT_DIGITS); + require(significant_digits_after_mult(x, y_z) > REQUIRED_SIGNIFICANT_DIGITS); + uint256 digitsLost = significant_digits_lost_in_mult(x, y); digitsLost += significant_digits_lost_in_mult(x, z); @@ -907,11 +916,12 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { // original number function inv_test_double_inverse(SD59x18 x) public { require(x.neq(ZERO_FP)); + require(inv(x).neq(ZERO_FP)); SD59x18 double_inv_x = inv(inv(x)); - // The maximum loss of precision will be 2 * log2(x) bits rounded up - uint256 loss = 2 * significant_digits_lost_in_mult(x, x)+ 2; + // The maximum loss of precision will be 2 * log10(x) digits rounded up + uint256 loss = 2 * significant_digits_lost_in_mult(x, inv(x))+ 2; assertEqWithinDecimalPrecision(x, double_inv_x, loss); } @@ -937,7 +947,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 x_y = div(x, y); SD59x18 y_x = div(y, x); - assertEqWithinTolerance(x_y, inv(y_x), ONE_TENTH_FP, "0.1%"); + assertEqWithinTolerance(x_y, inv(y_x), ONE_FP, "1%"); } // Test the multiplication of inverses @@ -952,11 +962,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 x_y = mul(x, y); SD59x18 inv_x_y = inv(x_y); - // The maximum loss of precision is given by the formula: - // 2 * | log2(x) - log2(y) | + 1 - uint256 loss = 2 * significant_digits_lost_in_mult(x, y) + 1; - - assertEqWithinDecimalPrecision(inv_x_y, inv_x_times_inv_y, loss); + assertEqWithinTolerance(inv_x_y, inv_x_times_inv_y, ONE_FP, "1%"); } // Test identity property @@ -966,8 +972,8 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 inv_x = inv(x); SD59x18 identity = mul(inv_x, x); - // They should agree with a tolerance of one tenth of a percent - assertEqWithinTolerance(identity, ONE_FP, ONE_TENTH_FP, "0.1%"); + // They should agree with a tolerance of one percent + assertEqWithinTolerance(identity, ONE_FP, ONE_FP, "1%"); } // Test that the absolute value of the result is in range zero-one @@ -1203,6 +1209,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 x_a = pow(x, a); SD59x18 x_a_b = pow(x_a, b); SD59x18 x_ab = pow(x, a.mul(b)); + require(x_a_b.neq(ZERO_FP) && x_ab.neq(ZERO_FP)); assertEqWithinDecimalPrecision(x_a_b, x_ab, 9); } @@ -1408,7 +1415,9 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 square_x = x.mul(x); SD59x18 sqrt_square_x = sqrt(square_x); - assertEq(sqrt_square_x, x); + uint256 loss = significant_digits_lost_in_mult(x, x); + + assertEqWithinDecimalPrecision(sqrt_square_x, x, loss); } /* ================================================================ @@ -1483,7 +1492,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); // The maximum loss of precision is given by the formula: - // | log2(x) + log2(y) | + // | log10(x) + log10(y) | uint256 loss = significant_digits_lost_in_mult(x, y) + 2; assertEqWithinDecimalPrecision(log2_x_log2_y, log2_xy, loss); @@ -1498,7 +1507,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 log2_x_y = log2(x_y); SD59x18 y_log2_x = mul(log2(x), y); - assertEqWithinTolerance(y_log2_x, log2_x_y, ONE_TENTH_FP, "0.1%"); + assertEqWithinTolerance(y_log2_x, log2_x_y, ONE_FP, "1%"); } // Base 2 logarithm is strictly increasing @@ -1598,7 +1607,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { require(significant_digits_after_mult(ln(x), y) > REQUIRED_SIGNIFICANT_DIGITS); - assertEqWithinTolerance(ln_x_y, y_ln_x, ONE_TENTH_FP, "0.1%"); + assertEqWithinTolerance(ln_x_y, y_ln_x, ONE_FP, "1%"); } // Natural logarithm is strictly increasing @@ -1907,7 +1916,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 x_b = powu(x, b); SD59x18 x_ab = powu(x, a + b); - assertEqWithinBitPrecision(mul(x_a, x_b), x_ab, 10); + assertEqWithinDecimalPrecision(mul(x_a, x_b), x_ab, 10); } // Test for power of an exponentiation @@ -1924,7 +1933,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 x_a_b = powu(x_a, b); SD59x18 x_ab = powu(x, a * b); - assertEqWithinBitPrecision(x_a_b, x_ab, 10); + assertEqWithinDecimalPrecision(x_a_b, x_ab, 10); } // Test for power of a product @@ -1945,7 +1954,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 x_a = powu(x, a); SD59x18 y_a = powu(y, a); - assertEqWithinBitPrecision(mul(x_a, y_a), xy_a, 10); + assertEqWithinDecimalPrecision(mul(x_a, y_a), xy_a, 10); } // Test for result being greater than or lower than the argument, depending on @@ -2059,11 +2068,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { // Ensure we have enough significant digits for the result to be meaningful require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); - // The maximum loss of precision is given by the formula: - // | log10(x) + log10(y) | - uint256 loss = significant_digits_lost_in_mult(x, y) + 2; - - assertEqWithinDecimalPrecision(log10_x_log10_y, log10_xy, loss); + assertEqWithinTolerance(log10_x_log10_y, log10_xy, ONE_FP, "1%"); } // Test for logarithm of a power @@ -2074,7 +2079,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 log10_x_y = log10(x_y); SD59x18 y_log10_x = mul(log10(x), y); - assertEqWithinTolerance(log10_x_y, y_log10_x, ONE_TENTH_FP, "0.1%"); + assertEqWithinTolerance(log10_x_y, y_log10_x, ONE_FP, "1%"); } // Base 10 logarithm is strictly increasing diff --git a/contracts/Math/PRBMath/v4/PRBMathUD60x18PropertyTests.sol b/contracts/Math/PRBMath/v4/PRBMathUD60x18PropertyTests.sol index 41f7385..2380c71 100644 --- a/contracts/Math/PRBMath/v4/PRBMathUD60x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v4/PRBMathUD60x18PropertyTests.sol @@ -561,10 +561,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { UD60x18 double_inv_x = inv(inv(x)); - // The maximum loss of precision will be 2 * log2(x) bits rounded up - uint256 loss = 2 * intoUint256(log2(x)) + 2; - - assertEqWithinBitPrecision(x, double_inv_x, loss); + assertEqWithinTolerance(x, double_inv_x, ONE_FP, "1%"); } // Test equivalence with division @@ -588,7 +585,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { UD60x18 x_y = div(x, y); UD60x18 y_x = div(y, x); - assertEqWithinTolerance(x_y, inv(y_x), ONE_TENTH_FP, "0.1%"); + assertEqWithinTolerance(x_y, inv(y_x), ONE_FP, "1%"); } // Test the multiplication of inverses @@ -603,11 +600,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { UD60x18 x_y = mul(x, y); UD60x18 inv_x_y = inv(x_y); - // The maximum loss of precision is given by the formula: - // 2 * | log2(x) - log2(y) | + 1 - uint256 loss = 2 * intoUint256(log2(x).sub(log2(y))) + 1; - - assertEqWithinBitPrecision(inv_x_y, inv_x_times_inv_y, loss); + assertEqWithinTolerance(inv_x_y, inv_x_times_inv_y, ONE_FP, "1%"); } // Test multiplicative identity property @@ -619,8 +612,8 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { require(inv_x.neq(ZERO_FP) && identity.neq(ZERO_FP)); - // They should agree with a tolerance of one tenth of a percent - assertEqWithinTolerance(identity, ONE_FP, ONE_TENTH_FP, "0.1%"); + // They should agree with a tolerance of one percent + assertEqWithinTolerance(identity, ONE_FP, ONE_FP, "1%"); } // Test that the value of the result is in range zero-one @@ -979,7 +972,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { UD60x18 square_x = x.mul(x); UD60x18 sqrt_square_x = sqrt(square_x); - assertEq(sqrt_square_x, x); + assertEqWithinDecimalPrecision(sqrt_square_x, x, 2); } /* ================================================================ @@ -1028,11 +1021,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { UD60x18 xy = mul(x, y); UD60x18 log2_xy = log2(xy); - // The maximum loss of precision is given by the formula: - // | log2(x) + log2(y) | - uint256 loss = intoUint256(log2(x).add(log2(y))); - - assertEqWithinBitPrecision(log2_x_log2_y, log2_xy, loss); + assertEqWithinTolerance(log2_x_log2_y, log2_xy, ONE_FP, "1%"); } // Test for logarithm of a power @@ -1044,7 +1033,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { UD60x18 log2_x_y = log2(x_y); UD60x18 y_log2_x = mul(log2(x), y); - assertEqWithinTolerance(y_log2_x, log2_x_y, ONE_TENTH_FP, "0.1%"); + assertEqWithinTolerance(y_log2_x, log2_x_y, ONE_FP, "1%"); } // Base 2 logarithm is strictly increasing @@ -1124,10 +1113,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { UD60x18 xy = mul(x, y); UD60x18 ln_xy = ln(xy); - // The maximum loss of precision is given by the formula: - // | log2(x) + log2(y) | - uint256 loss = intoUint256(log2(x).add(log2(y))); - assertEqWithinBitPrecision(ln_x_ln_y, ln_xy, loss); + assertEqWithinTolerance(ln_xy, ln_x_ln_y, ONE_FP, "1%"); } // Test for logarithm of a power @@ -1138,7 +1124,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { UD60x18 ln_x_y = ln(x_y); UD60x18 y_ln_x = mul(ln(x), y); - assertEqWithinDecimalPrecision(ln_x_y, y_ln_x, 9); + assertEqWithinTolerance(ln_x_y, y_ln_x, ONE_FP, "1%"); } // Natural logarithm is strictly increasing @@ -1502,11 +1488,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { UD60x18 xy = mul(x, y); UD60x18 log10_xy = log10(xy); - // The maximum loss of precision is given by the formula: - // | log10(x) + log10(y) | - uint256 loss = intoUint256(log10(x).add(log10(y))); - - assertEqWithinBitPrecision(log10_x_log10_y, log10_xy, loss); + assertEqWithinTolerance(log10_x_log10_y, log10_xy, ONE_FP, "1%"); } // Test for logarithm of a power @@ -1517,7 +1499,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { UD60x18 log10_x_y = log10(x_y); UD60x18 y_log10_x = mul(log10(x), y); - assertEqWithinTolerance(log10_x_y, y_log10_x, ONE_TENTH_FP, "0.1%"); + assertEqWithinTolerance(log10_x_y, y_log10_x, ONE_FP, "1%"); } // Base 10 logarithm is strictly increasing @@ -1527,8 +1509,8 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { UD60x18 log10_x = log10(x); UD60x18 log10_y = log10(y); - - assertGte(log10_x, log10_y); + + assertGt(log10_x, log10_y); } /* ================================================================ From a4a4d14e942946c05602d4ac1123adc79f8474f9 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Thu, 6 Jul 2023 17:04:44 +0200 Subject: [PATCH 30/32] formatting --- contracts/Math/PRBMath/README.md | 6 +- .../v3/PRBMathSD59x18PropertyTests.sol | 182 ++++++++++------- .../v3/PRBMathUD60x18PropertyTests.sol | 94 +++++---- .../PRBMath/v3/utils/AssertionHelperSD.sol | 69 ++++--- .../PRBMath/v3/utils/AssertionHelperUD.sol | 60 ++++-- .../v4/PRBMathSD59x18PropertyTests.sol | 184 +++++++++++------- .../v4/PRBMathUD60x18PropertyTests.sol | 96 +++++---- .../PRBMath/v4/utils/AssertionHelperSD.sol | 69 ++++--- .../PRBMath/v4/utils/AssertionHelperUD.sol | 60 ++++-- 9 files changed, 493 insertions(+), 327 deletions(-) diff --git a/contracts/Math/PRBMath/README.md b/contracts/Math/PRBMath/README.md index 646b51b..45b43d8 100644 --- a/contracts/Math/PRBMath/README.md +++ b/contracts/Math/PRBMath/README.md @@ -24,10 +24,10 @@ The next step, creating the tests, means to implement Solidity functions that ve // Test for commutative property // x + y == y + x function add_test_commutative(SD59x18 x, SD59x18 y) public pure { - SD59x18 x_y = x.add(y); - SD59x18 y_x = y.add(x); + SD59x18 x_y = x.add(y); + SD59x18 y_x = y.add(x); - assert(x_y.eq(y_x)); + assert(x_y.eq(y_x)); } ``` diff --git a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol index 34976f5..ca704a0 100644 --- a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol @@ -1,6 +1,6 @@ pragma solidity ^0.8.19; -import { SD59x18 } from "@prb-math-v3/SD59x18.sol"; +import {SD59x18} from "@prb-math-v3/SD59x18.sol"; import {add, sub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb-math-v3/sd59x18/Helpers.sol"; import {convert} from "@prb-math-v3/sd59x18/Conversions.sol"; import {msb} from "@prb-math-v3/Common.sol"; @@ -9,7 +9,6 @@ import {mul, div, abs, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, p import "./utils/AssertionHelperSD.sol"; contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { - /* ================================================================ 59x18 fixed-point constants used for testing specific values. This assumes that PRBMath library's convert(x) works as expected. @@ -41,11 +40,13 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 constant UNIT = SD59x18.wrap(1e18); /// @dev The minimum value an SD59x18 number can have. - int256 constant uMIN_SD59x18 = -57896044618658097711785492504343953926634992332820282019728_792003956564819968; + int256 constant uMIN_SD59x18 = + -57896044618658097711785492504343953926634992332820282019728_792003956564819968; SD59x18 constant MIN_SD59x18 = SD59x18.wrap(uMIN_SD59x18); /// @dev The maximum value an SD59x18 number can have. - int256 constant uMAX_SD59x18 = 57896044618658097711785492504343953926634992332820282019728_792003956564819967; + int256 constant uMAX_SD59x18 = + 57896044618658097711785492504343953926634992332820282019728_792003956564819967; SD59x18 constant MAX_SD59x18 = SD59x18.wrap(uMAX_SD59x18); /// @dev The maximum input permitted in {exp2}. @@ -63,12 +64,16 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 constant MAX_SQRT = SD59x18.wrap(uMAX_SQRT); SD59x18 internal constant MAX_PERMITTED_EXP2 = SD59x18.wrap(192e18 - 1); - SD59x18 internal constant MIN_PERMITTED_EXP2 = SD59x18.wrap(-59_794705707972522261); + SD59x18 internal constant MIN_PERMITTED_EXP2 = + SD59x18.wrap(-59_794705707972522261); + + SD59x18 internal constant MAX_PERMITTED_EXP = + SD59x18.wrap(133_084258667509499440); + SD59x18 internal constant MIN_PERMITTED_EXP = + SD59x18.wrap(-41_446531673892822322); - SD59x18 internal constant MAX_PERMITTED_EXP = SD59x18.wrap(133_084258667509499440); - SD59x18 internal constant MIN_PERMITTED_EXP = SD59x18.wrap(-41_446531673892822322); - - SD59x18 internal constant MAX_PERMITTED_POW = SD59x18.wrap(2 ** 192 * 10 ** 18 - 1); + SD59x18 internal constant MAX_PERMITTED_POW = + SD59x18.wrap(2 ** 192 * 10 ** 18 - 1); /// @dev Half the UNIT number. int256 constant uHALF_UNIT = 0.5e18; SD59x18 constant HALF_UNIT = SD59x18.wrap(uHALF_UNIT); @@ -87,34 +92,42 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { event Value(string reason, SD59x18 val); event LogErr(bytes error); - /* ================================================================ Helper functions. ================================================================ */ // Check that there are remaining significant digits after a multiplication // Uses functions from the library under test! - function significant_digits_are_lost_in_mult(SD59x18 a, SD59x18 b) public pure returns (bool) { + function significant_digits_are_lost_in_mult( + SD59x18 a, + SD59x18 b + ) public pure returns (bool) { int256 la = convert(floor(log10(abs(a)))); int256 lb = convert(floor(log10(abs(b)))); - return(la + lb < -18); + return (la + lb < -18); } // Return how many significant digits will remain after multiplying a and b // Uses functions from the library under test! - function significant_digits_after_mult(SD59x18 a, SD59x18 b) public pure returns (uint256) { + function significant_digits_after_mult( + SD59x18 a, + SD59x18 b + ) public pure returns (uint256) { int256 la = convert(floor(log10(abs(a)))); int256 lb = convert(floor(log10(abs(b)))); int256 prec = la + lb; if (prec < -18) return 0; - else return(18 + absInt(prec)); + else return (18 + absInt(prec)); } // Return how many significant digits will be lost after multiplying a and b // Uses functions from the library under test! - function significant_digits_lost_in_mult(SD59x18 a, SD59x18 b) public pure returns (uint256) { + function significant_digits_lost_in_mult( + SD59x18 a, + SD59x18 b + ) public pure returns (uint256) { int256 la = convert(floor(log10(abs(a)))); int256 lb = convert(floor(log10(abs(b)))); @@ -140,25 +153,25 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { // Wrapper for external try/catch calls function helpersAdd(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return add(x,y); + return add(x, y); } // Wrapper for external try/catch calls function helpersSub(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return sub(x,y); + return sub(x, y); } // Wrapper for external try/catch calls function helpersMul(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return mul(x,y); + return mul(x, y); } function helpersDiv(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return div(x,y); + return div(x, y); } function neg(SD59x18 x) public pure returns (SD59x18) { - return SD59x18.wrap(-SD59x18.unwrap(x)); + return SD59x18.wrap(-SD59x18.unwrap(x)); } function helpersAbs(SD59x18 x) public pure returns (SD59x18) { @@ -210,7 +223,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { } function helpersGm(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return gm(x,y); + return gm(x, y); } /* ================================================================ @@ -350,7 +363,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { function sub_test_non_commutative(SD59x18 x, SD59x18 y) public { SD59x18 x_y = x.sub(y); SD59x18 y_x = y.sub(x); - + assertEq(x_y, neg(y_x)); } @@ -371,7 +384,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 x_minus_y_plus_y = x_minus_y.add(y); SD59x18 x_plus_y_minus_y = x_plus_y.sub(y); - + assertEq(x_minus_y_plus_y, x_plus_y_minus_y); assertEq(x_minus_y_plus_y, x); } @@ -416,7 +429,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { } } - // Subtracting minus one from the maximum value should revert, + // Subtracting minus one from the maximum value should revert, // as it is out of range function sub_test_maximum_value_minus_neg_one() public { try this.helpersSub(MAX_SD59x18, MINUS_ONE_FP) { @@ -480,14 +493,22 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { require(xy_z.neq(ZERO_FP) && x_yz.neq(ZERO_FP)); // Checks that at least 9 digits of precision are left after multiplication - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); - require(significant_digits_after_mult(y, z) > REQUIRED_SIGNIFICANT_DIGITS); - require(significant_digits_after_mult(x_y, z) > REQUIRED_SIGNIFICANT_DIGITS); - require(significant_digits_after_mult(x, y_z) > REQUIRED_SIGNIFICANT_DIGITS); + require( + significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS + ); + require( + significant_digits_after_mult(y, z) > REQUIRED_SIGNIFICANT_DIGITS + ); + require( + significant_digits_after_mult(x_y, z) > REQUIRED_SIGNIFICANT_DIGITS + ); + require( + significant_digits_after_mult(x, y_z) > REQUIRED_SIGNIFICANT_DIGITS + ); uint256 digitsLost = significant_digits_lost_in_mult(x, y); digitsLost += significant_digits_lost_in_mult(x, z); - + assertEqWithinDecimalPrecision(xy_z, x_yz, digitsLost); } @@ -501,11 +522,18 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 x_times_y = x.mul(y); SD59x18 x_times_z = x.mul(z); - require(add(x_times_y, x_times_z).neq(ZERO_FP) && x_times_y_plus_z.neq(ZERO_FP)); - assertEqWithinTolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP, "0.1%"); + require( + add(x_times_y, x_times_z).neq(ZERO_FP) && + x_times_y_plus_z.neq(ZERO_FP) + ); + assertEqWithinTolerance( + add(x_times_y, x_times_z), + x_times_y_plus_z, + ONE_TENTH_FP, + "0.1%" + ); } - // Test for identity operation // x * 1 == x (also check that x * 0 == 0) function mul_test_identity(SD59x18 x) public { @@ -517,7 +545,6 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { assertEq(x_1, x); } - // If x is positive and y is >= 1, the result should be larger than or equal to x // If x is positive and y is < 1, the result should be smaller than x function mul_test_x_positive(SD59x18 x, SD59x18 y) public { @@ -545,7 +572,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { assertGte(x_y, x); } } - + /* ================================================================ Tests for overflow and edge cases. These will make sure that the function reverts on overflow and @@ -557,7 +584,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { function mul_test_range(SD59x18 x, SD59x18 y) public { require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); - try this.helpersMul(x, y) returns(SD59x18 result) { + try this.helpersMul(x, y) returns (SD59x18 result) { assertLte(result, MAX_SD59x18); assertGte(result, MIN_SD59x18); } catch { @@ -579,7 +606,9 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { // Multiplying the minimum value times one shouldn't revert, as it is valid // Moreover, the result must be MIN_SD59x18 function mul_test_minimum_value() public { - try this.helpersMul(MIN_SD59x18.add(ONE_FP), ONE_FP) returns (SD59x18 result) { + try this.helpersMul(MIN_SD59x18.add(ONE_FP), ONE_FP) returns ( + SD59x18 result + ) { // Expected behaviour, does not revert assertEq(result, MIN_SD59x18.add(ONE_FP)); } catch { @@ -670,7 +699,6 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { behaves correctly on edge cases ================================================================ */ - // Test for division by zero function div_test_div_by_zero(SD59x18 x) public { require(x.gt(MIN_SD59x18)); @@ -794,7 +822,6 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { with math rules and expected behaviour. ================================================================ */ - // Test that the absolute value is always positive function abs_test_positive(SD59x18 x) public { require(x.gt(MIN_SD59x18)); @@ -921,7 +948,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 double_inv_x = inv(inv(x)); // The maximum loss of precision will be 2 * log10(x) digits rounded up - uint256 loss = 2 * significant_digits_lost_in_mult(x, inv(x))+ 2; + uint256 loss = 2 * significant_digits_lost_in_mult(x, inv(x)) + 2; assertEqWithinDecimalPrecision(x, double_inv_x, loss); } @@ -938,10 +965,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { // Test the anticommutativity of the division // x / y == 1 / (y / x) - function inv_test_division_noncommutativity( - SD59x18 x, - SD59x18 y - ) public { + function inv_test_division_noncommutativity(SD59x18 x, SD59x18 y) public { require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); SD59x18 x_y = div(x, y); @@ -1177,7 +1201,6 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { assertEq(one_pow_a, ONE_FP); } - // Test for product of powers of the same base // x ** a * x ** b == x ** (a + b) function pow_test_product_same_base( @@ -1216,11 +1239,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { // Test for power of a product // (x * y) ** a == x ** a * y ** a - function pow_test_product_power( - SD59x18 x, - SD59x18 y, - SD59x18 a - ) public { + function pow_test_product_power(SD59x18 x, SD59x18 y, SD59x18 a) public { require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); require(y.gte(ZERO_FP) && y.lte(MAX_PERMITTED_POW)); @@ -1301,7 +1320,11 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { // Power is strictly increasing // x > y && a >= 0 --> pow(x, a) >= pow(y, a) - function pow_test_strictly_increasing(SD59x18 x, SD59x18 y, SD59x18 a) public { + function pow_test_strictly_increasing( + SD59x18 x, + SD59x18 y, + SD59x18 a + ) public { require(x.gt(y) && x.lte(MAX_PERMITTED_POW)); require(a.gte(ZERO_FP)); @@ -1359,7 +1382,11 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 sqrt_x_squared = mul(sqrt_x, sqrt_x); // Precision loss is at most half the bits of the operand - assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); + assertEqWithinBitPrecision( + sqrt_x_squared, + x, + (intoUint256(log2(x)) >> 1) + 2 + ); } // Test for the inverse operation @@ -1371,7 +1398,11 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 sqrt_x_squared = pow(sqrt_x, convert(2)); // Precision loss is at most half the bits of the operand - assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); + assertEqWithinBitPrecision( + sqrt_x_squared, + x, + (intoUint256(log2(x)) >> 1) + 2 + ); } // Test for distributive property respect to the multiplication @@ -1385,7 +1416,9 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 sqrt_xy = sqrt(mul(x, y)); // Ensure we have enough significant digits for the result to be meaningful - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + require( + significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS + ); require( significant_digits_after_mult(sqrt_x, sqrt_y) > REQUIRED_SIGNIFICANT_DIGITS @@ -1426,7 +1459,6 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { behaves correctly on edge cases ================================================================ */ - // Test for zero case function sqrt_test_zero() public { assertEq(sqrt(ZERO_FP), ZERO_FP); @@ -1489,7 +1521,9 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 log2_xy = log2(xy); // Ensure we have enough significant digits for the result to be meaningful - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + require( + significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS + ); // The maximum loss of precision is given by the formula: // | log10(x) + log10(y) | @@ -1514,10 +1548,10 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { // y > 0 && x > y --> log2(x) > log2(y) function log2_test_strictly_increasing(SD59x18 x, SD59x18 y) public { require(y.gt(ZERO_FP) && x.gt(y)); - + SD59x18 log2_x = log2(x); SD59x18 log2_y = log2(y); - + assertGte(log2_x, log2_y); } @@ -1587,7 +1621,9 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 xy = mul(x, y); SD59x18 ln_xy = ln(xy); - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + require( + significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS + ); // The maximum loss of precision is given by the formula: // | log2(x) + log2(y) | @@ -1605,7 +1641,10 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 y_ln_x = mul(ln(x), y); - require(significant_digits_after_mult(ln(x), y) > REQUIRED_SIGNIFICANT_DIGITS); + require( + significant_digits_after_mult(ln(x), y) > + REQUIRED_SIGNIFICANT_DIGITS + ); assertEqWithinTolerance(ln_x_y, y_ln_x, ONE_FP, "1%"); } @@ -1614,7 +1653,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { // y > 0 && x > y --> log2(x) > log2(y) function ln_test_strictly_increasing(SD59x18 x, SD59x18 y) public { require(y.gt(ZERO_FP) && x.gt(y)); - + SD59x18 log2_x = ln(x); SD59x18 log2_y = ln(y); @@ -1730,7 +1769,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { } } - // Test for maximum value. This should overflow as it won't fit + // Test for maximum value. This should overflow as it won't fit // in the data type function exp2_test_maximum_permitted() public { try this.helpersExp2(MAX_PERMITTED_EXP2) { @@ -1938,11 +1977,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { // Test for power of a product // (x * y) ** a == x ** a * y ** a - function powu_test_product_power( - SD59x18 x, - SD59x18 y, - uint256 a - ) public { + function powu_test_product_power(SD59x18 x, SD59x18 y, uint256 a) public { require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); require(x.lte(MAX_SD59x18) && y.lte(MAX_SD59x18)); @@ -2011,7 +2046,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 x_a = powu(x, a); SD59x18 y_a = powu(y, a); - + assertGte(x_a, y_a); } @@ -2066,7 +2101,9 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 log10_xy = log10(xy); // Ensure we have enough significant digits for the result to be meaningful - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + require( + significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS + ); assertEqWithinTolerance(log10_x_log10_y, log10_xy, ONE_FP, "1%"); } @@ -2078,7 +2115,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { SD59x18 x_y = pow(x, y); SD59x18 log10_x_y = log10(x_y); SD59x18 y_log10_x = mul(log10(x), y); - + assertEqWithinTolerance(log10_x_y, y_log10_x, ONE_FP, "1%"); } @@ -2086,7 +2123,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { // x > y && y > 0 --> log10(x) > log10(y) function log10_is_increasing(SD59x18 x, SD59x18 y) public { require(y.gt(ZERO_FP) && x.gt(y)); - + SD59x18 log2_x = log10(x); SD59x18 log2_y = log10(y); @@ -2155,7 +2192,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { require(x_sign = y_sign); SD59x18 x_mul_y = x.mul(y); - SD59x18 gm_squared = pow(gm(x,y), TWO_FP); + SD59x18 gm_squared = pow(gm(x, y), TWO_FP); assertEqWithinTolerance(x_mul_y, gm_squared, ONE_TENTH_FP, "0.1%"); } @@ -2212,5 +2249,4 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { // Expected revert, gm of a negative product is not defined } } - -} \ No newline at end of file +} diff --git a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol index 6acf564..47d7377 100644 --- a/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol @@ -1,6 +1,6 @@ pragma solidity ^0.8.19; -import { UD60x18 } from "@prb-math-v3/UD60x18.sol"; +import {UD60x18} from "@prb-math-v3/UD60x18.sol"; import {add, sub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb-math-v3/ud60x18/Helpers.sol"; import {convert} from "@prb-math-v3/ud60x18/Conversions.sol"; import {msb} from "@prb-math-v3/Common.sol"; @@ -9,7 +9,6 @@ import {mul, div, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, powu, import "./utils/AssertionHelperUD.sol"; contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { - /* ================================================================ 59x18 fixed-point constants used for testing specific values. This assumes that PRBMath library's convert(x) works as expected. @@ -49,11 +48,13 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { UD60x18 constant LOG2_E = UD60x18.wrap(uLOG2_E); /// @dev The maximum value an UD60x18 number can have. - uint256 constant uMAX_UD60x18 = 115792089237316195423570985008687907853269984665640564039457_584007913129639935; + uint256 constant uMAX_UD60x18 = + 115792089237316195423570985008687907853269984665640564039457_584007913129639935; UD60x18 constant MAX_UD60x18 = UD60x18.wrap(uMAX_UD60x18); /// @dev The maximum whole value an UD60x18 number can have. - uint256 constant uMAX_WHOLE_UD60x18 = 115792089237316195423570985008687907853269984665640564039457_000000000000000000; + uint256 constant uMAX_WHOLE_UD60x18 = + 115792089237316195423570985008687907853269984665640564039457_000000000000000000; UD60x18 constant MAX_WHOLE_UD60x18 = UD60x18.wrap(uMAX_WHOLE_UD60x18); /// @dev PI as an UD60x18 number. @@ -67,9 +68,14 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { UD60x18 constant ZERO = UD60x18.wrap(0); UD60x18 internal constant MAX_PERMITTED_EXP2 = UD60x18.wrap(192e18 - 1); - UD60x18 internal constant MAX_PERMITTED_EXP = UD60x18.wrap(133_084258667509499440); - UD60x18 internal constant MAX_PERMITTED_POW = UD60x18.wrap(2 ** 192 * 10 ** 18 - 1); - UD60x18 internal constant MAX_PERMITTED_SQRT = UD60x18.wrap(115792089237316195423570985008687907853269_984665640564039457); + UD60x18 internal constant MAX_PERMITTED_EXP = + UD60x18.wrap(133_084258667509499440); + UD60x18 internal constant MAX_PERMITTED_POW = + UD60x18.wrap(2 ** 192 * 10 ** 18 - 1); + UD60x18 internal constant MAX_PERMITTED_SQRT = + UD60x18.wrap( + 115792089237316195423570985008687907853269_984665640564039457 + ); /* ================================================================ Events used for debugging or showing information. @@ -87,21 +93,21 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { // Wrapper for external try/catch calls function helpersAdd(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { - return add(x,y); + return add(x, y); } // Wrapper for external try/catch calls function helpersSub(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { - return sub(x,y); + return sub(x, y); } // Wrapper for external try/catch calls function helpersMul(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { - return mul(x,y); + return mul(x, y); } function helpersDiv(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { - return div(x,y); + return div(x, y); } function helpersLn(UD60x18 x) public pure returns (UD60x18) { @@ -277,7 +283,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { UD60x18 x_minus_y_plus_y = x_minus_y.add(y); UD60x18 x_plus_y_minus_y = x_plus_y.sub(y); - + assertEq(x_minus_y_plus_y, x_plus_y_minus_y); assertEq(x_minus_y_plus_y, x); } @@ -378,7 +384,12 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { UD60x18 x_times_y = x.mul(y); UD60x18 x_times_z = x.mul(z); - assertEqWithinTolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP, "0.1%"); + assertEqWithinTolerance( + add(x_times_y, x_times_z), + x_times_y_plus_z, + ONE_TENTH_FP, + "0.1%" + ); } // Test for identity operation @@ -402,7 +413,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { assertLte(x_y, x); } } - + /* ================================================================ Tests for overflow and edge cases. These will make sure that the function reverts on overflow and @@ -412,7 +423,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { // The result of the multiplication must be between the maximum // and minimum allowed values for UD60x18 function mul_test_range(UD60x18 x, UD60x18 y) public { - try this.helpersMul(x, y) returns(UD60x18 result) { + try this.helpersMul(x, y) returns (UD60x18 result) { assertLte(result, MAX_UD60x18); } catch { // If it reverts, just ignore @@ -446,7 +457,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { // x / 1 == x (equivalent to x / x == 1) function div_test_division_identity_x_div_1(UD60x18 x) public { UD60x18 div_1 = div(x, ONE_FP); - + assertEq(x, div_1); } @@ -576,10 +587,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { // Test the anticommutativity of the division // x / y == 1 / (y / x) - function inv_test_division_noncommutativity( - UD60x18 x, - UD60x18 y - ) public { + function inv_test_division_noncommutativity(UD60x18 x, UD60x18 y) public { require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); UD60x18 x_y = div(x, y); @@ -819,11 +827,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { // Test for power of a product // (x * y) ** a == x ** a * y ** a - function pow_test_product_power( - UD60x18 x, - UD60x18 y, - UD60x18 a - ) public { + function pow_test_product_power(UD60x18 x, UD60x18 y, UD60x18 a) public { require(x.lte(MAX_PERMITTED_POW)); require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); require(a.gt(UD60x18.wrap(1e9))); // to avoid massive loss of precision @@ -856,7 +860,11 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { // Power is strictly increasing // x > y && a >= 0 --> pow(x) >= pow(y) - function pow_test_strictly_increasing(UD60x18 x, UD60x18 y, UD60x18 a) public { + function pow_test_strictly_increasing( + UD60x18 x, + UD60x18 y, + UD60x18 a + ) public { require(x.gt(y) && x.lte(MAX_PERMITTED_POW)); require(a.gte(ZERO_FP)); @@ -926,7 +934,11 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { UD60x18 sqrt_x_squared = mul(sqrt_x, sqrt_x); // Precision loss is at most half the bits of the operand - assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); + assertEqWithinBitPrecision( + sqrt_x_squared, + x, + (intoUint256(log2(x)) >> 1) + 2 + ); } // Test for the inverse operation @@ -937,7 +949,11 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { UD60x18 sqrt_x_squared = pow(sqrt_x, convert(2)); // Precision loss is at most half the bits of the operand - assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); + assertEqWithinBitPrecision( + sqrt_x_squared, + x, + (intoUint256(log2(x)) >> 1) + 2 + ); } // Test for distributive property respect to the multiplication @@ -1013,7 +1029,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { // log2(x * y) = log2(x) + log2(y) function log2_test_distributive_mul(UD60x18 x, UD60x18 y) public { require(x.gte(UNIT) && y.gte(UNIT)); - + UD60x18 log2_x = log2(x); UD60x18 log2_y = log2(y); UD60x18 log2_x_log2_y = add(log2_x, log2_y); @@ -1040,7 +1056,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { // y > 0 && x > y --> log2(x) > log2(y) function log2_test_strictly_increasing(UD60x18 x, UD60x18 y) public { require(y.gt(ZERO_FP) && x.gt(y)); - + UD60x18 log2_x = log2(x); UD60x18 log2_y = log2(y); @@ -1131,7 +1147,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { // y > 0 && x > y --> log2(x) > log2(y) function ln_test_strictly_increasing(UD60x18 x, UD60x18 y) public { require(y.gt(ZERO_FP) && x.gt(y)); - + UD60x18 ln_x = ln(x); UD60x18 ln_y = ln(y); @@ -1387,11 +1403,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { // Test for power of a product // (x * y) ** a == x ** a * y ** a - function powu_test_product_power( - UD60x18 x, - UD60x18 y, - uint256 a - ) public { + function powu_test_product_power(UD60x18 x, UD60x18 y, uint256 a) public { require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); require(a > 1e9); // to avoid massive loss of precision @@ -1434,7 +1446,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { UD60x18 x_a = powu(x, a); UD60x18 y_a = powu(y, a); - + assertGte(x_a, y_a); } @@ -1506,7 +1518,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { // x > y && y > 0 --> log10(x) > log10(y) function log10_test_strictly_increasing(UD60x18 x, UD60x18 y) public { require(y.gt(ZERO_FP) && x.gt(y)); - + UD60x18 log10_x = log10(x); UD60x18 log10_y = log10(y); @@ -1555,7 +1567,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { } } - /* ================================================================ + /* ================================================================ TESTS FOR FUNCTION gm() @@ -1571,7 +1583,7 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { // raised to the power of N (numbers in the set) function gm_test_product(UD60x18 x, UD60x18 y) public { UD60x18 x_mul_y = x.mul(y); - UD60x18 gm_squared = pow(gm(x,y), TWO_FP); + UD60x18 gm_squared = pow(gm(x, y), TWO_FP); assertEqWithinTolerance(x_mul_y, gm_squared, ONE_TENTH_FP, "0.1%"); } @@ -1614,4 +1626,4 @@ contract CryticPRBMath60x18Propertiesv3 is AssertionHelperUD { assert(false); } } -} \ No newline at end of file +} diff --git a/contracts/Math/PRBMath/v3/utils/AssertionHelperSD.sol b/contracts/Math/PRBMath/v3/utils/AssertionHelperSD.sol index d015343..3c542d0 100644 --- a/contracts/Math/PRBMath/v3/utils/AssertionHelperSD.sol +++ b/contracts/Math/PRBMath/v3/utils/AssertionHelperSD.sol @@ -1,10 +1,10 @@ pragma solidity ^0.8.0; -import { SD59x18 } from "@prb-math-v3/SD59x18.sol"; +import {SD59x18} from "@prb-math-v3/SD59x18.sol"; -import { convert } from "@prb-math-v3/sd59x18/Conversions.sol"; -import { add, sub, eq, gt, gte, lt, lte, rshift } from "@prb-math-v3/sd59x18/Helpers.sol"; -import { mul, div, abs } from "@prb-math-v3/sd59x18/Math.sol"; +import {convert} from "@prb-math-v3/sd59x18/Conversions.sol"; +import {add, sub, eq, gt, gte, lt, lte, rshift} from "@prb-math-v3/sd59x18/Helpers.sol"; +import {mul, div, abs} from "@prb-math-v3/sd59x18/Math.sol"; abstract contract AssertionHelperSD { event AssertEqFailure(string); @@ -30,11 +30,15 @@ abstract contract AssertionHelperSD { } } - function assertEqWithinBitPrecision(SD59x18 a, SD59x18 b, uint256 precision_bits) internal { - SD59x18 max = gt(a , b) ? a : b; - SD59x18 min = gt(a , b) ? b : a; + function assertEqWithinBitPrecision( + SD59x18 a, + SD59x18 b, + uint256 precision_bits + ) internal { + SD59x18 max = gt(a, b) ? a : b; + SD59x18 min = gt(a, b) ? b : a; SD59x18 r = rshift(sub(max, min), precision_bits); - + if (!eq(r, convert(0))) { string memory str_a = toString(SD59x18.unwrap(a)); string memory str_b = toString(SD59x18.unwrap(b)); @@ -54,7 +58,12 @@ abstract contract AssertionHelperSD { } } - function assertEqWithinTolerance(SD59x18 a, SD59x18 b, SD59x18 error_percent, string memory str_percent) internal { + function assertEqWithinTolerance( + SD59x18 a, + SD59x18 b, + SD59x18 error_percent, + string memory str_percent + ) internal { SD59x18 tol_value = mul(a, div(error_percent, convert(100))); require(tol_value.neq(convert(0))); @@ -78,22 +87,30 @@ abstract contract AssertionHelperSD { } } - // Returns true if the n most significant bits of a and b are almost equal + // Returns true if the n most significant bits of a and b are almost equal // Uses functions from the library under test! - function assertEqWithinDecimalPrecision(SD59x18 a, SD59x18 b, uint256 digits) internal { - // Divide both number by digits to truncate the unimportant digits - int256 a_int = SD59x18.unwrap(a); - int256 b_int = SD59x18.unwrap(b); - - int256 denominator = int256(10 ** digits); - - int256 a_significant = a_int / denominator; - int256 b_significant = b_int / denominator; - - int256 larger = a_significant > b_significant ? a_significant : b_significant; - int256 smaller = a_significant > b_significant ? b_significant : a_significant; - - if (!((larger - smaller) <= 1)) { + function assertEqWithinDecimalPrecision( + SD59x18 a, + SD59x18 b, + uint256 digits + ) internal { + // Divide both number by digits to truncate the unimportant digits + int256 a_int = SD59x18.unwrap(a); + int256 b_int = SD59x18.unwrap(b); + + int256 denominator = int256(10 ** digits); + + int256 a_significant = a_int / denominator; + int256 b_significant = b_int / denominator; + + int256 larger = a_significant > b_significant + ? a_significant + : b_significant; + int256 smaller = a_significant > b_significant + ? b_significant + : a_significant; + + if (!((larger - smaller) <= 1)) { string memory str_a = toString(a_int); string memory str_b = toString(b_int); string memory str_larger = toString(larger); @@ -117,7 +134,7 @@ abstract contract AssertionHelperSD { ); emit AssertEqFailure(string(assertMsg)); assert(false); - } + } } function assertGt(SD59x18 a, SD59x18 b, string memory reason) internal { @@ -313,4 +330,4 @@ abstract contract AssertionHelperSD { mstore(str, length) } } -} \ No newline at end of file +} diff --git a/contracts/Math/PRBMath/v3/utils/AssertionHelperUD.sol b/contracts/Math/PRBMath/v3/utils/AssertionHelperUD.sol index b4d1a0a..ad68cb5 100644 --- a/contracts/Math/PRBMath/v3/utils/AssertionHelperUD.sol +++ b/contracts/Math/PRBMath/v3/utils/AssertionHelperUD.sol @@ -1,6 +1,6 @@ pragma solidity ^0.8.0; -import { UD60x18 } from "@prb-math-v3/UD60x18.sol"; +import {UD60x18} from "@prb-math-v3/UD60x18.sol"; import {convert} from "@prb-math-v3/ud60x18/Conversions.sol"; import {add, sub, eq, gt, gte, lt, lte, rshift} from "@prb-math-v3/ud60x18/Helpers.sol"; @@ -30,11 +30,15 @@ abstract contract AssertionHelperUD { } } - function assertEqWithinBitPrecision(UD60x18 a, UD60x18 b, uint256 precision_bits) internal { - UD60x18 max = gt(a , b) ? a : b; - UD60x18 min = gt(a , b) ? b : a; + function assertEqWithinBitPrecision( + UD60x18 a, + UD60x18 b, + uint256 precision_bits + ) internal { + UD60x18 max = gt(a, b) ? a : b; + UD60x18 min = gt(a, b) ? b : a; UD60x18 r = rshift(sub(max, min), precision_bits); - + if (!eq(r, convert(0))) { string memory str_a = toString(UD60x18.unwrap(a)); string memory str_b = toString(UD60x18.unwrap(b)); @@ -54,7 +58,12 @@ abstract contract AssertionHelperUD { } } - function assertEqWithinTolerance(UD60x18 a, UD60x18 b, UD60x18 error_percent, string memory str_percent) internal { + function assertEqWithinTolerance( + UD60x18 a, + UD60x18 b, + UD60x18 error_percent, + string memory str_percent + ) internal { UD60x18 tol_value = mul(a, div(error_percent, convert(100))); require(tol_value.neq(convert(0))); @@ -78,20 +87,28 @@ abstract contract AssertionHelperUD { } } - // Returns true if the n most significant bits of a and b are almost equal + // Returns true if the n most significant bits of a and b are almost equal // Uses functions from the library under test! - function assertEqWithinDecimalPrecision(UD60x18 a, UD60x18 b, uint256 digits) internal { - // Divide both number by digits to truncate the unimportant digits - uint256 a_uint = UD60x18.unwrap(a); - uint256 b_uint = UD60x18.unwrap(b); - - uint256 a_significant = a_uint / 10 ** digits; - uint256 b_significant = b_uint / 10 ** digits; - - uint256 larger = a_significant > b_significant ? a_significant : b_significant; - uint256 smaller = a_significant > b_significant ? b_significant : a_significant; - - if (!((larger - smaller) <= 1)) { + function assertEqWithinDecimalPrecision( + UD60x18 a, + UD60x18 b, + uint256 digits + ) internal { + // Divide both number by digits to truncate the unimportant digits + uint256 a_uint = UD60x18.unwrap(a); + uint256 b_uint = UD60x18.unwrap(b); + + uint256 a_significant = a_uint / 10 ** digits; + uint256 b_significant = b_uint / 10 ** digits; + + uint256 larger = a_significant > b_significant + ? a_significant + : b_significant; + uint256 smaller = a_significant > b_significant + ? b_significant + : a_significant; + + if (!((larger - smaller) <= 1)) { string memory str_a = toString(a_uint); string memory str_b = toString(b_uint); string memory str_digits = toString(digits); @@ -115,7 +132,7 @@ abstract contract AssertionHelperUD { ); emit AssertEqFailure(string(assertMsg)); assert(false); - } + } } function assertGt(UD60x18 a, UD60x18 b, string memory reason) internal { @@ -135,6 +152,7 @@ abstract contract AssertionHelperUD { assert(false); } } + function assertGt(UD60x18 a, UD60x18 b) internal { if (!a.gt(b)) { string memory str_a = toString(UD60x18.unwrap(a)); @@ -310,4 +328,4 @@ abstract contract AssertionHelperUD { mstore(str, length) } } -} \ No newline at end of file +} diff --git a/contracts/Math/PRBMath/v4/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/v4/PRBMathSD59x18PropertyTests.sol index 876a875..fe3a564 100644 --- a/contracts/Math/PRBMath/v4/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v4/PRBMathSD59x18PropertyTests.sol @@ -1,6 +1,6 @@ pragma solidity ^0.8.19; -import { SD59x18 } from "@prb/math/SD59x18.sol"; +import {SD59x18} from "@prb/math/SD59x18.sol"; import {add, sub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb/math/sd59x18/Helpers.sol"; import {convert} from "@prb/math/sd59x18/Conversions.sol"; import {msb} from "@prb/math/Common.sol"; @@ -9,7 +9,6 @@ import {mul, div, abs, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, p import "./utils/AssertionHelperSD.sol"; contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { - /* ================================================================ 59x18 fixed-point constants used for testing specific values. This assumes that PRBMath library's convert(x) works as expected. @@ -41,11 +40,13 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 constant UNIT = SD59x18.wrap(1e18); /// @dev The minimum value an SD59x18 number can have. - int256 constant uMIN_SD59x18 = -57896044618658097711785492504343953926634992332820282019728_792003956564819968; + int256 constant uMIN_SD59x18 = + -57896044618658097711785492504343953926634992332820282019728_792003956564819968; SD59x18 constant MIN_SD59x18 = SD59x18.wrap(uMIN_SD59x18); /// @dev The maximum value an SD59x18 number can have. - int256 constant uMAX_SD59x18 = 57896044618658097711785492504343953926634992332820282019728_792003956564819967; + int256 constant uMAX_SD59x18 = + 57896044618658097711785492504343953926634992332820282019728_792003956564819967; SD59x18 constant MAX_SD59x18 = SD59x18.wrap(uMAX_SD59x18); /// @dev The maximum input permitted in {exp2}. @@ -63,12 +64,16 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 constant MAX_SQRT = SD59x18.wrap(uMAX_SQRT); SD59x18 internal constant MAX_PERMITTED_EXP2 = SD59x18.wrap(192e18 - 1); - SD59x18 internal constant MIN_PERMITTED_EXP2 = SD59x18.wrap(-59_794705707972522261); + SD59x18 internal constant MIN_PERMITTED_EXP2 = + SD59x18.wrap(-59_794705707972522261); + + SD59x18 internal constant MAX_PERMITTED_EXP = + SD59x18.wrap(133_084258667509499440); + SD59x18 internal constant MIN_PERMITTED_EXP = + SD59x18.wrap(-41_446531673892822322); - SD59x18 internal constant MAX_PERMITTED_EXP = SD59x18.wrap(133_084258667509499440); - SD59x18 internal constant MIN_PERMITTED_EXP = SD59x18.wrap(-41_446531673892822322); - - SD59x18 internal constant MAX_PERMITTED_POW = SD59x18.wrap(2 ** 192 * 10 ** 18 - 1); + SD59x18 internal constant MAX_PERMITTED_POW = + SD59x18.wrap(2 ** 192 * 10 ** 18 - 1); /// @dev Half the UNIT number. int256 constant uHALF_UNIT = 0.5e18; SD59x18 constant HALF_UNIT = SD59x18.wrap(uHALF_UNIT); @@ -87,34 +92,42 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { event Value(string reason, SD59x18 val); event LogErr(bytes error); - /* ================================================================ Helper functions. ================================================================ */ // Check that there are remaining significant digits after a multiplication // Uses functions from the library under test! - function significant_digits_are_lost_in_mult(SD59x18 a, SD59x18 b) public pure returns (bool) { + function significant_digits_are_lost_in_mult( + SD59x18 a, + SD59x18 b + ) public pure returns (bool) { int256 la = convert(floor(log10(abs(a)))); int256 lb = convert(floor(log10(abs(b)))); - return(la + lb < -18); + return (la + lb < -18); } // Return how many significant digits will remain after multiplying a and b // Uses functions from the library under test! - function significant_digits_after_mult(SD59x18 a, SD59x18 b) public pure returns (uint256) { + function significant_digits_after_mult( + SD59x18 a, + SD59x18 b + ) public pure returns (uint256) { int256 la = convert(floor(log10(abs(a)))); int256 lb = convert(floor(log10(abs(b)))); int256 prec = la + lb; if (prec < -18) return 0; - else return(18 + absInt(prec)); + else return (18 + absInt(prec)); } // Return how many significant digits will be lost after multiplying a and b // Uses functions from the library under test! - function significant_digits_lost_in_mult(SD59x18 a, SD59x18 b) public pure returns (uint256) { + function significant_digits_lost_in_mult( + SD59x18 a, + SD59x18 b + ) public pure returns (uint256) { int256 la = convert(floor(log10(abs(a)))); int256 lb = convert(floor(log10(abs(b)))); @@ -140,25 +153,25 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { // Wrapper for external try/catch calls function helpersAdd(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return add(x,y); + return add(x, y); } // Wrapper for external try/catch calls function helpersSub(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return sub(x,y); + return sub(x, y); } // Wrapper for external try/catch calls function helpersMul(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return mul(x,y); + return mul(x, y); } function helpersDiv(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return div(x,y); + return div(x, y); } function neg(SD59x18 x) public pure returns (SD59x18) { - return SD59x18.wrap(-SD59x18.unwrap(x)); + return SD59x18.wrap(-SD59x18.unwrap(x)); } function helpersAbs(SD59x18 x) public pure returns (SD59x18) { @@ -210,7 +223,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { } function helpersGm(SD59x18 x, SD59x18 y) public pure returns (SD59x18) { - return gm(x,y); + return gm(x, y); } /* ================================================================ @@ -350,7 +363,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { function sub_test_non_commutative(SD59x18 x, SD59x18 y) public { SD59x18 x_y = x.sub(y); SD59x18 y_x = y.sub(x); - + assertEq(x_y, neg(y_x)); } @@ -371,7 +384,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 x_minus_y_plus_y = x_minus_y.add(y); SD59x18 x_plus_y_minus_y = x_plus_y.sub(y); - + assertEq(x_minus_y_plus_y, x_plus_y_minus_y); assertEq(x_minus_y_plus_y, x); } @@ -416,7 +429,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { } } - // Subtracting minus one from the maximum value should revert, + // Subtracting minus one from the maximum value should revert, // as it is out of range function sub_test_maximum_value_minus_neg_one() public { try this.helpersSub(MAX_SD59x18, MINUS_ONE_FP) { @@ -479,15 +492,23 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { require(xy_z.neq(ZERO_FP) && x_yz.neq(ZERO_FP)); - // Checks that at least 9 digits of precision are left after multiplication - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); - require(significant_digits_after_mult(y, z) > REQUIRED_SIGNIFICANT_DIGITS); - require(significant_digits_after_mult(x_y, z) > REQUIRED_SIGNIFICANT_DIGITS); - require(significant_digits_after_mult(x, y_z) > REQUIRED_SIGNIFICANT_DIGITS); + // Checks that at least 9 digits of precision are left after multiplication + require( + significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS + ); + require( + significant_digits_after_mult(y, z) > REQUIRED_SIGNIFICANT_DIGITS + ); + require( + significant_digits_after_mult(x_y, z) > REQUIRED_SIGNIFICANT_DIGITS + ); + require( + significant_digits_after_mult(x, y_z) > REQUIRED_SIGNIFICANT_DIGITS + ); uint256 digitsLost = significant_digits_lost_in_mult(x, y); digitsLost += significant_digits_lost_in_mult(x, z); - + assertEqWithinDecimalPrecision(xy_z, x_yz, digitsLost); } @@ -501,11 +522,18 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 x_times_y = x.mul(y); SD59x18 x_times_z = x.mul(z); - require(add(x_times_y, x_times_z).neq(ZERO_FP) && x_times_y_plus_z.neq(ZERO_FP)); - assertEqWithinTolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP, "0.1%"); + require( + add(x_times_y, x_times_z).neq(ZERO_FP) && + x_times_y_plus_z.neq(ZERO_FP) + ); + assertEqWithinTolerance( + add(x_times_y, x_times_z), + x_times_y_plus_z, + ONE_TENTH_FP, + "0.1%" + ); } - // Test for identity operation // x * 1 == x (also check that x * 0 == 0) function mul_test_identity(SD59x18 x) public { @@ -517,7 +545,6 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { assertEq(x_1, x); } - // If x is positive and y is >= 1, the result should be larger than or equal to x // If x is positive and y is < 1, the result should be smaller than x function mul_test_x_positive(SD59x18 x, SD59x18 y) public { @@ -545,7 +572,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { assertGte(x_y, x); } } - + /* ================================================================ Tests for overflow and edge cases. These will make sure that the function reverts on overflow and @@ -557,7 +584,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { function mul_test_range(SD59x18 x, SD59x18 y) public { require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); - try this.helpersMul(x, y) returns(SD59x18 result) { + try this.helpersMul(x, y) returns (SD59x18 result) { assertLte(result, MAX_SD59x18); assertGte(result, MIN_SD59x18); } catch { @@ -579,7 +606,9 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { // Multiplying the minimum value times one shouldn't revert, as it is valid // Moreover, the result must be MIN_SD59x18 function mul_test_minimum_value() public { - try this.helpersMul(MIN_SD59x18.add(ONE_FP), ONE_FP) returns (SD59x18 result) { + try this.helpersMul(MIN_SD59x18.add(ONE_FP), ONE_FP) returns ( + SD59x18 result + ) { // Expected behaviour, does not revert assertEq(result, MIN_SD59x18.add(ONE_FP)); } catch { @@ -670,7 +699,6 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { behaves correctly on edge cases ================================================================ */ - // Test for division by zero function div_test_div_by_zero(SD59x18 x) public { require(x.gt(MIN_SD59x18)); @@ -794,7 +822,6 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { with math rules and expected behaviour. ================================================================ */ - // Test that the absolute value is always positive function abs_test_positive(SD59x18 x) public { require(x.gt(MIN_SD59x18)); @@ -921,7 +948,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 double_inv_x = inv(inv(x)); // The maximum loss of precision will be 2 * log10(x) digits rounded up - uint256 loss = 2 * significant_digits_lost_in_mult(x, inv(x))+ 2; + uint256 loss = 2 * significant_digits_lost_in_mult(x, inv(x)) + 2; assertEqWithinDecimalPrecision(x, double_inv_x, loss); } @@ -938,10 +965,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { // Test the anticommutativity of the division // x / y == 1 / (y / x) - function inv_test_division_noncommutativity( - SD59x18 x, - SD59x18 y - ) public { + function inv_test_division_noncommutativity(SD59x18 x, SD59x18 y) public { require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); SD59x18 x_y = div(x, y); @@ -1177,7 +1201,6 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { assertEq(one_pow_a, ONE_FP); } - // Test for product of powers of the same base // x ** a * x ** b == x ** (a + b) function pow_test_product_same_base( @@ -1216,11 +1239,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { // Test for power of a product // (x * y) ** a == x ** a * y ** a - function pow_test_product_power( - SD59x18 x, - SD59x18 y, - SD59x18 a - ) public { + function pow_test_product_power(SD59x18 x, SD59x18 y, SD59x18 a) public { require(x.gte(ZERO_FP) && x.lte(MAX_PERMITTED_POW)); require(y.gte(ZERO_FP) && y.lte(MAX_PERMITTED_POW)); @@ -1301,7 +1320,11 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { // Power is strictly increasing // x > y && a >= 0 --> pow(x, a) >= pow(y, a) - function pow_test_strictly_increasing(SD59x18 x, SD59x18 y, SD59x18 a) public { + function pow_test_strictly_increasing( + SD59x18 x, + SD59x18 y, + SD59x18 a + ) public { require(x.gt(y) && x.lte(MAX_PERMITTED_POW)); require(a.gte(ZERO_FP)); @@ -1359,7 +1382,11 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 sqrt_x_squared = mul(sqrt_x, sqrt_x); // Precision loss is at most half the bits of the operand - assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); + assertEqWithinBitPrecision( + sqrt_x_squared, + x, + (intoUint256(log2(x)) >> 1) + 2 + ); } // Test for the inverse operation @@ -1371,7 +1398,11 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 sqrt_x_squared = pow(sqrt_x, convert(2)); // Precision loss is at most half the bits of the operand - assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); + assertEqWithinBitPrecision( + sqrt_x_squared, + x, + (intoUint256(log2(x)) >> 1) + 2 + ); } // Test for distributive property respect to the multiplication @@ -1385,7 +1416,9 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 sqrt_xy = sqrt(mul(x, y)); // Ensure we have enough significant digits for the result to be meaningful - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + require( + significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS + ); require( significant_digits_after_mult(sqrt_x, sqrt_y) > REQUIRED_SIGNIFICANT_DIGITS @@ -1426,7 +1459,6 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { behaves correctly on edge cases ================================================================ */ - // Test for zero case function sqrt_test_zero() public { assertEq(sqrt(ZERO_FP), ZERO_FP); @@ -1489,7 +1521,9 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 log2_xy = log2(xy); // Ensure we have enough significant digits for the result to be meaningful - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + require( + significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS + ); // The maximum loss of precision is given by the formula: // | log10(x) + log10(y) | @@ -1514,10 +1548,10 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { // y > 0 && x > y --> log2(x) > log2(y) function log2_test_strictly_increasing(SD59x18 x, SD59x18 y) public { require(y.gt(ZERO_FP) && x.gt(y)); - + SD59x18 log2_x = log2(x); SD59x18 log2_y = log2(y); - + assertGte(log2_x, log2_y); } @@ -1587,7 +1621,9 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 xy = mul(x, y); SD59x18 ln_xy = ln(xy); - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + require( + significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS + ); // The maximum loss of precision is given by the formula: // | log2(x) + log2(y) | @@ -1605,7 +1641,10 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 y_ln_x = mul(ln(x), y); - require(significant_digits_after_mult(ln(x), y) > REQUIRED_SIGNIFICANT_DIGITS); + require( + significant_digits_after_mult(ln(x), y) > + REQUIRED_SIGNIFICANT_DIGITS + ); assertEqWithinTolerance(ln_x_y, y_ln_x, ONE_FP, "1%"); } @@ -1614,7 +1653,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { // y > 0 && x > y --> log2(x) > log2(y) function ln_test_strictly_increasing(SD59x18 x, SD59x18 y) public { require(y.gt(ZERO_FP) && x.gt(y)); - + SD59x18 log2_x = ln(x); SD59x18 log2_y = ln(y); @@ -1730,7 +1769,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { } } - // Test for maximum value. This should overflow as it won't fit + // Test for maximum value. This should overflow as it won't fit // in the data type function exp2_test_maximum_permitted() public { try this.helpersExp2(MAX_PERMITTED_EXP2) { @@ -1938,11 +1977,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { // Test for power of a product // (x * y) ** a == x ** a * y ** a - function powu_test_product_power( - SD59x18 x, - SD59x18 y, - uint256 a - ) public { + function powu_test_product_power(SD59x18 x, SD59x18 y, uint256 a) public { require(x.gt(MIN_SD59x18) && y.gt(MIN_SD59x18)); require(x.lte(MAX_SD59x18) && y.lte(MAX_SD59x18)); @@ -2011,7 +2046,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 x_a = powu(x, a); SD59x18 y_a = powu(y, a); - + assertGte(x_a, y_a); } @@ -2066,7 +2101,9 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 log10_xy = log10(xy); // Ensure we have enough significant digits for the result to be meaningful - require(significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS); + require( + significant_digits_after_mult(x, y) > REQUIRED_SIGNIFICANT_DIGITS + ); assertEqWithinTolerance(log10_x_log10_y, log10_xy, ONE_FP, "1%"); } @@ -2078,7 +2115,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { SD59x18 x_y = pow(x, y); SD59x18 log10_x_y = log10(x_y); SD59x18 y_log10_x = mul(log10(x), y); - + assertEqWithinTolerance(log10_x_y, y_log10_x, ONE_FP, "1%"); } @@ -2086,7 +2123,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { // x > y && y > 0 --> log10(x) > log10(y) function log10_is_increasing(SD59x18 x, SD59x18 y) public { require(y.gt(ZERO_FP) && x.gt(y)); - + SD59x18 log2_x = log10(x); SD59x18 log2_y = log10(y); @@ -2155,7 +2192,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { require(x_sign = y_sign); SD59x18 x_mul_y = x.mul(y); - SD59x18 gm_squared = pow(gm(x,y), TWO_FP); + SD59x18 gm_squared = pow(gm(x, y), TWO_FP); assertEqWithinTolerance(x_mul_y, gm_squared, ONE_TENTH_FP, "0.1%"); } @@ -2212,5 +2249,4 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { // Expected revert, gm of a negative product is not defined } } - -} \ No newline at end of file +} diff --git a/contracts/Math/PRBMath/v4/PRBMathUD60x18PropertyTests.sol b/contracts/Math/PRBMath/v4/PRBMathUD60x18PropertyTests.sol index 2380c71..a6f9c8d 100644 --- a/contracts/Math/PRBMath/v4/PRBMathUD60x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v4/PRBMathUD60x18PropertyTests.sol @@ -1,6 +1,6 @@ pragma solidity ^0.8.19; -import { UD60x18 } from "@prb/math/UD60x18.sol"; +import {UD60x18} from "@prb/math/UD60x18.sol"; import {add, sub, eq, gt, gte, lt, lte, lshift, rshift} from "@prb/math/ud60x18/Helpers.sol"; import {convert} from "@prb/math/ud60x18/Conversions.sol"; import {msb} from "@prb/math/Common.sol"; @@ -9,7 +9,6 @@ import {mul, div, ln, exp, exp2, log2, sqrt, pow, avg, inv, log10, floor, powu, import "./utils/AssertionHelperUD.sol"; contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { - /* ================================================================ 59x18 fixed-point constants used for testing specific values. This assumes that PRBMath library's convert(x) works as expected. @@ -49,11 +48,13 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { UD60x18 constant LOG2_E = UD60x18.wrap(uLOG2_E); /// @dev The maximum value an UD60x18 number can have. - uint256 constant uMAX_UD60x18 = 115792089237316195423570985008687907853269984665640564039457_584007913129639935; + uint256 constant uMAX_UD60x18 = + 115792089237316195423570985008687907853269984665640564039457_584007913129639935; UD60x18 constant MAX_UD60x18 = UD60x18.wrap(uMAX_UD60x18); /// @dev The maximum whole value an UD60x18 number can have. - uint256 constant uMAX_WHOLE_UD60x18 = 115792089237316195423570985008687907853269984665640564039457_000000000000000000; + uint256 constant uMAX_WHOLE_UD60x18 = + 115792089237316195423570985008687907853269984665640564039457_000000000000000000; UD60x18 constant MAX_WHOLE_UD60x18 = UD60x18.wrap(uMAX_WHOLE_UD60x18); /// @dev PI as an UD60x18 number. @@ -67,9 +68,14 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { UD60x18 constant ZERO = UD60x18.wrap(0); UD60x18 internal constant MAX_PERMITTED_EXP2 = UD60x18.wrap(192e18 - 1); - UD60x18 internal constant MAX_PERMITTED_EXP = UD60x18.wrap(133_084258667509499440); - UD60x18 internal constant MAX_PERMITTED_POW = UD60x18.wrap(2 ** 192 * 10 ** 18 - 1); - UD60x18 internal constant MAX_PERMITTED_SQRT = UD60x18.wrap(115792089237316195423570985008687907853269_984665640564039457); + UD60x18 internal constant MAX_PERMITTED_EXP = + UD60x18.wrap(133_084258667509499440); + UD60x18 internal constant MAX_PERMITTED_POW = + UD60x18.wrap(2 ** 192 * 10 ** 18 - 1); + UD60x18 internal constant MAX_PERMITTED_SQRT = + UD60x18.wrap( + 115792089237316195423570985008687907853269_984665640564039457 + ); /* ================================================================ Events used for debugging or showing information. @@ -87,21 +93,21 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { // Wrapper for external try/catch calls function helpersAdd(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { - return add(x,y); + return add(x, y); } // Wrapper for external try/catch calls function helpersSub(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { - return sub(x,y); + return sub(x, y); } // Wrapper for external try/catch calls function helpersMul(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { - return mul(x,y); + return mul(x, y); } function helpersDiv(UD60x18 x, UD60x18 y) public pure returns (UD60x18) { - return div(x,y); + return div(x, y); } function helpersLn(UD60x18 x) public pure returns (UD60x18) { @@ -277,7 +283,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { UD60x18 x_minus_y_plus_y = x_minus_y.add(y); UD60x18 x_plus_y_minus_y = x_plus_y.sub(y); - + assertEq(x_minus_y_plus_y, x_plus_y_minus_y); assertEq(x_minus_y_plus_y, x); } @@ -378,7 +384,12 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { UD60x18 x_times_y = x.mul(y); UD60x18 x_times_z = x.mul(z); - assertEqWithinTolerance(add(x_times_y, x_times_z), x_times_y_plus_z, ONE_TENTH_FP, "0.1%"); + assertEqWithinTolerance( + add(x_times_y, x_times_z), + x_times_y_plus_z, + ONE_TENTH_FP, + "0.1%" + ); } // Test for identity operation @@ -402,7 +413,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { assertLte(x_y, x); } } - + /* ================================================================ Tests for overflow and edge cases. These will make sure that the function reverts on overflow and @@ -412,7 +423,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { // The result of the multiplication must be between the maximum // and minimum allowed values for UD60x18 function mul_test_range(UD60x18 x, UD60x18 y) public { - try this.helpersMul(x, y) returns(UD60x18 result) { + try this.helpersMul(x, y) returns (UD60x18 result) { assertLte(result, MAX_UD60x18); } catch { // If it reverts, just ignore @@ -446,7 +457,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { // x / 1 == x (equivalent to x / x == 1) function div_test_division_identity_x_div_1(UD60x18 x) public { UD60x18 div_1 = div(x, ONE_FP); - + assertEq(x, div_1); } @@ -576,10 +587,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { // Test the anticommutativity of the division // x / y == 1 / (y / x) - function inv_test_division_noncommutativity( - UD60x18 x, - UD60x18 y - ) public { + function inv_test_division_noncommutativity(UD60x18 x, UD60x18 y) public { require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); UD60x18 x_y = div(x, y); @@ -819,11 +827,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { // Test for power of a product // (x * y) ** a == x ** a * y ** a - function pow_test_product_power( - UD60x18 x, - UD60x18 y, - UD60x18 a - ) public { + function pow_test_product_power(UD60x18 x, UD60x18 y, UD60x18 a) public { require(x.lte(MAX_PERMITTED_POW)); require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); require(a.gt(UD60x18.wrap(1e9))); // to avoid massive loss of precision @@ -856,7 +860,11 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { // Power is strictly increasing // x > y && a >= 0 --> pow(x) >= pow(y) - function pow_test_strictly_increasing(UD60x18 x, UD60x18 y, UD60x18 a) public { + function pow_test_strictly_increasing( + UD60x18 x, + UD60x18 y, + UD60x18 a + ) public { require(x.gt(y) && x.lte(MAX_PERMITTED_POW)); require(a.gte(ZERO_FP)); @@ -926,7 +934,11 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { UD60x18 sqrt_x_squared = mul(sqrt_x, sqrt_x); // Precision loss is at most half the bits of the operand - assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); + assertEqWithinBitPrecision( + sqrt_x_squared, + x, + (intoUint256(log2(x)) >> 1) + 2 + ); } // Test for the inverse operation @@ -937,7 +949,11 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { UD60x18 sqrt_x_squared = pow(sqrt_x, convert(2)); // Precision loss is at most half the bits of the operand - assertEqWithinBitPrecision(sqrt_x_squared, x, (intoUint256(log2(x)) >> 1) + 2); + assertEqWithinBitPrecision( + sqrt_x_squared, + x, + (intoUint256(log2(x)) >> 1) + 2 + ); } // Test for distributive property respect to the multiplication @@ -1013,7 +1029,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { // log2(x * y) = log2(x) + log2(y) function log2_test_distributive_mul(UD60x18 x, UD60x18 y) public { require(x.gte(UNIT) && y.gte(UNIT)); - + UD60x18 log2_x = log2(x); UD60x18 log2_y = log2(y); UD60x18 log2_x_log2_y = add(log2_x, log2_y); @@ -1040,7 +1056,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { // y > 0 && x > y --> log2(x) > log2(y) function log2_test_strictly_increasing(UD60x18 x, UD60x18 y) public { require(y.gt(ZERO_FP) && x.gt(y)); - + UD60x18 log2_x = log2(x); UD60x18 log2_y = log2(y); @@ -1131,7 +1147,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { // y > 0 && x > y --> log2(x) > log2(y) function ln_test_strictly_increasing(UD60x18 x, UD60x18 y) public { require(y.gt(ZERO_FP) && x.gt(y)); - + UD60x18 ln_x = ln(x); UD60x18 ln_y = ln(y); @@ -1387,11 +1403,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { // Test for power of a product // (x * y) ** a == x ** a * y ** a - function powu_test_product_power( - UD60x18 x, - UD60x18 y, - uint256 a - ) public { + function powu_test_product_power(UD60x18 x, UD60x18 y, uint256 a) public { require(x.neq(ZERO_FP) && y.neq(ZERO_FP)); require(a > 1e9); // to avoid massive loss of precision @@ -1434,7 +1446,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { UD60x18 x_a = powu(x, a); UD60x18 y_a = powu(y, a); - + assertGte(x_a, y_a); } @@ -1506,10 +1518,10 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { // x > y && y > 0 --> log10(x) > log10(y) function log10_test_strictly_increasing(UD60x18 x, UD60x18 y) public { require(y.gt(ZERO_FP) && x.gt(y)); - + UD60x18 log10_x = log10(x); UD60x18 log10_y = log10(y); - + assertGt(log10_x, log10_y); } @@ -1555,7 +1567,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { } } - /* ================================================================ + /* ================================================================ TESTS FOR FUNCTION gm() @@ -1571,7 +1583,7 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { // raised to the power of N (numbers in the set) function gm_test_product(UD60x18 x, UD60x18 y) public { UD60x18 x_mul_y = x.mul(y); - UD60x18 gm_squared = pow(gm(x,y), TWO_FP); + UD60x18 gm_squared = pow(gm(x, y), TWO_FP); assertEqWithinTolerance(x_mul_y, gm_squared, ONE_TENTH_FP, "0.1%"); } @@ -1614,4 +1626,4 @@ contract CryticPRBMath60x18Propertiesv4 is AssertionHelperUD { assert(false); } } -} \ No newline at end of file +} diff --git a/contracts/Math/PRBMath/v4/utils/AssertionHelperSD.sol b/contracts/Math/PRBMath/v4/utils/AssertionHelperSD.sol index 2ebe8ca..3fdf947 100644 --- a/contracts/Math/PRBMath/v4/utils/AssertionHelperSD.sol +++ b/contracts/Math/PRBMath/v4/utils/AssertionHelperSD.sol @@ -1,10 +1,10 @@ pragma solidity ^0.8.0; -import { SD59x18 } from "@prb/math/SD59x18.sol"; +import {SD59x18} from "@prb/math/SD59x18.sol"; -import { convert } from "@prb/math/sd59x18/Conversions.sol"; -import { add, sub, eq, gt, gte, lt, lte, rshift } from "@prb/math/sd59x18/Helpers.sol"; -import { mul, div, abs } from "@prb/math/sd59x18/Math.sol"; +import {convert} from "@prb/math/sd59x18/Conversions.sol"; +import {add, sub, eq, gt, gte, lt, lte, rshift} from "@prb/math/sd59x18/Helpers.sol"; +import {mul, div, abs} from "@prb/math/sd59x18/Math.sol"; abstract contract AssertionHelperSD { event AssertEqFailure(string); @@ -30,11 +30,15 @@ abstract contract AssertionHelperSD { } } - function assertEqWithinBitPrecision(SD59x18 a, SD59x18 b, uint256 precision_bits) internal { - SD59x18 max = gt(a , b) ? a : b; - SD59x18 min = gt(a , b) ? b : a; + function assertEqWithinBitPrecision( + SD59x18 a, + SD59x18 b, + uint256 precision_bits + ) internal { + SD59x18 max = gt(a, b) ? a : b; + SD59x18 min = gt(a, b) ? b : a; SD59x18 r = rshift(sub(max, min), precision_bits); - + if (!eq(r, convert(0))) { string memory str_a = toString(SD59x18.unwrap(a)); string memory str_b = toString(SD59x18.unwrap(b)); @@ -54,7 +58,12 @@ abstract contract AssertionHelperSD { } } - function assertEqWithinTolerance(SD59x18 a, SD59x18 b, SD59x18 error_percent, string memory str_percent) internal { + function assertEqWithinTolerance( + SD59x18 a, + SD59x18 b, + SD59x18 error_percent, + string memory str_percent + ) internal { SD59x18 tol_value = mul(a, div(error_percent, convert(100))); require(tol_value.neq(convert(0))); @@ -78,22 +87,30 @@ abstract contract AssertionHelperSD { } } - // Returns true if the n most significant bits of a and b are almost equal + // Returns true if the n most significant bits of a and b are almost equal // Uses functions from the library under test! - function assertEqWithinDecimalPrecision(SD59x18 a, SD59x18 b, uint256 digits) internal { - // Divide both number by digits to truncate the unimportant digits - int256 a_int = SD59x18.unwrap(a); - int256 b_int = SD59x18.unwrap(b); - - int256 denominator = int256(10 ** digits); - - int256 a_significant = a_int / denominator; - int256 b_significant = b_int / denominator; - - int256 larger = a_significant > b_significant ? a_significant : b_significant; - int256 smaller = a_significant > b_significant ? b_significant : a_significant; - - if (!((larger - smaller) <= 1)) { + function assertEqWithinDecimalPrecision( + SD59x18 a, + SD59x18 b, + uint256 digits + ) internal { + // Divide both number by digits to truncate the unimportant digits + int256 a_int = SD59x18.unwrap(a); + int256 b_int = SD59x18.unwrap(b); + + int256 denominator = int256(10 ** digits); + + int256 a_significant = a_int / denominator; + int256 b_significant = b_int / denominator; + + int256 larger = a_significant > b_significant + ? a_significant + : b_significant; + int256 smaller = a_significant > b_significant + ? b_significant + : a_significant; + + if (!((larger - smaller) <= 1)) { string memory str_a = toString(a_int); string memory str_b = toString(b_int); string memory str_larger = toString(larger); @@ -117,7 +134,7 @@ abstract contract AssertionHelperSD { ); emit AssertEqFailure(string(assertMsg)); assert(false); - } + } } function assertGt(SD59x18 a, SD59x18 b, string memory reason) internal { @@ -313,4 +330,4 @@ abstract contract AssertionHelperSD { mstore(str, length) } } -} \ No newline at end of file +} diff --git a/contracts/Math/PRBMath/v4/utils/AssertionHelperUD.sol b/contracts/Math/PRBMath/v4/utils/AssertionHelperUD.sol index 5f6f56a..799d391 100644 --- a/contracts/Math/PRBMath/v4/utils/AssertionHelperUD.sol +++ b/contracts/Math/PRBMath/v4/utils/AssertionHelperUD.sol @@ -1,6 +1,6 @@ pragma solidity ^0.8.0; -import { UD60x18 } from "@prb/math/UD60x18.sol"; +import {UD60x18} from "@prb/math/UD60x18.sol"; import {convert} from "@prb/math/ud60x18/Conversions.sol"; import {add, sub, eq, gt, gte, lt, lte, rshift} from "@prb/math/ud60x18/Helpers.sol"; @@ -30,11 +30,15 @@ abstract contract AssertionHelperUD { } } - function assertEqWithinBitPrecision(UD60x18 a, UD60x18 b, uint256 precision_bits) internal { - UD60x18 max = gt(a , b) ? a : b; - UD60x18 min = gt(a , b) ? b : a; + function assertEqWithinBitPrecision( + UD60x18 a, + UD60x18 b, + uint256 precision_bits + ) internal { + UD60x18 max = gt(a, b) ? a : b; + UD60x18 min = gt(a, b) ? b : a; UD60x18 r = rshift(sub(max, min), precision_bits); - + if (!eq(r, convert(0))) { string memory str_a = toString(UD60x18.unwrap(a)); string memory str_b = toString(UD60x18.unwrap(b)); @@ -54,7 +58,12 @@ abstract contract AssertionHelperUD { } } - function assertEqWithinTolerance(UD60x18 a, UD60x18 b, UD60x18 error_percent, string memory str_percent) internal { + function assertEqWithinTolerance( + UD60x18 a, + UD60x18 b, + UD60x18 error_percent, + string memory str_percent + ) internal { UD60x18 tol_value = mul(a, div(error_percent, convert(100))); require(tol_value.neq(convert(0))); @@ -78,20 +87,28 @@ abstract contract AssertionHelperUD { } } - // Returns true if the n most significant bits of a and b are almost equal + // Returns true if the n most significant bits of a and b are almost equal // Uses functions from the library under test! - function assertEqWithinDecimalPrecision(UD60x18 a, UD60x18 b, uint256 digits) internal { - // Divide both number by digits to truncate the unimportant digits - uint256 a_uint = UD60x18.unwrap(a); - uint256 b_uint = UD60x18.unwrap(b); - - uint256 a_significant = a_uint / 10 ** digits; - uint256 b_significant = b_uint / 10 ** digits; - - uint256 larger = a_significant > b_significant ? a_significant : b_significant; - uint256 smaller = a_significant > b_significant ? b_significant : a_significant; - - if (!((larger - smaller) <= 1)) { + function assertEqWithinDecimalPrecision( + UD60x18 a, + UD60x18 b, + uint256 digits + ) internal { + // Divide both number by digits to truncate the unimportant digits + uint256 a_uint = UD60x18.unwrap(a); + uint256 b_uint = UD60x18.unwrap(b); + + uint256 a_significant = a_uint / 10 ** digits; + uint256 b_significant = b_uint / 10 ** digits; + + uint256 larger = a_significant > b_significant + ? a_significant + : b_significant; + uint256 smaller = a_significant > b_significant + ? b_significant + : a_significant; + + if (!((larger - smaller) <= 1)) { string memory str_a = toString(a_uint); string memory str_b = toString(b_uint); string memory str_digits = toString(digits); @@ -115,7 +132,7 @@ abstract contract AssertionHelperUD { ); emit AssertEqFailure(string(assertMsg)); assert(false); - } + } } function assertGt(UD60x18 a, UD60x18 b, string memory reason) internal { @@ -135,6 +152,7 @@ abstract contract AssertionHelperUD { assert(false); } } + function assertGt(UD60x18 a, UD60x18 b) internal { if (!a.gt(b)) { string memory str_a = toString(UD60x18.unwrap(a)); @@ -310,4 +328,4 @@ abstract contract AssertionHelperUD { mstore(str, length) } } -} \ No newline at end of file +} From b9e1f211430e1fb3fde94c3e09e672679825d46e Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Thu, 6 Jul 2023 18:20:20 +0200 Subject: [PATCH 31/32] update property table --- PROPERTIES.md | 454 +++++++++++++++++++++++++------------------------- 1 file changed, 227 insertions(+), 227 deletions(-) diff --git a/PROPERTIES.md b/PROPERTIES.md index 9e55d78..b243f22 100644 --- a/PROPERTIES.md +++ b/PROPERTIES.md @@ -267,235 +267,235 @@ This file lists all the currently implemented Echidna property tests for ERC20, | ID | Name | Invariant tested | | ------------| ----------------------------------------------------------------------------------------------------------------------------------------------------------| ------------------------------------------------------------------------------------------------------ | -| SD59x18-001 | [add_test_commutative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L255) | Commutative property for addition. | -| SD59x18-002 | [add_test_associative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L264) | Associative property for addition. | -| SD59x18-003 | [add_test_identity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L275) | Identity operation for addition. | -| SD59x18-004 | [add_test_values](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L284) | Addition result should increase or decrease depending on operands signs. | -| SD59x18-005 | [add_test_range](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L302) | Addition result should be in the valid 59x18-arithmetic range. | -| SD59x18-006 | [add_test_maximum_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L312) | Addition edge case: maximum value plus zero should be maximum value. | -| SD59x18-007 | [add_test_maximum_value_plus_one](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L322) | Addition edge case: maximum value plus one should revert (out of range). | -| SD59x18-008 | [add_test_minimum_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L332) | Addition edge case: minimum value plus zero should be minimum value. | -| SD59x18-009 | [add_test_minimum_value_plus_negative_one](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L342) | Addition edge case: minimum value plus minus one should revert (out of range). | -| SD59x18-010 | [sub_test_equivalence_to_addition](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L364) | Subtraction should be equal to addition with opposite sign. | -| SD59x18-011 | [sub_test_non_commutative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L374) | Anti-commutative property for subtraction. | -| SD59x18-012 | [sub_test_identity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L383) | Identity operation for subtraction. | -| SD59x18-013 | [sub_test_neutrality](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L392) | Adding and subtracting the same value should not affect original value. | -| SD59x18-014 | [sub_test_values](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L405) | Subtraction result should increase or decrease depending on operands signs. | -| SD59x18-015 | [sub_test_range](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L423) | Subtraction result should be in the valid 59x18-arithmetic range. | -| SD59x18-016 | [sub_test_maximum_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L433) | Subtraction edge case: maximum value minus zero should be maximum value. | -| SD59x18-017 | [sub_test_maximum_value_minus_neg_one](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L444) | Subtraction edge case: maximum value minus negative one should revert (out of range). | -| SD59x18-018 | [sub_test_minimum_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L454) | Subtraction edge case: minimum value minus zero should be minimum value. | -| SD59x18-019 | [sub_test_minimum_value_minus_one](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L464) | Subtraction edge case: minimum value minus one should revert (out of range). | -| SD59x18-020 | [mul_test_commutative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L486) | Commutative property for multiplication. | -| SD59x18-021 | [mul_test_associative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L496) | Associative property for multiplication. | -| SD59x18-022 | [mul_test_distributive](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L509) | Distributive property for multiplication. | -| SD59x18-023 | [mul_test_identity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L525) | Identity operation for multiplication. | -| SD59x18-024 | [mul_test_x_positive](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L537) | Multiplication result should increase or decrease depending on operands signs. | -| SD59x18-025 | [mul_test_x_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L551) | Multiplication result should increase or decrease depending on operands signs. | -| SD59x18-026 | [mul_test_range](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L571) | Multiplication result should be in the valid 59x18-arithmetic range. | -| SD59x18-027 | [mul_test_maximum_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L583) | Multiplication edge case: maximum value times one should be maximum value | -| SD59x18-028 | [mul_test_minimum_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L594) | Multiplication edge case: minimum value times one should be minimum value | -| SD59x18-029 | [div_test_division_identity_x_div_1](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L617) | Identity operation for division. x / 1 == x | -| SD59x18-030 | [div_test_division_identity_x_div_x](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L626) | Identity operation for division. x / x == 1 | -| SD59x18-031 | [div_test_negative_divisor](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L644) | Division result sign should change according to divisor sign. | -| SD59x18-032 | [div_test_division_num_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L656) | Division with zero numerator should be zero. | -| SD59x18-033 | [div_test_values](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L667) | Division result should increase or decrease (in absolute value) depending on divisor's absolute value. | -| SD59x18-034 | [div_test_div_by_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L688) | Division edge case: Divisor zero should revert. | -| SD59x18-035 | [div_test_maximum_denominator](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L699) | Division edge case: Division result by a large number should be less than one. | -| SD59x18-036 | [div_test_maximum_numerator](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L708) | Division edge case: Division result of maximum value should revert if divisor is less than one. | -| SD59x18-037 | [div_test_range](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L722) | Division result should be in the valid 64x64-arithmetic range. | -| SD59x18-038 | [neg_test_double_negation](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L749) | Double sign negation should be equal to the original operand. | -| SD59x18-039 | [neg_test_identity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L757) | Identity operation for sign negation. | -| SD59x18-040 | [neg_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L771) | Negation edge case: Negation of zero should be zero. | -| SD59x18-041 | [neg_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L779) | Negation edge case: Negation of maximum value minus epsilon should not revert. | -| SD59x18-042 | [neg_test_minimum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L789) | Negation edge case: Negation of minimum value plus epsilon should not revert. | -| SD59x18-043 | [abs_test_positive](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L811) | Absolute value should be always positive. | -| SD59x18-044 | [abs_test_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L820) | Absolute value of a number and its negation should be equal. | -| SD59x18-045 | [abs_test_multiplicativeness](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L831) | Multiplicativeness property for absolute value. | -| SD59x18-046 | [abs_test_subadditivity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L848) | Subadditivity property for absolute value. | -| SD59x18-047 | [abs_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L865) | Absolute value edge case: absolute value of zero is zero. | -| SD59x18-048 | [abs_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L879) | Absolute value edge case: absolute value of maximum value is maximum value. | -| SD59x18-049 | [abs_test_minimum_revert](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L892) | Absolute value edge case: absolute value of minimum value should revert | -| SD59x18-050 | [abs_test_minimum_allowed](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L902) | Absolute value edge case: absolute value of minimum permitted value is the negation of minimum value. | -| SD59x18-051 | [inv_test_double_inverse](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L929) | Result of double inverse should be _close enough_ to the original operand. | -| SD59x18-052 | [inv_test_division](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L941) | Inverse should be equivalent to division. | -| SD59x18-053 | [inv_test_division_noncommutativity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L952) | Anticommutative property for inverse operation. | -| SD59x18-054 | [inv_test_multiplication](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L966) | Multiplication of inverses should be equal to inverse of multiplication. | -| SD59x18-055 | [inv_test_identity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L984) | Identity property for inverse. | -| SD59x18-056 | [inv_test_values](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L997) | Inverse result should be in range (0, 1) if operand is greater than one. | -| SD59x18-057 | [inv_test_sign](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1011) | Inverse result should keep operand's sign. | -| SD59x18-058 | [inv_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1030) | Inverse edge case: Inverse of zero should revert. | -| SD59x18-059 | [inv_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1038) | Inverse edge case: Inverse of maximum value should be close to zero. | -| SD59x18-060 | [inv_test_minimum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1051) | Inverse edge case: Inverse of minimum value should be close to zero. | -| SD59x18-061 | [avg_test_values_in_range](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1077) | Average result should be between operands. | -| SD59x18-062 | [avg_test_one_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1089) | Average of the same number twice is the number itself. | -| SD59x18-063 | [avg_test_operand_order](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1097) | Average result does not depend on the order of operands. | -| SD59x18-064 | [avg_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1111) | Average edge case: Average of maximum value twice is the maximum value. | -| SD59x18-065 | [avg_test_minimum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1125) | Average edge case: Average of minimum value twice is the minimum value. | -| SD59x18-066 | [pow_test_zero_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1152) | Power of zero should be one. | -| SD59x18-067 | [pow_test_zero_base_non_zero_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1161) | Zero to the power of any number should be zero. | -| SD59x18-068 | [pow_test_zero_base_zero_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1170) | Zero to the power of zero should be one | -| SD59x18-069 | [pow_test_one_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1178) | Power of one should be equal to the operand. | -| SD59x18-070 | [pow_test_base_one](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1187) | One to the power of any number should be one. | -| SD59x18-071 | [pow_test_product_same_base](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1196) | Product of powers of the same base property | -| SD59x18-072 | [pow_test_power_of_an_exponentiation](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1217) | Power of an exponentiation property | -| SD59x18-073 | [pow_test_product_power](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1239) | Distributive property for power of a product | -| SD59x18-074 | [pow_test_positive_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1260) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | -| SD59x18-075 | [pow_test_negative_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1278) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | -| SD59x18-076 | [pow_test_sign](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1296) | Power result sign should change according to the exponent sign. | -| SD59x18-077 | [pow_test_exp2_equivalence](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1319) | Base-2 exponentiation should be equal to power. | -| SD59x18-078 | [pow_test_maximum_base](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1334) | Power edge case: Power of the maximum value should revert if exponent > 1. | -| SD59x18-079 | [pow_test_high_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1346) | Power edge case: Result should be zero if base is small and exponent is large. | -| SD59x18-080 | [sqrt_test_inverse_mul](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1368) | Square root inverse as multiplication. | -| SD59x18-081 | [sqrt_test_inverse_pow](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1386) | Square root inverse as power. | -| SD59x18-082 | [sqrt_test_distributive](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1404) | Square root distributive property respect to multiplication. | -| SD59x18-083 | [sqrt_test_is_increasing](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1424) | Square root should be strictly increasing for any x | -| SD59x18-084 | [sqrt_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1442) | Square root edge case: square root of zero should be zero. | -| SD59x18-085 | [sqrt_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1447) | Square root edge case: square root of maximum value should not revert. | -| SD59x18-086 | [sqrt_test_minimum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1458) | Square root edge case: square root of minimum value should revert (negative). | -| SD59x18-087 | [sqrt_test_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1468) | Square root edge case: square root of a negative value should revert. | -| SD59x18-088 | [log2_test_distributive_mul](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1493) | Base-2 logarithm distributive property respect to multiplication. | -| SD59x18-089 | [log2_test_power](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1514) | Base-2 logarithm of a power property. | -| SD59x18-090 | [log2_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1531) | Base-2 logarithm edge case: Logarithm of zero should revert. | -| SD59x18-091 | [log2_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1541) | Base-2 logarithm edge case: Logarithm of maximum value should not revert. | -| SD59x18-092 | [log2_test_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1555) | Base-2 logarithm edge case: Logarithm of a negative value should revert. | -| SD59x18-093 | [ln_test_distributive_mul](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1580) | Natural logarithm distributive property respect to multiplication. | -| SD59x18-094 | [ln_test_power](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1601) | Natural logarithm of a power property. | -| SD59x18-095 | [ln_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1621) | Natural logarithm edge case: Logarithm of zero should revert. | -| SD59x18-096 | [ln_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1631) | Natural logarithm edge case: Logarithm of maximum value should not revert. | -| SD59x18-097 | [ln_test_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1645) | Natural logarithm edge case: Logarithm of a negative value should revert. | -| SD59x18-098 | [exp2_test_equivalence_pow](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1670) | Base-2 exponentiation should be equal to power. | -| SD59x18-099 | [exp2_test_inverse](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1680) | Base-2 exponentiation inverse function. | -| SD59x18-100 | [exp2_test_negative_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1694) | Base-2 exponentiation with negative exponent should equal the inverse. | -| SD59x18-101 | [exp2_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1716) | Base-2 exponentiation edge case: exponent zero result should be one. | -| SD59x18-102 | [exp2_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1723) | Base-2 exponentiation edge case: exponent maximum value should revert. | -| SD59x18-103 | [exp2_test_maximum_permitted](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1734) | Base-2 exponentiation edge case: exponent maximum permitted value should not revert. | -| SD59x18-104 | [exp2_test_minimum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1745) | Base-2 exponentiation edge case: exponent minimum value result should be zero. | -| SD59x18-105 | [exp_test_inverse](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1772) | Natural exponentiation inverse function. | -| SD59x18-106 | [exp_test_negative_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1787) | Natural exponentiation with negative exponent should equal the inverse. | -| SD59x18-107 | [exp_test_strictly_increasing](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1798) | Natural exponentiation should be strictly increasing for any x | -| SD59x18-108 | [exp_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1816) | Natural exponentiation edge case: exponent zero result should be one. | -| SD59x18-109 | [exp_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1823) | Natural exponentiation edge case: exponent maximum value should revert. | -| SD59x18-110 | [exp_test_maximum_permitted](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1834) | Natural exponentiation edge case: exponent maximum value should revert. | -| SD59x18-111 | [exp_test_minimum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1845) | Natural exponentiation edge case: exponent minimum value result should be zero. | -| SD59x18-112 | [powu_test_zero_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1872) | Power of zero should be one. | -| SD59x18-113 | [powu_test_zero_base](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1882) | Zero to the power of any number should be zero. | -| SD59x18-114 | [powu_test_one_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1892) | Power of one should be equal to the operand. | -| SD59x18-115 | [powu_test_base_one](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1902) | One to the power of any number should be one. | -| SD59x18-116 | [powu_test_product_same_base](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1910) | Product of powers of the same base property | -| SD59x18-117 | [powu_test_power_of_an_exponentiation](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1927) | Power of an exponentiation property | -| SD59x18-118 | [powu_test_product_power](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1944) | Distributive property for power of a product | -| SD59x18-119 | [powu_test_values](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1965) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | -| SD59x18-120 | [powu_test_sign](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1982) | Power result sign should change according to the exponent sign. | -| SD59x18-121 | [powu_test_maximum_base](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2011) | Power edge case: Power of the maximum value should revert if exponent > 1. | -| SD59x18-122 | [powu_test_high_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2023) | Power edge case: Result should be zero if base is small and exponent is large. | -| SD59x18-123 | [log10_test_distributive_mul](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2045) | Base-10 logarithm distributive property respect to multiplication. | -| SD59x18-124 | [log10_test_power](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2066) | Base-10 logarithm of a power property. | -| SD59x18-125 | [log10_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2082) | Base-10 logarithm edge case: Logarithm of zero should revert. | -| SD59x18-126 | [log10_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2092) | Base-10 logarithm edge case: Logarithm of maximum value should not revert. | -| SD59x18-127 | [log10_test_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2106) | Base-10 logarithm edge case: Logarithm of a negative value should revert. | -| SD59x18-128 | [gm_test_product](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2131) | Product of the values should be equal to the geometric mean raised to the power of N | -| SD59x18-129 | [gm_test_positive_set_avg](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2145) | The geometric mean of a set of positive values should be less than the arithmetic mean | -| SD59x18-130 | [gm_test_positive_equal_set_avg](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2157) | The geometric mean of a set of equal positive values should be equal to the arithmetic mean | -| SD59x18-131 | [gm_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2174) | GM edge case: if a set contains zero, the result is zero | -| SD59x18-132 | [gm_test_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2187) | GM edge case: The geometric mean is not defined when the set contains an odd number of negative values | +| SD59x18-001 | [add_test_commutative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L243) | Commutative property for addition. | +| SD59x18-002 | [add_test_associative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L252) | Associative property for addition. | +| SD59x18-003 | [add_test_identity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L263) | Identity operation for addition. | +| SD59x18-004 | [add_test_values](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L272) | Addition result should increase or decrease depending on operands signs. | +| SD59x18-005 | [add_test_range](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L290) | Addition result should be in the valid 59x18-arithmetic range. | +| SD59x18-006 | [add_test_maximum_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L301) | Addition edge case: maximum value plus zero should be maximum value. | +| SD59x18-007 | [add_test_maximum_value_plus_one](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L311) | Addition edge case: maximum value plus one should revert (out of range). | +| SD59x18-008 | [add_test_minimum_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L321) | Addition edge case: minimum value plus zero should be minimum value. | +| SD59x18-009 | [add_test_minimum_value_plus_negative_one](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L331) | Addition edge case: minimum value plus minus one should revert (out of range). | +| SD59x18-010 | [sub_test_equivalence_to_addition](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L353) | Subtraction should be equal to addition with opposite sign. | +| SD59x18-011 | [sub_test_non_commutative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L363) | Anti-commutative property for subtraction. | +| SD59x18-012 | [sub_test_identity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L372) | Identity operation for subtraction. | +| SD59x18-013 | [sub_test_neutrality](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L381) | Adding and subtracting the same value should not affect original value. | +| SD59x18-014 | [sub_test_values](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L394) | Subtraction result should increase or decrease depending on operands signs. | +| SD59x18-015 | [sub_test_range](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L412) | Subtraction result should be in the valid 59x18-arithmetic range. | +| SD59x18-016 | [sub_test_maximum_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L423) | Subtraction edge case: maximum value minus zero should be maximum value. | +| SD59x18-017 | [sub_test_maximum_value_minus_neg_one](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L434) | Subtraction edge case: maximum value minus negative one should revert (out of range). | +| SD59x18-018 | [sub_test_minimum_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L444) | Subtraction edge case: minimum value minus zero should be minimum value. | +| SD59x18-019 | [sub_test_minimum_value_minus_one](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L454) | Subtraction edge case: minimum value minus one should revert (out of range). | +| SD59x18-020 | [mul_test_commutative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L476) | Commutative property for multiplication. | +| SD59x18-021 | [mul_test_associative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L486) | Associative property for multiplication. | +| SD59x18-022 | [mul_test_distributive](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L517) | Distributive property for multiplication. | +| SD59x18-023 | [mul_test_identity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L539) | Identity operation for multiplication. | +| SD59x18-024 | [mul_test_x_positive](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L550) | Multiplication result should increase or decrease depending on operands signs. | +| SD59x18-025 | [mul_test_x_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L564) | Multiplication result should increase or decrease depending on operands signs. | +| SD59x18-026 | [mul_test_range](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L584) | Multiplication result should be in the valid 59x18-arithmetic range. | +| SD59x18-027 | [mul_test_maximum_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L597) | Multiplication edge case: maximum value times one should be maximum value | +| SD59x18-028 | [mul_test_minimum_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L608) | Multiplication edge case: minimum value times one should be minimum value | +| SD59x18-029 | [div_test_division_identity_x_div_1](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L633) | Identity operation for division. x / 1 == x | +| SD59x18-030 | [div_test_division_identity_x_div_x](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L642) | Identity operation for division. x / x == 1 | +| SD59x18-031 | [div_test_negative_divisor](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L660) | Division result sign should change according to divisor sign. | +| SD59x18-032 | [div_test_division_num_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L672) | Division with zero numerator should be zero. | +| SD59x18-033 | [div_test_values](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L683) | Division result should increase or decrease (in absolute value) depending on divisor's absolute value. | +| SD59x18-034 | [div_test_div_by_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L703) | Division edge case: Divisor zero should revert. | +| SD59x18-035 | [div_test_maximum_denominator](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L714) | Division edge case: Division result by a large number should be less than one. | +| SD59x18-036 | [div_test_maximum_numerator](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L723) | Division edge case: Division result of maximum value should revert if divisor is less than one. | +| SD59x18-037 | [div_test_range](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L737) | Division result should be in the valid 64x64-arithmetic range. | +| SD59x18-038 | [neg_test_double_negation](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L765) | Double sign negation should be equal to the original operand. | +| SD59x18-039 | [neg_test_identity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L773) | Identity operation for sign negation. | +| SD59x18-040 | [neg_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L787) | Negation edge case: Negation of zero should be zero. | +| SD59x18-041 | [neg_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L795) | Negation edge case: Negation of maximum value minus epsilon should not revert. | +| SD59x18-042 | [neg_test_minimum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L805) | Negation edge case: Negation of minimum value plus epsilon should not revert. | +| SD59x18-043 | [abs_test_positive](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L826) | Absolute value should be always positive. | +| SD59x18-044 | [abs_test_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L835) | Absolute value of a number and its negation should be equal. | +| SD59x18-045 | [abs_test_multiplicativeness](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L846) | Multiplicativeness property for absolute value. | +| SD59x18-046 | [abs_test_subadditivity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L863) | Subadditivity property for absolute value. | +| SD59x18-047 | [abs_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L880) | Absolute value edge case: absolute value of zero is zero. | +| SD59x18-048 | [abs_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L894) | Absolute value edge case: absolute value of maximum value is maximum value. | +| SD59x18-049 | [abs_test_minimum_revert](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L907) | Absolute value edge case: absolute value of minimum value should revert | +| SD59x18-050 | [abs_test_minimum_allowed](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L917) | Absolute value edge case: absolute value of minimum permitted value is the negation of minimum value. | +| SD59x18-051 | [inv_test_double_inverse](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L944) | Result of double inverse should be _close enough_ to the original operand. | +| SD59x18-052 | [inv_test_division](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L957) | Inverse should be equivalent to division. | +| SD59x18-053 | [inv_test_division_noncommutativity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L968) | Anticommutative property for inverse operation. | +| SD59x18-054 | [inv_test_multiplication](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L979) | Multiplication of inverses should be equal to inverse of multiplication. | +| SD59x18-055 | [inv_test_identity](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L993) | Identity property for inverse. | +| SD59x18-056 | [inv_test_values](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1006) | Inverse result should be in range (0, 1) if operand is greater than one. | +| SD59x18-057 | [inv_test_sign](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1020) | Inverse result should keep operand's sign. | +| SD59x18-058 | [inv_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1039) | Inverse edge case: Inverse of zero should revert. | +| SD59x18-059 | [inv_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1047) | Inverse edge case: Inverse of maximum value should be close to zero. | +| SD59x18-060 | [inv_test_minimum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1060) | Inverse edge case: Inverse of minimum value should be close to zero. | +| SD59x18-061 | [avg_test_values_in_range](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1086) | Average result should be between operands. | +| SD59x18-062 | [avg_test_one_value](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1100) | Average of the same number twice is the number itself. | +| SD59x18-063 | [avg_test_operand_order](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1108) | Average result does not depend on the order of operands. | +| SD59x18-064 | [avg_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1122) | Average edge case: Average of maximum value twice is the maximum value. | +| SD59x18-065 | [avg_test_minimum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1136) | Average edge case: Average of minimum value twice is the minimum value. | +| SD59x18-066 | [pow_test_zero_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1163) | Power of zero should be one. | +| SD59x18-067 | [pow_test_zero_base_non_zero_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1172) | Zero to the power of any number should be zero. | +| SD59x18-068 | [pow_test_zero_base_zero_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1181) | Zero to the power of zero should be one | +| SD59x18-069 | [pow_test_one_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1189) | Power of one should be equal to the operand. | +| SD59x18-070 | [pow_test_base_one](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1198) | One to the power of any number should be one. | +| SD59x18-071 | [pow_test_product_same_base](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1206) | Product of powers of the same base property | +| SD59x18-072 | [pow_test_power_of_an_exponentiation](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1223) | Power of an exponentiation property | +| SD59x18-073 | [pow_test_product_power](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1242) | Distributive property for power of a product | +| SD59x18-074 | [pow_test_positive_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1259) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | +| SD59x18-075 | [pow_test_negative_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1275) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | +| SD59x18-076 | [pow_test_sign](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1291) | Power result sign should change according to the exponent sign. | +| SD59x18-077 | [pow_test_exp2_equivalence](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1314) | Base-2 exponentiation should be equal to power. | +| SD59x18-078 | [pow_test_maximum_base](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1344) | Power edge case: Power of the maximum value should revert if exponent > 1. | +| SD59x18-079 | [pow_test_high_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1356) | Power edge case: Result should be zero if base is small and exponent is large. | +| SD59x18-080 | [sqrt_test_inverse_mul](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1378) | Square root inverse as multiplication. | +| SD59x18-081 | [sqrt_test_inverse_pow](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1394) | Square root inverse as power. | +| SD59x18-082 | [sqrt_test_distributive](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1410) | Square root distributive property respect to multiplication. | +| SD59x18-083 | [sqrt_test_is_increasing](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1433) | Square root should be strictly increasing for any x | +| SD59x18-084 | [sqrt_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1463) | Square root edge case: square root of zero should be zero. | +| SD59x18-085 | [sqrt_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1468) | Square root edge case: square root of maximum value should not revert. | +| SD59x18-086 | [sqrt_test_minimum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1479) | Square root edge case: square root of minimum value should revert (negative). | +| SD59x18-087 | [sqrt_test_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1489) | Square root edge case: square root of a negative value should revert. | +| SD59x18-088 | [log2_test_distributive_mul](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1514) | Base-2 logarithm distributive property respect to multiplication. | +| SD59x18-089 | [log2_test_power](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1537) | Base-2 logarithm of a power property. | +| SD59x18-090 | [log2_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1565) | Base-2 logarithm edge case: Logarithm of zero should revert. | +| SD59x18-091 | [log2_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1575) | Base-2 logarithm edge case: Logarithm of maximum value should not revert. | +| SD59x18-092 | [log2_test_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1589) | Base-2 logarithm edge case: Logarithm of a negative value should revert. | +| SD59x18-093 | [ln_test_distributive_mul](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1614) | Natural logarithm distributive property respect to multiplication. | +| SD59x18-094 | [ln_test_power](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1637) | Natural logarithm of a power property. | +| SD59x18-095 | [ln_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1670) | Natural logarithm edge case: Logarithm of zero should revert. | +| SD59x18-096 | [ln_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1680) | Natural logarithm edge case: Logarithm of maximum value should not revert. | +| SD59x18-097 | [ln_test_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1694) | Natural logarithm edge case: Logarithm of a negative value should revert. | +| SD59x18-098 | [exp2_test_equivalence_pow](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1719) | Base-2 exponentiation should be equal to power. | +| SD59x18-099 | [exp2_test_inverse](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1729) | Base-2 exponentiation inverse function. | +| SD59x18-100 | [exp2_test_negative_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1739) | Base-2 exponentiation with negative exponent should equal the inverse. | +| SD59x18-101 | [exp2_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1756) | Base-2 exponentiation edge case: exponent zero result should be one. | +| SD59x18-102 | [exp2_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1763) | Base-2 exponentiation edge case: exponent maximum value should revert. | +| SD59x18-103 | [exp2_test_maximum_permitted](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1774) | Base-2 exponentiation edge case: exponent maximum permitted value should not revert. | +| SD59x18-104 | [exp2_test_minimum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1785) | Base-2 exponentiation edge case: exponent minimum value result should be zero. | +| SD59x18-105 | [exp_test_inverse](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1812) | Natural exponentiation inverse function. | +| SD59x18-106 | [exp_test_negative_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1823) | Natural exponentiation with negative exponent should equal the inverse. | +| SD59x18-107 | [exp_test_strictly_increasing](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1834) | Natural exponentiation should be strictly increasing for any x | +| SD59x18-108 | [exp_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1852) | Natural exponentiation edge case: exponent zero result should be one. | +| SD59x18-109 | [exp_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1859) | Natural exponentiation edge case: exponent maximum value should revert. | +| SD59x18-110 | [exp_test_maximum_permitted](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1870) | Natural exponentiation edge case: exponent maximum value should revert. | +| SD59x18-111 | [exp_test_minimum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1881) | Natural exponentiation edge case: exponent minimum value result should be zero. | +| SD59x18-112 | [powu_test_zero_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1908) | Power of zero should be one. | +| SD59x18-113 | [powu_test_zero_base](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1918) | Zero to the power of any number should be zero. | +| SD59x18-114 | [powu_test_one_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1928) | Power of one should be equal to the operand. | +| SD59x18-115 | [powu_test_base_one](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1938) | One to the power of any number should be one. | +| SD59x18-116 | [powu_test_product_same_base](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1946) | Product of powers of the same base property | +| SD59x18-117 | [powu_test_power_of_an_exponentiation](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1963) | Power of an exponentiation property | +| SD59x18-118 | [powu_test_product_power](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1980) | Distributive property for power of a product | +| SD59x18-119 | [powu_test_values](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L1997) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | +| SD59x18-120 | [powu_test_sign](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2014) | Power result sign should change according to the exponent sign. | +| SD59x18-121 | [powu_test_maximum_base](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2060) | Power edge case: Power of the maximum value should revert if exponent > 1. | +| SD59x18-122 | [powu_test_high_exponent](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2072) | Power edge case: Result should be zero if base is small and exponent is large. | +| SD59x18-123 | [log10_test_distributive_mul](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2094) | Base-10 logarithm distributive property respect to multiplication. | +| SD59x18-124 | [log10_test_power](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2113) | Base-10 logarithm of a power property. | +| SD59x18-125 | [log10_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2140) | Base-10 logarithm edge case: Logarithm of zero should revert. | +| SD59x18-126 | [log10_test_maximum](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2150) | Base-10 logarithm edge case: Logarithm of maximum value should not revert. | +| SD59x18-127 | [log10_test_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2164) | Base-10 logarithm edge case: Logarithm of a negative value should revert. | +| SD59x18-128 | [gm_test_product](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2189) | Product of the values should be equal to the geometric mean raised to the power of N | +| SD59x18-129 | [gm_test_positive_set_avg](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2202) | The geometric mean of a set of positive values should be less than the arithmetic mean | +| SD59x18-130 | [gm_test_positive_equal_set_avg](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2213) | The geometric mean of a set of equal positive values should be equal to the arithmetic mean | +| SD59x18-131 | [gm_test_zero](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2229) | GM edge case: if a set contains zero, the result is zero | +| SD59x18-132 | [gm_test_negative](./contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol#L2242) | GM edge case: The geometric mean is not defined when the set contains an odd number of negative values | ## PRBMath UD60x18 | ID | Name | Invariant tested | | ------------| ----------------------------------------------------------------------------------------------------------------------------------------------------------| ------------------------------------------------------------------------------------------------------ | -| UD60x18-001 | [add_test_commutative](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L228) | Commutative property for addition. | -| UD60x18-002 | [add_test_associative](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L237) | Associative property for addition. | -| UD60x18-003 | [add_test_identity](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L248) | Identity operation for addition. | -| UD60x18-004 | [add_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L257) | Addition result should increase or decrease depending on operands signs. | -| UD60x18-005 | [add_test_range](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L271) | Addition result should be in the valid 60x18-arithmetic range. | -| UD60x18-006 | [add_test_maximum_value](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L281) | Addition edge case: maximum value plus zero should be maximum value. | -| UD60x18-007 | [add_test_maximum_value_plus_one](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L291) | Addition edge case: maximum value plus one should revert (out of range). | -| UD60x18-008 | [add_test_minimum_value](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L301) | Addition edge case: minimum value plus zero should be minimum value. | -| UD60x18-009 | [sub_test_identity](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L324) | Identity operation for subtraction. | -| UD60x18-010 | [sub_test_neutrality](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L333) | Adding and subtracting the same value should not affect original value. | -| UD60x18-011 | [sub_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L345) | Subtraction result should increase or decrease depending on operands signs. | -| UD60x18-012 | [sub_test_range](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L359) | Subtraction result should be in the valid 60x18-arithmetic range. | -| UD60x18-013 | [sub_test_maximum_value](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L369) | Subtraction edge case: maximum value minus zero should be maximum value. | -| UD60x18-014 | [sub_test_minimum_value](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L380) | Subtraction edge case: minimum value minus zero should be minimum value. | -| UD60x18-015 | [sub_test_minimum_value_minus_one](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L390) | Subtraction edge case: minimum value minus one should revert (out of range). | -| UD60x18-016 | [mul_test_commutative](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L412) | Commutative property for multiplication. | -| UD60x18-017 | [mul_test_associative](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L421) | Associative property for multiplication. | -| UD60x18-018 | [mul_test_distributive](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L433) | Distributive property for multiplication. | -| UD60x18-019 | [mul_test_identity](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L445) | Identity operation for multiplication. | -| UD60x18-020 | [mul_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L455) | Multiplication result should increase or decrease depending on operands signs. | -| UD60x18-021 | [mul_test_range](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L473) | Multiplication result should be in the valid 59x18-arithmetic range. | -| UD60x18-022 | [mul_test_maximum_value](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L483) | Multiplication edge case: maximum value times one should be maximum value | -| UD60x18-023 | [div_test_division_identity_x_div_1](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L506) | Identity operation for division. x / 1 == x | -| UD60x18-024 | [div_test_division_identity_x_div_x](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L514) | Identity operation for division. x / x == 1 | -| UD60x18-025 | [div_test_division_num_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L529) | Division with zero numerator should be zero. | -| UD60x18-026 | [div_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L539) | Division result should increase or decrease (in absolute value) depending on divisor's absolute value. | -| UD60x18-027 | [div_test_div_by_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L558) | Division edge case: Divisor zero should revert. | -| UD60x18-028 | [div_test_maximum_denominator](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L568) | Division edge case: Division result by a large number should be less than one. | -| UD60x18-029 | [div_test_maximum_numerator](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L576) | Division edge case: Division result of maximum value should revert if divisor is less than one. | -| UD60x18-030 | [div_test_range](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L591) | Division result should be in the valid 60x18-arithmetic range. | -| UD60x18-031 | [inv_test_double_inverse](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L618) | Result of double inverse should be _close enough_ to the original operand. | -| UD60x18-032 | [inv_test_division](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L630) | Inverse should be equivalent to division. | -| UD60x18-033 | [inv_test_division_noncommutativity](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L641) | Anticommutative property for inverse operation. | -| UD60x18-034 | [inv_test_multiplication](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L655) | Multiplication of inverses should be equal to inverse of multiplication. | -| UD60x18-035 | [inv_test_identity](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L673) | Identity property for inverse. | -| UD60x18-036 | [inv_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L688) | Inverse result should be in range (0, 1) if operand is greater than one. | -| UD60x18-037 | [inv_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L707) | Inverse edge case: Inverse of zero should revert. | -| UD60x18-038 | [inv_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L715) | Inverse edge case: Inverse of maximum value should be close to zero. | -| UD60x18-039 | [inv_test_minimum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L728) | Inverse edge case: Inverse of minimum value should be close to zero. | -| UD60x18-040 | [avg_test_values_in_range](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L754) | Average result should be between operands. | -| UD60x18-041 | [avg_test_one_value](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L766) | Average of the same number twice is the number itself. | -| UD60x18-042 | [avg_test_operand_order](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L774) | Average result does not depend on the order of operands. | -| UD60x18-043 | [avg_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L788) | Average edge case: Average of maximum value twice is the maximum value. | -| UD60x18-044 | [pow_test_zero_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L815) | Power of zero should be one. | -| UD60x18-045 | [pow_test_zero_base](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L824) | Zero to the power of any number should be zero. | -| UD60x18-046 | [pow_test_one_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L835) | Power of one should be equal to the operand. | -| UD60x18-047 | [pow_test_base_one](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L844) | One to the power of any number should be one. | -| UD60x18-048 | [pow_test_product_same_base](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L852) | Product of powers of the same base property | -| UD60x18-049 | [pow_test_power_of_an_exponentiation](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L873) | Power of an exponentiation property | -| UD60x18-050 | [pow_test_product_power](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L894) | Distributive property for power of a product | -| UD60x18-051 | [pow_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L918) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | -| UD60x18-052 | [pow_test_maximum_base](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L940) | Power edge case: Power of the maximum value should revert if exponent > 1. | -| UD60x18-053 | [pow_test_maximum_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L952) | Power edge case: Power of the maximum value should revert if exponent > 1. | -| UD60x18-054 | [pow_test_high_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L965) | Power edge case: Result should be zero if base is small and exponent is large. | -| UD60x18-055 | [sqrt_test_inverse_mul](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L987) | Square root inverse as multiplication. | -| UD60x18-056 | [sqrt_test_inverse_pow](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1004) | Square root inverse as power. | -| UD60x18-057 | [sqrt_test_distributive](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1021) | Square root distributive property respect to multiplication. | -| UD60x18-058 | [sqrt_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1039) | Square root edge case: square root of zero should be zero. | -| UD60x18-059 | [sqrt_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1044) | Square root edge case: square root of maximum value should not revert. | -| UD60x18-060 | [log2_test_distributive_mul](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1068) | Base-2 logarithm distributive property respect to multiplication. | -| UD60x18-061 | [log2_test_power](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1087) | Base-2 logarithm of a power property. | -| UD60x18-062 | [log2_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1109) | Base-2 logarithm edge case: Logarithm of zero should revert. | -| UD60x18-063 | [log2_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1119) | Base-2 logarithm edge case: Logarithm of maximum value should not revert. | -| UD60x18-064 | [log2_test_less_than_unit](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1133) | Base-2 logarithm edge case: Logarithm of a negative value should revert. | -| UD60x18-065 | [ln_test_distributive_mul](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1158) | Natural logarithm distributive property respect to multiplication. | -| UD60x18-066 | [ln_test_power](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1179) | Natural logarithm of a power property. | -| UD60x18-067 | [ln_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1199) | Natural logarithm edge case: Logarithm of zero should revert. | -| UD60x18-068 | [ln_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1209) | Natural logarithm edge case: Logarithm of maximum value should not revert. | -| UD60x18-069 | [ln_test_less_than_unit](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1223) | Natural logarithm edge case: Logarithm of a negative value should revert. | -| UD60x18-070 | [exp2_test_equivalence_pow](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1248) | Base-2 exponentiation should be equal to power. | -| UD60x18-071 | [exp2_test_inverse](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1258) | Base-2 exponentiation inverse function. | -| UD60x18-072 | [exp2_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1279) | Base-2 exponentiation edge case: exponent zero result should be one. | -| UD60x18-073 | [exp2_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1286) | Base-2 exponentiation edge case: exponent maximum value should revert. | -| UD60x18-075 | [exp_test_inverse](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1309) | Natural exponentiation inverse function. | -| UD60x18-076 | [exp_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1331) | Natural exponentiation edge case: exponent zero result should be one. | -| UD60x18-077 | [exp_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1338) | Natural exponentiation edge case: exponent maximum value should revert. | -| UD60x18-078 | [powu_test_zero_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1361) | Power of zero should be one. | -| UD60x18-079 | [powu_test_zero_base](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1369) | Zero to the power of any number should be zero. | -| UD60x18-080 | [powu_test_one_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1379) | Power of one should be equal to the operand. | -| UD60x18-081 | [powu_test_base_one](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1387) | One to the power of any number should be one. | -| UD60x18-082 | [powu_test_product_same_base](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1395) | Product of powers of the same base property | -| UD60x18-083 | [powu_test_power_of_an_exponentiation](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1411) | Power of an exponentiation property | -| UD60x18-084 | [powu_test_product_power](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1427) | Distributive property for power of a product | -| UD60x18-085 | [powu_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1447) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | -| UD60x18-086 | [powu_test_maximum_base](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1468) | Power edge case: Power of the maximum value should revert if exponent > 1. | -| UD60x18-087 | [powu_test_high_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1480) | Power edge case: Result should be zero if base is small and exponent is large. | -| UD60x18-088 | [log10_test_distributive_mul](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1502) | Base-10 logarithm distributive property respect to multiplication. | -| UD60x18-089 | [log10_test_power](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1523) | Base-10 logarithm of a power property. | -| UD60x18-090 | [log10_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1543) | Base-10 logarithm edge case: Logarithm of zero should revert. | -| UD60x18-091 | [log10_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1553) | Base-10 logarithm edge case: Logarithm of maximum value should not revert. | -| UD60x18-092 | [log10_test_less_than_unit](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1567) | Base-10 logarithm edge case: Logarithm of a negative value should revert. | -| UD60x18-093 | [gm_test_product](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1592) | Product of the values should be equal to the geometric mean raised to the power of N | -| UD60x18-094 | [gm_test_positive_set_avg](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1602) | The geometric mean of a set of positive values should be less than the arithmetic mean | -| UD60x18-095 | [gm_test_positive_equal_set_avg](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1614) | The geometric mean of a set of equal positive values should be equal to the arithmetic mean | -| UD60x18-096 | [gm_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1629) | GM edge case: if a set contains zero, the result is zero | \ No newline at end of file +| UD60x18-001 | [add_test_commutative](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L175) | Commutative property for addition. | +| UD60x18-002 | [add_test_associative](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L184) | Associative property for addition. | +| UD60x18-003 | [add_test_identity](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L195) | Identity operation for addition. | +| UD60x18-004 | [add_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L204) | Addition result should increase or decrease depending on operands signs. | +| UD60x18-005 | [add_test_range](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L218) | Addition result should be in the valid 60x18-arithmetic range. | +| UD60x18-006 | [add_test_maximum_value](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L228) | Addition edge case: maximum value plus zero should be maximum value. | +| UD60x18-007 | [add_test_maximum_value_plus_one](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L238) | Addition edge case: maximum value plus one should revert (out of range). | +| UD60x18-008 | [add_test_minimum_value](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L248) | Addition edge case: minimum value plus zero should be minimum value. | +| UD60x18-009 | [sub_test_identity](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L271) | Identity operation for subtraction. | +| UD60x18-010 | [sub_test_neutrality](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L280) | Adding and subtracting the same value should not affect original value. | +| UD60x18-011 | [sub_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L292) | Subtraction result should increase or decrease depending on operands signs. | +| UD60x18-012 | [sub_test_range](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L306) | Subtraction result should be in the valid 60x18-arithmetic range. | +| UD60x18-013 | [sub_test_maximum_value](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L316) | Subtraction edge case: maximum value minus zero should be maximum value. | +| UD60x18-014 | [sub_test_minimum_value](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L327) | Subtraction edge case: minimum value minus zero should be minimum value. | +| UD60x18-015 | [sub_test_minimum_value_minus_one](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L337) | Subtraction edge case: minimum value minus one should revert (out of range). | +| UD60x18-016 | [mul_test_commutative](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L359) | Commutative property for multiplication. | +| UD60x18-017 | [mul_test_associative](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L368) | Associative property for multiplication. | +| UD60x18-018 | [mul_test_distributive](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L380) | Distributive property for multiplication. | +| UD60x18-019 | [mul_test_identity](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L397) | Identity operation for multiplication. | +| UD60x18-020 | [mul_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L407) | Multiplication result should increase or decrease depending on operands signs. | +| UD60x18-021 | [mul_test_range](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L425) | Multiplication result should be in the valid 59x18-arithmetic range. | +| UD60x18-022 | [mul_test_maximum_value](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L435) | Multiplication edge case: maximum value times one should be maximum value | +| UD60x18-023 | [div_test_division_identity_x_div_1](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L458) | Identity operation for division. x / 1 == x | +| UD60x18-024 | [div_test_division_identity_x_div_x](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L466) | Identity operation for division. x / x == 1 | +| UD60x18-025 | [div_test_division_num_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L481) | Division with zero numerator should be zero. | +| UD60x18-026 | [div_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L491) | Division result should increase or decrease (in absolute value) depending on divisor's absolute value. | +| UD60x18-027 | [div_test_div_by_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L510) | Division edge case: Divisor zero should revert. | +| UD60x18-028 | [div_test_maximum_denominator](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L520) | Division edge case: Division result by a large number should be less than one. | +| UD60x18-029 | [div_test_maximum_numerator](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L528) | Division edge case: Division result of maximum value should revert if divisor is less than one. | +| UD60x18-030 | [div_test_range](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L543) | Division result should be in the valid 60x18-arithmetic range. | +| UD60x18-031 | [inv_test_double_inverse](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L570) | Result of double inverse should be _close enough_ to the original operand. | +| UD60x18-032 | [inv_test_division](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L579) | Inverse should be equivalent to division. | +| UD60x18-033 | [inv_test_division_noncommutativity](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L590) | Anticommutative property for inverse operation. | +| UD60x18-034 | [inv_test_multiplication](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L601) | Multiplication of inverses should be equal to inverse of multiplication. | +| UD60x18-035 | [inv_test_identity](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L615) | Identity property for inverse. | +| UD60x18-036 | [inv_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L630) | Inverse result should be in range (0, 1) if operand is greater than one. | +| UD60x18-037 | [inv_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L649) | Inverse edge case: Inverse of zero should revert. | +| UD60x18-038 | [inv_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L657) | Inverse edge case: Inverse of maximum value should be close to zero. | +| UD60x18-039 | [inv_test_minimum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L670) | Inverse edge case: Inverse of minimum value should be close to zero. | +| UD60x18-040 | [avg_test_values_in_range](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L696) | Average result should be between operands. | +| UD60x18-041 | [avg_test_one_value](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L710) | Average of the same number twice is the number itself. | +| UD60x18-042 | [avg_test_operand_order](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L718) | Average result does not depend on the order of operands. | +| UD60x18-043 | [avg_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L732) | Average edge case: Average of maximum value twice is the maximum value. | +| UD60x18-044 | [pow_test_zero_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L759) | Power of zero should be one. | +| UD60x18-045 | [pow_test_zero_base](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L768) | Zero to the power of any number should be zero. | +| UD60x18-046 | [pow_test_one_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L779) | Power of one should be equal to the operand. | +| UD60x18-047 | [pow_test_base_one](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L788) | One to the power of any number should be one. | +| UD60x18-048 | [pow_test_product_same_base](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L796) | Product of powers of the same base property | +| UD60x18-049 | [pow_test_power_of_an_exponentiation](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L813) | Power of an exponentiation property | +| UD60x18-050 | [pow_test_product_power](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L830) | Distributive property for power of a product | +| UD60x18-051 | [pow_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L846) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | +| UD60x18-052 | [pow_test_maximum_base](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L884) | Power edge case: Power of the maximum value should revert if exponent > 1. | +| UD60x18-053 | [pow_test_maximum_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L896) | Power edge case: Power of the maximum value should revert if exponent > 1. | +| UD60x18-054 | [pow_test_high_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L909) | Power edge case: Result should be zero if base is small and exponent is large. | +| UD60x18-055 | [sqrt_test_inverse_mul](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L931) | Square root inverse as multiplication. | +| UD60x18-056 | [sqrt_test_inverse_pow](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L946) | Square root inverse as power. | +| UD60x18-057 | [sqrt_test_distributive](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L961) | Square root distributive property respect to multiplication. | +| UD60x18-058 | [sqrt_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1001) | Square root edge case: square root of zero should be zero. | +| UD60x18-059 | [sqrt_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1006) | Square root edge case: square root of maximum value should not revert. | +| UD60x18-060 | [log2_test_distributive_mul](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1030) | Base-2 logarithm distributive property respect to multiplication. | +| UD60x18-061 | [log2_test_power](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1045) | Base-2 logarithm of a power property. | +| UD60x18-062 | [log2_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1073) | Base-2 logarithm edge case: Logarithm of zero should revert. | +| UD60x18-063 | [log2_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1083) | Base-2 logarithm edge case: Logarithm of maximum value should not revert. | +| UD60x18-064 | [log2_test_less_than_unit](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1097) | Base-2 logarithm edge case: Logarithm of a negative value should revert. | +| UD60x18-065 | [ln_test_distributive_mul](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1122) | Natural logarithm distributive property respect to multiplication. | +| UD60x18-066 | [ln_test_power](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1137) | Natural logarithm of a power property. | +| UD60x18-067 | [ln_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1164) | Natural logarithm edge case: Logarithm of zero should revert. | +| UD60x18-068 | [ln_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1174) | Natural logarithm edge case: Logarithm of maximum value should not revert. | +| UD60x18-069 | [ln_test_less_than_unit](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1188) | Natural logarithm edge case: Logarithm of a negative value should revert. | +| UD60x18-070 | [exp2_test_equivalence_pow](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1213) | Base-2 exponentiation should be equal to power. | +| UD60x18-071 | [exp2_test_inverse](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1223) | Base-2 exponentiation inverse function. | +| UD60x18-072 | [exp2_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1251) | Base-2 exponentiation edge case: exponent zero result should be one. | +| UD60x18-073 | [exp2_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1258) | Base-2 exponentiation edge case: exponent maximum value should revert. | +| UD60x18-075 | [exp_test_inverse](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1281) | Natural exponentiation inverse function. | +| UD60x18-076 | [exp_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1310) | Natural exponentiation edge case: exponent zero result should be one. | +| UD60x18-077 | [exp_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1317) | Natural exponentiation edge case: exponent maximum value should revert. | +| UD60x18-078 | [powu_test_zero_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1340) | Power of zero should be one. | +| UD60x18-079 | [powu_test_zero_base](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1348) | Zero to the power of any number should be zero. | +| UD60x18-080 | [powu_test_one_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1358) | Power of one should be equal to the operand. | +| UD60x18-081 | [powu_test_base_one](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1366) | One to the power of any number should be one. | +| UD60x18-082 | [powu_test_product_same_base](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1374) | Product of powers of the same base property | +| UD60x18-083 | [powu_test_power_of_an_exponentiation](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1390) | Power of an exponentiation property | +| UD60x18-084 | [powu_test_product_power](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1406) | Distributive property for power of a product | +| UD60x18-085 | [powu_test_values](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1422) | Power result should increase or decrease (in absolute value) depending on exponent's absolute value. | +| UD60x18-086 | [powu_test_maximum_base](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1460) | Power edge case: Power of the maximum value should revert if exponent > 1. | +| UD60x18-087 | [powu_test_high_exponent](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1472) | Power edge case: Result should be zero if base is small and exponent is large. | +| UD60x18-088 | [log10_test_distributive_mul](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1494) | Base-10 logarithm distributive property respect to multiplication. | +| UD60x18-089 | [log10_test_power](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1508) | Base-10 logarithm of a power property. | +| UD60x18-090 | [log10_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1535) | Base-10 logarithm edge case: Logarithm of zero should revert. | +| UD60x18-091 | [log10_test_maximum](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1545) | Base-10 logarithm edge case: Logarithm of maximum value should not revert. | +| UD60x18-092 | [log10_test_less_than_unit](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1559) | Base-10 logarithm edge case: Logarithm of a negative value should revert. | +| UD60x18-093 | [gm_test_product](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1584) | Product of the values should be equal to the geometric mean raised to the power of N | +| UD60x18-094 | [gm_test_positive_set_avg](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1593) | The geometric mean of a set of positive values should be less than the arithmetic mean | +| UD60x18-095 | [gm_test_positive_equal_set_avg](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1604) | The geometric mean of a set of equal positive values should be equal to the arithmetic mean | +| UD60x18-096 | [gm_test_zero](./contracts/Math/PRBMath/v3/PRBMathUD60x18PropertyTests.sol#L1618) | GM edge case: if a set contains zero, the result is zero | \ No newline at end of file From 2c3e1bbf5deba37cd1da625cc4bb3f76de71efcc Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Fri, 7 Jul 2023 10:10:40 +0200 Subject: [PATCH 32/32] add more decimals lost to mul associative --- contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol | 1 + contracts/Math/PRBMath/v4/PRBMathSD59x18PropertyTests.sol | 1 + 2 files changed, 2 insertions(+) diff --git a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol index ca704a0..298eb21 100644 --- a/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v3/PRBMathSD59x18PropertyTests.sol @@ -508,6 +508,7 @@ contract CryticPRBMath59x18Propertiesv3 is AssertionHelperSD { uint256 digitsLost = significant_digits_lost_in_mult(x, y); digitsLost += significant_digits_lost_in_mult(x, z); + digitsLost += significant_digits_lost_in_mult(y, z); assertEqWithinDecimalPrecision(xy_z, x_yz, digitsLost); } diff --git a/contracts/Math/PRBMath/v4/PRBMathSD59x18PropertyTests.sol b/contracts/Math/PRBMath/v4/PRBMathSD59x18PropertyTests.sol index fe3a564..2cf59e7 100644 --- a/contracts/Math/PRBMath/v4/PRBMathSD59x18PropertyTests.sol +++ b/contracts/Math/PRBMath/v4/PRBMathSD59x18PropertyTests.sol @@ -508,6 +508,7 @@ contract CryticPRBMath59x18Propertiesv4 is AssertionHelperSD { uint256 digitsLost = significant_digits_lost_in_mult(x, y); digitsLost += significant_digits_lost_in_mult(x, z); + digitsLost += significant_digits_lost_in_mult(y, z); assertEqWithinDecimalPrecision(xy_z, x_yz, digitsLost); }