From 1a23c5e20481b9add5070da1ca703062a5e2839b Mon Sep 17 00:00:00 2001 From: Lohann Paterno Coutinho Ferreira Date: Tue, 26 Mar 2024 21:35:25 -0300 Subject: [PATCH 01/18] Implement better log2, sqrt, min and max methods --- contracts/utils/math/Math.sol | 218 +++++++++++++--------------------- 1 file changed, 84 insertions(+), 134 deletions(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index b171e6f2d08..fc769356d9a 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -76,15 +76,23 @@ library Math { /** * @dev Returns the largest of two numbers. */ - function max(uint256 a, uint256 b) internal pure returns (uint256) { - return a > b ? a : b; + function max(uint256 a, uint256 b) internal pure returns (uint256 result) { + assembly ("memory-safe") { + // gas efficient branchless max function: + // max(x,y) = a ^ ((a ^ b) * (a < b)) + result := xor(a, mul(xor(a, b), lt(a, b))) + } } /** * @dev Returns the smallest of two numbers. */ - function min(uint256 a, uint256 b) internal pure returns (uint256) { - return a < b ? a : b; + function min(uint256 a, uint256 b) internal pure returns (uint256 result) { + assembly { + // gas efficient branchless min function: + // min(a,b) = b ^ ((a ^ b) * (a < b)) + result := xor(b, mul(xor(a, b), lt(a, b))) + } } /** @@ -385,112 +393,58 @@ library Math { * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded * towards zero. * - * This method is based on Newton's method for computing square roots; the algorithm is restricted to only + * This method first appro based on Newton's method for computing square roots; the algorithm is restricted to only * using integer operations. */ - function sqrt(uint256 a) internal pure returns (uint256) { - unchecked { - // Take care of easy edge cases when a == 0 or a == 1 - if (a <= 1) { - return a; - } - - // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a - // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between - // the current value as `ε_n = | x_n - sqrt(a) |`. - // - // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root - // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is - // bigger than any uint256. - // - // By noticing that - // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)` - // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar - // to the msb function. - uint256 aa = a; - uint256 xn = 1; - - if (aa >= (1 << 128)) { - aa >>= 128; - xn <<= 64; - } - if (aa >= (1 << 64)) { - aa >>= 64; - xn <<= 32; - } - if (aa >= (1 << 32)) { - aa >>= 32; - xn <<= 16; - } - if (aa >= (1 << 16)) { - aa >>= 16; - xn <<= 8; - } - if (aa >= (1 << 8)) { - aa >>= 8; - xn <<= 4; - } - if (aa >= (1 << 4)) { - aa >>= 4; - xn <<= 2; - } - if (aa >= (1 << 2)) { - xn <<= 1; - } - - // We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1). - // - // We can refine our estimation by noticing that the middle of that interval minimizes the error. - // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2). - // This is going to be our x_0 (and ε_0) - xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2) - - // From here, Newton's method give us: - // x_{n+1} = (x_n + a / x_n) / 2 - // - // One should note that: - // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a - // = ((x_n² + a) / (2 * x_n))² - a - // = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a - // = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²) - // = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²) - // = (x_n² - a)² / (2 * x_n)² - // = ((x_n² - a) / (2 * x_n))² - // ≥ 0 - // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n - // - // This gives us the proof of quadratic convergence of the sequence: - // ε_{n+1} = | x_{n+1} - sqrt(a) | - // = | (x_n + a / x_n) / 2 - sqrt(a) | - // = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) | - // = | (x_n - sqrt(a))² / (2 * x_n) | - // = | ε_n² / (2 * x_n) | - // = ε_n² / | (2 * x_n) | - // - // For the first iteration, we have a special case where x_0 is known: - // ε_1 = ε_0² / | (2 * x_0) | - // ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2))) - // ≤ 2**(2*e-4) / (3 * 2**(e-1)) - // ≤ 2**(e-3) / 3 - // ≤ 2**(e-3-log2(3)) - // ≤ 2**(e-4.5) + function sqrt(uint256 a) internal pure returns (uint256 xn) { + assembly { + // First we approximate the square root by calculate xn = 2 ** (log(x) / 2) + // then we need less iterations of Newton's method to find the result. // - // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n: - // ε_{n+1} = ε_n² / | (2 * x_n) | - // ≤ (2**(e-k))² / (2 * 2**(e-1)) - // ≤ 2**(2*e-2*k) / 2**e - // ≤ 2**(e-2*k) - xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above - xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5 - xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9 - xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18 - xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36 - xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72 - - // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision - // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either - // sqrt(a) or sqrt(a) + 1. - return xn - SafeCast.toUint(xn > a / xn); + // For that we use an optimized log2 function that doesn't do the final approximation step, + // once doing `log(x) / 2` will discard the least significant bit anyway. + xn := shl(7, gt(a, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) + let remainder := shr(xn, a) + + let shift := shl(6, gt(remainder, 0xFFFFFFFFFFFFFFFF)) + remainder := shr(shift, remainder) + xn := or(xn, shift) + + shift := shl(5, gt(remainder, 0xFFFFFFFF)) + remainder := shr(shift, remainder) + xn := or(xn, shift) + + shift := shl(4, gt(remainder, 0xFFFF)) + remainder := shr(shift, remainder) + xn := or(xn, shift) + + shift := shl(3, gt(remainder, 0xFF)) + remainder := shr(shift, remainder) + xn := or(xn, shift) + + shift := shl(2, gt(remainder, 0x0F)) + remainder := shr(shift, remainder) + xn := or(xn, shift) + + shift := shl(1, gt(remainder, 0x03)) + xn := or(xn, shift) + + // now xn = log2(a), so we compute: xn = 2 ** (xn / 2) + xn := shl(shr(1, xn), 1) + + // Newton's method + xn := shr(1, add(xn, div(a, xn))) + xn := shr(1, add(xn, div(a, xn))) + xn := shr(1, add(xn, div(a, xn))) + xn := shr(1, add(xn, div(a, xn))) + xn := shr(1, add(xn, div(a, xn))) + xn := shr(1, add(xn, div(a, xn))) + xn := shr(1, add(xn, div(a, xn))) + + // Once we round towards zero, we want the minimum between xn and a/xn + // we can safely assume that |xn - a/xn| is either 0 or 1, so we can easily compute the minimum as + // xn = xn - (xn > a/xn) + xn := sub(xn, gt(xn, div(a, xn))) } } @@ -508,41 +462,37 @@ library Math { * @dev Return the log in base 2 of a positive value rounded towards zero. * Returns 0 if given 0. */ - function log2(uint256 value) internal pure returns (uint256) { - uint256 result = 0; - uint256 exp; - unchecked { - exp = 128 * SafeCast.toUint(value > (1 << 128) - 1); - value >>= exp; - result += exp; + function log2(uint256 value) internal pure returns (uint256 result) { + assembly { + result := shl(7, gt(value, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) + value := shr(result, value) - exp = 64 * SafeCast.toUint(value > (1 << 64) - 1); - value >>= exp; - result += exp; + let shift := shl(6, gt(value, 0xFFFFFFFFFFFFFFFF)) + value := shr(shift, value) + result := or(result, shift) - exp = 32 * SafeCast.toUint(value > (1 << 32) - 1); - value >>= exp; - result += exp; + shift := shl(5, gt(value, 0xFFFFFFFF)) + value := shr(shift, value) + result := or(result, shift) - exp = 16 * SafeCast.toUint(value > (1 << 16) - 1); - value >>= exp; - result += exp; + shift := shl(4, gt(value, 0xFFFF)) + value := shr(shift, value) + result := or(result, shift) - exp = 8 * SafeCast.toUint(value > (1 << 8) - 1); - value >>= exp; - result += exp; + shift := shl(3, gt(value, 0xFF)) + value := shr(shift, value) + result := or(result, shift) - exp = 4 * SafeCast.toUint(value > (1 << 4) - 1); - value >>= exp; - result += exp; + shift := shl(2, gt(value, 0x0F)) + value := shr(shift, value) + result := or(result, shift) - exp = 2 * SafeCast.toUint(value > (1 << 2) - 1); - value >>= exp; - result += exp; + shift := shl(1, gt(value, 0x03)) + value := shr(shift, value) + result := or(result, shift) - result += SafeCast.toUint(value > 1); + result := or(result, eq(value, 2)) } - return result; } /** From 9ba03a739be1ecf1cd4fa8e9766f787affef89ec Mon Sep 17 00:00:00 2001 From: Lohann Paterno Coutinho Ferreira Date: Tue, 26 Mar 2024 22:19:06 -0300 Subject: [PATCH 02/18] Fix typo --- contracts/utils/math/Math.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index fc769356d9a..f9e844f4c47 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -393,7 +393,7 @@ library Math { * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded * towards zero. * - * This method first appro based on Newton's method for computing square roots; the algorithm is restricted to only + * This method is based on Newton's method for computing square roots; the algorithm is restricted to only * using integer operations. */ function sqrt(uint256 a) internal pure returns (uint256 xn) { From 35ccda847eadbba1f4121a94abb2f2d3e368df30 Mon Sep 17 00:00:00 2001 From: Lohann Paterno Coutinho Ferreira Date: Tue, 26 Mar 2024 22:23:45 -0300 Subject: [PATCH 03/18] Add changeset --- .changeset/spotty-falcons-explain.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/spotty-falcons-explain.md diff --git a/.changeset/spotty-falcons-explain.md b/.changeset/spotty-falcons-explain.md new file mode 100644 index 00000000000..e7db350db04 --- /dev/null +++ b/.changeset/spotty-falcons-explain.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +Improved Math.sol sqrt, log2, min and max methods From 0e7c0958aef5a31229c321b51a1e879745422130 Mon Sep 17 00:00:00 2001 From: Lohann Paterno Coutinho Ferreira Date: Tue, 26 Mar 2024 22:59:42 -0300 Subject: [PATCH 04/18] Fix log2 last iteration --- contracts/utils/math/Math.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index f9e844f4c47..351ed71c8e8 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -491,7 +491,7 @@ library Math { value := shr(shift, value) result := or(result, shift) - result := or(result, eq(value, 2)) + result := or(result, gt(value, 1)) } } From 69139d246a4015c58b4702f8da98630e031ac4e9 Mon Sep 17 00:00:00 2001 From: Lohann Paterno Coutinho Ferreira Date: Wed, 27 Mar 2024 01:54:52 -0300 Subject: [PATCH 05/18] Disable slither false positive --- contracts/utils/math/Math.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 351ed71c8e8..30726a06e4e 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -430,6 +430,7 @@ library Math { xn := or(xn, shift) // now xn = log2(a), so we compute: xn = 2 ** (xn / 2) + // slither-disable-next-line incorrect-shift xn := shl(shr(1, xn), 1) // Newton's method From 59c44bf58ee97e71afd407dadfba85038fa964d7 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 27 Mar 2024 10:23:09 +0100 Subject: [PATCH 06/18] add fuzzing tests for min and max --- test/utils/math/Math.t.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/utils/math/Math.t.sol b/test/utils/math/Math.t.sol index 40f5986f4f6..d15a014bccc 100644 --- a/test/utils/math/Math.t.sol +++ b/test/utils/math/Math.t.sol @@ -7,6 +7,12 @@ import {Test, stdError} from "forge-std/Test.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; contract MathTest is Test { + // MIN & MAX + function testMinMax(uint256 a, uint256 b) public { + assertEq(Math.min(a, b), a < b ? a : b); + assertEq(Math.max(a, b), a > b ? a : b); + } + // CEILDIV function testCeilDiv(uint256 a, uint256 b) public { vm.assume(b > 0); From 325ba78b8173245082d946c8935243735817424c Mon Sep 17 00:00:00 2001 From: Lohann Paterno Coutinho Ferreira Date: Thu, 28 Mar 2024 11:44:22 -0300 Subject: [PATCH 07/18] Impl choice function --- .changeset/spotty-falcons-explain.md | 4 +- contracts/utils/math/Math.sol | 236 +++++++++++++++++---------- contracts/utils/math/SignedMath.sol | 23 ++- 3 files changed, 174 insertions(+), 89 deletions(-) diff --git a/.changeset/spotty-falcons-explain.md b/.changeset/spotty-falcons-explain.md index e7db350db04..bdae3a4e846 100644 --- a/.changeset/spotty-falcons-explain.md +++ b/.changeset/spotty-falcons-explain.md @@ -1,5 +1,5 @@ --- -'openzeppelin-solidity': patch +'openzeppelin-solidity': minor --- -Improved Math.sol sqrt, log2, min and max methods +Replaces simple ternary operations `cond ? a : b` by a more efficient branchless `choice` function, now supported by `Math.sol` and `SignedMath.sol`. diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 30726a06e4e..fb0cd6932e3 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol) +// OpenZeppelin Contracts (last updated v5.0.2) (utils/math/Math.sol) pragma solidity ^0.8.20; @@ -77,21 +77,30 @@ library Math { * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256 result) { - assembly ("memory-safe") { - // gas efficient branchless max function: - // max(x,y) = a ^ ((a ^ b) * (a < b)) - result := xor(a, mul(xor(a, b), lt(a, b))) - } + return choice(a > b, a, b); } /** * @dev Returns the smallest of two numbers. */ function min(uint256 a, uint256 b) internal pure returns (uint256 result) { - assembly { - // gas efficient branchless min function: - // min(a,b) = b ^ ((a ^ b) * (a < b)) - result := xor(b, mul(xor(a, b), lt(a, b))) + return choice(a < b, a, b); + } + + /** + * @dev If `condition` is true, returns `a`, otherwise returns `b`. + */ + function choice(bool condition, uint256 a, uint256 b) internal pure returns (uint256) { + unchecked { + // branchless choice function, works because: + // b ^ (a ^ b) == a + // b ^ 0 == b + // + // This is better than doing `condition ? a : b` for the give reasons: + // - Consumes less gas + // - Constant gas cost regardless the inputs + // - Reduces the final bytecode size + return b ^ (a ^ b) * SafeCast.toUint(condition); } } @@ -122,7 +131,7 @@ library Math { // but the largest value we can obtain is type(uint256).max - 1, which happens // when a = type(uint256).max and b = 1. unchecked { - return a == 0 ? 0 : (a - 1) / b + 1; + return choice(a == 0, 0, (a - 1) / b + 1); } } @@ -155,7 +164,7 @@ library Math { // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0. if (denominator <= prod1) { - Panic.panic(denominator == 0 ? Panic.DIVISION_BY_ZERO : Panic.UNDER_OVERFLOW); + Panic.panic(choice(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW)); } /////////////////////////////////////////////// @@ -276,7 +285,7 @@ library Math { } if (gcd != 1) return 0; // No inverse exists. - return x < 0 ? (n - uint256(-x)) : uint256(x); // Wrap the result if it's negative. + return choice(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative. } } @@ -396,56 +405,109 @@ library Math { * This method is based on Newton's method for computing square roots; the algorithm is restricted to only * using integer operations. */ - function sqrt(uint256 a) internal pure returns (uint256 xn) { - assembly { - // First we approximate the square root by calculate xn = 2 ** (log(x) / 2) - // then we need less iterations of Newton's method to find the result. + function sqrt(uint256 a) internal pure returns (uint256) { + unchecked { + // Take care of easy edge cases when a == 0 or a == 1 + if (a <= 1) { + return a; + } + + // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a + // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between + // the current value as `ε_n = | x_n - sqrt(a) |`. // - // For that we use an optimized log2 function that doesn't do the final approximation step, - // once doing `log(x) / 2` will discard the least significant bit anyway. - xn := shl(7, gt(a, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) - let remainder := shr(xn, a) - - let shift := shl(6, gt(remainder, 0xFFFFFFFFFFFFFFFF)) - remainder := shr(shift, remainder) - xn := or(xn, shift) - - shift := shl(5, gt(remainder, 0xFFFFFFFF)) - remainder := shr(shift, remainder) - xn := or(xn, shift) - - shift := shl(4, gt(remainder, 0xFFFF)) - remainder := shr(shift, remainder) - xn := or(xn, shift) - - shift := shl(3, gt(remainder, 0xFF)) - remainder := shr(shift, remainder) - xn := or(xn, shift) - - shift := shl(2, gt(remainder, 0x0F)) - remainder := shr(shift, remainder) - xn := or(xn, shift) - - shift := shl(1, gt(remainder, 0x03)) - xn := or(xn, shift) - - // now xn = log2(a), so we compute: xn = 2 ** (xn / 2) - // slither-disable-next-line incorrect-shift - xn := shl(shr(1, xn), 1) - - // Newton's method - xn := shr(1, add(xn, div(a, xn))) - xn := shr(1, add(xn, div(a, xn))) - xn := shr(1, add(xn, div(a, xn))) - xn := shr(1, add(xn, div(a, xn))) - xn := shr(1, add(xn, div(a, xn))) - xn := shr(1, add(xn, div(a, xn))) - xn := shr(1, add(xn, div(a, xn))) - - // Once we round towards zero, we want the minimum between xn and a/xn - // we can safely assume that |xn - a/xn| is either 0 or 1, so we can easily compute the minimum as - // xn = xn - (xn > a/xn) - xn := sub(xn, gt(xn, div(a, xn))) + // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root + // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is + // bigger than any uint256. + // + // By noticing that + // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)` + // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar + // to the msb function. + uint256 aa = a; + uint256 xn = 1; + + if (aa >= (1 << 128)) { + aa >>= 128; + xn <<= 64; + } + if (aa >= (1 << 64)) { + aa >>= 64; + xn <<= 32; + } + if (aa >= (1 << 32)) { + aa >>= 32; + xn <<= 16; + } + if (aa >= (1 << 16)) { + aa >>= 16; + xn <<= 8; + } + if (aa >= (1 << 8)) { + aa >>= 8; + xn <<= 4; + } + if (aa >= (1 << 4)) { + aa >>= 4; + xn <<= 2; + } + if (aa >= (1 << 2)) { + xn <<= 1; + } + + // We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1). + // + // We can refine our estimation by noticing that the middle of that interval minimizes the error. + // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2). + // This is going to be our x_0 (and ε_0) + xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2) + + // From here, Newton's method give us: + // x_{n+1} = (x_n + a / x_n) / 2 + // + // One should note that: + // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a + // = ((x_n² + a) / (2 * x_n))² - a + // = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a + // = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²) + // = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²) + // = (x_n² - a)² / (2 * x_n)² + // = ((x_n² - a) / (2 * x_n))² + // ≥ 0 + // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n + // + // This gives us the proof of quadratic convergence of the sequence: + // ε_{n+1} = | x_{n+1} - sqrt(a) | + // = | (x_n + a / x_n) / 2 - sqrt(a) | + // = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) | + // = | (x_n - sqrt(a))² / (2 * x_n) | + // = | ε_n² / (2 * x_n) | + // = ε_n² / | (2 * x_n) | + // + // For the first iteration, we have a special case where x_0 is known: + // ε_1 = ε_0² / | (2 * x_0) | + // ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2))) + // ≤ 2**(2*e-4) / (3 * 2**(e-1)) + // ≤ 2**(e-3) / 3 + // ≤ 2**(e-3-log2(3)) + // ≤ 2**(e-4.5) + // + // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n: + // ε_{n+1} = ε_n² / | (2 * x_n) | + // ≤ (2**(e-k))² / (2 * 2**(e-1)) + // ≤ 2**(2*e-2*k) / 2**e + // ≤ 2**(e-2*k) + xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above + xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5 + xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9 + xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18 + xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36 + xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72 + + // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision + // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either + // sqrt(a) or sqrt(a) + 1. + return xn - SafeCast.toUint(xn > a / xn); } } @@ -463,37 +525,41 @@ library Math { * @dev Return the log in base 2 of a positive value rounded towards zero. * Returns 0 if given 0. */ - function log2(uint256 value) internal pure returns (uint256 result) { - assembly { - result := shl(7, gt(value, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) - value := shr(result, value) + function log2(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + uint256 exp; + unchecked { + exp = 128 * SafeCast.toUint(value > (1 << 128) - 1); + value >>= exp; + result += exp; - let shift := shl(6, gt(value, 0xFFFFFFFFFFFFFFFF)) - value := shr(shift, value) - result := or(result, shift) + exp = 64 * SafeCast.toUint(value > (1 << 64) - 1); + value >>= exp; + result += exp; - shift := shl(5, gt(value, 0xFFFFFFFF)) - value := shr(shift, value) - result := or(result, shift) + exp = 32 * SafeCast.toUint(value > (1 << 32) - 1); + value >>= exp; + result += exp; - shift := shl(4, gt(value, 0xFFFF)) - value := shr(shift, value) - result := or(result, shift) + exp = 16 * SafeCast.toUint(value > (1 << 16) - 1); + value >>= exp; + result += exp; - shift := shl(3, gt(value, 0xFF)) - value := shr(shift, value) - result := or(result, shift) + exp = 8 * SafeCast.toUint(value > (1 << 8) - 1); + value >>= exp; + result += exp; - shift := shl(2, gt(value, 0x0F)) - value := shr(shift, value) - result := or(result, shift) + exp = 4 * SafeCast.toUint(value > (1 << 4) - 1); + value >>= exp; + result += exp; - shift := shl(1, gt(value, 0x03)) - value := shr(shift, value) - result := or(result, shift) + exp = 2 * SafeCast.toUint(value > (1 << 2) - 1); + value >>= exp; + result += exp; - result := or(result, gt(value, 1)) + result += SafeCast.toUint(value > 1); } + return result; } /** diff --git a/contracts/utils/math/SignedMath.sol b/contracts/utils/math/SignedMath.sol index 535d757616f..088abb840d2 100644 --- a/contracts/utils/math/SignedMath.sol +++ b/contracts/utils/math/SignedMath.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.20; +import {SafeCast} from "./SafeCast.sol"; + /** * @dev Standard signed math utilities missing in the Solidity language. */ @@ -11,14 +13,14 @@ library SignedMath { * @dev Returns the largest of two signed numbers. */ function max(int256 a, int256 b) internal pure returns (int256) { - return a > b ? a : b; + return choice(a > b, a, b); } /** * @dev Returns the smallest of two signed numbers. */ function min(int256 a, int256 b) internal pure returns (int256) { - return a < b ? a : b; + return choice(a < b, a, b); } /** @@ -47,4 +49,21 @@ library SignedMath { return uint256((n + mask) ^ mask); } } + + /** + * @dev If `condition` is true, returns `a`, otherwise returns `b`. + */ + function choice(bool condition, int256 a, int256 b) internal pure returns (int256) { + unchecked { + // branchless choice function, works because: + // b ^ (a ^ b) == a + // b ^ 0 == b + // + // This is better than doing `condition ? a : b` for the give reasons: + // - Consumes less gas + // - Constant gas cost regardless the inputs + // - Reduces the final bytecode size + return b ^ (a ^ b) * int256(SafeCast.toUint(condition)); + } + } } From d24fe9a886d3983328c82481c330eedc01fba964 Mon Sep 17 00:00:00 2001 From: Lohann Paterno Coutinho Ferreira Date: Thu, 28 Mar 2024 11:45:53 -0300 Subject: [PATCH 08/18] Fix lint --- contracts/utils/math/Math.sol | 2 +- contracts/utils/math/SignedMath.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index fb0cd6932e3..f3e7fd30a4b 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -100,7 +100,7 @@ library Math { // - Consumes less gas // - Constant gas cost regardless the inputs // - Reduces the final bytecode size - return b ^ (a ^ b) * SafeCast.toUint(condition); + return b ^ ((a ^ b) * SafeCast.toUint(condition)); } } diff --git a/contracts/utils/math/SignedMath.sol b/contracts/utils/math/SignedMath.sol index 088abb840d2..7695eeb1582 100644 --- a/contracts/utils/math/SignedMath.sol +++ b/contracts/utils/math/SignedMath.sol @@ -63,7 +63,7 @@ library SignedMath { // - Consumes less gas // - Constant gas cost regardless the inputs // - Reduces the final bytecode size - return b ^ (a ^ b) * int256(SafeCast.toUint(condition)); + return b ^ ((a ^ b) * int256(SafeCast.toUint(condition))); } } } From 4b4777f37dc32f98677d2966ba654e2842230bff Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 28 Mar 2024 15:58:12 +0100 Subject: [PATCH 09/18] Update contracts/utils/math/Math.sol --- contracts/utils/math/Math.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index f3e7fd30a4b..7f250f56408 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.2) (utils/math/Math.sol) +// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol) pragma solidity ^0.8.20; From dfdc81e8509e4ef07de3eec256713fa5dec62ab6 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 28 Mar 2024 16:00:27 +0100 Subject: [PATCH 10/18] Update SignedMath.sol --- contracts/utils/math/SignedMath.sol | 34 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/contracts/utils/math/SignedMath.sol b/contracts/utils/math/SignedMath.sol index 7695eeb1582..459961714e2 100644 --- a/contracts/utils/math/SignedMath.sol +++ b/contracts/utils/math/SignedMath.sol @@ -9,6 +9,23 @@ import {SafeCast} from "./SafeCast.sol"; * @dev Standard signed math utilities missing in the Solidity language. */ library SignedMath { + /** + * @dev If `condition` is true, returns `a`, otherwise returns `b`. + */ + function choice(bool condition, int256 a, int256 b) internal pure returns (int256) { + unchecked { + // branchless choice function, works because: + // b ^ (a ^ b) == a + // b ^ 0 == b + // + // This is better than doing `condition ? a : b` for the give reasons: + // - Consumes less gas + // - Constant gas cost regardless the inputs + // - Reduces the final bytecode size + return b ^ ((a ^ b) * int256(SafeCast.toUint(condition))); + } + } + /** * @dev Returns the largest of two signed numbers. */ @@ -49,21 +66,4 @@ library SignedMath { return uint256((n + mask) ^ mask); } } - - /** - * @dev If `condition` is true, returns `a`, otherwise returns `b`. - */ - function choice(bool condition, int256 a, int256 b) internal pure returns (int256) { - unchecked { - // branchless choice function, works because: - // b ^ (a ^ b) == a - // b ^ 0 == b - // - // This is better than doing `condition ? a : b` for the give reasons: - // - Consumes less gas - // - Constant gas cost regardless the inputs - // - Reduces the final bytecode size - return b ^ ((a ^ b) * int256(SafeCast.toUint(condition))); - } - } } From 96d1e8477b8daf84ca82ed983fbe8ed262c57894 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 28 Mar 2024 16:00:53 +0100 Subject: [PATCH 11/18] Update Math.sol --- contracts/utils/math/Math.sol | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 7f250f56408..9716e419ce9 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -73,20 +73,6 @@ library Math { } } - /** - * @dev Returns the largest of two numbers. - */ - function max(uint256 a, uint256 b) internal pure returns (uint256 result) { - return choice(a > b, a, b); - } - - /** - * @dev Returns the smallest of two numbers. - */ - function min(uint256 a, uint256 b) internal pure returns (uint256 result) { - return choice(a < b, a, b); - } - /** * @dev If `condition` is true, returns `a`, otherwise returns `b`. */ @@ -104,6 +90,20 @@ library Math { } } + /** + * @dev Returns the largest of two numbers. + */ + function max(uint256 a, uint256 b) internal pure returns (uint256 result) { + return choice(a > b, a, b); + } + + /** + * @dev Returns the smallest of two numbers. + */ + function min(uint256 a, uint256 b) internal pure returns (uint256 result) { + return choice(a < b, a, b); + } + /** * @dev Returns the average of two numbers. The result is rounded towards * zero. From 0f45e02675c8d2ccb9ffd7dd88019fd37dc9f263 Mon Sep 17 00:00:00 2001 From: Lohann Paterno Coutinho Ferreira Date: Thu, 28 Mar 2024 14:49:14 -0300 Subject: [PATCH 12/18] Remove unnecessary return label --- contracts/utils/math/Math.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 9716e419ce9..a5d54b87399 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -93,14 +93,14 @@ library Math { /** * @dev Returns the largest of two numbers. */ - function max(uint256 a, uint256 b) internal pure returns (uint256 result) { + function max(uint256 a, uint256 b) internal pure returns (uint256) { return choice(a > b, a, b); } /** * @dev Returns the smallest of two numbers. */ - function min(uint256 a, uint256 b) internal pure returns (uint256 result) { + function min(uint256 a, uint256 b) internal pure returns (uint256) { return choice(a < b, a, b); } From 715a772b242fa23effce0b0631223c6a6bc64df3 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 28 Mar 2024 20:57:26 +0100 Subject: [PATCH 13/18] Update Math.t.sol --- test/utils/math/Math.t.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/utils/math/Math.t.sol b/test/utils/math/Math.t.sol index d15a014bccc..78a5e8f61ca 100644 --- a/test/utils/math/Math.t.sol +++ b/test/utils/math/Math.t.sol @@ -7,6 +7,10 @@ import {Test, stdError} from "forge-std/Test.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; contract MathTest is Test { + function testSelect(bool f, uint256 a, uint256 b) public { + assertEq(Math.select(f, a, b), f ? a : b); + } + // MIN & MAX function testMinMax(uint256 a, uint256 b) public { assertEq(Math.min(a, b), a < b ? a : b); From af95b8452813168792cb13c0d890346f953c9fe4 Mon Sep 17 00:00:00 2001 From: Lohann Paterno Coutinho Ferreira Date: Fri, 29 Mar 2024 13:18:57 -0300 Subject: [PATCH 14/18] Improve ceilDiv --- .changeset/spotty-falcons-explain.md | 2 +- contracts/utils/math/Math.sol | 14 +++++++------- contracts/utils/math/SignedMath.sol | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.changeset/spotty-falcons-explain.md b/.changeset/spotty-falcons-explain.md index bdae3a4e846..eceb26fa8c5 100644 --- a/.changeset/spotty-falcons-explain.md +++ b/.changeset/spotty-falcons-explain.md @@ -2,4 +2,4 @@ 'openzeppelin-solidity': minor --- -Replaces simple ternary operations `cond ? a : b` by a more efficient branchless `choice` function, now supported by `Math.sol` and `SignedMath.sol`. +Replaces simple ternary operations `cond ? a : b` by a more efficient branchless `select` function, now supported by `Math.sol` and `SignedMath.sol`. diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index a5d54b87399..b5b85d222e6 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -76,9 +76,9 @@ library Math { /** * @dev If `condition` is true, returns `a`, otherwise returns `b`. */ - function choice(bool condition, uint256 a, uint256 b) internal pure returns (uint256) { + function select(bool condition, uint256 a, uint256 b) internal pure returns (uint256) { unchecked { - // branchless choice function, works because: + // branchless select function, works because: // b ^ (a ^ b) == a // b ^ 0 == b // @@ -94,14 +94,14 @@ library Math { * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256) { - return choice(a > b, a, b); + return select(a > b, a, b); } /** * @dev Returns the smallest of two numbers. */ function min(uint256 a, uint256 b) internal pure returns (uint256) { - return choice(a < b, a, b); + return select(a < b, a, b); } /** @@ -131,7 +131,7 @@ library Math { // but the largest value we can obtain is type(uint256).max - 1, which happens // when a = type(uint256).max and b = 1. unchecked { - return choice(a == 0, 0, (a - 1) / b + 1); + return SafeCast.toUint(a > 0) * ((a - 1) / b + 1); } } @@ -164,7 +164,7 @@ library Math { // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0. if (denominator <= prod1) { - Panic.panic(choice(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW)); + Panic.panic(select(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW)); } /////////////////////////////////////////////// @@ -285,7 +285,7 @@ library Math { } if (gcd != 1) return 0; // No inverse exists. - return choice(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative. + return select(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative. } } diff --git a/contracts/utils/math/SignedMath.sol b/contracts/utils/math/SignedMath.sol index 459961714e2..3e19c7a7f22 100644 --- a/contracts/utils/math/SignedMath.sol +++ b/contracts/utils/math/SignedMath.sol @@ -12,9 +12,9 @@ library SignedMath { /** * @dev If `condition` is true, returns `a`, otherwise returns `b`. */ - function choice(bool condition, int256 a, int256 b) internal pure returns (int256) { + function select(bool condition, int256 a, int256 b) internal pure returns (int256) { unchecked { - // branchless choice function, works because: + // branchless select function, works because: // b ^ (a ^ b) == a // b ^ 0 == b // @@ -30,14 +30,14 @@ library SignedMath { * @dev Returns the largest of two signed numbers. */ function max(int256 a, int256 b) internal pure returns (int256) { - return choice(a > b, a, b); + return select(a > b, a, b); } /** * @dev Returns the smallest of two signed numbers. */ function min(int256 a, int256 b) internal pure returns (int256) { - return choice(a < b, a, b); + return select(a < b, a, b); } /** From 12d7b50876578a4aac96c77a780ec1d663cfdf03 Mon Sep 17 00:00:00 2001 From: Lohann Paterno Coutinho Ferreira Date: Mon, 22 Apr 2024 15:51:42 -0300 Subject: [PATCH 15/18] Rename 'Math.select' to 'Math.ternary' --- .changeset/spotty-falcons-explain.md | 2 +- contracts/utils/math/Math.sol | 23 +++++++++++------------ contracts/utils/math/SignedMath.sol | 19 +++++++++---------- test/utils/math/Math.t.sol | 2 +- test/utils/math/SignedMath.t.sol | 10 ++++++++++ 5 files changed, 32 insertions(+), 24 deletions(-) diff --git a/.changeset/spotty-falcons-explain.md b/.changeset/spotty-falcons-explain.md index eceb26fa8c5..b8bbd7ad4c4 100644 --- a/.changeset/spotty-falcons-explain.md +++ b/.changeset/spotty-falcons-explain.md @@ -2,4 +2,4 @@ 'openzeppelin-solidity': minor --- -Replaces simple ternary operations `cond ? a : b` by a more efficient branchless `select` function, now supported by `Math.sol` and `SignedMath.sol`. +Replaces simple ternary operations `cond ? a : b` by a more efficient branchless `ternary` function, now supported by `Math.sol` and `SignedMath.sol`. diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index b5b85d222e6..2c8098faf18 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -74,18 +74,17 @@ library Math { } /** - * @dev If `condition` is true, returns `a`, otherwise returns `b`. + * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant. + * + * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone. + * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute + * one branch when needed, making this function more expensive. */ - function select(bool condition, uint256 a, uint256 b) internal pure returns (uint256) { + function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) { unchecked { - // branchless select function, works because: + // branchless ternary works because: // b ^ (a ^ b) == a // b ^ 0 == b - // - // This is better than doing `condition ? a : b` for the give reasons: - // - Consumes less gas - // - Constant gas cost regardless the inputs - // - Reduces the final bytecode size return b ^ ((a ^ b) * SafeCast.toUint(condition)); } } @@ -94,14 +93,14 @@ library Math { * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256) { - return select(a > b, a, b); + return ternary(a > b, a, b); } /** * @dev Returns the smallest of two numbers. */ function min(uint256 a, uint256 b) internal pure returns (uint256) { - return select(a < b, a, b); + return ternary(a < b, a, b); } /** @@ -164,7 +163,7 @@ library Math { // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0. if (denominator <= prod1) { - Panic.panic(select(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW)); + Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW)); } /////////////////////////////////////////////// @@ -285,7 +284,7 @@ library Math { } if (gcd != 1) return 0; // No inverse exists. - return select(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative. + return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative. } } diff --git a/contracts/utils/math/SignedMath.sol b/contracts/utils/math/SignedMath.sol index 3e19c7a7f22..9b3672400f1 100644 --- a/contracts/utils/math/SignedMath.sol +++ b/contracts/utils/math/SignedMath.sol @@ -10,18 +10,17 @@ import {SafeCast} from "./SafeCast.sol"; */ library SignedMath { /** - * @dev If `condition` is true, returns `a`, otherwise returns `b`. + * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant. + * + * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone. + * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute + * one branch when needed, making this function more expensive. */ - function select(bool condition, int256 a, int256 b) internal pure returns (int256) { + function ternary(bool condition, int256 a, int256 b) internal pure returns (int256) { unchecked { - // branchless select function, works because: + // branchless terinary works because: // b ^ (a ^ b) == a // b ^ 0 == b - // - // This is better than doing `condition ? a : b` for the give reasons: - // - Consumes less gas - // - Constant gas cost regardless the inputs - // - Reduces the final bytecode size return b ^ ((a ^ b) * int256(SafeCast.toUint(condition))); } } @@ -30,14 +29,14 @@ library SignedMath { * @dev Returns the largest of two signed numbers. */ function max(int256 a, int256 b) internal pure returns (int256) { - return select(a > b, a, b); + return ternary(a > b, a, b); } /** * @dev Returns the smallest of two signed numbers. */ function min(int256 a, int256 b) internal pure returns (int256) { - return select(a < b, a, b); + return ternary(a < b, a, b); } /** diff --git a/test/utils/math/Math.t.sol b/test/utils/math/Math.t.sol index 78a5e8f61ca..75066f1f4e4 100644 --- a/test/utils/math/Math.t.sol +++ b/test/utils/math/Math.t.sol @@ -8,7 +8,7 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; contract MathTest is Test { function testSelect(bool f, uint256 a, uint256 b) public { - assertEq(Math.select(f, a, b), f ? a : b); + assertEq(Math.ternary(f, a, b), f ? a : b); } // MIN & MAX diff --git a/test/utils/math/SignedMath.t.sol b/test/utils/math/SignedMath.t.sol index fe5900a5d1b..c173e0a4285 100644 --- a/test/utils/math/SignedMath.t.sol +++ b/test/utils/math/SignedMath.t.sol @@ -8,6 +8,16 @@ import {Math} from "../../../contracts/utils/math/Math.sol"; import {SignedMath} from "../../../contracts/utils/math/SignedMath.sol"; contract SignedMathTest is Test { + function testSelect(bool f, int256 a, int256 b) public { + assertEq(SignedMath.ternary(f, a, b), f ? a : b); + } + + // MIN & MAX + function testMinMax(int256 a, int256 b) public { + assertEq(SignedMath.min(a, b), a < b ? a : b); + assertEq(SignedMath.max(a, b), a > b ? a : b); + } + // MIN function testMin(int256 a, int256 b) public { int256 result = SignedMath.min(a, b); From e7f5c42091f81355dd7fd57c1e0c8b9894b68305 Mon Sep 17 00:00:00 2001 From: Lohann Paterno Coutinho Ferreira Date: Mon, 22 Apr 2024 15:53:28 -0300 Subject: [PATCH 16/18] fix solidity lint --- contracts/utils/math/Math.sol | 2 +- contracts/utils/math/SignedMath.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 2c8098faf18..0a431719dd1 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -75,7 +75,7 @@ library Math { /** * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant. - * + * * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone. * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute * one branch when needed, making this function more expensive. diff --git a/contracts/utils/math/SignedMath.sol b/contracts/utils/math/SignedMath.sol index 9b3672400f1..c17d208775d 100644 --- a/contracts/utils/math/SignedMath.sol +++ b/contracts/utils/math/SignedMath.sol @@ -11,7 +11,7 @@ import {SafeCast} from "./SafeCast.sol"; library SignedMath { /** * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant. - * + * * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone. * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute * one branch when needed, making this function more expensive. From 52faabc0ab33ab5c23395f0e72b3d9f9f9527556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 22 Apr 2024 14:05:18 -0600 Subject: [PATCH 17/18] Update .changeset/spotty-falcons-explain.md --- .changeset/spotty-falcons-explain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/spotty-falcons-explain.md b/.changeset/spotty-falcons-explain.md index b8bbd7ad4c4..85962999e03 100644 --- a/.changeset/spotty-falcons-explain.md +++ b/.changeset/spotty-falcons-explain.md @@ -2,4 +2,4 @@ 'openzeppelin-solidity': minor --- -Replaces simple ternary operations `cond ? a : b` by a more efficient branchless `ternary` function, now supported by `Math.sol` and `SignedMath.sol`. +`Math`, `SafeMath`: Replace simple ternary operations `cond ? a : b` by a more efficient branchless `ternary` function. From dc6abbc43225ab99de93a871533852dc8441541c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 22 Apr 2024 14:14:14 -0600 Subject: [PATCH 18/18] Update .changeset/spotty-falcons-explain.md --- .changeset/spotty-falcons-explain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/spotty-falcons-explain.md b/.changeset/spotty-falcons-explain.md index 85962999e03..28cb95190c1 100644 --- a/.changeset/spotty-falcons-explain.md +++ b/.changeset/spotty-falcons-explain.md @@ -2,4 +2,4 @@ 'openzeppelin-solidity': minor --- -`Math`, `SafeMath`: Replace simple ternary operations `cond ? a : b` by a more efficient branchless `ternary` function. +`Math`, `SignedMath`: Add a branchless `ternary` function that computes`cond ? a : b` in constant gas cost.