Skip to content

Commit 8f11b32

Browse files
committed
checksum in place to avoid double allocation + simplification
1 parent ac713f0 commit 8f11b32

File tree

1 file changed

+21
-32
lines changed

1 file changed

+21
-32
lines changed

contracts/utils/Strings.sol

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {SignedMath} from "./math/SignedMath.sol";
1111
*/
1212
library Strings {
1313
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
14-
bytes16 private constant HEX_DIGITS_UPPERCASE = "0123456789ABCDEF";
1514
uint8 private constant ADDRESS_LENGTH = 20;
1615

1716
/**
@@ -64,15 +63,17 @@ library Strings {
6463
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
6564
*/
6665
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
67-
if (length < Math.log256(value) + 1) {
68-
revert StringsInsufficientHexLength(value, length);
69-
}
70-
66+
uint256 localValue = value;
7167
bytes memory buffer = new bytes(2 * length + 2);
7268
buffer[0] = "0";
7369
buffer[1] = "x";
74-
_unsafeSetHexString(buffer, 2, value);
75-
70+
for (uint256 i = 2 * length + 1; i > 1; --i) {
71+
buffer[i] = HEX_DIGITS[localValue & 0xf];
72+
localValue >>= 4;
73+
}
74+
if (localValue != 0) {
75+
revert StringsInsufficientHexLength(value, length);
76+
}
7677
return string(buffer);
7778
}
7879

@@ -89,22 +90,23 @@ library Strings {
8990
* representation, according to EIP-55.
9091
*/
9192
function toChecksumHexString(address addr) internal pure returns (string memory) {
92-
bytes memory lowercase = new bytes(40);
93-
uint160 addrValue = uint160(addr);
94-
_unsafeSetHexString(lowercase, 0, addrValue);
95-
bytes32 hashedAddr = keccak256(abi.encodePacked(lowercase));
93+
bytes memory buffer = bytes(toHexString(addr));
94+
95+
// hash the hex part of buffer (skip length + 2 bytes, length 40)
96+
uint256 hashValue;
97+
assembly ("memory-safe") {
98+
hashValue := shr(96, keccak256(add(buffer, 0x22), 40))
99+
}
96100

97-
bytes memory buffer = new bytes(42);
98-
buffer[0] = "0";
99-
buffer[1] = "x";
100-
uint160 hashValue = uint160(bytes20(hashedAddr));
101101
for (uint256 i = 41; i > 1; --i) {
102-
uint8 digit = uint8(addrValue & 0xf);
103-
buffer[i] = hashValue & 0xf > 7 ? HEX_DIGITS_UPPERCASE[digit] : HEX_DIGITS[digit];
104-
addrValue >>= 4;
102+
// possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f)
103+
if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) {
104+
// case shift by xoring with 0x20
105+
buffer[i] ^= 0x20;
106+
}
105107
hashValue >>= 4;
106108
}
107-
return string(abi.encodePacked(buffer));
109+
return string(buffer);
108110
}
109111

110112
/**
@@ -113,17 +115,4 @@ library Strings {
113115
function equal(string memory a, string memory b) internal pure returns (bool) {
114116
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
115117
}
116-
117-
/**
118-
* @dev Sets the hexadecimal representation of a value in the specified buffer starting from the given offset.
119-
*
120-
* NOTE: This function does not check that the `buffer` can allocate `value` without overflowing. Make sure
121-
* to check whether `Math.log256(value) + 1` is larger than the specified `length`.
122-
*/
123-
function _unsafeSetHexString(bytes memory buffer, uint256 offset, uint256 value) private pure {
124-
for (uint256 i = buffer.length; i > offset; --i) {
125-
buffer[i - 1] = HEX_DIGITS[value & 0xf];
126-
value >>= 4;
127-
}
128-
}
129118
}

0 commit comments

Comments
 (0)