Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Safe Casting Library from uint256 to uintXX #1926

Merged
merged 19 commits into from
Oct 22, 2019
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### New features:
* `Address.toPayable`: added a helper to convert between address types without having to resort to low-level casting. ([#1773](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1773))
* Facilities to make metatransaction-enabled contracts through the Gas Station Network. ([#1844](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1844))
* `SafeCast.toUintXX`: Increases security when used along with `SafeMath` for unitXX operations requiring downcasting.

### Improvements:
* `Address.isContract`: switched from `extcodesize` to `extcodehash` for less gas usage. ([#1802](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1802))
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();
}
}
87 changes: 87 additions & 0 deletions contracts/utils/SafeCast.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
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 by default 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.
*/
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);
}
}
39 changes: 39 additions & 0 deletions test/utils/SafeCast.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const { BN, expectRevert } = require('openzeppelin-test-helpers');

bh2smith marked this conversation as resolved.
Show resolved Hide resolved
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`);
bh2smith marked this conversation as resolved.
Show resolved Hide resolved
});

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`);
bh2smith marked this conversation as resolved.
Show resolved Hide resolved
});
});
}

[8, 16, 32, 64, 128].forEach(bits => testToUint(bits));
nventuro marked this conversation as resolved.
Show resolved Hide resolved
});