clean up tests

1. Replace all instances of `now()` with `await currentTime()` to get a more accurate representation of time from the block timestamp. Update examples.js to be async.
2. Move `RequestState` to `marketplace.js`
3. Delete `TestStorage` as `slashAmount` function no longer needed.
This commit is contained in:
Eric Mastro 2022-09-22 12:21:49 +10:00 committed by Eric Mastro
parent ad040cfee6
commit 321132b6fa
14 changed files with 121 additions and 306 deletions

View File

@ -49,7 +49,6 @@ contract AccountLocks {
function _extendLockExpiryTo(bytes32 lockId, uint256 expiry) internal { function _extendLockExpiryTo(bytes32 lockId, uint256 expiry) internal {
Lock storage lock = locks[lockId]; Lock storage lock = locks[lockId];
require(lock.owner != address(0), "Lock does not exist"); require(lock.owner != address(0), "Lock does not exist");
// require(lock.owner == msg.sender, "Only lock creator can extend expiry");
require(lock.expiry >= block.timestamp, "Lock already expired"); require(lock.expiry >= block.timestamp, "Lock already expired");
lock.expiry = expiry; lock.expiry = expiry;
} }

View File

@ -232,7 +232,7 @@ contract Marketplace is Collateral, Proofs {
function proofEnd(bytes32 slotId) public view returns (uint256) { function proofEnd(bytes32 slotId) public view returns (uint256) {
Slot memory slot = _slot(slotId); Slot memory slot = _slot(slotId);
uint256 end = _end(slotId); uint256 end = _end(slot.requestId);
if (!_slotAcceptsProofs(slotId)) { if (!_slotAcceptsProofs(slotId)) {
return end < block.timestamp ? end : block.timestamp - 1; return end < block.timestamp ? end : block.timestamp - 1;
} }
@ -263,7 +263,7 @@ contract Marketplace is Collateral, Proofs {
// TODO: add check for _isFinished // TODO: add check for _isFinished
if (_isCancelled(requestId)) { if (_isCancelled(requestId)) {
return RequestState.Cancelled; return RequestState.Cancelled;
else if (_isFinished(requestId) { } else if (_isFinished(requestId)) {
return RequestState.Finished; return RequestState.Finished;
} else { } else {
RequestContext storage context = _context(requestId); RequestContext storage context = _context(requestId);
@ -361,15 +361,6 @@ contract Marketplace is Collateral, Proofs {
assert(funds.received == funds.balance + funds.sent); assert(funds.received == funds.balance + funds.sent);
} }
function acceptsProofs(bytes32 requestId) private view {
RequestState s = state(requestId);
require(s == RequestState.New || s == RequestState.Started, "Invalid state");
// must test these states separately as they handle cases where the state hasn't
// yet been updated by a transaction
require(!_isCancelled(requestId), "Request cancelled");
require(!_isFinished(requestId), "Request finished");
}
/// @notice Modifier that requires the request state to be that which is accepting proof submissions from hosts occupying slots. /// @notice Modifier that requires the request state to be that which is accepting proof submissions from hosts occupying slots.
/// @dev Request state must be new or started, and must not be cancelled, finished, or failed. /// @dev Request state must be new or started, and must not be cancelled, finished, or failed.
/// @param slotId id of the slot, that is mapped to a request, for which to obtain state info /// @param slotId id of the slot, that is mapped to a request, for which to obtain state info

View File

@ -82,6 +82,7 @@ contract Storage is Collateral, Marketplace {
function markProofAsMissing(bytes32 slotId, uint256 period) function markProofAsMissing(bytes32 slotId, uint256 period)
public public
slotMustAcceptProofs(slotId)
{ {
_markProofAsMissing(slotId, period); _markProofAsMissing(slotId, period);
address host = _host(slotId); address host = _host(slotId);

View File

@ -37,7 +37,7 @@ contract TestMarketplace is Marketplace {
function testAcceptsProofs(bytes32 slotId) function testAcceptsProofs(bytes32 slotId)
public public
view view
slotAcceptsProofs(slotId) slotMustAcceptProofs(slotId)
// solhint-disable-next-line no-empty-blocks // solhint-disable-next-line no-empty-blocks
{ {

View File

@ -1,36 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./Storage.sol";
// exposes internal functions of Storage for testing
contract TestStorage is Storage {
constructor(
IERC20 token,
uint256 _proofPeriod,
uint256 _proofTimeout,
uint8 _proofDowntime,
uint256 _collateralAmount,
uint256 _slashMisses,
uint256 _slashPercentage,
uint256 _minCollateralThreshold
)
Storage(
token,
_proofPeriod,
_proofTimeout,
_proofDowntime,
_collateralAmount,
_slashMisses,
_slashPercentage,
_minCollateralThreshold
)
// solhint-disable-next-line no-empty-blocks
{
}
function slashAmount(address account, uint256 percentage) public view returns (uint256) {
return _slashAmount(account, percentage);
}
}

View File

@ -18,7 +18,7 @@ async function deployStorage({ deployments, getNamedAccounts }) {
minCollateralThreshold, minCollateralThreshold,
] ]
const { deployer } = await getNamedAccounts() const { deployer } = await getNamedAccounts()
await deployments.deploy("TestStorage", { args, from: deployer }) await deployments.deploy("Storage", { args, from: deployer })
} }
async function mine256blocks({ network, ethers }) { async function mine256blocks({ network, ethers }) {
@ -32,5 +32,5 @@ module.exports = async (environment) => {
await deployStorage(environment) await deployStorage(environment)
} }
module.exports.tags = ["TestStorage"] module.exports.tags = ["Storage"]
module.exports.dependencies = ["TestToken"] module.exports.dependencies = ["TestToken"]

View File

@ -1,10 +1,16 @@
const { ethers } = require("hardhat") const { ethers } = require("hardhat")
const { expect } = require("chai") const { expect } = require("chai")
const { hexlify, randomBytes, toHexString } = ethers.utils const { hexlify, randomBytes, toHexString } = ethers.utils
const { advanceTimeTo, snapshot, revert, advanceTime } = require("./evm") const {
advanceTimeTo,
snapshot,
revert,
advanceTime,
currentTime,
} = require("./evm")
const { exampleLock } = require("./examples") const { exampleLock } = require("./examples")
const { now, hours } = require("./time") const { hours } = require("./time")
const { waitUntilExpired } = require("./marketplace") const { waitUntilCancelled } = require("./marketplace")
describe("Account Locks", function () { describe("Account Locks", function () {
let locks let locks
@ -16,12 +22,12 @@ describe("Account Locks", function () {
describe("creating a lock", function () { describe("creating a lock", function () {
it("allows creation of a lock with an expiry time", async function () { it("allows creation of a lock with an expiry time", async function () {
let { id, expiry } = exampleLock() let { id, expiry } = await exampleLock()
await locks.createLock(id, expiry) await locks.createLock(id, expiry)
}) })
it("fails to create a lock with an existing id", async function () { it("fails to create a lock with an existing id", async function () {
let { id, expiry } = exampleLock() let { id, expiry } = await exampleLock()
await locks.createLock(id, expiry) await locks.createLock(id, expiry)
await expect(locks.createLock(id, expiry + 1)).to.be.revertedWith( await expect(locks.createLock(id, expiry + 1)).to.be.revertedWith(
"Lock already exists" "Lock already exists"
@ -33,7 +39,7 @@ describe("Account Locks", function () {
let lock let lock
beforeEach(async function () { beforeEach(async function () {
lock = exampleLock() lock = await exampleLock()
await locks.createLock(lock.id, lock.expiry) await locks.createLock(lock.id, lock.expiry)
}) })
@ -44,7 +50,7 @@ describe("Account Locks", function () {
it("fails to lock account when lock does not exist", async function () { it("fails to lock account when lock does not exist", async function () {
let [account] = await ethers.getSigners() let [account] = await ethers.getSigners()
let nonexistent = exampleLock().id let nonexistent = (await exampleLock()).id
await expect(locks.lock(account.address, nonexistent)).to.be.revertedWith( await expect(locks.lock(account.address, nonexistent)).to.be.revertedWith(
"Lock does not exist" "Lock does not exist"
) )
@ -55,7 +61,7 @@ describe("Account Locks", function () {
let lock let lock
beforeEach(async function () { beforeEach(async function () {
lock = exampleLock() lock = await exampleLock()
await locks.createLock(lock.id, lock.expiry) await locks.createLock(lock.id, lock.expiry)
}) })
@ -64,7 +70,7 @@ describe("Account Locks", function () {
}) })
it("fails to unlock a lock that does not exist", async function () { it("fails to unlock a lock that does not exist", async function () {
let nonexistent = exampleLock().id let nonexistent = (await exampleLock()).id
await expect(locks.unlock(nonexistent)).to.be.revertedWith( await expect(locks.unlock(nonexistent)).to.be.revertedWith(
"Lock does not exist" "Lock does not exist"
) )
@ -85,7 +91,7 @@ describe("Account Locks", function () {
it("unlocks an account whose locks have been unlocked", async function () { it("unlocks an account whose locks have been unlocked", async function () {
let [account] = await ethers.getSigners() let [account] = await ethers.getSigners()
let lock = exampleLock() let lock = await exampleLock()
await locks.createLock(lock.id, lock.expiry) await locks.createLock(lock.id, lock.expiry)
await locks.lock(account.address, lock.id) await locks.lock(account.address, lock.id)
await locks.unlock(lock.id) await locks.unlock(lock.id)
@ -94,7 +100,7 @@ describe("Account Locks", function () {
it("unlocks an account whose locks have expired", async function () { it("unlocks an account whose locks have expired", async function () {
let [account] = await ethers.getSigners() let [account] = await ethers.getSigners()
let lock = { ...exampleLock(), expiry: now() } let lock = { ...(await exampleLock()), expiry: currentTime() }
await locks.createLock(lock.id, lock.expiry) await locks.createLock(lock.id, lock.expiry)
await locks.lock(account.address, lock.id) await locks.lock(account.address, lock.id)
await locks.unlockAccount() await locks.unlockAccount()
@ -102,7 +108,7 @@ describe("Account Locks", function () {
it("unlocks multiple accounts tied to the same lock", async function () { it("unlocks multiple accounts tied to the same lock", async function () {
let [account0, account1] = await ethers.getSigners() let [account0, account1] = await ethers.getSigners()
let lock = exampleLock() let lock = await exampleLock()
await locks.createLock(lock.id, lock.expiry) await locks.createLock(lock.id, lock.expiry)
await locks.lock(account0.address, lock.id) await locks.lock(account0.address, lock.id)
await locks.lock(account1.address, lock.id) await locks.lock(account1.address, lock.id)
@ -113,7 +119,7 @@ describe("Account Locks", function () {
it("fails to unlock when some locks are still locked", async function () { it("fails to unlock when some locks are still locked", async function () {
let [account] = await ethers.getSigners() let [account] = await ethers.getSigners()
let [lock1, lock2] = [exampleLock(), exampleLock()] let [lock1, lock2] = [await exampleLock(), await exampleLock()]
await locks.createLock(lock1.id, lock1.expiry) await locks.createLock(lock1.id, lock1.expiry)
await locks.createLock(lock2.id, lock2.expiry) await locks.createLock(lock2.id, lock2.expiry)
await locks.lock(account.address, lock1.id) await locks.lock(account.address, lock1.id)
@ -133,7 +139,7 @@ describe("Account Locks", function () {
}) })
async function addLock() { async function addLock() {
let { id, expiry } = exampleLock() let { id, expiry } = await exampleLock()
await locks.createLock(id, expiry) await locks.createLock(id, expiry)
await locks.lock(account.address, id) await locks.lock(account.address, id)
return id return id
@ -168,15 +174,18 @@ describe("Account Locks", function () {
describe("extend lock expiry", function () { describe("extend lock expiry", function () {
let expiry let expiry
let newExpiry
let id let id
beforeEach(async function () { beforeEach(async function () {
await snapshot() await snapshot()
let lock = exampleLock() let lock = await exampleLock()
id = lock.id id = lock.id
expiry = lock.expiry expiry = lock.expiry
await locks.createLock(id, expiry) await locks.createLock(id, expiry)
newExpiry = (await currentTime()) + hours(1)
let [account] = await ethers.getSigners() let [account] = await ethers.getSigners()
await locks.lock(account.address, id) await locks.lock(account.address, id)
}) })
@ -186,30 +195,21 @@ describe("Account Locks", function () {
}) })
it("fails when lock id doesn't exist", async function () { it("fails when lock id doesn't exist", async function () {
let other = exampleLock() let other = await exampleLock()
await expect( await expect(
locks.extendLockExpiryTo(other.id, now() + hours(1)) locks.extendLockExpiryTo(other.id, newExpiry)
).to.be.revertedWith("Lock does not exist") ).to.be.revertedWith("Lock does not exist")
}) })
it("fails when lock is already expired", async function () { it("fails when lock is already expired", async function () {
waitUntilExpired(expiry) await waitUntilCancelled(expiry)
await expect(locks.extendLockExpiry(id, hours(1))).to.be.revertedWith( await expect(locks.extendLockExpiryTo(id, newExpiry)).to.be.revertedWith(
"Lock already expired" "Lock already expired"
) )
}) })
it("successfully updates lock expiry", async function () { it("successfully updates lock expiry", async function () {
await expect(locks.extendLockExpiryTo(id, now() + hours(1))).not.to.be await expect(locks.extendLockExpiryTo(id, newExpiry)).not.to.be.reverted
.reverted
})
it("unlocks account after expiry", async function () {
await expect(locks.extendLockExpiryTo(id, now() + hours(1))).not.to.be
.reverted
await expect(locks.unlockAccount()).to.be.revertedWith("Account locked")
advanceTime(hours(1))
await expect(locks.unlockAccount()).not.to.be.reverted
}) })
}) })
}) })

View File

@ -97,7 +97,7 @@ describe("Collateral", function () {
beforeEach(async function () { beforeEach(async function () {
await token.approve(collateral.address, 42) await token.approve(collateral.address, 42)
await collateral.deposit(42) await collateral.deposit(42)
lock = exampleLock() lock = await exampleLock()
await collateral.createLock(lock.id, lock.expiry) await collateral.createLock(lock.id, lock.expiry)
await collateral.lock(account0.address, lock.id) await collateral.lock(account0.address, lock.id)
}) })

View File

@ -2,13 +2,14 @@ const { ethers } = require("hardhat")
const { hexlify, randomBytes } = ethers.utils const { hexlify, randomBytes } = ethers.utils
const { expect } = require("chai") const { expect } = require("chai")
const { exampleRequest } = require("./examples") const { exampleRequest } = require("./examples")
const { now, hours, minutes } = require("./time") const { hours, minutes } = require("./time")
const { requestId, slotId, askToArray } = require("./ids") const { requestId, slotId, askToArray } = require("./ids")
const { waitUntilExpired, waitUntilAllSlotsFilled } = require("./marketplace") const {
waitUntilCancelled, waitUntilCancelled,
waitUntilStarted, waitUntilStarted,
waitUntilFinished, waitUntilFinished,
waitUntilFailed, waitUntilFailed,
RequestState
} = require("./marketplace") } = require("./marketplace")
const { price, pricePerSlot } = require("./price") const { price, pricePerSlot } = require("./price")
const { const {
@ -16,7 +17,7 @@ const {
revert, revert,
ensureMinimumBlockHeight, ensureMinimumBlockHeight,
advanceTime, advanceTime,
currentTime currentTime,
} = require("./evm") } = require("./evm")
describe("Marketplace", function () { describe("Marketplace", function () {
@ -53,7 +54,7 @@ describe("Marketplace", function () {
proofDowntime proofDowntime
) )
request = exampleRequest() request = await exampleRequest()
request.client = client.address request.client = client.address
slot = { slot = {
@ -108,80 +109,6 @@ describe("Marketplace", function () {
}) })
}) })
describe("freeing a slot", function () {
var id
beforeEach(async function () {
slot.index = 0
id = slotId(slot)
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("fails to free slot when slot not filled", async function () {
slot.index = 5
let nonExistentId = slotId(slot)
await expect(marketplace.freeSlot(nonExistentId)).to.be.revertedWith(
"Slot empty"
)
})
it("fails to free slot when not started", async function () {
await marketplace.fillSlot(slot.request, slot.index, proof)
await expect(marketplace.freeSlot(id)).to.be.revertedWith("Invalid state")
})
it("fails to free slot when finished", async function () {
await waitUntilStarted(
marketplace,
request.ask.slots,
slot.request,
proof
)
await waitUntilFinished(marketplace, slotId(slot))
await expect(marketplace.freeSlot(id)).to.be.revertedWith(
"Request finished"
)
})
it("successfully frees slot", async function () {
await waitUntilStarted(
marketplace,
request.ask.slots,
slot.request,
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 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 marketplace.freeSlot(id)
await expect(marketplace.slot(id)).to.be.revertedWith("Slot empty")
})
})
describe("filling a slot", function () { describe("filling a slot", function () {
beforeEach(async function () { beforeEach(async function () {
switchAccount(client) switchAccount(client)
@ -233,7 +160,7 @@ describe("Marketplace", function () {
}) })
it("is rejected when request is unknown", async function () { it("is rejected when request is unknown", async function () {
let unknown = exampleRequest() let unknown = await exampleRequest()
await expect( await expect(
marketplace.fillSlot(requestId(unknown), 0, proof) marketplace.fillSlot(requestId(unknown), 0, proof)
).to.be.revertedWith("Unknown request") ).to.be.revertedWith("Unknown request")
@ -241,7 +168,7 @@ describe("Marketplace", function () {
it("is rejected when request is cancelled", async function () { it("is rejected when request is cancelled", async function () {
switchAccount(client) switchAccount(client)
let expired = { ...request, expiry: now() - hours(1) } let expired = { ...request, expiry: (await currentTime()) - hours(1) }
await token.approve(marketplace.address, price(request)) await token.approve(marketplace.address, price(request))
await marketplace.requestStorage(expired) await marketplace.requestStorage(expired)
switchAccount(host) switchAccount(host)
@ -260,7 +187,7 @@ describe("Marketplace", function () {
await waitUntilFinished(marketplace, slotId(slot)) await waitUntilFinished(marketplace, slotId(slot))
await expect( await expect(
marketplace.fillSlot(slot.request, slot.index, proof) marketplace.fillSlot(slot.request, slot.index, proof)
).to.be.revertedWith("Request finished") ).to.be.revertedWith("Request not accepting proofs")
}) })
it("is rejected when request is failed", async function () { it("is rejected when request is failed", async function () {
@ -273,7 +200,7 @@ describe("Marketplace", function () {
await waitUntilFailed(marketplace, slot, request.ask.maxSlotLoss) await waitUntilFailed(marketplace, slot, request.ask.maxSlotLoss)
await expect( await expect(
marketplace.fillSlot(slot.request, slot.index, proof) marketplace.fillSlot(slot.request, slot.index, proof)
).to.be.revertedWith("Invalid state") ).to.be.revertedWith("Request not accepting proofs")
}) })
it("is rejected when slot index not in range", async function () { it("is rejected when slot index not in range", async function () {
@ -330,8 +257,21 @@ describe("Marketplace", function () {
) )
}) })
it("fails to free slot when finished", async function () {
await waitUntilStarted(
marketplace,
request.ask.slots,
slot.request,
proof
)
await waitUntilFinished(marketplace, slotId(slot))
await expect(marketplace.freeSlot(id)).to.be.revertedWith(
"Slot not accepting proofs"
)
})
it("successfully frees slot", async function () { it("successfully frees slot", async function () {
await waitUntilAllSlotsFilled( await waitUntilStarted(
marketplace, marketplace,
request.ask.slots, request.ask.slots,
slot.request, slot.request,
@ -341,7 +281,7 @@ describe("Marketplace", function () {
}) })
it("emits event once slot is freed", async function () { it("emits event once slot is freed", async function () {
await waitUntilAllSlotsFilled( await waitUntilStarted(
marketplace, marketplace,
request.ask.slots, request.ask.slots,
slot.request, slot.request,
@ -353,7 +293,7 @@ describe("Marketplace", function () {
}) })
it("cannot get slot once freed", async function () { it("cannot get slot once freed", async function () {
await waitUntilAllSlotsFilled( await waitUntilStarted(
marketplace, marketplace,
request.ask.slots, request.ask.slots,
slot.request, slot.request,
@ -388,12 +328,6 @@ describe("Marketplace", function () {
expect(endBalance - startBalance).to.equal(pricePerSlot(request)) expect(endBalance - startBalance).to.equal(pricePerSlot(request))
}) })
it("is only allowed when the contract is finished", async function () {
await expect(
marketplace.payoutSlot(slot.request, slot.index)
).to.be.revertedWith("Contract not ended")
})
it("is only allowed when the contract has ended", async function () { it("is only allowed when the contract has ended", async function () {
await marketplace.fillSlot(slot.request, slot.index, proof) await marketplace.fillSlot(slot.request, slot.index, proof)
await expect( await expect(
@ -454,6 +388,8 @@ describe("Marketplace", function () {
await marketplace.fillSlot(slot.request, i, proof) await marketplace.fillSlot(slot.request, i, proof)
} }
await expect(await marketplace.state(slot.request)).to.equal( await expect(await marketplace.state(slot.request)).to.equal(
RequestState.Started
)
}) })
it("fails when all slots are already filled", async function () { it("fails when all slots are already filled", async function () {
const lastSlot = request.ask.slots - 1 const lastSlot = request.ask.slots - 1
@ -531,7 +467,7 @@ describe("Marketplace", function () {
await marketplace.deposit(collateral) await marketplace.deposit(collateral)
}) })
it("state is Cancelled when client withdraws funds", async function () { it("changes state to Cancelled when client withdraws funds", async function () {
await expect(await marketplace.state(slot.request)).to.equal( await expect(await marketplace.state(slot.request)).to.equal(
RequestState.New RequestState.New
) )
@ -599,7 +535,7 @@ describe("Marketplace", function () {
}) })
it("changes state to Cancelled once request is cancelled", async function () { it("changes state to Cancelled once request is cancelled", async function () {
await waitUntilExpired(request.expiry) await waitUntilCancelled(request.expiry)
await expect(await marketplace.state(slot.request)).to.equal( await expect(await marketplace.state(slot.request)).to.equal(
RequestState.Cancelled RequestState.Cancelled
) )
@ -607,7 +543,7 @@ describe("Marketplace", function () {
it("changes isCancelled to true once request is cancelled", async function () { it("changes isCancelled to true once request is cancelled", async function () {
await expect(await marketplace.isCancelled(slot.request)).to.be.false await expect(await marketplace.isCancelled(slot.request)).to.be.false
await waitUntilExpired(request.expiry) await waitUntilCancelled(request.expiry)
await expect(await marketplace.isCancelled(slot.request)).to.be.true await expect(await marketplace.isCancelled(slot.request)).to.be.true
}) })
@ -620,7 +556,7 @@ describe("Marketplace", function () {
it("changes isSlotCancelled to true once request is cancelled", async function () { it("changes isSlotCancelled to true once request is cancelled", async function () {
await marketplace.fillSlot(slot.request, slot.index, proof) await marketplace.fillSlot(slot.request, slot.index, proof)
await expect(await marketplace.isSlotCancelled(slotId(slot))).to.be.false await expect(await marketplace.isSlotCancelled(slotId(slot))).to.be.false
await waitUntilExpired(request.expiry) await waitUntilCancelled(request.expiry)
await expect(await marketplace.isSlotCancelled(slotId(slot))).to.be.true await expect(await marketplace.isSlotCancelled(slotId(slot))).to.be.true
}) })
@ -629,7 +565,7 @@ describe("Marketplace", function () {
await expect(await marketplace.proofEnd(slotId(slot))).to.be.gt( await expect(await marketplace.proofEnd(slotId(slot))).to.be.gt(
await currentTime() await currentTime()
) )
await waitUntilExpired(request.expiry) await waitUntilCancelled(request.expiry)
await expect(await marketplace.proofEnd(slotId(slot))).to.be.lt( await expect(await marketplace.proofEnd(slotId(slot))).to.be.lt(
await currentTime() await currentTime()
) )
@ -651,7 +587,7 @@ describe("Marketplace", function () {
await waitUntilCancelled(request.expiry) await waitUntilCancelled(request.expiry)
await expect( await expect(
marketplace.testAcceptsProofs(slotId(slot)) marketplace.testAcceptsProofs(slotId(slot))
).to.be.revertedWith("Request cancelled") ).to.be.revertedWith("Slot not accepting proofs")
}) })
it("fails when request Cancelled (state set to Cancelled)", async function () { it("fails when request Cancelled (state set to Cancelled)", async function () {
@ -661,7 +597,7 @@ describe("Marketplace", function () {
await marketplace.withdrawFunds(slot.request) await marketplace.withdrawFunds(slot.request)
await expect( await expect(
marketplace.testAcceptsProofs(slotId(slot)) marketplace.testAcceptsProofs(slotId(slot))
).to.be.revertedWith("Invalid state") ).to.be.revertedWith("Slot not accepting proofs")
}) })
it("fails when request Finished (isFinished is true)", async function () { it("fails when request Finished (isFinished is true)", async function () {
@ -674,7 +610,7 @@ describe("Marketplace", function () {
await waitUntilFinished(marketplace, slotId(slot)) await waitUntilFinished(marketplace, slotId(slot))
await expect( await expect(
marketplace.testAcceptsProofs(slotId(slot)) marketplace.testAcceptsProofs(slotId(slot))
).to.be.revertedWith("Request finished") ).to.be.revertedWith("Slot not accepting proofs")
}) })
it("fails when request Finished (state set to Finished)", async function () { it("fails when request Finished (state set to Finished)", async function () {
@ -688,7 +624,7 @@ describe("Marketplace", function () {
await marketplace.payoutSlot(slot.request, slot.index) await marketplace.payoutSlot(slot.request, slot.index)
await expect( await expect(
marketplace.testAcceptsProofs(slotId(slot)) marketplace.testAcceptsProofs(slotId(slot))
).to.be.revertedWith("Invalid state") ).to.be.revertedWith("Slot not accepting proofs")
}) })
it("fails when request Failed", async function () { it("fails when request Failed", async function () {
@ -708,6 +644,5 @@ describe("Marketplace", function () {
).to.be.revertedWith("Slot empty") ).to.be.revertedWith("Slot empty")
}) })
}) })
}) })
}) })

View File

@ -10,7 +10,7 @@ const {
advanceTime, advanceTime,
advanceTimeTo, advanceTimeTo,
} = require("./evm") } = require("./evm")
const { periodic, hours, now, minutes } = require("./time") const { periodic, hours, minutes } = require("./time")
describe("Proofs", function () { describe("Proofs", function () {
const id = hexlify(randomBytes(32)) const id = hexlify(randomBytes(32))

View File

@ -32,9 +32,9 @@ describe("Storage", function () {
beforeEach(async function () { beforeEach(async function () {
;[client, host] = await ethers.getSigners() ;[client, host] = await ethers.getSigners()
await deployments.fixture(["TestToken", "TestStorage"]) await deployments.fixture(["TestToken", "Storage"])
token = await ethers.getContract("TestToken") token = await ethers.getContract("TestToken")
storage = await ethers.getContract("TestStorage") storage = await ethers.getContract("Storage")
await token.mint(client.address, 1_000_000_000) await token.mint(client.address, 1_000_000_000)
await token.mint(host.address, 1_000_000_000) await token.mint(host.address, 1_000_000_000)
@ -44,7 +44,7 @@ describe("Storage", function () {
slashPercentage = await storage.slashPercentage() slashPercentage = await storage.slashPercentage()
minCollateralThreshold = await storage.minCollateralThreshold() minCollateralThreshold = await storage.minCollateralThreshold()
request = exampleRequest() request = await exampleRequest()
request.client = client.address request.client = client.address
slot = { slot = {
request: requestId(request), request: requestId(request),
@ -121,12 +121,7 @@ describe("Storage", function () {
it("frees slot when collateral slashed below minimum threshold", async function () { it("frees slot when collateral slashed below minimum threshold", async function () {
const id = slotId(slot) const id = slotId(slot)
await waitUntilAllSlotsFilled( await waitUntilStarted(storage, request.ask.slots, slot.request, proof)
storage,
request.ask.slots,
slot.request,
proof
)
// max slashes before dropping below collateral threshold // max slashes before dropping below collateral threshold
const maxSlashes = 10 const maxSlashes = 10
@ -192,7 +187,6 @@ describe("Storage", function () {
await advanceTimeTo(request.expiry + 1) await advanceTimeTo(request.expiry + 1)
await expect(await storage.willProofBeRequired(id)).to.be.false await expect(await storage.willProofBeRequired(id)).to.be.false
}) })
it("does not require proofs once cancelled", async function () { it("does not require proofs once cancelled", async function () {
const id = slotId(slot) const id = slotId(slot)
@ -225,89 +219,6 @@ describe("Storage", function () {
expect(BigNumber.from(challenge2).isZero()) expect(BigNumber.from(challenge2).isZero())
}) })
}) })
describe("freeing a slot", function () {
beforeEach(async function () {
period = (await storage.proofPeriod()).toNumber()
;({ periodOf, periodEnd } = periodic(period))
})
async function waitUntilProofIsRequired(id) {
await advanceTimeTo(periodEnd(periodOf(await currentTime())))
while (
!(
(await storage.isProofRequired(id)) &&
(await storage.getPointer(id)) < 250
)
) {
await advanceTime(period)
}
}
async function markProofAsMissing(slotId, onMarkAsMissing) {
for (let i = 0; i < slashMisses; i++) {
await waitUntilProofIsRequired(slotId)
let missedPeriod = periodOf(await currentTime())
await advanceTime(period)
if (i === slashMisses - 1 && typeof onMarkAsMissing === "function") {
onMarkAsMissing(missedPeriod)
} else await storage.markProofAsMissing(slotId, missedPeriod)
}
}
it("frees slot when collateral slashed below minimum threshold", async function () {
const id = slotId(slot)
await waitUntilStarted(storage, request.ask.slots, slot.request, proof)
while (true) {
await markProofAsMissing(id)
let balance = await storage.balanceOf(host.address)
let slashAmount = await storage.slashAmount(
host.address,
slashPercentage
)
if (balance - slashAmount < minCollateralThreshold) {
break
}
}
let onMarkAsMissing = async function (missedPeriod) {
await expect(
await storage.markProofAsMissing(id, missedPeriod)
).to.emit(storage, "SlotFreed")
}
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 waitUntilCancelled(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 waitUntilCancelled(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 waitUntilStarted(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 // TODO: implement checking of actual proofs of storage, instead of dummy bool

View File

@ -1,35 +1,41 @@
const { ethers } = require("hardhat") const { ethers } = require("hardhat")
const { now, hours } = require("./time") const { hours } = require("./time")
const { currentTime } = require("./evm")
const { hexlify, randomBytes } = ethers.utils const { hexlify, randomBytes } = ethers.utils
const exampleRequest = () => ({ const exampleRequest = async () => {
client: hexlify(randomBytes(20)), const now = await currentTime()
ask: { return {
slots: 4, client: hexlify(randomBytes(20)),
slotSize: 1 * 1024 * 1024 * 1024, // 1 Gigabyte ask: {
duration: hours(10), slots: 4,
proofProbability: 4, // require a proof roughly once every 4 periods slotSize: 1 * 1024 * 1024 * 1024, // 1 Gigabyte
reward: 84, duration: hours(10),
maxSlotLoss: 2, proofProbability: 4, // require a proof roughly once every 4 periods
}, reward: 84,
content: { maxSlotLoss: 2,
cid: "zb2rhheVmk3bLks5MgzTqyznLu1zqGH5jrfTA1eAZXrjx7Vob",
erasure: {
totalChunks: 12,
}, },
por: { content: {
u: Array.from(randomBytes(480)), cid: "zb2rhheVmk3bLks5MgzTqyznLu1zqGH5jrfTA1eAZXrjx7Vob",
publicKey: Array.from(randomBytes(96)), erasure: {
name: Array.from(randomBytes(512)), totalChunks: 12,
},
por: {
u: Array.from(randomBytes(480)),
publicKey: Array.from(randomBytes(96)),
name: Array.from(randomBytes(512)),
},
}, },
}, expiry: now + hours(1),
expiry: now() + hours(1), nonce: hexlify(randomBytes(32)),
nonce: hexlify(randomBytes(32)), }
}) }
const exampleLock = async () => {
const exampleLock = () => ({ const now = await currentTime()
id: hexlify(randomBytes(32)), return {
expiry: now() + hours(1), id: hexlify(randomBytes(32)),
}) expiry: now + hours(1),
}
}
module.exports = { exampleRequest, exampleLock } module.exports = { exampleRequest, exampleLock }

View File

@ -25,9 +25,18 @@ async function waitUntilFailed(contract, slot, maxSlotLoss) {
} }
} }
const RequestState = {
New: 0,
Started: 1,
Cancelled: 2,
Finished: 3,
Failed: 4,
}
module.exports = { module.exports = {
waitUntilCancelled, waitUntilCancelled,
waitUntilStarted, waitUntilStarted,
waitUntilFinished, waitUntilFinished,
waitUntilFailed, waitUntilFailed,
RequestState,
} }

View File

@ -1,4 +1,3 @@
const now = () => Math.round(Date.now() / 1000)
const hours = (amount) => amount * minutes(60) const hours = (amount) => amount * minutes(60)
const minutes = (amount) => amount * seconds(60) const minutes = (amount) => amount * seconds(60)
const seconds = (amount) => amount const seconds = (amount) => amount
@ -9,4 +8,4 @@ const periodic = (length) => ({
periodEnd: (period) => (period + 1) * length, periodEnd: (period) => (period + 1) * length,
}) })
module.exports = { now, hours, minutes, seconds, periodic } module.exports = { hours, minutes, seconds, periodic }