From 895ab5bf3b47fa939b39626191ddaf29918ea889 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] feat: Adding SafeCast variants for signed integers --- contracts/mocks/SafeCastMock.sol | 22 ++++++++- contracts/utils/SafeCast.sol | 84 +++++++++++++++++++++++++++++++- test/utils/SafeCast.test.js | 57 ++++++++++++++++++++++ 3 files changed, 160 insertions(+), 3 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..e6c0b664026 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. @@ -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);