Forráskód Böngészése

Add ceiling division operation to the `Math.sol` library (#2681)

Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
Nicholas Rodrigues Lordello 4 éve
szülő
commit
7c754d0665

+ 2 - 1
CHANGELOG.md

@@ -2,11 +2,12 @@
 
 ## Unreleased
 
-* `ERC20Votes`: add a new extension of the `ERC20` token with support for voting snapshots and delegation. This extension is compatible with Compound's `Comp` token interface. ([#2632](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2632))
+ * `ERC20Votes`: add a new extension of the `ERC20` token with support for voting snapshots and delegation. This extension is compatible with Compound's `Comp` token interface. ([#2632](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2632))
  * Enumerables: Improve gas cost of removal in `EnumerableSet` and `EnumerableMap`.
  * Enumerables: Improve gas cost of lookup in `EnumerableSet` and `EnumerableMap`.
  * `Counter`: add a reset method. ([#2678](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2678))
  * Tokens: Wrap definitely safe subtractions in `unchecked` blocks. 
+ * `Math`: Add a `ceilDiv` method for performing ceiling division.
 
 ## 4.1.0 (2021-04-29)
 

+ 4 - 0
contracts/mocks/MathMock.sol

@@ -16,4 +16,8 @@ contract MathMock {
     function average(uint256 a, uint256 b) public pure returns (uint256) {
         return Math.average(a, b);
     }
+
+    function ceilDiv(uint256 a, uint256 b) public pure returns (uint256) {
+        return Math.ceilDiv(a, b);
+    }
 }

+ 11 - 0
contracts/utils/math/Math.sol

@@ -28,4 +28,15 @@ library Math {
         // (a + b) / 2 can overflow, so we distribute.
         return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
     }
+
+    /**
+     * @dev Returns the ceiling of the division of two numbers.
+     *
+     * This differs from standard division with `/` in that it rounds up instead
+     * of rounding down.
+     */
+    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
+        // (a + b - 1) / b can overflow on addition, so we distribute.
+        return a / b + (a % b == 0 ? 0 : 1);
+    }
 }

+ 27 - 2
test/utils/math/Math.test.js

@@ -1,6 +1,6 @@
-const { BN } = require('@openzeppelin/test-helpers');
-
+const { BN, constants } = require('@openzeppelin/test-helpers');
 const { expect } = require('chai');
+const { MAX_UINT256 } = constants;
 
 const MathMock = artifacts.require('MathMock');
 
@@ -55,4 +55,29 @@ contract('Math', function (accounts) {
       expect(await this.math.average(a, b)).to.be.bignumber.equal(bnAverage(a, b));
     });
   });
+
+  describe('ceilDiv', function () {
+    it('does not round up on exact division', async function () {
+      const a = new BN('10');
+      const b = new BN('5');
+      expect(await this.math.ceilDiv(a, b)).to.be.bignumber.equal('2');
+    });
+
+    it('rounds up on division with remainders', async function () {
+      const a = new BN('42');
+      const b = new BN('13');
+      expect(await this.math.ceilDiv(a, b)).to.be.bignumber.equal('4');
+    });
+
+    it('does not overflow', async function () {
+      const b = new BN('2');
+      const result = new BN('1').shln(255);
+      expect(await this.math.ceilDiv(MAX_UINT256, b)).to.be.bignumber.equal(result);
+    });
+
+    it('correctly computes max uint256 divided by 1', async function () {
+      const b = new BN('1');
+      expect(await this.math.ceilDiv(MAX_UINT256, b)).to.be.bignumber.equal(MAX_UINT256);
+    });
+  });
 });