diff --git a/CHANGELOG.md b/CHANGELOG.md index ac73a954e..2ffc3dbd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2.5.0 (unreleased) + +### New features: + * `SafeCast.toUintXX`: new library for integer downcasting, which allows for safe operation on smaller types (e.g. `uint32`) when combined with `SafeMath`. ([#1926](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1926)) + ## 2.4.0 (unreleased) ### New features: diff --git a/contracts/mocks/SafeCastMock.sol b/contracts/mocks/SafeCastMock.sol new file mode 100644 index 000000000..b6dd779a5 --- /dev/null +++ b/contracts/mocks/SafeCastMock.sol @@ -0,0 +1,27 @@ +pragma solidity ^0.5.0; + +import "../utils/SafeCast.sol"; + +contract SafeCastMock { + using SafeCast for uint; + + function toUint128(uint a) public pure returns (uint128) { + return a.toUint128(); + } + + function toUint64(uint a) public pure returns (uint64) { + return a.toUint64(); + } + + function toUint32(uint a) public pure returns (uint32) { + return a.toUint32(); + } + + function toUint16(uint a) public pure returns (uint16) { + return a.toUint16(); + } + + function toUint8(uint a) public pure returns (uint8) { + return a.toUint8(); + } +} diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 335144a7a..229080211 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -6,6 +6,8 @@ Miscellaneous contracts containing utility functions, often related to working w {{Address}} +{{SafeCast}} + {{Arrays}} {{ReentrancyGuard}} diff --git a/contracts/utils/SafeCast.sol b/contracts/utils/SafeCast.sol new file mode 100644 index 000000000..349825624 --- /dev/null +++ b/contracts/utils/SafeCast.sol @@ -0,0 +1,95 @@ +pragma solidity ^0.5.0; + + +/** + * @dev Wrappers over Solidity's uintXX casting operators with added overflow + * checks. + * + * Downcasting from uint256 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. + */ +library SafeCast { + + /** + * @dev Returns the downcasted uint128 from uint256, reverting on + * overflow (when the input is greater than largest uint128). + * + * Counterpart to Solidity's `uint128` operator. + * + * Requirements: + * + * - input must fit into 128 bits + */ + function toUint128(uint256 value) internal pure returns (uint128) { + require(value < 2**128, "SafeCast: value doesn\'t fit in 128 bits"); + return uint128(value); + } + + /** + * @dev Returns the downcasted uint64 from uint256, reverting on + * overflow (when the input is greater than largest uint64). + * + * Counterpart to Solidity's `uint64` operator. + * + * Requirements: + * + * - input must fit into 64 bits + */ + function toUint64(uint256 value) internal pure returns (uint64) { + require(value < 2**64, "SafeCast: value doesn\'t fit in 64 bits"); + return uint64(value); + } + + /** + * @dev Returns the downcasted uint32 from uint256, reverting on + * overflow (when the input is greater than largest uint32). + * + * Counterpart to Solidity's `uint32` operator. + * + * Requirements: + * + * - input must fit into 32 bits + */ + function toUint32(uint256 value) internal pure returns (uint32) { + require(value < 2**32, "SafeCast: value doesn\'t fit in 32 bits"); + return uint32(value); + } + + /** + * @dev Returns the downcasted uint16 from uint256, reverting on + * overflow (when the input is greater than largest uint16). + * + * Counterpart to Solidity's `uint16` operator. + * + * Requirements: + * + * - input must fit into 16 bits + */ + function toUint16(uint256 value) internal pure returns (uint16) { + require(value < 2**16, "SafeCast: value doesn\'t fit in 16 bits"); + return uint16(value); + } + + /** + * @dev Returns the downcasted uint8 from uint256, reverting on + * overflow (when the input is greater than largest uint8). + * + * Counterpart to Solidity's `uint8` operator. + * + * Requirements: + * + * - input must fit into 8 bits. + */ + function toUint8(uint256 value) internal pure returns (uint8) { + require(value < 2**8, "SafeCast: value doesn\'t fit in 8 bits"); + return uint8(value); + } +} diff --git a/test/utils/SafeCast.test.js b/test/utils/SafeCast.test.js new file mode 100644 index 000000000..193ef3864 --- /dev/null +++ b/test/utils/SafeCast.test.js @@ -0,0 +1,45 @@ +const { BN, expectRevert } = require('@openzeppelin/test-helpers'); + +const { expect } = require('chai'); + +const SafeCastMock = artifacts.require('SafeCastMock'); + +contract('SafeCast', async () => { + beforeEach(async function () { + this.safeCast = await SafeCastMock.new(); + }); + + function testToUint (bits) { + describe(`toUint${bits}`, () => { + const maxValue = new BN('2').pow(new BN(bits)).subn(1); + + it('downcasts 0', async function () { + expect(await this.safeCast[`toUint${bits}`](0)).to.be.bignumber.equal('0'); + }); + + it('downcasts 1', async function () { + expect(await this.safeCast[`toUint${bits}`](1)).to.be.bignumber.equal('1'); + }); + + it(`downcasts 2^${bits} - 1 (${maxValue})`, async function () { + expect(await this.safeCast[`toUint${bits}`](maxValue)).to.be.bignumber.equal(maxValue); + }); + + it(`reverts when downcasting 2^${bits} (${maxValue.addn(1)})`, async function () { + await expectRevert( + this.safeCast[`toUint${bits}`](maxValue.addn(1)), + `SafeCast: value doesn't fit in ${bits} bits` + ); + }); + + it(`reverts when downcasting 2^${bits} + 1 (${maxValue.addn(2)})`, async function () { + await expectRevert( + this.safeCast[`toUint${bits}`](maxValue.addn(2)), + `SafeCast: value doesn't fit in ${bits} bits` + ); + }); + }); + } + + [8, 16, 32, 64, 128].forEach(bits => testToUint(bits)); +});