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
This commit is contained in:
parent
807fc973c8
commit
f5a54c7ed4
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue