Skip to content

Commit

Permalink
feat: Added library for sorted set validations (#13774)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xsuryansh authored Jul 9, 2024
1 parent b364d9c commit b5c0ea9
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 1 deletion.
5 changes: 5 additions & 0 deletions contracts/.changeset/neat-brooms-repeat.md
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
15 changes: 14 additions & 1 deletion contracts/gas-snapshots/shared.gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,17 @@ OpStackBurnMintERC677_constructor:testConstructorSuccess() (gas: 1743649)
OpStackBurnMintERC677_interfaceCompatibility:testBurnCompatibility() (gas: 298649)
OpStackBurnMintERC677_interfaceCompatibility:testMintCompatibility() (gas: 137957)
OpStackBurnMintERC677_interfaceCompatibility:testStaticFunctionsCompatibility() (gas: 13781)
OpStackBurnMintERC677_supportsInterface:testConstructorSuccess() (gas: 12752)
OpStackBurnMintERC677_supportsInterface:testConstructorSuccess() (gas: 12752)
SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_EmptySubset_Reverts() (gas: 5460)
SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_EmptySuperset_Reverts() (gas: 4661)
SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_HasDuplicates_Reverts() (gas: 8265)
SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_NotASubset_Reverts() (gas: 12487)
SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_SingleElementSubset() (gas: 4489)
SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_SingleElementSubsetAndSuperset_Equal() (gas: 1464)
SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_SingleElementSubsetAndSuperset_NotEqual_Reverts() (gas: 6172)
SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_SubsetEqualsSuperset_NoRevert() (gas: 8867)
SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_SubsetLargerThanSuperset_Reverts() (gas: 16544)
SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_SupersetHasDuplicates_Reverts() (gas: 9420)
SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_UnsortedSubset_Reverts() (gas: 7380)
SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_UnsortedSuperset_Reverts() (gas: 9600)
SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_ValidSubset_Success() (gas: 6490)
168 changes: 168 additions & 0 deletions contracts/src/v0.8/shared/test/util/SortedSetValidationUtil.t.sol
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 contracts/src/v0.8/shared/util/SortedSetValidationUtil.sol
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);
}
}
}
}

0 comments on commit b5c0ea9

Please sign in to comment.