feat: collateral fractions (#47)

Co-authored-by: Eric Mastro <github@egonat.me>
This commit is contained in:
Adam Uhlíř 2023-03-30 11:11:21 +02:00 committed by GitHub
parent 8b39ef8f4a
commit 2b5d079882
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 67 additions and 13 deletions

View File

@ -9,9 +9,15 @@ struct MarketplaceConfig {
}
struct CollateralConfig {
uint256 minimumAmount; // frees slot when collateral drops below this minimum
uint256 slashCriterion; // amount of proofs missed that lead to slashing
uint256 slashPercentage; // percentage of the collateral that is slashed
/// @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/status-im/codex-contracts-eth/pull/47#issuecomment-1465511949.
uint8 repairRewardPercentage;
uint8 maxNumberOfSlashes; // frees slot when the number of slashing reaches this value
uint16 slashCriterion; // amount of proofs missed that lead to slashing
uint8 slashPercentage; // percentage of the collateral that is slashed
}
struct ProofConfig {

View File

@ -44,6 +44,10 @@ contract Marketplace is Proofs, StateRetrieval {
MarketplaceConfig memory configuration
) Proofs(configuration.proofs) marketplaceInvariant {
token = token_;
require(configuration.collateral.repairRewardPercentage <= 100, "Must be less than 100");
require(configuration.collateral.slashPercentage <= 100, "Must be less than 100");
require(configuration.collateral.maxNumberOfSlashes * configuration.collateral.slashPercentage <= 100, "Total slash percentage must be less then 100");
config = configuration;
}
@ -126,17 +130,17 @@ contract Marketplace is Proofs, StateRetrieval {
require(slotState(slotId) == SlotState.Filled, "Slot not accepting proofs");
_markProofAsMissing(slotId, period);
Slot storage slot = _slots[slotId];
Request storage request = _requests[slot.requestId];
if (missingProofs(slotId) % config.collateral.slashCriterion == 0) {
uint256 slashedAmount = (slot.currentCollateral * config.collateral.slashPercentage) / 100;
uint256 slashedAmount = (request.ask.collateral * config.collateral.slashPercentage) / 100;
slot.currentCollateral -= slashedAmount;
_funds.slashed += slashedAmount;
_funds.balance -= slashedAmount;
if (slot.currentCollateral < config.collateral.minimumAmount) {
// 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).
if (missingProofs(slotId) / config.collateral.slashCriterion >= config.collateral.maxNumberOfSlashes) {
// When the number of slashings is at or above the allowed amount,
// free the slot.
_forciblyFreeSlot(slotId);
}
}

View File

@ -8,7 +8,7 @@ struct Request {
address client;
Ask ask;
Content content;
uint256 expiry; // time at which this request timeouts if all slots are not filled and is pronounced cancelled
uint256 expiry; // timestamp as seconds since unix epoch at which this request expires
bytes32 nonce; // random nonce to differentiate between similar requests
}

View File

@ -2,7 +2,8 @@ async function deployMarketplace({ deployments, getNamedAccounts }) {
const token = await deployments.get("TestToken")
const configuration = {
collateral: {
minimumAmount: 40,
repairRewardPercentage: 10,
maxNumberOfSlashes: 5,
slashCriterion: 3,
slashPercentage: 10,
},

View File

@ -4,6 +4,7 @@
"scripts": {
"test": "npm run lint && hardhat test",
"start": "hardhat node --export deployment-localhost.json",
"compile": "hardhat compile",
"format": "prettier --write contracts/**/*.sol test/**/*.js",
"lint": "solhint contracts/**.sol"
},

View File

@ -29,6 +29,47 @@ const {
currentTime,
} = require("./evm")
describe('Marketplace constructor', function () {
let Marketplace, token, config
beforeEach(async function () {
await snapshot()
await ensureMinimumBlockHeight(256)
const TestToken = await ethers.getContractFactory("TestToken")
token = await TestToken.deploy()
Marketplace = await ethers.getContractFactory("TestMarketplace")
config = exampleConfiguration()
})
afterEach(async function () {
await revert()
})
function testPercentageOverflow(property) {
it(`should reject for ${property} overflowing percentage values`, async () => {
config.collateral[property] = 101
await expect(Marketplace.deploy(token.address, config)).to.be.revertedWith(
"Must be less than 100"
)
})
}
testPercentageOverflow('repairRewardPercentage')
testPercentageOverflow('slashPercentage')
it('should reject when total slash percentage exceeds 100%', async () => {
config.collateral.slashPercentage = 1
config.collateral.maxNumberOfSlashes = 101
await expect(Marketplace.deploy(token.address, config)).to.be.revertedWith(
"Total slash percentage must be less then 100"
)
})
})
describe("Marketplace", function () {
const proof = hexlify(randomBytes(42))
const config = exampleConfiguration()
@ -765,7 +806,7 @@ describe("Marketplace", function () {
})
it("frees slot when collateral slashed below minimum threshold", async function () {
const minimum = config.collateral.minimumAmount
const minimum = request.ask.collateral - (request.ask.collateral*config.collateral.maxNumberOfSlashes*config.collateral.slashPercentage)/100
await waitUntilStarted(marketplace, request, proof, token)
while ((await marketplace.slotState(slotId(slot))) === SlotState.Filled) {
expect(await marketplace.getSlotCollateral(slotId(slot))).to.be.gt(minimum)
@ -779,7 +820,7 @@ describe("Marketplace", function () {
})
it("free slot when minimum reached and resets missed proof counter", async function () {
const minimum = config.collateral.minimumAmount
const minimum = request.ask.collateral - (request.ask.collateral*config.collateral.maxNumberOfSlashes*config.collateral.slashPercentage)/100
await waitUntilStarted(marketplace, request, proof, token)
let missedProofs = 0
while ((await marketplace.slotState(slotId(slot))) === SlotState.Filled) {

View File

@ -5,7 +5,8 @@ const { hexlify, randomBytes } = ethers.utils
const exampleConfiguration = () => ({
collateral: {
minimumAmount: 40,
repairRewardPercentage: 10,
maxNumberOfSlashes: 5,
slashCriterion: 3,
slashPercentage: 10,
},