diff --git a/contracts/Marketplace.sol b/contracts/Marketplace.sol index d9c0ecd..8c47063 100644 --- a/contracts/Marketplace.sol +++ b/contracts/Marketplace.sol @@ -318,6 +318,31 @@ contract Marketplace is Collateral, Proofs { } } + function isProofRequired(SlotId slotId) public view returns (bool) { + if (!_slotAcceptsProofs(slotId)) { + return false; + } + return _isProofRequired(slotId); + } + + function willProofBeRequired(SlotId slotId) public view returns (bool) { + if (!_slotAcceptsProofs(slotId)) { + return false; + } + return _willProofBeRequired(slotId); + } + + function getChallenge(SlotId slotId) public view returns (bytes32) { + if (!_slotAcceptsProofs(slotId)) { + return bytes32(0); + } + return _getChallenge(slotId); + } + + function getPointer(SlotId slotId) public view returns (uint8) { + return _getPointer(slotId); + } + function _price( uint64 numSlots, uint256 duration, diff --git a/contracts/Storage.sol b/contracts/Storage.sol index 5e6aa40..7f6f616 100644 --- a/contracts/Storage.sol +++ b/contracts/Storage.sol @@ -35,31 +35,6 @@ contract Storage is Marketplace { minCollateralThreshold = _minCollateralThreshold; } - function isProofRequired(SlotId slotId) public view returns (bool) { - if (!_slotAcceptsProofs(slotId)) { - return false; - } - return _isProofRequired(slotId); - } - - function willProofBeRequired(SlotId slotId) public view returns (bool) { - if (!_slotAcceptsProofs(slotId)) { - return false; - } - return _willProofBeRequired(slotId); - } - - function getChallenge(SlotId slotId) public view returns (bytes32) { - if (!_slotAcceptsProofs(slotId)) { - return bytes32(0); - } - return _getChallenge(slotId); - } - - function getPointer(SlotId slotId) public view returns (uint8) { - return _getPointer(slotId); - } - function submitProof(SlotId slotId, bytes calldata proof) public { _submitProof(slotId, proof); } diff --git a/test/Marketplace.test.js b/test/Marketplace.test.js index b44234d..dd48569 100644 --- a/test/Marketplace.test.js +++ b/test/Marketplace.test.js @@ -1,9 +1,10 @@ const { ethers } = require("hardhat") const { hexlify, randomBytes } = ethers.utils const { AddressZero } = ethers.constants +const { BigNumber } = ethers const { expect } = require("chai") const { exampleRequest } = require("./examples") -const { hours, minutes } = require("./time") +const { periodic, hours, minutes } = require("./time") const { requestId, slotId, askToArray } = require("./ids") const { waitUntilCancelled, @@ -17,14 +18,16 @@ const { price, pricePerSlot } = require("./price") const { snapshot, revert, + mine, ensureMinimumBlockHeight, advanceTime, + advanceTimeTo, currentTime, } = require("./evm") describe("Marketplace", function () { const collateral = 100 - const proofPeriod = 30 * 60 + const proofPeriod = 10 const proofTimeout = 5 const proofDowntime = 64 const proof = hexlify(randomBytes(42)) @@ -674,6 +677,81 @@ describe("Marketplace", function () { ) }) }) + + describe("proof requirements", function () { + let period, periodOf, periodEnd + + beforeEach(async function () { + period = (await marketplace.proofPeriod()).toNumber() + ;({ periodOf, periodEnd } = periodic(period)) + + 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) + }) + + async function waitUntilProofWillBeRequired(id) { + while (!(await marketplace.willProofBeRequired(id))) { + await mine() + } + } + + async function waitUntilProofIsRequired(id) { + await advanceTimeTo(periodEnd(periodOf(await currentTime()))) + while ( + !( + (await marketplace.isProofRequired(id)) && + (await marketplace.getPointer(id)) < 250 + ) + ) { + await advanceTime(period) + } + } + + it("will not require proofs once cancelled", async function () { + const id = slotId(slot) + await marketplace.fillSlot(slot.request, slot.index, proof) + await waitUntilProofWillBeRequired(id) + await expect(await marketplace.willProofBeRequired(id)).to.be.true + await advanceTimeTo(request.expiry + 1) + await expect(await marketplace.willProofBeRequired(id)).to.be.false + }) + + it("does not require proofs once cancelled", async function () { + const id = slotId(slot) + await marketplace.fillSlot(slot.request, slot.index, proof) + await waitUntilProofIsRequired(id) + await expect(await marketplace.isProofRequired(id)).to.be.true + await advanceTimeTo(request.expiry + 1) + await expect(await marketplace.isProofRequired(id)).to.be.false + }) + + it("does not provide challenges once cancelled", async function () { + const id = slotId(slot) + await marketplace.fillSlot(slot.request, slot.index, proof) + await waitUntilProofIsRequired(id) + const challenge1 = await marketplace.getChallenge(id) + expect(BigNumber.from(challenge1).gt(0)) + await advanceTimeTo(request.expiry + 1) + const challenge2 = await marketplace.getChallenge(id) + expect(BigNumber.from(challenge2).isZero()) + }) + + it("does not provide pointer once cancelled", async function () { + const id = slotId(slot) + await marketplace.fillSlot(slot.request, slot.index, proof) + await waitUntilProofIsRequired(id) + const challenge1 = await marketplace.getChallenge(id) + expect(BigNumber.from(challenge1).gt(0)) + await advanceTimeTo(request.expiry + 1) + const challenge2 = await marketplace.getChallenge(id) + expect(BigNumber.from(challenge2).isZero()) + }) + }) + describe("modifiers", function () { beforeEach(async function () { switchAccount(client) diff --git a/test/Storage.test.js b/test/Storage.test.js index aeb6748..86a3780 100644 --- a/test/Storage.test.js +++ b/test/Storage.test.js @@ -1,10 +1,9 @@ const { expect } = require("chai") const { ethers, deployments } = require("hardhat") -const { BigNumber } = ethers const { hexlify, randomBytes } = ethers.utils const { AddressZero } = ethers.constants const { exampleRequest } = require("./examples") -const { advanceTime, advanceTimeTo, currentTime, mine } = require("./evm") +const { advanceTime, advanceTimeTo, currentTime } = require("./evm") const { requestId, slotId } = require("./ids") const { periodic } = require("./time") const { price } = require("./price") @@ -88,6 +87,15 @@ describe("Storage", function () { } } + it("fails to mark proof as missing when cancelled", async function () { + await storage.fillSlot(slot.request, slot.index, proof) + await waitUntilCancelled(request) + let missedPeriod = periodOf(await currentTime()) + await expect( + storage.markProofAsMissing(slotId(slot), missedPeriod) + ).to.be.revertedWith("Slot not accepting proofs") + }) + describe("slashing when missing proofs", function () { it("reduces collateral when too many proofs are missing", async function () { const id = slotId(slot) @@ -130,82 +138,6 @@ describe("Storage", function () { }) }) }) - - describe("contract state", function () { - let period, periodOf, periodEnd - - beforeEach(async function () { - period = (await storage.proofPeriod()).toNumber() - ;({ periodOf, periodEnd } = periodic(period)) - }) - - async function waitUntilProofWillBeRequired(id) { - while (!(await storage.willProofBeRequired(id))) { - await mine() - } - } - - async function waitUntilProofIsRequired(id) { - await advanceTimeTo(periodEnd(periodOf(await currentTime()))) - while ( - !( - (await storage.isProofRequired(id)) && - (await storage.getPointer(id)) < 250 - ) - ) { - await advanceTime(period) - } - } - - it("fails to mark proof as missing when cancelled", async function () { - await storage.fillSlot(slot.request, slot.index, proof) - await waitUntilCancelled(request) - let missedPeriod = periodOf(await currentTime()) - await expect( - storage.markProofAsMissing(slotId(slot), missedPeriod) - ).to.be.revertedWith("Slot not accepting proofs") - }) - - it("will not require proofs once cancelled", async function () { - const id = slotId(slot) - await storage.fillSlot(slot.request, slot.index, proof) - await waitUntilProofWillBeRequired(id) - await expect(await storage.willProofBeRequired(id)).to.be.true - 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) - await storage.fillSlot(slot.request, slot.index, proof) - await waitUntilProofIsRequired(id) - await expect(await storage.isProofRequired(id)).to.be.true - await advanceTimeTo(request.expiry + 1) - await expect(await storage.isProofRequired(id)).to.be.false - }) - - it("does not provide challenges once cancelled", async function () { - const id = slotId(slot) - await storage.fillSlot(slot.request, slot.index, proof) - await waitUntilProofIsRequired(id) - const challenge1 = await storage.getChallenge(id) - expect(BigNumber.from(challenge1).gt(0)) - await advanceTimeTo(request.expiry + 1) - const challenge2 = await storage.getChallenge(id) - expect(BigNumber.from(challenge2).isZero()) - }) - - it("does not provide pointer once cancelled", async function () { - const id = slotId(slot) - await storage.fillSlot(slot.request, slot.index, proof) - await waitUntilProofIsRequired(id) - const challenge1 = await storage.getChallenge(id) - expect(BigNumber.from(challenge1).gt(0)) - await advanceTimeTo(request.expiry + 1) - const challenge2 = await storage.getChallenge(id) - expect(BigNumber.from(challenge2).isZero()) - }) - }) }) // TODO: implement checking of actual proofs of storage, instead of dummy bool