mirror of
https://github.com/status-im/codex-contracts-eth.git
synced 2025-03-03 17:40:48 +00:00
Merge pull request #22 from status-im/list-of-active-requests
List of active requests
This commit is contained in:
commit
a4057d712f
@ -3,10 +3,13 @@ pragma solidity ^0.8.8;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "@openzeppelin/contracts/utils/math/Math.sol";
|
||||
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
||||
import "./Collateral.sol";
|
||||
import "./Proofs.sol";
|
||||
|
||||
contract Marketplace is Collateral, Proofs {
|
||||
using EnumerableSet for EnumerableSet.Bytes32Set;
|
||||
|
||||
type RequestId is bytes32;
|
||||
type SlotId is bytes32;
|
||||
|
||||
@ -15,6 +18,7 @@ contract Marketplace is Collateral, Proofs {
|
||||
mapping(RequestId => Request) private requests;
|
||||
mapping(RequestId => RequestContext) private requestContexts;
|
||||
mapping(SlotId => Slot) private slots;
|
||||
mapping(address => EnumerableSet.Bytes32Set) private activeRequests;
|
||||
|
||||
constructor(
|
||||
IERC20 _token,
|
||||
@ -30,6 +34,10 @@ contract Marketplace is Collateral, Proofs {
|
||||
collateral = _collateral;
|
||||
}
|
||||
|
||||
function myRequests() public view returns (RequestId[] memory) {
|
||||
return _toRequestIds(activeRequests[msg.sender].values());
|
||||
}
|
||||
|
||||
function requestStorage(Request calldata request)
|
||||
public
|
||||
marketplaceInvariant
|
||||
@ -45,6 +53,7 @@ contract Marketplace is Collateral, Proofs {
|
||||
context.endsAt = block.timestamp + request.ask.duration;
|
||||
_setProofEnd(_toEndId(id), context.endsAt);
|
||||
|
||||
activeRequests[request.client].add(RequestId.unwrap(id));
|
||||
|
||||
_createLock(_toLockId(id), request.expiry);
|
||||
|
||||
@ -73,10 +82,7 @@ contract Marketplace is Collateral, Proofs {
|
||||
_lock(msg.sender, lockId);
|
||||
|
||||
ProofId proofId = _toProofId(slotId);
|
||||
_expectProofs(
|
||||
proofId,
|
||||
_toEndId(requestId),
|
||||
request.ask.proofProbability);
|
||||
_expectProofs(proofId, _toEndId(requestId), request.ask.proofProbability);
|
||||
_submitProof(proofId, proof);
|
||||
|
||||
slot.host = msg.sender;
|
||||
@ -92,9 +98,11 @@ contract Marketplace is Collateral, Proofs {
|
||||
}
|
||||
}
|
||||
|
||||
function _freeSlot(
|
||||
SlotId slotId
|
||||
) internal slotMustAcceptProofs(slotId) marketplaceInvariant {
|
||||
function _freeSlot(SlotId slotId)
|
||||
internal
|
||||
slotMustAcceptProofs(slotId)
|
||||
marketplaceInvariant
|
||||
{
|
||||
Slot storage slot = _slot(slotId);
|
||||
RequestId requestId = slot.requestId;
|
||||
RequestContext storage context = requestContexts[requestId];
|
||||
@ -113,12 +121,14 @@ contract Marketplace is Collateral, Proofs {
|
||||
|
||||
Request storage request = _request(requestId);
|
||||
uint256 slotsLost = request.ask.slots - context.slotsFilled;
|
||||
if (slotsLost > request.ask.maxSlotLoss &&
|
||||
context.state == RequestState.Started) {
|
||||
|
||||
if (
|
||||
slotsLost > request.ask.maxSlotLoss &&
|
||||
context.state == RequestState.Started
|
||||
) {
|
||||
context.state = RequestState.Failed;
|
||||
_setProofEnd(_toEndId(requestId), block.timestamp - 1);
|
||||
context.endsAt = block.timestamp - 1;
|
||||
activeRequests[request.client].remove(RequestId.unwrap(requestId));
|
||||
emit RequestFailed(requestId);
|
||||
|
||||
// TODO: burn all remaining slot collateral (note: slot collateral not
|
||||
@ -126,13 +136,16 @@ contract Marketplace is Collateral, Proofs {
|
||||
// TODO: send client remaining funds
|
||||
}
|
||||
}
|
||||
|
||||
function payoutSlot(RequestId requestId, uint256 slotIndex)
|
||||
public
|
||||
marketplaceInvariant
|
||||
{
|
||||
require(_isFinished(requestId), "Contract not ended");
|
||||
RequestContext storage context = _context(requestId);
|
||||
Request storage request = _request(requestId);
|
||||
context.state = RequestState.Finished;
|
||||
activeRequests[request.client].remove(RequestId.unwrap(requestId));
|
||||
SlotId slotId = _toSlotId(requestId, slotIndex);
|
||||
Slot storage slot = _slot(slotId);
|
||||
require(!slot.hostPaid, "Already paid");
|
||||
@ -156,6 +169,7 @@ contract Marketplace is Collateral, Proofs {
|
||||
// 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;
|
||||
activeRequests[request.client].remove(RequestId.unwrap(requestId));
|
||||
emit RequestCancelled(requestId);
|
||||
|
||||
// TODO: To be changed once we start paying out hosts for the time they
|
||||
@ -175,10 +189,8 @@ contract Marketplace is Collateral, Proofs {
|
||||
RequestContext storage context = _context(requestId);
|
||||
return
|
||||
context.state == RequestState.Cancelled ||
|
||||
(
|
||||
context.state == RequestState.New &&
|
||||
block.timestamp > _request(requestId).expiry
|
||||
);
|
||||
(context.state == RequestState.New &&
|
||||
block.timestamp > _request(requestId).expiry);
|
||||
}
|
||||
|
||||
/// @notice Return true if the request state is RequestState.Finished or if the request duration has elapsed and the request was started.
|
||||
@ -189,10 +201,8 @@ contract Marketplace is Collateral, Proofs {
|
||||
RequestContext memory context = _context(requestId);
|
||||
return
|
||||
context.state == RequestState.Finished ||
|
||||
(
|
||||
context.state == RequestState.Started &&
|
||||
block.timestamp > context.endsAt
|
||||
);
|
||||
(context.state == RequestState.Started &&
|
||||
block.timestamp > context.endsAt);
|
||||
}
|
||||
|
||||
/// @notice Return id of request that slot belongs to
|
||||
@ -255,9 +265,12 @@ contract Marketplace is Collateral, Proofs {
|
||||
}
|
||||
|
||||
function proofEnd(SlotId slotId) public view returns (uint256) {
|
||||
Slot memory slot = _slot(slotId);
|
||||
uint256 end = _end(_toEndId(slot.requestId));
|
||||
if (_slotAcceptsProofs(slotId)) {
|
||||
return requestEnd(_slot(slotId).requestId);
|
||||
}
|
||||
|
||||
function requestEnd(RequestId requestId) public view returns (uint256) {
|
||||
uint256 end = _end(_toEndId(requestId));
|
||||
if (_requestAcceptsProofs(requestId)) {
|
||||
return end;
|
||||
} else {
|
||||
return Math.min(end, block.timestamp - 1);
|
||||
@ -267,8 +280,8 @@ contract Marketplace is Collateral, Proofs {
|
||||
function _price(
|
||||
uint64 numSlots,
|
||||
uint256 duration,
|
||||
uint256 reward) internal pure returns (uint256) {
|
||||
|
||||
uint256 reward
|
||||
) internal pure returns (uint256) {
|
||||
return numSlots * duration * reward;
|
||||
}
|
||||
|
||||
@ -306,7 +319,11 @@ contract Marketplace is Collateral, Proofs {
|
||||
/// @notice returns true when the request is accepting proof submissions from hosts occupying slots.
|
||||
/// @dev Request state must be new or started, and must not be cancelled, finished, or failed.
|
||||
/// @param requestId id of the request for which to obtain state info
|
||||
function _requestAcceptsProofs(RequestId requestId) internal view returns (bool) {
|
||||
function _requestAcceptsProofs(RequestId requestId)
|
||||
internal
|
||||
view
|
||||
returns (bool)
|
||||
{
|
||||
RequestState s = state(requestId);
|
||||
return s == RequestState.New || s == RequestState.Started;
|
||||
}
|
||||
@ -319,9 +336,18 @@ contract Marketplace is Collateral, Proofs {
|
||||
return RequestId.wrap(keccak256(abi.encode(request)));
|
||||
}
|
||||
|
||||
function _toSlotId(
|
||||
RequestId requestId,
|
||||
uint256 slotIndex)
|
||||
function _toRequestIds(bytes32[] memory array)
|
||||
private
|
||||
pure
|
||||
returns (RequestId[] memory result)
|
||||
{
|
||||
// solhint-disable-next-line no-inline-assembly
|
||||
assembly {
|
||||
result := array
|
||||
}
|
||||
}
|
||||
|
||||
function _toSlotId(RequestId requestId, uint256 slotIndex)
|
||||
internal
|
||||
pure
|
||||
returns (SlotId)
|
||||
@ -379,11 +405,11 @@ contract Marketplace is Collateral, Proofs {
|
||||
}
|
||||
|
||||
enum RequestState {
|
||||
New, // [default] waiting to fill slots
|
||||
Started, // all slots filled, accepting regular proofs
|
||||
Cancelled, // not enough slots filled before expiry
|
||||
Finished, // successfully completed
|
||||
Failed // too many nodes have failed to provide proofs, data lost
|
||||
New, // [default] waiting to fill slots
|
||||
Started, // all slots filled, accepting regular proofs
|
||||
Cancelled, // not enough slots filled before expiry
|
||||
Finished, // successfully completed
|
||||
Failed // too many nodes have failed to provide proofs, data lost
|
||||
}
|
||||
|
||||
struct RequestContext {
|
||||
|
@ -9,7 +9,7 @@ const {
|
||||
waitUntilStarted,
|
||||
waitUntilFinished,
|
||||
waitUntilFailed,
|
||||
RequestState
|
||||
RequestState,
|
||||
} = require("./marketplace")
|
||||
const { price, pricePerSlot } = require("./price")
|
||||
const {
|
||||
@ -178,8 +178,8 @@ describe("Marketplace", function () {
|
||||
})
|
||||
|
||||
it("is rejected when request is finished", async function () {
|
||||
const lastSlot = await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFinished(marketplace, lastSlot)
|
||||
await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
await expect(
|
||||
marketplace.fillSlot(slot.request, slot.index, proof)
|
||||
).to.be.revertedWith("Request not accepting proofs")
|
||||
@ -261,8 +261,8 @@ describe("Marketplace", function () {
|
||||
})
|
||||
|
||||
it("checks that proof end time is in the past once finished", async function () {
|
||||
const lastSlot = await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFinished(marketplace, lastSlot)
|
||||
await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
const now = await currentTime()
|
||||
// in the process of calling currentTime and proofEnd,
|
||||
// block.timestamp has advanced by 1, so the expected proof end time will
|
||||
@ -271,6 +271,72 @@ describe("Marketplace", function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe("request end", function () {
|
||||
var requestTime
|
||||
beforeEach(async function () {
|
||||
switchAccount(client)
|
||||
await token.approve(marketplace.address, price(request))
|
||||
await marketplace.requestStorage(request)
|
||||
requestTime = await currentTime()
|
||||
switchAccount(host)
|
||||
await token.approve(marketplace.address, collateral)
|
||||
await marketplace.deposit(collateral)
|
||||
})
|
||||
|
||||
it("shares request end time for all slots in request", async function () {
|
||||
const lastSlot = request.ask.slots - 1
|
||||
for (let i = 0; i < lastSlot; i++) {
|
||||
await marketplace.fillSlot(slot.request, i, proof)
|
||||
}
|
||||
advanceTime(minutes(10))
|
||||
await marketplace.fillSlot(slot.request, lastSlot, proof)
|
||||
let slot0 = { ...slot, index: 0 }
|
||||
let end = await marketplace.requestEnd(requestId(request))
|
||||
for (let i = 1; i <= lastSlot; i++) {
|
||||
let sloti = { ...slot, index: i }
|
||||
await expect((await marketplace.proofEnd(slotId(sloti))) === end)
|
||||
}
|
||||
})
|
||||
|
||||
it("sets the request end time to now + duration", async function () {
|
||||
await marketplace.fillSlot(slot.request, slot.index, proof)
|
||||
await expect(
|
||||
(await marketplace.requestEnd(requestId(request))).toNumber()
|
||||
).to.be.closeTo(requestTime + request.ask.duration, 1)
|
||||
})
|
||||
|
||||
it("sets request end time to the past once failed", async function () {
|
||||
await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFailed(marketplace, request, slot)
|
||||
let slot0 = { ...slot, index: request.ask.maxSlotLoss + 1 }
|
||||
const now = await currentTime()
|
||||
await expect(await marketplace.requestEnd(requestId(request))).to.be.eq(
|
||||
now - 1
|
||||
)
|
||||
})
|
||||
|
||||
it("sets request end time to the past once cancelled", async function () {
|
||||
await marketplace.fillSlot(slot.request, slot.index, proof)
|
||||
await waitUntilCancelled(request)
|
||||
const now = await currentTime()
|
||||
await expect(await marketplace.requestEnd(requestId(request))).to.be.eq(
|
||||
now - 1
|
||||
)
|
||||
})
|
||||
|
||||
it("checks that request end time is in the past once finished", async function () {
|
||||
await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
const now = await currentTime()
|
||||
// in the process of calling currentTime and proofEnd,
|
||||
// block.timestamp has advanced by 1, so the expected proof end time will
|
||||
// be block.timestamp - 1.
|
||||
await expect(await marketplace.requestEnd(requestId(request))).to.be.eq(
|
||||
now - 1
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("freeing a slot", function () {
|
||||
var id
|
||||
beforeEach(async function () {
|
||||
@ -302,8 +368,8 @@ describe("Marketplace", function () {
|
||||
})
|
||||
|
||||
it("fails to free slot when finished", async function () {
|
||||
const lastSlot = await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFinished(marketplace, lastSlot)
|
||||
await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
await expect(marketplace.freeSlot(slotId(slot))).to.be.revertedWith(
|
||||
"Slot not accepting proofs"
|
||||
)
|
||||
@ -339,8 +405,8 @@ describe("Marketplace", function () {
|
||||
})
|
||||
|
||||
it("pays the host", async function () {
|
||||
const lastSlot = await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFinished(marketplace, lastSlot)
|
||||
await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
const startBalance = await token.balanceOf(host.address)
|
||||
await marketplace.payoutSlot(slot.request, slot.index)
|
||||
const endBalance = await token.balanceOf(host.address)
|
||||
@ -355,8 +421,8 @@ describe("Marketplace", function () {
|
||||
})
|
||||
|
||||
it("can only be done once", async function () {
|
||||
const lastSlot = await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFinished(marketplace, lastSlot)
|
||||
await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
await marketplace.payoutSlot(slot.request, slot.index)
|
||||
await expect(
|
||||
marketplace.payoutSlot(slot.request, slot.index)
|
||||
@ -364,8 +430,8 @@ describe("Marketplace", function () {
|
||||
})
|
||||
|
||||
it("cannot be filled again", async function () {
|
||||
const lastSlot = await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFinished(marketplace, lastSlot)
|
||||
await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
await marketplace.payoutSlot(slot.request, slot.index)
|
||||
await expect(marketplace.fillSlot(slot.request, slot.index, proof)).to.be
|
||||
.reverted
|
||||
@ -507,8 +573,8 @@ describe("Marketplace", function () {
|
||||
})
|
||||
|
||||
it("state is Finished once slot is paid out", async function () {
|
||||
const lastSlot = await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFinished(marketplace, lastSlot)
|
||||
await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
await marketplace.payoutSlot(slot.request, slot.index)
|
||||
await expect(await marketplace.state(slot.request)).to.equal(
|
||||
RequestState.Finished
|
||||
@ -595,26 +661,16 @@ describe("Marketplace", function () {
|
||||
})
|
||||
|
||||
it("fails when request Finished (isFinished is true)", async function () {
|
||||
const lastSlot = await waitUntilStarted(
|
||||
marketplace,
|
||||
request,
|
||||
slot,
|
||||
proof
|
||||
)
|
||||
await waitUntilFinished(marketplace, lastSlot)
|
||||
await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
await expect(
|
||||
marketplace.testAcceptsProofs(slotId(slot))
|
||||
).to.be.revertedWith("Slot not accepting proofs")
|
||||
})
|
||||
|
||||
it("fails when request Finished (state set to Finished)", async function () {
|
||||
const lastSlot = await waitUntilStarted(
|
||||
marketplace,
|
||||
request,
|
||||
slot,
|
||||
proof
|
||||
)
|
||||
await waitUntilFinished(marketplace, lastSlot)
|
||||
await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
await marketplace.payoutSlot(slot.request, slot.index)
|
||||
await expect(
|
||||
marketplace.testAcceptsProofs(slotId(slot))
|
||||
@ -634,4 +690,45 @@ describe("Marketplace", function () {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("list of active requests", function () {
|
||||
beforeEach(async function () {
|
||||
switchAccount(host)
|
||||
await token.approve(marketplace.address, collateral)
|
||||
await marketplace.deposit(collateral)
|
||||
switchAccount(client)
|
||||
await token.approve(marketplace.address, price(request))
|
||||
})
|
||||
|
||||
it("adds request to list when requesting storage", async function () {
|
||||
await marketplace.requestStorage(request)
|
||||
expect(await marketplace.myRequests()).to.deep.equal([requestId(request)])
|
||||
})
|
||||
|
||||
it("removes request from list when funds are withdrawn", async function () {
|
||||
await marketplace.requestStorage(request)
|
||||
await waitUntilCancelled(request)
|
||||
await marketplace.withdrawFunds(requestId(request))
|
||||
expect(await marketplace.myRequests()).to.deep.equal([])
|
||||
})
|
||||
|
||||
it("removes request from list when request fails", async function () {
|
||||
await marketplace.requestStorage(request)
|
||||
switchAccount(host)
|
||||
await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFailed(marketplace, request, slot)
|
||||
switchAccount(client)
|
||||
expect(await marketplace.myRequests()).to.deep.equal([])
|
||||
})
|
||||
|
||||
it("removes request from list when request finishes", async function () {
|
||||
await marketplace.requestStorage(request)
|
||||
switchAccount(host)
|
||||
await waitUntilStarted(marketplace, request, slot, proof)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
await marketplace.payoutSlot(slot.request, slot.index)
|
||||
switchAccount(client)
|
||||
expect(await marketplace.myRequests()).to.deep.equal([])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -76,7 +76,7 @@ describe("Storage", function () {
|
||||
describe("ending the contract", function () {
|
||||
it("unlocks the host collateral", async function () {
|
||||
await storage.fillSlot(slot.request, slot.index, proof)
|
||||
await waitUntilFinished(storage, slot)
|
||||
await waitUntilFinished(storage, slot.request)
|
||||
await expect(storage.withdraw()).not.to.be.reverted
|
||||
})
|
||||
})
|
||||
|
@ -6,16 +6,13 @@ async function waitUntilCancelled(request) {
|
||||
}
|
||||
|
||||
async function waitUntilStarted(contract, request, slot, proof) {
|
||||
const lastSlotIdx = request.ask.slots - 1
|
||||
for (let i = 0; i <= lastSlotIdx; i++) {
|
||||
for (let i = 0; i < request.ask.slots; i++) {
|
||||
await contract.fillSlot(slot.request, i, proof)
|
||||
}
|
||||
return { ...slot, index: lastSlotIdx }
|
||||
}
|
||||
|
||||
async function waitUntilFinished(contract, lastSlot) {
|
||||
const lastSlotId = slotId(lastSlot)
|
||||
const end = (await contract.proofEnd(lastSlotId)).toNumber()
|
||||
async function waitUntilFinished(contract, requestId) {
|
||||
const end = (await contract.requestEnd(requestId)).toNumber()
|
||||
await advanceTimeTo(end + 1)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user