diff --git a/contracts/Marketplace.sol b/contracts/Marketplace.sol index ef26468..99a55a9 100644 --- a/contracts/Marketplace.sol +++ b/contracts/Marketplace.sol @@ -31,11 +31,11 @@ contract Marketplace is Collateral { _createLock(id, request.expiry); - funds.received += request.maxPrice; - funds.balance += request.maxPrice; - transferFrom(msg.sender, request.maxPrice); + funds.received += request.ask.maxPrice; + funds.balance += request.ask.maxPrice; + transferFrom(msg.sender, request.ask.maxPrice); - emit StorageRequested(id, request); + emit StorageRequested(id, request.ask); } function offerStorage(Offer calldata offer) public marketplaceInvariant { @@ -46,7 +46,7 @@ contract Marketplace is Collateral { require(request.client != address(0), "Unknown request"); require(request.expiry > block.timestamp, "Request expired"); - require(offer.price <= request.maxPrice, "Price too high"); + require(offer.price <= request.ask.maxPrice, "Price too high"); bytes32 id = keccak256(abi.encode(offer)); require(offers[id].host == address(0), "Offer already exists"); @@ -75,7 +75,7 @@ contract Marketplace is Collateral { _lock(offer.host, id); _unlock(offer.requestId); - uint256 difference = request.maxPrice - offer.price; + uint256 difference = request.ask.maxPrice - offer.price; funds.sent += difference; funds.balance -= difference; token.transfer(request.client, difference); @@ -95,15 +95,39 @@ contract Marketplace is Collateral { return requestState[requestId].selectedOffer; } + uint256 private constant POR_SECTORS = 10; // amount of sectors in PoR scheme + struct Request { address client; - uint256 duration; - uint256 size; - bytes32 contentHash; - uint256 proofProbability; - uint256 maxPrice; - uint256 expiry; - bytes32 nonce; + Ask ask; + Content content; + uint256 expiry; // time at which this request expires + bytes32 nonce; // random nonce to differentiate between similar requests + } + + struct Ask { + uint256 size; // size of requested storage in number of bytes + uint256 duration; // how long content should be stored (in seconds) + uint256 proofProbability; // how often storage proofs are required + uint256 maxPrice; // maximum price client will pay (in number of tokens) + } + + struct Content { + string cid; // content id (if part of a larger set, the chunk cid) + Erasure erasure; // Erasure coding attributes + PoR por; // Proof of Retrievability parameters + } + + struct Erasure { + uint64 totalChunks; // the total number of chunks in the larger data set + uint64 totalNodes; // the total number of nodes that store the data set + uint64 nodeId; // index of this node in the list of total nodes + } + + struct PoR { + bytes1[POR_SECTORS * 48] u; // parameters u_1..u_s + bytes1[96] publicKey; // public key + bytes1[512] name; // random name } struct RequestState { @@ -117,7 +141,7 @@ contract Marketplace is Collateral { uint256 expiry; } - event StorageRequested(bytes32 requestId, Request request); + event StorageRequested(bytes32 requestId, Ask ask); event StorageOffered(bytes32 offerId, Offer offer, bytes32 indexed requestId); event OfferSelected(bytes32 offerId, bytes32 indexed requestId); diff --git a/contracts/Storage.sol b/contracts/Storage.sol index ec23b22..c390af1 100644 --- a/contracts/Storage.sol +++ b/contracts/Storage.sol @@ -35,7 +35,7 @@ contract Storage is Collateral, Marketplace, Proofs { require(_selectedOffer(offer.requestId) == id, "Offer was not selected"); contractState[id] = ContractState.started; Request storage request = _request(offer.requestId); - _expectProofs(id, request.proofProbability, request.duration); + _expectProofs(id, request.ask.proofProbability, request.ask.duration); } function finishContract(bytes32 id) public { diff --git a/test/Marketplace.test.js b/test/Marketplace.test.js index eaf730c..b0cc2e4 100644 --- a/test/Marketplace.test.js +++ b/test/Marketplace.test.js @@ -2,7 +2,7 @@ const { ethers } = require("hardhat") const { expect } = require("chai") const { exampleRequest, exampleOffer } = require("./examples") const { now, hours } = require("./time") -const { requestId, offerId, requestToArray, offerToArray } = require("./ids") +const { requestId, offerId, offerToArray, askToArray } = require("./ids") describe("Marketplace", function () { const collateral = 100 @@ -44,22 +44,22 @@ describe("Marketplace", function () { }) it("emits event when storage is requested", async function () { - await token.approve(marketplace.address, request.maxPrice) + await token.approve(marketplace.address, request.ask.maxPrice) await expect(marketplace.requestStorage(request)) .to.emit(marketplace, "StorageRequested") - .withArgs(requestId(request), requestToArray(request)) + .withArgs(requestId(request), askToArray(request.ask)) }) it("rejects request with invalid client address", async function () { let invalid = { ...request, client: host.address } - await token.approve(marketplace.address, invalid.maxPrice) + await token.approve(marketplace.address, invalid.ask.maxPrice) await expect(marketplace.requestStorage(invalid)).to.be.revertedWith( "Invalid client address" ) }) it("rejects request with insufficient payment", async function () { - let insufficient = request.maxPrice - 1 + let insufficient = request.ask.maxPrice - 1 await token.approve(marketplace.address, insufficient) await expect(marketplace.requestStorage(request)).to.be.revertedWith( "ERC20: insufficient allowance" @@ -67,7 +67,7 @@ describe("Marketplace", function () { }) it("rejects resubmission of request", async function () { - await token.approve(marketplace.address, request.maxPrice * 2) + await token.approve(marketplace.address, request.ask.maxPrice * 2) await marketplace.requestStorage(request) await expect(marketplace.requestStorage(request)).to.be.revertedWith( "Request already exists" @@ -78,7 +78,7 @@ describe("Marketplace", function () { describe("offering storage", function () { beforeEach(async function () { switchAccount(client) - await token.approve(marketplace.address, request.maxPrice) + await token.approve(marketplace.address, request.ask.maxPrice) await marketplace.requestStorage(request) switchAccount(host) await token.approve(marketplace.address, collateral) @@ -114,7 +114,7 @@ describe("Marketplace", function () { it("rejects offer for expired request", async function () { switchAccount(client) let expired = { ...request, expiry: now() - hours(1) } - await token.approve(marketplace.address, request.maxPrice) + await token.approve(marketplace.address, request.ask.maxPrice) await marketplace.requestStorage(expired) switchAccount(host) let invalid = { ...offer, requestId: requestId(expired) } @@ -124,7 +124,7 @@ describe("Marketplace", function () { }) it("rejects an offer that exceeds the maximum price", async function () { - let invalid = { ...offer, price: request.maxPrice + 1 } + let invalid = { ...offer, price: request.ask.maxPrice + 1 } await expect(marketplace.offerStorage(invalid)).to.be.revertedWith( "Price too high" ) @@ -151,7 +151,7 @@ describe("Marketplace", function () { describe("selecting an offer", async function () { beforeEach(async function () { switchAccount(client) - await token.approve(marketplace.address, request.maxPrice) + await token.approve(marketplace.address, request.ask.maxPrice) await marketplace.requestStorage(request) for (host of [host1, host2, host3]) { switchAccount(host) @@ -170,7 +170,7 @@ describe("Marketplace", function () { }) it("returns price difference to client", async function () { - let difference = request.maxPrice - offer.price + 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) diff --git a/test/Storage.test.js b/test/Storage.test.js index b0c9e5e..4b61b01 100644 --- a/test/Storage.test.js +++ b/test/Storage.test.js @@ -40,7 +40,7 @@ describe("Storage", function () { offer.requestId = requestId(request) switchAccount(client) - await token.approve(storage.address, request.maxPrice) + await token.approve(storage.address, request.ask.maxPrice) await storage.requestStorage(request) switchAccount(host) await token.approve(storage.address, collateralAmount) diff --git a/test/examples.js b/test/examples.js index 0a98a28..4da050c 100644 --- a/test/examples.js +++ b/test/examples.js @@ -1,14 +1,28 @@ const { ethers } = require("hardhat") const { now, hours } = require("./time") -const { sha256, hexlify, randomBytes } = ethers.utils +const { hexlify, randomBytes } = ethers.utils const exampleRequest = () => ({ client: hexlify(randomBytes(20)), - duration: hours(10), - size: 1 * 1024 * 1024 * 1024, // 1 Gigabyte - contentHash: sha256("0xdeadbeef"), - proofProbability: 4, // require a proof roughly once every 4 periods - maxPrice: 84, + ask: { + size: 1 * 1024 * 1024 * 1024, // 1 Gigabyte + duration: hours(10), + proofProbability: 4, // require a proof roughly once every 4 periods + maxPrice: 84, + }, + content: { + cid: "zb2rhheVmk3bLks5MgzTqyznLu1zqGH5jrfTA1eAZXrjx7Vob", + erasure: { + totalChunks: 12, + totalNodes: 4, + nodeId: 3, + }, + por: { + u: Array.from(randomBytes(480)), + publicKey: Array.from(randomBytes(96)), + name: Array.from(randomBytes(512)), + }, + }, expiry: now() + hours(1), nonce: hexlify(randomBytes(32)), }) diff --git a/test/ids.js b/test/ids.js index 55a7ce7..adcedce 100644 --- a/test/ids.js +++ b/test/ids.js @@ -2,21 +2,13 @@ const { ethers } = require("hardhat") const { keccak256, defaultAbiCoder } = ethers.utils function requestId(request) { - return keccak256( - defaultAbiCoder.encode( - [ - "address", - "uint256", - "uint256", - "bytes32", - "uint256", - "uint256", - "uint256", - "bytes32", - ], - requestToArray(request) - ) - ) + const Ask = "tuple(uint256, uint256, uint256, uint256)" + const Erasure = "tuple(uint64, uint64, uint64)" + const PoR = "tuple(bytes1[480], bytes1[96], bytes1[512])" + const Content = "tuple(string, " + Erasure + ", " + PoR + ")" + const Request = + "tuple(address, " + Ask + ", " + Content + ", uint256, bytes32)" + return keccak256(defaultAbiCoder.encode([Request], requestToArray(request))) } function offerId(offer) { @@ -28,16 +20,31 @@ function offerId(offer) { ) } +function askToArray(ask) { + return [ask.size, ask.duration, ask.proofProbability, ask.maxPrice] +} + +function erasureToArray(erasure) { + return [erasure.totalChunks, erasure.totalNodes, erasure.nodeId] +} + +function porToArray(por) { + return [por.u, por.publicKey, por.name] +} + +function contentToArray(content) { + return [content.cid, erasureToArray(content.erasure), porToArray(content.por)] +} + function requestToArray(request) { return [ - request.client, - request.duration, - request.size, - request.contentHash, - request.proofProbability, - request.maxPrice, - request.expiry, - request.nonce, + [ + request.client, + askToArray(request.ask), + contentToArray(request.content), + request.expiry, + request.nonce, + ], ] } @@ -45,4 +52,10 @@ function offerToArray(offer) { return [offer.host, offer.requestId, offer.price, offer.expiry] } -module.exports = { requestId, offerId, requestToArray, offerToArray } +module.exports = { + requestId, + offerId, + requestToArray, + askToArray, + offerToArray, +}