[marketplace] add isFinished

Add function `isFinished` that checks whethere the state has been set to `RequestState.Finished` (which is not being set yet) or if the contract was started (`RequestState.Started`) and the contract duration has lapsed.

To facilitate `isFinished`, the contract start time (`startedAt`) was added to calculate the contract end time.
This commit is contained in:
Eric Mastro 2022-09-21 19:38:42 +10:00 committed by Eric Mastro
parent 2170d6bd19
commit 1fff2f7295
4 changed files with 74 additions and 12 deletions

View File

@ -104,6 +104,7 @@ contract Marketplace is Collateral, Proofs {
emit SlotFilled(requestId, slotIndex, slotId);
if (context.slotsFilled == request.ask.slots) {
context.state = RequestState.Started;
context.startedAt = block.timestamp;
_extendLockExpiry(requestId, block.timestamp + request.ask.duration);
emit RequestFulfilled(requestId);
}
@ -146,10 +147,9 @@ contract Marketplace is Collateral, Proofs {
public
marketplaceInvariant
{
require(_isFinished(requestId), "Contract not ended");
bytes32 slotId = keccak256(abi.encode(requestId, slotIndex));
require(block.timestamp > proofEnd(slotId), "Contract not ended");
Slot storage slot = slots[slotId];
require(slot.host != address(0), "Slot empty");
Slot storage slot = _slot(slotId);
require(!slot.hostPaid, "Already paid");
uint256 amount = pricePerSlot(requests[requestId]);
funds.sent += amount;
@ -196,6 +196,21 @@ contract Marketplace is Collateral, Proofs {
);
}
/// @notice Return true if the request state is RequestState.Finished or if the request duration has elapsed and the request was started.
/// @dev Handles the case when a request may have been finished, but the state has not yet been updated by a transaction.
/// @param requestId the id of the request
/// @return true if request is finished
function _isFinished(bytes32 requestId) internal view returns (bool) {
RequestContext memory context = requestContexts[requestId];
Request memory request = _request(requestId);
return
context.state == RequestState.Finished ||
(
context.state == RequestState.Started &&
block.timestamp > context.startedAt + request.ask.duration
);
}
/// @notice Return id of request that slot belongs to
/// @dev Returns requestId that is mapped to the slotId
/// @param slotId id of the slot
@ -342,6 +357,7 @@ contract Marketplace is Collateral, Proofs {
struct RequestContext {
uint256 slotsFilled;
RequestState state;
uint256 startedAt;
}
struct Slot {

View File

@ -34,7 +34,7 @@ contract Proofs {
return timeout;
}
function _end(bytes32 id) internal view returns (uint256) {
function _end(bytes32 id) internal view returns (uint256) {
return ends[id];
}

View File

@ -324,7 +324,12 @@ describe("Marketplace", function () {
}
it("pays the host", async function () {
await marketplace.fillSlot(slot.request, slot.index, proof)
await waitUntilAllSlotsFilled(
marketplace,
request.ask.slots,
slot.request,
proof
)
await waitUntilEnd()
const startBalance = await token.balanceOf(host.address)
await marketplace.payoutSlot(slot.request, slot.index)
@ -332,10 +337,10 @@ describe("Marketplace", function () {
expect(endBalance - startBalance).to.equal(pricePerSlot(request))
})
it("is only allowed when the slot is filled", async function () {
it("is only allowed when the contract is finished", async function () {
await expect(
marketplace.payoutSlot(slot.request, slot.index)
).to.be.revertedWith("Slot empty")
).to.be.revertedWith("Contract not ended")
})
it("is only allowed when the contract has ended", async function () {
@ -346,7 +351,12 @@ describe("Marketplace", function () {
})
it("can only be done once", async function () {
await marketplace.fillSlot(slot.request, slot.index, proof)
await waitUntilAllSlotsFilled(
marketplace,
request.ask.slots,
slot.request,
proof
)
await waitUntilEnd()
await marketplace.payoutSlot(slot.request, slot.index)
await expect(
@ -355,7 +365,12 @@ describe("Marketplace", function () {
})
it("cannot be filled again", async function () {
await marketplace.fillSlot(slot.request, slot.index, proof)
await waitUntilAllSlotsFilled(
marketplace,
request.ask.slots,
slot.request,
proof
)
await waitUntilEnd()
await marketplace.payoutSlot(slot.request, slot.index)
await expect(marketplace.fillSlot(slot.request, slot.index, proof)).to.be

View File

@ -193,8 +193,7 @@ describe("Storage", function () {
await advanceTimeTo(request.expiry + 1)
await expect(await storage.willProofBeRequired(id)).to.be.false
})
it("does not require proofs once cancelled", async function () {
const id = slotId(slot)
@ -286,8 +285,40 @@ describe("Storage", function () {
await markProofAsMissing(id, onMarkAsMissing)
await expect(storage.getSlot(id)).to.be.revertedWith("Slot empty")
})
})
})
describe("contract state", function () {
it("isCancelled is true once request is cancelled", async function () {
await expect(await storage.isCancelled(slot.request)).to.equal(false)
await waitUntilExpired(request.expiry)
await expect(await storage.isCancelled(slot.request)).to.equal(true)
})
it("isSlotCancelled fails when slot is empty", async function () {
await expect(storage.isSlotCancelled(slotId(slot))).to.be.revertedWith(
"Slot empty"
)
})
it("isSlotCancelled is true once request is cancelled", async function () {
await storage.fillSlot(slot.request, slot.index, proof)
await waitUntilExpired(request.expiry)
await expect(await storage.isSlotCancelled(slotId(slot))).to.equal(true)
})
it("isFinished is true once started and contract duration lapses", async function () {
await expect(await storage.isFinished(slot.request)).to.be.false
// fill all slots, should change state to RequestState.Started
await waitUntilAllSlotsFilled(
storage,
request.ask.slots,
slot.request,
proof
)
await expect(await storage.isFinished(slot.request)).to.be.false
advanceTime(request.ask.duration + 1)
await expect(await storage.isFinished(slot.request)).to.be.true
})
})
})
// TODO: implement checking of actual proofs of storage, instead of dummy bool