-
Notifications
You must be signed in to change notification settings - Fork 12.4k
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 1 commit
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 characteres that are not in scope of the given base. | ||||||
ernestognw marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| */ | ||||||
| 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.
Outdated
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.
Outdated
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.
Outdated
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.
Outdated
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.
Outdated
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.
Outdated
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 | ||||||
|
||||||
| // skip 0x prefix if present. Length check doesn't appear to be critical | |
| // skip 0x prefix if present |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The 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 "1". In some cases, doing a input[1] or even a input[0] would revert (out of bound acces). Doing bytes2(input) does not revert if the string is to short.
So we check the length to verify that it ok to read the prefix ? It turn out no.
- If the string is empty, then regardless of the result of that lookup (that could read dirty bytes), the loop will not run (because length == 0), and the result will be 0 -> that is ok
- If the string has length 1 then we have two options
- the check identifies the prefix
- that means the string is
"0", and there is a dirtyxafter. - In that case we have an offset of 2, and the length is 1, so the for loop does not run and the function returns 0 -> that is ok
- that means the string is
- the check does not find the prefix
- the only "digit" is read by the loop, and the result should be just fine
- the check identifies the prefix
- If the string has length >= 2, then the prefix lookup is in the bounds
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 ...
Amxx marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
ernestognw marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
ernestognw marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
| 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.