Bläddra i källkod

Unsigned conversion #2111 (#2123)

* Add signed to unsigned conversion to SafeCast

* Update SafeCast exception message

* Add test for SafeCast int to uint conversion

- Update SafeCastMock
- Add tests for SafeCast int256 to uint256

* Update SafeCast int to uint definition

Apply suggestions from code review.

Co-Authored-By: Nicolás Venturo <nicolas.venturo@gmail.com>

* Update test for SafeCast int to uint conversion

* Update SafeCast test after code review

- Change "downcasts" to "casts"
- Move test closer to its function

* Fix error in SafeCast toUint256 description

* Fix breaking error in SafeCast

* Add uint256 to int256 conversion to SafeCast

- Add function
- Add mock
- Add test

* Update SafeCast unsigned to signed conversion

- Update error in conversion to be more clear
- Update constants in test to be powers of 2 instead of shifts

* Add changelog entry

* Update SafeCast tests

- Add minus in INT256_MIN for clarity

Co-Authored-By: Nicolás Venturo <nicolas.venturo@gmail.com>

Co-authored-by: Nicolás Venturo <nicolas.venturo@gmail.com>
pepelu 5 år sedan
förälder
incheckning
4476a2d531
4 ändrade filer med 104 tillägg och 0 borttagningar
  1. 1 0
      CHANGELOG.md
  2. 9 0
      contracts/mocks/SafeCastMock.sol
  3. 24 0
      contracts/utils/SafeCast.sol
  4. 70 0
      test/utils/SafeCast.test.js

+ 1 - 0
CHANGELOG.md

@@ -4,6 +4,7 @@
 
 ### New features
  * `AccessControl`: new contract for managing permissions in a system, replacement for `Ownable` and `Roles`. ([#2112](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2112))
+ * `SafeCast`: new functions to convert to and from signed and unsigned values: `toUint256` and `toInt256`. ([#2123](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2123))
 
 ### Breaking changes
  * `ERC721`: `burn(owner, tokenId)` was removed, use `burn(owner)` instead. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125))

+ 9 - 0
contracts/mocks/SafeCastMock.sol

@@ -4,6 +4,15 @@ import "../utils/SafeCast.sol";
 
 contract SafeCastMock {
     using SafeCast for uint;
+    using SafeCast for int;
+
+    function toUint256(int a) public pure returns (uint256) {
+        return a.toUint256();
+    }
+
+    function toInt256(uint a) public pure returns (int256) {
+        return a.toInt256();
+    }
 
     function toUint128(uint a) public pure returns (uint128) {
         return a.toUint128();

+ 24 - 0
contracts/utils/SafeCast.sol

@@ -92,4 +92,28 @@ library SafeCast {
         require(value < 2**8, "SafeCast: value doesn\'t fit in 8 bits");
         return uint8(value);
     }
+
+    /**
+     * @dev Converts a signed int256 into an unsigned uint256.
+     *
+     * Requirements:
+     *
+     * - input must be greater than or equal to 0.
+     */
+    function toUint256(int256 value) internal pure returns (uint256) {
+        require(value >= 0, "SafeCast: value must be positive");
+        return uint256(value);
+    }
+
+    /**
+     * @dev Converts an unsigned uint256 into a signed int256.
+     *
+     * Requirements:
+     *
+     * - input must be less than or equal to maxInt256.
+     */
+    function toInt256(uint256 value) internal pure returns (int256) {
+        require(value < 2**255, "SafeCast: value doesn't fit in an int256");
+        return int256(value);
+    }
 }

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

@@ -43,4 +43,74 @@ describe('SafeCast', async () => {
   }
 
   [8, 16, 32, 64, 128].forEach(bits => testToUint(bits));
+
+  describe('toUint256', () => {
+    const maxInt256 = new BN('2').pow(new BN(255)).subn(1);
+    const minInt256 = new BN('2').pow(new BN(255)).neg();
+    const maxUint256 = new BN('2').pow(new BN(256)).subn(1);
+
+    it('casts 0', async function () {
+      expect(await this.safeCast.toUint256(0)).to.be.bignumber.equal('0');
+    });
+
+    it('casts 1', async function () {
+      expect(await this.safeCast.toUint256(1)).to.be.bignumber.equal('1');
+    });
+
+    it(`casts INT256_MAX (${maxInt256})`, async function () {
+      expect(await this.safeCast.toUint256(maxInt256)).to.be.bignumber.equal(maxInt256);
+    });
+
+    it('reverts when casting -1', async function () {
+      await expectRevert(
+        this.safeCast.toUint256(-1),
+        'SafeCast: value must be positive'
+      );
+    });
+
+    it(`reverts when casting INT256_MIN (${minInt256})`, async function () {
+      await expectRevert(
+        this.safeCast.toUint256(minInt256),
+        'SafeCast: value must be positive'
+      );
+    });
+
+    it(`reverts when casting UINT256_MAX (${maxUint256})`, async function () {
+      await expectRevert(
+        this.safeCast.toUint256(maxUint256),
+        'SafeCast: value must be positive'
+      );
+    });
+  });
+
+  describe('toInt256', () => {
+    const maxUint256 = new BN('2').pow(new BN(256)).subn(1);
+    const maxInt256 = new BN('2').pow(new BN(255)).subn(1);
+
+    it('casts 0', async function () {
+      expect(await this.safeCast.toInt256(0)).to.be.bignumber.equal('0');
+    });
+
+    it('casts 1', async function () {
+      expect(await this.safeCast.toInt256(1)).to.be.bignumber.equal('1');
+    });
+
+    it(`casts INT256_MAX (${maxInt256})`, async function () {
+      expect(await this.safeCast.toInt256(maxInt256)).to.be.bignumber.equal(maxInt256);
+    });
+
+    it(`reverts when casting INT256_MAX + 1 (${maxInt256.addn(1)})`, async function () {
+      await expectRevert(
+        this.safeCast.toInt256(maxInt256.addn(1)),
+        'SafeCast: value doesn\'t fit in an int256'
+      );
+    });
+
+    it(`reverts when casting UINT256_MAX (${maxUint256})`, async function () {
+      await expectRevert(
+        this.safeCast.toInt256(maxUint256),
+        'SafeCast: value doesn\'t fit in an int256'
+      );
+    });
+  });
 });