diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index db6abf6a25b..3a345c05000 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,6 +30,7 @@ jobs: FORCE_COLOR: 1 ENABLE_GAS_REPORT: true - run: npm run test:inheritance + - run: npm run test:generation - name: Print gas report run: cat gas-report.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 495fbddd020..71182a1785c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * `ERC20FlashMint`: Add customizable flash fee receiver. ([#3327](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3327)) * `Strings`: add a new overloaded function `toHexString` that converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. ([#3403](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3403)) * `EnumerableMap`: add new `UintToUintMap` map type. ([#3338](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3338)) + * `SafeCast`: add support for many more types, using procedural code generation. ([#3245](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3245)) ## 4.6.0 (2022-04-26) diff --git a/contracts/mocks/SafeCastMock.sol b/contracts/mocks/SafeCastMock.sol index d1f1aaaba31..806ce12740f 100644 --- a/contracts/mocks/SafeCastMock.sol +++ b/contracts/mocks/SafeCastMock.sol @@ -12,26 +12,122 @@ contract SafeCastMock { return a.toUint256(); } + function toUint248(uint256 a) public pure returns (uint248) { + return a.toUint248(); + } + + function toUint240(uint256 a) public pure returns (uint240) { + return a.toUint240(); + } + + function toUint232(uint256 a) public pure returns (uint232) { + return a.toUint232(); + } + function toUint224(uint256 a) public pure returns (uint224) { return a.toUint224(); } + function toUint216(uint256 a) public pure returns (uint216) { + return a.toUint216(); + } + + function toUint208(uint256 a) public pure returns (uint208) { + return a.toUint208(); + } + + function toUint200(uint256 a) public pure returns (uint200) { + return a.toUint200(); + } + + function toUint192(uint256 a) public pure returns (uint192) { + return a.toUint192(); + } + + function toUint184(uint256 a) public pure returns (uint184) { + return a.toUint184(); + } + + function toUint176(uint256 a) public pure returns (uint176) { + return a.toUint176(); + } + + function toUint168(uint256 a) public pure returns (uint168) { + return a.toUint168(); + } + + function toUint160(uint256 a) public pure returns (uint160) { + return a.toUint160(); + } + + function toUint152(uint256 a) public pure returns (uint152) { + return a.toUint152(); + } + + function toUint144(uint256 a) public pure returns (uint144) { + return a.toUint144(); + } + + function toUint136(uint256 a) public pure returns (uint136) { + return a.toUint136(); + } + function toUint128(uint256 a) public pure returns (uint128) { return a.toUint128(); } + function toUint120(uint256 a) public pure returns (uint120) { + return a.toUint120(); + } + + function toUint112(uint256 a) public pure returns (uint112) { + return a.toUint112(); + } + + function toUint104(uint256 a) public pure returns (uint104) { + return a.toUint104(); + } + function toUint96(uint256 a) public pure returns (uint96) { return a.toUint96(); } + function toUint88(uint256 a) public pure returns (uint88) { + return a.toUint88(); + } + + function toUint80(uint256 a) public pure returns (uint80) { + return a.toUint80(); + } + + function toUint72(uint256 a) public pure returns (uint72) { + return a.toUint72(); + } + function toUint64(uint256 a) public pure returns (uint64) { return a.toUint64(); } + function toUint56(uint256 a) public pure returns (uint56) { + return a.toUint56(); + } + + function toUint48(uint256 a) public pure returns (uint48) { + return a.toUint48(); + } + + function toUint40(uint256 a) public pure returns (uint40) { + return a.toUint40(); + } + function toUint32(uint256 a) public pure returns (uint32) { return a.toUint32(); } + function toUint24(uint256 a) public pure returns (uint24) { + return a.toUint24(); + } + function toUint16(uint256 a) public pure returns (uint16) { return a.toUint16(); } @@ -44,18 +140,122 @@ contract SafeCastMock { return a.toInt256(); } + function toInt248(int256 a) public pure returns (int248) { + return a.toInt248(); + } + + function toInt240(int256 a) public pure returns (int240) { + return a.toInt240(); + } + + function toInt232(int256 a) public pure returns (int232) { + return a.toInt232(); + } + + function toInt224(int256 a) public pure returns (int224) { + return a.toInt224(); + } + + function toInt216(int256 a) public pure returns (int216) { + return a.toInt216(); + } + + function toInt208(int256 a) public pure returns (int208) { + return a.toInt208(); + } + + function toInt200(int256 a) public pure returns (int200) { + return a.toInt200(); + } + + function toInt192(int256 a) public pure returns (int192) { + return a.toInt192(); + } + + function toInt184(int256 a) public pure returns (int184) { + return a.toInt184(); + } + + function toInt176(int256 a) public pure returns (int176) { + return a.toInt176(); + } + + function toInt168(int256 a) public pure returns (int168) { + return a.toInt168(); + } + + function toInt160(int256 a) public pure returns (int160) { + return a.toInt160(); + } + + function toInt152(int256 a) public pure returns (int152) { + return a.toInt152(); + } + + function toInt144(int256 a) public pure returns (int144) { + return a.toInt144(); + } + + function toInt136(int256 a) public pure returns (int136) { + return a.toInt136(); + } + function toInt128(int256 a) public pure returns (int128) { return a.toInt128(); } + function toInt120(int256 a) public pure returns (int120) { + return a.toInt120(); + } + + function toInt112(int256 a) public pure returns (int112) { + return a.toInt112(); + } + + function toInt104(int256 a) public pure returns (int104) { + return a.toInt104(); + } + + function toInt96(int256 a) public pure returns (int96) { + return a.toInt96(); + } + + function toInt88(int256 a) public pure returns (int88) { + return a.toInt88(); + } + + function toInt80(int256 a) public pure returns (int80) { + return a.toInt80(); + } + + function toInt72(int256 a) public pure returns (int72) { + return a.toInt72(); + } + function toInt64(int256 a) public pure returns (int64) { return a.toInt64(); } + function toInt56(int256 a) public pure returns (int56) { + return a.toInt56(); + } + + function toInt48(int256 a) public pure returns (int48) { + return a.toInt48(); + } + + function toInt40(int256 a) public pure returns (int40) { + return a.toInt40(); + } + function toInt32(int256 a) public pure returns (int32) { return a.toInt32(); } + function toInt24(int256 a) public pure returns (int24) { + return a.toInt24(); + } + function toInt16(int256 a) public pure returns (int16) { return a.toInt16(); } diff --git a/contracts/utils/math/SafeCast.sol b/contracts/utils/math/SafeCast.sol index 3cd64735789..2857638bdcf 100644 --- a/contracts/utils/math/SafeCast.sol +++ b/contracts/utils/math/SafeCast.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol) +// OpenZeppelin Contracts (last updated v4.4.1) (utils/math/SafeCast.sol) pragma solidity ^0.8.0; @@ -19,6 +19,57 @@ pragma solidity ^0.8.0; * all math on `uint256` and `int256` and then downcasting. */ library SafeCast { + /** + * @dev Returns the downcasted uint248 from uint256, reverting on + * overflow (when the input is greater than largest uint248). + * + * Counterpart to Solidity's `uint248` operator. + * + * Requirements: + * + * - input must fit into 248 bits + * + * _Available since v4.7._ + */ + function toUint248(uint256 value) internal pure returns (uint248) { + require(value <= type(uint248).max, "SafeCast: value doesn't fit in 248 bits"); + return uint248(value); + } + + /** + * @dev Returns the downcasted uint240 from uint256, reverting on + * overflow (when the input is greater than largest uint240). + * + * Counterpart to Solidity's `uint240` operator. + * + * Requirements: + * + * - input must fit into 240 bits + * + * _Available since v4.7._ + */ + function toUint240(uint256 value) internal pure returns (uint240) { + require(value <= type(uint240).max, "SafeCast: value doesn't fit in 240 bits"); + return uint240(value); + } + + /** + * @dev Returns the downcasted uint232 from uint256, reverting on + * overflow (when the input is greater than largest uint232). + * + * Counterpart to Solidity's `uint232` operator. + * + * Requirements: + * + * - input must fit into 232 bits + * + * _Available since v4.7._ + */ + function toUint232(uint256 value) internal pure returns (uint232) { + require(value <= type(uint232).max, "SafeCast: value doesn't fit in 232 bits"); + return uint232(value); + } + /** * @dev Returns the downcasted uint224 from uint256, reverting on * overflow (when the input is greater than largest uint224). @@ -28,12 +79,201 @@ library SafeCast { * Requirements: * * - input must fit into 224 bits + * + * _Available since v4.2._ */ function toUint224(uint256 value) internal pure returns (uint224) { require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits"); return uint224(value); } + /** + * @dev Returns the downcasted uint216 from uint256, reverting on + * overflow (when the input is greater than largest uint216). + * + * Counterpart to Solidity's `uint216` operator. + * + * Requirements: + * + * - input must fit into 216 bits + * + * _Available since v4.7._ + */ + function toUint216(uint256 value) internal pure returns (uint216) { + require(value <= type(uint216).max, "SafeCast: value doesn't fit in 216 bits"); + return uint216(value); + } + + /** + * @dev Returns the downcasted uint208 from uint256, reverting on + * overflow (when the input is greater than largest uint208). + * + * Counterpart to Solidity's `uint208` operator. + * + * Requirements: + * + * - input must fit into 208 bits + * + * _Available since v4.7._ + */ + function toUint208(uint256 value) internal pure returns (uint208) { + require(value <= type(uint208).max, "SafeCast: value doesn't fit in 208 bits"); + return uint208(value); + } + + /** + * @dev Returns the downcasted uint200 from uint256, reverting on + * overflow (when the input is greater than largest uint200). + * + * Counterpart to Solidity's `uint200` operator. + * + * Requirements: + * + * - input must fit into 200 bits + * + * _Available since v4.7._ + */ + function toUint200(uint256 value) internal pure returns (uint200) { + require(value <= type(uint200).max, "SafeCast: value doesn't fit in 200 bits"); + return uint200(value); + } + + /** + * @dev Returns the downcasted uint192 from uint256, reverting on + * overflow (when the input is greater than largest uint192). + * + * Counterpart to Solidity's `uint192` operator. + * + * Requirements: + * + * - input must fit into 192 bits + * + * _Available since v4.7._ + */ + function toUint192(uint256 value) internal pure returns (uint192) { + require(value <= type(uint192).max, "SafeCast: value doesn't fit in 192 bits"); + return uint192(value); + } + + /** + * @dev Returns the downcasted uint184 from uint256, reverting on + * overflow (when the input is greater than largest uint184). + * + * Counterpart to Solidity's `uint184` operator. + * + * Requirements: + * + * - input must fit into 184 bits + * + * _Available since v4.7._ + */ + function toUint184(uint256 value) internal pure returns (uint184) { + require(value <= type(uint184).max, "SafeCast: value doesn't fit in 184 bits"); + return uint184(value); + } + + /** + * @dev Returns the downcasted uint176 from uint256, reverting on + * overflow (when the input is greater than largest uint176). + * + * Counterpart to Solidity's `uint176` operator. + * + * Requirements: + * + * - input must fit into 176 bits + * + * _Available since v4.7._ + */ + function toUint176(uint256 value) internal pure returns (uint176) { + require(value <= type(uint176).max, "SafeCast: value doesn't fit in 176 bits"); + return uint176(value); + } + + /** + * @dev Returns the downcasted uint168 from uint256, reverting on + * overflow (when the input is greater than largest uint168). + * + * Counterpart to Solidity's `uint168` operator. + * + * Requirements: + * + * - input must fit into 168 bits + * + * _Available since v4.7._ + */ + function toUint168(uint256 value) internal pure returns (uint168) { + require(value <= type(uint168).max, "SafeCast: value doesn't fit in 168 bits"); + return uint168(value); + } + + /** + * @dev Returns the downcasted uint160 from uint256, reverting on + * overflow (when the input is greater than largest uint160). + * + * Counterpart to Solidity's `uint160` operator. + * + * Requirements: + * + * - input must fit into 160 bits + * + * _Available since v4.7._ + */ + function toUint160(uint256 value) internal pure returns (uint160) { + require(value <= type(uint160).max, "SafeCast: value doesn't fit in 160 bits"); + return uint160(value); + } + + /** + * @dev Returns the downcasted uint152 from uint256, reverting on + * overflow (when the input is greater than largest uint152). + * + * Counterpart to Solidity's `uint152` operator. + * + * Requirements: + * + * - input must fit into 152 bits + * + * _Available since v4.7._ + */ + function toUint152(uint256 value) internal pure returns (uint152) { + require(value <= type(uint152).max, "SafeCast: value doesn't fit in 152 bits"); + return uint152(value); + } + + /** + * @dev Returns the downcasted uint144 from uint256, reverting on + * overflow (when the input is greater than largest uint144). + * + * Counterpart to Solidity's `uint144` operator. + * + * Requirements: + * + * - input must fit into 144 bits + * + * _Available since v4.7._ + */ + function toUint144(uint256 value) internal pure returns (uint144) { + require(value <= type(uint144).max, "SafeCast: value doesn't fit in 144 bits"); + return uint144(value); + } + + /** + * @dev Returns the downcasted uint136 from uint256, reverting on + * overflow (when the input is greater than largest uint136). + * + * Counterpart to Solidity's `uint136` operator. + * + * Requirements: + * + * - input must fit into 136 bits + * + * _Available since v4.7._ + */ + function toUint136(uint256 value) internal pure returns (uint136) { + require(value <= type(uint136).max, "SafeCast: value doesn't fit in 136 bits"); + return uint136(value); + } + /** * @dev Returns the downcasted uint128 from uint256, reverting on * overflow (when the input is greater than largest uint128). @@ -43,12 +283,65 @@ library SafeCast { * Requirements: * * - input must fit into 128 bits + * + * _Available since v2.5._ */ function toUint128(uint256 value) internal pure returns (uint128) { require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits"); return uint128(value); } + /** + * @dev Returns the downcasted uint120 from uint256, reverting on + * overflow (when the input is greater than largest uint120). + * + * Counterpart to Solidity's `uint120` operator. + * + * Requirements: + * + * - input must fit into 120 bits + * + * _Available since v4.7._ + */ + function toUint120(uint256 value) internal pure returns (uint120) { + require(value <= type(uint120).max, "SafeCast: value doesn't fit in 120 bits"); + return uint120(value); + } + + /** + * @dev Returns the downcasted uint112 from uint256, reverting on + * overflow (when the input is greater than largest uint112). + * + * Counterpart to Solidity's `uint112` operator. + * + * Requirements: + * + * - input must fit into 112 bits + * + * _Available since v4.7._ + */ + function toUint112(uint256 value) internal pure returns (uint112) { + require(value <= type(uint112).max, "SafeCast: value doesn't fit in 112 bits"); + return uint112(value); + } + + /** + * @dev Returns the downcasted uint104 from uint256, reverting on + * overflow (when the input is greater than largest uint104). + * + * Counterpart to Solidity's `uint104` operator. + * + * Requirements: + * + * - input must fit into 104 bits + * + * _Available since v4.7._ + */ + function toUint104(uint256 value) internal pure returns (uint104) { + require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits"); + return uint104(value); + } + /** * @dev Returns the downcasted uint96 from uint256, reverting on * overflow (when the input is greater than largest uint96). @@ -58,12 +351,65 @@ library SafeCast { * Requirements: * * - input must fit into 96 bits + * + * _Available since v4.2._ */ function toUint96(uint256 value) internal pure returns (uint96) { require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits"); return uint96(value); } + /** + * @dev Returns the downcasted uint88 from uint256, reverting on + * overflow (when the input is greater than largest uint88). + * + * Counterpart to Solidity's `uint88` operator. + * + * Requirements: + * + * - input must fit into 88 bits + * + * _Available since v4.7._ + */ + function toUint88(uint256 value) internal pure returns (uint88) { + require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits"); + return uint88(value); + } + + /** + * @dev Returns the downcasted uint80 from uint256, reverting on + * overflow (when the input is greater than largest uint80). + * + * Counterpart to Solidity's `uint80` operator. + * + * Requirements: + * + * - input must fit into 80 bits + * + * _Available since v4.7._ + */ + function toUint80(uint256 value) internal pure returns (uint80) { + require(value <= type(uint80).max, "SafeCast: value doesn't fit in 80 bits"); + return uint80(value); + } + + /** + * @dev Returns the downcasted uint72 from uint256, reverting on + * overflow (when the input is greater than largest uint72). + * + * Counterpart to Solidity's `uint72` operator. + * + * Requirements: + * + * - input must fit into 72 bits + * + * _Available since v4.7._ + */ + function toUint72(uint256 value) internal pure returns (uint72) { + require(value <= type(uint72).max, "SafeCast: value doesn't fit in 72 bits"); + return uint72(value); + } + /** * @dev Returns the downcasted uint64 from uint256, reverting on * overflow (when the input is greater than largest uint64). @@ -73,12 +419,65 @@ library SafeCast { * Requirements: * * - input must fit into 64 bits + * + * _Available since v2.5._ */ function toUint64(uint256 value) internal pure returns (uint64) { require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits"); return uint64(value); } + /** + * @dev Returns the downcasted uint56 from uint256, reverting on + * overflow (when the input is greater than largest uint56). + * + * Counterpart to Solidity's `uint56` operator. + * + * Requirements: + * + * - input must fit into 56 bits + * + * _Available since v4.7._ + */ + function toUint56(uint256 value) internal pure returns (uint56) { + require(value <= type(uint56).max, "SafeCast: value doesn't fit in 56 bits"); + return uint56(value); + } + + /** + * @dev Returns the downcasted uint48 from uint256, reverting on + * overflow (when the input is greater than largest uint48). + * + * Counterpart to Solidity's `uint48` operator. + * + * Requirements: + * + * - input must fit into 48 bits + * + * _Available since v4.7._ + */ + function toUint48(uint256 value) internal pure returns (uint48) { + require(value <= type(uint48).max, "SafeCast: value doesn't fit in 48 bits"); + return uint48(value); + } + + /** + * @dev Returns the downcasted uint40 from uint256, reverting on + * overflow (when the input is greater than largest uint40). + * + * Counterpart to Solidity's `uint40` operator. + * + * Requirements: + * + * - input must fit into 40 bits + * + * _Available since v4.7._ + */ + function toUint40(uint256 value) internal pure returns (uint40) { + require(value <= type(uint40).max, "SafeCast: value doesn't fit in 40 bits"); + return uint40(value); + } + /** * @dev Returns the downcasted uint32 from uint256, reverting on * overflow (when the input is greater than largest uint32). @@ -88,12 +487,31 @@ library SafeCast { * Requirements: * * - input must fit into 32 bits + * + * _Available since v2.5._ */ function toUint32(uint256 value) internal pure returns (uint32) { require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits"); return uint32(value); } + /** + * @dev Returns the downcasted uint24 from uint256, reverting on + * overflow (when the input is greater than largest uint24). + * + * Counterpart to Solidity's `uint24` operator. + * + * Requirements: + * + * - input must fit into 24 bits + * + * _Available since v4.7._ + */ + function toUint24(uint256 value) internal pure returns (uint24) { + require(value <= type(uint24).max, "SafeCast: value doesn't fit in 24 bits"); + return uint24(value); + } + /** * @dev Returns the downcasted uint16 from uint256, reverting on * overflow (when the input is greater than largest uint16). @@ -103,6 +521,8 @@ library SafeCast { * Requirements: * * - input must fit into 16 bits + * + * _Available since v2.5._ */ function toUint16(uint256 value) internal pure returns (uint16) { require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits"); @@ -117,7 +537,9 @@ library SafeCast { * * Requirements: * - * - input must fit into 8 bits. + * - input must fit into 8 bits + * + * _Available since v2.5._ */ function toUint8(uint256 value) internal pure returns (uint8) { require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits"); @@ -130,12 +552,284 @@ library SafeCast { * Requirements: * * - input must be greater than or equal to 0. + * + * _Available since v3.0._ */ function toUint256(int256 value) internal pure returns (uint256) { require(value >= 0, "SafeCast: value must be positive"); return uint256(value); } + /** + * @dev Returns the downcasted int248 from int256, reverting on + * overflow (when the input is less than smallest int248 or + * greater than largest int248). + * + * Counterpart to Solidity's `int248` operator. + * + * Requirements: + * + * - input must fit into 248 bits + * + * _Available since v4.7._ + */ + function toInt248(int256 value) internal pure returns (int248) { + require(value >= type(int248).min && value <= type(int248).max, "SafeCast: value doesn't fit in 248 bits"); + return int248(value); + } + + /** + * @dev Returns the downcasted int240 from int256, reverting on + * overflow (when the input is less than smallest int240 or + * greater than largest int240). + * + * Counterpart to Solidity's `int240` operator. + * + * Requirements: + * + * - input must fit into 240 bits + * + * _Available since v4.7._ + */ + function toInt240(int256 value) internal pure returns (int240) { + require(value >= type(int240).min && value <= type(int240).max, "SafeCast: value doesn't fit in 240 bits"); + return int240(value); + } + + /** + * @dev Returns the downcasted int232 from int256, reverting on + * overflow (when the input is less than smallest int232 or + * greater than largest int232). + * + * Counterpart to Solidity's `int232` operator. + * + * Requirements: + * + * - input must fit into 232 bits + * + * _Available since v4.7._ + */ + function toInt232(int256 value) internal pure returns (int232) { + require(value >= type(int232).min && value <= type(int232).max, "SafeCast: value doesn't fit in 232 bits"); + return int232(value); + } + + /** + * @dev Returns the downcasted int224 from int256, reverting on + * overflow (when the input is less than smallest int224 or + * greater than largest int224). + * + * Counterpart to Solidity's `int224` operator. + * + * Requirements: + * + * - input must fit into 224 bits + * + * _Available since v4.7._ + */ + function toInt224(int256 value) internal pure returns (int224) { + require(value >= type(int224).min && value <= type(int224).max, "SafeCast: value doesn't fit in 224 bits"); + return int224(value); + } + + /** + * @dev Returns the downcasted int216 from int256, reverting on + * overflow (when the input is less than smallest int216 or + * greater than largest int216). + * + * Counterpart to Solidity's `int216` operator. + * + * Requirements: + * + * - input must fit into 216 bits + * + * _Available since v4.7._ + */ + function toInt216(int256 value) internal pure returns (int216) { + require(value >= type(int216).min && value <= type(int216).max, "SafeCast: value doesn't fit in 216 bits"); + return int216(value); + } + + /** + * @dev Returns the downcasted int208 from int256, reverting on + * overflow (when the input is less than smallest int208 or + * greater than largest int208). + * + * Counterpart to Solidity's `int208` operator. + * + * Requirements: + * + * - input must fit into 208 bits + * + * _Available since v4.7._ + */ + function toInt208(int256 value) internal pure returns (int208) { + require(value >= type(int208).min && value <= type(int208).max, "SafeCast: value doesn't fit in 208 bits"); + return int208(value); + } + + /** + * @dev Returns the downcasted int200 from int256, reverting on + * overflow (when the input is less than smallest int200 or + * greater than largest int200). + * + * Counterpart to Solidity's `int200` operator. + * + * Requirements: + * + * - input must fit into 200 bits + * + * _Available since v4.7._ + */ + function toInt200(int256 value) internal pure returns (int200) { + require(value >= type(int200).min && value <= type(int200).max, "SafeCast: value doesn't fit in 200 bits"); + return int200(value); + } + + /** + * @dev Returns the downcasted int192 from int256, reverting on + * overflow (when the input is less than smallest int192 or + * greater than largest int192). + * + * Counterpart to Solidity's `int192` operator. + * + * Requirements: + * + * - input must fit into 192 bits + * + * _Available since v4.7._ + */ + function toInt192(int256 value) internal pure returns (int192) { + require(value >= type(int192).min && value <= type(int192).max, "SafeCast: value doesn't fit in 192 bits"); + return int192(value); + } + + /** + * @dev Returns the downcasted int184 from int256, reverting on + * overflow (when the input is less than smallest int184 or + * greater than largest int184). + * + * Counterpart to Solidity's `int184` operator. + * + * Requirements: + * + * - input must fit into 184 bits + * + * _Available since v4.7._ + */ + function toInt184(int256 value) internal pure returns (int184) { + require(value >= type(int184).min && value <= type(int184).max, "SafeCast: value doesn't fit in 184 bits"); + return int184(value); + } + + /** + * @dev Returns the downcasted int176 from int256, reverting on + * overflow (when the input is less than smallest int176 or + * greater than largest int176). + * + * Counterpart to Solidity's `int176` operator. + * + * Requirements: + * + * - input must fit into 176 bits + * + * _Available since v4.7._ + */ + function toInt176(int256 value) internal pure returns (int176) { + require(value >= type(int176).min && value <= type(int176).max, "SafeCast: value doesn't fit in 176 bits"); + return int176(value); + } + + /** + * @dev Returns the downcasted int168 from int256, reverting on + * overflow (when the input is less than smallest int168 or + * greater than largest int168). + * + * Counterpart to Solidity's `int168` operator. + * + * Requirements: + * + * - input must fit into 168 bits + * + * _Available since v4.7._ + */ + function toInt168(int256 value) internal pure returns (int168) { + require(value >= type(int168).min && value <= type(int168).max, "SafeCast: value doesn't fit in 168 bits"); + return int168(value); + } + + /** + * @dev Returns the downcasted int160 from int256, reverting on + * overflow (when the input is less than smallest int160 or + * greater than largest int160). + * + * Counterpart to Solidity's `int160` operator. + * + * Requirements: + * + * - input must fit into 160 bits + * + * _Available since v4.7._ + */ + function toInt160(int256 value) internal pure returns (int160) { + require(value >= type(int160).min && value <= type(int160).max, "SafeCast: value doesn't fit in 160 bits"); + return int160(value); + } + + /** + * @dev Returns the downcasted int152 from int256, reverting on + * overflow (when the input is less than smallest int152 or + * greater than largest int152). + * + * Counterpart to Solidity's `int152` operator. + * + * Requirements: + * + * - input must fit into 152 bits + * + * _Available since v4.7._ + */ + function toInt152(int256 value) internal pure returns (int152) { + require(value >= type(int152).min && value <= type(int152).max, "SafeCast: value doesn't fit in 152 bits"); + return int152(value); + } + + /** + * @dev Returns the downcasted int144 from int256, reverting on + * overflow (when the input is less than smallest int144 or + * greater than largest int144). + * + * Counterpart to Solidity's `int144` operator. + * + * Requirements: + * + * - input must fit into 144 bits + * + * _Available since v4.7._ + */ + function toInt144(int256 value) internal pure returns (int144) { + require(value >= type(int144).min && value <= type(int144).max, "SafeCast: value doesn't fit in 144 bits"); + return int144(value); + } + + /** + * @dev Returns the downcasted int136 from int256, reverting on + * overflow (when the input is less than smallest int136 or + * greater than largest int136). + * + * Counterpart to Solidity's `int136` operator. + * + * Requirements: + * + * - input must fit into 136 bits + * + * _Available since v4.7._ + */ + function toInt136(int256 value) internal pure returns (int136) { + require(value >= type(int136).min && value <= type(int136).max, "SafeCast: value doesn't fit in 136 bits"); + return int136(value); + } + /** * @dev Returns the downcasted int128 from int256, reverting on * overflow (when the input is less than smallest int128 or @@ -154,6 +848,132 @@ library SafeCast { return int128(value); } + /** + * @dev Returns the downcasted int120 from int256, reverting on + * overflow (when the input is less than smallest int120 or + * greater than largest int120). + * + * Counterpart to Solidity's `int120` operator. + * + * Requirements: + * + * - input must fit into 120 bits + * + * _Available since v4.7._ + */ + function toInt120(int256 value) internal pure returns (int120) { + require(value >= type(int120).min && value <= type(int120).max, "SafeCast: value doesn't fit in 120 bits"); + return int120(value); + } + + /** + * @dev Returns the downcasted int112 from int256, reverting on + * overflow (when the input is less than smallest int112 or + * greater than largest int112). + * + * Counterpart to Solidity's `int112` operator. + * + * Requirements: + * + * - input must fit into 112 bits + * + * _Available since v4.7._ + */ + function toInt112(int256 value) internal pure returns (int112) { + require(value >= type(int112).min && value <= type(int112).max, "SafeCast: value doesn't fit in 112 bits"); + return int112(value); + } + + /** + * @dev Returns the downcasted int104 from int256, reverting on + * overflow (when the input is less than smallest int104 or + * greater than largest int104). + * + * Counterpart to Solidity's `int104` operator. + * + * Requirements: + * + * - input must fit into 104 bits + * + * _Available since v4.7._ + */ + function toInt104(int256 value) internal pure returns (int104) { + require(value >= type(int104).min && value <= type(int104).max, "SafeCast: value doesn't fit in 104 bits"); + return int104(value); + } + + /** + * @dev Returns the downcasted int96 from int256, reverting on + * overflow (when the input is less than smallest int96 or + * greater than largest int96). + * + * Counterpart to Solidity's `int96` operator. + * + * Requirements: + * + * - input must fit into 96 bits + * + * _Available since v4.7._ + */ + function toInt96(int256 value) internal pure returns (int96) { + require(value >= type(int96).min && value <= type(int96).max, "SafeCast: value doesn't fit in 96 bits"); + return int96(value); + } + + /** + * @dev Returns the downcasted int88 from int256, reverting on + * overflow (when the input is less than smallest int88 or + * greater than largest int88). + * + * Counterpart to Solidity's `int88` operator. + * + * Requirements: + * + * - input must fit into 88 bits + * + * _Available since v4.7._ + */ + function toInt88(int256 value) internal pure returns (int88) { + require(value >= type(int88).min && value <= type(int88).max, "SafeCast: value doesn't fit in 88 bits"); + return int88(value); + } + + /** + * @dev Returns the downcasted int80 from int256, reverting on + * overflow (when the input is less than smallest int80 or + * greater than largest int80). + * + * Counterpart to Solidity's `int80` operator. + * + * Requirements: + * + * - input must fit into 80 bits + * + * _Available since v4.7._ + */ + function toInt80(int256 value) internal pure returns (int80) { + require(value >= type(int80).min && value <= type(int80).max, "SafeCast: value doesn't fit in 80 bits"); + return int80(value); + } + + /** + * @dev Returns the downcasted int72 from int256, reverting on + * overflow (when the input is less than smallest int72 or + * greater than largest int72). + * + * Counterpart to Solidity's `int72` operator. + * + * Requirements: + * + * - input must fit into 72 bits + * + * _Available since v4.7._ + */ + function toInt72(int256 value) internal pure returns (int72) { + require(value >= type(int72).min && value <= type(int72).max, "SafeCast: value doesn't fit in 72 bits"); + return int72(value); + } + /** * @dev Returns the downcasted int64 from int256, reverting on * overflow (when the input is less than smallest int64 or @@ -172,6 +992,60 @@ library SafeCast { return int64(value); } + /** + * @dev Returns the downcasted int56 from int256, reverting on + * overflow (when the input is less than smallest int56 or + * greater than largest int56). + * + * Counterpart to Solidity's `int56` operator. + * + * Requirements: + * + * - input must fit into 56 bits + * + * _Available since v4.7._ + */ + function toInt56(int256 value) internal pure returns (int56) { + require(value >= type(int56).min && value <= type(int56).max, "SafeCast: value doesn't fit in 56 bits"); + return int56(value); + } + + /** + * @dev Returns the downcasted int48 from int256, reverting on + * overflow (when the input is less than smallest int48 or + * greater than largest int48). + * + * Counterpart to Solidity's `int48` operator. + * + * Requirements: + * + * - input must fit into 48 bits + * + * _Available since v4.7._ + */ + function toInt48(int256 value) internal pure returns (int48) { + require(value >= type(int48).min && value <= type(int48).max, "SafeCast: value doesn't fit in 48 bits"); + return int48(value); + } + + /** + * @dev Returns the downcasted int40 from int256, reverting on + * overflow (when the input is less than smallest int40 or + * greater than largest int40). + * + * Counterpart to Solidity's `int40` operator. + * + * Requirements: + * + * - input must fit into 40 bits + * + * _Available since v4.7._ + */ + function toInt40(int256 value) internal pure returns (int40) { + require(value >= type(int40).min && value <= type(int40).max, "SafeCast: value doesn't fit in 40 bits"); + return int40(value); + } + /** * @dev Returns the downcasted int32 from int256, reverting on * overflow (when the input is less than smallest int32 or @@ -190,6 +1064,24 @@ library SafeCast { return int32(value); } + /** + * @dev Returns the downcasted int24 from int256, reverting on + * overflow (when the input is less than smallest int24 or + * greater than largest int24). + * + * Counterpart to Solidity's `int24` operator. + * + * Requirements: + * + * - input must fit into 24 bits + * + * _Available since v4.7._ + */ + function toInt24(int256 value) internal pure returns (int24) { + require(value >= type(int24).min && value <= type(int24).max, "SafeCast: value doesn't fit in 24 bits"); + return int24(value); + } + /** * @dev Returns the downcasted int16 from int256, reverting on * overflow (when the input is less than smallest int16 or @@ -217,7 +1109,7 @@ library SafeCast { * * Requirements: * - * - input must fit into 8 bits. + * - input must fit into 8 bits * * _Available since v3.1._ */ @@ -232,6 +1124,8 @@ library SafeCast { * Requirements: * * - input must be less than or equal to maxInt256. + * + * _Available since v3.0._ */ function toInt256(uint256 value) internal pure returns (int256) { // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive diff --git a/package.json b/package.json index 8f2f10c87af..cb1ac557181 100644 --- a/package.json +++ b/package.json @@ -25,11 +25,14 @@ "clean": "hardhat clean && rimraf build contracts/build", "prepare": "npm run clean && env COMPILE_MODE=production npm run compile", "prepack": "scripts/prepack.sh", + "generate": "scripts/generate/run.js", "release": "scripts/release/release.sh", "version": "scripts/release/version.sh", "test": "hardhat test", - "test:inheritance": "node scripts/inheritanceOrdering artifacts/build-info/*", - "gas-report": "env ENABLE_GAS_REPORT=true npm run test" + "test:inheritance": "scripts/checks/inheritanceOrdering.js artifacts/build-info/*", + "test:generation": "scripts/checks/generation.sh", + "gas-report": "env ENABLE_GAS_REPORT=true npm run test", + "slither": "npm run clean && slither . --detect reentrancy-eth,reentrancy-no-eth,reentrancy-unlimited-gas" }, "repository": { "type": "git", diff --git a/scripts/checks/generation.sh b/scripts/checks/generation.sh new file mode 100755 index 00000000000..165c35a06b0 --- /dev/null +++ b/scripts/checks/generation.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -euo pipefail + +npm run generate +git diff --quiet --exit-code diff --git a/scripts/inheritanceOrdering.js b/scripts/checks/inheritanceOrdering.js old mode 100644 new mode 100755 similarity index 97% rename from scripts/inheritanceOrdering.js rename to scripts/checks/inheritanceOrdering.js index 2d285895c72..9d332cba330 --- a/scripts/inheritanceOrdering.js +++ b/scripts/checks/inheritanceOrdering.js @@ -1,10 +1,12 @@ +#!/usr/bin/env node + const path = require('path'); const graphlib = require('graphlib'); const { findAll } = require('solidity-ast/utils'); const { _: artifacts } = require('yargs').argv; for (const artifact of artifacts) { - const { output: solcOutput } = require(path.resolve(__dirname, '..', artifact)); + const { output: solcOutput } = require(path.resolve(__dirname, '../..', artifact)); const graph = new graphlib.Graph({ directed: true }); const names = {}; diff --git a/scripts/generate/format-lines.js b/scripts/generate/format-lines.js new file mode 100644 index 00000000000..e1f1823e834 --- /dev/null +++ b/scripts/generate/format-lines.js @@ -0,0 +1,16 @@ +function formatLines (...lines) { + return [...indentEach(0, lines)].join('\n') + '\n'; +} + +function *indentEach (indent, lines) { + for (const line of lines) { + if (Array.isArray(line)) { + yield * indentEach(indent + 1, line); + } else { + const padding = ' '.repeat(indent); + yield * line.split('\n').map(subline => subline === '' ? '' : padding + subline); + } + } +} + +module.exports = formatLines; diff --git a/scripts/generate/run.js b/scripts/generate/run.js new file mode 100755 index 00000000000..5ebb1b37a33 --- /dev/null +++ b/scripts/generate/run.js @@ -0,0 +1,29 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const format = require('./format-lines'); + +function getVersion (path) { + try { + return fs + .readFileSync(path, 'utf8') + .match(/\/\/ OpenZeppelin Contracts \(last updated v\d+\.\d+\.\d+\)/)[0]; + } catch (err) { + return null; + } +} + +for (const [ file, template ] of Object.entries({ + 'utils/math/SafeCast.sol': './templates/SafeCast', + 'mocks/SafeCastMock.sol': './templates/SafeCastMock', +})) { + const path = `./contracts/${file}`; + const version = getVersion(path); + const content = format( + '// SPDX-License-Identifier: MIT', + (version ? version + ` (${file})\n` : ''), + require(template).trimEnd(), + ); + + fs.writeFileSync(path, content); +} diff --git a/scripts/generate/templates/SafeCast.js b/scripts/generate/templates/SafeCast.js new file mode 100755 index 00000000000..ce36c26c4e4 --- /dev/null +++ b/scripts/generate/templates/SafeCast.js @@ -0,0 +1,168 @@ +const assert = require('assert'); +const format = require('../format-lines'); +const { range } = require('../../helpers'); + +const LENGTHS = range(8, 256, 8).reverse(); // 248 → 8 (in steps of 8) + +// Returns the version of OpenZeppelin Contracts in which a particular function was introduced. +// This is used in the docs for each function. +const version = (selector, length) => { + switch (selector) { + case 'toUint(uint)': { + switch (length) { + case 8: + case 16: + case 32: + case 64: + case 128: + return '2.5'; + case 96: + case 224: + return '4.2'; + default: + assert(LENGTHS.includes(length)); + return '4.7'; + } + } + case 'toInt(int)': { + switch (length) { + case 8: + case 16: + case 32: + case 64: + case 128: + return '3.1'; + default: + assert(LENGTHS.includes(length)); + return '4.7'; + } + } + case 'toUint(int)': { + switch (length) { + case 256: + return '3.0'; + default: + assert(false); + return; + } + } + case 'toInt(uint)': { + switch (length) { + case 256: + return '3.0'; + default: + assert(false); + return; + } + } + default: + assert(false); + } +}; + +const header = `\ +pragma solidity ^0.8.0; + +/** + * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow + * checks. + * + * 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} and {SignedSafeMath} to extend it to smaller types, by performing + * all math on \`uint256\` and \`int256\` and then downcasting. + */ +`; + +const toUintDownCast = length => `\ +/** + * @dev Returns the downcasted uint${length} from uint256, reverting on + * overflow (when the input is greater than largest uint${length}). + * + * Counterpart to Solidity's \`uint${length}\` operator. + * + * Requirements: + * + * - input must fit into ${length} bits + * + * _Available since v${version('toUint(uint)', length)}._ + */ +function toUint${length}(uint256 value) internal pure returns (uint${length}) { + require(value <= type(uint${length}).max, "SafeCast: value doesn't fit in ${length} bits"); + return uint${length}(value); +} +`; + +/* eslint-disable max-len */ +const toIntDownCast = length => `\ +/** + * @dev Returns the downcasted int${length} from int256, reverting on + * overflow (when the input is less than smallest int${length} or + * greater than largest int${length}). + * + * Counterpart to Solidity's \`int${length}\` operator. + * + * Requirements: + * + * - input must fit into ${length} bits + * + * _Available since v${version('toInt(int)', length)}._ + */ +function toInt${length}(int256 value) internal pure returns (int${length}) { + require(value >= type(int${length}).min && value <= type(int${length}).max, "SafeCast: value doesn't fit in ${length} bits"); + return int${length}(value); +} +`; +/* eslint-enable max-len */ + +const toInt = length => `\ +/** + * @dev Converts an unsigned uint${length} into a signed int${length}. + * + * Requirements: + * + * - input must be less than or equal to maxInt${length}. + * + * _Available since v${version('toInt(uint)', length)}._ + */ +function toInt${length}(uint${length} value) internal pure returns (int${length}) { + // Note: Unsafe cast below is okay because \`type(int${length}).max\` is guaranteed to be positive + require(value <= uint${length}(type(int${length}).max), "SafeCast: value doesn't fit in an int${length}"); + return int${length}(value); +} +`; + +const toUint = length => `\ +/** + * @dev Converts a signed int${length} into an unsigned uint${length}. + * + * Requirements: + * + * - input must be greater than or equal to 0. + * + * _Available since v${version('toUint(int)', length)}._ + */ +function toUint${length}(int${length} value) internal pure returns (uint${length}) { + require(value >= 0, "SafeCast: value must be positive"); + return uint${length}(value); +} +`; + +// GENERATE +module.exports = format( + header.trimEnd(), + 'library SafeCast {', + [ + ...LENGTHS.map(size => toUintDownCast(size)), + toUint(256), + ...LENGTHS.map(size => toIntDownCast(size)), + toInt(256).trimEnd(), + ], + '}', +); diff --git a/scripts/generate/templates/SafeCastMock.js b/scripts/generate/templates/SafeCastMock.js new file mode 100755 index 00000000000..9bb64d2c766 --- /dev/null +++ b/scripts/generate/templates/SafeCastMock.js @@ -0,0 +1,50 @@ +const format = require('../format-lines'); +const { range } = require('../../helpers'); + +const LENGTHS = range(8, 256, 8).reverse(); // 248 → 8 (in steps of 8) + +const header = `\ +pragma solidity ^0.8.0; + +import "../utils/math/SafeCast.sol"; +`; + +const toInt = length => `\ +function toInt${length}(uint${length} a) public pure returns (int${length}) { + return a.toInt${length}(); +} +`; + +const toUint = length => `\ +function toUint${length}(int${length} a) public pure returns (uint${length}) { + return a.toUint${length}(); +} +`; + +const toIntDownCast = length => `\ +function toInt${length}(int256 a) public pure returns (int${length}) { + return a.toInt${length}(); +} +`; + +const toUintDownCast = length => `\ +function toUint${length}(uint256 a) public pure returns (uint${length}) { + return a.toUint${length}(); +} +`; + +// GENERATE +module.exports = format( + header, + 'contract SafeCastMock {', + [ + 'using SafeCast for uint256;', + 'using SafeCast for int256;', + '', + toUint(256), + ...LENGTHS.map(size => toUintDownCast(size)), + toInt(256), + ...LENGTHS.map(size => toIntDownCast(size)), + ].flatMap(fn => fn.split('\n')).slice(0, -1), + '}', +); diff --git a/scripts/helpers.js b/scripts/helpers.js new file mode 100644 index 00000000000..cbd0e01e3b5 --- /dev/null +++ b/scripts/helpers.js @@ -0,0 +1,23 @@ +function chunk (array, size = 1) { + return Array.range(Math.ceil(array.length / size)).map(i => array.slice(i * size, i * size + size)); +} + +function range (start, stop = undefined, step = 1) { + if (!stop) { stop = start; start = 0; } + return start < stop ? Array(Math.ceil((stop - start) / step)).fill().map((_, i) => start + i * step) : []; +} + +function unique (array, op = x => x) { + return array.filter((obj, i) => array.findIndex(entry => op(obj) === op(entry)) === i); +} + +function zip (...args) { + return Array(Math.max(...args.map(arg => arg.length))).fill(null).map((_, i) => args.map(arg => arg[i])); +} + +module.exports = { + chunk, + range, + unique, + zip, +}; diff --git a/test/utils/math/SafeCast.test.js b/test/utils/math/SafeCast.test.js index 09c7a3f1aed..97fc22e9258 100644 --- a/test/utils/math/SafeCast.test.js +++ b/test/utils/math/SafeCast.test.js @@ -1,6 +1,6 @@ const { BN, expectRevert } = require('@openzeppelin/test-helpers'); - const { expect } = require('chai'); +const { range } = require('../../../scripts/helpers'); const SafeCastMock = artifacts.require('SafeCastMock'); @@ -41,7 +41,7 @@ contract('SafeCast', async (accounts) => { }); } - [8, 16, 32, 64, 96, 128, 224].forEach(bits => testToUint(bits)); + range(8, 256, 8).forEach(bits => testToUint(bits)); describe('toUint256', () => { const maxInt256 = new BN('2').pow(new BN(255)).subn(1); @@ -129,7 +129,7 @@ contract('SafeCast', async (accounts) => { }); } - [8, 16, 32, 64, 128].forEach(bits => testToInt(bits)); + range(8, 256, 8).forEach(bits => testToInt(bits)); describe('toInt256', () => { const maxUint256 = new BN('2').pow(new BN(256)).subn(1);