Assessment reports>Proof of Data Possession>Medium findings>Proof-set owner can reset ,proofSetLastProvenEpoch, to avoid accumulated fees
Category: Business Logic

Proof-set owner can reset proofSetLastProvenEpoch to avoid accumulated fees

Medium Severity
Medium Impact
Low Likelihood

Description

The nextProvingPeriod function, before initializing the next period, first removes all roots that were previously scheduled for removal by the proof-set owner.

If, as a result of removeRoots, all roots are deleted and the proof set becomes empty, the proofSetLastProvenEpoch and nextChallengeEpoch for this setId will be reset to zero.

function nextProvingPeriod(uint256 setId, uint256 challengeEpoch, bytes calldata extraData) public {
    [...]

    removeRoots(setId, removalsToProcess);
    [...]
    if (proofSetLeafCount[setId] == 0) {
        emit ProofSetEmpty(setId);
        proofSetLastProvenEpoch[setId] = NO_PROVEN_EPOCH;
        nextChallengeEpoch[setId] = NO_CHALLENGE_SCHEDULED;
    }
    [...]
}

The issue arises because proofSetLastProvenEpoch is used for fee calculation during provePossession execution. This value tracks how many blocks have passed since the last proof was provided. So, by design, this is a fee that is charged as time progresses, regardless of how often proofs are actually submitted.

However, if a long time has passed since the previous proving period, the proof-set owner can reset proofSetLastProvenEpoch to the current block.number by calling nextProvingPeriod after removing all roots. This effectively resets the block counter used in fee calculations, allowing them to avoid higher fees that would have accumulated over time had they not reset it first.

Impact

If a significant amount of time has passed since the last provePossession call, the proof-set owner could remove and re-add all roots to reset proofSetLastProvenEpoch and avoid paying a portion of the accrued fees.

Recommendations

We recommend to not reset proofSetLastProvenEpoch when all roots are removed. Instead, maintain the original timestamp to ensure that fee accumulation remains consistent.

This does not completely solve the issue, but the general problem of PDPVerifier fee avoidance through collusion between the market and the proof-set owner is not solvable.

This is because the PDPVerifier contract only sees snapshots of the execution of the storage agreement between the market and the proof-set owner, and the fee is integrated over time. These two properties mean that theoretically, the market could instruct the proof-set owner to use the PDPVerifier contract by submitting three checkpoints when it otherwise would submit one — the first one with zero data actually stored, the second one with the real checkpoint it wants proven, and the third with zero data stored. This would mean that the integral of the storage, as seen by the PDPVerifier, is close to zero, so it would charge a negligible fee — but, from the market's perspective, they are still obtaining the proper proofs they need. From the PDPVerifier's perspective, this use case is not distinguishable from a legitimate use case where storage is actually only needed and paid for in short bursts.

Additionally, without extra design elements that pin this contract to the reputation of the underlying protocol, the market can also redeploy this contract without any of the fee logic. This is a more likely solution than asking the proof-set owner to do anything unusual.

Remediation

The off-chain logic and documentation will ensure that removing and re-adding all the roots will be seen as a violation of the contract between the market and the proof-set owner, which will guard against this form of fee avoidance.

Zellic © 2025Back to top ↑