diff --git a/CHANGELOG.md b/CHANGELOG.md index 64154934d..171186945 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * `Address`: added `functionStaticCall` and `functionDelegateCall`, similar to the existing `functionCall`. ([#2333](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2333)) * `TimelockController`: added a contract to augment access control schemes with a delay. ([#2364](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2364)) + * `EnumerableSet`: added `BytesSet`, for sets of `bytes32`. ([#2395](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2395)) ## 3.2.0 (2020-09-10) diff --git a/contracts/mocks/EnumerableSetMock.sol b/contracts/mocks/EnumerableSetMock.sol index f9a2c9727..2241b3236 100644 --- a/contracts/mocks/EnumerableSetMock.sol +++ b/contracts/mocks/EnumerableSetMock.sol @@ -4,6 +4,37 @@ pragma solidity ^0.6.0; import "../utils/EnumerableSet.sol"; +// Bytes32Set +contract EnumerableBytes32SetMock { + using EnumerableSet for EnumerableSet.Bytes32Set; + + event OperationResult(bool result); + + EnumerableSet.Bytes32Set private _set; + + function contains(bytes32 value) public view returns (bool) { + return _set.contains(value); + } + + function add(bytes32 value) public { + bool result = _set.add(value); + emit OperationResult(result); + } + + function remove(bytes32 value) public { + bool result = _set.remove(value); + emit OperationResult(result); + } + + function length() public view returns (uint256) { + return _set.length(); + } + + function at(uint256 index) public view returns (bytes32) { + return _set.at(index); + } +} + // AddressSet contract EnumerableAddressSetMock { using EnumerableSet for EnumerableSet.AddressSet; @@ -64,4 +95,4 @@ contract EnumerableUintSetMock { function at(uint256 index) public view returns (uint256) { return _set.at(index); } -} \ No newline at end of file +} diff --git a/contracts/utils/EnumerableSet.sol b/contracts/utils/EnumerableSet.sol index 7f4c761ab..3685c648b 100644 --- a/contracts/utils/EnumerableSet.sol +++ b/contracts/utils/EnumerableSet.sol @@ -23,8 +23,8 @@ pragma solidity ^0.6.0; * } * ``` * - * As of v3.0.0, only sets of type `address` (`AddressSet`) and `uint256` - * (`UintSet`) are supported. + * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) + * and `uint256` (`UintSet`) are supported. */ library EnumerableSet { // To implement this library for multiple types with as little code @@ -132,6 +132,60 @@ library EnumerableSet { return set._values[index]; } + // Bytes32Set + + struct Bytes32Set { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { + return _add(set._inner, value); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { + return _remove(set._inner, value); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { + return _contains(set._inner, value); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(Bytes32Set storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value 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(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { + return _at(set._inner, index); + } + // AddressSet struct AddressSet { diff --git a/test/utils/EnumerableSet.test.js b/test/utils/EnumerableSet.test.js index 71aa2c233..2b7d0a3d6 100644 --- a/test/utils/EnumerableSet.test.js +++ b/test/utils/EnumerableSet.test.js @@ -1,14 +1,28 @@ const { BN } = require('@openzeppelin/test-helpers'); +const EnumerableBytes32SetMock = artifacts.require('EnumerableBytes32SetMock'); const EnumerableAddressSetMock = artifacts.require('EnumerableAddressSetMock'); const EnumerableUintSetMock = artifacts.require('EnumerableUintSetMock'); const { shouldBehaveLikeSet } = require('./EnumerableSet.behavior'); contract('EnumerableSet', function (accounts) { + // Bytes32Set + describe('EnumerableBytes32Set', function () { + const bytesA = '0xdeadbeef'.padEnd(66, '0'); + const bytesB = '0x0123456789'.padEnd(66, '0'); + const bytesC = '0x42424242'.padEnd(66, '0'); + + beforeEach(async function () { + this.set = await EnumerableBytes32SetMock.new(); + }); + + shouldBehaveLikeSet(bytesA, bytesB, bytesC); + }); + // AddressSet describe('EnumerableAddressSet', function () { - const [ accountA, accountB, accountC ] = accounts; + const [accountA, accountB, accountC] = accounts; beforeEach(async function () { this.set = await EnumerableAddressSetMock.new();