mirror of
https://github.com/logos-storage/logos-storage-contracts-eth.git
synced 2026-01-02 21:33:08 +00:00
marketplace: burn tokens in vault when slashing
- move all collateral calculatons to separate library
This commit is contained in:
parent
639466662d
commit
bddbd02f02
@ -14,11 +14,9 @@ import "./StateRetrieval.sol";
|
||||
import "./Endian.sol";
|
||||
import "./Groth16.sol";
|
||||
import "./marketplace/VaultHelpers.sol";
|
||||
import "./marketplace/Collateral.sol";
|
||||
|
||||
contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
error Marketplace_RepairRewardPercentageTooHigh();
|
||||
error Marketplace_SlashPercentageTooHigh();
|
||||
error Marketplace_MaximumSlashingTooHigh();
|
||||
error Marketplace_InvalidExpiry();
|
||||
error Marketplace_InvalidMaxSlotLoss();
|
||||
error Marketplace_InsufficientSlots();
|
||||
@ -49,6 +47,8 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
using VaultHelpers for Vault;
|
||||
using VaultHelpers for RequestId;
|
||||
using VaultHelpers for Request;
|
||||
using Collateral for Request;
|
||||
using Collateral for CollateralConfig;
|
||||
using Timestamps for Timestamp;
|
||||
using Tokens for TokensPerSecond;
|
||||
|
||||
@ -75,14 +75,6 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
/// based on time they actually host the content
|
||||
Timestamp filledAt;
|
||||
uint64 slotIndex;
|
||||
/// @notice Tracks the current amount of host's collateral that is
|
||||
/// to be payed out at the end of Slot's lifespan.
|
||||
/// @dev When Slot is filled, the collateral is collected in amount
|
||||
/// of request.ask.collateralPerByte * request.ask.slotSize
|
||||
/// (== request.ask.collateralPerSlot() when using the AskHelpers library)
|
||||
/// @dev When Host is slashed for missing a proof the slashed amount is
|
||||
/// reflected in this variable
|
||||
uint256 currentCollateral;
|
||||
/// @notice address used for collateral interactions and identifying hosts
|
||||
address host;
|
||||
}
|
||||
@ -98,18 +90,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
IGroth16Verifier verifier
|
||||
) SlotReservations(config.reservations) Proofs(config.proofs, verifier) {
|
||||
_vault = vault_;
|
||||
|
||||
if (config.collateral.repairRewardPercentage > 100)
|
||||
revert Marketplace_RepairRewardPercentageTooHigh();
|
||||
if (config.collateral.slashPercentage > 100)
|
||||
revert Marketplace_SlashPercentageTooHigh();
|
||||
|
||||
if (
|
||||
config.collateral.maxNumberOfSlashes * config.collateral.slashPercentage >
|
||||
100
|
||||
) {
|
||||
revert Marketplace_MaximumSlashingTooHigh();
|
||||
}
|
||||
config.collateral.checkCorrectness();
|
||||
_config = config;
|
||||
}
|
||||
|
||||
@ -125,10 +106,6 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
return _vault;
|
||||
}
|
||||
|
||||
function currentCollateral(SlotId slotId) public view returns (uint256) {
|
||||
return _slots[slotId].currentCollateral;
|
||||
}
|
||||
|
||||
function requestStorage(Request calldata request) public {
|
||||
RequestId id = request.id();
|
||||
|
||||
@ -226,17 +203,15 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
context.slotsFilled += 1;
|
||||
|
||||
// Collect collateral
|
||||
uint128 collateralAmount;
|
||||
uint128 collateralPerSlot = request.ask.collateralPerSlot();
|
||||
uint128 collateralAmount = request.collateralPerSlot();
|
||||
uint128 designatedAmount = _config.collateral.designatedCollateral(
|
||||
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 =
|
||||
collateralPerSlot -
|
||||
((collateralPerSlot * _config.collateral.repairRewardPercentage) / 100);
|
||||
} else {
|
||||
collateralAmount = collateralPerSlot;
|
||||
collateralAmount -= _config.collateral.repairReward(collateralAmount);
|
||||
}
|
||||
|
||||
FundId fund = requestId.asFundId();
|
||||
@ -245,10 +220,9 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
TokensPerSecond rate = request.ask.pricePerSlotPerSecond();
|
||||
|
||||
_transferToVault(slot.host, fund, hostAccount, collateralAmount);
|
||||
_vault.designate(fund, hostAccount, designatedAmount);
|
||||
_vault.flow(fund, clientAccount, hostAccount, rate);
|
||||
|
||||
slot.currentCollateral = collateralPerSlot; // Even if he has collateral discounted, he is operating with full collateral
|
||||
|
||||
_addToMySlots(slot.host, slotId);
|
||||
|
||||
slot.state = SlotState.Filled;
|
||||
@ -343,23 +317,16 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
Slot storage slot = _slots[slotId];
|
||||
Request storage request = _requests[slot.requestId];
|
||||
|
||||
uint128 slashedAmount = (request.ask.collateralPerSlot() *
|
||||
_config.collateral.slashPercentage) / 100;
|
||||
|
||||
uint128 validatorRewardAmount = (slashedAmount *
|
||||
_config.collateral.validatorRewardPercentage) / 100;
|
||||
uint128 collateral = request.collateralPerSlot();
|
||||
uint128 slashedAmount = _config.collateral.slashAmount(collateral);
|
||||
uint128 validatorReward = _config.collateral.validatorReward(slashedAmount);
|
||||
|
||||
FundId fund = slot.requestId.asFundId();
|
||||
AccountId hostAccount = _vault.hostAccount(slot.host, slot.slotIndex);
|
||||
AccountId validatorAccount = _vault.validatorAccount(msg.sender);
|
||||
_vault.transfer(
|
||||
fund,
|
||||
hostAccount,
|
||||
validatorAccount,
|
||||
validatorRewardAmount
|
||||
);
|
||||
_vault.transfer(fund, hostAccount, validatorAccount, validatorReward);
|
||||
_vault.burnDesignated(fund, hostAccount, slashedAmount - validatorReward);
|
||||
|
||||
slot.currentCollateral -= slashedAmount;
|
||||
if (missingProofs(slotId) >= _config.collateral.maxNumberOfSlashes) {
|
||||
// When the number of slashings is at or above the allowed amount,
|
||||
// free the slot.
|
||||
@ -367,13 +334,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
to 0.
|
||||
*/
|
||||
/// Abandons the slot, burns all associated tokens
|
||||
function _forciblyFreeSlot(SlotId slotId) internal {
|
||||
Slot storage slot = _slots[slotId];
|
||||
RequestId requestId = slot.requestId;
|
||||
@ -393,7 +354,6 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
delete _reservations[slotId]; // We purge all the reservations for the slot
|
||||
slot.state = SlotState.Repair;
|
||||
slot.filledAt = Timestamp.wrap(0);
|
||||
slot.currentCollateral = 0;
|
||||
slot.host = address(0);
|
||||
context.slotsFilled -= 1;
|
||||
emit SlotFreed(requestId, slot.slotIndex);
|
||||
|
||||
@ -49,10 +49,6 @@ enum SlotState {
|
||||
}
|
||||
|
||||
library AskHelpers {
|
||||
function collateralPerSlot(Ask memory ask) internal pure returns (uint128) {
|
||||
return ask.collateralPerByte * ask.slotSize;
|
||||
}
|
||||
|
||||
function pricePerSlotPerSecond(
|
||||
Ask memory ask
|
||||
) internal pure returns (TokensPerSecond) {
|
||||
|
||||
@ -5,6 +5,9 @@ import "./Marketplace.sol";
|
||||
|
||||
// exposes internal functions of Marketplace for testing
|
||||
contract TestMarketplace is Marketplace {
|
||||
using VaultHelpers for RequestId;
|
||||
using VaultHelpers for Vault;
|
||||
|
||||
constructor(
|
||||
MarketplaceConfig memory config,
|
||||
Vault vault,
|
||||
@ -15,8 +18,11 @@ contract TestMarketplace is Marketplace {
|
||||
_forciblyFreeSlot(slotId);
|
||||
}
|
||||
|
||||
function getSlotCollateral(SlotId slotId) public view returns (uint256) {
|
||||
return _slots[slotId].currentCollateral;
|
||||
function getSlotBalance(SlotId slotId) public view returns (uint256) {
|
||||
Slot storage slot = _slots[slotId];
|
||||
FundId fund = slot.requestId.asFundId();
|
||||
AccountId account = vault().hostAccount(slot.host, slot.slotIndex);
|
||||
return vault().getBalance(fund, account);
|
||||
}
|
||||
|
||||
function challengeToFieldElement(
|
||||
|
||||
69
contracts/marketplace/Collateral.sol
Normal file
69
contracts/marketplace/Collateral.sol
Normal file
@ -0,0 +1,69 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
import "../Configuration.sol";
|
||||
import "../Requests.sol";
|
||||
|
||||
library Collateral {
|
||||
using Collateral for Request;
|
||||
using Collateral for CollateralConfig;
|
||||
|
||||
function checkCorrectness(
|
||||
CollateralConfig memory configuration
|
||||
) internal pure {
|
||||
require(
|
||||
configuration.repairRewardPercentage <= 100,
|
||||
Marketplace_RepairRewardPercentageTooHigh()
|
||||
);
|
||||
require(
|
||||
configuration.slashPercentage <= 100,
|
||||
Marketplace_SlashPercentageTooHigh()
|
||||
);
|
||||
require(
|
||||
configuration.maxNumberOfSlashes * configuration.slashPercentage <= 100,
|
||||
Marketplace_MaximumSlashingTooHigh()
|
||||
);
|
||||
}
|
||||
|
||||
function collateralPerSlot(
|
||||
Request storage request
|
||||
) internal view returns (uint128) {
|
||||
return request.ask.collateralPerByte * request.ask.slotSize;
|
||||
}
|
||||
|
||||
function slashAmount(
|
||||
CollateralConfig storage configuration,
|
||||
uint128 collateral
|
||||
) internal view returns (uint128) {
|
||||
return (collateral * configuration.slashPercentage) / 100;
|
||||
}
|
||||
|
||||
function repairReward(
|
||||
CollateralConfig storage configuration,
|
||||
uint128 collateral
|
||||
) internal view returns (uint128) {
|
||||
return (collateral * configuration.repairRewardPercentage) / 100;
|
||||
}
|
||||
|
||||
function validatorReward(
|
||||
CollateralConfig storage configuration,
|
||||
uint128 slashed
|
||||
) internal view returns (uint128) {
|
||||
return (slashed * configuration.validatorRewardPercentage) / 100;
|
||||
}
|
||||
|
||||
function designatedCollateral(
|
||||
CollateralConfig storage configuration,
|
||||
uint128 collateral
|
||||
) internal view returns (uint128) {
|
||||
uint8 slashes = configuration.maxNumberOfSlashes;
|
||||
uint128 slashing = configuration.slashAmount(collateral);
|
||||
uint128 validation = slashes * configuration.validatorReward(slashing);
|
||||
uint128 repair = configuration.repairReward(collateral);
|
||||
return collateral - validation - repair;
|
||||
}
|
||||
|
||||
error Marketplace_RepairRewardPercentageTooHigh();
|
||||
error Marketplace_SlashPercentageTooHigh();
|
||||
error Marketplace_MaximumSlashingTooHigh();
|
||||
}
|
||||
@ -1166,18 +1166,16 @@ describe("Marketplace", function () {
|
||||
await waitUntilProofIsRequired(id)
|
||||
let missedPeriod = periodOf(await currentTime())
|
||||
await advanceTime(period + 1)
|
||||
|
||||
const startBalance = await marketplace.getSlotBalance(id)
|
||||
await setNextBlockTimestamp(await currentTime())
|
||||
await marketplace.markProofAsMissing(id, missedPeriod)
|
||||
const endBalance = await marketplace.getSlotBalance(id)
|
||||
|
||||
const collateral = collateralPerSlot(request)
|
||||
const expectedBalance = Math.round(
|
||||
(collateral * (100 - slashPercentage)) / 100
|
||||
)
|
||||
const expectedSlash = Math.round((collateral * slashPercentage) / 100)
|
||||
|
||||
expect(
|
||||
BigNumber.from(expectedBalance).eq(
|
||||
await marketplace.getSlotCollateral(id)
|
||||
)
|
||||
)
|
||||
expect(endBalance).to.equal(startBalance - expectedSlash)
|
||||
})
|
||||
|
||||
it("rewards validator when marking proof as missing", async function () {
|
||||
@ -1211,62 +1209,30 @@ describe("Marketplace", function () {
|
||||
})
|
||||
})
|
||||
|
||||
it("frees slot when collateral slashed below minimum threshold", async function () {
|
||||
const collateral = collateralPerSlot(request)
|
||||
const minimum =
|
||||
collateral -
|
||||
(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
|
||||
)
|
||||
await waitUntilProofIsRequired(slotId(slot))
|
||||
const missedPeriod = periodOf(await currentTime())
|
||||
await advanceTime(period + 1)
|
||||
await marketplace.markProofAsMissing(slotId(slot), missedPeriod)
|
||||
}
|
||||
expect(await marketplace.slotState(slotId(slot))).to.equal(
|
||||
SlotState.Repair
|
||||
)
|
||||
expect(await marketplace.getSlotCollateral(slotId(slot))).to.be.lte(
|
||||
minimum
|
||||
)
|
||||
})
|
||||
describe("when slashing the maximum number of times", function () {
|
||||
beforeEach(async function () {
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
for (let i = 0; i < config.collateral.maxNumberOfSlashes; i++) {
|
||||
await waitUntilProofIsRequired(slotId(slot))
|
||||
const missedPeriod = periodOf(await currentTime())
|
||||
await advanceTime(period + 1)
|
||||
await marketplace.markProofAsMissing(slotId(slot), missedPeriod)
|
||||
}
|
||||
})
|
||||
|
||||
it("free slot when minimum reached and resets missed proof counter", async function () {
|
||||
const collateral = collateralPerSlot(request)
|
||||
const minimum =
|
||||
collateral -
|
||||
(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) {
|
||||
expect(await marketplace.getSlotCollateral(slotId(slot))).to.be.gt(
|
||||
minimum
|
||||
it("sets the state to 'repair'", async function () {
|
||||
expect(await marketplace.slotState(slotId(slot))).to.equal(
|
||||
SlotState.Repair
|
||||
)
|
||||
await waitUntilProofIsRequired(slotId(slot))
|
||||
const missedPeriod = periodOf(await currentTime())
|
||||
await advanceTime(period + 1)
|
||||
expect(await marketplace.missingProofs(slotId(slot))).to.equal(
|
||||
missedProofs
|
||||
)
|
||||
await marketplace.markProofAsMissing(slotId(slot), missedPeriod)
|
||||
missedProofs += 1
|
||||
}
|
||||
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
|
||||
)
|
||||
})
|
||||
|
||||
it("burns the balance", async function () {
|
||||
expect(await marketplace.getSlotBalance(slotId(slot))).to.equal(0)
|
||||
})
|
||||
|
||||
it("resets missed proof counter", async function () {
|
||||
expect(await marketplace.missingProofs(slotId(slot))).to.equal(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user