diff --git a/.gas-snapshot b/.gas-snapshot index e7e122e3d1..4a03a15e53 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,9 +1,9 @@ Base64Test:testBase64DecodeSentenceGas() (gas: 3673) Base64Test:testBase64DecodeShortStringGas() (gas: 906) Base64Test:testBase64EncodeDecode(bytes) (runs: 256, μ: 16948, ~: 12932) -Base64Test:testBase64EncodeDecodeAltModes(bytes) (runs: 256, μ: 517511, ~: 377223) +Base64Test:testBase64EncodeDecodeAltModes(bytes) (runs: 256, μ: 520084, ~: 390549) Base64Test:testBase64EncodeEmptyString() (gas: 953) -Base64Test:testBase64EncodeFileSafeAndNoPadding(bytes,bool,bool) (runs: 256, μ: 12311, ~: 6360) +Base64Test:testBase64EncodeFileSafeAndNoPadding(bytes,bool,bool) (runs: 256, μ: 12275, ~: 6360) Base64Test:testBase64EncodeSentence() (gas: 4877) Base64Test:testBase64EncodeShortStrings() (gas: 8594) Base64Test:testBase64EncodeToStringWithDoublePadding() (gas: 1723) @@ -277,73 +277,77 @@ LibSortTest:testUniquifySortedAddress() (gas: 4016) LibSortTest:testUniquifySortedAddress(address[]) (runs: 256, μ: 34333, ~: 31069) LibSortTest:testUniquifySortedDifferential(uint256[]) (runs: 256, μ: 29416, ~: 24501) LibSortTest:testUniquifySortedWithEmptyArray() (gas: 550) -LibStringTest:testAddressToHexStringZeroRightPadded(address) (runs: 256, μ: 3468, ~: 3468) -LibStringTest:testFromAddressToHexString() (gas: 3120) +LibStringTest:testAddressToHexStringZeroRightPadded(address) (runs: 256, μ: 3446, ~: 3446) +LibStringTest:testFromAddressToHexString() (gas: 3098) LibStringTest:testFromAddressToHexStringChecksumed() (gas: 35075) -LibStringTest:testFromAddressToHexStringChecksumedDifferential(uint256) (runs: 256, μ: 479991, ~: 637247) -LibStringTest:testFromAddressToHexStringWithLeadingZeros() (gas: 3076) -LibStringTest:testHexStringNoPrefixVariants(uint256,uint256) (runs: 256, μ: 415942, ~: 219170) +LibStringTest:testFromAddressToHexStringChecksumedDifferential(uint256) (runs: 256, μ: 424660, ~: 637248) +LibStringTest:testFromAddressToHexStringWithLeadingZeros() (gas: 3099) +LibStringTest:testHexStringNoPrefixVariants(uint256,uint256) (runs: 256, μ: 421080, ~: 219465) LibStringTest:testStringConcat() (gas: 7381) -LibStringTest:testStringConcat(string,string) (runs: 256, μ: 403227, ~: 187811) -LibStringTest:testStringConcatOriginal() (gas: 8003) -LibStringTest:testStringDirectReturn() (gas: 8171) -LibStringTest:testStringDirectReturn(string) (runs: 256, μ: 3681, ~: 3731) -LibStringTest:testStringEndsWith() (gas: 2829) -LibStringTest:testStringEndsWith(uint256) (runs: 256, μ: 388812, ~: 205901) -LibStringTest:testStringEq(string,string) (runs: 256, μ: 1574, ~: 1579) +LibStringTest:testStringConcat(string,string) (runs: 256, μ: 410122, ~: 629052) +LibStringTest:testStringConcatOriginal() (gas: 7981) +LibStringTest:testStringDirectReturn() (gas: 8105) +LibStringTest:testStringDirectReturn(string) (runs: 256, μ: 3647, ~: 3687) +LibStringTest:testStringEndsWith() (gas: 2807) +LibStringTest:testStringEndsWith(uint256) (runs: 256, μ: 409327, ~: 206526) +LibStringTest:testStringEq(string,string) (runs: 256, μ: 1552, ~: 1557) LibStringTest:testStringEscapeHTML() (gas: 11935) -LibStringTest:testStringEscapeHTML(uint256) (runs: 256, μ: 400147, ~: 218353) -LibStringTest:testStringEscapeJSON() (gas: 16965) -LibStringTest:testStringEscapeJSONHexEncode() (gas: 766230) +LibStringTest:testStringEscapeHTML(uint256) (runs: 256, μ: 439728, ~: 635347) +LibStringTest:testStringEscapeJSON() (gas: 16943) +LibStringTest:testStringEscapeJSONHexEncode() (gas: 323619) LibStringTest:testStringIndexOf() (gas: 17230) -LibStringTest:testStringIndexOf(uint256) (runs: 256, μ: 401814, ~: 216203) +LibStringTest:testStringIndexOf(uint256) (runs: 256, μ: 389749, ~: 213643) LibStringTest:testStringIndicesOf() (gas: 19437) -LibStringTest:testStringIndicesOf(uint256) (runs: 256, μ: 368140, ~: 213798) -LibStringTest:testStringLastIndexOf() (gas: 23187) -LibStringTest:testStringLastIndexOf(uint256) (runs: 256, μ: 388809, ~: 213627) +LibStringTest:testStringIndicesOf(uint256) (runs: 256, μ: 446328, ~: 635652) +LibStringTest:testStringLastIndexOf() (gas: 23210) +LibStringTest:testStringLastIndexOf(uint256) (runs: 256, μ: 435815, ~: 633445) LibStringTest:testStringLowerDifferential() (gas: 3332571) -LibStringTest:testStringLowerDifferential(string) (runs: 256, μ: 11055, ~: 9248) -LibStringTest:testStringLowerOriginal() (gas: 1716) -LibStringTest:testStringPackAndUnpackOne() (gas: 740030) -LibStringTest:testStringPackAndUnpackOne(string) (runs: 256, μ: 410123, ~: 627784) -LibStringTest:testStringPackAndUnpackOneDifferential(string) (runs: 256, μ: 409443, ~: 627168) -LibStringTest:testStringPackAndUnpackTwo() (gas: 937045) -LibStringTest:testStringPackAndUnpackTwo(string,string) (runs: 256, μ: 418735, ~: 629628) -LibStringTest:testStringPackAndUnpackTwoDifferential(string,string) (runs: 256, μ: 398315, ~: 185787) +LibStringTest:testStringLowerDifferential(string) (runs: 256, μ: 11106, ~: 9271) +LibStringTest:testStringLowerOriginal() (gas: 1739) +LibStringTest:testStringPackAndUnpackOne() (gas: 740053) +LibStringTest:testStringPackAndUnpackOne(string) (runs: 256, μ: 387595, ~: 185319) +LibStringTest:testStringPackAndUnpackOneDifferential(string) (runs: 256, μ: 360993, ~: 184794) +LibStringTest:testStringPackAndUnpackTwo() (gas: 809512) +LibStringTest:testStringPackAndUnpackTwo(string,string) (runs: 256, μ: 399709, ~: 187033) +LibStringTest:testStringPackAndUnpackTwoDifferential(string,string) (runs: 256, μ: 400045, ~: 185895) LibStringTest:testStringRepeat() (gas: 9107) -LibStringTest:testStringRepeat(string,uint256) (runs: 256, μ: 439456, ~: 628061) +LibStringTest:testStringRepeat(string,uint256) (runs: 256, μ: 399902, ~: 194189) LibStringTest:testStringRepeatOriginal() (gas: 13235) -LibStringTest:testStringReplace(uint256) (runs: 256, μ: 409332, ~: 227178) -LibStringTest:testStringReplaceLong() (gas: 9738) -LibStringTest:testStringReplaceMedium() (gas: 8527) +LibStringTest:testStringReplace(uint256) (runs: 256, μ: 451698, ~: 637465) +LibStringTest:testStringReplaceLong() (gas: 9803) +LibStringTest:testStringReplaceMedium() (gas: 8505) LibStringTest:testStringReplaceShort() (gas: 17530) -LibStringTest:testStringRuneCount() (gas: 2535116) -LibStringTest:testStringRuneCountDifferential(string) (runs: 256, μ: 9437, ~: 9378) +LibStringTest:testStringRuneCount() (gas: 2501341) +LibStringTest:testStringRuneCountDifferential(string) (runs: 256, μ: 9426, ~: 9432) LibStringTest:testStringSlice() (gas: 17767) -LibStringTest:testStringSlice(uint256) (runs: 256, μ: 396155, ~: 215986) +LibStringTest:testStringSlice(uint256) (runs: 256, μ: 432228, ~: 634704) LibStringTest:testStringSplit() (gas: 20287) -LibStringTest:testStringSplit(uint256) (runs: 256, μ: 497630, ~: 640480) -LibStringTest:testStringStartsWith() (gas: 2588) -LibStringTest:testStringStartsWith(uint256) (runs: 256, μ: 423252, ~: 630845) +LibStringTest:testStringSplit(uint256) (runs: 256, μ: 428161, ~: 445755) +LibStringTest:testStringStartsWith() (gas: 2566) +LibStringTest:testStringStartsWith(uint256) (runs: 256, μ: 417668, ~: 630183) LibStringTest:testStringUpperDifferential() (gas: 3308750) -LibStringTest:testStringUpperDifferential(string) (runs: 256, μ: 11033, ~: 9226) -LibStringTest:testStringUpperOriginal() (gas: 1781) +LibStringTest:testStringUpperDifferential(string) (runs: 256, μ: 11127, ~: 9292) +LibStringTest:testStringUpperOriginal() (gas: 1759) LibStringTest:testToHexStringFixedLengthInsufficientLength() (gas: 3351) -LibStringTest:testToHexStringFixedLengthPositiveNumberLong() (gas: 4108) +LibStringTest:testToHexStringFixedLengthPositiveNumberLong() (gas: 4152) LibStringTest:testToHexStringFixedLengthPositiveNumberShort() (gas: 926) -LibStringTest:testToHexStringFixedLengthUint256Max() (gas: 4109) -LibStringTest:testToHexStringFixedLengthZeroRightPadded(uint256,uint256) (runs: 256, μ: 9407, ~: 5527) -LibStringTest:testToHexStringPositiveNumber() (gas: 840) -LibStringTest:testToHexStringUint256Max() (gas: 3478) -LibStringTest:testToHexStringZero() (gas: 731) -LibStringTest:testToHexStringZeroRightPadded(uint256) (runs: 256, μ: 2040, ~: 1188) -LibStringTest:testToStringPositiveNumber() (gas: 839) -LibStringTest:testToStringPositiveNumberBrutalized() (gas: 1700) -LibStringTest:testToStringUint256Max() (gas: 6803) -LibStringTest:testToStringUint256MaxBrutalized() (gas: 13424) +LibStringTest:testToHexStringFixedLengthUint256Max() (gas: 4132) +LibStringTest:testToHexStringFixedLengthZeroRightPadded(uint256,uint256) (runs: 256, μ: 9397, ~: 5584) +LibStringTest:testToHexStringPositiveNumber() (gas: 818) +LibStringTest:testToHexStringUint256Max() (gas: 3522) +LibStringTest:testToHexStringZero() (gas: 753) +LibStringTest:testToHexStringZeroRightPadded(uint256) (runs: 256, μ: 2063, ~: 1211) +LibStringTest:testToStringPositiveNumber() (gas: 903) +LibStringTest:testToStringPositiveNumberBrutalized() (gas: 1678) +LibStringTest:testToStringSignedDifferential(int256) (runs: 256, μ: 5875, ~: 2991) +LibStringTest:testToStringSignedGas() (gas: 8473) +LibStringTest:testToStringSignedMemory(int256) (runs: 256, μ: 3220, ~: 1869) +LibStringTest:testToStringSignedOriginalGas() (gas: 11059) +LibStringTest:testToStringUint256Max() (gas: 6804) +LibStringTest:testToStringUint256MaxBrutalized() (gas: 13468) LibStringTest:testToStringZero() (gas: 646) -LibStringTest:testToStringZeroBrutalized() (gas: 1204) -LibStringTest:testToStringZeroRightPadded(uint256) (runs: 256, μ: 3165, ~: 1325) +LibStringTest:testToStringZeroBrutalized() (gas: 1182) +LibStringTest:testToStringZeroRightPadded(uint256) (runs: 256, μ: 3209, ~: 1369) MerkleProofLibTest:testVerifyMultiProof(bool,bool,bool,bool,bytes32) (runs: 256, μ: 413960, ~: 632782) MerkleProofLibTest:testVerifyMultiProofForHeightOneTree(bool,bool,bool,bool,bool,bool[]) (runs: 256, μ: 31951, ~: 30979) MerkleProofLibTest:testVerifyMultiProofForHeightTwoTree(bool,bool,bool,bool,bool,bytes32) (runs: 256, μ: 5656, ~: 5562) diff --git a/src/utils/LibString.sol b/src/utils/LibString.sol index 87b03f87fd..92d9c2daa3 100644 --- a/src/utils/LibString.sol +++ b/src/utils/LibString.sol @@ -62,6 +62,23 @@ library LibString { } } + /// @dev Returns the base 10 decimal representation of `value`. + function toString(int256 value) internal pure returns (string memory str) { + if (value >= 0) { + return toString(uint256(value)); + } + unchecked { + str = toString(uint256(-value)); + } + /// @solidity memory-safe-assembly + assembly { + let length := mload(str) // Load the string length. + mstore(str, 0x2d) // Store the '-' character. + str := sub(str, 1) // Move back the string pointer by a byte. + mstore(str, add(length, 1)) // Update the string length. + } + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* HEXADECIMAL OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ diff --git a/test/LibString.t.sol b/test/LibString.t.sol index e4c95e9f32..cee4af8e49 100644 --- a/test/LibString.t.sol +++ b/test/LibString.t.sol @@ -6,11 +6,11 @@ import {LibString} from "../src/utils/LibString.sol"; contract LibStringTest is TestPlus { function testToStringZero() public { - assertEq(keccak256(bytes(LibString.toString(0))), keccak256(bytes("0"))); + assertEq(keccak256(bytes(LibString.toString(uint256(0)))), keccak256(bytes("0"))); } function testToStringPositiveNumber() public { - assertEq(keccak256(bytes(LibString.toString(4132))), keccak256(bytes("4132"))); + assertEq(keccak256(bytes(LibString.toString(uint256(4132)))), keccak256(bytes("4132"))); } function testToStringUint256Max() public { @@ -25,13 +25,13 @@ contract LibStringTest is TestPlus { } function testToStringZeroBrutalized() public { - string memory s0 = LibString.toString(0); + string memory s0 = LibString.toString(uint256(0)); /// @solidity memory-safe-assembly assembly { mstore(mload(0x40), not(0)) mstore(0x40, add(mload(0x40), 0x20)) } - string memory s1 = LibString.toString(0); + string memory s1 = LibString.toString(uint256(0)); /// @solidity memory-safe-assembly assembly { mstore(mload(0x40), not(0)) @@ -42,13 +42,13 @@ contract LibStringTest is TestPlus { } function testToStringPositiveNumberBrutalized() public { - string memory s0 = LibString.toString(4132); + string memory s0 = LibString.toString(uint256(4132)); /// @solidity memory-safe-assembly assembly { mstore(mload(0x40), not(0)) mstore(0x40, add(mload(0x40), 0x20)) } - string memory s1 = LibString.toString(4132); + string memory s1 = LibString.toString(uint256(4132)); /// @solidity memory-safe-assembly assembly { mstore(mload(0x40), not(0)) @@ -93,6 +93,45 @@ contract LibStringTest is TestPlus { _checkMemory(LibString.toString(x)); } + function testToStringSignedDifferential(int256 x) public { + assertEq(LibString.toString(x), _toStringSignedOriginal(x)); + } + + function testToStringSignedMemory(int256 x) public pure { + _roundUpFreeMemoryPointer(); + uint256 freeMemoryPointer; + /// @solidity memory-safe-assembly + assembly { + freeMemoryPointer := mload(0x40) + } + string memory str = LibString.toString(x); + /// @solidity memory-safe-assembly + assembly { + if lt(str, freeMemoryPointer) { revert(0, 0) } + } + _checkMemory(str); + } + + function testToStringSignedGas() public pure { + for (int256 x = -10; x < 10; ++x) { + LibString.toString(x); + } + } + + function testToStringSignedOriginalGas() public pure { + for (int256 x = -10; x < 10; ++x) { + _toStringSignedOriginal(x); + } + } + + function _toStringSignedOriginal(int256 x) internal pure returns (string memory) { + unchecked { + return x >= 0 + ? LibString.toString(uint256(x)) + : string(abi.encodePacked("-", LibString.toString(uint256(-x)))); + } + } + function testToHexStringZero() public { assertEq(keccak256(bytes(LibString.toHexString(0))), keccak256(bytes("0x00"))); } @@ -323,6 +362,7 @@ contract LibStringTest is TestPlus { unchecked { string memory runes = new string(256); for (uint256 i; i < 256; ++i) { + /// @solidity memory-safe-assembly assembly { mstore8(add(add(runes, 0x20), i), i) } @@ -1050,6 +1090,7 @@ contract LibStringTest is TestPlus { unchecked { string memory ascii = new string(128); for (uint256 i; i < 128; ++i) { + /// @solidity memory-safe-assembly assembly { mstore8(add(add(ascii, 0x20), i), i) } @@ -1077,6 +1118,7 @@ contract LibStringTest is TestPlus { unchecked { string memory ascii = new string(128); for (uint256 i; i < 128; ++i) { + /// @solidity memory-safe-assembly assembly { mstore8(add(add(ascii, 0x20), i), i) }