diff --git a/contracts/Marketplace.sol b/contracts/Marketplace.sol index b531784..3787d51 100644 --- a/contracts/Marketplace.sol +++ b/contracts/Marketplace.sol @@ -11,11 +11,13 @@ import "./libs/DAL.sol"; contract Marketplace is Collateral, Proofs { using DAL for DAL.Database; using EnumerableSet for EnumerableSet.Bytes32Set; + using DAL for EnumerableSet.Bytes32Set; uint256 public immutable collateral; MarketplaceFunds private funds; mapping(DAL.RequestId => RequestContext) private requestContexts; DAL.Database private db; + uint16 private constant MAX_SLOTS = 256; constructor( IERC20 _token, @@ -45,6 +47,8 @@ contract Marketplace is Collateral, Proofs { public marketplaceInvariant { + require(request.ask.slots <= MAX_SLOTS, "Max slots exceeded"); + require(request.ask.maxSlotLoss <= MAX_SLOTS, "Max slot loss exceeded"); require(request.client == msg.sender, "Invalid client address"); DAL.RequestId requestId = _toRequestId(request); @@ -152,7 +156,14 @@ contract Marketplace is Collateral, Proofs { DAL.Client storage client = db.select(request.client); db.remove(client.requests, requestId); - db.remove(host.requests, requestId); + + // Remove all request's slots from hosts.slots. This is an expensive + // operation as it iterates all slots and removes them one-by-one. + // An alternative is to use EnumerableExtensions.ClearableBytes32Set + // for host.slots instead, however this would not free any storage + // but instead use a new EnumerableSet.Bytes32Set each time it is cleared. + db.clearSlots(host, requestId, MAX_SLOTS); + emit RequestFailed(requestId); // TODO: burn all remaining slot collateral (note: slot collateral not diff --git a/contracts/libs/DAL.sol b/contracts/libs/DAL.sol index b5ab72a..8cc34a4 100644 --- a/contracts/libs/DAL.sol +++ b/contracts/libs/DAL.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.8; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import "../Marketplace.sol"; import "./Utils.sol"; library DAL { @@ -18,18 +17,18 @@ library DAL { struct Client { ClientId id; // PK - EnumerableSet.Bytes32Set requests; + EnumerableSet.Bytes32Set requests; // FKs } struct Host { HostId id; // PK - EnumerableSet.Bytes32Set slots; - EnumerableSet.Bytes32Set requests; + EnumerableSet.Bytes32Set slots; // FKs + EnumerableSet.Bytes32Set requests; // FKs } struct Request { - RequestId id; + RequestId id; // PK ClientId client; Ask ask; Content content; @@ -40,10 +39,10 @@ library DAL { } struct Slot { - SlotId id; - HostId host; + SlotId id; // PK + HostId host; // FK bool hostPaid; - RequestId requestId; + RequestId requestId; // FK } struct Ask { @@ -246,6 +245,8 @@ library DAL { Client storage client = db.clients[request.client]; bytes32 bRequestId = RequestId.unwrap(request.id); require(!client.requests.contains(bRequestId), "active request refs"); + // TODO: iterate all request's hosts and check that host.requests doesn't + // have requestId delete db.requests[request.id]; } @@ -279,6 +280,8 @@ library DAL { internal { require(exists(db, requestId), "request does not exist"); + // TODO: check that host.slots doesn't have a slot belonging to the request + // being removed requests.remove(RequestId.unwrap(requestId)); } @@ -293,6 +296,26 @@ library DAL { slots.remove(SlotId.unwrap(slotId)); } + // WARNING: this may cause a transaction to run out of gas! + function clearSlots(Database storage db, + Host storage host, + RequestId requestId, + uint256 maxIterations) + internal + { + require(maxIterations > 0, "maxIterations must be > 0"); + require(host.slots.length() <= maxIterations, "iterations out of bounds"); + + for (uint256 i = 0; i < host.slots.length(); i++) { + bytes32 slotId = host.slots.at(i); + Slot storage slot = select(db, SlotId.wrap(slotId)); + if (equals(slot.requestId, requestId)) { + host.slots.remove(slotId); + } + } + remove(db, host.requests, requestId); + } + /// *** CALCULATED PROPERTIES *** ///