Browse Source

Adding SafeCast variants for signed integers (#2243)

* feat: Adding SafeCast variants for signed integers

* Add newline at EOF

* Update CHANGELOG.md

* Update contracts/utils/SafeCast.sol

Co-authored-by: Nicolás Venturo <nicolas.venturo@gmail.com>

Co-authored-by: Nicolás Venturo <nicolas.venturo@gmail.com>
Julian M. Rodriguez 5 years ago
parent
commit
5513dfd3cf
4 changed files with 164 additions and 4 deletions
  1. 3 0
      CHANGELOG.md
  2. 20 0
      contracts/mocks/SafeCastMock.sol
  3. 84 4
      contracts/utils/SafeCast.sol
  4. 57 0
      test/utils/SafeCast.test.js

+ 3 - 0
CHANGELOG.md

@@ -2,6 +2,9 @@
 
 ## 3.1.0 (unreleased)
 
+### New features
+ * `SafeCast`: added functions to downcast signed integers (e.g. `toInt32`), improving usability of `SignedSafeMath`. ([#2243](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2243))
+
 ### Improvements
  * `ReentrancyGuard`: reduced overhead of using the `nonReentrant` modifier. ([#2171](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2171))
  * `AccessControl`: added a `RoleAdminChanged` event to `_setAdminRole`. ([#2214](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2214))

+ 20 - 0
contracts/mocks/SafeCastMock.sol

@@ -35,4 +35,24 @@ contract SafeCastMock {
     function toUint8(uint a) public pure returns (uint8) {
         return a.toUint8();
     }
+
+    function toInt128(int a) public pure returns (int128) {
+        return a.toInt128();
+    }
+
+    function toInt64(int a) public pure returns (int64) {
+        return a.toInt64();
+    }
+
+    function toInt32(int a) public pure returns (int32) {
+        return a.toInt32();
+    }
+
+    function toInt16(int a) public pure returns (int16) {
+        return a.toInt16();
+    }
+
+    function toInt8(int a) public pure returns (int8) {
+        return a.toInt8();
+    }
 }

+ 84 - 4
contracts/utils/SafeCast.sol

@@ -4,10 +4,10 @@ pragma solidity ^0.6.0;
 
 
 /**
- * @dev Wrappers over Solidity's uintXX casting operators with added overflow
+ * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
  * checks.
  *
- * Downcasting from uint256 in Solidity does not revert on overflow. This can
+ * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
  * easily result in undesired exploitation or bugs, since developers usually
  * assume that overflows raise errors. `SafeCast` restores this intuition by
  * reverting the transaction when such an operation overflows.
@@ -15,8 +15,8 @@ pragma solidity ^0.6.0;
  * Using this library instead of the unchecked operations eliminates an entire
  * class of bugs, so it's recommended to use it always.
  *
- * Can be combined with {SafeMath} to extend it to smaller types, by performing
- * all math on `uint256` and then downcasting.
+ * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
+ * all math on `uint256` and `int256` and then downcasting.
  */
 library SafeCast {
 
@@ -107,6 +107,86 @@ library SafeCast {
         return uint256(value);
     }
 
+    /**
+     * @dev Returns the downcasted int128 from int256, reverting on
+     * overflow (when the input is less than smallest int128 or
+     * greater than largest int128).
+     *
+     * Counterpart to Solidity's `int128` operator.
+     *
+     * Requirements:
+     *
+     * - input must fit into 128 bits
+     */
+    function toInt128(int256 value) internal pure returns (int128) {
+        require(value >= -2**127 && value < 2**127, "SafeCast: value doesn\'t fit in 128 bits");
+        return int128(value);
+    }
+
+    /**
+     * @dev Returns the downcasted int64 from int256, reverting on
+     * overflow (when the input is less than smallest int64 or
+     * greater than largest int64).
+     *
+     * Counterpart to Solidity's `int64` operator.
+     *
+     * Requirements:
+     *
+     * - input must fit into 64 bits
+     */
+    function toInt64(int256 value) internal pure returns (int64) {
+        require(value >= -2**63 && value < 2**63, "SafeCast: value doesn\'t fit in 64 bits");
+        return int64(value);
+    }
+
+    /**
+     * @dev Returns the downcasted int32 from int256, reverting on
+     * overflow (when the input is less than smallest int32 or
+     * greater than largest int32).
+     *
+     * Counterpart to Solidity's `int32` operator.
+     *
+     * Requirements:
+     *
+     * - input must fit into 32 bits
+     */
+    function toInt32(int256 value) internal pure returns (int32) {
+        require(value >= -2**31 && value < 2**31, "SafeCast: value doesn\'t fit in 32 bits");
+        return int32(value);
+    }
+
+    /**
+     * @dev Returns the downcasted int16 from int256, reverting on
+     * overflow (when the input is less than smallest int16 or
+     * greater than largest int16).
+     *
+     * Counterpart to Solidity's `int16` operator.
+     *
+     * Requirements:
+     *
+     * - input must fit into 16 bits
+     */
+    function toInt16(int256 value) internal pure returns (int16) {
+        require(value >= -2**15 && value < 2**15, "SafeCast: value doesn\'t fit in 16 bits");
+        return int16(value);
+    }
+
+    /**
+     * @dev Returns the downcasted int8 from int256, reverting on
+     * overflow (when the input is less than smallest int8 or
+     * greater than largest int8).
+     *
+     * Counterpart to Solidity's `int8` operator.
+     *
+     * Requirements:
+     *
+     * - input must fit into 8 bits.
+     */
+    function toInt8(int256 value) internal pure returns (int8) {
+        require(value >= -2**7 && value < 2**7, "SafeCast: value doesn\'t fit in 8 bits");
+        return int8(value);
+    }
+
     /**
      * @dev Converts an unsigned uint256 into a signed int256.
      *

+ 57 - 0
test/utils/SafeCast.test.js

@@ -83,6 +83,63 @@ describe('SafeCast', async () => {
     });
   });
 
+  function testToInt (bits) {
+    describe(`toInt${bits}`, () => {
+      const minValue = new BN('-2').pow(new BN(bits - 1));
+      const maxValue = new BN('2').pow(new BN(bits - 1)).subn(1);
+
+      it('downcasts 0', async function () {
+        expect(await this.safeCast[`toInt${bits}`](0)).to.be.bignumber.equal('0');
+      });
+
+      it('downcasts 1', async function () {
+        expect(await this.safeCast[`toInt${bits}`](1)).to.be.bignumber.equal('1');
+      });
+
+      it('downcasts -1', async function () {
+        expect(await this.safeCast[`toInt${bits}`](-1)).to.be.bignumber.equal('-1');
+      });
+
+      it(`downcasts -2^${bits - 1} (${minValue})`, async function () {
+        expect(await this.safeCast[`toInt${bits}`](minValue)).to.be.bignumber.equal(minValue);
+      });
+
+      it(`downcasts 2^${bits - 1} - 1 (${maxValue})`, async function () {
+        expect(await this.safeCast[`toInt${bits}`](maxValue)).to.be.bignumber.equal(maxValue);
+      });
+
+      it(`reverts when downcasting -2^${bits - 1} - 1 (${minValue.subn(1)})`, async function () {
+        await expectRevert(
+          this.safeCast[`toInt${bits}`](minValue.subn(1)),
+          `SafeCast: value doesn't fit in ${bits} bits`
+        );
+      });
+
+      it(`reverts when downcasting -2^${bits - 1} - 2 (${minValue.subn(2)})`, async function () {
+        await expectRevert(
+          this.safeCast[`toInt${bits}`](minValue.subn(2)),
+          `SafeCast: value doesn't fit in ${bits} bits`
+        );
+      });
+
+      it(`reverts when downcasting 2^${bits - 1} (${maxValue.addn(1)})`, async function () {
+        await expectRevert(
+          this.safeCast[`toInt${bits}`](maxValue.addn(1)),
+          `SafeCast: value doesn't fit in ${bits} bits`
+        );
+      });
+
+      it(`reverts when downcasting 2^${bits - 1} + 1 (${maxValue.addn(2)})`, async function () {
+        await expectRevert(
+          this.safeCast[`toInt${bits}`](maxValue.addn(2)),
+          `SafeCast: value doesn't fit in ${bits} bits`
+        );
+      });
+    });
+  }
+
+  [8, 16, 32, 64, 128].forEach(bits => testToInt(bits));
+
   describe('toInt256', () => {
     const maxUint256 = new BN('2').pow(new BN(256)).subn(1);
     const maxInt256 = new BN('2').pow(new BN(255)).subn(1);