Skip to content

Commit bd58895

Browse files
Amxxcairoethernestognw
authored
Add toUint, toInt and hexToUint to Strings (#5166)
Co-authored-by: cairo <cairoeth@protonmail.com> Co-authored-by: Ernesto García <ernestognw@gmail.com>
1 parent 72c152d commit bd58895

6 files changed

Lines changed: 503 additions & 68 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`Strings`: Add `parseUint`, `parseInt`, `parseHexUint` and `parseAddress` to parse strings into numbers and addresses. Also provide variants of these functions that parse substrings, and `tryXxx` variants that do not revert on invalid input.

contracts/governance/Governor.sol

Lines changed: 27 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {DoubleEndedQueue} from "../utils/structs/DoubleEndedQueue.sol";
1313
import {Address} from "../utils/Address.sol";
1414
import {Context} from "../utils/Context.sol";
1515
import {Nonces} from "../utils/Nonces.sol";
16+
import {Strings} from "../utils/Strings.sol";
1617
import {IGovernor, IERC6372} from "./IGovernor.sol";
1718

1819
/**
@@ -760,67 +761,25 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72
760761
address proposer,
761762
string memory description
762763
) internal view virtual returns (bool) {
763-
uint256 len = bytes(description).length;
764-
765-
// Length is too short to contain a valid proposer suffix
766-
if (len < 52) {
767-
return true;
768-
}
769-
770-
// Extract what would be the `#proposer=0x` marker beginning the suffix
771-
bytes12 marker;
772-
assembly ("memory-safe") {
773-
// - Start of the string contents in memory = description + 32
774-
// - First character of the marker = len - 52
775-
// - Length of "#proposer=0x0000000000000000000000000000000000000000" = 52
776-
// - We read the memory word starting at the first character of the marker:
777-
// - (description + 32) + (len - 52) = description + (len - 20)
778-
// - Note: Solidity will ignore anything past the first 12 bytes
779-
marker := mload(add(description, sub(len, 20)))
780-
}
781-
782-
// If the marker is not found, there is no proposer suffix to check
783-
if (marker != bytes12("#proposer=0x")) {
784-
return true;
785-
}
764+
unchecked {
765+
uint256 length = bytes(description).length;
786766

787-
// Parse the 40 characters following the marker as uint160
788-
uint160 recovered = 0;
789-
for (uint256 i = len - 40; i < len; ++i) {
790-
(bool isHex, uint8 value) = _tryHexToUint(bytes(description)[i]);
791-
// If any of the characters is not a hex digit, ignore the suffix entirely
792-
if (!isHex) {
767+
// Length is too short to contain a valid proposer suffix
768+
if (length < 52) {
793769
return true;
794770
}
795-
recovered = (recovered << 4) | value;
796-
}
797771

798-
return recovered == uint160(proposer);
799-
}
772+
// Extract what would be the `#proposer=` marker beginning the suffix
773+
bytes10 marker = bytes10(_unsafeReadBytesOffset(bytes(description), length - 52));
800774

801-
/**
802-
* @dev Try to parse a character from a string as a hex value. Returns `(true, value)` if the char is in
803-
* `[0-9a-fA-F]` and `(false, 0)` otherwise. Value is guaranteed to be in the range `0 <= value < 16`
804-
*/
805-
function _tryHexToUint(bytes1 char) private pure returns (bool isHex, uint8 value) {
806-
uint8 c = uint8(char);
807-
unchecked {
808-
// Case 0-9
809-
if (47 < c && c < 58) {
810-
return (true, c - 48);
811-
}
812-
// Case A-F
813-
else if (64 < c && c < 71) {
814-
return (true, c - 55);
815-
}
816-
// Case a-f
817-
else if (96 < c && c < 103) {
818-
return (true, c - 87);
819-
}
820-
// Else: not a hex char
821-
else {
822-
return (false, 0);
775+
// If the marker is not found, there is no proposer suffix to check
776+
if (marker != bytes10("#proposer=")) {
777+
return true;
823778
}
779+
780+
// Check that the last 42 characters (after the marker) are a properly formatted address.
781+
(bool success, address recovered) = Strings.tryParseAddress(description, length - 42, length);
782+
return !success || recovered == proposer;
824783
}
825784
}
826785

@@ -849,4 +808,17 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72
849808
* @inheritdoc IGovernor
850809
*/
851810
function quorum(uint256 timepoint) public view virtual returns (uint256);
811+
812+
/**
813+
* @dev Reads a bytes32 from a bytes array without bounds checking.
814+
*
815+
* NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the
816+
* assembly block as such would prevent some optimizations.
817+
*/
818+
function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) {
819+
// This is not memory safe in the general case, but all calls to this private function are within bounds.
820+
assembly ("memory-safe") {
821+
value := mload(add(buffer, add(0x20, offset)))
822+
}
823+
}
852824
}

contracts/utils/README.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
3434
* {Strings}: Common operations for strings formatting.
3535
* {ShortString}: Library to encode (and decode) short strings into (or from) a single bytes32 slot for optimizing costs. Short strings are limited to 31 characters.
3636
* {SlotDerivation}: Methods for deriving storage slot from ERC-7201 namespaces as well as from constructions such as mapping and arrays.
37-
* {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types.
37+
* {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types.
3838
* {TransientSlot}: Primitives for reading from and writing to transient storage (only value types are currently supported).
3939
* {Multicall}: Abstract contract with a utility to allow batching together multiple calls in a single transaction. Useful for allowing EOAs to perform multiple operations at once.
4040
* {Context}: A utility for abstracting the sender and calldata in the current execution context.

0 commit comments

Comments
 (0)