Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sixty-tips-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`EnumerableSet`: Add `clear` function to EnumerableSets which deletes all items in the set.
48 changes: 47 additions & 1 deletion contracts/utils/structs/EnumerableSet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

pragma solidity ^0.8.20;

import {Arrays} from "../Arrays.sol";

/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
Expand All @@ -13,7 +15,7 @@ pragma solidity ^0.8.20;
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
* - Elements are enumerated and cleared in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
Expand Down Expand Up @@ -114,6 +116,20 @@ library EnumerableSet {
}
}

/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function _clear(Set storage set) private {
uint256 len = _length(set);
for (uint256 i = 0; i < len; ++i) {
delete set._positions[set._values[i]];
}
Arrays.unsafeSetLength(set._values, 0);
}

/**
* @dev Returns true if the value is in the set. O(1).
*/
Expand Down Expand Up @@ -180,6 +196,16 @@ library EnumerableSet {
return _remove(set._inner, value);
}

/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(Bytes32Set storage set) internal {
_clear(set._inner);
}

/**
* @dev Returns true if the value is in the set. O(1).
*/
Expand Down Expand Up @@ -253,6 +279,16 @@ library EnumerableSet {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}

/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(AddressSet storage set) internal {
_clear(set._inner);
}

/**
* @dev Returns true if the value is in the set. O(1).
*/
Expand Down Expand Up @@ -326,6 +362,16 @@ library EnumerableSet {
return _remove(set._inner, bytes32(value));
}

/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(UintSet storage set) internal {
_clear(set._inner);
}

/**
* @dev Returns true if the value is in the set. O(1).
*/
Expand Down
28 changes: 27 additions & 1 deletion scripts/generate/templates/EnumerableSet.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const { TYPES } = require('./EnumerableSet.opts');
const header = `\
pragma solidity ^0.8.20;

import {Arrays} from "../Arrays.sol";

/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
Expand All @@ -14,7 +16,7 @@ pragma solidity ^0.8.20;
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
* - Elements are enumerated and cleared in O(n). No guarantees are made on the ordering.
*
* \`\`\`solidity
* contract Example {
Expand Down Expand Up @@ -117,6 +119,20 @@ function _remove(Set storage set, bytes32 value) private returns (bool) {
}
}

/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function _clear(Set storage set) private {
uint256 len = _length(set);
for (uint256 i = 0; i < len; ++i) {
delete set._positions[set._values[i]];
}
Arrays.unsafeSetLength(set._values, 0);
}

/**
* @dev Returns true if the value is in the set. O(1).
*/
Expand Down Expand Up @@ -185,6 +201,16 @@ function remove(${name} storage set, ${type} value) internal returns (bool) {
return _remove(set._inner, ${toBytes32(type, 'value')});
}

/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(${name} storage set) internal {
_clear(set._inner);
}

/**
* @dev Returns true if the value is in the set. O(1).
*/
Expand Down
35 changes: 35 additions & 0 deletions test/utils/structs/EnumerableSet.behavior.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,41 @@ function shouldBehaveLikeSet() {
expect(await this.methods.contains(this.valueB)).to.be.false;
});
});

describe('clear', function () {
it('clears a single item', async function () {
await this.methods.add(this.valueA);
await expect(this.methods.length()).to.eventually.equal(1);

await this.methods.clear();

await expect(this.methods.length()).to.eventually.equal(0);
});

it('clears multiple items', async function () {
await this.methods.add(this.valueA);
await this.methods.add(this.valueB);
await this.methods.add(this.valueC);
await expect(this.methods.length()).to.eventually.equal(3);

await this.methods.clear();

await expect(this.methods.length()).to.eventually.equal(0);
});

it('does not revert on empty set', async function () {
await this.methods.clear();
});

it('clear then add value', async function () {
await this.methods.add(this.valueA);
await this.methods.add(this.valueB);
await this.methods.clear();

await this.methods.add(this.valueA);
await expectMembersMatch(this.methods, [this.valueA]);
});
});
}

module.exports = {
Expand Down
1 change: 1 addition & 0 deletions test/utils/structs/EnumerableSet.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ async function fixture() {
methods: getMethods(mock, {
add: `$add(uint256,${type})`,
remove: `$remove(uint256,${type})`,
clear: `$clear_EnumerableSet_${name}(uint256)`,
contains: `$contains(uint256,${type})`,
length: `$length_EnumerableSet_${name}(uint256)`,
at: `$at_EnumerableSet_${name}(uint256,uint256)`,
Expand Down