Skip to content

Commit

Permalink
feat: Adding SafeCast variants for signed integers
Browse files Browse the repository at this point in the history
  • Loading branch information
julianmrodri committed May 23, 2020
1 parent e2b97d6 commit 9c07599
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 5 deletions.
22 changes: 21 additions & 1 deletion contracts/mocks/SafeCastMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
88 changes: 84 additions & 4 deletions contracts/utils/SafeCast.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ 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.
*
* 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 {

Expand Down Expand Up @@ -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.
*
Expand Down
57 changes: 57 additions & 0 deletions test/utils/SafeCast.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 9c07599

Please sign in to comment.