From 9c07599b01df35ce14fe01184ff7a0bcf3cb0198 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Sat, 23 May 2020 03:20:02 -0300 Subject: [PATCH 1/4] feat: Adding SafeCast variants for signed integers --- contracts/mocks/SafeCastMock.sol | 22 +++++++- contracts/utils/SafeCast.sol | 88 ++++++++++++++++++++++++++++++-- test/utils/SafeCast.test.js | 57 +++++++++++++++++++++ 3 files changed, 162 insertions(+), 5 deletions(-) diff --git a/contracts/mocks/SafeCastMock.sol b/contracts/mocks/SafeCastMock.sol index e9af21321b1..a932764864a 100644 --- a/contracts/mocks/SafeCastMock.sol +++ b/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(); + } +} \ No newline at end of file diff --git a/contracts/utils/SafeCast.sol b/contracts/utils/SafeCast.sol index 6ff76b21f97..26d0700100b 100644 --- a/contracts/utils/SafeCast.sol +++ b/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/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. * diff --git a/test/utils/SafeCast.test.js b/test/utils/SafeCast.test.js index 37bec0f7525..818db06eab2 100644 --- a/test/utils/SafeCast.test.js +++ b/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); From bc01f73d74b0af74028f11afda864f7a209ee91c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Tue, 26 May 2020 18:29:26 -0300 Subject: [PATCH 2/4] Add newline at EOF --- contracts/mocks/SafeCastMock.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mocks/SafeCastMock.sol b/contracts/mocks/SafeCastMock.sol index a932764864a..cb953213d22 100644 --- a/contracts/mocks/SafeCastMock.sol +++ b/contracts/mocks/SafeCastMock.sol @@ -55,4 +55,4 @@ contract SafeCastMock { function toInt8(int a) public pure returns (int8) { return a.toInt8(); } -} \ No newline at end of file +} From b751554400bcc746b858927caffe1bc7872d2868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Tue, 26 May 2020 18:38:45 -0300 Subject: [PATCH 3/4] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c2f1f2a52a..0d655c4752e 100644 --- a/CHANGELOG.md +++ b/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)) From ffec5c2005f7ad76db2637520bdd0371a32fdbea Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Tue, 26 May 2020 19:10:56 -0300 Subject: [PATCH 4/4] Update contracts/utils/SafeCast.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nicolás Venturo --- contracts/utils/SafeCast.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/SafeCast.sol b/contracts/utils/SafeCast.sol index 26d0700100b..0509ba59ca9 100644 --- a/contracts/utils/SafeCast.sol +++ b/contracts/utils/SafeCast.sol @@ -16,7 +16,7 @@ pragma solidity ^0.6.0; * class of bugs, so it's recommended to use it always. * * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing - * all math on `uint256/int256` and then downcasting. + * all math on `uint256` and `int256` and then downcasting. */ library SafeCast {