[marketplace] fulfill request by presenting proof of storage

This commit is contained in:
Mark Spanbroek 2022-06-13 10:40:18 +02:00 committed by markspanbroek
parent 2bf01da728
commit 83291ef06b
3 changed files with 124 additions and 6 deletions

View File

@ -3,16 +3,24 @@ pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./Collateral.sol";
import "./Proofs.sol";
contract Marketplace is Collateral {
contract Marketplace is Collateral, Proofs {
uint256 public immutable collateral;
MarketplaceFunds private funds;
mapping(bytes32 => Request) private requests;
mapping(bytes32 => RequestState) private requestState;
mapping(bytes32 => Offer) private offers;
constructor(IERC20 _token, uint256 _collateral)
constructor(
IERC20 _token,
uint256 _collateral,
uint256 _proofPeriod,
uint256 _proofTimeout,
uint8 _proofDowntime
)
Collateral(_token)
Proofs(_proofPeriod, _proofTimeout, _proofDowntime)
marketplaceInvariant
{
collateral = _collateral;
@ -38,6 +46,26 @@ contract Marketplace is Collateral {
emit StorageRequested(id, request.ask);
}
function fulfillRequest(bytes32 requestId, bytes calldata proof)
public
marketplaceInvariant
{
RequestState storage state = requestState[requestId];
require(!state.fulfilled, "Request already fulfilled");
Request storage request = requests[requestId];
require(request.client != address(0), "Unknown request");
require(request.expiry > block.timestamp, "Request expired");
require(balanceOf(msg.sender) >= collateral, "Insufficient collateral");
_lock(msg.sender, requestId);
_submitProof(requestId, proof);
state.fulfilled = true;
emit RequestFulfilled(requestId);
}
function offerStorage(Offer calldata offer) public marketplaceInvariant {
require(offer.host == msg.sender, "Invalid host address");
require(balanceOf(msg.sender) >= collateral, "Insufficient collateral");
@ -129,6 +157,7 @@ contract Marketplace is Collateral {
}
struct RequestState {
bool fulfilled;
bytes32 selectedOffer;
}
@ -140,6 +169,7 @@ contract Marketplace is Collateral {
}
event StorageRequested(bytes32 requestId, Ask ask);
event RequestFulfilled(bytes32 indexed requestId);
event StorageOffered(bytes32 offerId, Offer offer, bytes32 indexed requestId);
event OfferSelected(bytes32 offerId, bytes32 indexed requestId);

View File

@ -5,7 +5,7 @@ import "./Marketplace.sol";
import "./Proofs.sol";
import "./Collateral.sol";
contract Storage is Collateral, Marketplace, Proofs {
contract Storage is Collateral, Marketplace {
uint256 public collateralAmount;
uint256 public slashMisses;
uint256 public slashPercentage;
@ -21,8 +21,13 @@ contract Storage is Collateral, Marketplace, Proofs {
uint256 _slashMisses,
uint256 _slashPercentage
)
Marketplace(token, _collateralAmount)
Proofs(_proofPeriod, _proofTimeout, _proofDowntime)
Marketplace(
token,
_collateralAmount,
_proofPeriod,
_proofTimeout,
_proofDowntime
)
{
collateralAmount = _collateralAmount;
slashMisses = _slashMisses;

View File

@ -1,11 +1,16 @@
const { ethers } = require("hardhat")
const { hexlify, randomBytes } = ethers.utils
const { expect } = require("chai")
const { exampleRequest, exampleOffer } = require("./examples")
const { snapshot, revert, ensureMinimumBlockHeight } = require("./evm")
const { now, hours } = require("./time")
const { requestId, offerId, offerToArray, askToArray } = require("./ids")
describe("Marketplace", function () {
const collateral = 100
const proofPeriod = 30 * 60
const proofTimeout = 5
const proofDowntime = 64
let marketplace
let token
@ -13,6 +18,8 @@ describe("Marketplace", function () {
let request, offer
beforeEach(async function () {
await snapshot()
await ensureMinimumBlockHeight(256)
;[client, host1, host2, host3] = await ethers.getSigners()
host = host1
@ -23,7 +30,13 @@ describe("Marketplace", function () {
}
const Marketplace = await ethers.getContractFactory("Marketplace")
marketplace = await Marketplace.deploy(token.address, collateral)
marketplace = await Marketplace.deploy(
token.address,
collateral,
proofPeriod,
proofTimeout,
proofDowntime
)
request = exampleRequest()
request.client = client.address
@ -33,6 +46,10 @@ describe("Marketplace", function () {
offer.requestId = requestId(request)
})
afterEach(async function () {
await revert()
})
function switchAccount(account) {
token = token.connect(account)
marketplace = marketplace.connect(account)
@ -75,6 +92,72 @@ describe("Marketplace", function () {
})
})
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")
})
})
describe("offering storage", function () {
beforeEach(async function () {
switchAccount(client)