2022-02-16 09:50:00 +00:00
|
|
|
const { ethers } = require("hardhat")
|
2022-06-13 08:40:18 +00:00
|
|
|
const { hexlify, randomBytes } = ethers.utils
|
2022-02-16 09:50:00 +00:00
|
|
|
const { expect } = require("chai")
|
2022-02-16 13:38:19 +00:00
|
|
|
const { exampleRequest, exampleOffer } = require("./examples")
|
2022-06-13 08:40:18 +00:00
|
|
|
const { snapshot, revert, ensureMinimumBlockHeight } = require("./evm")
|
2022-02-16 13:38:19 +00:00
|
|
|
const { now, hours } = require("./time")
|
2022-04-06 12:26:56 +00:00
|
|
|
const { requestId, offerId, offerToArray, askToArray } = require("./ids")
|
2022-02-16 09:50:00 +00:00
|
|
|
|
|
|
|
describe("Marketplace", function () {
|
2022-02-16 13:38:19 +00:00
|
|
|
const collateral = 100
|
2022-06-13 08:40:18 +00:00
|
|
|
const proofPeriod = 30 * 60
|
|
|
|
const proofTimeout = 5
|
|
|
|
const proofDowntime = 64
|
2022-02-16 09:50:00 +00:00
|
|
|
|
|
|
|
let marketplace
|
|
|
|
let token
|
2022-02-21 10:31:37 +00:00
|
|
|
let client, host, host1, host2, host3
|
2022-02-17 10:00:18 +00:00
|
|
|
let request, offer
|
2022-02-16 09:50:00 +00:00
|
|
|
|
|
|
|
beforeEach(async function () {
|
2022-06-13 08:40:18 +00:00
|
|
|
await snapshot()
|
|
|
|
await ensureMinimumBlockHeight(256)
|
2022-02-21 10:31:37 +00:00
|
|
|
;[client, host1, host2, host3] = await ethers.getSigners()
|
|
|
|
host = host1
|
2022-02-17 10:00:18 +00:00
|
|
|
|
2022-02-16 09:50:00 +00:00
|
|
|
const TestToken = await ethers.getContractFactory("TestToken")
|
|
|
|
token = await TestToken.deploy()
|
2022-02-21 10:31:37 +00:00
|
|
|
for (account of [client, host1, host2, host3]) {
|
|
|
|
await token.mint(account.address, 1000)
|
|
|
|
}
|
2022-02-17 10:00:18 +00:00
|
|
|
|
2022-02-16 09:50:00 +00:00
|
|
|
const Marketplace = await ethers.getContractFactory("Marketplace")
|
2022-06-13 08:40:18 +00:00
|
|
|
marketplace = await Marketplace.deploy(
|
|
|
|
token.address,
|
|
|
|
collateral,
|
|
|
|
proofPeriod,
|
|
|
|
proofTimeout,
|
|
|
|
proofDowntime
|
|
|
|
)
|
2022-02-17 10:00:18 +00:00
|
|
|
|
|
|
|
request = exampleRequest()
|
|
|
|
request.client = client.address
|
|
|
|
|
|
|
|
offer = exampleOffer()
|
|
|
|
offer.host = host.address
|
|
|
|
offer.requestId = requestId(request)
|
2022-02-16 09:50:00 +00:00
|
|
|
})
|
|
|
|
|
2022-06-13 08:40:18 +00:00
|
|
|
afterEach(async function () {
|
|
|
|
await revert()
|
|
|
|
})
|
|
|
|
|
2022-02-17 10:00:18 +00:00
|
|
|
function switchAccount(account) {
|
|
|
|
token = token.connect(account)
|
|
|
|
marketplace = marketplace.connect(account)
|
|
|
|
}
|
|
|
|
|
2022-02-16 09:50:00 +00:00
|
|
|
describe("requesting storage", function () {
|
2022-02-17 10:00:18 +00:00
|
|
|
beforeEach(function () {
|
|
|
|
switchAccount(client)
|
|
|
|
})
|
|
|
|
|
2022-02-16 09:50:00 +00:00
|
|
|
it("emits event when storage is requested", async function () {
|
2022-04-06 12:26:56 +00:00
|
|
|
await token.approve(marketplace.address, request.ask.maxPrice)
|
2022-02-16 09:50:00 +00:00
|
|
|
await expect(marketplace.requestStorage(request))
|
|
|
|
.to.emit(marketplace, "StorageRequested")
|
2022-04-06 12:26:56 +00:00
|
|
|
.withArgs(requestId(request), askToArray(request.ask))
|
2022-02-16 09:50:00 +00:00
|
|
|
})
|
|
|
|
|
2022-02-17 10:00:18 +00:00
|
|
|
it("rejects request with invalid client address", async function () {
|
|
|
|
let invalid = { ...request, client: host.address }
|
2022-04-06 12:26:56 +00:00
|
|
|
await token.approve(marketplace.address, invalid.ask.maxPrice)
|
2022-02-17 10:00:18 +00:00
|
|
|
await expect(marketplace.requestStorage(invalid)).to.be.revertedWith(
|
|
|
|
"Invalid client address"
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
2022-02-16 09:50:00 +00:00
|
|
|
it("rejects request with insufficient payment", async function () {
|
2022-04-06 12:26:56 +00:00
|
|
|
let insufficient = request.ask.maxPrice - 1
|
2022-02-16 09:50:00 +00:00
|
|
|
await token.approve(marketplace.address, insufficient)
|
|
|
|
await expect(marketplace.requestStorage(request)).to.be.revertedWith(
|
2022-03-15 15:18:44 +00:00
|
|
|
"ERC20: insufficient allowance"
|
2022-02-16 09:50:00 +00:00
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it("rejects resubmission of request", async function () {
|
2022-04-06 12:26:56 +00:00
|
|
|
await token.approve(marketplace.address, request.ask.maxPrice * 2)
|
2022-02-16 09:50:00 +00:00
|
|
|
await marketplace.requestStorage(request)
|
|
|
|
await expect(marketplace.requestStorage(request)).to.be.revertedWith(
|
|
|
|
"Request already exists"
|
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
2022-02-16 13:38:19 +00:00
|
|
|
|
2022-06-13 08:40:18 +00:00
|
|
|
describe("fulfilling request", function () {
|
|
|
|
const proof = hexlify(randomBytes(42))
|
|
|
|
|
|
|
|
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 request is fulfilled", async function () {
|
|
|
|
await expect(marketplace.fulfillRequest(requestId(request), proof))
|
|
|
|
.to.emit(marketplace, "RequestFulfilled")
|
|
|
|
.withArgs(requestId(request))
|
|
|
|
})
|
|
|
|
|
|
|
|
it("locks collateral of host", async function () {
|
|
|
|
await marketplace.fulfillRequest(requestId(request), proof)
|
|
|
|
await expect(marketplace.withdraw()).to.be.revertedWith("Account locked")
|
|
|
|
})
|
|
|
|
|
|
|
|
it("is rejected when proof is incorrect", async function () {
|
|
|
|
let invalid = hexlify([])
|
|
|
|
await expect(
|
|
|
|
marketplace.fulfillRequest(requestId(request), invalid)
|
|
|
|
).to.be.revertedWith("Invalid proof")
|
|
|
|
})
|
|
|
|
|
|
|
|
it("is rejected when collateral is insufficient", async function () {
|
|
|
|
let insufficient = collateral - 1
|
|
|
|
await marketplace.withdraw()
|
|
|
|
await token.approve(marketplace.address, insufficient)
|
|
|
|
await marketplace.deposit(insufficient)
|
|
|
|
await expect(
|
|
|
|
marketplace.fulfillRequest(requestId(request), proof)
|
|
|
|
).to.be.revertedWith("Insufficient collateral")
|
|
|
|
})
|
|
|
|
|
|
|
|
it("is rejected when request already fulfilled", async function () {
|
|
|
|
await marketplace.fulfillRequest(requestId(request), proof)
|
|
|
|
await expect(
|
|
|
|
marketplace.fulfillRequest(requestId(request), proof)
|
|
|
|
).to.be.revertedWith("Request already fulfilled")
|
|
|
|
})
|
|
|
|
|
|
|
|
it("is rejected when request is unknown", async function () {
|
|
|
|
let unknown = exampleRequest()
|
|
|
|
await expect(
|
|
|
|
marketplace.fulfillRequest(requestId(unknown), proof)
|
|
|
|
).to.be.revertedWith("Unknown request")
|
|
|
|
})
|
|
|
|
|
|
|
|
it("is rejected when request is expired", 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)
|
|
|
|
await expect(
|
|
|
|
marketplace.fulfillRequest(requestId(expired), proof)
|
|
|
|
).to.be.revertedWith("Request expired")
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2022-02-16 13:38:19 +00:00
|
|
|
describe("offering storage", function () {
|
|
|
|
beforeEach(async function () {
|
2022-02-17 10:00:18 +00:00
|
|
|
switchAccount(client)
|
2022-04-06 12:26:56 +00:00
|
|
|
await token.approve(marketplace.address, request.ask.maxPrice)
|
2022-02-16 13:38:19 +00:00
|
|
|
await marketplace.requestStorage(request)
|
2022-02-17 10:00:18 +00:00
|
|
|
switchAccount(host)
|
2022-02-16 13:38:19 +00:00
|
|
|
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")
|
2022-02-21 13:18:28 +00:00
|
|
|
.withArgs(offerId(offer), offerToArray(offer), requestId(request))
|
2022-02-16 13:38:19 +00:00
|
|
|
})
|
|
|
|
|
2022-02-17 11:31:37 +00:00
|
|
|
it("locks collateral of host", async function () {
|
|
|
|
await marketplace.offerStorage(offer)
|
|
|
|
await expect(marketplace.withdraw()).to.be.revertedWith("Account locked")
|
|
|
|
})
|
|
|
|
|
2022-02-17 10:06:14 +00:00
|
|
|
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"
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
2022-02-16 13:38:19 +00:00
|
|
|
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"
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
2022-02-21 11:16:27 +00:00
|
|
|
it("rejects offer for expired request", async function () {
|
|
|
|
switchAccount(client)
|
|
|
|
let expired = { ...request, expiry: now() - hours(1) }
|
2022-04-06 12:26:56 +00:00
|
|
|
await token.approve(marketplace.address, request.ask.maxPrice)
|
2022-02-21 11:16:27 +00:00
|
|
|
await marketplace.requestStorage(expired)
|
|
|
|
switchAccount(host)
|
|
|
|
let invalid = { ...offer, requestId: requestId(expired) }
|
|
|
|
await expect(marketplace.offerStorage(invalid)).to.be.revertedWith(
|
|
|
|
"Request expired"
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
2022-02-16 13:38:19 +00:00
|
|
|
it("rejects an offer that exceeds the maximum price", async function () {
|
2022-04-06 12:26:56 +00:00
|
|
|
let invalid = { ...offer, price: request.ask.maxPrice + 1 }
|
2022-02-16 13:38:19 +00:00
|
|
|
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"
|
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
2022-02-21 10:31:37 +00:00
|
|
|
|
2022-04-11 12:09:48 +00:00
|
|
|
describe("selecting an offer", function () {
|
2022-02-21 10:31:37 +00:00
|
|
|
beforeEach(async function () {
|
|
|
|
switchAccount(client)
|
2022-04-06 12:26:56 +00:00
|
|
|
await token.approve(marketplace.address, request.ask.maxPrice)
|
2022-02-21 10:31:37 +00:00
|
|
|
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)
|
|
|
|
})
|
|
|
|
|
2022-02-21 13:00:59 +00:00
|
|
|
it("emits event when offer is selected", async function () {
|
|
|
|
await expect(marketplace.selectOffer(offerId(offer)))
|
|
|
|
.to.emit(marketplace, "OfferSelected")
|
2022-02-21 13:18:28 +00:00
|
|
|
.withArgs(offerId(offer), requestId(request))
|
2022-02-21 13:00:59 +00:00
|
|
|
})
|
|
|
|
|
2022-02-21 10:31:37 +00:00
|
|
|
it("returns price difference to client", async function () {
|
2022-04-06 12:26:56 +00:00
|
|
|
let difference = request.ask.maxPrice - offer.price
|
2022-02-21 10:31:37 +00:00
|
|
|
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"
|
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
2022-02-16 09:50:00 +00:00
|
|
|
})
|