From e57bd0e44daaf2d38ebdc7e45179ab9b4608989f Mon Sep 17 00:00:00 2001 From: sivaratrisrinivas Date: Thu, 27 Feb 2025 00:33:48 -0600 Subject: [PATCH] Add Array.uniquifySorted to the Arrays library This adds a uniquifySorted function to remove duplicates from sorted arrays. The function has O(n) time complexity and works with uint256, address, and bytes32 array types. This is particularly useful when mappings cannot be utilized, and provides an easy way to combine Array.sort() + Array.uniquifySorted(). Fixes #5520 Co-Authored-By: Srinivas Sivaratri --- contracts/mocks/ArraysMock.sol | 12 + contracts/utils/Arrays.sol | 65 +++++ package-lock.json | 505 +++++++++++++++------------------ package.json | 2 +- test/utils/Arrays.t.sol | 22 ++ test/utils/Arrays.test.js | 11 + 6 files changed, 345 insertions(+), 272 deletions(-) diff --git a/contracts/mocks/ArraysMock.sol b/contracts/mocks/ArraysMock.sol index 63f4c8e69f3..e6c31669546 100644 --- a/contracts/mocks/ArraysMock.sol +++ b/contracts/mocks/ArraysMock.sol @@ -56,6 +56,10 @@ contract Uint256ArraysMock { function length() external view returns (uint256) { return _array.length; } + + function uniquifySorted(uint256[] memory array) external pure returns (uint256[] memory) { + return array.uniquifySorted(); + } } contract AddressArraysMock { @@ -90,6 +94,10 @@ contract AddressArraysMock { function length() external view returns (uint256) { return _array.length; } + + function uniquifySorted(address[] memory array) external pure returns (address[] memory) { + return array.uniquifySorted(); + } } contract Bytes32ArraysMock { @@ -124,4 +132,8 @@ contract Bytes32ArraysMock { function length() external view returns (uint256) { return _array.length; } + + function uniquifySorted(bytes32[] memory array) external pure returns (bytes32[] memory) { + return array.uniquifySorted(); + } } diff --git a/contracts/utils/Arrays.sol b/contracts/utils/Arrays.sol index b3cb3f42189..e7c6d223ec9 100644 --- a/contracts/utils/Arrays.sol +++ b/contracts/utils/Arrays.sol @@ -479,4 +479,69 @@ library Arrays { sstore(array.slot, len) } } + + /** + * @dev Removes duplicate values from a sorted array. This function does not check that the array is sorted, + * behavior is undefined if the array is not sorted. The resulting array will have no duplicates and will + * be shorter or the same length as the input array. + * + * This operation is performed in-place by moving elements and modifying the length of the array. + * Time complexity O(n). + * + * WARNING: This function is destructive. It will modify the array passed by reference. + */ + function uniquifySorted(uint256[] memory array) internal pure returns (uint256[] memory) { + if (array.length <= 1) { + return array; + } + + uint256 resultSize = 1; + for (uint256 i = 1; i < array.length; ++i) { + if (array[i] != array[i - 1]) { + if (i != resultSize) { + array[resultSize] = array[i]; + } + ++resultSize; + } + } + + // Resize the array by creating a new one (can't modify length of memory arrays directly) + uint256[] memory result = new uint256[](resultSize); + for (uint256 i = 0; i < resultSize; ++i) { + result[i] = array[i]; + } + return result; + } + + /** + * @dev Removes duplicate values from a sorted address array. This function does not check that the array + * is sorted, behavior is undefined if the array is not sorted. The resulting array will have no duplicates + * and will be shorter or the same length as the input array. + * + * This operation is performed in-place by moving elements and modifying the length of the array. + * Time complexity O(n). + * + * WARNING: This function is destructive. It will modify the array passed by reference. + */ + function uniquifySorted(address[] memory array) internal pure returns (address[] memory) { + uint256[] memory castedArray = _castToUint256Array(array); + uniquifySorted(castedArray); + return array; + } + + /** + * @dev Removes duplicate values from a sorted bytes32 array. This function does not check that the array + * is sorted, behavior is undefined if the array is not sorted. The resulting array will have no duplicates + * and will be shorter or the same length as the input array. + * + * This operation is performed in-place by moving elements and modifying the length of the array. + * Time complexity O(n). + * + * WARNING: This function is destructive. It will modify the array passed by reference. + */ + function uniquifySorted(bytes32[] memory array) internal pure returns (bytes32[] memory) { + uint256[] memory castedArray = _castToUint256Array(array); + uniquifySorted(castedArray); + return array; + } } diff --git a/package-lock.json b/package-lock.json index 2b7f5083416..e83d70b2798 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openzeppelin-solidity", - "version": "5.1.0", + "version": "5.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openzeppelin-solidity", - "version": "5.1.0", + "version": "5.2.0", "license": "MIT", "devDependencies": { "@changesets/changelog-github": "^0.5.0", @@ -28,7 +28,7 @@ "glob": "^11.0.0", "globals": "^15.3.0", "graphlib": "^2.1.8", - "hardhat": "^2.22.2", + "hardhat": "^2.22.19", "hardhat-exposed": "^0.3.15", "hardhat-gas-reporter": "^2.1.0", "hardhat-ignore-warnings": "^0.2.11", @@ -1553,165 +1553,90 @@ } }, "node_modules/@nomicfoundation/edr": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.3.3.tgz", - "integrity": "sha512-zP+e+3B1nEUx6bW5BPnIzCQbkhmYfdMBJdiVggTqqTfAA82sOkdOG7wsOMcz5qF3fYfx/irNRM1kgc9HVFIbpQ==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.8.0.tgz", + "integrity": "sha512-dwWRrghSVBQDpt0wP+6RXD8BMz2i/9TI34TcmZqeEAZuCLei3U9KZRgGTKVAM1rMRvrpf5ROfPqrWNetKVUTag==", "dev": true, + "license": "MIT", + "dependencies": { + "@nomicfoundation/edr-darwin-arm64": "0.8.0", + "@nomicfoundation/edr-darwin-x64": "0.8.0", + "@nomicfoundation/edr-linux-arm64-gnu": "0.8.0", + "@nomicfoundation/edr-linux-arm64-musl": "0.8.0", + "@nomicfoundation/edr-linux-x64-gnu": "0.8.0", + "@nomicfoundation/edr-linux-x64-musl": "0.8.0", + "@nomicfoundation/edr-win32-x64-msvc": "0.8.0" + }, "engines": { "node": ">= 18" - }, - "optionalDependencies": { - "@nomicfoundation/edr-darwin-arm64": "0.3.3", - "@nomicfoundation/edr-darwin-x64": "0.3.3", - "@nomicfoundation/edr-linux-arm64-gnu": "0.3.3", - "@nomicfoundation/edr-linux-arm64-musl": "0.3.3", - "@nomicfoundation/edr-linux-x64-gnu": "0.3.3", - "@nomicfoundation/edr-linux-x64-musl": "0.3.3", - "@nomicfoundation/edr-win32-arm64-msvc": "0.3.3", - "@nomicfoundation/edr-win32-ia32-msvc": "0.3.3", - "@nomicfoundation/edr-win32-x64-msvc": "0.3.3" } }, "node_modules/@nomicfoundation/edr-darwin-arm64": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.3.3.tgz", - "integrity": "sha512-E9VGsUD+1Ga4mn/5ooHsMi8JEfhZbKP6CXN/BhJ8kXbIC10NqTD1RuhCKGRtYq4vqH/3Nfq25Xg8E8RWOF4KBQ==", - "cpu": [ - "arm64" - ], + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.8.0.tgz", + "integrity": "sha512-sKTmOu/P5YYhxT0ThN2Pe3hmCE/5Ag6K/eYoiavjLWbR7HEb5ZwPu2rC3DpuUk1H+UKJqt7o4/xIgJxqw9wu6A==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], + "license": "MIT", "engines": { "node": ">= 18" } }, "node_modules/@nomicfoundation/edr-darwin-x64": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.3.3.tgz", - "integrity": "sha512-vkZXZ1ydPg+Ijb2iyqENA+KCkxGTCUWG5itCSliiA0Li2YE7ujDMGhheEpFp1WVlZadviz0bfk1rZXbCqlirpg==", - "cpu": [ - "x64" - ], + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.8.0.tgz", + "integrity": "sha512-8ymEtWw1xf1Id1cc42XIeE+9wyo3Dpn9OD/X8GiaMz9R70Ebmj2g+FrbETu8o6UM+aL28sBZQCiCzjlft2yWAg==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], + "license": "MIT", "engines": { "node": ">= 18" } }, "node_modules/@nomicfoundation/edr-linux-arm64-gnu": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.3.3.tgz", - "integrity": "sha512-gdIg0Yj1qqS9wVuywc5B/+DqKylfUGB6/CQn/shMqwAfsAVAVpchkhy66PR+REEx7fh/GkNctxLlENXPeLzDiA==", - "cpu": [ - "arm64" - ], + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.8.0.tgz", + "integrity": "sha512-h/wWzS2EyQuycz+x/SjMRbyA+QMCCVmotRsgM1WycPARvVZWIVfwRRsKoXKdCftsb3S8NTprqBdJlOmsFyETFA==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", "engines": { "node": ">= 18" } }, "node_modules/@nomicfoundation/edr-linux-arm64-musl": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.3.3.tgz", - "integrity": "sha512-AXZ08MFvhNeBZbOBNmz1SJ/DMrMOE2mHEJtaNnsctlxIunjxfrWww4q+WXB34jbr9iaVYYlPsaWe5sueuw6s3Q==", - "cpu": [ - "arm64" - ], + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.8.0.tgz", + "integrity": "sha512-gnWxDgdkka0O9GpPX/gZT3REeKYV28Guyg13+Vj/bbLpmK1HmGh6Kx+fMhWv+Ht/wEmGDBGMCW1wdyT/CftJaQ==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", "engines": { "node": ">= 18" } }, "node_modules/@nomicfoundation/edr-linux-x64-gnu": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.3.3.tgz", - "integrity": "sha512-xElOs1U+E6lBLtv1mnJ+E8nr2MxZgKiLo8bZAgBboy9odYtmkDVwhMjtsFKSuZbGxFtsSyGRT4cXw3JAbtUDeA==", - "cpu": [ - "x64" - ], + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.8.0.tgz", + "integrity": "sha512-DTMiAkgAx+nyxcxKyxFZk1HPakXXUCgrmei7r5G7kngiggiGp/AUuBBWFHi8xvl2y04GYhro5Wp+KprnLVoAPA==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", "engines": { "node": ">= 18" } }, "node_modules/@nomicfoundation/edr-linux-x64-musl": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.3.3.tgz", - "integrity": "sha512-2Fe6gwm1RAGQ/PfMYiaSba2OrFp8zzYWh+am9lYObOFjV9D+A1zhIzfy0UC74glPks5eV8eY4pBPrVR042m2Nw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nomicfoundation/edr-win32-arm64-msvc": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-arm64-msvc/-/edr-win32-arm64-msvc-0.3.3.tgz", - "integrity": "sha512-8NHyxIsFrl0ufSQ/ErqF2lKIa/gz1gaaa1a2vKkDEqvqCUcPhBTYhA5NHgTPhLETFTnCFr0z+YbctFCyjh4qrA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/edr-win32-ia32-msvc": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-ia32-msvc/-/edr-win32-ia32-msvc-0.3.3.tgz", - "integrity": "sha512-0F6hM0kGia4dQVb/kauho9JcP1ozWisY2/She+ISR5ceuhzmAwQJluM0g+0TYDME0LtxBxiMPq/yPiZMQeq31w==", - "cpu": [ - "ia32" - ], + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.8.0.tgz", + "integrity": "sha512-iTITWe0Zj8cNqS0xTblmxPbHVWwEtMiDC+Yxwr64d7QBn/1W0ilFQ16J8gB6RVVFU3GpfNyoeg3tUoMpSnrm6Q==", "dev": true, - "optional": true, - "os": [ - "win32" - ], + "license": "MIT", "engines": { "node": ">= 18" } }, "node_modules/@nomicfoundation/edr-win32-x64-msvc": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.3.3.tgz", - "integrity": "sha512-d75q1uaMb6z9i+GQZoblbOfFBvlBnWc+5rB13UWRkCOJSnoYwyFWhGJx5GeM59gC7aIblc5VD9qOAhHuvM9N+w==", - "cpu": [ - "x64" - ], + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.8.0.tgz", + "integrity": "sha512-mNRDyd/C3j7RMcwapifzv2K57sfA5xOw8g2U84ZDvgSrXVXLC99ZPxn9kmolb+dz8VMm9FONTZz9ESS6v8DTnA==", "dev": true, - "optional": true, - "os": [ - "win32" - ], + "license": "MIT", "engines": { "node": ">= 18" } @@ -3733,7 +3658,8 @@ "version": "1.2.9", "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/commander": { "version": "10.0.1", @@ -5758,14 +5684,15 @@ } }, "node_modules/hardhat": { - "version": "2.22.2", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.22.2.tgz", - "integrity": "sha512-0xZ7MdCZ5sJem4MrvpQWLR3R3zGDoHw5lsR+pBFimqwagimIOn3bWuZv69KA+veXClwI1s/zpqgwPwiFrd4Dxw==", + "version": "2.22.19", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.22.19.tgz", + "integrity": "sha512-jptJR5o6MCgNbhd7eKa3mrteR+Ggq1exmE5RUL5ydQEVKcZm0sss5laa86yZ0ixIavIvF4zzS7TdGDuyopj0sQ==", "dev": true, + "license": "MIT", "dependencies": { "@ethersproject/abi": "^5.1.2", "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/edr": "^0.3.1", + "@nomicfoundation/edr": "^0.8.0", "@nomicfoundation/ethereumjs-common": "4.0.4", "@nomicfoundation/ethereumjs-tx": "5.0.4", "@nomicfoundation/ethereumjs-util": "9.0.4", @@ -5777,31 +5704,32 @@ "aggregate-error": "^3.0.0", "ansi-escapes": "^4.3.0", "boxen": "^5.1.2", - "chalk": "^2.4.2", - "chokidar": "^3.4.0", + "chokidar": "^4.0.0", "ci-info": "^2.0.0", "debug": "^4.1.1", "enquirer": "^2.3.0", "env-paths": "^2.2.0", "ethereum-cryptography": "^1.0.3", "ethereumjs-abi": "^0.6.8", - "find-up": "^2.1.0", + "find-up": "^5.0.0", "fp-ts": "1.19.3", "fs-extra": "^7.0.1", - "glob": "7.2.0", "immutable": "^4.0.0-rc.12", "io-ts": "1.10.4", + "json-stream-stringify": "^3.1.4", "keccak": "^3.0.2", "lodash": "^4.17.11", "mnemonist": "^0.38.0", "mocha": "^10.0.0", "p-map": "^4.0.0", + "picocolors": "^1.1.0", "raw-body": "^2.4.1", "resolve": "1.17.0", "semver": "^6.3.0", - "solc": "0.7.3", + "solc": "0.8.26", "source-map-support": "^0.5.13", "stacktrace-parser": "^0.1.10", + "tinyglobby": "^0.2.6", "tsort": "0.0.1", "undici": "^5.14.0", "uuid": "^8.3.2", @@ -6211,18 +6139,28 @@ "@scure/base": "~1.1.0" } }, + "node_modules/hardhat/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/hardhat/node_modules/ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, - "node_modules/hardhat/node_modules/commander": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", - "dev": true - }, "node_modules/hardhat/node_modules/ethereum-cryptography": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", @@ -6236,120 +6174,82 @@ } }, "node_modules/hardhat/node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", - "dev": true, - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hardhat/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": "*" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/hardhat/node_modules/jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/hardhat/node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/hardhat/node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { - "p-try": "^1.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/hardhat/node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { - "p-limit": "^1.1.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=4" - } - }, - "node_modules/hardhat/node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/hardhat/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/hardhat/node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "node_modules/hardhat/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hardhat/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" + "node": ">= 14.18.0" }, - "bin": { - "rimraf": "bin.js" + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/hardhat/node_modules/semver": { @@ -6361,51 +6261,6 @@ "semver": "bin/semver.js" } }, - "node_modules/hardhat/node_modules/solc": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.7.3.tgz", - "integrity": "sha512-GAsWNAjGzIDg7VxzP6mPjdurby3IkGCjQcM8GFYZT6RyaoUZKmMU6Y7YwG+tFGhv7dwZ8rmR4iwFDrrD99JwqA==", - "dev": true, - "dependencies": { - "command-exists": "^1.2.8", - "commander": "3.0.2", - "follow-redirects": "^1.12.1", - "fs-extra": "^0.30.0", - "js-sha3": "0.8.0", - "memorystream": "^0.3.1", - "require-from-string": "^2.0.0", - "semver": "^5.5.0", - "tmp": "0.0.33" - }, - "bin": { - "solcjs": "solcjs" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/hardhat/node_modules/solc/node_modules/fs-extra": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", - "integrity": "sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0", - "path-is-absolute": "^1.0.0", - "rimraf": "^2.2.8" - } - }, - "node_modules/hardhat/node_modules/solc/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, "node_modules/hardhat/node_modules/undici": { "version": "5.28.4", "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", @@ -6418,6 +6273,19 @@ "node": ">=14.0" } }, + "node_modules/hardhat/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -7202,6 +7070,16 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json-stream-stringify": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/json-stream-stringify/-/json-stream-stringify-3.1.6.tgz", + "integrity": "sha512-x7fpwxOkbhFCaJDJ8vb1fBY3DdSa4AlITaz+HHILQJzdPMnHEFjxPwVUi1ALIbcIxDE0PNe/0i7frnY8QnBQog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=7.10.1" + } + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -7254,15 +7132,6 @@ "node": ">=0.10.0" } }, - "node_modules/klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.9" - } - }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -8863,6 +8732,13 @@ "node": ">=0.12" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -10161,6 +10037,48 @@ "node": ">=8" } }, + "node_modules/solc": { + "version": "0.8.26", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.26.tgz", + "integrity": "sha512-yiPQNVf5rBFHwN6SIf3TUUvVAFKcQqmSUFeq+fb6pNRCo0ZCgpYOZDi3BVoezCPIAcKrVYd/qXlBLUP9wVrZ9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "command-exists": "^1.2.8", + "commander": "^8.1.0", + "follow-redirects": "^1.12.1", + "js-sha3": "0.8.0", + "memorystream": "^0.3.1", + "semver": "^5.5.0", + "tmp": "0.0.33" + }, + "bin": { + "solcjs": "solc.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/solc/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/solc/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/solhint": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/solhint/-/solhint-5.0.0.tgz", @@ -11133,6 +11051,51 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/tinyglobby": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", + "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", diff --git a/package.json b/package.json index f9e7d92058b..29026b74372 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "glob": "^11.0.0", "globals": "^15.3.0", "graphlib": "^2.1.8", - "hardhat": "^2.22.2", + "hardhat": "^2.22.19", "hardhat-exposed": "^0.3.15", "hardhat-gas-reporter": "^2.1.0", "hardhat-ignore-warnings": "^0.2.11", diff --git a/test/utils/Arrays.t.sol b/test/utils/Arrays.t.sol index e45d29c919d..ef16d857f58 100644 --- a/test/utils/Arrays.t.sol +++ b/test/utils/Arrays.t.sol @@ -11,6 +11,22 @@ contract ArraysTest is Test, SymTest { Arrays.sort(values); _assertSort(values); } + + function testUniquifySorted(uint256[] memory values) public pure { + if (values.length == 0) return; + + // First sort the array + Arrays.sort(values); + + // Then uniquify + uint256[] memory result = Arrays.uniquifySorted(values); + + // Assert the result is still sorted + _assertSort(result); + + // Assert no duplicates exist + _assertNoDuplicates(result); + } function symbolicSort() public pure { uint256[] memory values = new uint256[](3); @@ -28,4 +44,10 @@ contract ArraysTest is Test, SymTest { assertLe(values[i - 1], values[i]); } } + + function _assertNoDuplicates(uint256[] memory values) internal pure { + for (uint256 i = 1; i < values.length; ++i) { + assertTrue(values[i - 1] != values[i]); + } + } } diff --git a/test/utils/Arrays.test.js b/test/utils/Arrays.test.js index bcb385897df..2e17789b627 100644 --- a/test/utils/Arrays.test.js +++ b/test/utils/Arrays.test.js @@ -144,6 +144,17 @@ describe('Arrays', function () { expect(await this.instance.sort(this.array)).to.deep.equal(expected); expect(await this.instance.sortReverse(this.array)).to.deep.equal(reversed); }); + + it('uniquify sorted array', async function () { + // Sort and then add duplicates + this.array.sort(comparator); + + // Create expected result (unique values) + const expected = [...new Set(this.array)].sort(comparator); + + // Test uniquifySorted on the sorted array + expect(await this.instance.uniquifySorted(this.array)).to.deep.equal(expected); + }); it('sort array', async function () { // nothing to do here, beforeEach and afterEach already take care of everything.