slash first then check collateral threshold

Once a proof is marked as missing, if that missing proof is enough to slash a host, first slash the host, then check the hosts balance. If the balance has dropped below the minimum allowable collateral threshold, then remove them from the slot.
This commit is contained in:
Eric Mastro 2022-09-16 14:58:51 +10:00 committed by Eric Mastro
parent 9050a0d52d
commit 1b216f6655
3 changed files with 49 additions and 72 deletions

View File

@ -47,19 +47,11 @@ contract Collateral is AccountLocks {
assert(token.transfer(msg.sender, amount)); assert(token.transfer(msg.sender, amount));
} }
function _slashAmount(address account, uint256 percentage)
internal
view
returns (uint256)
{
return (balanceOf(account) * percentage) / 100;
}
function _slash(address account, uint256 percentage) function _slash(address account, uint256 percentage)
internal internal
collateralInvariant collateralInvariant
{ {
uint256 amount = _slashAmount(account, percentage); uint256 amount = (balanceOf(account) * percentage) / 100;
funds.slashed += amount; funds.slashed += amount;
subtract(account, amount); subtract(account, amount);
} }

View File

@ -87,18 +87,15 @@ contract Storage is Collateral, Marketplace {
_markProofAsMissing(slotId, period); _markProofAsMissing(slotId, period);
address host = _host(slotId); address host = _host(slotId);
if (_missed(slotId) % slashMisses == 0) { if (_missed(slotId) % slashMisses == 0) {
_slash(host, slashPercentage);
uint256 slashAmount = _slashAmount(host, slashPercentage); if (balanceOf(host) < minCollateralThreshold) {
if (balanceOf(host) - slashAmount < minCollateralThreshold) {
// If host has been slashed enough such that the next slashing would // If host has been slashed enough such that the next slashing would
// cause the collateral to drop below the minimum threshold, the slot // cause the collateral to drop below the minimum threshold, the slot
// needs to be freed so that there is enough remaining collateral to be // needs to be freed so that there is enough remaining collateral to be
// distributed for repairs and rewards (with any leftover to be burnt). // distributed for repairs and rewards (with any leftover to be burnt).
_freeSlot(slotId); _freeSlot(slotId);
} }
else {
_slash(host, slashPercentage);
}
} }
} }
} }

View File

@ -82,7 +82,7 @@ describe("Storage", function () {
}) })
}) })
describe("slashing when missing proofs", function () { describe("missing proofs", function () {
let period, periodOf, periodEnd let period, periodOf, periodEnd
beforeEach(async function () { beforeEach(async function () {
@ -102,17 +102,51 @@ describe("Storage", function () {
} }
} }
it("reduces collateral when too many proofs are missing", async function () { describe("slashing when missing proofs", function () {
const id = slotId(slot) it("reduces collateral when too many proofs are missing", async function () {
await storage.fillSlot(slot.request, slot.index, proof) const id = slotId(slot)
for (let i = 0; i < slashMisses; i++) { await storage.fillSlot(slot.request, slot.index, proof)
await waitUntilProofIsRequired(id) for (let i = 0; i < slashMisses; i++) {
let missedPeriod = periodOf(await currentTime()) await waitUntilProofIsRequired(id)
await advanceTime(period) let missedPeriod = periodOf(await currentTime())
await storage.markProofAsMissing(id, missedPeriod) await advanceTime(period)
} await storage.markProofAsMissing(id, missedPeriod)
const expectedBalance = (collateralAmount * (100 - slashPercentage)) / 100 }
expect(await storage.balanceOf(host.address)).to.equal(expectedBalance) 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 waitUntilAllSlotsFilled(
storage,
request.ask.slots,
slot.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")
await expect(storage.getSlot(id)).to.be.revertedWith("Slot empty")
} else {
await storage.markProofAsMissing(id, missedPeriod)
}
}
}
})
}) })
}) })
@ -192,53 +226,7 @@ describe("Storage", function () {
}) })
}) })
describe("freeing a slot", function () {
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("frees slot when collateral slashed below minimum threshold", async function () {
const id = slotId(slot)
await waitUntilAllSlotsFilled(
storage,
request.ask.slots,
slot.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")
await expect(storage.getSlot(id)).to.be.revertedWith("Slot empty")
} else {
await storage.markProofAsMissing(id, missedPeriod)
}
}
}
})
})
}) })
// TODO: implement checking of actual proofs of storage, instead of dummy bool // TODO: implement checking of actual proofs of storage, instead of dummy bool