-
Notifications
You must be signed in to change notification settings - Fork 12.1k
Add toUint, toInt and hexToUint to Strings #5166
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
Changes from 2 commits
b2eedbe
efd2f30
bc42b25
07f4b44
40ba631
07ec518
95fb0db
f263819
f51fbe6
52a301b
027859e
a91a999
86abf5a
6dca3cb
a7a6e9e
ec9a659
568dc7b
0292c31
aea4a14
cf78a9f
26cec97
3a7f904
4d18729
c7a7c94
d6319e8
b3bf461
2ab63b7
231b93b
24f1490
43f0dc1
7b7c1fd
2abfa49
f433e6d
27c7c0d
75e1e4c
4f48757
1ec1e3f
53d72d7
c5790f8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'openzeppelin-solidity': minor | ||
--- | ||
|
||
`Strings`: Add `toUint`, `toInt` and `hexToUint` to parse strings into numbers.` | ||
cairoeth marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -18,6 +18,11 @@ library Strings { | |||||
*/ | ||||||
error StringsInsufficientHexLength(uint256 value, uint256 length); | ||||||
|
||||||
/** | ||||||
* @dev The string being parsed contains characters that are not in scope of the given base. | ||||||
*/ | ||||||
error StringsInvalidChar(bytes1 chr, uint8 base); | ||||||
|
||||||
/** | ||||||
* @dev Converts a `uint256` to its ASCII `string` decimal representation. | ||||||
*/ | ||||||
|
@@ -115,4 +120,85 @@ library Strings { | |||||
function equal(string memory a, string memory b) internal pure returns (bool) { | ||||||
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b)); | ||||||
} | ||||||
|
||||||
/** | ||||||
* @dev Parse an decimal string and returns the value as a `uint256`. | ||||||
ernestognw marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
* | ||||||
* This function will revert if: | ||||||
* - the string contains any character that is not in [0-9]. | ||||||
* - the result does not fit in a uint256. | ||||||
Amxx marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
*/ | ||||||
function toUint(string memory input) internal pure returns (uint256) { | ||||||
bytes memory buffer = bytes(input); | ||||||
ernestognw marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
uint256 result = 0; | ||||||
for (uint256 i = 0; i < buffer.length; ++i) { | ||||||
result *= 10; // will revert if overflow | ||||||
result += _parseChr(buffer[i], 10); | ||||||
} | ||||||
return result; | ||||||
} | ||||||
Amxx marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
/** | ||||||
* @dev Parse an decimal string and returns the value as a `int256`. | ||||||
* | ||||||
* This function will revert if: | ||||||
* - the string contains any character (outside the prefix) that is not in [0-9]. | ||||||
* - the result does not fit in a int256. | ||||||
Amxx marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
*/ | ||||||
Amxx marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
function toInt(string memory input) internal pure returns (int256) { | ||||||
bytes memory buffer = bytes(input); | ||||||
|
||||||
// check presence of a negative sign. | ||||||
uint256 offset = bytes1(buffer) == 0x2d ? 1 : 0; | ||||||
int8 factor = bytes1(buffer) == 0x2d ? int8(-1) : int8(1); | ||||||
|
||||||
int256 result = 0; | ||||||
for (uint256 i = offset; i < buffer.length; ++i) { | ||||||
result *= 10; // will revert if overflow | ||||||
result += factor * int8(_parseChr(buffer[i], 10)); // parseChr is at most 35, it fits into an int8 | ||||||
Amxx marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
return result; | ||||||
} | ||||||
|
||||||
/** | ||||||
* @dev Parse an hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`. | ||||||
* | ||||||
* This function will revert if: | ||||||
* - the string contains any character (outside the prefix) that is not in [0-9a-fA-F]. | ||||||
* - the result does not fit in a uint256. | ||||||
Amxx marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
*/ | ||||||
function hexToUint(string memory input) internal pure returns (uint256) { | ||||||
bytes memory buffer = bytes(input); | ||||||
|
||||||
// skip 0x prefix if present. Length check doesn't appear to be critical | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. second sentence feels out of place
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just for the record, this is what the comment was about: The string length may be less than 2. String could be empty, of just So we check the length to verify that it ok to read the prefix ? It turn out no.
That is the long explanation (I'm happy its visible in the PR 😃) to something that is not really trivial, and can be missed, but missing it is not a risk. We may get questions about it though ... |
||||||
uint256 offset = bytes2(buffer) == 0x3078 ? 2 : 0; | ||||||
Amxx marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
uint256 result = 0; | ||||||
for (uint256 i = offset; i < buffer.length; ++i) { | ||||||
result *= 16; // will revert if overflow | ||||||
result += _parseChr(buffer[i], 16); | ||||||
} | ||||||
return result; | ||||||
} | ||||||
|
||||||
function _parseChr(bytes1 chr, uint8 base) private pure returns (uint8) { | ||||||
uint8 result; | ||||||
|
||||||
// Try to parse `chr`: | ||||||
// - Case 1: [0-9] | ||||||
// - Case 2: [a-z] | ||||||
// - Case 2: [A-Z] | ||||||
// - otherwise not supported | ||||||
unchecked { | ||||||
if (uint8(chr) > 47 && uint8(chr) < 58) result = uint8(chr) - 48; | ||||||
ernestognw marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
else if (uint8(chr) > 96 && uint8(chr) < 123) result = uint8(chr) - 87; | ||||||
else if (uint8(chr) > 64 && uint8(chr) < 91) result = uint8(chr) - 55; | ||||||
else revert StringsInvalidChar(chr, base); | ||||||
} | ||||||
Amxx marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
// check base | ||||||
if (result >= base) revert StringsInvalidChar(chr, base); | ||||||
return result; | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.20; | ||
|
||
import {Test} from "forge-std/Test.sol"; | ||
|
||
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; | ||
|
||
contract StringsTest is Test { | ||
using Strings for *; | ||
|
||
function testParse(uint256 value) external { | ||
assertEq(value, value.toString().toUint()); | ||
} | ||
|
||
function testParseSigned(int256 value) external { | ||
assertEq(value, value.toStringSigned().toInt()); | ||
} | ||
|
||
function testParseHex(uint256 value) external { | ||
assertEq(value, value.toHexString().hexToUint()); | ||
} | ||
|
||
function testParseChecksumHex(address value) external { | ||
assertEq(value, address(uint160(value.toChecksumHexString().hexToUint()))); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.