[marketplace] replace mySlots(requestId) by mySlots()

Allows a host to get all slots that it participates in
in a single call.
This commit is contained in:
Mark Spanbroek 2022-11-23 12:25:47 +01:00 committed by markspanbroek
parent 7f59e545b2
commit c0a1e11b87
3 changed files with 72 additions and 131 deletions

View File

@ -12,6 +12,7 @@ import "./libs/Utils.sol";
contract Marketplace is Collateral, Proofs { contract Marketplace is Collateral, Proofs {
using EnumerableSet for EnumerableSet.Bytes32Set; using EnumerableSet for EnumerableSet.Bytes32Set;
using EnumerableSet for EnumerableSet.AddressSet; using EnumerableSet for EnumerableSet.AddressSet;
using Utils for EnumerableSet.Bytes32Set;
using SetMap for SetMap.Bytes32SetMap; using SetMap for SetMap.Bytes32SetMap;
using SetMap for SetMap.AddressBytes32SetMap; using SetMap for SetMap.AddressBytes32SetMap;
using SetMap for SetMap.Bytes32AddressSetMap; using SetMap for SetMap.Bytes32AddressSetMap;
@ -25,8 +26,8 @@ contract Marketplace is Collateral, Proofs {
mapping(RequestId => RequestContext) private requestContexts; mapping(RequestId => RequestContext) private requestContexts;
mapping(SlotId => Slot) private slots; mapping(SlotId => Slot) private slots;
SetMap.AddressBytes32SetMap private activeRequestsForClients; // purchasing SetMap.AddressBytes32SetMap private activeRequestsForClients; // purchasing
SetMap.Bytes32AddressSetMap private activeRequestsForHosts; // purchasing mapping(address => EnumerableSet.Bytes32Set) private slotsPerHost; // sales
SetMap.Bytes32SetMap private activeSlots; // sales mapping(SlotId => RequestId) private requestForSlot;
constructor( constructor(
IERC20 _token, IERC20 _token,
@ -44,7 +45,9 @@ contract Marketplace is Collateral, Proofs {
function myRequests() public view returns (RequestId[] memory) { function myRequests() public view returns (RequestId[] memory) {
SetMap.AddressBytes32SetMapKey key = _toAddressSetMapKey(msg.sender); SetMap.AddressBytes32SetMapKey key = _toAddressSetMapKey(msg.sender);
RequestId[] memory requestIds = _toRequestIds(activeRequestsForClients.values(key)); RequestId[] memory requestIds = _toRequestIds(
activeRequestsForClients.values(key)
);
RequestId[] memory result = new RequestId[](requestIds.length); RequestId[] memory result = new RequestId[](requestIds.length);
uint8 counter = 0; uint8 counter = 0;
for (uint8 i = 0; i < requestIds.length; i++) { for (uint8 i = 0; i < requestIds.length; i++) {
@ -56,40 +59,16 @@ contract Marketplace is Collateral, Proofs {
return _toRequestIds(Utils._resize(_toBytes32s(result), counter)); return _toRequestIds(Utils._resize(_toBytes32s(result), counter));
} }
function requestsForHost(address host) public view returns(RequestId[] memory) { function isActive(bytes32 slot) private view returns (bool) {
EnumerableSet.Bytes32Set storage keys = activeRequestsForHosts.keys(); RequestState s = state(requestForSlot[SlotId.wrap(slot)]);
uint256 keyLength = keys.length(); return
RequestId[] memory result = new RequestId[](keyLength); s == RequestState.New ||
s == RequestState.Started ||
uint8 counter = 0; // should be big enough s == RequestState.Finished;
for (uint8 i = 0; i < keyLength; i++) {
RequestId requestId = RequestId.wrap(keys.at(i));
SetMap.Bytes32AddressSetMapKey key = _toBytes32AddressSetMapKey(requestId);
if (activeRequestsForHosts.contains(key, host) &&
!_isCancelled(requestId))
{
result[counter] = requestId;
counter++;
}
}
return _toRequestIds(Utils._resize(_toBytes32s(result), counter));
} }
function mySlots(RequestId requestId) function mySlots() public view returns (SlotId[] memory) {
public return _toSlotIds(slotsPerHost[msg.sender].filter(isActive));
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);
} }
function _equals(RequestId a, RequestId b) internal pure returns (bool) { function _equals(RequestId a, RequestId b) internal pure returns (bool) {
@ -111,8 +90,10 @@ contract Marketplace is Collateral, Proofs {
context.endsAt = block.timestamp + request.ask.duration; context.endsAt = block.timestamp + request.ask.duration;
_setProofEnd(_toEndId(id), context.endsAt); _setProofEnd(_toEndId(id), context.endsAt);
activeRequestsForClients.add(_toAddressSetMapKey(request.client), activeRequestsForClients.add(
RequestId.unwrap(id)); _toAddressSetMapKey(request.client),
RequestId.unwrap(id)
);
_createLock(_toLockId(id), request.expiry); _createLock(_toLockId(id), request.expiry);
@ -148,11 +129,10 @@ contract Marketplace is Collateral, Proofs {
slot.requestId = requestId; slot.requestId = requestId;
RequestContext storage context = _context(requestId); RequestContext storage context = _context(requestId);
context.slotsFilled += 1; context.slotsFilled += 1;
activeSlots.add(_toBytes32SetMapKey(requestId),
slot.host, slotsPerHost[slot.host].add(SlotId.unwrap(slotId));
SlotId.unwrap(slotId)); requestForSlot[slotId] = requestId;
activeRequestsForHosts.add(_toBytes32AddressSetMapKey(requestId),
slot.host);
emit SlotFilled(requestId, slotIndex, slotId); emit SlotFilled(requestId, slotIndex, slotId);
if (context.slotsFilled == request.ask.slots) { if (context.slotsFilled == request.ask.slots) {
context.state = RequestState.Started; context.state = RequestState.Started;
@ -166,7 +146,7 @@ contract Marketplace is Collateral, Proofs {
internal internal
slotMustAcceptProofs(slotId) slotMustAcceptProofs(slotId)
marketplaceInvariant marketplaceInvariant
// TODO: restrict senders that can call this function // TODO: restrict senders that can call this function
{ {
Slot storage slot = _slot(slotId); Slot storage slot = _slot(slotId);
RequestId requestId = slot.requestId; RequestId requestId = slot.requestId;
@ -179,14 +159,8 @@ contract Marketplace is Collateral, Proofs {
_unexpectProofs(_toProofId(slotId)); _unexpectProofs(_toProofId(slotId));
SetMap.Bytes32SetMapKey requestIdKey = _toBytes32SetMapKey(requestId); slotsPerHost[slot.host].remove(SlotId.unwrap(slotId));
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.host = address(0);
slot.requestId = RequestId.wrap(0); slot.requestId = RequestId.wrap(0);
context.slotsFilled -= 1; context.slotsFilled -= 1;
@ -201,10 +175,10 @@ contract Marketplace is Collateral, Proofs {
context.state = RequestState.Failed; context.state = RequestState.Failed;
_setProofEnd(_toEndId(requestId), block.timestamp - 1); _setProofEnd(_toEndId(requestId), block.timestamp - 1);
context.endsAt = block.timestamp - 1; context.endsAt = block.timestamp - 1;
activeRequestsForClients.remove(_toAddressSetMapKey(request.client), activeRequestsForClients.remove(
RequestId.unwrap(requestId)); _toAddressSetMapKey(request.client),
activeRequestsForHosts.clear(_toBytes32AddressSetMapKey(requestId)); RequestId.unwrap(requestId)
activeSlots.clear(_toBytes32SetMapKey(requestId)); );
emit RequestFailed(requestId); emit RequestFailed(requestId);
// TODO: burn all remaining slot collateral (note: slot collateral not // TODO: burn all remaining slot collateral (note: slot collateral not
@ -221,16 +195,16 @@ contract Marketplace is Collateral, Proofs {
RequestContext storage context = _context(requestId); RequestContext storage context = _context(requestId);
Request storage request = _request(requestId); Request storage request = _request(requestId);
context.state = RequestState.Finished; context.state = RequestState.Finished;
activeRequestsForClients.remove(_toAddressSetMapKey(request.client), activeRequestsForClients.remove(
RequestId.unwrap(requestId)); _toAddressSetMapKey(request.client),
RequestId.unwrap(requestId)
);
SlotId slotId = _toSlotId(requestId, slotIndex); SlotId slotId = _toSlotId(requestId, slotIndex);
Slot storage slot = _slot(slotId); Slot storage slot = _slot(slotId);
require(!slot.hostPaid, "Already paid"); require(!slot.hostPaid, "Already paid");
activeSlots.remove(_toBytes32SetMapKey(requestId),
slot.host, slotsPerHost[slot.host].remove(SlotId.unwrap(slotId));
SlotId.unwrap(slotId));
activeRequestsForHosts.remove(_toBytes32AddressSetMapKey(requestId),
slot.host);
uint256 amount = pricePerSlot(requests[requestId]); uint256 amount = pricePerSlot(requests[requestId]);
funds.sent += amount; funds.sent += amount;
funds.balance -= amount; funds.balance -= amount;
@ -251,10 +225,11 @@ contract Marketplace is Collateral, Proofs {
// Update request state to Cancelled. Handle in the withdraw transaction // 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 // as there needs to be someone to pay for the gas to update the state
context.state = RequestState.Cancelled; context.state = RequestState.Cancelled;
activeRequestsForClients.remove(_toAddressSetMapKey(request.client), activeRequestsForClients.remove(
RequestId.unwrap(requestId)); _toAddressSetMapKey(request.client),
activeRequestsForHosts.clear(_toBytes32AddressSetMapKey(requestId)); RequestId.unwrap(requestId)
activeSlots.clear(_toBytes32SetMapKey(requestId)); );
emit RequestCancelled(requestId); emit RequestCancelled(requestId);
// TODO: To be changed once we start paying out hosts for the time they // TODO: To be changed once we start paying out hosts for the time they

View File

@ -1,7 +1,11 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.8; pragma solidity ^0.8.8;
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
library Utils { library Utils {
using EnumerableSet for EnumerableSet.Bytes32Set;
function _resize(bytes32[] memory array, uint8 newSize) function _resize(bytes32[] memory array, uint8 newSize)
internal internal
pure pure
@ -20,4 +24,23 @@ library Utils {
return sized; return sized;
} }
} }
function filter(
EnumerableSet.Bytes32Set storage set,
function(bytes32) internal view returns (bool) include
) internal view returns (bytes32[] memory result) {
bytes32[] memory selected = new bytes32[](set.length());
uint256 amount = 0;
for (uint256 i = 0; i < set.length(); i++) {
if (include(set.at(i))) {
selected[amount++] = set.at(i);
}
}
result = new bytes32[](amount);
for (uint256 i = 0; i < result.length; i++) {
result[i] = selected[i];
}
}
} }

View File

@ -738,56 +738,6 @@ describe("Marketplace", function () {
}) })
}) })
describe("list of active requests for host", function () {
beforeEach(async function () {
switchAccount(client)
await token.approve(marketplace.address, price(request))
await marketplace.requestStorage(request)
switchAccount(host)
await token.approve(marketplace.address, collateral)
await marketplace.deposit(collateral)
})
it("is empty when no slot is filled", async function () {
expect(await marketplace.requestsForHost(host.address)).to.deep.equal([])
})
it("adds request to list when filling slot", async function () {
await marketplace.fillSlot(slot.request, slot.index, proof)
expect(await marketplace.requestsForHost(host.address)).to.deep.equal([
slot.request,
])
})
it("removes request from list when cancelled", async function () {
await marketplace.fillSlot(slot.request, slot.index, proof)
await waitUntilCancelled(request)
expect(await marketplace.requestsForHost(host.address)).to.deep.equal([])
})
it("removes request from list when funds are withdrawn", async function () {
await marketplace.fillSlot(slot.request, slot.index, proof)
await waitUntilCancelled(request)
switchAccount(client)
await marketplace.withdrawFunds(slot.request)
expect(await marketplace.requestsForHost(host.address)).to.deep.equal([])
})
it("removes request from list when request fails", async function () {
await waitUntilStarted(marketplace, request, proof)
await waitUntilFailed(marketplace, request, slot)
expect(await marketplace.requestsForHost(host.address)).to.deep.equal([])
})
it("removes request from list when request finishes", async function () {
switchAccount(host)
await waitUntilStarted(marketplace, request, proof)
await waitUntilFinished(marketplace, requestId(request))
await marketplace.payoutSlot(slot.request, slot.index)
expect(await marketplace.requestsForHost(host.address)).to.deep.equal([])
})
})
describe("list of active slots", function () { describe("list of active slots", function () {
beforeEach(async function () { beforeEach(async function () {
switchAccount(client) switchAccount(client)
@ -802,7 +752,7 @@ describe("Marketplace", function () {
await marketplace.fillSlot(slot.request, slot.index, proof) await marketplace.fillSlot(slot.request, slot.index, proof)
let slot1 = { ...slot, index: slot.index + 1 } let slot1 = { ...slot, index: slot.index + 1 }
await marketplace.fillSlot(slot.request, slot1.index, proof) await marketplace.fillSlot(slot.request, slot1.index, proof)
expect(await marketplace.mySlots(slot.request)).to.deep.equal([ expect(await marketplace.mySlots()).to.have.members([
slotId(slot), slotId(slot),
slotId(slot1), slotId(slot1),
]) ])
@ -813,9 +763,7 @@ describe("Marketplace", function () {
let slot1 = { ...slot, index: slot.index + 1 } let slot1 = { ...slot, index: slot.index + 1 }
await marketplace.fillSlot(slot.request, slot1.index, proof) await marketplace.fillSlot(slot.request, slot1.index, proof)
await marketplace.freeSlot(slotId(slot)) await marketplace.freeSlot(slotId(slot))
expect(await marketplace.mySlots(slot.request)).to.deep.equal([ expect(await marketplace.mySlots()).to.have.members([slotId(slot1)])
slotId(slot1),
])
}) })
it("returns no slots when cancelled", async function () { it("returns no slots when cancelled", async function () {
@ -823,7 +771,7 @@ describe("Marketplace", function () {
let slot1 = { ...slot, index: slot.index + 1 } let slot1 = { ...slot, index: slot.index + 1 }
await marketplace.fillSlot(slot.request, slot1.index, proof) await marketplace.fillSlot(slot.request, slot1.index, proof)
await waitUntilCancelled(request) await waitUntilCancelled(request)
expect(await marketplace.mySlots(slot.request)).to.deep.equal([]) expect(await marketplace.mySlots()).to.have.members([])
}) })
it("removes active slots for all hosts in a request when it fails", async function () { it("removes active slots for all hosts in a request when it fails", async function () {
@ -843,9 +791,9 @@ describe("Marketplace", function () {
} }
await waitUntilFailed(marketplace, request, slot) await waitUntilFailed(marketplace, request, slot)
expect(await marketplace.mySlots(slot.request)).to.deep.equal([]) expect(await marketplace.mySlots()).to.have.members([])
switchAccount(host) switchAccount(host)
expect(await marketplace.mySlots(slot.request)).to.deep.equal([]) expect(await marketplace.mySlots()).to.have.members([])
}) })
it("doesn't remove active slots for hosts in request that didn't fail", async function () { it("doesn't remove active slots for hosts in request that didn't fail", async function () {
@ -872,19 +820,14 @@ describe("Marketplace", function () {
let id = slotId(expectedSlot) let id = slotId(expectedSlot)
expected.push(id) expected.push(id)
} }
expect(await marketplace.mySlots(slot.request)).to.deep.equal([]) expect(await marketplace.mySlots()).to.have.members(expected)
expect(await marketplace.mySlots(expectedSlot.request)).to.deep.equal(
expected
)
}) })
it("removes slots from list when request finishes", async function () { it("removes slot from list when slot is paid out", async function () {
await waitUntilStarted(marketplace, request, proof) await waitUntilStarted(marketplace, request, proof)
await waitUntilFinished(marketplace, requestId(request)) await waitUntilFinished(marketplace, requestId(request))
await marketplace.payoutSlot(slot.request, slot.index) await marketplace.payoutSlot(slot.request, slot.index)
expect(await marketplace.mySlots(slot.request)).to.not.contain( expect(await marketplace.mySlots()).to.not.contain(slotId(slot))
slotId(slot)
)
}) })
}) })
}) })