[marketplace] fulfill request by presenting proof of storage
This commit is contained in:
parent
2bf01da728
commit
83291ef06b
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue