Remove proof extension, test clean up

1. Remove proof extension as it is not needed. Host are required to provide proofs from the moment they fill a slot, for the duration specified by the contract. This means that the ending of their requirements will be staggered at the end, as they were at the start, but this is more predicable for determining the cost of a request.
2. The proof end time was modified so that if the request state is not accepting proofs, it takes the min of the slot proof end time, the request end time, or block.timestamp - 1, which ensures that it returns a time in the past. If the slot is accepting proofs, it returns the slot end time.
3. Modify marketplace tests so that `waitUntilFinished` advances time to the proof ending of the last slot filled.
This commit is contained in:
Eric Mastro 2022-09-23 12:33:39 +10:00 committed by Eric Mastro
parent 321132b6fa
commit fd74268a8a
8 changed files with 140 additions and 265 deletions

View File

@ -2,6 +2,7 @@
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "./Collateral.sol";
import "./Proofs.sol";
@ -62,20 +63,19 @@ contract Marketplace is Collateral, Proofs {
require(balanceOf(msg.sender) >= collateral, "Insufficient collateral");
_lock(msg.sender, requestId);
_expectProofs(slotId, requestId, request.ask.proofProbability, request.ask.duration);
_expectProofs(slotId, request.ask.proofProbability, request.ask.duration);
_submitProof(slotId, proof);
slot.host = msg.sender;
slot.requestId = requestId;
RequestContext storage context = _context(requestId);
context.slotsFilled += 1;
context.endsAt = block.timestamp + request.ask.duration;
emit SlotFilled(requestId, slotIndex, slotId);
if (context.slotsFilled == request.ask.slots) {
context.state = RequestState.Started;
context.startedAt = block.timestamp;
context.endsAt = block.timestamp + request.ask.duration;
_extendLockExpiryTo(requestId, context.endsAt);
_extendProofEndTo(slotId, context.endsAt);
emit RequestFulfilled(requestId);
}
}
@ -105,6 +105,7 @@ contract Marketplace is Collateral, Proofs {
context.state == RequestState.Started) {
context.state = RequestState.Failed;
context.endsAt = block.timestamp - 1;
emit RequestFailed(requestId);
// TODO: burn all remaining slot collateral (note: slot collateral not
@ -232,11 +233,16 @@ contract Marketplace is Collateral, Proofs {
function proofEnd(bytes32 slotId) public view returns (uint256) {
Slot memory slot = _slot(slotId);
uint256 end = _end(slot.requestId);
if (!_slotAcceptsProofs(slotId)) {
return end < block.timestamp ? end : block.timestamp - 1;
}
uint256 end = _end(slotId);
RequestContext storage context = _context(slot.requestId);
if (_slotAcceptsProofs(slotId)) {
return end;
} else {
// Calculate the earliest ending between a slot and a request.
// Request endings are set, for eg, when a request fails.
uint256 earliestEnd = Math.min(end, context.endsAt);
return Math.min(earliestEnd, block.timestamp - 1);
}
}
function _price(
@ -260,7 +266,6 @@ contract Marketplace is Collateral, Proofs {
}
function state(bytes32 requestId) public view returns (RequestState) {
// TODO: add check for _isFinished
if (_isCancelled(requestId)) {
return RequestState.Cancelled;
} else if (_isFinished(requestId)) {

View File

@ -20,7 +20,6 @@ contract Proofs {
mapping(bytes32 => bool) private ids;
mapping(bytes32 => uint256) private starts;
mapping(bytes32 => uint256) private ends;
mapping(bytes32 => bytes32) private idEnds;
mapping(bytes32 => uint256) private probabilities;
mapping(bytes32 => uint256) private markers;
mapping(bytes32 => uint256) private missed;
@ -35,21 +34,10 @@ contract Proofs {
return timeout;
}
function _end(bytes32 endId) internal view returns (uint256) {
uint256 end = ends[endId];
function _end(bytes32 id) internal view returns (uint256) {
uint256 end = ends[id];
require(end > 0, "Proof ending doesn't exist");
return ends[endId];
}
function _endId(bytes32 id) internal view returns (bytes32) {
bytes32 endId = idEnds[id];
require(endId > 0, "endId for given id doesn't exist");
return endId;
}
function _endFromId(bytes32 id) internal view returns (uint256) {
bytes32 endId = _endId(id);
return _end(endId);
return ends[id];
}
function _missed(bytes32 id) internal view returns (uint256) {
@ -67,22 +55,19 @@ contract Proofs {
/// @notice Informs the contract that proofs should be expected for id
/// @dev Requires that the id is not already in use
/// @param id identifies the proof expectation, typically a slot id
/// @param endId Identifies the id of the proof expectation ending. Typically a request id. Different from id because the proof ending is shared amongst many ids.
/// @param probability The probability that a proof should be expected
/// @param duration Duration, from now, for which proofs should be expected
function _expectProofs(
bytes32 id, // typically slot id
bytes32 endId, // typically request id, used so that the ending is global for all slots
uint256 probability,
uint256 duration
) internal {
require(!ids[id], "Proof id already in use");
ids[id] = true;
starts[id] = block.timestamp;
ends[endId] = block.timestamp + duration;
ends[id] = block.timestamp + duration;
probabilities[id] = probability;
markers[id] = uint256(blockhash(block.number - 1)) % period;
idEnds[id] = endId;
}
function _unexpectProofs(
@ -134,14 +119,13 @@ contract Proofs {
if (proofPeriod <= periodOf(starts[id])) {
return (false, 0);
}
uint256 end = _endFromId(id);
uint256 end = _end(id);
if (proofPeriod >= periodOf(end)) {
return (false, 0);
}
pointer = _getPointer(id, proofPeriod);
bytes32 challenge = _getChallenge(pointer);
uint256 probability = (probabilities[id] * (256 - downtime)) / 256;
// TODO: add test for below change
isRequired = ids[id] && uint256(challenge) % probability == 0;
}
@ -185,19 +169,5 @@ contract Proofs {
missed[id] += 1;
}
/// @notice Extends the proof end time
/// @dev The id must have a mapping to an end id, the end must exist, and the end must not have elapsed yet
/// @param id the id of the proofs to extend. Typically a slot id, the id is mapped to an endId.
/// @param ending the new end time (in seconds)
function _extendProofEndTo(bytes32 id, uint256 ending) internal {
bytes32 endId = _endId(id);
uint256 end = ends[endId];
// TODO: create type aliases for id and endId so that _end() can return
// EndId storage and we don't need to replicate the below require here
require (end > 0, "Proof ending doesn't exist");
require (block.timestamp <= end, "Proof already ended");
ends[endId] = ending;
}
event ProofSubmitted(bytes32 id, bytes proof);
}

View File

@ -34,11 +34,10 @@ contract TestProofs is Proofs {
function expectProofs(
bytes32 id,
bytes32 endId,
uint256 _probability,
uint256 _duration
) public {
_expectProofs(id, endId, _probability, _duration);
_expectProofs(id, _probability, _duration);
}
function unexpectProofs(bytes32 id) public {
@ -68,8 +67,4 @@ contract TestProofs is Proofs {
function markProofAsMissing(bytes32 id, uint256 _period) public {
_markProofAsMissing(id, _period);
}
function extendProofEndTo(bytes32 id, uint256 ending) public {
_extendProofEndTo(id, ending);
}
}

View File

@ -10,7 +10,6 @@ const {
} = require("./evm")
const { exampleLock } = require("./examples")
const { hours } = require("./time")
const { waitUntilCancelled } = require("./marketplace")
describe("Account Locks", function () {
let locks
@ -202,7 +201,7 @@ describe("Account Locks", function () {
})
it("fails when lock is already expired", async function () {
await waitUntilCancelled(expiry)
await advanceTimeTo(expiry)
await expect(locks.extendLockExpiryTo(id, newExpiry)).to.be.revertedWith(
"Lock already expired"
)

View File

@ -178,26 +178,16 @@ describe("Marketplace", function () {
})
it("is rejected when request is finished", async function () {
await waitUntilStarted(
marketplace,
request.ask.slots,
slot.request,
proof
)
await waitUntilFinished(marketplace, slotId(slot))
const lastSlot = await waitUntilStarted(marketplace, request, slot, proof)
await waitUntilFinished(marketplace, lastSlot)
await expect(
marketplace.fillSlot(slot.request, slot.index, proof)
).to.be.revertedWith("Request not accepting proofs")
})
it("is rejected when request is failed", async function () {
await waitUntilStarted(
marketplace,
request.ask.slots,
slot.request,
proof
)
await waitUntilFailed(marketplace, slot, request.ask.maxSlotLoss)
await waitUntilStarted(marketplace, request, slot, proof)
await waitUntilFailed(marketplace, request, slot)
await expect(
marketplace.fillSlot(slot.request, slot.index, proof)
).to.be.revertedWith("Request not accepting proofs")
@ -219,6 +209,18 @@ describe("Marketplace", function () {
marketplace.fillSlot(slot.request, lastSlot, proof)
).to.be.revertedWith("Slot already filled")
})
})
describe("proof end", function () {
beforeEach(async function () {
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)
})
it("shares proof end time for all slots in request", async function () {
const lastSlot = request.ask.slots - 1
for (let i = 0; i < lastSlot; i++) {
@ -233,6 +235,39 @@ describe("Marketplace", function () {
await expect((await marketplace.proofEnd(slotId(sloti))) === end)
}
})
it("sets proof end time to the request duration once filled", async function () {
await marketplace.fillSlot(slot.request, slot.index, proof)
await expect(await marketplace.proofEnd(slotId(slot))).to.be.eq(
(await currentTime()) + request.ask.duration
)
})
it("sets proof end time to the past once failed", async function () {
await waitUntilStarted(marketplace, request, slot, proof)
await waitUntilFailed(marketplace, request, slot)
let slot0 = { ...slot, index: request.ask.maxSlotLoss + 1 }
const now = await currentTime()
await expect(await marketplace.proofEnd(slotId(slot0))).to.be.eq(now - 1)
})
it("sets proof end time to the past once cancelled", async function () {
await marketplace.fillSlot(slot.request, slot.index, proof)
await waitUntilCancelled(request)
const now = await currentTime()
await expect(await marketplace.proofEnd(slotId(slot))).to.be.eq(now - 1)
})
it("sets proof end time to the past once finished", async function () {
const lastSlot = await waitUntilStarted(marketplace, request, slot, proof)
await waitUntilFinished(marketplace, lastSlot) // sets proofEnd to block.timestamp - 1
const now = await currentTime()
// the proof end time is set to block.timestamp - 1 when the contract is
// finished. in the process of calling currentTime and proofEnd,
// block.timestamp has advanced by 1, so the expected proof end time will
// be block.timestamp - 2.
await expect(await marketplace.proofEnd(slotId(slot))).to.be.eq(now - 2)
})
})
describe("freeing a slot", function () {
@ -257,48 +292,36 @@ describe("Marketplace", function () {
)
})
it("fails to free slot when finished", async function () {
await waitUntilStarted(
marketplace,
request.ask.slots,
slot.request,
proof
it("fails to free slot when cancelled", async function () {
await marketplace.fillSlot(slot.request, slot.index, proof)
await waitUntilCancelled(request)
await expect(marketplace.freeSlot(slotId(slot))).to.be.revertedWith(
"Slot not accepting proofs"
)
await waitUntilFinished(marketplace, slotId(slot))
await expect(marketplace.freeSlot(id)).to.be.revertedWith(
})
it("fails to free slot when finished", async function () {
const lastSlot = await waitUntilStarted(marketplace, request, slot, proof)
await waitUntilFinished(marketplace, lastSlot)
await expect(marketplace.freeSlot(slotId(slot))).to.be.revertedWith(
"Slot not accepting proofs"
)
})
it("successfully frees slot", async function () {
await waitUntilStarted(
marketplace,
request.ask.slots,
slot.request,
proof
)
await waitUntilStarted(marketplace, request, slot, proof)
await expect(marketplace.freeSlot(id)).not.to.be.reverted
})
it("emits event once slot is freed", async function () {
await waitUntilStarted(
marketplace,
request.ask.slots,
slot.request,
proof
)
await waitUntilStarted(marketplace, request, slot, proof)
await expect(await marketplace.freeSlot(id))
.to.emit(marketplace, "SlotFreed")
.withArgs(slot.request, id)
})
it("cannot get slot once freed", async function () {
await waitUntilStarted(
marketplace,
request.ask.slots,
slot.request,
proof
)
await waitUntilStarted(marketplace, request, slot, proof)
await marketplace.freeSlot(id)
await expect(marketplace.slot(id)).to.be.revertedWith("Slot empty")
})
@ -315,13 +338,8 @@ describe("Marketplace", function () {
})
it("pays the host", async function () {
await waitUntilStarted(
marketplace,
request.ask.slots,
slot.request,
proof
)
await waitUntilFinished(marketplace, slotId(slot))
const lastSlot = await waitUntilStarted(marketplace, request, slot, proof)
await waitUntilFinished(marketplace, lastSlot)
const startBalance = await token.balanceOf(host.address)
await marketplace.payoutSlot(slot.request, slot.index)
const endBalance = await token.balanceOf(host.address)
@ -336,13 +354,8 @@ describe("Marketplace", function () {
})
it("can only be done once", async function () {
await waitUntilStarted(
marketplace,
request.ask.slots,
slot.request,
proof
)
await waitUntilFinished(marketplace, slotId(slot))
const lastSlot = await waitUntilStarted(marketplace, request, slot, proof)
await waitUntilFinished(marketplace, lastSlot)
await marketplace.payoutSlot(slot.request, slot.index)
await expect(
marketplace.payoutSlot(slot.request, slot.index)
@ -350,13 +363,8 @@ describe("Marketplace", function () {
})
it("cannot be filled again", async function () {
await waitUntilStarted(
marketplace,
request.ask.slots,
slot.request,
proof
)
await waitUntilFinished(marketplace, slotId(slot))
const lastSlot = await waitUntilStarted(marketplace, request, slot, proof)
await waitUntilFinished(marketplace, lastSlot)
await marketplace.payoutSlot(slot.request, slot.index)
await expect(marketplace.fillSlot(slot.request, slot.index, proof)).to.be
.reverted
@ -420,7 +428,7 @@ describe("Marketplace", function () {
})
it("rejects withdraw when wrong account used", async function () {
await waitUntilCancelled(request.expiry)
await waitUntilCancelled(request)
await expect(marketplace.withdrawFunds(slot.request)).to.be.revertedWith(
"Invalid client address"
)
@ -432,7 +440,7 @@ describe("Marketplace", function () {
for (let i = 0; i <= lastSlot; i++) {
await marketplace.fillSlot(slot.request, i, proof)
}
await waitUntilCancelled(request.expiry)
await waitUntilCancelled(request)
switchAccount(client)
await expect(marketplace.withdrawFunds(slot.request)).to.be.revertedWith(
"Invalid state"
@ -440,7 +448,7 @@ describe("Marketplace", function () {
})
it("emits event once request is cancelled", async function () {
await waitUntilCancelled(request.expiry)
await waitUntilCancelled(request)
switchAccount(client)
await expect(marketplace.withdrawFunds(slot.request))
.to.emit(marketplace, "RequestCancelled")
@ -448,7 +456,7 @@ describe("Marketplace", function () {
})
it("withdraws to the client", async function () {
await waitUntilCancelled(request.expiry)
await waitUntilCancelled(request)
switchAccount(client)
const startBalance = await token.balanceOf(client.address)
await marketplace.withdrawFunds(slot.request)
@ -474,7 +482,7 @@ describe("Marketplace", function () {
})
it("state is Cancelled when client withdraws funds", async function () {
await waitUntilCancelled(request.expiry)
await waitUntilCancelled(request)
switchAccount(client)
await marketplace.withdrawFunds(slot.request)
await expect(await marketplace.state(slot.request)).to.equal(
@ -483,38 +491,23 @@ describe("Marketplace", function () {
})
it("changes state to Started once all slots are filled", async function () {
await waitUntilStarted(
marketplace,
request.ask.slots,
slot.request,
proof
)
await waitUntilStarted(marketplace, request, slot, proof)
await expect(await marketplace.state(slot.request)).to.equal(
RequestState.Started
)
})
it("state is Failed once too many slots are freed", async function () {
await waitUntilStarted(
marketplace,
request.ask.slots,
slot.request,
proof
)
await waitUntilFailed(marketplace, slot, request.ask.maxSlotLoss)
await waitUntilStarted(marketplace, request, slot, proof)
await waitUntilFailed(marketplace, request, slot)
await expect(await marketplace.state(slot.request)).to.equal(
RequestState.Failed
)
})
it("state is Finished once slot is paid out", async function () {
await waitUntilStarted(
marketplace,
request.ask.slots,
slot.request,
proof
)
await waitUntilFinished(marketplace, slotId(slot))
const lastSlot = await waitUntilStarted(marketplace, request, slot, proof)
await waitUntilFinished(marketplace, lastSlot)
await marketplace.payoutSlot(slot.request, slot.index)
await expect(await marketplace.state(slot.request)).to.equal(
RequestState.Finished
@ -535,7 +528,7 @@ describe("Marketplace", function () {
})
it("changes state to Cancelled once request is cancelled", async function () {
await waitUntilCancelled(request.expiry)
await waitUntilCancelled(request)
await expect(await marketplace.state(slot.request)).to.equal(
RequestState.Cancelled
)
@ -543,7 +536,7 @@ describe("Marketplace", function () {
it("changes isCancelled to true once request is cancelled", async function () {
await expect(await marketplace.isCancelled(slot.request)).to.be.false
await waitUntilCancelled(request.expiry)
await waitUntilCancelled(request)
await expect(await marketplace.isCancelled(slot.request)).to.be.true
})
@ -556,7 +549,7 @@ describe("Marketplace", function () {
it("changes isSlotCancelled to true once request is cancelled", async function () {
await marketplace.fillSlot(slot.request, slot.index, proof)
await expect(await marketplace.isSlotCancelled(slotId(slot))).to.be.false
await waitUntilCancelled(request.expiry)
await waitUntilCancelled(request)
await expect(await marketplace.isSlotCancelled(slotId(slot))).to.be.true
})
@ -565,7 +558,7 @@ describe("Marketplace", function () {
await expect(await marketplace.proofEnd(slotId(slot))).to.be.gt(
await currentTime()
)
await waitUntilCancelled(request.expiry)
await waitUntilCancelled(request)
await expect(await marketplace.proofEnd(slotId(slot))).to.be.lt(
await currentTime()
)
@ -584,7 +577,7 @@ describe("Marketplace", function () {
describe("accepting proofs", function () {
it("fails when request Cancelled (isCancelled is true)", async function () {
await marketplace.fillSlot(slot.request, slot.index, proof)
await waitUntilCancelled(request.expiry)
await waitUntilCancelled(request)
await expect(
marketplace.testAcceptsProofs(slotId(slot))
).to.be.revertedWith("Slot not accepting proofs")
@ -592,7 +585,7 @@ describe("Marketplace", function () {
it("fails when request Cancelled (state set to Cancelled)", async function () {
await marketplace.fillSlot(slot.request, slot.index, proof)
await waitUntilCancelled(request.expiry)
await waitUntilCancelled(request)
switchAccount(client)
await marketplace.withdrawFunds(slot.request)
await expect(
@ -601,26 +594,26 @@ describe("Marketplace", function () {
})
it("fails when request Finished (isFinished is true)", async function () {
await waitUntilStarted(
const lastSlot = await waitUntilStarted(
marketplace,
request.ask.slots,
slot.request,
request,
slot,
proof
)
await waitUntilFinished(marketplace, slotId(slot))
await waitUntilFinished(marketplace, lastSlot)
await expect(
marketplace.testAcceptsProofs(slotId(slot))
).to.be.revertedWith("Slot not accepting proofs")
})
it("fails when request Finished (state set to Finished)", async function () {
await waitUntilStarted(
const lastSlot = await waitUntilStarted(
marketplace,
request.ask.slots,
slot.request,
request,
slot,
proof
)
await waitUntilFinished(marketplace, slotId(slot))
await waitUntilFinished(marketplace, lastSlot)
await marketplace.payoutSlot(slot.request, slot.index)
await expect(
marketplace.testAcceptsProofs(slotId(slot))
@ -628,12 +621,7 @@ describe("Marketplace", function () {
})
it("fails when request Failed", async function () {
await waitUntilStarted(
marketplace,
request.ask.slots,
slot.request,
proof
)
await waitUntilStarted(marketplace, request, slot, proof)
for (let i = 0; i <= request.ask.maxSlotLoss; i++) {
slot.index = i
let id = slotId(slot)

View File

@ -14,7 +14,6 @@ const { periodic, hours, minutes } = require("./time")
describe("Proofs", function () {
const id = hexlify(randomBytes(32))
const endId = hexlify(randomBytes(32))
const period = 30 * 60
const timeout = 5
const downtime = 64
@ -36,20 +35,20 @@ describe("Proofs", function () {
})
it("calculates an end time based on duration", async function () {
await proofs.expectProofs(id, endId, probability, duration)
await proofs.expectProofs(id, probability, duration)
let end = (await currentTime()) + duration
expect((await proofs.end(endId)).toNumber()).to.be.closeTo(end, 1)
expect((await proofs.end(id)).toNumber()).to.be.closeTo(end, 1)
})
it("does not allow ids to be reused", async function () {
await proofs.expectProofs(id, endId, probability, duration)
await proofs.expectProofs(id, probability, duration)
await expect(
proofs.expectProofs(id, endId, probability, duration)
proofs.expectProofs(id, probability, duration)
).to.be.revertedWith("Proof id already in use")
})
it("requires proofs with an agreed upon probability", async function () {
await proofs.expectProofs(id, endId, probability, duration)
await proofs.expectProofs(id, probability, duration)
let amount = 0
for (let i = 0; i < 100; i++) {
if (await proofs.isProofRequired(id)) {
@ -64,7 +63,7 @@ describe("Proofs", function () {
it("requires no proofs in the start period", async function () {
const startPeriod = Math.floor((await currentTime()) / period)
const probability = 1
await proofs.expectProofs(id, endId, probability, duration)
await proofs.expectProofs(id, probability, duration)
while (Math.floor((await currentTime()) / period) == startPeriod) {
expect(await proofs.isProofRequired(id)).to.be.false
await advanceTime(Math.floor(period / 10))
@ -73,14 +72,14 @@ describe("Proofs", function () {
it("requires no proofs in the end period", async function () {
const probability = 1
await proofs.expectProofs(id, endId, probability, duration)
await proofs.expectProofs(id, probability, duration)
await advanceTime(duration)
expect(await proofs.isProofRequired(id)).to.be.false
})
it("requires no proofs after the end time", async function () {
const probability = 1
await proofs.expectProofs(id, endId, probability, duration)
await proofs.expectProofs(id, probability, duration)
await advanceTime(duration + timeout)
expect(await proofs.isProofRequired(id)).to.be.false
})
@ -90,7 +89,7 @@ describe("Proofs", function () {
let id2 = hexlify(randomBytes(32))
let id3 = hexlify(randomBytes(32))
for (let id of [id1, id2, id3]) {
await proofs.expectProofs(id, endId, probability, duration)
await proofs.expectProofs(id, probability, duration)
}
let req1, req2, req3
while (req1 === req2 && req2 === req3) {
@ -119,7 +118,7 @@ describe("Proofs", function () {
}
beforeEach(async function () {
await proofs.expectProofs(id, endId, probability, duration)
await proofs.expectProofs(id, probability, duration)
await advanceTimeTo(periodEnd(periodOf(await currentTime())))
await waitUntilProofWillBeRequired()
})
@ -152,7 +151,7 @@ describe("Proofs", function () {
const proof = hexlify(randomBytes(42))
beforeEach(async function () {
await proofs.expectProofs(id, endId, probability, duration)
await proofs.expectProofs(id, probability, duration)
})
async function waitUntilProofIsRequired(id) {
@ -271,87 +270,4 @@ describe("Proofs", function () {
await expect(await proofs.isProofRequired(id)).to.be.false
})
})
describe("extend proof end", function () {
const proof = hexlify(randomBytes(42))
beforeEach(async function () {
await proofs.expectProofs(id, endId, probability, duration)
})
async function waitUntilProofIsRequired(id) {
await advanceTimeTo(periodEnd(periodOf(await currentTime())))
while (
!(
(await proofs.isProofRequired(id)) &&
(await proofs.getPointer(id)) < 250
)
) {
await advanceTime(period)
}
}
async function isProofRequiredBefore(id, ending) {
let start = periodOf(await currentTime())
let end = periodOf(ending)
let periods = end - start
await advanceTimeTo(periodEnd(periodOf(await currentTime())))
for (let i = 0; i < periods; i++) {
if (await proofs.isProofRequired(id)) {
return true
}
await advanceTime(period)
}
return false
}
it("can't extend if proof doesn't exist", async function () {
let ending = (await currentTime()) + duration
const otherId = hexlify(randomBytes(32))
await expect(
proofs.extendProofEndTo(otherId, ending + 1)
).to.be.revertedWith("endId for given id doesn't exist")
})
it("can't extend already lapsed proof ending", async function () {
let ending = (await currentTime()) + duration
await waitUntilProofIsRequired(id)
await advanceTimeTo(ending + 1)
await expect(proofs.extendProofEndTo(id, ending + 1)).to.be.revertedWith(
"Proof already ended"
)
})
it("requires no proofs when ending has not been extended", async function () {
let ending = (await currentTime()) + duration
await expect(await isProofRequiredBefore(id, ending)).to.be.true
let endingExtended = ending + hours(1)
await advanceTimeTo(periodEnd(periodOf(endingExtended) + 1))
await expect(await isProofRequiredBefore(id, endingExtended)).to.be.false
})
it("requires proofs when ending has been extended", async function () {
let ending = (await currentTime()) + duration
await expect(await isProofRequiredBefore(id, ending)).to.be.true
let endingExtended = ending + hours(1)
await proofs.extendProofEndTo(id, endingExtended)
await expect(await isProofRequiredBefore(id, endingExtended)).to.be.true
})
it("no longer requires proofs after extension lapsed", async function () {
async function expectNoProofsForPeriods(id, periods) {
await advanceTimeTo(periodEnd(periodOf(await currentTime())))
for (let i = 0; i < periods; i++) {
await expect(await proofs.isProofRequired(id)).to.be.false
await advanceTime(period)
}
}
let ending = (await currentTime()) + duration
let endingExtended = ending + hours(1)
await proofs.extendProofEndTo(id, endingExtended)
await advanceTimeTo(periodEnd(periodOf(endingExtended) + 1))
await expectNoProofsForPeriods(id, 100)
})
})
})

View File

@ -76,7 +76,7 @@ describe("Storage", function () {
describe("ending the contract", function () {
it("unlocks the host collateral", async function () {
await storage.fillSlot(slot.request, slot.index, proof)
await waitUntilFinished(storage, slotId(slot))
await waitUntilFinished(storage, slot)
await expect(storage.withdraw()).not.to.be.reverted
})
})
@ -121,7 +121,7 @@ describe("Storage", function () {
it("frees slot when collateral slashed below minimum threshold", async function () {
const id = slotId(slot)
await waitUntilStarted(storage, request.ask.slots, slot.request, proof)
await waitUntilStarted(storage, request, slot, proof)
// max slashes before dropping below collateral threshold
const maxSlashes = 10
@ -172,7 +172,7 @@ 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.expiry)
await waitUntilCancelled(request)
let missedPeriod = periodOf(await currentTime())
await expect(
storage.markProofAsMissing(slotId(slot), missedPeriod)

View File

@ -1,24 +1,26 @@
const { advanceTimeTo } = require("./evm")
const { slotId } = require("./ids")
async function waitUntilCancelled(expiry) {
await advanceTimeTo(expiry + 1)
async function waitUntilCancelled(request) {
await advanceTimeTo(request.expiry + 1)
}
async function waitUntilStarted(contract, numSlots, requestId, proof) {
const lastSlot = numSlots - 1
for (let i = 0; i <= lastSlot; i++) {
await contract.fillSlot(requestId, i, proof)
async function waitUntilStarted(contract, request, slot, proof) {
const lastSlotIdx = request.ask.slots - 1
for (let i = 0; i <= lastSlotIdx; i++) {
await contract.fillSlot(slot.request, i, proof)
}
return { ...slot, index: lastSlotIdx }
}
async function waitUntilFinished(contract, slotId) {
const end = (await contract.proofEnd(slotId)).toNumber()
async function waitUntilFinished(contract, lastSlot) {
const lastSlotId = slotId(lastSlot)
const end = (await contract.proofEnd(lastSlotId)).toNumber()
await advanceTimeTo(end + 1)
}
async function waitUntilFailed(contract, slot, maxSlotLoss) {
for (let i = 0; i <= maxSlotLoss; i++) {
async function waitUntilFailed(contract, request, slot) {
for (let i = 0; i <= request.ask.maxSlotLoss; i++) {
slot.index = i
let id = slotId(slot)
await contract.freeSlot(id)