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-02-16 09:50:00 +00:00
|
|
|
|
2022-02-16 13:38:19 +00:00
|
|
|
contract Marketplace is Collateral {
|
|
|
|
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-02-21 10:31:37 +00:00
|
|
|
mapping(bytes32 => RequestState) private requestState;
|
2022-02-16 13:38:19 +00:00
|
|
|
mapping(bytes32 => Offer) private offers;
|
2022-02-16 09:50:00 +00:00
|
|
|
|
2022-02-16 13:38:19 +00:00
|
|
|
constructor(IERC20 _token, uint256 _collateral)
|
|
|
|
Collateral(_token)
|
|
|
|
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-02-16 13:15:43 +00:00
|
|
|
funds.received += request.maxPrice;
|
|
|
|
funds.balance += request.maxPrice;
|
2022-02-21 11:55:00 +00:00
|
|
|
transferFrom(msg.sender, request.maxPrice);
|
|
|
|
|
2022-02-16 09:50:00 +00:00
|
|
|
emit StorageRequested(id, request);
|
|
|
|
}
|
|
|
|
|
2022-02-16 13:38:19 +00:00
|
|
|
function offerStorage(Offer calldata offer) public marketplaceInvariant {
|
2022-02-21 11:55:00 +00:00
|
|
|
require(offer.host == msg.sender, "Invalid host address");
|
2022-02-16 13:38:19 +00:00
|
|
|
require(balanceOf(msg.sender) >= collateral, "Insufficient collateral");
|
2022-02-21 11:55:00 +00:00
|
|
|
|
|
|
|
Request storage request = requests[offer.requestId];
|
2022-02-17 10:09:35 +00:00
|
|
|
require(request.client != address(0), "Unknown request");
|
2022-02-21 11:16:27 +00:00
|
|
|
// solhint-disable-next-line not-rely-on-time
|
|
|
|
require(request.expiry > block.timestamp, "Request expired");
|
2022-02-21 11:55:00 +00:00
|
|
|
|
2022-02-16 13:38:19 +00:00
|
|
|
require(offer.price <= request.maxPrice, "Price too high");
|
2022-02-21 11:55:00 +00:00
|
|
|
|
|
|
|
bytes32 id = keccak256(abi.encode(offer));
|
|
|
|
require(offers[id].host == address(0), "Offer already exists");
|
|
|
|
|
2022-02-16 13:38:19 +00:00
|
|
|
offers[id] = offer;
|
2022-02-21 11:55:00 +00:00
|
|
|
|
2022-02-17 11:31:37 +00:00
|
|
|
_lock(msg.sender, offer.requestId);
|
2022-02-21 11:55:00 +00:00
|
|
|
|
2022-02-16 13:38:19 +00:00
|
|
|
emit StorageOffered(id, offer);
|
|
|
|
}
|
|
|
|
|
2022-02-21 10:31:37 +00:00
|
|
|
function selectOffer(bytes32 id) public marketplaceInvariant {
|
|
|
|
Offer storage offer = offers[id];
|
|
|
|
require(offer.host != address(0), "Unknown offer");
|
|
|
|
// solhint-disable-next-line not-rely-on-time
|
|
|
|
require(offer.expiry > block.timestamp, "Offer expired");
|
2022-02-21 11:55:00 +00:00
|
|
|
|
2022-02-21 10:31:37 +00:00
|
|
|
Request storage request = requests[offer.requestId];
|
|
|
|
require(request.client == msg.sender, "Only client can select offer");
|
2022-02-21 11:55:00 +00:00
|
|
|
|
2022-02-21 10:31:37 +00:00
|
|
|
RequestState storage state = requestState[offer.requestId];
|
|
|
|
require(!state.offerSelected, "Offer already selected");
|
2022-02-21 11:55:00 +00:00
|
|
|
|
2022-02-21 10:31:37 +00:00
|
|
|
state.offerSelected = true;
|
2022-02-21 11:55:00 +00:00
|
|
|
|
2022-02-21 10:31:37 +00:00
|
|
|
_createLock(id, offer.expiry);
|
|
|
|
_lock(offer.host, id);
|
|
|
|
_unlock(offer.requestId);
|
2022-02-21 11:55:00 +00:00
|
|
|
|
2022-02-21 10:31:37 +00:00
|
|
|
uint256 difference = request.maxPrice - offer.price;
|
|
|
|
funds.sent += difference;
|
|
|
|
funds.balance -= difference;
|
|
|
|
token.transfer(request.client, difference);
|
2022-02-21 13:00:59 +00:00
|
|
|
|
|
|
|
emit OfferSelected(id);
|
2022-02-21 10:31:37 +00:00
|
|
|
}
|
|
|
|
|
2022-02-16 09:50:00 +00:00
|
|
|
struct Request {
|
2022-02-17 10:00:18 +00:00
|
|
|
address client;
|
2022-02-16 09:50:00 +00:00
|
|
|
uint256 duration;
|
|
|
|
uint256 size;
|
|
|
|
bytes32 contentHash;
|
|
|
|
uint256 proofPeriod;
|
|
|
|
uint256 proofTimeout;
|
|
|
|
uint256 maxPrice;
|
2022-02-17 11:24:27 +00:00
|
|
|
uint256 expiry;
|
2022-02-16 09:50:00 +00:00
|
|
|
bytes32 nonce;
|
|
|
|
}
|
|
|
|
|
2022-02-21 10:31:37 +00:00
|
|
|
struct RequestState {
|
|
|
|
bool offerSelected;
|
|
|
|
}
|
|
|
|
|
2022-02-16 13:38:19 +00:00
|
|
|
struct Offer {
|
2022-02-17 10:06:14 +00:00
|
|
|
address host;
|
2022-02-16 13:38:19 +00:00
|
|
|
bytes32 requestId;
|
|
|
|
uint256 price;
|
|
|
|
uint256 expiry;
|
|
|
|
}
|
|
|
|
|
2022-02-16 09:50:00 +00:00
|
|
|
event StorageRequested(bytes32 id, Request request);
|
2022-02-16 13:38:19 +00:00
|
|
|
event StorageOffered(bytes32 id, Offer offer);
|
2022-02-21 13:00:59 +00:00
|
|
|
event OfferSelected(bytes32 id);
|
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;
|
|
|
|
}
|
|
|
|
}
|