Skip to content

Commit fcfc4f8

Browse files
authored
⚡️ Optimize toString and add comment (ethereum#256)
1 parent 5b42f50 commit fcfc4f8

File tree

2 files changed

+46
-47
lines changed

2 files changed

+46
-47
lines changed

.gas-snapshot

+37-37
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
Base64Test:testBase64DecodeSentenceGas() (gas: 3673)
22
Base64Test:testBase64DecodeShortStringGas() (gas: 906)
33
Base64Test:testBase64EncodeDecode(bytes) (runs: 256, μ: 16948, ~: 12932)
4-
Base64Test:testBase64EncodeDecodeAltModes(bytes) (runs: 256, μ: 520084, ~: 390549)
4+
Base64Test:testBase64EncodeDecodeAltModes(bytes) (runs: 256, μ: 515224, ~: 371734)
55
Base64Test:testBase64EncodeEmptyString() (gas: 953)
6-
Base64Test:testBase64EncodeFileSafeAndNoPadding(bytes,bool,bool) (runs: 256, μ: 12275, ~: 6360)
6+
Base64Test:testBase64EncodeFileSafeAndNoPadding(bytes,bool,bool) (runs: 256, μ: 12293, ~: 6360)
77
Base64Test:testBase64EncodeSentence() (gas: 4877)
88
Base64Test:testBase64EncodeShortStrings() (gas: 8594)
99
Base64Test:testBase64EncodeToStringWithDoublePadding() (gas: 1723)
@@ -284,72 +284,72 @@ LibStringTest:testFromAddressToHexString() (gas: 3098)
284284
LibStringTest:testFromAddressToHexStringChecksumed() (gas: 35075)
285285
LibStringTest:testFromAddressToHexStringChecksumedDifferential(uint256) (runs: 256, μ: 422930, ~: 637248)
286286
LibStringTest:testFromAddressToHexStringWithLeadingZeros() (gas: 3099)
287-
LibStringTest:testHexStringNoPrefixVariants(uint256,uint256) (runs: 256, μ: 412476, ~: 219066)
287+
LibStringTest:testHexStringNoPrefixVariants(uint256,uint256) (runs: 256, μ: 412369, ~: 219152)
288288
LibStringTest:testStringConcat() (gas: 7381)
289-
LibStringTest:testStringConcat(string,string) (runs: 256, μ: 404934, ~: 187709)
289+
LibStringTest:testStringConcat(string,string) (runs: 256, μ: 410123, ~: 629052)
290290
LibStringTest:testStringConcatOriginal() (gas: 7981)
291291
LibStringTest:testStringDirectReturn() (gas: 8105)
292292
LibStringTest:testStringDirectReturn(string) (runs: 256, μ: 3647, ~: 3687)
293293
LibStringTest:testStringEndsWith() (gas: 2807)
294-
LibStringTest:testStringEndsWith(uint256) (runs: 256, μ: 397134, ~: 205901)
294+
LibStringTest:testStringEndsWith(uint256) (runs: 256, μ: 395390, ~: 205901)
295295
LibStringTest:testStringEq(string,string) (runs: 256, μ: 1552, ~: 1557)
296296
LibStringTest:testStringEscapeHTML() (gas: 11935)
297-
LibStringTest:testStringEscapeHTML(uint256) (runs: 256, μ: 448693, ~: 635936)
297+
LibStringTest:testStringEscapeHTML(uint256) (runs: 256, μ: 448423, ~: 635936)
298298
LibStringTest:testStringEscapeJSON() (gas: 16943)
299-
LibStringTest:testStringEscapeJSONHexEncode() (gas: 323619)
299+
LibStringTest:testStringEscapeJSONHexEncode() (gas: 323592)
300300
LibStringTest:testStringIndexOf() (gas: 17230)
301-
LibStringTest:testStringIndexOf(uint256) (runs: 256, μ: 393636, ~: 212810)
301+
LibStringTest:testStringIndexOf(uint256) (runs: 256, μ: 393539, ~: 212810)
302302
LibStringTest:testStringIndicesOf() (gas: 19437)
303-
LibStringTest:testStringIndicesOf(uint256) (runs: 256, μ: 444346, ~: 635652)
303+
LibStringTest:testStringIndicesOf(uint256) (runs: 256, μ: 444444, ~: 635652)
304304
LibStringTest:testStringLastIndexOf() (gas: 23210)
305-
LibStringTest:testStringLastIndexOf(uint256) (runs: 256, μ: 427117, ~: 632447)
305+
LibStringTest:testStringLastIndexOf(uint256) (runs: 256, μ: 428735, ~: 632447)
306306
LibStringTest:testStringLowerDifferential() (gas: 3332571)
307307
LibStringTest:testStringLowerDifferential(string) (runs: 256, μ: 11106, ~: 9271)
308308
LibStringTest:testStringLowerOriginal() (gas: 1739)
309309
LibStringTest:testStringPackAndUnpackOne() (gas: 740053)
310310
LibStringTest:testStringPackAndUnpackOne(string) (runs: 256, μ: 387595, ~: 185319)
311-
LibStringTest:testStringPackAndUnpackOneDifferential(string) (runs: 256, μ: 360993, ~: 184794)
311+
LibStringTest:testStringPackAndUnpackOneDifferential(string) (runs: 256, μ: 362722, ~: 184794)
312312
LibStringTest:testStringPackAndUnpackTwo() (gas: 809512)
313313
LibStringTest:testStringPackAndUnpackTwo(string,string) (runs: 256, μ: 411817, ~: 629628)
314-
LibStringTest:testStringPackAndUnpackTwoDifferential(string,string) (runs: 256, μ: 405235, ~: 185956)
314+
LibStringTest:testStringPackAndUnpackTwoDifferential(string,string) (runs: 256, μ: 408694, ~: 628047)
315315
LibStringTest:testStringRepeat() (gas: 9107)
316-
LibStringTest:testStringRepeat(string,uint256) (runs: 256, μ: 413645, ~: 627968)
316+
LibStringTest:testStringRepeat(string,uint256) (runs: 256, μ: 410257, ~: 413035)
317317
LibStringTest:testStringRepeatOriginal() (gas: 13235)
318-
LibStringTest:testStringReplace(uint256) (runs: 256, μ: 456871, ~: 637465)
318+
LibStringTest:testStringReplace(uint256) (runs: 256, μ: 453106, ~: 637465)
319319
LibStringTest:testStringReplaceLong() (gas: 9803)
320320
LibStringTest:testStringReplaceMedium() (gas: 8505)
321321
LibStringTest:testStringReplaceShort() (gas: 17530)
322322
LibStringTest:testStringRuneCount() (gas: 2501341)
323323
LibStringTest:testStringRuneCountDifferential(string) (runs: 256, μ: 9426, ~: 9432)
324324
LibStringTest:testStringSlice() (gas: 17767)
325-
LibStringTest:testStringSlice(uint256) (runs: 256, μ: 425536, ~: 633417)
325+
LibStringTest:testStringSlice(uint256) (runs: 256, μ: 427119, ~: 634413)
326326
LibStringTest:testStringSplit() (gas: 20287)
327-
LibStringTest:testStringSplit(uint256) (runs: 256, μ: 424564, ~: 260249)
327+
LibStringTest:testStringSplit(uint256) (runs: 256, μ: 425199, ~: 260249)
328328
LibStringTest:testStringStartsWith() (gas: 2566)
329-
LibStringTest:testStringStartsWith(uint256) (runs: 256, μ: 417689, ~: 629966)
329+
LibStringTest:testStringStartsWith(uint256) (runs: 256, μ: 419440, ~: 630464)
330330
LibStringTest:testStringUpperDifferential() (gas: 3308750)
331331
LibStringTest:testStringUpperDifferential(string) (runs: 256, μ: 11127, ~: 9292)
332332
LibStringTest:testStringUpperOriginal() (gas: 1759)
333-
LibStringTest:testToHexStringFixedLengthInsufficientLength() (gas: 3351)
334-
LibStringTest:testToHexStringFixedLengthPositiveNumberLong() (gas: 4152)
335-
LibStringTest:testToHexStringFixedLengthPositiveNumberShort() (gas: 926)
336-
LibStringTest:testToHexStringFixedLengthUint256Max() (gas: 4132)
337-
LibStringTest:testToHexStringFixedLengthZeroRightPadded(uint256,uint256) (runs: 256, μ: 9483, ~: 5412)
338-
LibStringTest:testToHexStringPositiveNumber() (gas: 818)
339-
LibStringTest:testToHexStringUint256Max() (gas: 3522)
340-
LibStringTest:testToHexStringZero() (gas: 753)
341-
LibStringTest:testToHexStringZeroRightPadded(uint256) (runs: 256, μ: 2063, ~: 1211)
342-
LibStringTest:testToStringPositiveNumber() (gas: 903)
343-
LibStringTest:testToStringPositiveNumberBrutalized() (gas: 627388)
344-
LibStringTest:testToStringSignedDifferential(int256) (runs: 256, μ: 429287, ~: 627373)
345-
LibStringTest:testToStringSignedGas() (gas: 8473)
346-
LibStringTest:testToStringSignedMemory(int256) (runs: 256, μ: 404183, ~: 190308)
347-
LibStringTest:testToStringSignedOriginalGas() (gas: 11059)
348-
LibStringTest:testToStringUint256Max() (gas: 6804)
349-
LibStringTest:testToStringUint256MaxBrutalized() (gas: 639165)
350-
LibStringTest:testToStringZero() (gas: 646)
351-
LibStringTest:testToStringZeroBrutalized() (gas: 626892)
352-
LibStringTest:testToStringZeroRightPadded(uint256) (runs: 256, μ: 411089, ~: 626802)
333+
LibStringTest:testToHexStringFixedLengthInsufficientLength() (gas: 3343)
334+
LibStringTest:testToHexStringFixedLengthPositiveNumberLong() (gas: 4144)
335+
LibStringTest:testToHexStringFixedLengthPositiveNumberShort() (gas: 918)
336+
LibStringTest:testToHexStringFixedLengthUint256Max() (gas: 4124)
337+
LibStringTest:testToHexStringFixedLengthZeroRightPadded(uint256,uint256) (runs: 256, μ: 9443, ~: 5385)
338+
LibStringTest:testToHexStringPositiveNumber() (gas: 817)
339+
LibStringTest:testToHexStringUint256Max() (gas: 3521)
340+
LibStringTest:testToHexStringZero() (gas: 752)
341+
LibStringTest:testToHexStringZeroRightPadded(uint256) (runs: 256, μ: 2062, ~: 1210)
342+
LibStringTest:testToStringPositiveNumber() (gas: 852)
343+
LibStringTest:testToStringPositiveNumberBrutalized() (gas: 627286)
344+
LibStringTest:testToStringSignedDifferential(int256) (runs: 256, μ: 428451, ~: 627351)
345+
LibStringTest:testToStringSignedGas() (gas: 8397)
346+
LibStringTest:testToStringSignedMemory(int256) (runs: 256, μ: 402037, ~: 189063)
347+
LibStringTest:testToStringSignedOriginalGas() (gas: 10983)
348+
LibStringTest:testToStringUint256Max() (gas: 5569)
349+
LibStringTest:testToStringUint256MaxBrutalized() (gas: 636695)
350+
LibStringTest:testToStringZero() (gas: 643)
351+
LibStringTest:testToStringZeroBrutalized() (gas: 626886)
352+
LibStringTest:testToStringZeroRightPadded(uint256) (runs: 256, μ: 410649, ~: 626799)
353353
MerkleProofLibTest:testVerifyMultiProof(bool,bool,bool,bool,bytes32) (runs: 256, μ: 413960, ~: 632782)
354354
MerkleProofLibTest:testVerifyMultiProofForHeightOneTree(bool,bool,bool,bool,bool,bool[]) (runs: 256, μ: 31951, ~: 30979)
355355
MerkleProofLibTest:testVerifyMultiProofForHeightTwoTree(bool,bool,bool,bool,bool,bytes32) (runs: 256, μ: 5656, ~: 5562)

src/utils/LibString.sol

+9-10
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,21 @@ library LibString {
3030
// The maximum value of a uint256 contains 78 digits (1 byte per digit), but
3131
// we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned.
3232
// We will need 1 word for the trailing zeros padding, 1 word for the length,
33-
// and 3 words for a maximum of 78 digits. Total: 5 * 0x20 = 0xa0.
34-
let m := add(mload(0x40), 0xa0)
33+
// and 3 words for a maximum of 78 digits.
34+
str := add(mload(0x40), 0x80)
3535
// Update the free memory pointer to allocate.
36-
mstore(0x40, m)
37-
// Assign the `str` to the end.
38-
str := sub(m, 0x20)
36+
mstore(0x40, add(str, 0x20))
3937
// Zeroize the slot after the string.
4038
mstore(str, 0)
4139

4240
// Cache the end of the memory to calculate the length later.
4341
let end := str
4442

43+
let w := not(0) // Tsk.
4544
// We write the string from rightmost digit to leftmost digit.
4645
// The following is essentially a do-while loop that also handles the zero case.
4746
for { let temp := value } 1 {} {
48-
str := sub(str, 1)
47+
str := add(str, w) // `sub(str, 1)`.
4948
// Write the character to the pointer.
5049
// The ASCII index of the '0' character is 48.
5150
mstore8(str, add(48, mod(temp, 10)))
@@ -72,6 +71,8 @@ library LibString {
7271
}
7372
/// @solidity memory-safe-assembly
7473
assembly {
74+
// We still have some spare memory space on the left,
75+
// as we have allocated 3 words (96 bytes) for up to 78 digits.
7576
let length := mload(str) // Load the string length.
7677
mstore(str, 0x2d) // Store the '-' character.
7778
str := sub(str, 1) // Move back the string pointer by a byte.
@@ -116,11 +117,9 @@ library LibString {
116117
// for the digits, 0x02 bytes for the prefix, and 0x20 bytes for the length.
117118
// We add 0x20 to the total and round down to a multiple of 0x20.
118119
// (0x20 + 0x20 + 0x02 + 0x20) = 0x62.
119-
let m := add(start, and(add(shl(1, length), 0x62), not(0x1f)))
120+
str := add(start, and(add(shl(1, length), 0x42), not(0x1f)))
120121
// Allocate the memory.
121-
mstore(0x40, m)
122-
// Assign the `str` to the end.
123-
str := sub(m, 0x20)
122+
mstore(0x40, add(str, 0x20))
124123
// Zeroize the slot after the string.
125124
mstore(str, 0)
126125

0 commit comments

Comments
 (0)