From 2e5d62b87d3a889229b424f899256d827d268936 Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 7 Dec 2022 14:15:49 +0300 Subject: [PATCH] feat: add getAfterStrict, more tests --- src/Slice.sol | 21 ++++++-- src/StrSlice.sol | 2 +- test/Slice.t.sol | 125 +++++++++++++++++++++++++++++++++-------------- 3 files changed, 106 insertions(+), 42 deletions(-) diff --git a/src/Slice.sol b/src/Slice.sol index 58f267f..d794a3a 100644 --- a/src/Slice.sol +++ b/src/Slice.sol @@ -77,7 +77,7 @@ using { cmp, eq, ne, lt, lte, gt, gte, // index get, first, last, - splitAt, getSubslice, getBefore, getAfter, + splitAt, getSubslice, getBefore, getAfter, getAfterStrict, // search find, rfind, contains, startsWith, endsWith, @@ -144,6 +144,8 @@ function toBytes32(Slice self) pure returns (bytes32 b) { /** * @dev Returns keccak256 of all the bytes of `Slice`. + * Note that for any `bytes memory b`, keccak256(b) == b.toSlice().keccak() + * (keccak256 does not include the length byte) */ function keccak(Slice self) pure returns (bytes32 result) { uint256 selfPtr = self.ptr(); @@ -349,17 +351,30 @@ function getBefore(Slice self, uint256 index) pure returns (Slice) { /** * @dev Returns a subslice [index:] of `self`. - * Reverts if `index` >= length. + * Reverts if `index` > length. */ function getAfter(Slice self, uint256 index) pure returns (Slice) { uint256 selfLen = self.len(); - if (index >= selfLen) revert Slice__OutOfBounds(); + if (index > selfLen) revert Slice__OutOfBounds(); // safe because index <= selfLen (ptr+len is implicitly safe) unchecked { return Slice__.fromRawParts(self.ptr() + index, selfLen - index); } } +/** + * @dev Returns a non-zero subslice [index:] of `self`. + * Reverts if `index` >= length. + */ +function getAfterStrict(Slice self, uint256 index) pure returns (Slice) { + uint256 selfLen = self.len(); + if (index >= selfLen) revert Slice__OutOfBounds(); + // safe because index < selfLen (ptr+len is implicitly safe) + unchecked { + return Slice__.fromRawParts(self.ptr() + index, selfLen - index); + } +} + /** * @dev Returns the byte index of the first slice of `self` that matches `pattern`. * Returns type(uint256).max if the `pattern` does not match. diff --git a/src/StrSlice.sol b/src/StrSlice.sol index 2a4fe9e..1f418bd 100644 --- a/src/StrSlice.sol +++ b/src/StrSlice.sol @@ -225,7 +225,7 @@ function isCharBoundary(StrSlice self, uint256 index) pure returns (bool) { * Reverts if index is out of bounds. */ function get(StrSlice self, uint256 index) pure returns (StrChar char) { - bytes32 b = self.asSlice().getAfter(index).toBytes32(); + bytes32 b = self.asSlice().getAfterStrict(index).toBytes32(); if (!isValidUtf8(b)) revert StrSlice__InvalidCharBoundary(); return StrChar__.fromValidUtf8(b); } diff --git a/test/Slice.t.sol b/test/Slice.t.sol index fa8a958..ec063d8 100644 --- a/test/Slice.t.sol +++ b/test/Slice.t.sol @@ -19,13 +19,52 @@ contract SliceTest is PRBTest, Assertions { } // skipping cmp tests, that's covered by SliceAssertionsTest - // TODO explicitly test stuff like copyFromSlice,startsWith etc - // (tho it's likely fine due to other tests) function testLen(bytes calldata _b) public { assertEq(_b.toSlice().len(), _b.length); } + function testIsEmpty() public { + assertTrue(bytes("").toSlice().isEmpty()); + assertFalse(new bytes(1).toSlice().isEmpty()); + } + + function testToBytes(bytes calldata _b) public { + assertEq(_b, _b.toSlice().toBytes()); + } + + function testToBytes32(bytes memory _b) public { + bytes32 b32; + if (_b.length > 0) { + /// @solidity memory-safe-assembly + assembly { + b32 := mload(add(_b, 0x20)) + } + } + assertEq(b32, _b.toSlice().toBytes32()); + } + + function testKeccak__Eq(bytes calldata _b) public { + bytes memory b1 = _b; + bytes memory b2 = _b; + + assertEq(b1.toSlice().keccak(), b2.toSlice().keccak()); + assertEq(keccak256(b1), keccak256(b2)); + assertEq(b1.toSlice().keccak(), keccak256(b1)); + } + + function testKeccak__NotEq(bytes calldata _b) public { + vm.assume(_b.length > 0); + bytes memory b1 = _b; + bytes memory b2 = _b; + + uint256 i = uint256(keccak256(abi.encode(_b, "i"))) % _b.length; + b1[i] ^= 0x01; + assertEq(b1.toSlice().keccak(), keccak256(b1)); + assertNotEq(b1.toSlice().keccak(), b2.toSlice().keccak()); + assertNotEq(keccak256(b1), keccak256(b2)); + } + /*////////////////////////////////////////////////////////////////////////// CONCATENATION //////////////////////////////////////////////////////////////////////////*/ @@ -34,10 +73,7 @@ contract SliceTest is PRBTest, Assertions { bytes memory b1 = _b[:_b.length / 2]; bytes memory b2 = _b[_b.length / 2:]; - assertEq( - keccak256(b1.toSlice().add(b2.toSlice())), - keccak256(_b) - ); + assertEq(b1.toSlice().add(b2.toSlice()), _b); } function testJoin__EmptySeparator(bytes calldata _b) public { @@ -49,10 +85,7 @@ contract SliceTest is PRBTest, Assertions { slices[0] = b1.toSlice(); slices[1] = b2.toSlice(); - assertEq( - keccak256(sep.toSlice().join(slices)), - keccak256(_b) - ); + assertEq(sep.toSlice().join(slices), _b); } function testJoin__RandomSeparator(bytes calldata _b) public { @@ -66,10 +99,7 @@ contract SliceTest is PRBTest, Assertions { slices[1] = b2.toSlice(); slices[2] = b3.toSlice(); - assertEq( - keccak256(sep.toSlice().join(slices)), - keccak256(abi.encodePacked(b1, sep, b2, sep, b3)) - ); + assertEq(sep.toSlice().join(slices), abi.encodePacked(b1, sep, b2, sep, b3)); } function testJoin__ArrayLen1(bytes calldata _b) public { @@ -79,10 +109,7 @@ contract SliceTest is PRBTest, Assertions { Slice[] memory slices = new Slice[](1); slices[0] = b1.toSlice(); - assertEq( - keccak256(sep.toSlice().join(slices)), - keccak256(abi.encodePacked(b1)) - ); + assertEq(sep.toSlice().join(slices), abi.encodePacked(b1)); } function testJoin__ArrayLen0() public { @@ -90,10 +117,7 @@ contract SliceTest is PRBTest, Assertions { Slice[] memory slices; - assertEq( - keccak256(sep.toSlice().join(slices)), - keccak256('') - ); + assertEq(sep.toSlice().join(slices), ''); } /*////////////////////////////////////////////////////////////////////////// @@ -124,30 +148,24 @@ contract SliceTest is PRBTest, Assertions { Slice slice = _b.toSlice(); (Slice s1, Slice s2) = slice.splitAt(_b.length / 2); assertEq( - keccak256(abi.encodePacked( - s1.copyToBytes(), s2.copyToBytes() - )), - keccak256(_b) + abi.encodePacked( + s1.toBytes(), s2.toBytes() + ), + _b ); } function testSplitAt__0(bytes calldata _b) public { Slice slice = _b.toSlice(); (Slice s1, Slice s2) = slice.splitAt(0); - assertEq( - keccak256(s2.copyToBytes()), - keccak256(_b) - ); + assertEq(s2.toBytes(), _b); assertEq(s1.len(), 0); } function testSplitAt__Length(bytes calldata _b) public { Slice slice = _b.toSlice(); (Slice s1, Slice s2) = slice.splitAt(_b.length); - assertEq( - keccak256(s1.copyToBytes()), - keccak256(_b) - ); + assertEq(s1.toBytes(), _b); assertEq(s2.len(), 0); } @@ -157,10 +175,7 @@ contract SliceTest is PRBTest, Assertions { uint256 end = _b.length == 0 ? 0 : uint256(keccak256(abi.encode(_b, "end"))) % _b.length; vm.assume(start <= end); Slice subslice = _b.toSlice().getSubslice(start, end); - assertEq( - keccak256(subslice.copyToBytes()), - keccak256(_b[start:end]) - ); + assertEq(subslice.toBytes(), _b[start:end]); } function testGetSubslice__RevertStartAfterEnd(bytes calldata _b) public { @@ -172,6 +187,40 @@ contract SliceTest is PRBTest, Assertions { _b.toSlice().getSubslice(start, end); } + function testGetBefore(bytes calldata _b) public { + Slice s1 = _b.toSlice().getBefore(_b.length / 2); + assertEq(s1, _b[:_b.length / 2]); + } + + function testGetBefore_RevertOutOfBounds() public { + bytes memory _b; + vm.expectRevert(Slice__OutOfBounds.selector); + _b.toSlice().getBefore(1); + } + + function testGetAfter(bytes calldata _b) public { + Slice s1 = _b.toSlice().getAfter(_b.length / 2); + assertEq(s1, _b[_b.length / 2:]); + } + + function testGetAfter_RevertOutOfBounds() public { + bytes memory _b; + vm.expectRevert(Slice__OutOfBounds.selector); + _b.toSlice().getAfter(1); + } + + function testGetAfterStrict(bytes calldata _b) public { + vm.assume(_b.length > 0); + Slice s1 = _b.toSlice().getAfterStrict(_b.length / 2); + assertEq(s1, _b[_b.length / 2:]); + } + + function testGetAfterStrict_RevertOutOfBounds() public { + bytes memory _b; + vm.expectRevert(Slice__OutOfBounds.selector); + _b.toSlice().getAfterStrict(0); + } + /*////////////////////////////////////////////////////////////////////////// FIND //////////////////////////////////////////////////////////////////////////*/