diff --git a/CHANGELOG.md b/CHANGELOG.md index 58bec1469..5549260a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * `Initializable`: optimize `_disableInitializers` by using `!=` instead of `<`. ([#3787](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3787)) * `Math`: optimize `log256` rounding check. ([#3745](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3745)) * `Strings`: add `equal` method. ([#3774](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3774)) + * `Strings`: add `toString` method for signed integers. ([#3773](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3773)) ### Deprecations diff --git a/contracts/mocks/StringsMock.sol b/contracts/mocks/StringsMock.sol index 81389fad4..9f98e2778 100644 --- a/contracts/mocks/StringsMock.sol +++ b/contracts/mocks/StringsMock.sol @@ -9,6 +9,10 @@ contract StringsMock { return Strings.toString(value); } + function toString(int256 value) public pure returns (string memory) { + return Strings.toString(value); + } + function toHexString(uint256 value) public pure returns (string memory) { return Strings.toHexString(value); } diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index e7597f12e..3a037f477 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import "./math/Math.sol"; +import "./math/SignedMath.sol"; /** * @dev String operations. @@ -37,6 +38,13 @@ library Strings { } } + /** + * @dev Converts a `int256` to its ASCII `string` decimal representation. + */ + function toString(int256 value) internal pure returns (string memory) { + return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value)))); + } + /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. */ diff --git a/test/utils/Strings.test.js b/test/utils/Strings.test.js index db9e25438..67e414e44 100644 --- a/test/utils/Strings.test.js +++ b/test/utils/Strings.test.js @@ -10,7 +10,7 @@ contract('Strings', function (accounts) { }); describe('toString', function () { - for (const [ key, value ] of Object.entries([ + const values = [ '0', '7', '10', @@ -29,13 +29,43 @@ contract('Strings', function (accounts) { '12345678901234567890123456789012345678901234567890', '123456789012345678901234567890123456789012345678901234567890', '1234567890123456789012345678901234567890123456789012345678901234567890', - ].reduce((acc, value) => Object.assign(acc, { [value]: new BN(value) }), { - MAX_UINT256: constants.MAX_UINT256.toString(), - }))) { - it(`converts ${key}`, async function () { + ]; + + describe('uint256', function () { + it('converts MAX_UINT256', async function () { + const value = constants.MAX_UINT256; expect(await this.strings.methods['toString(uint256)'](value)).to.equal(value.toString(10)); }); - } + + for (const value of values) { + it(`converts ${value}`, async function () { + expect(await this.strings.methods['toString(uint256)'](value)).to.equal(value); + }); + } + }); + + describe('int256', function () { + it('converts MAX_INT256', async function () { + const value = constants.MAX_INT256; + expect(await this.strings.methods['toString(int256)'](value)).to.equal(value.toString(10)); + }); + + it('converts MIN_INT256', async function () { + const value = constants.MIN_INT256; + expect(await this.strings.methods['toString(int256)'](value)).to.equal(value.toString(10)); + }); + + for (const value of values) { + it(`convert ${value}`, async function () { + expect(await this.strings.methods['toString(int256)'](value)).to.equal(value); + }); + + it(`convert negative ${value}`, async function () { + const negated = new BN(value).neg(); + expect(await this.strings.methods['toString(int256)'](negated)).to.equal(negated.toString(10)); + }); + } + }); }); describe('toHexString', function () {