Add clear function to Enumerable{Set,Map} (#5486)
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
This commit is contained in:
5
.changeset/good-cameras-rush.md
Normal file
5
.changeset/good-cameras-rush.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'openzeppelin-solidity': minor
|
||||
---
|
||||
|
||||
`EnumerableMap`: Add `clear` function to EnumerableMaps which deletes all entries in the map.
|
||||
5
.changeset/sixty-tips-wink.md
Normal file
5
.changeset/sixty-tips-wink.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'openzeppelin-solidity': minor
|
||||
---
|
||||
|
||||
`EnumerableSet`: Add `clear` function to EnumerableSets which deletes all values in the set.
|
||||
@ -16,6 +16,7 @@ import {EnumerableSet} from "./EnumerableSet.sol";
|
||||
* - 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.
|
||||
* - Map can be cleared (all entries removed) in O(n).
|
||||
*
|
||||
* ```solidity
|
||||
* contract Example {
|
||||
@ -90,6 +91,20 @@ library EnumerableMap {
|
||||
return map._keys.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes all the entries from a map. 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 map grows to the point where clearing it consumes too much gas to fit in a block.
|
||||
*/
|
||||
function clear(Bytes32ToBytes32Map storage map) internal {
|
||||
uint256 len = length(map);
|
||||
for (uint256 i = 0; i < len; ++i) {
|
||||
delete map._values[map._keys.at(i)];
|
||||
}
|
||||
map._keys.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns true if the key is in the map. O(1).
|
||||
*/
|
||||
@ -185,6 +200,16 @@ library EnumerableMap {
|
||||
return remove(map._inner, bytes32(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes all the entries from a map. 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 map grows to the point where clearing it consumes too much gas to fit in a block.
|
||||
*/
|
||||
function clear(UintToUintMap storage map) internal {
|
||||
clear(map._inner);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns true if the key is in the map. O(1).
|
||||
*/
|
||||
@ -278,6 +303,16 @@ library EnumerableMap {
|
||||
return remove(map._inner, bytes32(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes all the entries from a map. 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 map grows to the point where clearing it consumes too much gas to fit in a block.
|
||||
*/
|
||||
function clear(UintToAddressMap storage map) internal {
|
||||
clear(map._inner);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns true if the key is in the map. O(1).
|
||||
*/
|
||||
@ -371,6 +406,16 @@ library EnumerableMap {
|
||||
return remove(map._inner, bytes32(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes all the entries from a map. 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 map grows to the point where clearing it consumes too much gas to fit in a block.
|
||||
*/
|
||||
function clear(UintToBytes32Map storage map) internal {
|
||||
clear(map._inner);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns true if the key is in the map. O(1).
|
||||
*/
|
||||
@ -464,6 +509,16 @@ library EnumerableMap {
|
||||
return remove(map._inner, bytes32(uint256(uint160(key))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes all the entries from a map. 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 map grows to the point where clearing it consumes too much gas to fit in a block.
|
||||
*/
|
||||
function clear(AddressToUintMap storage map) internal {
|
||||
clear(map._inner);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns true if the key is in the map. O(1).
|
||||
*/
|
||||
@ -557,6 +612,16 @@ library EnumerableMap {
|
||||
return remove(map._inner, bytes32(uint256(uint160(key))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes all the entries from a map. 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 map grows to the point where clearing it consumes too much gas to fit in a block.
|
||||
*/
|
||||
function clear(AddressToAddressMap storage map) internal {
|
||||
clear(map._inner);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns true if the key is in the map. O(1).
|
||||
*/
|
||||
@ -650,6 +715,16 @@ library EnumerableMap {
|
||||
return remove(map._inner, bytes32(uint256(uint160(key))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes all the entries from a map. 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 map grows to the point where clearing it consumes too much gas to fit in a block.
|
||||
*/
|
||||
function clear(AddressToBytes32Map storage map) internal {
|
||||
clear(map._inner);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns true if the key is in the map. O(1).
|
||||
*/
|
||||
@ -743,6 +818,16 @@ library EnumerableMap {
|
||||
return remove(map._inner, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes all the entries from a map. 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 map grows to the point where clearing it consumes too much gas to fit in a block.
|
||||
*/
|
||||
function clear(Bytes32ToUintMap storage map) internal {
|
||||
clear(map._inner);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns true if the key is in the map. O(1).
|
||||
*/
|
||||
@ -836,6 +921,16 @@ library EnumerableMap {
|
||||
return remove(map._inner, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes all the entries from a map. 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 map grows to the point where clearing it consumes too much gas to fit in a block.
|
||||
*/
|
||||
function clear(Bytes32ToAddressMap storage map) internal {
|
||||
clear(map._inner);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns true if the key is in the map. O(1).
|
||||
*/
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Arrays} from "../Arrays.sol";
|
||||
import {Hashes} from "../cryptography/Hashes.sol";
|
||||
|
||||
/**
|
||||
@ -16,6 +17,7 @@ import {Hashes} from "../cryptography/Hashes.sol";
|
||||
* - 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.
|
||||
* - Set can be cleared (all elements removed) in O(n).
|
||||
*
|
||||
* ```solidity
|
||||
* contract Example {
|
||||
@ -116,6 +118,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).
|
||||
*/
|
||||
@ -182,6 +198,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).
|
||||
*/
|
||||
@ -255,6 +281,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).
|
||||
*/
|
||||
@ -328,6 +364,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).
|
||||
*/
|
||||
@ -442,6 +488,24 @@ 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(Bytes32x2Set storage self) internal {
|
||||
bytes32[2][] storage v = self._values;
|
||||
|
||||
uint256 len = length(self);
|
||||
for (uint256 i = 0; i < len; ++i) {
|
||||
delete self._positions[_hash(v[i])];
|
||||
}
|
||||
assembly ("memory-safe") {
|
||||
sstore(v.slot, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns true if the value is in the self. O(1).
|
||||
*/
|
||||
|
||||
@ -17,6 +17,7 @@ import {EnumerableSet} from "./EnumerableSet.sol";
|
||||
* - 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.
|
||||
* - Map can be cleared (all entries removed) in O(n).
|
||||
*
|
||||
* \`\`\`solidity
|
||||
* contract Example {
|
||||
@ -91,6 +92,20 @@ function remove(Bytes32ToBytes32Map storage map, bytes32 key) internal returns (
|
||||
return map._keys.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes all the entries from a map. 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 map grows to the point where clearing it consumes too much gas to fit in a block.
|
||||
*/
|
||||
function clear(Bytes32ToBytes32Map storage map) internal {
|
||||
uint256 len = length(map);
|
||||
for (uint256 i = 0; i < len; ++i) {
|
||||
delete map._values[map._keys.at(i)];
|
||||
}
|
||||
map._keys.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns true if the key is in the map. O(1).
|
||||
*/
|
||||
@ -188,6 +203,16 @@ function remove(${name} storage map, ${keyType} key) internal returns (bool) {
|
||||
return remove(map._inner, ${toBytes32(keyType, 'key')});
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes all the entries from a map. 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 map grows to the point where clearing it consumes too much gas to fit in a block.
|
||||
*/
|
||||
function clear(${name} storage map) internal {
|
||||
clear(map._inner);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns true if the key is in the map. O(1).
|
||||
*/
|
||||
|
||||
@ -5,6 +5,7 @@ const { TYPES } = require('./EnumerableSet.opts');
|
||||
const header = `\
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Arrays} from "../Arrays.sol";
|
||||
import {Hashes} from "../cryptography/Hashes.sol";
|
||||
|
||||
/**
|
||||
@ -17,6 +18,7 @@ import {Hashes} from "../cryptography/Hashes.sol";
|
||||
* - 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.
|
||||
* - Set can be cleared (all elements removed) in O(n).
|
||||
*
|
||||
* \`\`\`solidity
|
||||
* contract Example {
|
||||
@ -119,6 +121,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).
|
||||
*/
|
||||
@ -187,6 +203,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).
|
||||
*/
|
||||
@ -303,6 +329,24 @@ function remove(${name} storage self, ${type} memory value) internal returns (bo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 self) internal {
|
||||
${type}[] storage v = self._values;
|
||||
|
||||
uint256 len = length(self);
|
||||
for (uint256 i = 0; i < len; ++i) {
|
||||
delete self._positions[_hash(v[i])];
|
||||
}
|
||||
assembly ("memory-safe") {
|
||||
sstore(v.slot, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns true if the value is in the self. O(1).
|
||||
*/
|
||||
|
||||
@ -117,6 +117,49 @@ function shouldBehaveLikeMap() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('clear', function () {
|
||||
it('clears a single entry', async function () {
|
||||
await this.methods.set(this.keyA, this.valueA);
|
||||
|
||||
await this.methods.clear();
|
||||
|
||||
expect(await this.methods.contains(this.keyA)).to.be.false;
|
||||
await expectMembersMatch(this.methods, [], []);
|
||||
});
|
||||
|
||||
it('clears multiple entries', async function () {
|
||||
await this.methods.set(this.keyA, this.valueA);
|
||||
await this.methods.set(this.keyB, this.valueB);
|
||||
await this.methods.set(this.keyC, this.valueC);
|
||||
|
||||
await this.methods.clear();
|
||||
|
||||
expect(await this.methods.contains(this.keyA)).to.be.false;
|
||||
expect(await this.methods.contains(this.keyB)).to.be.false;
|
||||
expect(await this.methods.contains(this.keyC)).to.be.false;
|
||||
await expectMembersMatch(this.methods, [], []);
|
||||
});
|
||||
|
||||
it('does not revert on empty map', async function () {
|
||||
await this.methods.clear();
|
||||
});
|
||||
|
||||
it('clear then add entry', async function () {
|
||||
await this.methods.set(this.keyA, this.valueA);
|
||||
await this.methods.set(this.keyB, this.valueB);
|
||||
await this.methods.set(this.keyC, this.valueC);
|
||||
|
||||
await this.methods.clear();
|
||||
|
||||
await this.methods.set(this.keyA, this.valueA);
|
||||
|
||||
expect(await this.methods.contains(this.keyA)).to.be.true;
|
||||
expect(await this.methods.contains(this.keyB)).to.be.false;
|
||||
expect(await this.methods.contains(this.keyC)).to.be.false;
|
||||
await expectMembersMatch(this.methods, [this.keyA], [this.valueA]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('read', function () {
|
||||
beforeEach(async function () {
|
||||
await this.methods.set(this.keyA, this.valueA);
|
||||
|
||||
@ -26,6 +26,7 @@ async function fixture() {
|
||||
get: `$get_EnumerableMap_${name}(uint256,${keyType})`,
|
||||
tryGet: `$tryGet_EnumerableMap_${name}(uint256,${keyType})`,
|
||||
remove: `$remove_EnumerableMap_${name}(uint256,${keyType})`,
|
||||
clear: `$clear_EnumerableMap_${name}(uint256)`,
|
||||
length: `$length_EnumerableMap_${name}(uint256)`,
|
||||
at: `$at_EnumerableMap_${name}(uint256,uint256)`,
|
||||
contains: `$contains_EnumerableMap_${name}(uint256,${keyType})`,
|
||||
|
||||
@ -109,6 +109,49 @@ function shouldBehaveLikeSet() {
|
||||
expect(await this.methods.contains(this.valueB)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('clear', function () {
|
||||
it('clears a single value', async function () {
|
||||
await this.methods.add(this.valueA);
|
||||
|
||||
await this.methods.clear();
|
||||
|
||||
expect(await this.methods.contains(this.valueA)).to.be.false;
|
||||
await expectMembersMatch(this.methods, []);
|
||||
});
|
||||
|
||||
it('clears multiple values', async function () {
|
||||
await this.methods.add(this.valueA);
|
||||
await this.methods.add(this.valueB);
|
||||
await this.methods.add(this.valueC);
|
||||
|
||||
await this.methods.clear();
|
||||
|
||||
expect(await this.methods.contains(this.valueA)).to.be.false;
|
||||
expect(await this.methods.contains(this.valueB)).to.be.false;
|
||||
expect(await this.methods.contains(this.valueC)).to.be.false;
|
||||
await expectMembersMatch(this.methods, []);
|
||||
});
|
||||
|
||||
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.add(this.valueC);
|
||||
|
||||
await this.methods.clear();
|
||||
|
||||
await this.methods.add(this.valueA);
|
||||
|
||||
expect(await this.methods.contains(this.valueA)).to.be.true;
|
||||
expect(await this.methods.contains(this.valueB)).to.be.false;
|
||||
expect(await this.methods.contains(this.valueC)).to.be.false;
|
||||
await expectMembersMatch(this.methods, [this.valueA]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@ -30,6 +30,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)`,
|
||||
|
||||
Reference in New Issue
Block a user