feat: repair reward
This commit is contained in:
parent
997696a20e
commit
4aacb6f485
|
@ -10,10 +10,7 @@ struct MarketplaceConfig {
|
|||
}
|
||||
|
||||
struct CollateralConfig {
|
||||
/// @dev percentage of remaining collateral slot after it has been freed
|
||||
/// (equivalent to `collateral - (collateral*maxNumberOfSlashes*slashPercentage)/100`)
|
||||
/// TODO: to be aligned more closely with actual cost of repair once bandwidth incentives are known,
|
||||
/// see https://github.com/codex-storage/codex-contracts-eth/pull/47#issuecomment-1465511949.
|
||||
/// @dev percentage of collateral that is used as repair reward
|
||||
uint8 repairRewardPercentage;
|
||||
uint8 maxNumberOfSlashes; // frees slot when the number of slashing reaches this value
|
||||
uint16 slashCriterion; // amount of proofs missed that lead to slashing
|
||||
|
|
|
@ -149,28 +149,47 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
|||
slot.slotIndex = slotIndex;
|
||||
RequestContext storage context = _requestContexts[requestId];
|
||||
|
||||
require(slotState(slotId) == SlotState.Free, "Slot is not free");
|
||||
require(
|
||||
slotState(slotId) == SlotState.Free ||
|
||||
slotState(slotId) == SlotState.Repair,
|
||||
"Slot is not free"
|
||||
);
|
||||
|
||||
_startRequiringProofs(slotId, request.ask.proofProbability);
|
||||
submitProof(slotId, proof);
|
||||
|
||||
slot.host = msg.sender;
|
||||
slot.state = SlotState.Filled;
|
||||
slot.filledAt = block.timestamp;
|
||||
|
||||
context.slotsFilled += 1;
|
||||
context.fundsToReturnToClient -= _slotPayout(requestId, slot.filledAt);
|
||||
|
||||
// Collect collateral
|
||||
uint256 collateralAmount = request.ask.collateral;
|
||||
uint256 collateralAmount;
|
||||
if (slotState(slotId) == SlotState.Repair) {
|
||||
// Host is repairing a slot and is entitled for repair reward, so he gets "discounted collateral"
|
||||
// in this way he gets "physically" the reward at the end of the request when the full amount of collateral
|
||||
// is returned to him.
|
||||
collateralAmount =
|
||||
request.ask.collateral -
|
||||
((request.ask.collateral * _config.collateral.repairRewardPercentage) /
|
||||
100);
|
||||
} else {
|
||||
collateralAmount = request.ask.collateral;
|
||||
}
|
||||
_transferFrom(msg.sender, collateralAmount);
|
||||
_marketplaceTotals.received += collateralAmount;
|
||||
slot.currentCollateral = collateralAmount;
|
||||
slot.currentCollateral = request.ask.collateral; // Even if he has collateral discounted, he is operating with full collateral
|
||||
|
||||
_addToMySlots(slot.host, slotId);
|
||||
|
||||
slot.state = SlotState.Filled;
|
||||
emit SlotFilled(requestId, slotIndex);
|
||||
if (context.slotsFilled == request.ask.slots) {
|
||||
|
||||
if (
|
||||
context.slotsFilled == request.ask.slots &&
|
||||
context.state != RequestState.Started // In case of repair, we already have the request started, so we prevent from "restarting it"
|
||||
) {
|
||||
context.state = RequestState.Started;
|
||||
context.startedAt = block.timestamp;
|
||||
emit RequestFulfilled(requestId);
|
||||
|
@ -298,6 +317,9 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
|||
_removeFromMySlots(slot.host, slotId);
|
||||
uint256 slotIndex = slot.slotIndex;
|
||||
delete _slots[slotId];
|
||||
delete _reservations[slotId]; // We purge all the reservations for the slot
|
||||
slot.state = SlotState.Repair;
|
||||
slot.requestId = requestId;
|
||||
context.slotsFilled -= 1;
|
||||
emit SlotFreed(requestId, slotIndex);
|
||||
_resetMissingProofs(slotId);
|
||||
|
|
|
@ -36,12 +36,13 @@ enum RequestState {
|
|||
}
|
||||
|
||||
enum SlotState {
|
||||
Free, // [default] not filled yet, or host has vacated the slot
|
||||
Free, // [default] not filled yet
|
||||
Filled, // host has filled slot
|
||||
Finished, // successfully completed
|
||||
Failed, // the request has failed
|
||||
Paid, // host has been paid
|
||||
Cancelled // when request was cancelled then slot is cancelled as well
|
||||
Cancelled, // when request was cancelled then slot is cancelled as well
|
||||
Repair // when slot slot was forcible freed (host was kicked out from hosting the slot because of too many missed proofs) and needs to be repaired
|
||||
}
|
||||
|
||||
library Requests {
|
||||
|
|
|
@ -257,6 +257,33 @@ describe("Marketplace", function () {
|
|||
expect(await marketplace.getHost(slotId(slot))).to.equal(host.address)
|
||||
})
|
||||
|
||||
it("gives discount on the collateral for repaired slot", async function () {
|
||||
await marketplace.reserveSlot(slot.request, slot.index)
|
||||
await marketplace.fillSlot(slot.request, slot.index, proof)
|
||||
await marketplace.freeSlot(slotId(slot))
|
||||
expect(await marketplace.slotState(slotId(slot))).to.equal(
|
||||
SlotState.Repair
|
||||
)
|
||||
|
||||
// We need to advance the time to next period, because filling slot
|
||||
// must not be done in the same period as for that period there was already proof
|
||||
// submitted with the previous `fillSlot` and the transaction would revert with "Proof already submitted".
|
||||
await advanceTimeForNextBlock(config.proofs.period + 1)
|
||||
|
||||
const startBalance = await token.balanceOf(host.address)
|
||||
const discountedCollateral =
|
||||
request.ask.collateral -
|
||||
(request.ask.collateral * config.collateral.repairRewardPercentage) /
|
||||
100
|
||||
await token.approve(marketplace.address, discountedCollateral)
|
||||
await marketplace.fillSlot(slot.request, slot.index, proof)
|
||||
const endBalance = await token.balanceOf(host.address)
|
||||
expect(startBalance - endBalance).to.equal(discountedCollateral)
|
||||
expect(await marketplace.slotState(slotId(slot))).to.equal(
|
||||
SlotState.Filled
|
||||
)
|
||||
})
|
||||
|
||||
it("fails to retrieve a request of an empty slot", async function () {
|
||||
expect(marketplace.getActiveSlot(slotId(slot))).to.be.revertedWith(
|
||||
"Slot is free"
|
||||
|
@ -371,11 +398,11 @@ describe("Marketplace", function () {
|
|||
|
||||
it("collects only requested collateral and not more", async function () {
|
||||
await token.approve(marketplace.address, request.ask.collateral * 2)
|
||||
const startBalanace = await token.balanceOf(host.address)
|
||||
const startBalance = await token.balanceOf(host.address)
|
||||
await marketplace.reserveSlot(slot.request, slot.index)
|
||||
await marketplace.fillSlot(slot.request, slot.index, proof)
|
||||
const endBalance = await token.balanceOf(host.address)
|
||||
expect(startBalanace - endBalance).to.eq(request.ask.collateral)
|
||||
expect(startBalance - endBalance).to.eq(request.ask.collateral)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1015,7 +1042,8 @@ describe("Marketplace", function () {
|
|||
})
|
||||
|
||||
describe("slot state", function () {
|
||||
const { Free, Filled, Finished, Failed, Paid, Cancelled } = SlotState
|
||||
const { Free, Filled, Finished, Failed, Paid, Cancelled, Repair } =
|
||||
SlotState
|
||||
let period, periodEnd
|
||||
|
||||
beforeEach(async function () {
|
||||
|
@ -1068,14 +1096,14 @@ describe("Marketplace", function () {
|
|||
expect(await marketplace.slotState(slotId(slot))).to.equal(Cancelled)
|
||||
})
|
||||
|
||||
it("changes to 'Free' when host frees the slot", async function () {
|
||||
it("changes to 'Repair' when host frees the slot", async function () {
|
||||
await marketplace.reserveSlot(slot.request, slot.index)
|
||||
await marketplace.fillSlot(slot.request, slot.index, proof)
|
||||
await marketplace.freeSlot(slotId(slot))
|
||||
expect(await marketplace.slotState(slotId(slot))).to.equal(Free)
|
||||
expect(await marketplace.slotState(slotId(slot))).to.equal(Repair)
|
||||
})
|
||||
|
||||
it("changes to 'Free' when too many proofs are missed", async function () {
|
||||
it("changes to 'Repair' when too many proofs are missed", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
while ((await marketplace.slotState(slotId(slot))) === Filled) {
|
||||
await waitUntilProofIsRequired(slotId(slot))
|
||||
|
@ -1084,7 +1112,7 @@ describe("Marketplace", function () {
|
|||
await mine()
|
||||
await marketplace.markProofAsMissing(slotId(slot), missedPeriod)
|
||||
}
|
||||
expect(await marketplace.slotState(slotId(slot))).to.equal(Free)
|
||||
expect(await marketplace.slotState(slotId(slot))).to.equal(Repair)
|
||||
})
|
||||
|
||||
it("changes to 'Failed' when request fails", async function () {
|
||||
|
@ -1271,7 +1299,9 @@ describe("Marketplace", function () {
|
|||
await advanceTimeForNextBlock(period + 1)
|
||||
await marketplace.markProofAsMissing(slotId(slot), missedPeriod)
|
||||
}
|
||||
expect(await marketplace.slotState(slotId(slot))).to.equal(SlotState.Free)
|
||||
expect(await marketplace.slotState(slotId(slot))).to.equal(
|
||||
SlotState.Repair
|
||||
)
|
||||
expect(await marketplace.getSlotCollateral(slotId(slot))).to.be.lte(
|
||||
minimum
|
||||
)
|
||||
|
@ -1299,7 +1329,9 @@ describe("Marketplace", function () {
|
|||
await marketplace.markProofAsMissing(slotId(slot), missedPeriod)
|
||||
missedProofs += 1
|
||||
}
|
||||
expect(await marketplace.slotState(slotId(slot))).to.equal(SlotState.Free)
|
||||
expect(await marketplace.slotState(slotId(slot))).to.equal(
|
||||
SlotState.Repair
|
||||
)
|
||||
expect(await marketplace.missingProofs(slotId(slot))).to.equal(0)
|
||||
expect(await marketplace.getSlotCollateral(slotId(slot))).to.be.lte(
|
||||
minimum
|
||||
|
|
|
@ -16,6 +16,7 @@ const SlotState = {
|
|||
Failed: 3,
|
||||
Paid: 4,
|
||||
Cancelled: 5,
|
||||
Repair: 6,
|
||||
}
|
||||
|
||||
function enableRequestAssertions() {
|
||||
|
|
Loading…
Reference in New Issue