feat: repair reward

This commit is contained in:
Adam Uhlíř 2024-10-11 09:14:00 +02:00
parent 997696a20e
commit 4aacb6f485
No known key found for this signature in database
GPG Key ID: 1D17A9E81F76155B
5 changed files with 73 additions and 20 deletions

View File

@ -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

View File

@ -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);

View File

@ -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 {

View File

@ -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

View File

@ -16,6 +16,7 @@ const SlotState = {
Failed: 3,
Paid: 4,
Cancelled: 5,
Repair: 6,
}
function enableRequestAssertions() {