2022-02-16 09:50:00 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
pragma solidity ^0.8.0;
|
|
|
|
|
|
|
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
2022-02-16 13:38:19 +00:00
|
|
|
import "./Collateral.sol";
|
2022-06-13 08:40:18 +00:00
|
|
|
import "./Proofs.sol";
|
2022-02-16 09:50:00 +00:00
|
|
|
|
2022-06-13 08:40:18 +00:00
|
|
|
contract Marketplace is Collateral, Proofs {
|
2022-02-16 13:38:19 +00:00
|
|
|
uint256 public immutable collateral;
|
2022-02-16 13:15:43 +00:00
|
|
|
MarketplaceFunds private funds;
|
2022-02-16 09:50:00 +00:00
|
|
|
mapping(bytes32 => Request) private requests;
|
2022-08-03 09:54:29 +00:00
|
|
|
mapping(bytes32 => RequestContext) private requestContexts;
|
2022-07-19 09:33:54 +00:00
|
|
|
mapping(bytes32 => Slot) private slots;
|
2022-02-16 09:50:00 +00:00
|
|
|
|
2022-06-13 08:40:18 +00:00
|
|
|
constructor(
|
|
|
|
IERC20 _token,
|
|
|
|
uint256 _collateral,
|
|
|
|
uint256 _proofPeriod,
|
|
|
|
uint256 _proofTimeout,
|
|
|
|
uint8 _proofDowntime
|
|
|
|
)
|
2022-02-16 13:38:19 +00:00
|
|
|
Collateral(_token)
|
2022-06-13 08:40:18 +00:00
|
|
|
Proofs(_proofPeriod, _proofTimeout, _proofDowntime)
|
2022-02-16 13:38:19 +00:00
|
|
|
marketplaceInvariant
|
|
|
|
{
|
|
|
|
collateral = _collateral;
|
2022-02-16 09:50:00 +00:00
|
|
|
}
|
|
|
|
|
2022-02-16 13:15:43 +00:00
|
|
|
function requestStorage(Request calldata request)
|
|
|
|
public
|
|
|
|
marketplaceInvariant
|
|
|
|
{
|
2022-02-17 10:00:18 +00:00
|
|
|
require(request.client == msg.sender, "Invalid client address");
|
2022-02-21 11:55:00 +00:00
|
|
|
|
|
|
|
bytes32 id = keccak256(abi.encode(request));
|
2022-02-17 10:09:35 +00:00
|
|
|
require(requests[id].client == address(0), "Request already exists");
|
2022-02-21 11:55:00 +00:00
|
|
|
|
2022-02-16 09:50:00 +00:00
|
|
|
requests[id] = request;
|
2022-02-21 11:55:00 +00:00
|
|
|
|
2022-02-17 11:31:37 +00:00
|
|
|
_createLock(id, request.expiry);
|
2022-02-21 11:55:00 +00:00
|
|
|
|
2022-07-20 09:07:20 +00:00
|
|
|
uint256 amount = price(request);
|
|
|
|
funds.received += amount;
|
|
|
|
funds.balance += amount;
|
|
|
|
transferFrom(msg.sender, amount);
|
2022-02-21 11:55:00 +00:00
|
|
|
|
2022-04-06 12:26:56 +00:00
|
|
|
emit StorageRequested(id, request.ask);
|
2022-02-16 09:50:00 +00:00
|
|
|
}
|
|
|
|
|
2022-07-19 09:33:54 +00:00
|
|
|
function fillSlot(
|
|
|
|
bytes32 requestId,
|
|
|
|
uint256 slotIndex,
|
|
|
|
bytes calldata proof
|
|
|
|
) public marketplaceInvariant {
|
|
|
|
Request storage request = requests[requestId];
|
|
|
|
require(request.client != address(0), "Unknown request");
|
|
|
|
require(request.expiry > block.timestamp, "Request expired");
|
2022-07-20 08:44:22 +00:00
|
|
|
require(slotIndex < request.ask.slots, "Invalid slot");
|
2022-08-03 09:54:29 +00:00
|
|
|
RequestContext storage context = requestContexts[requestId];
|
|
|
|
// TODO: in the case of repair, update below require condition by adding
|
|
|
|
// || context.state == RequestState.Started
|
|
|
|
require(context.state == RequestState.New, "Invalid state");
|
2022-07-19 09:33:54 +00:00
|
|
|
|
|
|
|
bytes32 slotId = keccak256(abi.encode(requestId, slotIndex));
|
|
|
|
Slot storage slot = slots[slotId];
|
|
|
|
require(slot.host == address(0), "Slot already filled");
|
|
|
|
|
|
|
|
require(balanceOf(msg.sender) >= collateral, "Insufficient collateral");
|
|
|
|
_lock(msg.sender, requestId);
|
|
|
|
|
|
|
|
_expectProofs(slotId, request.ask.proofProbability, request.ask.duration);
|
|
|
|
_submitProof(slotId, proof);
|
|
|
|
|
|
|
|
slot.host = msg.sender;
|
2022-08-03 09:54:29 +00:00
|
|
|
context.slotsFilled += 1;
|
2022-07-19 09:33:54 +00:00
|
|
|
emit SlotFilled(requestId, slotIndex, slotId);
|
2022-08-03 09:54:29 +00:00
|
|
|
if (context.slotsFilled == request.ask.slots) {
|
|
|
|
context.state = RequestState.Started;
|
2022-07-20 08:29:24 +00:00
|
|
|
emit RequestFulfilled(requestId);
|
|
|
|
}
|
2022-07-19 09:33:54 +00:00
|
|
|
}
|
|
|
|
|
2022-07-19 15:09:35 +00:00
|
|
|
function payoutSlot(bytes32 requestId, uint256 slotIndex)
|
|
|
|
public
|
|
|
|
marketplaceInvariant
|
|
|
|
{
|
|
|
|
bytes32 slotId = keccak256(abi.encode(requestId, slotIndex));
|
|
|
|
require(block.timestamp > proofEnd(slotId), "Contract not ended");
|
|
|
|
Slot storage slot = slots[slotId];
|
|
|
|
require(slot.host != address(0), "Slot empty");
|
|
|
|
require(!slot.hostPaid, "Already paid");
|
2022-07-20 09:07:20 +00:00
|
|
|
uint256 amount = pricePerSlot(requests[requestId]);
|
2022-07-19 15:09:35 +00:00
|
|
|
funds.sent += amount;
|
|
|
|
funds.balance -= amount;
|
|
|
|
slot.hostPaid = true;
|
|
|
|
require(token.transfer(slot.host, amount), "Payment failed");
|
|
|
|
}
|
|
|
|
|
2022-08-04 02:14:36 +00:00
|
|
|
function withdrawFunds(bytes32 requestId) public marketplaceInvariant {
|
|
|
|
Request memory request = requests[requestId];
|
|
|
|
require(block.timestamp > request.expiry, "Request not yet timed out");
|
|
|
|
require(request.client == msg.sender, "Invalid client address");
|
|
|
|
RequestContext storage context = requestContexts[requestId];
|
|
|
|
require(context.state == RequestState.New, "Invalid state");
|
|
|
|
|
|
|
|
uint256 amount = _price(request);
|
|
|
|
funds.sent += amount;
|
|
|
|
funds.balance -= amount;
|
|
|
|
token.transfer(msg.sender, amount);
|
|
|
|
emit FundsWithdrawn(requestId);
|
|
|
|
|
|
|
|
// Update request state to Cancelled. Handle in the withdraw transaction
|
|
|
|
// as there needs to be someone to pay for the gas to update the state
|
|
|
|
context.state = RequestState.Cancelled;
|
|
|
|
emit RequestCancelled(requestId);
|
|
|
|
}
|
|
|
|
|
2022-07-20 08:10:22 +00:00
|
|
|
function _host(bytes32 slotId) internal view returns (address) {
|
|
|
|
return slots[slotId].host;
|
2022-02-21 10:31:37 +00:00
|
|
|
}
|
|
|
|
|
2022-02-22 08:25:42 +00:00
|
|
|
function _request(bytes32 id) internal view returns (Request storage) {
|
|
|
|
return requests[id];
|
|
|
|
}
|
|
|
|
|
2022-06-13 09:49:25 +00:00
|
|
|
function proofPeriod() public view returns (uint256) {
|
|
|
|
return _period();
|
|
|
|
}
|
|
|
|
|
|
|
|
function proofTimeout() public view returns (uint256) {
|
|
|
|
return _timeout();
|
|
|
|
}
|
|
|
|
|
2022-08-17 05:24:19 +00:00
|
|
|
function proofEnd(bytes32 slotId) public view returns (uint256) {
|
|
|
|
return _end(slotId);
|
2022-06-13 09:49:25 +00:00
|
|
|
}
|
|
|
|
|
2022-08-04 02:14:36 +00:00
|
|
|
function _price(
|
|
|
|
uint64 numSlots,
|
|
|
|
uint256 duration,
|
|
|
|
uint256 reward) internal pure returns (uint256) {
|
|
|
|
|
|
|
|
return numSlots * duration * reward;
|
|
|
|
}
|
|
|
|
|
|
|
|
function _price(Request memory request) internal pure returns (uint256) {
|
|
|
|
return _price(request.ask.slots, request.ask.duration, request.ask.reward);
|
|
|
|
}
|
|
|
|
|
2022-07-20 09:07:20 +00:00
|
|
|
function price(Request calldata request) private pure returns (uint256) {
|
2022-08-04 02:14:36 +00:00
|
|
|
return _price(request.ask.slots, request.ask.duration, request.ask.reward);
|
2022-07-20 09:07:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function pricePerSlot(Request memory request) private pure returns (uint256) {
|
|
|
|
return request.ask.duration * request.ask.reward;
|
|
|
|
}
|
|
|
|
|
2022-08-03 09:54:29 +00:00
|
|
|
function state(bytes32 requestId) public view returns (RequestState) {
|
|
|
|
return requestContexts[requestId].state;
|
|
|
|
}
|
|
|
|
|
2022-02-16 09:50:00 +00:00
|
|
|
struct Request {
|
2022-02-17 10:00:18 +00:00
|
|
|
address client;
|
2022-04-06 12:26:56 +00:00
|
|
|
Ask ask;
|
|
|
|
Content content;
|
|
|
|
uint256 expiry; // time at which this request expires
|
|
|
|
bytes32 nonce; // random nonce to differentiate between similar requests
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Ask {
|
2022-08-02 09:41:49 +00:00
|
|
|
uint64 slots; // the number of requested slots
|
|
|
|
uint256 slotSize; // amount of storage per slot (in number of bytes)
|
2022-04-06 12:26:56 +00:00
|
|
|
uint256 duration; // how long content should be stored (in seconds)
|
|
|
|
uint256 proofProbability; // how often storage proofs are required
|
2022-07-20 09:07:20 +00:00
|
|
|
uint256 reward; // amount of tokens paid per second per slot to hosts
|
2022-04-06 12:26:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
struct PoR {
|
2022-04-07 13:44:56 +00:00
|
|
|
bytes u; // parameters u_1..u_s
|
|
|
|
bytes publicKey; // public key
|
|
|
|
bytes name; // random name
|
2022-02-16 09:50:00 +00:00
|
|
|
}
|
|
|
|
|
2022-08-03 09:54:29 +00:00
|
|
|
enum RequestState {
|
|
|
|
New, // [default] waiting to fill slots
|
|
|
|
Started, // all slots filled, accepting regular proofs
|
|
|
|
Cancelled, // not enough slots filled before expiry
|
2022-08-04 02:14:36 +00:00
|
|
|
Finished, // successfully completed
|
|
|
|
Failed // too many nodes have failed to provide proofs, data lost
|
2022-08-03 09:54:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct RequestContext {
|
2022-07-20 08:29:24 +00:00
|
|
|
uint256 slotsFilled;
|
2022-08-03 09:54:29 +00:00
|
|
|
RequestState state;
|
2022-07-20 08:29:24 +00:00
|
|
|
}
|
|
|
|
|
2022-07-19 09:33:54 +00:00
|
|
|
struct Slot {
|
|
|
|
address host;
|
2022-07-19 15:09:35 +00:00
|
|
|
bool hostPaid;
|
2022-07-19 09:33:54 +00:00
|
|
|
}
|
|
|
|
|
2022-04-06 12:26:56 +00:00
|
|
|
event StorageRequested(bytes32 requestId, Ask ask);
|
2022-06-13 08:40:18 +00:00
|
|
|
event RequestFulfilled(bytes32 indexed requestId);
|
2022-07-19 09:33:54 +00:00
|
|
|
event SlotFilled(
|
|
|
|
bytes32 indexed requestId,
|
|
|
|
uint256 indexed slotIndex,
|
|
|
|
bytes32 indexed slotId
|
|
|
|
);
|
2022-08-04 02:14:36 +00:00
|
|
|
event RequestCancelled(bytes32 requestId);
|
|
|
|
event FundsWithdrawn(bytes32 requestId);
|
2022-02-16 09:50:00 +00:00
|
|
|
|
2022-02-16 13:15:43 +00:00
|
|
|
modifier marketplaceInvariant() {
|
|
|
|
MarketplaceFunds memory oldFunds = funds;
|
2022-02-16 09:50:00 +00:00
|
|
|
_;
|
2022-02-16 13:15:43 +00:00
|
|
|
assert(funds.received >= oldFunds.received);
|
|
|
|
assert(funds.sent >= oldFunds.sent);
|
|
|
|
assert(funds.received == funds.balance + funds.sent);
|
2022-02-16 09:50:00 +00:00
|
|
|
}
|
|
|
|
|
2022-02-16 13:15:43 +00:00
|
|
|
struct MarketplaceFunds {
|
2022-02-16 09:50:00 +00:00
|
|
|
uint256 balance;
|
|
|
|
uint256 received;
|
|
|
|
uint256 sent;
|
|
|
|
}
|
|
|
|
}
|