Skip to content

Commit

Permalink
feat: CCIP-2465 enumerableMap for addressToBytes Mapping (#14012)
Browse files Browse the repository at this point in the history
* feat: CCIP-2465 enumerableMap for addressToBytes Mapping

* fix: CCIP-2465 prettier write for new library contracts

* fix: CCIP-2465 gas-snapshot updated and changeset added for new library

* fix: CCIP-2465 gassnapshot corrections for EnumerableAddresses
  • Loading branch information
defistar authored Aug 12, 2024
1 parent 1fcfd80 commit 518cc28
Show file tree
Hide file tree
Showing 5 changed files with 338 additions and 31 deletions.
5 changes: 5 additions & 0 deletions contracts/.changeset/nice-planets-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/contracts': minor
---

EnumerableMap Library for an Address to Bytes mapping
28 changes: 18 additions & 10 deletions contracts/gas-snapshots/shared.gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,29 @@ 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_at:testAtSuccess() (gas: 95086)
EnumerableMapAddresses_at:testBytes32AtSuccess() (gas: 94877)
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_get:testBytes32GetSuccess() (gas: 94278)
EnumerableMapAddresses_get:testGetSuccess() (gas: 94453)
EnumerableMapAddresses_get_errorMessage:testGetErrorMessageSuccess() (gas: 94489)
EnumerableMapAddresses_length:testBytes32LengthSuccess() (gas: 72445)
EnumerableMapAddresses_length:testLengthSuccess() (gas: 72640)
EnumerableMapAddresses_remove:testBytes32RemoveSuccess() (gas: 73462)
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)
EnumerableMapAddresses_tryGet:testBytes32TryGetSuccess() (gas: 94622)
EnumerableMapAddresses_tryGet:testTryGetSuccess() (gas: 94893)
EnumerableMapAddresses_at:testBytesAtSuccess() (gas: 96564)
EnumerableMapAddresses_contains:testBytesContainsSuccess() (gas: 94012)
EnumerableMapAddresses_get:testBytesGetSuccess() (gas: 95879)
EnumerableMapAddresses_get_errorMessage:testBytesGetErrorMessageSuccess() (gas: 95878)
EnumerableMapAddresses_length:testBytesLengthSuccess() (gas: 73011)
EnumerableMapAddresses_remove:testBytesRemoveSuccess() (gas: 74249)
EnumerableMapAddresses_set:testBytesSetSuccess() (gas: 95428)
EnumerableMapAddresses_tryGet:testBytesTryGetSuccess() (gas: 96279)
OpStackBurnMintERC677_constructor:testConstructorSuccess() (gas: 1743649)
OpStackBurnMintERC677_interfaceCompatibility:testBurnCompatibility() (gas: 298649)
OpStackBurnMintERC677_interfaceCompatibility:testMintCompatibility() (gas: 137957)
Expand Down
89 changes: 87 additions & 2 deletions contracts/src/v0.8/shared/enumerable/EnumerableMapAddresses.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
// SPDX-License-Identifier: MIT
/* solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore */
pragma solidity ^0.8.0;

import {EnumerableMap} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableMap.sol";
import {EnumerableMapBytes32} from "./EnumerableMapBytes32.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;
using EnumerableMapBytes32 for EnumerableMapBytes32.Bytes32ToBytesMap;

struct AddressToAddressMap {
EnumerableMap.UintToAddressMap _inner;
Expand Down Expand Up @@ -57,8 +60,6 @@ library EnumerableMapAddresses {
return map._inner.get(uint256(uint160(key)), errorMessage);
}

// AddressToBytes32Map

struct AddressToBytes32Map {
EnumerableMap.Bytes32ToBytes32Map _inner;
}
Expand Down Expand Up @@ -137,4 +138,88 @@ library EnumerableMapAddresses {
function get(AddressToBytes32Map storage map, address key) internal view returns (bytes32) {
return map._inner.get(bytes32(uint256(uint160(key))));
}

struct AddressToBytesMap {
EnumerableMapBytes32.Bytes32ToBytesMap _inner;
}

/**
* @dev Sets the value for `key` in the map. Returns true if the key was added to the map, that is if it was not already present.
* @param map The map where the value will be set
* @param key The key to set the value for
* @param value The value to set for the key
* @return bool indicating whether the key was added to the map
*/
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
function set(AddressToBytesMap storage map, address key, bytes memory value) internal returns (bool) {
return map._inner.set(bytes32(uint256(uint160(key))), value);
}

/**
* @dev Removes the value for `key` in the map. Returns true if the key was removed from the map, that is if it was present.
* @param map The map where the value will be removed
* @param key The key to remove the value for
* @return bool indicating whether the key was removed from the map
*/
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
function remove(AddressToBytesMap storage map, address key) internal returns (bool) {
return map._inner.remove(bytes32(uint256(uint160(key))));
}

/**
* @dev Checks if the map contains the `key`. Returns true if the key is in the map.
* @param map The map to check for the presence of the key
* @param key The key to check for presence in the map
* @return bool indicating whether the key is in the map
*/
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
function contains(AddressToBytesMap 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.
* @param map The map to check the length of
* @return uint256 indicating the number of elements in the map
*/
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
function length(AddressToBytesMap storage map) internal view returns (uint256) {
return map._inner.length();
}

/**
* @dev Returns the element stored at position `index` in the map. 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.
* @param map The map to retrieve the element from
* @param index The index to retrieve the element at
* @return address The key of the element at the specified index
* @return bytes The value of the element at the specified index
*/
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
function at(AddressToBytesMap storage map, uint256 index) internal view returns (address, bytes memory) {
(bytes32 key, bytes memory value) = map._inner.at(index);
return (address(uint160(uint256(key))), value);
}

/**
* @dev Tries to return the value associated with `key`. Does not revert if `key` is not in the map.
* @param map The map to retrieve the value from
* @param key The key to retrieve the value for
* @return bool indicating whether the key was in the map
* @return bytes The value associated with the key
*/
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
function tryGet(AddressToBytesMap storage map, address key) internal view returns (bool, bytes memory) {
return map._inner.tryGet(bytes32(uint256(uint160(key))));
}

/**
* @dev Returns the value associated with `key`.
* @param map The map to retrieve the value from
* @param key The key to retrieve the value for
* @return bytes The value associated with the key
*/
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
function get(AddressToBytesMap storage map, address key) internal view returns (bytes memory) {
return map._inner.get(bytes32(uint256(uint160(key))));
}
}
136 changes: 136 additions & 0 deletions contracts/src/v0.8/shared/enumerable/EnumerableMapBytes32.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: MIT
/* solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore */
pragma solidity ^0.8.0;

import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableSet.sol";

/**
* @dev Library for managing an enumerable variant of Solidity's
* https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`]
* type.
*
* Maps have the following properties:
*
* - Entries are added, removed, and checked for existence in constant time
* (O(1)).
* - Entries are enumerated in O(n). No guarantees are made on the ordering.
*
* ```
* contract Example {
* // Add the library methods
* using EnumerableMapBytes32 for EnumerableMapBytes32.Bytes32ToBytesMap;
*
* // Declare a set state variable
* EnumerableMapBytes32.Bytes32ToBytesMap private myMap;
* }
* ```
*
* The following map types are supported:
*
* - `bytes32 -> bytes` (`Bytes32ToBytes`)
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean up an EnumerableMapBytes32, you should remove all elements one by one.
* ====
*/
library EnumerableMapBytes32 {
using EnumerableSet for EnumerableSet.Bytes32Set;

error NonexistentKeyError();

struct Bytes32ToBytesMap {
EnumerableSet.Bytes32Set _keys;
mapping(bytes32 => bytes) _values;
}

/**
* @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(Bytes32ToBytesMap storage map, bytes32 key, bytes memory value) internal returns (bool) {
map._values[key] = value;
return map._keys.add(key);
}

/**
* @dev Removes a key-value pair 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(Bytes32ToBytesMap storage map, bytes32 key) internal returns (bool) {
delete map._values[key];
return map._keys.remove(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(Bytes32ToBytesMap storage map, bytes32 key) internal view returns (bool) {
return map._keys.contains(key);
}

/**
* @dev Returns the number of key-value pairs in the map. O(1).
*/
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
function length(Bytes32ToBytesMap storage map) internal view returns (uint256) {
return map._keys.length();
}

/**
* @dev Returns the key-value pair stored at position `index` in the map. O(1).
*
* Note that there are no guarantees on the ordering of entries inside the
* array, and it may change when more entries 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(Bytes32ToBytesMap storage map, uint256 index) internal view returns (bytes32, bytes memory) {
bytes32 key = map._keys.at(index);
return (key, map._values[key]);
}

/**
* @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(Bytes32ToBytesMap storage map, bytes32 key) internal view returns (bool, bytes memory) {
bytes memory value = map._values[key];
if (value.length == 0) {
return (contains(map, key), bytes(""));
} else {
return (true, 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(Bytes32ToBytesMap storage map, bytes32 key) internal view returns (bytes memory) {
bytes memory value = map._values[key];
if (value.length == 0 && !contains(map, key)) {
revert NonexistentKeyError();
}
return value;
}
}
Loading

0 comments on commit 518cc28

Please sign in to comment.