-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Added library for sorted set validations (#13774)
- Loading branch information
1 parent
b364d9c
commit b5c0ea9
Showing
4 changed files
with
247 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@chainlink/contracts': minor | ||
--- | ||
|
||
We have multiple validation use-cases on-chain which requires the inputs to be a set, sorted-set or we need to do subset checks.Adding a library for these validations |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
168 changes: 168 additions & 0 deletions
168
contracts/src/v0.8/shared/test/util/SortedSetValidationUtil.t.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.4; | ||
|
||
import {BaseTest} from "../BaseTest.t.sol"; | ||
import {SortedSetValidationUtil} from "../../../shared/util/SortedSetValidationUtil.sol"; | ||
|
||
contract SortedSetValidationUtilBaseTest is BaseTest { | ||
uint256 constant OFFSET = 5; | ||
|
||
modifier _ensureSetLength(uint256 subsetLength, uint256 supersetLength) { | ||
vm.assume(subsetLength > 0 && supersetLength > 0 && subsetLength <= supersetLength); | ||
_; | ||
} | ||
|
||
function _createSets( | ||
uint256 subsetLength, | ||
uint256 supersetLength | ||
) internal pure returns (bytes32[] memory subset, bytes32[] memory superset) { | ||
subset = new bytes32[](subsetLength); | ||
superset = new bytes32[](supersetLength); | ||
} | ||
|
||
function _convertArrayToSortedSet(bytes32[] memory arr, uint256 offSet) internal pure { | ||
for (uint256 i = 1; i < arr.length; ++i) { | ||
arr[i] = bytes32(uint256(arr[i - 1]) + offSet); | ||
} | ||
} | ||
|
||
function _convertToUnsortedSet(bytes32[] memory arr, uint256 ptr1, uint256 ptr2) internal pure { | ||
// Swap two elements to make it unsorted | ||
(arr[ptr1], arr[ptr2]) = (arr[ptr2], arr[ptr1]); | ||
} | ||
|
||
function _convertArrayToSubset(bytes32[] memory subset, bytes32[] memory superset) internal pure { | ||
for (uint256 i; i < subset.length; ++i) { | ||
subset[i] = superset[i]; | ||
} | ||
} | ||
|
||
function _makeInvalidSubset(bytes32[] memory subset, bytes32[] memory superset, uint256 ptr) internal pure { | ||
_convertArrayToSubset(subset, superset); | ||
subset[ptr] = bytes32(uint256(subset[ptr]) + 1); | ||
} | ||
|
||
function _convertArrayToHaveDuplicates(bytes32[] memory arr, uint256 ptr1, uint256 ptr2) internal pure { | ||
arr[ptr2] = arr[ptr1]; | ||
} | ||
} | ||
|
||
contract SortedSetValidationUtil_CheckIsValidUniqueSubsetTest is SortedSetValidationUtilBaseTest { | ||
// Successes. | ||
|
||
function test__checkIsValidUniqueSubset_ValidSubset_Success() public { | ||
(bytes32[] memory subset, bytes32[] memory superset) = _createSets(3, 5); | ||
_convertArrayToSortedSet(superset, OFFSET); | ||
_convertArrayToSubset(subset, superset); | ||
|
||
SortedSetValidationUtil._checkIsValidUniqueSubset(subset, superset); | ||
} | ||
|
||
// Reverts. | ||
|
||
function test__checkIsValidUniqueSubset_EmptySubset_Reverts() public { | ||
(bytes32[] memory subset, bytes32[] memory superset) = _createSets(0, 5); | ||
_convertArrayToSortedSet(superset, OFFSET); | ||
|
||
vm.expectRevert(abi.encodeWithSelector(SortedSetValidationUtil.EmptySet.selector)); | ||
SortedSetValidationUtil._checkIsValidUniqueSubset(subset, superset); | ||
} | ||
|
||
function test__checkIsValidUniqueSubset_EmptySuperset_Reverts() public { | ||
(bytes32[] memory subset, bytes32[] memory superset) = _createSets(3, 0); | ||
_convertArrayToSortedSet(subset, OFFSET); | ||
|
||
vm.expectRevert(abi.encodeWithSelector(SortedSetValidationUtil.EmptySet.selector)); | ||
SortedSetValidationUtil._checkIsValidUniqueSubset(subset, superset); | ||
} | ||
|
||
function test__checkIsValidUniqueSubset_NotASubset_Reverts() public { | ||
(bytes32[] memory subset, bytes32[] memory superset) = _createSets(3, 5); | ||
_convertArrayToSortedSet(superset, OFFSET); | ||
_makeInvalidSubset(subset, superset, 1); | ||
|
||
vm.expectRevert(abi.encodeWithSelector(SortedSetValidationUtil.NotASubset.selector, subset, superset)); | ||
SortedSetValidationUtil._checkIsValidUniqueSubset(subset, superset); | ||
} | ||
|
||
function test__checkIsValidUniqueSubset_UnsortedSubset_Reverts() public { | ||
(bytes32[] memory subset, bytes32[] memory superset) = _createSets(3, 5); | ||
_convertArrayToSortedSet(superset, OFFSET); | ||
_convertToUnsortedSet(subset, 1, 2); | ||
|
||
vm.expectRevert(abi.encodeWithSelector(SortedSetValidationUtil.NotASortedSet.selector, subset)); | ||
SortedSetValidationUtil._checkIsValidUniqueSubset(subset, superset); | ||
} | ||
|
||
function test__checkIsValidUniqueSubset_UnsortedSuperset_Reverts() public { | ||
(bytes32[] memory subset, bytes32[] memory superset) = _createSets(3, 5); | ||
_convertArrayToSortedSet(superset, OFFSET); | ||
_convertArrayToSubset(subset, superset); | ||
_convertToUnsortedSet(superset, 1, 2); | ||
|
||
vm.expectRevert(abi.encodeWithSelector(SortedSetValidationUtil.NotASortedSet.selector, superset)); | ||
SortedSetValidationUtil._checkIsValidUniqueSubset(subset, superset); | ||
} | ||
|
||
function test__checkIsValidUniqueSubset_HasDuplicates_Reverts() public { | ||
(bytes32[] memory subset, bytes32[] memory superset) = _createSets(3, 5); | ||
_convertArrayToSortedSet(superset, OFFSET); | ||
_convertArrayToSubset(subset, superset); | ||
_convertArrayToHaveDuplicates(subset, 1, 2); | ||
|
||
vm.expectRevert(abi.encodeWithSelector(SortedSetValidationUtil.NotASortedSet.selector, subset)); | ||
SortedSetValidationUtil._checkIsValidUniqueSubset(subset, superset); | ||
} | ||
|
||
function test__checkIsValidUniqueSubset_SubsetLargerThanSuperset_Reverts() public { | ||
(bytes32[] memory subset, bytes32[] memory superset) = _createSets(6, 5); | ||
_convertArrayToSortedSet(subset, OFFSET); | ||
_convertArrayToSortedSet(superset, OFFSET); | ||
|
||
vm.expectRevert(abi.encodeWithSelector(SortedSetValidationUtil.NotASubset.selector, subset, superset)); | ||
SortedSetValidationUtil._checkIsValidUniqueSubset(subset, superset); | ||
} | ||
|
||
function test__checkIsValidUniqueSubset_SubsetEqualsSuperset_NoRevert() public { | ||
(bytes32[] memory subset, bytes32[] memory superset) = _createSets(5, 5); | ||
_convertArrayToSortedSet(subset, OFFSET); | ||
_convertArrayToSortedSet(superset, OFFSET); | ||
|
||
SortedSetValidationUtil._checkIsValidUniqueSubset(subset, superset); | ||
} | ||
|
||
function test__checkIsValidUniqueSubset_SingleElementSubset() public { | ||
(bytes32[] memory subset, bytes32[] memory superset) = _createSets(1, 5); | ||
_convertArrayToSortedSet(superset, OFFSET); | ||
_convertArrayToSubset(subset, superset); | ||
|
||
SortedSetValidationUtil._checkIsValidUniqueSubset(subset, superset); | ||
} | ||
|
||
function test__checkIsValidUniqueSubset_SingleElementSubsetAndSuperset_Equal() public { | ||
(bytes32[] memory subset, bytes32[] memory superset) = _createSets(1, 1); | ||
_convertArrayToSortedSet(subset, OFFSET); | ||
_convertArrayToSortedSet(superset, OFFSET); | ||
|
||
SortedSetValidationUtil._checkIsValidUniqueSubset(subset, superset); | ||
} | ||
|
||
function test__checkIsValidUniqueSubset_SingleElementSubsetAndSuperset_NotEqual_Reverts() public { | ||
(bytes32[] memory subset, bytes32[] memory superset) = _createSets(1, 1); | ||
_convertArrayToSortedSet(subset, OFFSET); | ||
superset[0] = bytes32(uint256(subset[0]) + 10); // Different value | ||
|
||
vm.expectRevert(abi.encodeWithSelector(SortedSetValidationUtil.NotASubset.selector, subset, superset)); | ||
SortedSetValidationUtil._checkIsValidUniqueSubset(subset, superset); | ||
} | ||
|
||
function test__checkIsValidUniqueSubset_SupersetHasDuplicates_Reverts() public { | ||
(bytes32[] memory subset, bytes32[] memory superset) = _createSets(3, 5); | ||
_convertArrayToSortedSet(superset, OFFSET); | ||
_convertArrayToSubset(subset, superset); | ||
_convertArrayToHaveDuplicates(superset, 1, 2); | ||
|
||
vm.expectRevert(abi.encodeWithSelector(SortedSetValidationUtil.NotASortedSet.selector, superset)); | ||
SortedSetValidationUtil._checkIsValidUniqueSubset(subset, superset); | ||
} | ||
} |
60 changes: 60 additions & 0 deletions
60
contracts/src/v0.8/shared/util/SortedSetValidationUtil.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.0; | ||
|
||
/// @title Sorted Set Validation Utility | ||
/// @notice Provides utility functions for validating sorted sets and their subset relationships. | ||
/// @dev This library is used to ensure that arrays of bytes32 are sorted sets and to check subset relations. | ||
library SortedSetValidationUtil { | ||
/// @dev Error to be thrown when an operation is attempted on an empty set. | ||
error EmptySet(); | ||
/// @dev Error to be thrown when the set is not in ascending unique order. | ||
error NotASortedSet(bytes32[] set); | ||
/// @dev Error to be thrown when the first array is not a subset of the second array. | ||
error NotASubset(bytes32[] subset, bytes32[] superset); | ||
|
||
/// @notice Checks if `subset` is a valid and unique subset of `superset`. | ||
/// NOTE: Empty set is not considered a valid subset of superset for our use case. | ||
/// @dev Both arrays must be valid sets (unique, sorted in ascending order) and `subset` must be entirely contained within `superset`. | ||
/// @param subset The array of bytes32 to validate as a subset. | ||
/// @param superset The array of bytes32 in which subset is checked against. | ||
/// @custom:revert EmptySet If either `subset` or `superset` is empty. | ||
/// @custom:revert NotASubset If `subset` is not a subset of `superset`. | ||
function _checkIsValidUniqueSubset(bytes32[] memory subset, bytes32[] memory superset) internal pure { | ||
if (subset.length == 0 || superset.length == 0) { | ||
revert EmptySet(); | ||
} | ||
|
||
_checkIsValidSet(subset); | ||
_checkIsValidSet(superset); | ||
|
||
uint256 i = 0; // Pointer for 'subset' | ||
uint256 j = 0; // Pointer for 'superset' | ||
|
||
while (i < subset.length && j < superset.length) { | ||
if (subset[i] > superset[j]) { | ||
++j; // Move the pointer in 'superset' to find a match | ||
} else if (subset[i] == superset[j]) { | ||
++i; // Found a match, move the pointer in 'subset' | ||
++j; // Also move in 'superset' to continue checking | ||
} else { | ||
revert NotASubset(subset, superset); | ||
} | ||
} | ||
|
||
if (i < subset.length) { | ||
revert NotASubset(subset, superset); | ||
} | ||
} | ||
|
||
/// @notice Validates that a given set is sorted and has unique elements. | ||
/// @dev Iterates through the array to check that each element is greater than the previous. | ||
/// @param set The array of bytes32 to validate. | ||
/// @custom:revert NotASortedSet If any element is not greater than its predecessor or if any two consecutive elements are equal. | ||
function _checkIsValidSet(bytes32[] memory set) private pure { | ||
for (uint256 i = 1; i < set.length; ++i) { | ||
if (set[i] <= set[i - 1]) { | ||
revert NotASortedSet(set); | ||
} | ||
} | ||
} | ||
} |