From f5a54c7ed4e9d562d984b02169a7e6ab2be973ba Mon Sep 17 00:00:00 2001 From: Eric <5089238+emizzle@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:55:17 +1100 Subject: [PATCH] feat(slot-reservations): require slots to be reserved before filling slot (#185) * Require slots to be reserved before filling slot * Add test that checks filling slot fails without reservation --- contracts/Marketplace.sol | 3 +++ test/Marketplace.test.js | 45 +++++++++++++++++++++++++++++++++++++++ test/marketplace.js | 1 + 3 files changed, 49 insertions(+) diff --git a/contracts/Marketplace.sol b/contracts/Marketplace.sol index 431e364..e62552c 100644 --- a/contracts/Marketplace.sol +++ b/contracts/Marketplace.sol @@ -14,6 +14,7 @@ import "./Groth16.sol"; contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian { using EnumerableSet for EnumerableSet.Bytes32Set; + using EnumerableSet for EnumerableSet.AddressSet; using Requests for Request; IERC20 private immutable _token; @@ -136,6 +137,8 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian { require(slotIndex < request.ask.slots, "Invalid slot"); SlotId slotId = Requests.slotId(requestId, slotIndex); + require(_reservations[slotId].contains(msg.sender), "Reservation required"); + Slot storage slot = _slots[slotId]; slot.requestId = requestId; slot.slotIndex = slotIndex; diff --git a/test/Marketplace.test.js b/test/Marketplace.test.js index 3b4f1a9..32e4e01 100644 --- a/test/Marketplace.test.js +++ b/test/Marketplace.test.js @@ -244,6 +244,7 @@ describe("Marketplace", function () { }) it("emits event when slot is filled", async function () { + await marketplace.reserveSlot(slot.request, slot.index) await expect(marketplace.fillSlot(slot.request, slot.index, proof)) .to.emit(marketplace, "SlotFilled") .withArgs(slot.request, slot.index) @@ -251,6 +252,7 @@ describe("Marketplace", function () { it("allows retrieval of host that filled slot", async function () { expect(await marketplace.getHost(slotId(slot))).to.equal(AddressZero) + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) expect(await marketplace.getHost(slotId(slot))).to.equal(host.address) }) @@ -262,6 +264,7 @@ describe("Marketplace", function () { }) it("allows retrieval of request of a filled slot", async function () { + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) let activeSlot = await marketplace.getActiveSlot(slotId(slot)) expect(activeSlot.request).to.be.request(request) @@ -269,12 +272,14 @@ describe("Marketplace", function () { }) it("is rejected when proof is incorrect", async function () { + await marketplace.reserveSlot(slot.request, slot.index) await expect( marketplace.fillSlot(slot.request, slot.index, invalidProof()) ).to.be.revertedWith("Invalid proof") }) it("is rejected when slot already filled", async function () { + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) await expect( marketplace.fillSlot(slot.request, slot.index, proof) @@ -295,6 +300,7 @@ describe("Marketplace", function () { await marketplace.requestStorage(expired) await waitUntilCancelled(expired) switchAccount(host) + await marketplace.reserveSlot(requestId(expired), slot.index) await expect( marketplace.fillSlot(requestId(expired), slot.index, proof) ).to.be.revertedWith("Slot is not free") @@ -331,12 +337,19 @@ describe("Marketplace", function () { ) await token.approve(marketplace.address, price(request) * lastSlot) for (let i = 0; i <= lastSlot; i++) { + await marketplace.reserveSlot(slot.request, i) await marketplace.fillSlot(slot.request, i, proof) } await expect( marketplace.fillSlot(slot.request, lastSlot, proof) ).to.be.revertedWith("Slot is not free") }) + + it("fails if slot is not reserved first", async function () { + await expect( + marketplace.fillSlot(slot.request, slot.index, proof) + ).to.be.revertedWith("Reservation required") + }) }) describe("filling slot without collateral", function () { @@ -350,6 +363,7 @@ describe("Marketplace", function () { it("is rejected when approved collateral is insufficient", async function () { let insufficient = request.ask.collateral - 1 await token.approve(marketplace.address, insufficient) + await marketplace.reserveSlot(slot.request, slot.index) await expect( marketplace.fillSlot(slot.request, slot.index, proof) ).to.be.revertedWith("ERC20: insufficient allowance") @@ -358,6 +372,7 @@ describe("Marketplace", function () { it("collects only requested collateral and not more", async function () { await token.approve(marketplace.address, request.ask.collateral * 2) const startBalanace = await token.balanceOf(host.address) + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) const endBalance = await token.balanceOf(host.address) expect(startBalanace - endBalance).to.eq(request.ask.collateral) @@ -371,6 +386,7 @@ describe("Marketplace", function () { await marketplace.requestStorage(request) switchAccount(host) await token.approve(marketplace.address, request.ask.collateral) + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) await advanceTimeForNextBlock(config.proofs.period) }) @@ -411,6 +427,7 @@ describe("Marketplace", function () { }) it("sets the request end time to now + duration", async function () { + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) await expect( (await marketplace.requestEnd(requestId(request))).toNumber() @@ -428,6 +445,7 @@ describe("Marketplace", function () { }) it("sets request end time to the past once cancelled", async function () { + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) await waitUntilCancelled(request) await mine() @@ -552,6 +570,7 @@ describe("Marketplace", function () { await marketplace.requestExpiry(requestId(request)) ).toNumber() + await marketplace.reserveSlot(slot.request, slot.index) await advanceTimeToForNextBlock(filledAt) await marketplace.fillSlot(slot.request, slot.index, proof) await waitUntilCancelled(request) @@ -571,6 +590,7 @@ describe("Marketplace", function () { await marketplace.requestExpiry(requestId(request)) ).toNumber() + await marketplace.reserveSlot(slot.request, slot.index) await advanceTimeToForNextBlock(filledAt) await marketplace.fillSlot(slot.request, slot.index, proof) await waitUntilCancelled(request) @@ -608,6 +628,7 @@ describe("Marketplace", function () { }) it("does not pay when the contract hasn't ended", async function () { + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) const startBalanceHost = await token.balanceOf(host.address) const startBalanceReward = await token.balanceOf( @@ -663,10 +684,12 @@ describe("Marketplace", function () { request.ask.collateral * lastSlot ) for (let i = 0; i < lastSlot; i++) { + await marketplace.reserveSlot(slot.request, i) await marketplace.fillSlot(slot.request, i, proof) } await token.approve(marketplace.address, request.ask.collateral) + await marketplace.reserveSlot(slot.request, lastSlot) await expect(marketplace.fillSlot(slot.request, lastSlot, proof)) .to.emit(marketplace, "RequestFulfilled") .withArgs(requestId(request)) @@ -675,6 +698,7 @@ describe("Marketplace", function () { const slots = request.ask.slots await token.approve(marketplace.address, request.ask.collateral * slots) for (let i = 0; i < slots; i++) { + await marketplace.reserveSlot(slot.request, i) await marketplace.fillSlot(slot.request, i, proof) } await expect(await marketplace.requestState(slot.request)).to.equal( @@ -688,6 +712,7 @@ describe("Marketplace", function () { request.ask.collateral * (lastSlot + 1) ) for (let i = 0; i <= lastSlot; i++) { + await marketplace.reserveSlot(slot.request, i) await marketplace.fillSlot(slot.request, i, proof) } await expect( @@ -727,6 +752,7 @@ describe("Marketplace", function () { request.ask.collateral * (lastSlot + 1) ) for (let i = 0; i <= lastSlot; i++) { + await marketplace.reserveSlot(slot.request, i) await marketplace.fillSlot(slot.request, i, proof) } await waitUntilCancelled(request) @@ -772,6 +798,7 @@ describe("Marketplace", function () { await marketplace.requestExpiry(requestId(request)) ).toNumber() + await marketplace.reserveSlot(slot.request, slot.index) await advanceTimeToForNextBlock(filledAt) await marketplace.fillSlot(slot.request, slot.index, proof) await waitUntilCancelled(request) @@ -839,6 +866,7 @@ describe("Marketplace", function () { request.ask.collateral * (request.ask.maxSlotLoss + 1) ) for (let i = 0; i <= request.ask.maxSlotLoss; i++) { + await marketplace.reserveSlot(slot.request, i) await marketplace.fillSlot(slot.request, i, proof) } for (let i = 0; i <= request.ask.maxSlotLoss; i++) { @@ -898,6 +926,7 @@ describe("Marketplace", function () { }) it("changes to 'Filled' when slot is filled", async function () { + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) expect(await marketplace.slotState(slotId(slot))).to.equal(Filled) }) @@ -910,6 +939,7 @@ describe("Marketplace", function () { }) it("changes to 'Cancelled' when request is cancelled", async function () { + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) await waitUntilCancelled(request) await mine() @@ -917,6 +947,7 @@ describe("Marketplace", function () { }) it("changes to 'Free' when host frees the slot", async function () { + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) await marketplace.freeSlot(slotId(slot)) expect(await marketplace.slotState(slotId(slot))).to.equal(Free) @@ -984,12 +1015,14 @@ describe("Marketplace", function () { it("requires proofs when slot is filled", async function () { const id = slotId(slot) + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) await waitUntilProofWillBeRequired(id) }) it("will not require proofs once cancelled", async function () { const id = slotId(slot) + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) await waitUntilProofWillBeRequired(id) await expect(await marketplace.willProofBeRequired(id)).to.be.true @@ -1000,6 +1033,7 @@ describe("Marketplace", function () { it("does not require proofs once cancelled", async function () { const id = slotId(slot) + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) await waitUntilProofIsRequired(id) await expect(await marketplace.isProofRequired(id)).to.be.true @@ -1010,6 +1044,7 @@ describe("Marketplace", function () { it("does not provide challenges once cancelled", async function () { const id = slotId(slot) + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) await waitUntilProofIsRequired(id) await mine() @@ -1023,6 +1058,7 @@ describe("Marketplace", function () { it("does not provide pointer once cancelled", async function () { const id = slotId(slot) + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) await waitUntilProofIsRequired(id) await mine() @@ -1064,6 +1100,7 @@ describe("Marketplace", function () { } it("fails to mark proof as missing when cancelled", async function () { + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) await waitUntilCancelled(request) let missedPeriod = periodOf(await currentTime()) @@ -1076,6 +1113,7 @@ describe("Marketplace", function () { it("reduces collateral when too many proofs are missing", async function () { const id = slotId(slot) const { slashCriterion, slashPercentage } = config.collateral + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) for (let i = 0; i < slashCriterion; i++) { await waitUntilProofIsRequired(id) @@ -1208,9 +1246,11 @@ describe("Marketplace", function () { }) it("adds slot to list when filling slot", async function () { + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) let slot1 = { ...slot, index: slot.index + 1 } await token.approve(marketplace.address, request.ask.collateral) + await marketplace.reserveSlot(slot.request, slot1.index) await marketplace.fillSlot(slot.request, slot1.index, proof) expect(await marketplace.mySlots()).to.have.members([ slotId(slot), @@ -1219,9 +1259,11 @@ describe("Marketplace", function () { }) it("removes slot from list when slot is freed", async function () { + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) let slot1 = { ...slot, index: slot.index + 1 } await token.approve(marketplace.address, request.ask.collateral) + await marketplace.reserveSlot(slot.request, slot1.index) await marketplace.fillSlot(slot.request, slot1.index, proof) await token.approve(marketplace.address, request.ask.collateral) await marketplace.freeSlot(slotId(slot)) @@ -1229,10 +1271,12 @@ describe("Marketplace", function () { }) it("keeps slots when cancelled", async function () { + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) let slot1 = { ...slot, index: slot.index + 1 } await token.approve(marketplace.address, request.ask.collateral) + await marketplace.reserveSlot(slot.request, slot1.index) await marketplace.fillSlot(slot.request, slot1.index, proof) await waitUntilCancelled(request) await mine() @@ -1250,6 +1294,7 @@ describe("Marketplace", function () { }) it("removes slot when cancelled slot is freed", async function () { + await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) await waitUntilCancelled(request) await marketplace.freeSlot(slotId(slot)) diff --git a/test/marketplace.js b/test/marketplace.js index 3b10289..7f77cd3 100644 --- a/test/marketplace.js +++ b/test/marketplace.js @@ -18,6 +18,7 @@ async function waitUntilStarted(contract, request, proof, token) { await token.approve(contract.address, price(request) * request.ask.slots) for (let i = 0; i < request.ask.slots; i++) { + await contract.reserveSlot(requestId(request), i) await contract.fillSlot(requestId(request), i, proof) } }