diff --git a/src/utils/DynamicArrayLib.sol b/src/utils/DynamicArrayLib.sol index 36a0c8681..2465e3846 100644 --- a/src/utils/DynamicArrayLib.sol +++ b/src/utils/DynamicArrayLib.sol @@ -15,6 +15,13 @@ library DynamicArrayLib { uint256[] data; } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The constant returned when the element is not found in the array. + uint256 internal constant NOT_FOUND = type(uint256).max; + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* UINT256 ARRAY OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -97,6 +104,107 @@ library DynamicArrayLib { } } + /// @dev Returns a copy of `array` sliced from `start` to `end` (exclusive). + function slice(uint256[] memory array, uint256 start, uint256 end) + internal + pure + returns (uint256[] memory result) + { + /// @solidity memory-safe-assembly + assembly { + let arrayLen := mload(array) + if iszero(gt(arrayLen, end)) { end := arrayLen } + if iszero(gt(arrayLen, start)) { start := arrayLen } + if lt(start, end) { + result := mload(0x40) + let resultLen := sub(end, start) + mstore(result, resultLen) + array := add(array, shl(5, start)) + // Copy the `array` one word at a time, backwards. + let o := add(shl(5, resultLen), 0x20) + mstore(0x40, add(result, o)) // Allocate memory. + for {} 1 {} { + mstore(add(result, o), mload(add(array, o))) + o := sub(o, 0x20) + if iszero(o) { break } + } + } + } + } + + /// @dev Returns if `needle` is in `array`. + function contains(uint256[] memory array, uint256 needle) internal pure returns (bool) { + return ~indexOf(array, needle, 0) != 0; + } + + /// @dev Returns the first index of `needle`, scanning forward from `from`. + /// If `needle` is not in `array`, returns `NOT_FOUND`. + function indexOf(uint256[] memory array, uint256 needle, uint256 from) + internal + pure + returns (uint256 result) + { + /// @solidity memory-safe-assembly + assembly { + result := not(0) + if lt(from, mload(array)) { + let o := add(array, shl(5, from)) + let end := add(shl(5, add(1, mload(array))), array) + let c := mload(end) // Cache the word after the array. + for { mstore(end, needle) } 1 {} { + o := add(o, 0x20) + if eq(mload(o), needle) { break } + } + mstore(end, c) // Restore the word after the array. + if iszero(eq(o, end)) { result := shr(5, sub(o, add(0x20, array))) } + } + } + } + + /// @dev Returns the first index of `needle`. + /// If `needle` is not in `array`, returns `NOT_FOUND`. + function indexOf(uint256[] memory array, uint256 needle) + internal + pure + returns (uint256 result) + { + result = indexOf(array, needle, 0); + } + + /// @dev Returns the last index of `needle`, scanning backwards from `from`. + /// If `needle` is not in `array`, returns `NOT_FOUND`. + function lastIndexOf(uint256[] memory array, uint256 needle, uint256 from) + internal + pure + returns (uint256 result) + { + /// @solidity memory-safe-assembly + assembly { + result := not(0) + let n := mload(array) + if n { + if iszero(lt(from, n)) { from := sub(n, 1) } + let o := add(shl(5, add(2, from)), array) + for { mstore(array, needle) } 1 {} { + o := sub(o, 0x20) + if eq(mload(o), needle) { break } + } + mstore(array, n) // Restore the length of the array. + if iszero(eq(o, array)) { result := shr(5, sub(o, add(0x20, array))) } + } + } + } + + /// @dev Returns the first index of `needle`. + /// If `needle` is not in `array`, returns `NOT_FOUND`. + function lastIndexOf(uint256[] memory array, uint256 needle) + internal + pure + returns (uint256 result) + { + result = lastIndexOf(array, needle, NOT_FOUND); + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* DYNAMIC ARRAY OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -574,29 +682,7 @@ library DynamicArrayLib { pure returns (DynamicArray memory result) { - /// @solidity memory-safe-assembly - assembly { - let arrData := mload(array) - let arrDataLen := mload(arrData) - if iszero(gt(arrDataLen, end)) { end := arrDataLen } - if iszero(gt(arrDataLen, start)) { start := arrDataLen } - if lt(start, end) { - let resultData := mload(0x40) - let resultDataLen := sub(end, start) - mstore(resultData, resultDataLen) - arrData := add(arrData, shl(5, start)) - let w := not(0x1f) - // Copy the `arrData` one word at a time, backwards. - let o := add(shl(5, resultDataLen), 0x20) - mstore(0x40, add(resultData, o)) // Allocate memory. - for {} 1 {} { - mstore(add(resultData, o), mload(add(arrData, o))) - o := add(o, w) // `sub(o, 0x20)`. - if iszero(o) { break } - } - mstore(result, resultData) - } - } + result.data = slice(array.data, start, end); } /// @dev Returns a copy of `array` sliced from `start` to the end of the array. @@ -605,8 +691,130 @@ library DynamicArrayLib { pure returns (DynamicArray memory result) { - _deallocate(result); - result = slice(array, start, type(uint256).max); + result.data = slice(array.data, start, type(uint256).max); + } + + /// @dev Returns if `needle` is in `array`. + function contains(DynamicArray memory array, uint256 needle) internal pure returns (bool) { + return ~indexOf(array.data, needle, 0) != 0; + } + + /// @dev Returns if `needle` is in `array`. + function contains(DynamicArray memory array, address needle) internal pure returns (bool) { + return ~indexOf(array.data, uint160(needle), 0) != 0; + } + + /// @dev Returns if `needle` is in `array`. + function contains(DynamicArray memory array, bytes32 needle) internal pure returns (bool) { + return ~indexOf(array.data, uint256(needle), 0) != 0; + } + + /// @dev Returns the first index of `needle`, scanning forward from `from`. + /// If `needle` is not in `array`, returns `NOT_FOUND`. + function indexOf(DynamicArray memory array, uint256 needle, uint256 from) + internal + pure + returns (uint256) + { + return indexOf(array.data, needle, from); + } + + /// @dev Returns the first index of `needle`, scanning forward from `from`. + /// If `needle` is not in `array`, returns `NOT_FOUND`. + function indexOf(DynamicArray memory array, address needle, uint256 from) + internal + pure + returns (uint256) + { + return indexOf(array.data, uint160(needle), from); + } + + /// @dev Returns the first index of `needle`, scanning forward from `from`. + /// If `needle` is not in `array`, returns `NOT_FOUND`. + function indexOf(DynamicArray memory array, bytes32 needle, uint256 from) + internal + pure + returns (uint256) + { + return indexOf(array.data, uint256(needle), from); + } + + /// @dev Returns the first index of `needle`. + /// If `needle` is not in `array`, returns `NOT_FOUND`. + function indexOf(DynamicArray memory array, uint256 needle) internal pure returns (uint256) { + return indexOf(array.data, needle, 0); + } + + /// @dev Returns the first index of `needle`. + /// If `needle` is not in `array`, returns `NOT_FOUND`. + function indexOf(DynamicArray memory array, address needle) internal pure returns (uint256) { + return indexOf(array.data, uint160(needle), 0); + } + + /// @dev Returns the first index of `needle`. + /// If `needle` is not in `array`, returns `NOT_FOUND`. + function indexOf(DynamicArray memory array, bytes32 needle) internal pure returns (uint256) { + return indexOf(array.data, uint256(needle), 0); + } + + /// @dev Returns the last index of `needle`, scanning backwards from `from`. + /// If `needle` is not in `array`, returns `NOT_FOUND`. + function lastIndexOf(DynamicArray memory array, uint256 needle, uint256 from) + internal + pure + returns (uint256) + { + return lastIndexOf(array.data, needle, from); + } + + /// @dev Returns the last index of `needle`, scanning backwards from `from`. + /// If `needle` is not in `array`, returns `NOT_FOUND`. + function lastIndexOf(DynamicArray memory array, address needle, uint256 from) + internal + pure + returns (uint256) + { + return lastIndexOf(array.data, uint160(needle), from); + } + + /// @dev Returns the last index of `needle`, scanning backwards from `from`. + /// If `needle` is not in `array`, returns `NOT_FOUND`. + function lastIndexOf(DynamicArray memory array, bytes32 needle, uint256 from) + internal + pure + returns (uint256) + { + return lastIndexOf(array.data, uint256(needle), from); + } + + /// @dev Returns the last index of `needle`. + /// If `needle` is not in `array`, returns `NOT_FOUND`. + function lastIndexOf(DynamicArray memory array, uint256 needle) + internal + pure + returns (uint256) + { + return lastIndexOf(array.data, needle, NOT_FOUND); + } + + /// @dev Returns the last index of `needle`. + /// If `needle` is not in `array`, returns `NOT_FOUND`. + function lastIndexOf(DynamicArray memory array, address needle) + internal + pure + returns (uint256) + { + return lastIndexOf(array.data, uint160(needle), NOT_FOUND); + } + + /// @dev Returns the last index of `needle`. + /// If `needle` is not in `array`, returns `NOT_FOUND`. + function lastIndexOf(DynamicArray memory array, bytes32 needle) + internal + pure + returns (uint256) + { + return lastIndexOf(array.data, uint256(needle), NOT_FOUND); } /// @dev Equivalent to `keccak256(abi.encodePacked(array.data))`. diff --git a/test/DynamicArrayLib.t.sol b/test/DynamicArrayLib.t.sol index c8f6a8434..ae87a9d98 100644 --- a/test/DynamicArrayLib.t.sol +++ b/test/DynamicArrayLib.t.sol @@ -214,6 +214,190 @@ contract DynamicArrayLibTest is SoladyTest { } } + function testUint256Contains() public { + uint256 n = 50; + uint256[] memory a; + assertEq(DynamicArrayLib.contains(a, 0), false); + assertEq(DynamicArrayLib.contains(a, 1), false); + assertEq(DynamicArrayLib.contains(a, 2), false); + a = new uint256[](0); + assertEq(DynamicArrayLib.contains(a, 0), false); + assertEq(DynamicArrayLib.contains(a, 1), false); + assertEq(DynamicArrayLib.contains(a, 2), false); + a = new uint256[](1); + assertEq(DynamicArrayLib.contains(a, 0), true); + assertEq(DynamicArrayLib.contains(a, 1), false); + assertEq(DynamicArrayLib.contains(a, 2), false); + a = DynamicArrayLib.malloc(n); + unchecked { + for (uint256 i; i != n; ++i) { + a.set(i, i); + } + } + assertEq(DynamicArrayLib.contains(a, 0), true); + assertEq(DynamicArrayLib.contains(a, 1), true); + assertEq(DynamicArrayLib.contains(a, 10), true); + assertEq(DynamicArrayLib.contains(a, 31), true); + assertEq(DynamicArrayLib.contains(a, 32), true); + assertEq(DynamicArrayLib.contains(a, 49), true); + assertEq(DynamicArrayLib.contains(a, 50), false); + assertEq(DynamicArrayLib.contains(a, 100), false); + } + + function testUint256ArrayIndexOf() public { + uint256 n = 50; + uint256[] memory a; + assertEq(DynamicArrayLib.indexOf(a, 0), DynamicArrayLib.NOT_FOUND); + assertEq(DynamicArrayLib.indexOf(a, 1), DynamicArrayLib.NOT_FOUND); + assertEq(DynamicArrayLib.indexOf(a, 2), DynamicArrayLib.NOT_FOUND); + a = new uint256[](0); + assertEq(DynamicArrayLib.indexOf(a, 0), DynamicArrayLib.NOT_FOUND); + assertEq(DynamicArrayLib.indexOf(a, 1), DynamicArrayLib.NOT_FOUND); + assertEq(DynamicArrayLib.indexOf(a, 2), DynamicArrayLib.NOT_FOUND); + a = new uint256[](1); + assertEq(DynamicArrayLib.indexOf(a, 0), 0); + assertEq(DynamicArrayLib.indexOf(a, 1), DynamicArrayLib.NOT_FOUND); + assertEq(DynamicArrayLib.indexOf(a, 2), DynamicArrayLib.NOT_FOUND); + a = DynamicArrayLib.malloc(n); + unchecked { + for (uint256 i; i != n; ++i) { + a.set(i, i); + } + } + assertEq(DynamicArrayLib.indexOf(a, 0), 0); + assertEq(DynamicArrayLib.indexOf(a, 1), 1); + assertEq(DynamicArrayLib.indexOf(a, 10), 10); + assertEq(DynamicArrayLib.indexOf(a, 31), 31); + assertEq(DynamicArrayLib.indexOf(a, 32), 32); + assertEq(DynamicArrayLib.indexOf(a, 49), 49); + assertEq(DynamicArrayLib.indexOf(a, 50), DynamicArrayLib.NOT_FOUND); + assertEq(DynamicArrayLib.indexOf(a, 100), DynamicArrayLib.NOT_FOUND); + } + + function testUint256ArrayIndexOfDifferential( + uint256[] memory array, + uint256 needle, + uint256 from + ) public { + if (_randomChance(2)) _misalignFreeMemoryPointer(); + if (_randomChance(8)) _brutalizeMemory(); + from = _bound(from, 0, array.length + 10); + uint256 computed = DynamicArrayLib.indexOf(array, needle, from); + assertEq(computed, _indexOfOriginal(array, needle, from)); + if (_randomChance(16)) { + computed = DynamicArrayLib.indexOf(array, needle); + assertEq(computed, _indexOfOriginal(array, needle)); + computed = DynamicArrayLib.indexOf(DynamicArrayLib.DynamicArray(array), needle); + assertEq(computed, _indexOfOriginal(array, needle)); + } + } + + function _indexOfOriginal(uint256[] memory array, uint256 needle) + internal + pure + returns (uint256) + { + return _indexOfOriginal(array, needle, 0); + } + + function _indexOfOriginal(uint256[] memory array, uint256 needle, uint256 from) + internal + pure + returns (uint256) + { + unchecked { + uint256 n = array.length; + for (uint256 i = from; i < n; ++i) { + if (array[i] == needle) return i; + } + } + return type(uint256).max; + } + + function testUint256ArrayLastIndexOf() public { + uint256 n = 50; + uint256[] memory a; + assertEq(DynamicArrayLib.lastIndexOf(a, 0), DynamicArrayLib.NOT_FOUND); + assertEq(DynamicArrayLib.lastIndexOf(a, 1), DynamicArrayLib.NOT_FOUND); + assertEq(DynamicArrayLib.lastIndexOf(a, 2), DynamicArrayLib.NOT_FOUND); + a = new uint256[](0); + assertEq(DynamicArrayLib.lastIndexOf(a, 0), DynamicArrayLib.NOT_FOUND); + assertEq(DynamicArrayLib.lastIndexOf(a, 1), DynamicArrayLib.NOT_FOUND); + assertEq(DynamicArrayLib.lastIndexOf(a, 2), DynamicArrayLib.NOT_FOUND); + a = new uint256[](1); + assertEq(DynamicArrayLib.lastIndexOf(a, 0), 0); + assertEq(DynamicArrayLib.lastIndexOf(a, 1), DynamicArrayLib.NOT_FOUND); + assertEq(DynamicArrayLib.lastIndexOf(a, 2), DynamicArrayLib.NOT_FOUND); + a = DynamicArrayLib.malloc(n); + unchecked { + for (uint256 i; i != n; ++i) { + a.set(i, i); + } + } + assertEq(DynamicArrayLib.lastIndexOf(a, 0), 0); + assertEq(DynamicArrayLib.lastIndexOf(a, 1), 1); + assertEq(DynamicArrayLib.lastIndexOf(a, 10), 10); + assertEq(DynamicArrayLib.lastIndexOf(a, 31), 31); + assertEq(DynamicArrayLib.lastIndexOf(a, 32), 32); + assertEq(DynamicArrayLib.lastIndexOf(a, 49), 49); + assertEq(DynamicArrayLib.lastIndexOf(a, 50), DynamicArrayLib.NOT_FOUND); + assertEq(DynamicArrayLib.lastIndexOf(a, 100), DynamicArrayLib.NOT_FOUND); + + // edge case + assertEq(DynamicArrayLib.lastIndexOf(a, 0, 0), 0); + assertEq(DynamicArrayLib.lastIndexOf(a, 1, 1), 1); + assertEq(DynamicArrayLib.lastIndexOf(a, 10, 10), 10); + assertEq(DynamicArrayLib.lastIndexOf(a, 31, 31), 31); + assertEq(DynamicArrayLib.lastIndexOf(a, 32, 32), 32); + assertEq(DynamicArrayLib.lastIndexOf(a, 49, 49), 49); + assertEq(DynamicArrayLib.lastIndexOf(a, 50, 50), DynamicArrayLib.NOT_FOUND); + assertEq(DynamicArrayLib.lastIndexOf(a, 100, 100), DynamicArrayLib.NOT_FOUND); + } + + function testUint256ArrayLastIndexOfDifferential( + uint256[] memory array, + uint256 needle, + uint256 from + ) public { + if (_randomChance(2)) _misalignFreeMemoryPointer(); + if (_randomChance(8)) _brutalizeMemory(); + from = _bound(from, 0, array.length + 10); + uint256 computed = DynamicArrayLib.lastIndexOf(array, needle, from); + assertEq(computed, _lastIndexOfOriginal(array, needle, from)); + if (_randomChance(16)) { + computed = DynamicArrayLib.lastIndexOf(array, needle); + assertEq(computed, _lastIndexOfOriginal(array, needle)); + computed = DynamicArrayLib.lastIndexOf(DynamicArrayLib.DynamicArray(array), needle); + assertEq(computed, _lastIndexOfOriginal(array, needle)); + } + } + + function _lastIndexOfOriginal(uint256[] memory array, uint256 needle) + internal + pure + returns (uint256) + { + return _lastIndexOfOriginal(array, needle, type(uint256).max); + } + + function _lastIndexOfOriginal(uint256[] memory array, uint256 needle, uint256 from) + internal + pure + returns (uint256) + { + unchecked { + uint256 n = array.length; + if (n > 0) { + if (from >= n) from = (n - 1); + for (uint256 i = (from + 1); i != 0;) { + --i; + if (array[i] == needle) return i; + } + } + } + return type(uint256).max; + } + function testUint256ArrayPopulate() public { unchecked { uint256 n = 100;