[Storage] Move markProofAsMissing() to Marketplace

This commit is contained in:
Mark Spanbroek 2023-01-09 15:52:27 +01:00 committed by markspanbroek
parent a7397981bb
commit 6ea2b77a8f
5 changed files with 128 additions and 103 deletions

View File

@ -12,6 +12,10 @@ contract Marketplace is Collateral, Proofs {
using EnumerableSet for EnumerableSet.Bytes32Set;
uint256 public immutable collateral;
uint256 public immutable minCollateralThreshold;
uint256 public immutable slashMisses;
uint256 public immutable slashPercentage;
MarketplaceFunds private funds;
mapping(RequestId => Request) private requests;
mapping(RequestId => RequestContext) private requestContexts;
@ -22,6 +26,9 @@ contract Marketplace is Collateral, Proofs {
constructor(
IERC20 _token,
uint256 _collateral,
uint256 _minCollateralThreshold,
uint256 _slashMisses,
uint256 _slashPercentage,
uint256 _proofPeriod,
uint256 _proofTimeout,
uint8 _proofDowntime
@ -31,6 +38,9 @@ contract Marketplace is Collateral, Proofs {
marketplaceInvariant
{
collateral = _collateral;
minCollateralThreshold = _minCollateralThreshold;
slashMisses = _slashMisses;
slashPercentage = _slashPercentage;
}
function myRequests() public view returns (RequestId[] memory) {
@ -119,6 +129,24 @@ contract Marketplace is Collateral, Proofs {
}
}
function markProofAsMissing(SlotId slotId, uint256 period)
public
slotMustAcceptProofs(slotId)
{
_markProofAsMissing(slotId, period);
address host = _host(slotId);
if (missingProofs(slotId) % slashMisses == 0) {
_slash(host, slashPercentage);
if (balanceOf(host) < minCollateralThreshold) {
// When the collateral drops below the minimum threshold, the slot
// needs to be freed so that there is enough remaining collateral to be
// distributed for repairs and rewards (with any leftover to be burnt).
_forciblyFreeSlot(slotId);
}
}
}
function _forciblyFreeSlot(SlotId slotId)
internal
slotMustAcceptProofs(slotId)

View File

@ -6,11 +6,6 @@ import "./Proofs.sol";
import "./Collateral.sol";
contract Storage is Marketplace {
uint256 public collateralAmount;
uint256 public slashMisses;
uint256 public slashPercentage;
uint256 public minCollateralThreshold;
constructor(
IERC20 token,
uint256 _proofPeriod,
@ -24,32 +19,12 @@ contract Storage is Marketplace {
Marketplace(
token,
_collateralAmount,
_minCollateralThreshold,
_slashMisses,
_slashPercentage,
_proofPeriod,
_proofTimeout,
_proofDowntime
)
{
collateralAmount = _collateralAmount;
slashMisses = _slashMisses;
slashPercentage = _slashPercentage;
minCollateralThreshold = _minCollateralThreshold;
}
function markProofAsMissing(SlotId slotId, uint256 period)
public
slotMustAcceptProofs(slotId)
{
_markProofAsMissing(slotId, period);
address host = _host(slotId);
if (missingProofs(slotId) % slashMisses == 0) {
_slash(host, slashPercentage);
if (balanceOf(host) < minCollateralThreshold) {
// When the collateral drops below the minimum threshold, the slot
// needs to be freed so that there is enough remaining collateral to be
// distributed for repairs and rewards (with any leftover to be burnt).
_forciblyFreeSlot(slotId);
}
}
}
{}
}

View File

@ -8,6 +8,9 @@ contract TestMarketplace is Marketplace {
constructor(
IERC20 _token,
uint256 _collateral,
uint256 _minCollateralThreshold,
uint256 _slashMisses,
uint256 _slashPercentage,
uint256 _proofPeriod,
uint256 _proofTimeout,
uint8 _proofDowntime
@ -15,6 +18,9 @@ contract TestMarketplace is Marketplace {
Marketplace(
_token,
_collateral,
_minCollateralThreshold,
_slashMisses,
_slashPercentage,
_proofPeriod,
_proofTimeout,
_proofDowntime
@ -40,7 +46,9 @@ contract TestMarketplace is Marketplace {
return _slot(slotId);
}
function testAcceptsProofs(SlotId slotId)
function testAcceptsProofs(
SlotId slotId
)
public
view
slotMustAcceptProofs(slotId)

View File

@ -27,6 +27,9 @@ const {
describe("Marketplace", function () {
const collateral = 100
const minCollateralThreshold = 40
const slashMisses = 3
const slashPercentage = 10
const proofPeriod = 10
const proofTimeout = 5
const proofDowntime = 64
@ -54,6 +57,9 @@ describe("Marketplace", function () {
marketplace = await Marketplace.deploy(
token.address,
collateral,
minCollateralThreshold,
slashMisses,
slashPercentage,
proofPeriod,
proofTimeout,
proofDowntime
@ -752,6 +758,86 @@ describe("Marketplace", function () {
})
})
describe("missing proofs", function () {
let period, periodOf, periodEnd
beforeEach(async function () {
period = (await marketplace.proofPeriod()).toNumber()
;({ periodOf, periodEnd } = periodic(period))
switchAccount(client)
await token.approve(marketplace.address, price(request))
await marketplace.requestStorage(request)
switchAccount(host)
await token.approve(marketplace.address, collateral)
await marketplace.deposit(collateral)
})
async function waitUntilProofIsRequired(id) {
await advanceTimeTo(periodEnd(periodOf(await currentTime())))
while (
!(
(await marketplace.isProofRequired(id)) &&
(await marketplace.getPointer(id)) < 250
)
) {
await advanceTime(period)
}
}
it("fails to mark proof as missing when cancelled", async function () {
await marketplace.fillSlot(slot.request, slot.index, proof)
await waitUntilCancelled(request)
let missedPeriod = periodOf(await currentTime())
await expect(
marketplace.markProofAsMissing(slotId(slot), missedPeriod)
).to.be.revertedWith("Slot not accepting proofs")
})
describe("slashing when missing proofs", function () {
it("reduces collateral when too many proofs are missing", async function () {
const id = slotId(slot)
await marketplace.fillSlot(slot.request, slot.index, proof)
for (let i = 0; i < slashMisses; i++) {
await waitUntilProofIsRequired(id)
let missedPeriod = periodOf(await currentTime())
await advanceTime(period)
await marketplace.markProofAsMissing(id, missedPeriod)
}
const expectedBalance = (collateral * (100 - slashPercentage)) / 100
expect(await marketplace.balanceOf(host.address)).to.equal(
expectedBalance
)
})
})
describe("freeing a slot", function () {
it("frees slot when collateral slashed below minimum threshold", async function () {
const id = slotId(slot)
await waitUntilStarted(marketplace, request, proof)
// max slashes before dropping below collateral threshold
const maxSlashes = 10
for (let i = 0; i < maxSlashes; i++) {
for (let j = 0; j < slashMisses; j++) {
await waitUntilProofIsRequired(id)
let missedPeriod = periodOf(await currentTime())
await advanceTime(period)
if (i === maxSlashes - 1 && j === slashMisses - 1) {
await expect(
await marketplace.markProofAsMissing(id, missedPeriod)
).to.emit(marketplace, "SlotFreed")
expect(await marketplace.getHost(id)).to.equal(AddressZero)
} else {
await marketplace.markProofAsMissing(id, missedPeriod)
}
}
}
})
})
})
describe("modifiers", function () {
beforeEach(async function () {
switchAccount(client)

View File

@ -38,7 +38,7 @@ describe("Storage", function () {
await token.mint(client.address, 1_000_000_000)
await token.mint(host.address, 1_000_000_000)
collateralAmount = await storage.collateralAmount()
collateralAmount = await storage.collateral()
slashMisses = await storage.slashMisses()
slashPercentage = await storage.slashPercentage()
minCollateralThreshold = await storage.minCollateralThreshold()
@ -66,78 +66,6 @@ describe("Storage", function () {
await expect(storage.withdraw()).not.to.be.reverted
})
})
describe("missing proofs", function () {
let period, periodOf, periodEnd
beforeEach(async function () {
period = (await storage.proofPeriod()).toNumber()
;({ periodOf, periodEnd } = periodic(period))
})
async function waitUntilProofIsRequired(id) {
await advanceTimeTo(periodEnd(periodOf(await currentTime())))
while (
!(
(await storage.isProofRequired(id)) &&
(await storage.getPointer(id)) < 250
)
) {
await advanceTime(period)
}
}
it("fails to mark proof as missing when cancelled", async function () {
await storage.fillSlot(slot.request, slot.index, proof)
await waitUntilCancelled(request)
let missedPeriod = periodOf(await currentTime())
await expect(
storage.markProofAsMissing(slotId(slot), missedPeriod)
).to.be.revertedWith("Slot not accepting proofs")
})
describe("slashing when missing proofs", function () {
it("reduces collateral when too many proofs are missing", async function () {
const id = slotId(slot)
await storage.fillSlot(slot.request, slot.index, proof)
for (let i = 0; i < slashMisses; i++) {
await waitUntilProofIsRequired(id)
let missedPeriod = periodOf(await currentTime())
await advanceTime(period)
await storage.markProofAsMissing(id, missedPeriod)
}
const expectedBalance =
(collateralAmount * (100 - slashPercentage)) / 100
expect(await storage.balanceOf(host.address)).to.equal(expectedBalance)
})
})
describe("freeing a slot", function () {
it("frees slot when collateral slashed below minimum threshold", async function () {
const id = slotId(slot)
await waitUntilStarted(storage, request, proof)
// max slashes before dropping below collateral threshold
const maxSlashes = 10
for (let i = 0; i < maxSlashes; i++) {
for (let j = 0; j < slashMisses; j++) {
await waitUntilProofIsRequired(id)
let missedPeriod = periodOf(await currentTime())
await advanceTime(period)
if (i === maxSlashes - 1 && j === slashMisses - 1) {
await expect(
await storage.markProofAsMissing(id, missedPeriod)
).to.emit(storage, "SlotFreed")
expect(await storage.getHost(id)).to.equal(AddressZero)
} else {
await storage.markProofAsMissing(id, missedPeriod)
}
}
}
})
})
})
})
// TODO: implement checking of actual proofs of storage, instead of dummy bool