mirror of
https://github.com/status-im/dagger-contracts.git
synced 2025-01-12 07:14:50 +00:00
feat: repair reward (#193)
This commit is contained in:
parent
06f9f56cd2
commit
dfab6102e7
@ -301,7 +301,7 @@ rule functionsCausingSlotStateChanges(env e, method f) {
|
||||
assert slotStateBefore == Marketplace.SlotState.Finished && slotStateAfter == Marketplace.SlotState.Paid => canMakeSlotPaid(f);
|
||||
|
||||
// SlotState.Free -> SlotState.Filled
|
||||
assert slotStateBefore != Marketplace.SlotState.Filled && slotStateAfter == Marketplace.SlotState.Filled => canFillSlot(f);
|
||||
assert (slotStateBefore == Marketplace.SlotState.Free || slotStateBefore == Marketplace.SlotState.Repair) && slotStateAfter == Marketplace.SlotState.Filled => canFillSlot(f);
|
||||
}
|
||||
|
||||
rule allowedSlotStateChanges(env e, method f) {
|
||||
@ -326,8 +326,8 @@ rule allowedSlotStateChanges(env e, method f) {
|
||||
slotStateAfter == Marketplace.SlotState.Paid
|
||||
);
|
||||
|
||||
// SlotState.Filled only from Free
|
||||
assert slotStateBefore != Marketplace.SlotState.Filled && slotStateAfter == Marketplace.SlotState.Filled => slotStateBefore == Marketplace.SlotState.Free;
|
||||
// SlotState.Filled only from Free or Repair
|
||||
assert slotStateBefore != Marketplace.SlotState.Filled && slotStateAfter == Marketplace.SlotState.Filled => (slotStateBefore == Marketplace.SlotState.Free || slotStateBefore == Marketplace.SlotState.Repair);
|
||||
}
|
||||
|
||||
rule cancelledRequestsStayCancelled(env e, method f) {
|
||||
|
@ -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
|
||||
|
@ -126,7 +126,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Fills a slot. Reverts if an invalid proof of the slot data is
|
||||
* @notice Fills a slot. Reverts if an invalid proof of the slot data is
|
||||
provided.
|
||||
* @param requestId RequestId identifying the request containing the slot to
|
||||
fill.
|
||||
@ -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.New // Only New requests can "start" the requests
|
||||
) {
|
||||
context.state = RequestState.Started;
|
||||
context.startedAt = block.timestamp;
|
||||
emit RequestFulfilled(requestId);
|
||||
@ -178,7 +197,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Frees a slot, paying out rewards and returning collateral for
|
||||
* @notice Frees a slot, paying out rewards and returning collateral for
|
||||
finished or cancelled requests to the host that has filled the slot.
|
||||
* @param slotId id of the slot to free
|
||||
* @dev The host that filled the slot must have initiated the transaction
|
||||
@ -190,7 +209,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Frees a slot, paying out rewards and returning collateral for
|
||||
* @notice Frees a slot, paying out rewards and returning collateral for
|
||||
finished or cancelled requests.
|
||||
* @param slotId id of the slot to free
|
||||
* @param rewardRecipient address to send rewards to
|
||||
@ -280,7 +299,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Abandons the slot without returning collateral, effectively slashing the
|
||||
* @notice Abandons the slot without returning collateral, effectively slashing the
|
||||
entire collateral.
|
||||
* @param slotId SlotId of the slot to free.
|
||||
* @dev _slots[slotId] is deleted, resetting _slots[slotId].currentCollateral
|
||||
@ -296,10 +315,13 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
context.fundsToReturnToClient += _slotPayout(requestId, slot.filledAt);
|
||||
|
||||
_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.filledAt = 0;
|
||||
slot.currentCollateral = 0;
|
||||
slot.host = address(0);
|
||||
context.slotsFilled -= 1;
|
||||
emit SlotFreed(requestId, slotIndex);
|
||||
emit SlotFreed(requestId, slot.slotIndex);
|
||||
_resetMissingProofs(slotId);
|
||||
|
||||
Request storage request = _requests[requestId];
|
||||
@ -337,7 +359,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Pays out a host for duration of time that the slot was filled, and
|
||||
* @notice Pays out a host for duration of time that the slot was filled, and
|
||||
returns the collateral.
|
||||
* @dev The payouts are sent to the rewardRecipient, and collateral is returned
|
||||
to the host address.
|
||||
@ -367,7 +389,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Withdraws remaining storage request funds back to the client that
|
||||
* @notice Withdraws remaining storage request funds back to the client that
|
||||
deposited them.
|
||||
* @dev Request must be cancelled, failed or finished, and the
|
||||
transaction must originate from the depositor address.
|
||||
@ -378,7 +400,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Withdraws storage request funds to the provided address.
|
||||
* @notice Withdraws storage request funds to the provided address.
|
||||
* @dev Request must be expired, must be in RequestState.New, and the
|
||||
transaction must originate from the depositer address.
|
||||
* @param requestId the id of the request
|
||||
|
@ -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…
x
Reference in New Issue
Block a user