[marketplace] remove `offer`, `select` and `startContract`
Contract is started when first proof is submitted.
This commit is contained in:
parent
f3b969fd7c
commit
8d7b7aed1d
|
@ -10,7 +10,6 @@ contract Marketplace is Collateral, Proofs {
|
||||||
MarketplaceFunds private funds;
|
MarketplaceFunds private funds;
|
||||||
mapping(bytes32 => Request) private requests;
|
mapping(bytes32 => Request) private requests;
|
||||||
mapping(bytes32 => RequestState) private requestState;
|
mapping(bytes32 => RequestState) private requestState;
|
||||||
mapping(bytes32 => Offer) private offers;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
IERC20 _token,
|
IERC20 _token,
|
||||||
|
@ -51,7 +50,7 @@ contract Marketplace is Collateral, Proofs {
|
||||||
marketplaceInvariant
|
marketplaceInvariant
|
||||||
{
|
{
|
||||||
RequestState storage state = requestState[requestId];
|
RequestState storage state = requestState[requestId];
|
||||||
require(!state.fulfilled, "Request already fulfilled");
|
require(state.host == address(0), "Request already fulfilled");
|
||||||
|
|
||||||
Request storage request = requests[requestId];
|
Request storage request = requests[requestId];
|
||||||
require(request.client != address(0), "Unknown request");
|
require(request.client != address(0), "Unknown request");
|
||||||
|
@ -67,67 +66,18 @@ contract Marketplace is Collateral, Proofs {
|
||||||
);
|
);
|
||||||
_submitProof(requestId, proof);
|
_submitProof(requestId, proof);
|
||||||
|
|
||||||
state.fulfilled = true;
|
state.host = msg.sender;
|
||||||
emit RequestFulfilled(requestId);
|
emit RequestFulfilled(requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function offerStorage(Offer calldata offer) public marketplaceInvariant {
|
function _host(bytes32 requestId) internal view returns (address) {
|
||||||
require(offer.host == msg.sender, "Invalid host address");
|
return requestState[requestId].host;
|
||||||
require(balanceOf(msg.sender) >= collateral, "Insufficient collateral");
|
|
||||||
|
|
||||||
Request storage request = requests[offer.requestId];
|
|
||||||
require(request.client != address(0), "Unknown request");
|
|
||||||
require(request.expiry > block.timestamp, "Request expired");
|
|
||||||
|
|
||||||
require(offer.price <= request.ask.maxPrice, "Price too high");
|
|
||||||
|
|
||||||
bytes32 id = keccak256(abi.encode(offer));
|
|
||||||
require(offers[id].host == address(0), "Offer already exists");
|
|
||||||
|
|
||||||
offers[id] = offer;
|
|
||||||
|
|
||||||
_lock(msg.sender, offer.requestId);
|
|
||||||
|
|
||||||
emit StorageOffered(id, offer, offer.requestId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectOffer(bytes32 id) public marketplaceInvariant {
|
|
||||||
Offer storage offer = offers[id];
|
|
||||||
require(offer.host != address(0), "Unknown offer");
|
|
||||||
require(offer.expiry > block.timestamp, "Offer expired");
|
|
||||||
|
|
||||||
Request storage request = requests[offer.requestId];
|
|
||||||
require(request.client == msg.sender, "Only client can select offer");
|
|
||||||
|
|
||||||
RequestState storage state = requestState[offer.requestId];
|
|
||||||
require(state.selectedOffer == bytes32(0), "Offer already selected");
|
|
||||||
|
|
||||||
state.selectedOffer = id;
|
|
||||||
|
|
||||||
_createLock(id, offer.expiry);
|
|
||||||
_lock(offer.host, id);
|
|
||||||
_unlock(offer.requestId);
|
|
||||||
|
|
||||||
uint256 difference = request.ask.maxPrice - offer.price;
|
|
||||||
funds.sent += difference;
|
|
||||||
funds.balance -= difference;
|
|
||||||
token.transfer(request.client, difference);
|
|
||||||
|
|
||||||
emit OfferSelected(id, offer.requestId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _request(bytes32 id) internal view returns (Request storage) {
|
function _request(bytes32 id) internal view returns (Request storage) {
|
||||||
return requests[id];
|
return requests[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
function _offer(bytes32 id) internal view returns (Offer storage) {
|
|
||||||
return offers[id];
|
|
||||||
}
|
|
||||||
|
|
||||||
function _selectedOffer(bytes32 requestId) internal view returns (bytes32) {
|
|
||||||
return requestState[requestId].selectedOffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
function proofPeriod() public view returns (uint256) {
|
function proofPeriod() public view returns (uint256) {
|
||||||
return _period();
|
return _period();
|
||||||
}
|
}
|
||||||
|
@ -174,21 +124,11 @@ contract Marketplace is Collateral, Proofs {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RequestState {
|
struct RequestState {
|
||||||
bool fulfilled;
|
|
||||||
bytes32 selectedOffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Offer {
|
|
||||||
address host;
|
address host;
|
||||||
bytes32 requestId;
|
|
||||||
uint256 price;
|
|
||||||
uint256 expiry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
event StorageRequested(bytes32 requestId, Ask ask);
|
event StorageRequested(bytes32 requestId, Ask ask);
|
||||||
event RequestFulfilled(bytes32 indexed requestId);
|
event RequestFulfilled(bytes32 indexed requestId);
|
||||||
event StorageOffered(bytes32 offerId, Offer offer, bytes32 indexed requestId);
|
|
||||||
event OfferSelected(bytes32 offerId, bytes32 indexed requestId);
|
|
||||||
|
|
||||||
modifier marketplaceInvariant() {
|
modifier marketplaceInvariant() {
|
||||||
MarketplaceFunds memory oldFunds = funds;
|
MarketplaceFunds memory oldFunds = funds;
|
||||||
|
|
|
@ -10,7 +10,7 @@ contract Storage is Collateral, Marketplace {
|
||||||
uint256 public slashMisses;
|
uint256 public slashMisses;
|
||||||
uint256 public slashPercentage;
|
uint256 public slashPercentage;
|
||||||
|
|
||||||
mapping(bytes32 => ContractState) private contractState;
|
mapping(bytes32 => bool) private contractFinished;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
IERC20 token,
|
IERC20 token,
|
||||||
|
@ -38,25 +38,15 @@ contract Storage is Collateral, Marketplace {
|
||||||
return _request(id);
|
return _request(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOffer(bytes32 id) public view returns (Offer memory) {
|
|
||||||
return _offer(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function startContract(bytes32 id) public {
|
|
||||||
Offer storage offer = _offer(id);
|
|
||||||
require(msg.sender == offer.host, "Only host can call this function");
|
|
||||||
require(_selectedOffer(offer.requestId) == id, "Offer was not selected");
|
|
||||||
contractState[id] = ContractState.started;
|
|
||||||
Request storage request = _request(offer.requestId);
|
|
||||||
_expectProofs(id, request.ask.proofProbability, request.ask.duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
function finishContract(bytes32 id) public {
|
function finishContract(bytes32 id) public {
|
||||||
require(contractState[id] == ContractState.started, "Contract not started");
|
require(_host(id) != address(0), "Contract not started");
|
||||||
|
require(!contractFinished[id], "Contract already finished");
|
||||||
require(block.timestamp > proofEnd(id), "Contract has not ended yet");
|
require(block.timestamp > proofEnd(id), "Contract has not ended yet");
|
||||||
contractState[id] = ContractState.finished;
|
contractFinished[id] = true;
|
||||||
Offer storage offer = _offer(id);
|
require(
|
||||||
require(token.transfer(offer.host, offer.price), "Payment failed");
|
token.transfer(_host(id), _request(id).ask.maxPrice),
|
||||||
|
"Payment failed"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function missingProofs(bytes32 contractId) public view returns (uint256) {
|
function missingProofs(bytes32 contractId) public view returns (uint256) {
|
||||||
|
@ -86,14 +76,7 @@ contract Storage is Collateral, Marketplace {
|
||||||
function markProofAsMissing(bytes32 contractId, uint256 period) public {
|
function markProofAsMissing(bytes32 contractId, uint256 period) public {
|
||||||
_markProofAsMissing(contractId, period);
|
_markProofAsMissing(contractId, period);
|
||||||
if (_missed(contractId) % slashMisses == 0) {
|
if (_missed(contractId) % slashMisses == 0) {
|
||||||
Offer storage offer = _offer(contractId);
|
_slash(_host(contractId), slashPercentage);
|
||||||
_slash(offer.host, slashPercentage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ContractState {
|
|
||||||
none,
|
|
||||||
started,
|
|
||||||
finished
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
const { ethers } = require("hardhat")
|
const { ethers } = require("hardhat")
|
||||||
const { hexlify, randomBytes } = ethers.utils
|
const { hexlify, randomBytes } = ethers.utils
|
||||||
const { expect } = require("chai")
|
const { expect } = require("chai")
|
||||||
const { exampleRequest, exampleOffer } = require("./examples")
|
const { exampleRequest } = require("./examples")
|
||||||
const { snapshot, revert, ensureMinimumBlockHeight } = require("./evm")
|
const { snapshot, revert, ensureMinimumBlockHeight } = require("./evm")
|
||||||
const { now, hours } = require("./time")
|
const { now, hours } = require("./time")
|
||||||
const { requestId, offerId, offerToArray, askToArray } = require("./ids")
|
const { requestId, askToArray } = require("./ids")
|
||||||
|
|
||||||
describe("Marketplace", function () {
|
describe("Marketplace", function () {
|
||||||
const collateral = 100
|
const collateral = 100
|
||||||
|
@ -15,7 +15,7 @@ describe("Marketplace", function () {
|
||||||
let marketplace
|
let marketplace
|
||||||
let token
|
let token
|
||||||
let client, host, host1, host2, host3
|
let client, host, host1, host2, host3
|
||||||
let request, offer
|
let request
|
||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
await snapshot()
|
await snapshot()
|
||||||
|
@ -40,10 +40,6 @@ describe("Marketplace", function () {
|
||||||
|
|
||||||
request = exampleRequest()
|
request = exampleRequest()
|
||||||
request.client = client.address
|
request.client = client.address
|
||||||
|
|
||||||
offer = exampleOffer()
|
|
||||||
offer.host = host.address
|
|
||||||
offer.requestId = requestId(request)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async function () {
|
afterEach(async function () {
|
||||||
|
@ -162,153 +158,4 @@ describe("Marketplace", function () {
|
||||||
).to.be.revertedWith("Request expired")
|
).to.be.revertedWith("Request expired")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("offering storage", function () {
|
|
||||||
beforeEach(async function () {
|
|
||||||
switchAccount(client)
|
|
||||||
await token.approve(marketplace.address, request.ask.maxPrice)
|
|
||||||
await marketplace.requestStorage(request)
|
|
||||||
switchAccount(host)
|
|
||||||
await token.approve(marketplace.address, collateral)
|
|
||||||
await marketplace.deposit(collateral)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("emits event when storage is offered", async function () {
|
|
||||||
await expect(marketplace.offerStorage(offer))
|
|
||||||
.to.emit(marketplace, "StorageOffered")
|
|
||||||
.withArgs(offerId(offer), offerToArray(offer), requestId(request))
|
|
||||||
})
|
|
||||||
|
|
||||||
it("locks collateral of host", async function () {
|
|
||||||
await marketplace.offerStorage(offer)
|
|
||||||
await expect(marketplace.withdraw()).to.be.revertedWith("Account locked")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("rejects offer with invalid host address", async function () {
|
|
||||||
let invalid = { ...offer, host: client.address }
|
|
||||||
await expect(marketplace.offerStorage(invalid)).to.be.revertedWith(
|
|
||||||
"Invalid host address"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("rejects offer for unknown request", async function () {
|
|
||||||
let unknown = exampleRequest()
|
|
||||||
let invalid = { ...offer, requestId: requestId(unknown) }
|
|
||||||
await expect(marketplace.offerStorage(invalid)).to.be.revertedWith(
|
|
||||||
"Unknown request"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("rejects offer for expired request", async function () {
|
|
||||||
switchAccount(client)
|
|
||||||
let expired = { ...request, expiry: now() - hours(1) }
|
|
||||||
await token.approve(marketplace.address, request.ask.maxPrice)
|
|
||||||
await marketplace.requestStorage(expired)
|
|
||||||
switchAccount(host)
|
|
||||||
let invalid = { ...offer, requestId: requestId(expired) }
|
|
||||||
await expect(marketplace.offerStorage(invalid)).to.be.revertedWith(
|
|
||||||
"Request expired"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("rejects an offer that exceeds the maximum price", async function () {
|
|
||||||
let invalid = { ...offer, price: request.ask.maxPrice + 1 }
|
|
||||||
await expect(marketplace.offerStorage(invalid)).to.be.revertedWith(
|
|
||||||
"Price too high"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("rejects resubmission of offer", async function () {
|
|
||||||
await marketplace.offerStorage(offer)
|
|
||||||
await expect(marketplace.offerStorage(offer)).to.be.revertedWith(
|
|
||||||
"Offer already exists"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("rejects offer with insufficient collateral", async function () {
|
|
||||||
let insufficient = collateral - 1
|
|
||||||
await marketplace.withdraw()
|
|
||||||
await token.approve(marketplace.address, insufficient)
|
|
||||||
await marketplace.deposit(insufficient)
|
|
||||||
await expect(marketplace.offerStorage(offer)).to.be.revertedWith(
|
|
||||||
"Insufficient collateral"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("selecting an offer", function () {
|
|
||||||
beforeEach(async function () {
|
|
||||||
switchAccount(client)
|
|
||||||
await token.approve(marketplace.address, request.ask.maxPrice)
|
|
||||||
await marketplace.requestStorage(request)
|
|
||||||
for (host of [host1, host2, host3]) {
|
|
||||||
switchAccount(host)
|
|
||||||
let hostOffer = { ...offer, host: host.address }
|
|
||||||
await token.approve(marketplace.address, collateral)
|
|
||||||
await marketplace.deposit(collateral)
|
|
||||||
await marketplace.offerStorage(hostOffer)
|
|
||||||
}
|
|
||||||
switchAccount(client)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("emits event when offer is selected", async function () {
|
|
||||||
await expect(marketplace.selectOffer(offerId(offer)))
|
|
||||||
.to.emit(marketplace, "OfferSelected")
|
|
||||||
.withArgs(offerId(offer), requestId(request))
|
|
||||||
})
|
|
||||||
|
|
||||||
it("returns price difference to client", async function () {
|
|
||||||
let difference = request.ask.maxPrice - offer.price
|
|
||||||
let before = await token.balanceOf(client.address)
|
|
||||||
await marketplace.selectOffer(offerId(offer))
|
|
||||||
let after = await token.balanceOf(client.address)
|
|
||||||
expect(after - before).to.equal(difference)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("unlocks collateral of hosts that weren't chosen", async function () {
|
|
||||||
await marketplace.selectOffer(offerId(offer))
|
|
||||||
switchAccount(host2)
|
|
||||||
await expect(marketplace.withdraw()).not.to.be.reverted
|
|
||||||
switchAccount(host3)
|
|
||||||
await expect(marketplace.withdraw()).not.to.be.reverted
|
|
||||||
})
|
|
||||||
|
|
||||||
it("locks collateral of host that was chosen", async function () {
|
|
||||||
await marketplace.selectOffer(offerId(offer))
|
|
||||||
switchAccount(host1)
|
|
||||||
await expect(marketplace.withdraw()).to.be.revertedWith("Account locked")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("rejects selection of unknown offer", async function () {
|
|
||||||
let unknown = exampleOffer()
|
|
||||||
await expect(
|
|
||||||
marketplace.selectOffer(offerId(unknown))
|
|
||||||
).to.be.revertedWith("Unknown offer")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("rejects selection of expired offer", async function () {
|
|
||||||
let expired = { ...offer, expiry: now() - hours(1) }
|
|
||||||
switchAccount(host1)
|
|
||||||
await marketplace.offerStorage(expired)
|
|
||||||
switchAccount(client)
|
|
||||||
await expect(
|
|
||||||
marketplace.selectOffer(offerId(expired))
|
|
||||||
).to.be.revertedWith("Offer expired")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("rejects reselection of offer", async function () {
|
|
||||||
let secondOffer = { ...offer, host: host2.address }
|
|
||||||
await marketplace.selectOffer(offerId(offer))
|
|
||||||
await expect(
|
|
||||||
marketplace.selectOffer(offerId(secondOffer))
|
|
||||||
).to.be.revertedWith("Offer already selected")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("rejects selection by anyone other than the client", async function () {
|
|
||||||
switchAccount(host1)
|
|
||||||
await expect(marketplace.selectOffer(offerId(offer))).to.be.revertedWith(
|
|
||||||
"Only client can select offer"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
const { expect } = require("chai")
|
const { expect } = require("chai")
|
||||||
const { ethers, deployments } = require("hardhat")
|
const { ethers, deployments } = require("hardhat")
|
||||||
const { exampleRequest, exampleOffer } = require("./examples")
|
const { hexlify, randomBytes } = ethers.utils
|
||||||
|
const { exampleRequest } = require("./examples")
|
||||||
const { advanceTime, advanceTimeTo, currentTime } = require("./evm")
|
const { advanceTime, advanceTimeTo, currentTime } = require("./evm")
|
||||||
const { requestId, offerId } = require("./ids")
|
const { requestId } = require("./ids")
|
||||||
const { periodic } = require("./time")
|
const { periodic } = require("./time")
|
||||||
|
|
||||||
describe("Storage", function () {
|
describe("Storage", function () {
|
||||||
|
const proof = hexlify(randomBytes(42))
|
||||||
|
|
||||||
let storage
|
let storage
|
||||||
let token
|
let token
|
||||||
let client, host
|
let client, host
|
||||||
let request, offer
|
let request
|
||||||
let collateralAmount, slashMisses, slashPercentage
|
let collateralAmount, slashMisses, slashPercentage
|
||||||
let id
|
let id
|
||||||
|
|
||||||
|
@ -34,10 +37,7 @@ describe("Storage", function () {
|
||||||
|
|
||||||
request = exampleRequest()
|
request = exampleRequest()
|
||||||
request.client = client.address
|
request.client = client.address
|
||||||
|
id = requestId(request)
|
||||||
offer = exampleOffer()
|
|
||||||
offer.host = host.address
|
|
||||||
offer.requestId = requestId(request)
|
|
||||||
|
|
||||||
switchAccount(client)
|
switchAccount(client)
|
||||||
await token.approve(storage.address, request.ask.maxPrice)
|
await token.approve(storage.address, request.ask.maxPrice)
|
||||||
|
@ -45,10 +45,6 @@ describe("Storage", function () {
|
||||||
switchAccount(host)
|
switchAccount(host)
|
||||||
await token.approve(storage.address, collateralAmount)
|
await token.approve(storage.address, collateralAmount)
|
||||||
await storage.deposit(collateralAmount)
|
await storage.deposit(collateralAmount)
|
||||||
await storage.offerStorage(offer)
|
|
||||||
switchAccount(client)
|
|
||||||
await storage.selectOffer(offerId(offer))
|
|
||||||
id = offerId(offer)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("can retrieve storage requests", async function () {
|
it("can retrieve storage requests", async function () {
|
||||||
|
@ -59,69 +55,26 @@ describe("Storage", function () {
|
||||||
expect(retrieved.nonce).to.equal(request.nonce)
|
expect(retrieved.nonce).to.equal(request.nonce)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("can retrieve storage offers", async function () {
|
|
||||||
const id = offerId(offer)
|
|
||||||
const retrieved = await storage.getOffer(id)
|
|
||||||
expect(retrieved.requestId).to.equal(offer.requestId)
|
|
||||||
expect(retrieved.host).to.equal(offer.host)
|
|
||||||
expect(retrieved.price).to.equal(offer.price)
|
|
||||||
expect(retrieved.expiry).to.equal(offer.expiry)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("starting the contract", function () {
|
|
||||||
it("starts requiring storage proofs", async function () {
|
|
||||||
switchAccount(host)
|
|
||||||
await storage.startContract(id)
|
|
||||||
expect(await storage.proofEnd(id)).to.be.gt(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("can only be done by the host", async function () {
|
|
||||||
switchAccount(client)
|
|
||||||
await expect(storage.startContract(id)).to.be.revertedWith(
|
|
||||||
"Only host can call this function"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("can only be done once", async function () {
|
|
||||||
switchAccount(host)
|
|
||||||
await storage.startContract(id)
|
|
||||||
await expect(storage.startContract(id)).to.be.reverted
|
|
||||||
})
|
|
||||||
|
|
||||||
it("can only be done for a selected offer", async function () {
|
|
||||||
switchAccount(host)
|
|
||||||
const differentOffer = { ...offer, price: offer.price * 2 }
|
|
||||||
await storage.offerStorage(differentOffer)
|
|
||||||
await expect(
|
|
||||||
storage.startContract(offerId(differentOffer))
|
|
||||||
).to.be.revertedWith("Offer was not selected")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("finishing the contract", function () {
|
describe("finishing the contract", function () {
|
||||||
beforeEach(async function () {
|
|
||||||
switchAccount(host)
|
|
||||||
})
|
|
||||||
|
|
||||||
async function waitUntilEnd() {
|
async function waitUntilEnd() {
|
||||||
const end = (await storage.proofEnd(id)).toNumber()
|
const end = (await storage.proofEnd(id)).toNumber()
|
||||||
await advanceTimeTo(end)
|
await advanceTimeTo(end)
|
||||||
}
|
}
|
||||||
|
|
||||||
it("unlocks the host collateral", async function () {
|
it("unlocks the host collateral", async function () {
|
||||||
await storage.startContract(id)
|
await storage.fulfillRequest(requestId(request), proof)
|
||||||
await waitUntilEnd()
|
await waitUntilEnd()
|
||||||
await storage.finishContract(id)
|
await storage.finishContract(id)
|
||||||
await expect(storage.withdraw()).not.to.be.reverted
|
await expect(storage.withdraw()).not.to.be.reverted
|
||||||
})
|
})
|
||||||
|
|
||||||
it("pays the host", async function () {
|
it("pays the host", async function () {
|
||||||
await storage.startContract(id)
|
await storage.fulfillRequest(requestId(request), proof)
|
||||||
await waitUntilEnd()
|
await waitUntilEnd()
|
||||||
const startBalance = await token.balanceOf(host.address)
|
const startBalance = await token.balanceOf(host.address)
|
||||||
await storage.finishContract(id)
|
await storage.finishContract(id)
|
||||||
const endBalance = await token.balanceOf(host.address)
|
const endBalance = await token.balanceOf(host.address)
|
||||||
expect(endBalance - startBalance).to.equal(offer.price)
|
expect(endBalance - startBalance).to.equal(request.ask.maxPrice)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("is only allowed when the contract has started", async function () {
|
it("is only allowed when the contract has started", async function () {
|
||||||
|
@ -131,24 +84,24 @@ describe("Storage", function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("is only allowed when end time has passed", async function () {
|
it("is only allowed when end time has passed", async function () {
|
||||||
await storage.startContract(id)
|
await storage.fulfillRequest(requestId(request), proof)
|
||||||
await expect(storage.finishContract(id)).to.be.revertedWith(
|
await expect(storage.finishContract(id)).to.be.revertedWith(
|
||||||
"Contract has not ended yet"
|
"Contract has not ended yet"
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("can only be done once", async function () {
|
it("can only be done once", async function () {
|
||||||
await storage.startContract(id)
|
await storage.fulfillRequest(requestId(request), proof)
|
||||||
await waitUntilEnd()
|
await waitUntilEnd()
|
||||||
await storage.finishContract(id)
|
await storage.finishContract(id)
|
||||||
await expect(storage.finishContract(id)).to.be.reverted
|
await expect(storage.finishContract(id)).to.be.reverted
|
||||||
})
|
})
|
||||||
|
|
||||||
it("can not be restarted", async function () {
|
it("can not be restarted", async function () {
|
||||||
await storage.startContract(id)
|
await storage.fulfillRequest(requestId(request), proof)
|
||||||
await waitUntilEnd()
|
await waitUntilEnd()
|
||||||
await storage.finishContract(id)
|
await storage.finishContract(id)
|
||||||
await expect(storage.startContract(id)).to.be.reverted
|
await expect(storage.fulfillRequest(id, proof)).to.be.reverted
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -156,7 +109,6 @@ describe("Storage", function () {
|
||||||
let period, periodOf, periodEnd
|
let period, periodOf, periodEnd
|
||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
switchAccount(host)
|
|
||||||
period = (await storage.proofPeriod()).toNumber()
|
period = (await storage.proofPeriod()).toNumber()
|
||||||
;({ periodOf, periodEnd } = periodic(period))
|
;({ periodOf, periodEnd } = periodic(period))
|
||||||
})
|
})
|
||||||
|
@ -174,7 +126,7 @@ describe("Storage", function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
it("reduces collateral when too many proofs are missing", async function () {
|
it("reduces collateral when too many proofs are missing", async function () {
|
||||||
await storage.connect(host).startContract(id)
|
await storage.fulfillRequest(requestId(request), proof)
|
||||||
for (let i = 0; i < slashMisses; i++) {
|
for (let i = 0; i < slashMisses; i++) {
|
||||||
await waitUntilProofIsRequired()
|
await waitUntilProofIsRequired()
|
||||||
let missedPeriod = periodOf(await currentTime())
|
let missedPeriod = periodOf(await currentTime())
|
||||||
|
@ -187,7 +139,6 @@ describe("Storage", function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: failure to start contract burns host and client
|
|
||||||
// TODO: implement checking of actual proofs of storage, instead of dummy bool
|
// TODO: implement checking of actual proofs of storage, instead of dummy bool
|
||||||
// TODO: allow other host to take over contract when too many missed proofs
|
// TODO: allow other host to take over contract when too many missed proofs
|
||||||
// TODO: small partial payouts when proofs are being submitted
|
// TODO: small partial payouts when proofs are being submitted
|
||||||
|
|
|
@ -27,21 +27,9 @@ const exampleRequest = () => ({
|
||||||
nonce: hexlify(randomBytes(32)),
|
nonce: hexlify(randomBytes(32)),
|
||||||
})
|
})
|
||||||
|
|
||||||
const exampleBid = () => ({
|
|
||||||
price: 42,
|
|
||||||
bidExpiry: now() + hours(1),
|
|
||||||
})
|
|
||||||
|
|
||||||
const exampleOffer = () => ({
|
|
||||||
requestId: hexlify(randomBytes(32)),
|
|
||||||
host: hexlify(randomBytes(20)),
|
|
||||||
price: 42,
|
|
||||||
expiry: now() + hours(1),
|
|
||||||
})
|
|
||||||
|
|
||||||
const exampleLock = () => ({
|
const exampleLock = () => ({
|
||||||
id: hexlify(randomBytes(32)),
|
id: hexlify(randomBytes(32)),
|
||||||
expiry: now() + hours(1),
|
expiry: now() + hours(1),
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = { exampleRequest, exampleOffer, exampleBid, exampleLock }
|
module.exports = { exampleRequest, exampleLock }
|
||||||
|
|
15
test/ids.js
15
test/ids.js
|
@ -11,15 +11,6 @@ function requestId(request) {
|
||||||
return keccak256(defaultAbiCoder.encode([Request], requestToArray(request)))
|
return keccak256(defaultAbiCoder.encode([Request], requestToArray(request)))
|
||||||
}
|
}
|
||||||
|
|
||||||
function offerId(offer) {
|
|
||||||
return keccak256(
|
|
||||||
defaultAbiCoder.encode(
|
|
||||||
["address", "bytes32", "uint256", "uint256"],
|
|
||||||
offerToArray(offer)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function askToArray(ask) {
|
function askToArray(ask) {
|
||||||
return [ask.size, ask.duration, ask.proofProbability, ask.maxPrice]
|
return [ask.size, ask.duration, ask.proofProbability, ask.maxPrice]
|
||||||
}
|
}
|
||||||
|
@ -48,14 +39,8 @@ function requestToArray(request) {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
function offerToArray(offer) {
|
|
||||||
return [offer.host, offer.requestId, offer.price, offer.expiry]
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
requestId,
|
requestId,
|
||||||
offerId,
|
|
||||||
requestToArray,
|
requestToArray,
|
||||||
askToArray,
|
askToArray,
|
||||||
offerToArray,
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue