From e5fb718d4071b4be5e8dc980a3733616605c570a Mon Sep 17 00:00:00 2001 From: carter-ya Date: Thu, 23 Nov 2023 23:31:14 +0800 Subject: [PATCH] Optimized gas costs in `ceilDiv` (#4553) --- .changeset/angry-dodos-grow.md | 5 +++++ contracts/utils/math/Math.sol | 10 ++++++++-- test/utils/math/Math.t.sol | 9 +++++---- 3 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 .changeset/angry-dodos-grow.md diff --git a/.changeset/angry-dodos-grow.md b/.changeset/angry-dodos-grow.md new file mode 100644 index 000000000..ab2b60104 --- /dev/null +++ b/.changeset/angry-dodos-grow.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`Math`: Optimized gas cost of `ceilDiv` by using `unchecked`. diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 968152452..86316cb29 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -110,8 +110,14 @@ library Math { return a / b; } - // (a + b - 1) / b can overflow on addition, so we distribute. - return a == 0 ? 0 : (a - 1) / b + 1; + // The following calculation ensures accurate ceiling division without overflow. + // Since a is non-zero, (a - 1) / b will not overflow. + // The largest possible result occurs when (a - 1) / b is type(uint256).max, + // 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; + } } /** diff --git a/test/utils/math/Math.t.sol b/test/utils/math/Math.t.sol index 0b497a858..a75783379 100644 --- a/test/utils/math/Math.t.sol +++ b/test/utils/math/Math.t.sol @@ -16,10 +16,11 @@ contract MathTest is Test { if (result == 0) { assertEq(a, 0); } else { - uint256 maxdiv = UINT256_MAX / b; - bool overflow = maxdiv * b < a; - assertTrue(a > b * (result - 1)); - assertTrue(overflow ? result == maxdiv + 1 : a <= b * result); + uint256 expect = a / b; + if (expect * b < a) { + expect += 1; + } + assertEq(result, expect); } }