diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d94c23c5..375c6c8e4 100644 --- a/CHANGELOG.md +++ b/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)) diff --git a/contracts/mocks/SafeCastMock.sol b/contracts/mocks/SafeCastMock.sol index daa2fe6de..7b54a2623 100644 --- a/contracts/mocks/SafeCastMock.sol +++ b/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(); diff --git a/contracts/utils/SafeCast.sol b/contracts/utils/SafeCast.sol index d2a26775c..e3ed6d426 100644 --- a/contracts/utils/SafeCast.sol +++ b/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); + } } diff --git a/test/utils/SafeCast.test.js b/test/utils/SafeCast.test.js index bc32a2df5..37bec0f75 100644 --- a/test/utils/SafeCast.test.js +++ b/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' + ); + }); + }); });