[marketplace] Add erasure coding and PoR attributes to request
This commit is contained in:
parent
29b5775951
commit
b8ce6c3682
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)),
|
||||
})
|
||||
|
|
61
test/ids.js
61
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,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue