Skip to content

Commit

Permalink
Add EnumerableMapAddresses library (#13428)
Browse files Browse the repository at this point in the history
* feat: add EnumerableMapAddresses library

* refactor: remove redundant helpers
  • Loading branch information
elatoskinas authored Jun 6, 2024
1 parent eeb363f commit a02bb0a
Show file tree
Hide file tree
Showing 4 changed files with 336 additions and 0 deletions.
5 changes: 5 additions & 0 deletions contracts/.changeset/cyan-apes-perform.md
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
15 changes: 15 additions & 0 deletions contracts/gas-snapshots/shared.gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,21 @@ CallWithExactGas__callWithExactGasSafeReturnData:test_NoContractReverts() (gas:
CallWithExactGas__callWithExactGasSafeReturnData:test_NoGasForCallExactCheckReverts() (gas: 16139)
CallWithExactGas__callWithExactGasSafeReturnData:test_NotEnoughGasForCallReverts() (gas: 16547)
CallWithExactGas__callWithExactGasSafeReturnData:test_callWithExactGasSafeReturnData_ThrowOOGError_Revert() (gas: 36752)
EnumerableMapAddresses_at:testAtSuccess() (gas: 95001)
EnumerableMapAddresses_at:testBytes32AtSuccess() (gas: 94770)
EnumerableMapAddresses_contains:testBytes32ContainsSuccess() (gas: 93518)
EnumerableMapAddresses_contains:testContainsSuccess() (gas: 93696)
EnumerableMapAddresses_get:testBytes32GetSuccess() (gas: 94249)
EnumerableMapAddresses_get:testGetSuccess() (gas: 94436)
EnumerableMapAddresses_get_errorMessage:testGetErrorMessageSuccess() (gas: 94477)
EnumerableMapAddresses_length:testBytes32LengthSuccess() (gas: 72404)
EnumerableMapAddresses_length:testLengthSuccess() (gas: 72582)
EnumerableMapAddresses_remove:testBytes32RemoveSuccess() (gas: 73408)
EnumerableMapAddresses_remove:testRemoveSuccess() (gas: 73686)
EnumerableMapAddresses_set:testBytes32SetSuccess() (gas: 94496)
EnumerableMapAddresses_set:testSetSuccess() (gas: 94685)
EnumerableMapAddresses_tryGet:testBytes32TryGetSuccess() (gas: 94604)
EnumerableMapAddresses_tryGet:testTryGetSuccess() (gas: 94864)
OpStackBurnMintERC677_constructor:testConstructorSuccess() (gas: 1743649)
OpStackBurnMintERC677_interfaceCompatibility:testBurnCompatibility() (gas: 298649)
OpStackBurnMintERC677_interfaceCompatibility:testMintCompatibility() (gas: 137957)
Expand Down
140 changes: 140 additions & 0 deletions contracts/src/v0.8/shared/enumerable/EnumerableMapAddresses.sol
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 contracts/src/v0.8/shared/test/enumerable/EnumerableMapAddresses.t.sol
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));
}
}

0 comments on commit a02bb0a

Please sign in to comment.