Skip to content

Commit

Permalink
feat: repair reward
Browse files Browse the repository at this point in the history
  • Loading branch information
AuHau committed Oct 11, 2024
1 parent 997696a commit 4aacb6f
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 20 deletions.
5 changes: 1 addition & 4 deletions contracts/Configuration.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 27 additions & 5 deletions contracts/Marketplace.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
5 changes: 3 additions & 2 deletions contracts/Requests.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
50 changes: 41 additions & 9 deletions test/Marketplace.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
})
})

Expand Down Expand Up @@ -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 () {
Expand Down Expand Up @@ -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))
Expand All @@ -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 () {
Expand Down Expand Up @@ -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
)
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions test/requests.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const SlotState = {
Failed: 3,
Paid: 4,
Cancelled: 5,
Repair: 6,
}

function enableRequestAssertions() {
Expand Down

0 comments on commit 4aacb6f

Please sign in to comment.