From f6991d6933788eb5d2a9831322ae49559f8e32b2 Mon Sep 17 00:00:00 2001 From: Eric Mastro Date: Fri, 18 Nov 2022 18:46:03 +1100 Subject: [PATCH] add Bytes32AddressSetMap for active requests by host Add `Bytes32AddressSetMap` which maps addresses to a requestId. This is used in `Marketplace.activeRequestsForHost`, where all addresses for a particular requestId are listed. This can then be used to iterate and list out the actives requests for a particular address in a view function only. This allows for all addresses for a request to be cleared in situations such as when a request fails or is cancelled. --- contracts/Marketplace.sol | 69 +++++++++--- contracts/libs/SetMap.sol | 217 ++++++++++++++++++++++++-------------- 2 files changed, 193 insertions(+), 93 deletions(-) diff --git a/contracts/Marketplace.sol b/contracts/Marketplace.sol index ace74df..04764c8 100644 --- a/contracts/Marketplace.sol +++ b/contracts/Marketplace.sol @@ -13,6 +13,7 @@ contract Marketplace is Collateral, Proofs { using EnumerableSet for EnumerableSet.AddressSet; using SetMap for SetMap.Bytes32SetMap; using SetMap for SetMap.AddressSetMap; + using SetMap for SetMap.Bytes32AddressSetMap; type RequestId is bytes32; type SlotId is bytes32; @@ -22,9 +23,9 @@ contract Marketplace is Collateral, Proofs { mapping(RequestId => Request) private requests; mapping(RequestId => RequestContext) private requestContexts; mapping(SlotId => Slot) private slots; - // mapping(address => EnumerableSet.Bytes32Set) private activeRequests; - SetMap.AddressSetMap private activeRequests; - SetMap.Bytes32SetMap private activeSlots; + SetMap.AddressSetMap private activeRequestsForClients; // purchasing + SetMap.Bytes32AddressSetMap private activeRequestsForHosts; // purchasing + SetMap.Bytes32SetMap private activeSlots; // sales constructor( IERC20 _token, @@ -42,11 +43,24 @@ contract Marketplace is Collateral, Proofs { function myRequests() public view returns (RequestId[] memory) { SetMap.AddressSetMapKey key = _toAddressSetMapKey(msg.sender); - return _toRequestIds(activeRequests.values(key)); + return _toRequestIds(activeRequestsForClients.values(key)); } - function allRequests() public view returns(RequestId[] memory) { - return _toRequestIds(activeRequests.values()); + function requestsForHost(address host) public view returns(RequestId[] memory) { + EnumerableSet.Bytes32Set storage keys = activeRequestsForHosts.keys(); + uint256 keyLength = keys.length(); + RequestId[] memory result = new RequestId[](keyLength); + + uint8 counter = 0; // should be big enough + for (uint8 i = 0; i < keyLength; i++) { + RequestId requestId = RequestId.wrap(keys.at(i)); + SetMap.Bytes32AddressSetMapKey key = _toBytes32AddressSetMapKey(requestId); + if (activeRequestsForHosts.contains(key, host)) { + result[counter] = requestId; + counter++; + } + } + return result; } function mySlots(RequestId requestId) @@ -54,6 +68,13 @@ contract Marketplace is Collateral, Proofs { view returns (SlotId[] memory) { + // There may exist slots that are still "active", but are part of a request + // that is expired but has not been set to the cancelled state yet. In that + // case, return an empty array. + if (_isCancelled(requestId)) { + SlotId[] memory result; + return result; + } bytes32[] memory slotIds = activeSlots.values(_toBytes32SetMapKey(requestId), msg.sender); return _toSlotIds(slotIds); @@ -78,7 +99,7 @@ contract Marketplace is Collateral, Proofs { context.endsAt = block.timestamp + request.ask.duration; _setProofEnd(_toEndId(id), context.endsAt); - activeRequests.add(_toAddressSetMapKey(request.client), + activeRequestsForClients.add(_toAddressSetMapKey(request.client), RequestId.unwrap(id)); _createLock(_toLockId(id), request.expiry); @@ -118,6 +139,8 @@ contract Marketplace is Collateral, Proofs { activeSlots.add(_toBytes32SetMapKey(requestId), slot.host, SlotId.unwrap(slotId)); + activeRequestsForHosts.add(_toBytes32AddressSetMapKey(requestId), + slot.host); emit SlotFilled(requestId, slotIndex, slotId); if (context.slotsFilled == request.ask.slots) { context.state = RequestState.Started; @@ -144,9 +167,14 @@ contract Marketplace is Collateral, Proofs { _unexpectProofs(_toProofId(slotId)); - activeSlots.remove(_toBytes32SetMapKey(requestId), + SetMap.Bytes32SetMapKey requestIdKey = _toBytes32SetMapKey(requestId); + activeSlots.remove(requestIdKey, slot.host, SlotId.unwrap(slotId)); + if (activeSlots.length(requestIdKey, slot.host) == 0) { + activeRequestsForHosts.remove(_toBytes32AddressSetMapKey(requestId), + slot.host); + } slot.host = address(0); slot.requestId = RequestId.wrap(0); context.slotsFilled -= 1; @@ -161,8 +189,9 @@ contract Marketplace is Collateral, Proofs { context.state = RequestState.Failed; _setProofEnd(_toEndId(requestId), block.timestamp - 1); context.endsAt = block.timestamp - 1; - activeRequests.remove(_toAddressSetMapKey(request.client), - RequestId.unwrap(requestId)); + activeRequestsForClients.remove(_toAddressSetMapKey(request.client), + RequestId.unwrap(requestId)); + activeRequestsForHosts.clear(_toBytes32AddressSetMapKey(requestId)); activeSlots.clear(_toBytes32SetMapKey(requestId)); emit RequestFailed(requestId); @@ -180,14 +209,16 @@ contract Marketplace is Collateral, Proofs { RequestContext storage context = _context(requestId); Request storage request = _request(requestId); context.state = RequestState.Finished; - activeRequests.remove(_toAddressSetMapKey(request.client), - RequestId.unwrap(requestId)); + activeRequestsForClients.remove(_toAddressSetMapKey(request.client), + RequestId.unwrap(requestId)); SlotId slotId = _toSlotId(requestId, slotIndex); Slot storage slot = _slot(slotId); require(!slot.hostPaid, "Already paid"); activeSlots.remove(_toBytes32SetMapKey(requestId), slot.host, SlotId.unwrap(slotId)); + activeRequestsForHosts.remove(_toBytes32AddressSetMapKey(requestId), + slot.host); uint256 amount = pricePerSlot(requests[requestId]); funds.sent += amount; funds.balance -= amount; @@ -208,8 +239,10 @@ contract Marketplace is Collateral, Proofs { // Update request state to Cancelled. Handle in the withdraw transaction // as there needs to be someone to pay for the gas to update the state context.state = RequestState.Cancelled; - activeRequests.remove(_toAddressSetMapKey(request.client), - RequestId.unwrap(requestId)); + activeRequestsForClients.remove(_toAddressSetMapKey(request.client), + RequestId.unwrap(requestId)); + activeRequestsForHosts.clear(_toBytes32AddressSetMapKey(requestId)); + activeSlots.clear(_toBytes32SetMapKey(requestId)); emit RequestCancelled(requestId); // TODO: To be changed once we start paying out hosts for the time they @@ -445,6 +478,14 @@ contract Marketplace is Collateral, Proofs { return SetMap.AddressSetMapKey.wrap(addr); } + function _toBytes32AddressSetMapKey(RequestId requestId) + internal + pure + returns (SetMap.Bytes32AddressSetMapKey) + { + return SetMap.Bytes32AddressSetMapKey.wrap(RequestId.unwrap(requestId)); + } + function _notEqual(RequestId a, uint256 b) internal pure returns (bool) { return RequestId.unwrap(a) != bytes32(b); } diff --git a/contracts/libs/SetMap.sol b/contracts/libs/SetMap.sol index a7a1484..c4b0d0d 100644 --- a/contracts/libs/SetMap.sol +++ b/contracts/libs/SetMap.sol @@ -11,11 +11,10 @@ library SetMap { struct Bytes32SetMap { mapping(Bytes32SetMapKey => - mapping(address => - mapping(uint8 => - EnumerableSet.Bytes32Set))) _values; + mapping(address => + mapping(uint8 => + EnumerableSet.Bytes32Set))) _values; mapping(Bytes32SetMapKey => uint8) _index; - EnumerableSet.Bytes32Set _keys; } /// @notice Returns the EnumerableSet.Bytes32 containing the values for a key @@ -52,28 +51,6 @@ library SetMap { return _set(map, key, addr).values(); } - function _toKeys(bytes32[] memory array) - private - pure - returns (Bytes32SetMapKey[] memory result) - { - // solhint-disable-next-line no-inline-assembly - assembly { - result := array - } - } - - /// @notice Lists all keys for an Bytes32SetMap - /// @param map Bytes32SetMap to list keys - /// @return bytes32[] array of bytes32 values - function keys(Bytes32SetMap storage map) - internal - view - returns (Bytes32SetMapKey[] memory) - { - return _toKeys(map._keys.values()); - } - /// @notice Adds a single value to an Bytes32SetMap /// @param map Bytes32SetMap to add the value to /// @param key key of the value to be added @@ -88,7 +65,6 @@ library SetMap { internal returns (bool) { - map._keys.add(Bytes32SetMapKey.unwrap(key)); return _set(map, key, addr).add(value); } @@ -106,12 +82,7 @@ library SetMap { internal returns (bool) { - EnumerableSet.Bytes32Set storage set = _set(map, key, addr); - bool success = set.remove(value); - if (success && set.length() == 0) { - map._keys.remove(Bytes32SetMapKey.unwrap(key)); - } - return success; + return _set(map, key, addr).remove(value); } /// @notice Clears values for a key. @@ -126,6 +97,20 @@ library SetMap { map._index[key]++; } + /// @notice Returns the length of values for a key and address. + /// @param map Bytes32SetMap for which to get length of values + /// @param key key for which to get the length of values + /// @param addr address for which to get the length of values + function length(Bytes32SetMap storage map, + Bytes32SetMapKey key, + address addr) + internal + view + returns (uint256) + { + return _set(map, key, addr).length(); + } + type AddressSetMapKey is address; struct AddressSetMap { @@ -133,11 +118,9 @@ library SetMap { mapping(uint8 => EnumerableSet.Bytes32Set)) _values; mapping(AddressSetMapKey => uint8) _index; - EnumerableSet.AddressSet _keys; - EnumerableSet.Bytes32Set _allValues; } - /// @notice Returns the EnumerableSet.AddressSet containing the values for a + /// @notice Returns the EnumerableSet.Bytes32Set containing the values for a /// key in an AddressSetMap. /// @dev This is used internally to the library only. `.values()` should only /// be called on its return value in a view/pure function. @@ -154,18 +137,6 @@ library SetMap { return map._values[key][id]; } - /// @notice Lists all values contained in an AddressSetMap, regardless of - /// the key. - /// @param map AddressSetMap to list values - /// @return bytes32[] array of bytes32 values - function values(AddressSetMap storage map) - internal - view - returns (bytes32[] memory) - { - return map._allValues.values(); - } - /// @notice Lists all values for a key in an AddressSetMap /// @param map AddressSetMap to list values /// @param key key of the values to be listed @@ -178,28 +149,6 @@ library SetMap { return _set(map, key).values(); } - function _toAddressSetMapKeys(address[] memory array) - private - pure - returns (AddressSetMapKey[] memory result) - { - // solhint-disable-next-line no-inline-assembly - assembly { - result := array - } - } - - /// @notice Lists all keys for an Bytes32SetMap. - /// @param map AddressSetMap to list keys. - /// @return bytes32[] array of bytes32 values. - function keys(AddressSetMap storage map) - internal - view - returns (AddressSetMapKey[] memory) - { - return _toAddressSetMapKeys(map._keys.values()); - } - /// @notice Adds a single value to an AddressSetMap /// @param map AddressSetMap to add the value to. /// @param key key of the value to be added. @@ -212,8 +161,6 @@ library SetMap { internal returns (bool) { - map._keys.add(AddressSetMapKey.unwrap(key)); - map._allValues.add(value); return _set(map, key).add(value); } @@ -229,13 +176,7 @@ library SetMap { internal returns (bool) { - EnumerableSet.Bytes32Set storage set = _set(map, key); - bool success = set.remove(value); - if (success && set.length() == 0) { - map._keys.remove(AddressSetMapKey.unwrap(key)); - } - map._allValues.remove(value); - return success; + return _set(map, key).remove(value); } /// @notice Clears values for a key. @@ -248,4 +189,122 @@ library SetMap { { map._index[key]++; } + + type Bytes32AddressSetMapKey is bytes32; + + struct Bytes32AddressSetMap { + mapping(Bytes32AddressSetMapKey => + mapping(uint8 => + EnumerableSet.AddressSet)) _values; + mapping(Bytes32AddressSetMapKey => uint8) _index; + EnumerableSet.Bytes32Set _keys; + } + + /// @notice Returns the EnumerableSet.AddressSet containing the values for a + /// key in an Bytes32AddressSetMap. + /// @dev This is used internally to the library only. `.values()` should only + /// be called on its return value in a view/pure function. + /// @param map Bytes32AddressSetMap containing the set to be retrieved. + /// @param key key of the set to be retrieved. + /// @return bytes32[] array of bytes32 values. + function _set(Bytes32AddressSetMap storage map, + Bytes32AddressSetMapKey key) + private + view + returns (EnumerableSet.AddressSet storage) + { + uint8 id = map._index[key]; + return map._values[key][id]; + } + + /// @notice Lists all keys for an Bytes32AddressSetMap + /// @param map Bytes32AddressSetMap to list keys + /// @return bytes32[] array of bytes32 values + function keys(Bytes32AddressSetMap storage map) + internal + view + returns (EnumerableSet.Bytes32Set storage) + { + return map._keys; + } + + /// @notice Lists all values for a key in an Bytes32AddressSetMap + /// @param map Bytes32AddressSetMap to list values + /// @param key key of the values to be listed + /// @return bytes32[] array of bytes32 values + function values(Bytes32AddressSetMap storage map, + Bytes32AddressSetMapKey key) + internal + view + returns (address[] memory) + { + return _set(map, key).values(); + } + + /// @notice Lists all values for a key in an Bytes32AddressSetMap + /// @param map Bytes32AddressSetMap to list values + /// @param key key of the values to be listed + /// @return bytes32[] array of bytes32 values + function contains(Bytes32AddressSetMap storage map, + Bytes32AddressSetMapKey key, + address addr) + internal + view + returns (bool) + { + return _set(map, key).contains(addr); + } + + /// @notice Returns the length of values for a key. + /// @param map Bytes32AddressSetMap for which to get length + /// @param key key for which to get the length of values + function length(Bytes32AddressSetMap storage map, + Bytes32AddressSetMapKey key) + internal + view + returns (uint256) + { + return _set(map, key).length(); + } + + /// @notice Adds a single value to an Bytes32AddressSetMap + /// @param map Bytes32AddressSetMap to add the value to. + /// @param key key of the value to be added. + /// @param value the value to be added. + /// @return true if the value was added to the set, that is if it was not + /// already present. + function add(Bytes32AddressSetMap storage map, + Bytes32AddressSetMapKey key, + address value) + internal + returns (bool) + { + return _set(map, key).add(value); + } + + /// @notice Removes a single value from an Bytes32AddressSetMap + /// @param map Bytes32AddressSetMap to remove the value from + /// @param key key of the value to be removed + /// @param value the value to be removed + /// @return true if the value was removed from the set, that is if it was + /// present. + function remove(Bytes32AddressSetMap storage map, + Bytes32AddressSetMapKey key, + address value) + internal + returns (bool) + { + return _set(map, key).remove(value); + } + + /// @notice Clears values for a key. + /// @dev Updates an index such that the next time values for that key are + /// retrieved, it will reference a new EnumerableSet. + /// @param map Bytes32AddressSetMap for which to clear values + /// @param key key for which to clear values + function clear(Bytes32AddressSetMap storage map, Bytes32AddressSetMapKey key) + internal + { + map._index[key]++; + } } \ No newline at end of file