feat: collateral fractions (#47)
Co-authored-by: Eric Mastro <github@egonat.me>
This commit is contained in:
parent
8b39ef8f4a
commit
2b5d079882
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -5,7 +5,8 @@ const { hexlify, randomBytes } = ethers.utils
|
|||
|
||||
const exampleConfiguration = () => ({
|
||||
collateral: {
|
||||
minimumAmount: 40,
|
||||
repairRewardPercentage: 10,
|
||||
maxNumberOfSlashes: 5,
|
||||
slashCriterion: 3,
|
||||
slashPercentage: 10,
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue