Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Utils Struct EnumerableMap Bytes32ToUintMap #3416

Merged
merged 13 commits into from May 23, 2022
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -8,6 +8,7 @@
* `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))
* `EnumerableMap`: add new `Bytes32ToUintMap` map type. ([#3416](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3416))
* `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)
Expand Down
43 changes: 43 additions & 0 deletions contracts/mocks/EnumerableMapMock.sol
Expand Up @@ -174,3 +174,46 @@ contract UintToUintMapMock {
return _map.get(key, errorMessage);
}
}

// Bytes32ToUintMap
contract Bytes32ToUintMapMock {
using EnumerableMap for EnumerableMap.Bytes32ToUintMap;

event OperationResult(bool result);

EnumerableMap.Bytes32ToUintMap private _map;

function contains(bytes32 key) public view returns (bool) {
return _map.contains(key);
}

function set(bytes32 key, uint256 value) public {
bool result = _map.set(key, value);
emit OperationResult(result);
}

function remove(bytes32 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 (bytes32 key, uint256 value) {
return _map.at(index);
}

function tryGet(bytes32 key) public view returns (bool, uint256) {
return _map.tryGet(key);
}

function get(bytes32 key) public view returns (uint256) {
return _map.get(key);
}

function getWithMessage(bytes32 key, string calldata errorMessage) public view returns (uint256) {
return _map.get(key, errorMessage);
}
}
93 changes: 93 additions & 0 deletions contracts/utils/structs/EnumerableMap.sol
Expand Up @@ -32,6 +32,7 @@ import "./EnumerableSet.sol";
* - `address -> uint256` (`AddressToUintMap`) since v4.6.0
* - `bytes32 -> bytes32` (`Bytes32ToBytes32`) since v4.6.0
* - `uint256 -> uint256` (`UintToUintMap`) since v4.7.0
* - `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.0
*
* [WARNING]
* ====
Expand Down Expand Up @@ -433,4 +434,96 @@ library EnumerableMap {
) internal view returns (uint256) {
return uint256(get(map._inner, bytes32(uint256(uint160(key))), errorMessage));
}

// Bytes32ToUintMap

struct Bytes32ToUintMap {
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(
Bytes32ToUintMap storage map,
bytes32 key,
uint256 value
) internal returns (bool) {
return set(map._inner, 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(Bytes32ToUintMap storage map, bytes32 key) internal returns (bool) {
return remove(map._inner, key);
}

/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool) {
return contains(map._inner, key);
}

/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(Bytes32ToUintMap 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(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32, uint256) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (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(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, key);
return (success, uint256(value));
}

/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(Bytes32ToUintMap storage map, bytes32 key) internal view returns (uint256) {
return uint256(get(map._inner, 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(
Bytes32ToUintMap storage map,
bytes32 key,
string memory errorMessage
) internal view returns (uint256) {
return uint256(get(map._inner, key, errorMessage));
}
}
26 changes: 20 additions & 6 deletions test/utils/structs/EnumerableMap.test.js
Expand Up @@ -4,6 +4,7 @@ const AddressToUintMapMock = artifacts.require('AddressToUintMapMock');
const UintToAddressMapMock = artifacts.require('UintToAddressMapMock');
const Bytes32ToBytes32MapMock = artifacts.require('Bytes32ToBytes32MapMock');
const UintToUintMapMock = artifacts.require('UintToUintMapMock');
const Bytes32ToUintMapMock = artifacts.require('Bytes32ToUintMapMock');

const { shouldBehaveLikeMap } = require('./EnumerableMap.behavior');

Expand All @@ -25,8 +26,8 @@ contract('EnumerableMap', function (accounts) {
});

shouldBehaveLikeMap(
[accountA, accountB, accountC],
[keyA, keyB, keyC],
[ accountA, accountB, accountC ],
[ keyA, keyB, keyC ],
new BN('0'),
);
});
Expand All @@ -38,8 +39,8 @@ contract('EnumerableMap', function (accounts) {
});

shouldBehaveLikeMap(
[keyA, keyB, keyC],
[accountA, accountB, accountC],
[ keyA, keyB, keyC ],
[ accountA, accountB, accountC ],
constants.ZERO_ADDRESS,
);
});
Expand All @@ -51,8 +52,8 @@ contract('EnumerableMap', function (accounts) {
});

shouldBehaveLikeMap(
[keyA, keyB, keyC].map(k => ('0x' + k.toString(16)).padEnd(66, '0')),
[bytesA, bytesB, bytesC],
[ keyA, keyB, keyC ].map(k => '0x' + k.toString(16).padEnd(64, '0')),
[ bytesA, bytesB, bytesC ],
constants.ZERO_BYTES32,
);
});
Expand All @@ -69,4 +70,17 @@ contract('EnumerableMap', function (accounts) {
new BN('0'),
);
});

// Bytes32ToUintMap
describe('Bytes32ToUintMap', function () {
beforeEach(async function () {
this.map = await Bytes32ToUintMapMock.new();
});

shouldBehaveLikeMap(
[ bytesA, bytesB, bytesC ],
[ keyA, keyB, keyC ],
new BN('0'),
);
});
});