-
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.
Add EnumerableMapAddresses library (#13428)
* feat: add EnumerableMapAddresses library * refactor: remove redundant helpers
- Loading branch information
1 parent
eeb363f
commit a02bb0a
Showing
4 changed files
with
336 additions
and
0 deletions.
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 | ||
--- | ||
|
||
#added EnumerableMapAddresses shared lib for AddressToAddress and AddressToBytes32 maps |
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
140 changes: 140 additions & 0 deletions
140
contracts/src/v0.8/shared/enumerable/EnumerableMapAddresses.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,140 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import {EnumerableMap} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableMap.sol"; | ||
|
||
// TODO: the lib can be replaced with OZ v5.1 post-upgrade, which has AddressToAddressMap and AddressToBytes32Map | ||
library EnumerableMapAddresses { | ||
using EnumerableMap for EnumerableMap.UintToAddressMap; | ||
using EnumerableMap for EnumerableMap.Bytes32ToBytes32Map; | ||
|
||
struct AddressToAddressMap { | ||
EnumerableMap.UintToAddressMap _inner; | ||
} | ||
|
||
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore | ||
function set(AddressToAddressMap storage map, address key, address value) internal returns (bool) { | ||
return map._inner.set(uint256(uint160(key)), value); | ||
} | ||
|
||
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore | ||
function remove(AddressToAddressMap storage map, address key) internal returns (bool) { | ||
return map._inner.remove(uint256(uint160(key))); | ||
} | ||
|
||
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore | ||
function contains(AddressToAddressMap storage map, address key) internal view returns (bool) { | ||
return map._inner.contains(uint256(uint160(key))); | ||
} | ||
|
||
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore | ||
function length(AddressToAddressMap storage map) internal view returns (uint256) { | ||
return map._inner.length(); | ||
} | ||
|
||
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore | ||
function at(AddressToAddressMap storage map, uint256 index) internal view returns (address, address) { | ||
(uint256 key, address value) = map._inner.at(index); | ||
return (address(uint160(key)), value); | ||
} | ||
|
||
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore | ||
function tryGet(AddressToAddressMap storage map, address key) internal view returns (bool, address) { | ||
return map._inner.tryGet(uint256(uint160(key))); | ||
} | ||
|
||
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore | ||
function get(AddressToAddressMap storage map, address key) internal view returns (address) { | ||
return map._inner.get(uint256(uint160(key))); | ||
} | ||
|
||
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore | ||
function get( | ||
AddressToAddressMap storage map, | ||
address key, | ||
string memory errorMessage | ||
) internal view returns (address) { | ||
return map._inner.get(uint256(uint160(key)), errorMessage); | ||
} | ||
|
||
// AddressToBytes32Map | ||
|
||
struct AddressToBytes32Map { | ||
EnumerableMap.Bytes32ToBytes32Map _inner; | ||
} | ||
|
||
/** | ||
* @dev Adds a key-value pair to a map, or updates the value for an existing | ||
* key. O(1). | ||
* | ||
* Returns true if the key was added to the map, that is if it was not | ||
* already present. | ||
*/ | ||
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore | ||
function set(AddressToBytes32Map storage map, address key, bytes32 value) internal returns (bool) { | ||
return map._inner.set(bytes32(uint256(uint160(key))), value); | ||
} | ||
|
||
/** | ||
* @dev Removes a value from a map. O(1). | ||
* | ||
* Returns true if the key was removed from the map, that is if it was present. | ||
*/ | ||
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore | ||
function remove(AddressToBytes32Map storage map, address key) internal returns (bool) { | ||
return map._inner.remove(bytes32(uint256(uint160(key)))); | ||
} | ||
|
||
/** | ||
* @dev Returns true if the key is in the map. O(1). | ||
*/ | ||
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore | ||
function contains(AddressToBytes32Map storage map, address key) internal view returns (bool) { | ||
return map._inner.contains(bytes32(uint256(uint160(key)))); | ||
} | ||
|
||
/** | ||
* @dev Returns the number of elements in the map. O(1). | ||
*/ | ||
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore | ||
function length(AddressToBytes32Map storage map) internal view returns (uint256) { | ||
return map._inner.length(); | ||
} | ||
|
||
/** | ||
* @dev Returns the element stored at position `index` in the map. O(1). | ||
* Note that there are no guarantees on the ordering of values inside the | ||
* array, and it may change when more values are added or removed. | ||
* | ||
* Requirements: | ||
* | ||
* - `index` must be strictly less than {length}. | ||
*/ | ||
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore | ||
function at(AddressToBytes32Map storage map, uint256 index) internal view returns (address, bytes32) { | ||
(bytes32 key, bytes32 value) = map._inner.at(index); | ||
return (address(uint160(uint256(key))), value); | ||
} | ||
|
||
/** | ||
* @dev Tries to returns the value associated with `key`. O(1). | ||
* Does not revert if `key` is not in the map. | ||
*/ | ||
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore | ||
function tryGet(AddressToBytes32Map storage map, address key) internal view returns (bool, bytes32) { | ||
(bool success, bytes32 value) = map._inner.tryGet(bytes32(uint256(uint160(key)))); | ||
return (success, value); | ||
} | ||
|
||
/** | ||
* @dev Returns the value associated with `key`. O(1). | ||
* | ||
* Requirements: | ||
* | ||
* - `key` must be in the map. | ||
*/ | ||
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore | ||
function get(AddressToBytes32Map storage map, address key) internal view returns (bytes32) { | ||
return map._inner.get(bytes32(uint256(uint160(key)))); | ||
} | ||
} |
176 changes: 176 additions & 0 deletions
176
contracts/src/v0.8/shared/test/enumerable/EnumerableMapAddresses.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,176 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.19; | ||
|
||
import {BaseTest} from "../BaseTest.t.sol"; | ||
import {EnumerableMapAddresses} from "../../enumerable/EnumerableMapAddresses.sol"; | ||
|
||
contract EnumerableMapAddressesTest is BaseTest { | ||
using EnumerableMapAddresses for EnumerableMapAddresses.AddressToBytes32Map; | ||
using EnumerableMapAddresses for EnumerableMapAddresses.AddressToAddressMap; | ||
|
||
EnumerableMapAddresses.AddressToAddressMap internal s_addressToAddressMap; | ||
EnumerableMapAddresses.AddressToBytes32Map internal s_addressToBytes32Map; | ||
|
||
bytes32 internal constant MOCK_BYTES32_VALUE = bytes32(uint256(42)); | ||
|
||
function setUp() public virtual override { | ||
BaseTest.setUp(); | ||
} | ||
} | ||
|
||
contract EnumerableMapAddresses_set is EnumerableMapAddressesTest { | ||
using EnumerableMapAddresses for EnumerableMapAddresses.AddressToBytes32Map; | ||
using EnumerableMapAddresses for EnumerableMapAddresses.AddressToAddressMap; | ||
|
||
function testSetSuccess() public { | ||
assertTrue(!s_addressToAddressMap.contains(address(this))); | ||
assertTrue(s_addressToAddressMap.set(address(this), address(this))); | ||
assertTrue(s_addressToAddressMap.contains(address(this))); | ||
assertTrue(!s_addressToAddressMap.set(address(this), address(this))); | ||
} | ||
|
||
function testBytes32SetSuccess() public { | ||
assertTrue(!s_addressToBytes32Map.contains(address(this))); | ||
assertTrue(s_addressToBytes32Map.set(address(this), MOCK_BYTES32_VALUE)); | ||
assertTrue(s_addressToBytes32Map.contains(address(this))); | ||
assertTrue(!s_addressToBytes32Map.set(address(this), MOCK_BYTES32_VALUE)); | ||
} | ||
} | ||
|
||
contract EnumerableMapAddresses_remove is EnumerableMapAddressesTest { | ||
using EnumerableMapAddresses for EnumerableMapAddresses.AddressToBytes32Map; | ||
using EnumerableMapAddresses for EnumerableMapAddresses.AddressToAddressMap; | ||
|
||
function testRemoveSuccess() public { | ||
assertTrue(!s_addressToAddressMap.contains(address(this))); | ||
assertTrue(s_addressToAddressMap.set(address(this), address(this))); | ||
assertTrue(s_addressToAddressMap.contains(address(this))); | ||
assertTrue(s_addressToAddressMap.remove(address(this))); | ||
assertTrue(!s_addressToAddressMap.contains(address(this))); | ||
assertTrue(!s_addressToAddressMap.remove(address(this))); | ||
} | ||
|
||
function testBytes32RemoveSuccess() public { | ||
assertTrue(!s_addressToBytes32Map.contains(address(this))); | ||
assertTrue(s_addressToBytes32Map.set(address(this), MOCK_BYTES32_VALUE)); | ||
assertTrue(s_addressToBytes32Map.contains(address(this))); | ||
assertTrue(s_addressToBytes32Map.remove(address(this))); | ||
assertTrue(!s_addressToBytes32Map.contains(address(this))); | ||
assertTrue(!s_addressToBytes32Map.remove(address(this))); | ||
} | ||
} | ||
|
||
contract EnumerableMapAddresses_contains is EnumerableMapAddressesTest { | ||
using EnumerableMapAddresses for EnumerableMapAddresses.AddressToBytes32Map; | ||
using EnumerableMapAddresses for EnumerableMapAddresses.AddressToAddressMap; | ||
|
||
function testContainsSuccess() public { | ||
assertTrue(!s_addressToAddressMap.contains(address(this))); | ||
assertTrue(s_addressToAddressMap.set(address(this), address(this))); | ||
assertTrue(s_addressToAddressMap.contains(address(this))); | ||
} | ||
|
||
function testBytes32ContainsSuccess() public { | ||
assertTrue(!s_addressToBytes32Map.contains(address(this))); | ||
assertTrue(s_addressToBytes32Map.set(address(this), MOCK_BYTES32_VALUE)); | ||
assertTrue(s_addressToBytes32Map.contains(address(this))); | ||
} | ||
} | ||
|
||
contract EnumerableMapAddresses_length is EnumerableMapAddressesTest { | ||
using EnumerableMapAddresses for EnumerableMapAddresses.AddressToBytes32Map; | ||
using EnumerableMapAddresses for EnumerableMapAddresses.AddressToAddressMap; | ||
|
||
function testLengthSuccess() public { | ||
assertTrue(s_addressToAddressMap.length() == 0); | ||
assertTrue(s_addressToAddressMap.set(address(this), address(this))); | ||
assertTrue(s_addressToAddressMap.length() == 1); | ||
assertTrue(s_addressToAddressMap.remove(address(this))); | ||
assertTrue(s_addressToAddressMap.length() == 0); | ||
} | ||
|
||
function testBytes32LengthSuccess() public { | ||
assertTrue(s_addressToBytes32Map.length() == 0); | ||
assertTrue(s_addressToBytes32Map.set(address(this), MOCK_BYTES32_VALUE)); | ||
assertTrue(s_addressToBytes32Map.length() == 1); | ||
assertTrue(s_addressToBytes32Map.remove(address(this))); | ||
assertTrue(s_addressToBytes32Map.length() == 0); | ||
} | ||
} | ||
|
||
contract EnumerableMapAddresses_at is EnumerableMapAddressesTest { | ||
using EnumerableMapAddresses for EnumerableMapAddresses.AddressToBytes32Map; | ||
using EnumerableMapAddresses for EnumerableMapAddresses.AddressToAddressMap; | ||
|
||
function testAtSuccess() public { | ||
assertTrue(s_addressToAddressMap.length() == 0); | ||
assertTrue(s_addressToAddressMap.set(address(this), address(this))); | ||
assertTrue(s_addressToAddressMap.length() == 1); | ||
(address key, address value) = s_addressToAddressMap.at(0); | ||
assertTrue(key == address(this)); | ||
assertTrue(value == address(this)); | ||
} | ||
|
||
function testBytes32AtSuccess() public { | ||
assertTrue(s_addressToBytes32Map.length() == 0); | ||
assertTrue(s_addressToBytes32Map.set(address(this), MOCK_BYTES32_VALUE)); | ||
assertTrue(s_addressToBytes32Map.length() == 1); | ||
(address key, bytes32 value) = s_addressToBytes32Map.at(0); | ||
assertTrue(key == address(this)); | ||
assertTrue(value == MOCK_BYTES32_VALUE); | ||
} | ||
} | ||
|
||
contract EnumerableMapAddresses_tryGet is EnumerableMapAddressesTest { | ||
using EnumerableMapAddresses for EnumerableMapAddresses.AddressToBytes32Map; | ||
using EnumerableMapAddresses for EnumerableMapAddresses.AddressToAddressMap; | ||
|
||
function testTryGetSuccess() public { | ||
assertTrue(!s_addressToAddressMap.contains(address(this))); | ||
assertTrue(s_addressToAddressMap.set(address(this), address(this))); | ||
assertTrue(s_addressToAddressMap.contains(address(this))); | ||
(bool success, address value) = s_addressToAddressMap.tryGet(address(this)); | ||
assertTrue(success); | ||
assertTrue(value == address(this)); | ||
} | ||
|
||
function testBytes32TryGetSuccess() public { | ||
assertTrue(!s_addressToBytes32Map.contains(address(this))); | ||
assertTrue(s_addressToBytes32Map.set(address(this), MOCK_BYTES32_VALUE)); | ||
assertTrue(s_addressToBytes32Map.contains(address(this))); | ||
(bool success, bytes32 value) = s_addressToBytes32Map.tryGet(address(this)); | ||
assertTrue(success); | ||
assertTrue(value == MOCK_BYTES32_VALUE); | ||
} | ||
} | ||
|
||
contract EnumerableMapAddresses_get is EnumerableMapAddressesTest { | ||
using EnumerableMapAddresses for EnumerableMapAddresses.AddressToBytes32Map; | ||
using EnumerableMapAddresses for EnumerableMapAddresses.AddressToAddressMap; | ||
|
||
function testGetSuccess() public { | ||
assertTrue(!s_addressToAddressMap.contains(address(this))); | ||
assertTrue(s_addressToAddressMap.set(address(this), address(this))); | ||
assertTrue(s_addressToAddressMap.contains(address(this))); | ||
assertTrue(s_addressToAddressMap.get(address(this)) == address(this)); | ||
} | ||
|
||
function testBytes32GetSuccess() public { | ||
assertTrue(!s_addressToBytes32Map.contains(address(this))); | ||
assertTrue(s_addressToBytes32Map.set(address(this), MOCK_BYTES32_VALUE)); | ||
assertTrue(s_addressToBytes32Map.contains(address(this))); | ||
assertTrue(s_addressToBytes32Map.get(address(this)) == MOCK_BYTES32_VALUE); | ||
} | ||
} | ||
|
||
contract EnumerableMapAddresses_get_errorMessage is EnumerableMapAddressesTest { | ||
using EnumerableMapAddresses for EnumerableMapAddresses.AddressToBytes32Map; | ||
using EnumerableMapAddresses for EnumerableMapAddresses.AddressToAddressMap; | ||
|
||
function testGetErrorMessageSuccess() public { | ||
assertTrue(!s_addressToAddressMap.contains(address(this))); | ||
assertTrue(s_addressToAddressMap.set(address(this), address(this))); | ||
assertTrue(s_addressToAddressMap.contains(address(this))); | ||
assertTrue(s_addressToAddressMap.get(address(this), "EnumerableMapAddresses: nonexistent key") == address(this)); | ||
} | ||
} |