瀏覽代碼

Safe Casting Library from uint256 to uintXX (#1926)

* Include Safe Casting Library with complete and exhaustive test-suite.

* linting test file.

* Typo in SafeCast import statement

* Update test/utils/SafeCast.test.js

* Rename `castUXX` to `toUintXX` from suggestion

* Tackling the quick and easy suggestions regarding error string improvements etc.

* typo and changelog update.

* Improve SafeCast tests

* Update test/utils/SafeCast.test.js

* Update test/utils/SafeCast.test.js

* incorrect import

* add SafeCast to docs site

* Update CHANGELOG.md

* Update SafeCast.sol
Benjamin Smith 6 年之前
父節點
當前提交
2c11ed59fa
共有 5 個文件被更改,包括 174 次插入0 次删除
  1. 5 0
      CHANGELOG.md
  2. 27 0
      contracts/mocks/SafeCastMock.sol
  3. 2 0
      contracts/utils/README.adoc
  4. 95 0
      contracts/utils/SafeCast.sol
  5. 45 0
      test/utils/SafeCast.test.js

+ 5 - 0
CHANGELOG.md

@@ -1,5 +1,10 @@
 # Changelog
 
+## 2.5.0 (unreleased)
+
+### New features:
+ * `SafeCast.toUintXX`: new library for integer downcasting, which allows for safe operation on smaller types (e.g. `uint32`) when combined with `SafeMath`. ([#1926](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1926))
+
 ## 2.4.0 (unreleased)
 
 ### New features:

+ 27 - 0
contracts/mocks/SafeCastMock.sol

@@ -0,0 +1,27 @@
+pragma solidity ^0.5.0;
+
+import "../utils/SafeCast.sol";
+
+contract SafeCastMock {
+    using SafeCast for uint;
+
+    function toUint128(uint a) public pure returns (uint128) {
+        return a.toUint128();
+    }
+
+    function toUint64(uint a) public pure returns (uint64) {
+        return a.toUint64();
+    }
+
+    function toUint32(uint a) public pure returns (uint32) {
+        return a.toUint32();
+    }
+
+    function toUint16(uint a) public pure returns (uint16) {
+        return a.toUint16();
+    }
+
+    function toUint8(uint a) public pure returns (uint8) {
+        return a.toUint8();
+    }
+}

+ 2 - 0
contracts/utils/README.adoc

@@ -6,6 +6,8 @@ Miscellaneous contracts containing utility functions, often related to working w
 
 {{Address}}
 
+{{SafeCast}}
+
 {{Arrays}}
 
 {{ReentrancyGuard}}

+ 95 - 0
contracts/utils/SafeCast.sol

@@ -0,0 +1,95 @@
+pragma solidity ^0.5.0;
+
+
+/**
+ * @dev Wrappers over Solidity's uintXX casting operators with added overflow
+ * checks.
+ *
+ * Downcasting from uint256 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.
+ *
+ * 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.
+ */
+library SafeCast {
+
+    /**
+     * @dev Returns the downcasted uint128 from uint256, reverting on
+     * overflow (when the input is greater than largest uint128).
+     *
+     * Counterpart to Solidity's `uint128` operator.
+     *
+     * Requirements:
+     *
+     * - input must fit into 128 bits
+     */
+    function toUint128(uint256 value) internal pure returns (uint128) {
+        require(value < 2**128, "SafeCast: value doesn\'t fit in 128 bits");
+        return uint128(value);
+    }
+
+    /**
+     * @dev Returns the downcasted uint64 from uint256, reverting on
+     * overflow (when the input is greater than largest uint64).
+     *
+     * Counterpart to Solidity's `uint64` operator.
+     *
+     * Requirements:
+     *
+     * - input must fit into 64 bits
+     */
+    function toUint64(uint256 value) internal pure returns (uint64) {
+        require(value < 2**64, "SafeCast: value doesn\'t fit in 64 bits");
+        return uint64(value);
+    }
+
+    /**
+     * @dev Returns the downcasted uint32 from uint256, reverting on
+     * overflow (when the input is greater than largest uint32).
+     *
+     * Counterpart to Solidity's `uint32` operator.
+     *
+     * Requirements:
+     *
+     * - input must fit into 32 bits
+     */
+    function toUint32(uint256 value) internal pure returns (uint32) {
+        require(value < 2**32, "SafeCast: value doesn\'t fit in 32 bits");
+        return uint32(value);
+    }
+
+    /**
+     * @dev Returns the downcasted uint16 from uint256, reverting on
+     * overflow (when the input is greater than largest uint16).
+     *
+     * Counterpart to Solidity's `uint16` operator.
+     *
+     * Requirements:
+     *
+     * - input must fit into 16 bits
+     */
+    function toUint16(uint256 value) internal pure returns (uint16) {
+        require(value < 2**16, "SafeCast: value doesn\'t fit in 16 bits");
+        return uint16(value);
+    }
+
+    /**
+     * @dev Returns the downcasted uint8 from uint256, reverting on
+     * overflow (when the input is greater than largest uint8).
+     *
+     * Counterpart to Solidity's `uint8` operator.
+     *
+     * Requirements:
+     *
+     * - input must fit into 8 bits.
+     */
+    function toUint8(uint256 value) internal pure returns (uint8) {
+        require(value < 2**8, "SafeCast: value doesn\'t fit in 8 bits");
+        return uint8(value);
+    }
+}

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

@@ -0,0 +1,45 @@
+const { BN, expectRevert } = require('@openzeppelin/test-helpers');
+
+const { expect } = require('chai');
+
+const SafeCastMock = artifacts.require('SafeCastMock');
+
+contract('SafeCast', async () => {
+  beforeEach(async function () {
+    this.safeCast = await SafeCastMock.new();
+  });
+
+  function testToUint (bits) {
+    describe(`toUint${bits}`, () => {
+      const maxValue = new BN('2').pow(new BN(bits)).subn(1);
+
+      it('downcasts 0', async function () {
+        expect(await this.safeCast[`toUint${bits}`](0)).to.be.bignumber.equal('0');
+      });
+
+      it('downcasts 1', async function () {
+        expect(await this.safeCast[`toUint${bits}`](1)).to.be.bignumber.equal('1');
+      });
+
+      it(`downcasts 2^${bits} - 1 (${maxValue})`, async function () {
+        expect(await this.safeCast[`toUint${bits}`](maxValue)).to.be.bignumber.equal(maxValue);
+      });
+
+      it(`reverts when downcasting 2^${bits} (${maxValue.addn(1)})`, async function () {
+        await expectRevert(
+          this.safeCast[`toUint${bits}`](maxValue.addn(1)),
+          `SafeCast: value doesn't fit in ${bits} bits`
+        );
+      });
+
+      it(`reverts when downcasting 2^${bits} + 1 (${maxValue.addn(2)})`, async function () {
+        await expectRevert(
+          this.safeCast[`toUint${bits}`](maxValue.addn(2)),
+          `SafeCast: value doesn't fit in ${bits} bits`
+        );
+      });
+    });
+  }
+
+  [8, 16, 32, 64, 128].forEach(bits => testToUint(bits));
+});