From 9131286d4d111bae09f7e1ff835197a2fd8a7b7c Mon Sep 17 00:00:00 2001 From: Vectorized Date: Mon, 4 Aug 2025 11:28:43 +0000 Subject: [PATCH 1/2] Add indexOf --- src/utils/EnumerableSetLib.sol | 104 +++++++++++++++++++++++++++++++ src/utils/g/EnumerableSetLib.sol | 104 +++++++++++++++++++++++++++++++ test/EnumerableSetLib.t.sol | 60 ++++++++++++++++++ 3 files changed, 268 insertions(+) diff --git a/src/utils/EnumerableSetLib.sol b/src/utils/EnumerableSetLib.sol index 05d029fac3..cb770dd1ec 100644 --- a/src/utils/EnumerableSetLib.sol +++ b/src/utils/EnumerableSetLib.sol @@ -32,6 +32,9 @@ library EnumerableSetLib { /* CONSTANTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + /// @dev The index to represent a value that does not exist. + uint256 internal constant NOT_FOUND = type(uint256).max; + /// @dev A sentinel value to denote the zero value in storage. /// No elements can be equal to this value. /// `uint72(bytes9(keccak256(bytes("_ZERO_SENTINEL"))))`. @@ -756,6 +759,107 @@ library EnumerableSetLib { } } + /// @dev Returns the index of `value`. Returns `NOT_FOUND` if the value does not exist. + function indexOf(AddressSet storage set, address value) + internal + view + returns (uint256 result) + { + result = NOT_FOUND; + if (uint160(value) == _ZERO_SENTINEL) return result; + bytes32 rootSlot = _rootSlot(set); + /// @solidity memory-safe-assembly + assembly { + if iszero(value) { value := _ZERO_SENTINEL } + result := not(0) + let rootPacked := sload(rootSlot) + for {} 1 {} { + if iszero(shr(160, shl(160, rootPacked))) { + if eq(shr(96, rootPacked), value) { + result := 0 + break + } + if eq(shr(96, sload(add(rootSlot, 1))), value) { + result := 1 + break + } + if eq(shr(96, sload(add(rootSlot, 2))), value) { + result := 2 + break + } + break + } + mstore(0x20, rootSlot) + mstore(0x00, value) + result := sub(sload(keccak256(0x00, 0x40)), 1) + break + } + } + } + + /// @dev Returns the index of `value`. Returns `NOT_FOUND` if the value does not exist. + function indexOf(Bytes32Set storage set, bytes32 value) + internal + view + returns (uint256 result) + { + result = NOT_FOUND; + if (uint256(value) == _ZERO_SENTINEL) return result; + bytes32 rootSlot = _rootSlot(set); + /// @solidity memory-safe-assembly + assembly { + if iszero(value) { value := _ZERO_SENTINEL } + for {} 1 {} { + if iszero(sload(not(rootSlot))) { + if eq(sload(rootSlot), value) { + result := 0 + break + } + if eq(sload(add(rootSlot, 1)), value) { + result := 1 + break + } + if eq(sload(add(rootSlot, 2)), value) { + result := 2 + break + } + break + } + mstore(0x20, rootSlot) + mstore(0x00, value) + result := sub(sload(keccak256(0x00, 0x40)), 1) + break + } + } + } + + /// @dev Returns the index of `value`. Returns `NOT_FOUND` if the value does not exist. + function indexOf(Uint256Set storage set, uint256 i) internal view returns (uint256 result) { + result = indexOf(_toBytes32Set(set), bytes32(i)); + } + + /// @dev Returns the index of `value`. Returns `NOT_FOUND` if the value does not exist. + function indexOf(Int256Set storage set, int256 i) internal view returns (uint256 result) { + result = indexOf(_toBytes32Set(set), bytes32(uint256(i))); + } + + /// @dev Returns the index of `value`. Returns `NOT_FOUND` if the value does not exist. + function indexOf(Uint8Set storage set, uint8 value) internal view returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + result := not(0) + let packed := sload(set.slot) + let m := shl(and(0xff, value), 1) + if and(packed, m) { + result := 0 + for { let p := and(packed, sub(m, 1)) } p {} { + p := xor(p, and(p, add(1, not(p)))) + result := add(result, 1) + } + } + } + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PRIVATE HELPERS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ diff --git a/src/utils/g/EnumerableSetLib.sol b/src/utils/g/EnumerableSetLib.sol index ac1110aac9..dc879f1849 100644 --- a/src/utils/g/EnumerableSetLib.sol +++ b/src/utils/g/EnumerableSetLib.sol @@ -69,6 +69,9 @@ library EnumerableSetLib { /* CONSTANTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + /// @dev The index to represent a value that does not exist. + uint256 internal constant NOT_FOUND = type(uint256).max; + /// @dev A sentinel value to denote the zero value in storage. /// No elements can be equal to this value. /// `uint72(bytes9(keccak256(bytes("_ZERO_SENTINEL"))))`. @@ -764,6 +767,107 @@ library EnumerableSetLib { } } + /// @dev Returns the index of `value`. Returns `NOT_FOUND` if the value does not exist. + function indexOf(AddressSet storage set, address value) + internal + view + returns (uint256 result) + { + result = NOT_FOUND; + if (uint160(value) == _ZERO_SENTINEL) return result; + bytes32 rootSlot = _rootSlot(set); + /// @solidity memory-safe-assembly + assembly { + if iszero(value) { value := _ZERO_SENTINEL } + result := not(0) + let rootPacked := sload(rootSlot) + for {} 1 {} { + if iszero(shr(160, shl(160, rootPacked))) { + if eq(shr(96, rootPacked), value) { + result := 0 + break + } + if eq(shr(96, sload(add(rootSlot, 1))), value) { + result := 1 + break + } + if eq(shr(96, sload(add(rootSlot, 2))), value) { + result := 2 + break + } + break + } + mstore(0x20, rootSlot) + mstore(0x00, value) + result := sub(sload(keccak256(0x00, 0x40)), 1) + break + } + } + } + + /// @dev Returns the index of `value`. Returns `NOT_FOUND` if the value does not exist. + function indexOf(Bytes32Set storage set, bytes32 value) + internal + view + returns (uint256 result) + { + result = NOT_FOUND; + if (uint256(value) == _ZERO_SENTINEL) return result; + bytes32 rootSlot = _rootSlot(set); + /// @solidity memory-safe-assembly + assembly { + if iszero(value) { value := _ZERO_SENTINEL } + for {} 1 {} { + if iszero(sload(not(rootSlot))) { + if eq(sload(rootSlot), value) { + result := 0 + break + } + if eq(sload(add(rootSlot, 1)), value) { + result := 1 + break + } + if eq(sload(add(rootSlot, 2)), value) { + result := 2 + break + } + break + } + mstore(0x20, rootSlot) + mstore(0x00, value) + result := sub(sload(keccak256(0x00, 0x40)), 1) + break + } + } + } + + /// @dev Returns the index of `value`. Returns `NOT_FOUND` if the value does not exist. + function indexOf(Uint256Set storage set, uint256 i) internal view returns (uint256 result) { + result = indexOf(_toBytes32Set(set), bytes32(i)); + } + + /// @dev Returns the index of `value`. Returns `NOT_FOUND` if the value does not exist. + function indexOf(Int256Set storage set, int256 i) internal view returns (uint256 result) { + result = indexOf(_toBytes32Set(set), bytes32(uint256(i))); + } + + /// @dev Returns the index of `value`. Returns `NOT_FOUND` if the value does not exist. + function indexOf(Uint8Set storage set, uint8 value) internal view returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + result := not(0) + let packed := sload(set.slot) + let m := shl(and(0xff, value), 1) + if and(packed, m) { + result := 0 + for { let p := and(packed, sub(m, 1)) } p {} { + p := xor(p, and(p, add(1, not(p)))) + result := add(result, 1) + } + } + } + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PRIVATE HELPERS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ diff --git a/test/EnumerableSetLib.t.sol b/test/EnumerableSetLib.t.sol index cd60a988f4..a8964ba619 100644 --- a/test/EnumerableSetLib.t.sol +++ b/test/EnumerableSetLib.t.sol @@ -844,4 +844,64 @@ contract EnumerableSetLibTest is SoladyTest { function updateUint8Set(uint8 value, bool isAdd, uint256 cap) public returns (bool) { return uint8Set.update(value, isAdd, cap); } + + function testAddressSetIndexOf(bytes32 r) public { + LibPRNG.PRNG memory prng; + prng.state = uint256(r); + address[] memory a = new address[](_bound(_random(), 0, 16)); + + for (uint256 i; i != a.length; ++i) { + address value = address(uint160(prng.next())); + addressSet.add(value); + a[i] = value; + } + + if (a.length != 0) { + uint256 i = _random() % a.length; + address value = a[i]; + assertEq(addressSet.indexOf(value), i); + assertEq(addressSet.at(addressSet.indexOf(value)), value); + if (_randomChance(2)) { + value = address(bytes20(keccak256(abi.encode(value, value, "abcdef")))); + assertEq(addressSet.indexOf(value), EnumerableSetLib.NOT_FOUND); + } + } + } + + function testBytes32SetIndexOf(bytes32 r) public { + LibPRNG.PRNG memory prng; + prng.state = uint256(r); + bytes32[] memory a = new bytes32[](_bound(_random(), 0, 16)); + + for (uint256 i; i != a.length; ++i) { + bytes32 value = bytes32(prng.next()); + bytes32Set.add(value); + a[i] = value; + } + + if (a.length != 0) { + uint256 i = _random() % a.length; + bytes32 value = a[i]; + assertEq(bytes32Set.indexOf(value), i); + assertEq(bytes32Set.at(bytes32Set.indexOf(value)), value); + if (_randomChance(2)) { + value = keccak256(abi.encode(value, value, "abcdef")); + assertEq(bytes32Set.indexOf(value), EnumerableSetLib.NOT_FOUND); + } + } + } + + function testEnumerableUint8SetIndexOf(uint256 flags, uint8 value) public { + uint8[] memory ordinals = _flagsToOrdinals(flags); + unchecked { + for (uint256 i; i != ordinals.length; ++i) { + uint8Set.add(ordinals[i]); + } + if (uint8Set.contains(value)) { + assertEq(uint8Set.at(uint8Set.indexOf(value)), value); + } else { + assertEq(uint8Set.indexOf(value), EnumerableSetLib.NOT_FOUND); + } + } + } } From 5a16cf73a96bec0496504fff9c625d15fc307880 Mon Sep 17 00:00:00 2001 From: Vectorized Date: Mon, 4 Aug 2025 11:28:57 +0000 Subject: [PATCH 2/2] Regen docs --- docs/utils/enumerablesetlib.md | 67 +++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/docs/utils/enumerablesetlib.md b/docs/utils/enumerablesetlib.md index a16ba00134..cee5aaaa28 100644 --- a/docs/utils/enumerablesetlib.md +++ b/docs/utils/enumerablesetlib.md @@ -45,6 +45,16 @@ error ExceedsCapacity() Cannot accommodate a new unique value with the capacity. +## Constants + +### NOT_FOUND + +```solidity +uint256 internal constant NOT_FOUND = type(uint256).max +``` + +The index to represent a value that does not exist. + ## Structs ### AddressSet @@ -538,4 +548,59 @@ function at(Uint8Set storage set, uint256 i) returns (uint8 result) ``` -Returns the element at index `i` in the set. Reverts if `i` is out-of-bounds. \ No newline at end of file +Returns the element at index `i` in the set. Reverts if `i` is out-of-bounds. + +### indexOf(AddressSet,address) + +```solidity +function indexOf(AddressSet storage set, address value) + internal + view + returns (uint256 result) +``` + +Returns the index of `value`. Returns `NOT_FOUND` if the value does not exist. + +### indexOf(Bytes32Set,bytes32) + +```solidity +function indexOf(Bytes32Set storage set, bytes32 value) + internal + view + returns (uint256 result) +``` + +Returns the index of `value`. Returns `NOT_FOUND` if the value does not exist. + +### indexOf(Uint256Set,uint256) + +```solidity +function indexOf(Uint256Set storage set, uint256 i) + internal + view + returns (uint256 result) +``` + +Returns the index of `value`. Returns `NOT_FOUND` if the value does not exist. + +### indexOf(Int256Set,int256) + +```solidity +function indexOf(Int256Set storage set, int256 i) + internal + view + returns (uint256 result) +``` + +Returns the index of `value`. Returns `NOT_FOUND` if the value does not exist. + +### indexOf(Uint8Set,uint8) + +```solidity +function indexOf(Uint8Set storage set, uint8 value) + internal + view + returns (uint256 result) +``` + +Returns the index of `value`. Returns `NOT_FOUND` if the value does not exist. \ No newline at end of file