Skip to content

Commit

Permalink
Safe Casting Library from uint256 to uintXX (#1926)
Browse files Browse the repository at this point in the history
* Include Safe Casting Library with complete and exhaustive test-suite.

* linting test file.

* Typo in SafeCast import statement

* Update test/utils/SafeCast.test.js

* Rename `castUXX` to `toUintXX` from suggestion

* Tackling the quick and easy suggestions regarding error string improvements etc.

* typo and changelog update.

* Improve SafeCast tests

* Update test/utils/SafeCast.test.js

* Update test/utils/SafeCast.test.js

* incorrect import

* add SafeCast to docs site

* Update CHANGELOG.md

* Update SafeCast.sol
  • Loading branch information
bh2smith authored and nventuro committed Oct 22, 2019
1 parent bfca6bd commit 2c11ed5
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
27 changes: 27 additions & 0 deletions contracts/mocks/SafeCastMock.sol
Original file line number Diff line number Diff line change
@@ -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();
}
}
2 changes: 2 additions & 0 deletions contracts/utils/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Miscellaneous contracts containing utility functions, often related to working w

{{Address}}

{{SafeCast}}

{{Arrays}}

{{ReentrancyGuard}}
95 changes: 95 additions & 0 deletions contracts/utils/SafeCast.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
45 changes: 45 additions & 0 deletions test/utils/SafeCast.test.js
Original file line number Diff line number Diff line change
@@ -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));
});

0 comments on commit 2c11ed5

Please sign in to comment.