diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f272d827d7..71182a1785c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * `CrossChainEnabledPolygonChild`: replace the `require` statement with the custom error `NotCrossChainCall`. ([#3380](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3380)) * `ERC20FlashMint`: Add customizable flash fee receiver. ([#3327](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3327)) * `Strings`: add a new overloaded function `toHexString` that converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. ([#3403](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3403)) + * `EnumerableMap`: add new `UintToUintMap` map type. ([#3338](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3338)) * `SafeCast`: add support for many more types, using procedural code generation. ([#3245](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3245)) ## 4.6.0 (2022-04-26) diff --git a/contracts/access/AccessControl.sol b/contracts/access/AccessControl.sol index 8274bb55c11..2103be9144b 100644 --- a/contracts/access/AccessControl.sol +++ b/contracts/access/AccessControl.sol @@ -138,6 +138,8 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { * Requirements: * * - the caller must have ``role``'s admin role. + * + * May emit a {RoleGranted} event. */ function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) { _grantRole(role, account); @@ -151,6 +153,8 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { * Requirements: * * - the caller must have ``role``'s admin role. + * + * May emit a {RoleRevoked} event. */ function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) { _revokeRole(role, account); @@ -169,6 +173,8 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { * Requirements: * * - the caller must be `account`. + * + * May emit a {RoleRevoked} event. */ function renounceRole(bytes32 role, address account) public virtual override { require(account == _msgSender(), "AccessControl: can only renounce roles for self"); @@ -183,6 +189,8 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { * event. Note that unlike {grantRole}, this function doesn't perform any * checks on the calling account. * + * May emit a {RoleGranted} event. + * * [WARNING] * ==== * This function should only be called from the constructor when setting @@ -213,6 +221,8 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { * @dev Grants `role` to `account`. * * Internal function without access restriction. + * + * May emit a {RoleGranted} event. */ function _grantRole(bytes32 role, address account) internal virtual { if (!hasRole(role, account)) { @@ -225,6 +235,8 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { * @dev Revokes `role` from `account`. * * Internal function without access restriction. + * + * May emit a {RoleRevoked} event. */ function _revokeRole(bytes32 role, address account) internal virtual { if (hasRole(role, account)) { diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol index b293cd16384..382c3a4affb 100644 --- a/contracts/governance/TimelockController.sol +++ b/contracts/governance/TimelockController.sol @@ -131,7 +131,7 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver * @dev Returns whether an id correspond to a registered operation. This * includes both Pending, Ready and Done operations. */ - function isOperation(bytes32 id) public view virtual returns (bool pending) { + function isOperation(bytes32 id) public view virtual returns (bool registered) { return getTimestamp(id) > 0; } diff --git a/contracts/mocks/EnumerableMapMock.sol b/contracts/mocks/EnumerableMapMock.sol index 7cecd0d60d7..fec07252b5a 100644 --- a/contracts/mocks/EnumerableMapMock.sol +++ b/contracts/mocks/EnumerableMapMock.sol @@ -131,3 +131,46 @@ contract Bytes32ToBytes32MapMock { return _map.get(key, errorMessage); } } + +// UintToUintMap +contract UintToUintMapMock { + using EnumerableMap for EnumerableMap.UintToUintMap; + + event OperationResult(bool result); + + EnumerableMap.UintToUintMap private _map; + + function contains(uint256 key) public view returns (bool) { + return _map.contains(key); + } + + function set(uint256 key, uint256 value) public { + bool result = _map.set(key, value); + emit OperationResult(result); + } + + function remove(uint256 key) public { + bool result = _map.remove(key); + emit OperationResult(result); + } + + function length() public view returns (uint256) { + return _map.length(); + } + + function at(uint256 index) public view returns (uint256 key, uint256 value) { + return _map.at(index); + } + + function tryGet(uint256 key) public view returns (bool, uint256) { + return _map.tryGet(key); + } + + function get(uint256 key) public view returns (uint256) { + return _map.get(key); + } + + function getWithMessage(uint256 key, string calldata errorMessage) public view returns (uint256) { + return _map.get(key, errorMessage); + } +} diff --git a/contracts/utils/structs/EnumerableMap.sol b/contracts/utils/structs/EnumerableMap.sol index 5ff27433a4a..7e8f4e9023f 100644 --- a/contracts/utils/structs/EnumerableMap.sol +++ b/contracts/utils/structs/EnumerableMap.sol @@ -31,6 +31,7 @@ import "./EnumerableSet.sol"; * - `uint256 -> address` (`UintToAddressMap`) since v3.0.0 * - `address -> uint256` (`AddressToUintMap`) since v4.6.0 * - `bytes32 -> bytes32` (`Bytes32ToBytes32`) since v4.6.0 + * - `uint256 -> uint256` (`UintToUintMap`) since v4.7.0 * * [WARNING] * ==== @@ -155,6 +156,98 @@ library EnumerableMap { return value; } + // UintToUintMap + + struct UintToUintMap { + 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. + */ + function set( + UintToUintMap storage map, + uint256 key, + uint256 value + ) internal returns (bool) { + return set(map._inner, bytes32(key), bytes32(value)); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(UintToUintMap storage map, uint256 key) internal returns (bool) { + return remove(map._inner, bytes32(key)); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(UintToUintMap storage map, uint256 key) internal view returns (bool) { + return contains(map._inner, bytes32(key)); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(UintToUintMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the set. 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}. + */ + function at(UintToUintMap storage map, uint256 index) internal view returns (uint256, uint256) { + (bytes32 key, bytes32 value) = at(map._inner, index); + return (uint256(key), uint256(value)); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool, uint256) { + (bool success, bytes32 value) = tryGet(map._inner, bytes32(key)); + return (success, uint256(value)); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(UintToUintMap storage map, uint256 key) internal view returns (uint256) { + return uint256(get(map._inner, bytes32(key))); + } + + /** + * @dev Same as {get}, with a custom error message when `key` is not in the map. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryGet}. + */ + function get( + UintToUintMap storage map, + uint256 key, + string memory errorMessage + ) internal view returns (uint256) { + return uint256(get(map._inner, bytes32(key), errorMessage)); + } + // UintToAddressMap struct UintToAddressMap { @@ -310,8 +403,6 @@ library EnumerableMap { /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. - * - * _Available since v3.4._ */ function tryGet(AddressToUintMap storage map, address key) internal view returns (bool, uint256) { (bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key)))); diff --git a/test/utils/structs/EnumerableMap.test.js b/test/utils/structs/EnumerableMap.test.js index 866ff64397a..4b800d219b0 100644 --- a/test/utils/structs/EnumerableMap.test.js +++ b/test/utils/structs/EnumerableMap.test.js @@ -3,6 +3,7 @@ const { BN, constants } = require('@openzeppelin/test-helpers'); const AddressToUintMapMock = artifacts.require('AddressToUintMapMock'); const UintToAddressMapMock = artifacts.require('UintToAddressMapMock'); const Bytes32ToBytes32MapMock = artifacts.require('Bytes32ToBytes32MapMock'); +const UintToUintMapMock = artifacts.require('UintToUintMapMock'); const { shouldBehaveLikeMap } = require('./EnumerableMap.behavior'); @@ -55,4 +56,17 @@ contract('EnumerableMap', function (accounts) { constants.ZERO_BYTES32, ); }); + + // UintToUintMap + describe('UintToUintMap', function () { + beforeEach(async function () { + this.map = await UintToUintMapMock.new(); + }); + + shouldBehaveLikeMap( + [ keyA, keyB, keyC ], + [ keyA, keyB, keyC ].map(k => k.add(new BN('1332'))), + new BN('0'), + ); + }); });