From eaefd332420ace9ed04f056cd484788056491797 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Fri, 15 May 2026 19:41:57 -0500 Subject: [PATCH 01/22] feat(cleanup): add data set cleanup bond and cleanupPieces function Assisted-by: Claude:claude-sonnet-4-6 --- src/Fees.sol | 8 ++ src/PDPVerifier.sol | 179 +++++++++++++++++------ src/PDPVerifierLayout.sol | 2 + src/interfaces/IPDPVerifier.sol | 4 + test/CleanupPieces.t.sol | 242 ++++++++++++++++++++++++++++++++ test/PDPVerifier.t.sol | 165 ++++++++++++---------- test/PDPVerifierProofTest.t.sol | 8 +- 7 files changed, 487 insertions(+), 121 deletions(-) create mode 100644 test/CleanupPieces.t.sol diff --git a/src/Fees.sol b/src/Fees.sol index 8b80600..d9fbf3b 100644 --- a/src/Fees.sol +++ b/src/Fees.sol @@ -10,6 +10,9 @@ library PDPFees { // 0.1 FIL uint256 constant SYBIL_FEE = FIL_TO_ATTO_FIL / 10; + // 0.1 FIL cleanup bond held per data set, returned to whoever finalizes cleanup + uint256 constant CLEANUP_DEPOSIT = FIL_TO_ATTO_FIL / 10; + // Default FIL-based proof fee: 0.00023 FIL per TiB (used for initialization) // Based on: 0.00067 USD per TiB / 2.88 USD per FIL = 0.00023 FIL per TiB uint96 constant DEFAULT_FEE_PER_TIB = 230000 gwei; // 0.00023 FIL in attoFIL @@ -34,4 +37,9 @@ library PDPFees { function sybilFee() internal pure returns (uint256) { return SYBIL_FEE; } + + // cleanup deposit is held per data set and paid out to whoever completes cleanup. 0.1 FIL + function cleanupDeposit() internal pure returns (uint256) { + return CLEANUP_DEPOSIT; + } } diff --git a/src/PDPVerifier.sol b/src/PDPVerifier.sol index db625c3..3a32d77 100644 --- a/src/PDPVerifier.sol +++ b/src/PDPVerifier.sol @@ -52,6 +52,11 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { uint256 public constant MAX_ENQUEUED_REMOVALS = 2000; uint256 public constant NO_CHALLENGE_SCHEDULED = 0; uint256 public constant NO_PROVEN_EPOCH = 0; + // Sentinel written to nextChallengeEpoch[setId] when a data set enters cleanup mode after deleteDataSet. + // Real challenge epochs are block numbers bounded well below type(uint256).max. + uint256 public constant CLEANUP_MODE_SENTINEL = type(uint256).max; + // Number of blocks of SP inactivity after which permissionless deletion/cleanup is allowed (~30 days on mainnet). + uint256 public constant INACTIVITY_WINDOW = 86400; // Upgrade sequence number, used by Initializable.reinitializer uint64 private immutable REINITIALIZER_VERSION; @@ -174,6 +179,11 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { PlannedUpgrade public nextUpgrade; + // FIL deposit collected at createDataSet and returned to whoever finalizes cleanup for that data set. + mapping(uint256 => uint256) cleanupDeposit; + // Block number when deleteDataSet was called, used to gate permissionless cleanupPieces calls. + mapping(uint256 => uint256) cleanupModeEpoch; + // Methods /// @custom:oz-upgrades-unsafe-allow constructor @@ -199,7 +209,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { feeStatus.nextFeePerTiB = PDPFees.DEFAULT_FEE_PER_TIB; } - string public constant VERSION = "3.2.0"; + string public constant VERSION = "3.3.0"; event ContractUpgraded(string version, address implementation); event UpgradeAnnounced(PlannedUpgrade plannedUpgrade); @@ -228,36 +238,30 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { require(success, "Burn failed"); } - // Validates msg.value meets sybil fee requirement and burns the fee. - // Returns the sybil fee amount for later refund calculation. - function _validateAndBurnSybilFee() internal returns (uint256 sybilFee) { - sybilFee = PDPFees.sybilFee(); - require(msg.value >= sybilFee, "sybil fee not met"); - burnFee(sybilFee); - } - - // Refunds any amount sent over the sybil fee back to msg.sender. + // Handles fee payment for dataset creation: requires cleanup deposit in FIL always, + // plus sybil fee either via USDFC (usdfcBurned=true) or FIL. Stores the deposit for setId. // Must be called after all state changes to avoid re-entrancy issues. - function _refundExcessSybilFee(uint256 sybilFee) internal { - if (msg.value > sybilFee) { - (bool success,) = msg.sender.call{value: msg.value - sybilFee}(""); - require(success, "Transfer failed."); - } - } - - function ensureBurned(bool usdfcBurned, bool defaultToFilBurn) internal { - if (!usdfcBurned) { - if (defaultToFilBurn) { - uint256 sybilFee = _validateAndBurnSybilFee(); - _refundExcessSybilFee(sybilFee); - } else { - revert UsdfcSybilFeeNotMet(); + function _handleFeesWithDeposit(uint256 setId, bool usdfcBurned) internal { + uint256 deposit = PDPFees.cleanupDeposit(); + cleanupDeposit[setId] = deposit; + + if (usdfcBurned) { + // USDFC covers sybil fee; FIL msg.value must cover just the cleanup deposit + require(msg.value >= deposit, "cleanup deposit required"); + uint256 excess = msg.value - deposit; + if (excess > 0) { + (bool success,) = msg.sender.call{value: excess}(""); + if (!success) revert FilRefundFailed(); } } else { - // USDFC burned, refund any FIL sent - if (msg.value > 0) { - (bool success,) = msg.sender.call{value: msg.value}(""); - if (!success) revert FilRefundFailed(); + // FIL covers both sybil fee and cleanup deposit + uint256 required = PDPFees.sybilFee() + deposit; + require(msg.value >= required, "insufficient payment"); + burnFee(PDPFees.sybilFee()); + uint256 excess = msg.value - required; + if (excess > 0) { + (bool success,) = msg.sender.call{value: excess}(""); + require(success, "Transfer failed."); } } } @@ -279,9 +283,10 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { return nextDataSetId; } - // Returns false if the data set is 1) not yet created 2) deleted + // Returns false if the data set is 1) not yet created 2) deleted or in cleanup mode function dataSetLive(uint256 setId) public view returns (bool) { - return setId < nextDataSetId && storageProvider[setId] != address(0); + return setId < nextDataSetId && storageProvider[setId] != address(0) + && nextChallengeEpoch[setId] != CLEANUP_MODE_SENTINEL; } // Returns false if the data set is not live or if the piece id is 1) not yet created 2) deleted @@ -600,8 +605,8 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { // Parameters: // - listenerAddr: Address of PDPListener contract to receive callbacks (can be address(0) for no listener) // - extraData: Arbitrary bytes passed to listener's dataSetCreated callback - // - defaultToFilBurn: If true, falls back to FIL burn when USDFC not available. If false, reverts. - // - msg.value: Must include sybil fee (PDPFees.sybilFee()) when using FIL fallback, excess is refunded + // - msg.value: Must always include cleanup deposit (PDPFees.cleanupDeposit()). When not using USDFC, + // must also include sybil fee (PDPFees.sybilFee()). Excess is refunded. // // Returns: The newly created data set ID // @@ -610,29 +615,118 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { uint256 balanceBefore = _getPaymentsUsdfcBalance(); uint256 setId = _createDataSet(listenerAddr, extraData); uint256 balanceAfter = _getPaymentsUsdfcBalance(); - bool defaultToFilBurn = msg.value > 0; - ensureBurned(balanceAfter >= balanceBefore + USDFC_SYBIL_FEE, defaultToFilBurn); + _handleFeesWithDeposit(setId, balanceAfter >= balanceBefore + USDFC_SYBIL_FEE); return setId; } - // Removes a data set. Must be called by the storage provider. + // Removes a data set. Normally called by the storage provider; permissionless after INACTIVITY_WINDOW + // blocks of SP inactivity (measured from dataSetLastProvenEpoch). + // + // After this call the data set is no longer live. If pieces remain, cleanup mode is entered and + // cleanupPieces must be called to finish storage teardown and collect the cleanup deposit. + // For zero-piece data sets, cleanup is finalized immediately and the deposit is paid to msg.sender. function deleteDataSet(uint256 setId, bytes calldata extraData) public { if (setId >= nextDataSetId) { revert("data set id out of bounds"); } - require(storageProvider[setId] == msg.sender, "Only the storage provider can delete data sets"); + address sp = storageProvider[setId]; + require(sp != address(0), "data set not live"); + require(nextChallengeEpoch[setId] != CLEANUP_MODE_SENTINEL, "data set already in cleanup"); + + // Permissionless if the SP has been inactive for more than INACTIVITY_WINDOW blocks. + if (block.number <= dataSetLastProvenEpoch[setId] + INACTIVITY_WINDOW) { + require(msg.sender == sp, "Only the storage provider can delete data sets"); + } + uint256 deletedLeafCount = dataSetLeafCount[setId]; dataSetLeafCount[setId] = 0; - storageProvider[setId] = address(0); - nextChallengeEpoch[setId] = 0; - dataSetLastProvenEpoch[setId] = NO_PROVEN_EPOCH; address listenerAddr = dataSetListener[setId]; if (listenerAddr != address(0)) { PDPListener(listenerAddr).dataSetDeleted(setId, deletedLeafCount, extraData); } emit DataSetDeleted(setId, deletedLeafCount); + + if (nextPieceId[setId] == 0) { + // Zero-piece data set: finalize cleanup immediately and pay deposit to caller. + _finalizeCleanup(setId); + } else { + // Pieces remain: enter cleanup mode. storageProvider and dataSetLastProvenEpoch are kept + // so cleanupPieces can apply the same permission gate. + nextChallengeEpoch[setId] = CLEANUP_MODE_SENTINEL; + cleanupModeEpoch[setId] = block.number; + } + } + + // Releases storage for a deleted data set piece-by-piece. Can only be called after deleteDataSet + // has placed the data set in cleanup mode (nextChallengeEpoch == CLEANUP_MODE_SENTINEL), or for + // legacy data sets deleted before this feature was added (storageProvider == 0 && nextPieceId > 0). + // + // The caller gate mirrors deleteDataSet: SP-exclusive within INACTIVITY_WINDOW blocks of deleteDataSet, + // permissionless after. Legacy data sets are always permissionless. + // + // On the final call that clears all pieces, all remaining data set state is also cleared and + // the cleanup deposit is transferred to msg.sender. Returns true when cleanup is complete. + function cleanupPieces(uint256 setId, uint256 maxPieces) external returns (bool done) { + require(maxPieces > 0, "maxPieces must be greater than 0"); + + bool isCleanupMode = nextChallengeEpoch[setId] == CLEANUP_MODE_SENTINEL; + // Legacy data sets deleted before this feature: storageProvider already zeroed, pieces remain. + bool isLegacyDataset = storageProvider[setId] == address(0) && nextPieceId[setId] > 0; + + require(isCleanupMode || isLegacyDataset, "data set not in cleanup mode"); + + if (isCleanupMode) { + // Same inactivity gate as deleteDataSet, anchored to when cleanup mode was entered. + if (block.number <= cleanupModeEpoch[setId] + INACTIVITY_WINDOW) { + require(msg.sender == storageProvider[setId], "Only the storage provider can clean up pieces"); + } + } + + uint256 pieceCount = nextPieceId[setId]; + uint256 toClean = pieceCount < maxPieces ? pieceCount : maxPieces; + + for (uint256 i = 0; i < toClean; i++) { + uint256 pieceId = pieceCount - 1 - i; + delete pieceCids[setId][pieceId]; + delete pieceLeafCounts[setId][pieceId]; + delete sumTreeCounts[setId][pieceId]; + } + + nextPieceId[setId] = pieceCount - toClean; + + if (nextPieceId[setId] == 0) { + _finalizeCleanup(setId); + done = true; + } + } + + // Clears all remaining singleton state for a data set and transfers the cleanup deposit to msg.sender. + // Must only be called when nextPieceId[setId] == 0. + function _finalizeCleanup(uint256 setId) internal { + // Clear scheduled removal bitmap entries before deleting the array. + uint256[] storage removals = scheduledRemovals[setId]; + for (uint256 i = 0; i < removals.length; i++) { + delete scheduledRemovalsBitmap[setId][removals[i] >> 8]; + } + delete scheduledRemovals[setId]; + + delete dataSetListener[setId]; + delete dataSetProposedStorageProvider[setId]; + delete challengeRange[setId]; + delete storageProvider[setId]; + delete dataSetLastProvenEpoch[setId]; + delete nextChallengeEpoch[setId]; + delete cleanupModeEpoch[setId]; + + uint256 deposit = cleanupDeposit[setId]; + delete cleanupDeposit[setId]; + + if (deposit > 0) { + (bool success,) = msg.sender.call{value: deposit}(""); + require(success, "Deposit transfer failed"); + } } // Appends pieces to a data set. Optionally creates a new data set if setId == 0. @@ -676,8 +770,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { _addPiecesToDataSet(newSetId, pieceData, addPayload); } - bool defaultToFilBurn = msg.value > 0; - ensureBurned(balanceAfter >= balanceBefore + USDFC_SYBIL_FEE, defaultToFilBurn); + _handleFeesWithDeposit(newSetId, balanceAfter >= balanceBefore + USDFC_SYBIL_FEE); return newSetId; } else { // Adding to an existing set; no fee should be sent and listenerAddr must be zero @@ -878,6 +971,10 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { return PDPFees.sybilFee(); } + function FIL_CLEANUP_DEPOSIT() external pure returns (uint256) { + return PDPFees.cleanupDeposit(); + } + // Public getters for packed fee status function feePerTiB() public view returns (uint96) { return _currentFeePerTiB(); diff --git a/src/PDPVerifierLayout.sol b/src/PDPVerifierLayout.sol index fc20391..986490f 100644 --- a/src/PDPVerifierLayout.sol +++ b/src/PDPVerifierLayout.sol @@ -22,3 +22,5 @@ bytes32 constant DATA_SET_PROPOSED_STORAGE_PROVIDER_SLOT = bytes32(uint256(13)); bytes32 constant DATA_SET_LAST_PROVEN_EPOCH_SLOT = bytes32(uint256(14)); bytes32 constant FEE_STATUS_SLOT = bytes32(uint256(15)); bytes32 constant NEXT_UPGRADE_SLOT = bytes32(uint256(16)); +bytes32 constant CLEANUP_DEPOSIT_SLOT = bytes32(uint256(17)); +bytes32 constant CLEANUP_MODE_EPOCH_SLOT = bytes32(uint256(18)); diff --git a/src/interfaces/IPDPVerifier.sol b/src/interfaces/IPDPVerifier.sol index e339da4..f9c28ba 100644 --- a/src/interfaces/IPDPVerifier.sol +++ b/src/interfaces/IPDPVerifier.sol @@ -30,6 +30,7 @@ interface IPDPVerifier is IPDPEvents { function claimDataSetStorageProvider(uint256 setId, bytes calldata extraData) external; function createDataSet(address listenerAddr, bytes calldata extraData) external payable returns (uint256); function deleteDataSet(uint256 setId, bytes calldata extraData) external; + function cleanupPieces(uint256 setId, uint256 maxPieces) external returns (bool done); function addPieces(uint256 setId, address listenerAddr, Cids.Cid[] calldata pieceData, bytes calldata extraData) external payable @@ -50,4 +51,7 @@ interface IPDPVerifier is IPDPEvents { // FIL sybil fee amount function FIL_SYBIL_FEE() external pure returns (uint256); + + // FIL cleanup deposit amount held per data set + function FIL_CLEANUP_DEPOSIT() external pure returns (uint256); } diff --git a/test/CleanupPieces.t.sol b/test/CleanupPieces.t.sol new file mode 100644 index 0000000..9e6801f --- /dev/null +++ b/test/CleanupPieces.t.sol @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +pragma solidity ^0.8.13; + +import {MockFVMTest} from "fvm-solidity/mocks/MockFVMTest.sol"; +import {Cids} from "../src/Cids.sol"; +import {PDPVerifier, PDPListener} from "../src/PDPVerifier.sol"; +import {MyERC1967Proxy} from "../src/ERC1967Proxy.sol"; +import {PDPFees} from "../src/Fees.sol"; +import {PDPRecordKeeper} from "../src/SimplePDPService.sol"; +import {PieceHelper} from "./PieceHelper.t.sol"; +import {NEW_DATA_SET_SENTINEL} from "../src/PDPVerifier.sol"; + +contract TestListener is PDPListener, PDPRecordKeeper { + function storageProviderChanged(uint256, address, address, bytes calldata) external override {} + + function dataSetCreated(uint256 dataSetId, address creator, bytes calldata extraData) external override { + receiveDataSetEvent(dataSetId, PDPRecordKeeper.OperationType.CREATE, abi.encode(creator, extraData)); + } + + function dataSetDeleted(uint256 dataSetId, uint256 deletedLeafCount, bytes calldata) external override { + receiveDataSetEvent(dataSetId, PDPRecordKeeper.OperationType.DELETE, abi.encode(deletedLeafCount)); + } + + function piecesAdded(uint256 dataSetId, uint256 firstAdded, Cids.Cid[] calldata pieceData, bytes calldata) + external + override + { + receiveDataSetEvent(dataSetId, PDPRecordKeeper.OperationType.ADD, abi.encode(firstAdded, pieceData)); + } + + function piecesScheduledRemove(uint256 dataSetId, uint256[] calldata pieceIds, bytes calldata) external override { + receiveDataSetEvent(dataSetId, PDPRecordKeeper.OperationType.REMOVE_SCHEDULED, abi.encode(pieceIds)); + } + + function possessionProven(uint256 dataSetId, uint256 challengedLeafCount, uint256 seed, uint256 challengeCount) + external + override + { + receiveDataSetEvent( + dataSetId, + PDPRecordKeeper.OperationType.PROVE_POSSESSION, + abi.encode(challengedLeafCount, seed, challengeCount) + ); + } + + function nextProvingPeriod(uint256 dataSetId, uint256 challengeEpoch, uint256 leafCount, bytes calldata) + external + override + { + receiveDataSetEvent( + dataSetId, PDPRecordKeeper.OperationType.NEXT_PROVING_PERIOD, abi.encode(challengeEpoch, leafCount) + ); + } +} + +contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { + uint256 constant CHALLENGE_FINALITY_DELAY = 2; + + PDPVerifier pdpVerifier; + TestListener listener; + bytes empty = new bytes(0); + + receive() external payable {} + + function setUp() public override { + super.setUp(); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1, address(0), 1, address(0)); + bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, CHALLENGE_FINALITY_DELAY); + MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); + pdpVerifier = PDPVerifier(address(proxy)); + listener = new TestListener(); + } + + function _createAndPopulate(uint256 numPieces) internal returns (uint256 setId) { + setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) + ); + if (numPieces > 0) { + Cids.Cid[] memory pieces = new Cids.Cid[](numPieces); + for (uint256 i = 0; i < numPieces; i++) { + pieces[i] = makeSamplePiece(2); + } + pdpVerifier.addPieces(setId, address(0), pieces, empty); + } + } + + // --- cleanupPieces basics --- + + function testCleanupPiecesIncrementalBatches() public { + uint256 setId = _createAndPopulate(3); + pdpVerifier.deleteDataSet(setId, empty); + assertFalse(pdpVerifier.dataSetLive(setId)); + + // clean 2 of 3 + bool done = pdpVerifier.cleanupPieces(setId, 2); + assertFalse(done, "should not be done after 2 of 3"); + + // clean last piece + uint256 balanceBefore = address(this).balance; + done = pdpVerifier.cleanupPieces(setId, 1); + assertTrue(done, "should be done after last piece"); + assertEq(address(this).balance - balanceBefore, PDPFees.cleanupDeposit(), "deposit returned on completion"); + } + + function testCleanupPiecesInOneCall() public { + uint256 setId = _createAndPopulate(2); + pdpVerifier.deleteDataSet(setId, empty); + + uint256 balanceBefore = address(this).balance; + bool done = pdpVerifier.cleanupPieces(setId, 100); + assertTrue(done); + assertEq(address(this).balance - balanceBefore, PDPFees.cleanupDeposit()); + } + + function testCleanupPiecesDepositNotPaidUntilComplete() public { + uint256 setId = _createAndPopulate(3); + pdpVerifier.deleteDataSet(setId, empty); + + uint256 balanceBefore = address(this).balance; + pdpVerifier.cleanupPieces(setId, 2); + assertEq(address(this).balance, balanceBefore, "no deposit paid for partial cleanup"); + } + + // --- zero-piece data sets --- + + function testZeroPieceDataSetFinalizesAtDelete() public { + uint256 balanceBefore = address(this).balance; + uint256 setId = _createAndPopulate(0); // burns sybilFee, holds cleanupDeposit + + pdpVerifier.deleteDataSet(setId, empty); // returns cleanupDeposit immediately + + // Net cost is only the sybil fee; the cleanup deposit was returned at delete time + assertEq(balanceBefore - address(this).balance, PDPFees.sybilFee(), "net cost is only sybil fee"); + assertFalse(pdpVerifier.dataSetLive(setId)); + } + + function testZeroPieceDataSetCleanupPiecesReverts() public { + uint256 setId = _createAndPopulate(0); + pdpVerifier.deleteDataSet(setId, empty); + + // After full finalization, cleanupPieces should revert + vm.expectRevert("data set not in cleanup mode"); + pdpVerifier.cleanupPieces(setId, 10); + } + + // --- caller gating --- + + function testOnlySpCanCleanWithinInactivityWindow() public { + uint256 setId = _createAndPopulate(1); + pdpVerifier.deleteDataSet(setId, empty); + + // block.number (1) <= cleanupModeEpoch + INACTIVITY_WINDOW, so only SP can call + address notSp = address(0xBEEF); + vm.prank(notSp); + vm.expectRevert("Only the storage provider can clean up pieces"); + pdpVerifier.cleanupPieces(setId, 10); + + // SP succeeds + bool done = pdpVerifier.cleanupPieces(setId, 10); + assertTrue(done); + } + + function testCleanupPermissionlessAfterInactivityWindow() public { + uint256 setId = _createAndPopulate(1); + pdpVerifier.deleteDataSet(setId, empty); + + vm.roll(block.number + pdpVerifier.INACTIVITY_WINDOW() + 1); + + address anyone = address(0xCAFE); + vm.deal(anyone, 10 ether); + uint256 balanceBefore = anyone.balance; + + vm.prank(anyone); + bool done = pdpVerifier.cleanupPieces(setId, 10); + assertTrue(done); + assertEq(anyone.balance - balanceBefore, PDPFees.cleanupDeposit(), "third party receives deposit"); + } + + function testPermissionlessDeleteAfterInactivity() public { + uint256 setId = _createAndPopulate(1); + + // Roll past the inactivity window from last proven epoch (NO_PROVEN_EPOCH = 0) + vm.roll(pdpVerifier.INACTIVITY_WINDOW() + 1); + + address anyone = address(0xDEAD); + vm.prank(anyone); + pdpVerifier.deleteDataSet(setId, empty); + assertFalse(pdpVerifier.dataSetLive(setId)); + } + + function testOnlySpCanDeleteWithinInactivityWindow() public { + uint256 setId = _createAndPopulate(1); + + // block.number (1) <= lastProvenEpoch(0) + INACTIVITY_WINDOW, so only SP can delete + address notSp = address(0xBEEF); + vm.prank(notSp); + vm.expectRevert("Only the storage provider can delete data sets"); + pdpVerifier.deleteDataSet(setId, empty); + } + + // --- guard conditions --- + + function testCleanupPiecesRequiresCleanupMode() public { + uint256 setId = _createAndPopulate(1); + // data set is still live — cleanupPieces must revert + vm.expectRevert("data set not in cleanup mode"); + pdpVerifier.cleanupPieces(setId, 10); + } + + function testCleanupPiecesMaxPiecesZeroReverts() public { + uint256 setId = _createAndPopulate(1); + pdpVerifier.deleteDataSet(setId, empty); + + vm.expectRevert("maxPieces must be greater than 0"); + pdpVerifier.cleanupPieces(setId, 0); + } + + function testDeleteAlreadyInCleanupReverts() public { + uint256 setId = _createAndPopulate(1); + pdpVerifier.deleteDataSet(setId, empty); + + vm.expectRevert("data set already in cleanup"); + pdpVerifier.deleteDataSet(setId, empty); + } + + // --- scheduled removals cleanup --- + + function testCleanupWithUnprocessedScheduledRemovals() public { + uint256 setId = _createAndPopulate(2); + + // Schedule removal without processing via nextProvingPeriod + uint256[] memory toRemove = new uint256[](1); + toRemove[0] = 0; + pdpVerifier.schedulePieceDeletions(setId, toRemove, empty); + + pdpVerifier.deleteDataSet(setId, empty); + + // Should finalize cleanly even with unprocessed scheduled removals + bool done = pdpVerifier.cleanupPieces(setId, 100); + assertTrue(done); + } +} diff --git a/test/PDPVerifier.t.sol b/test/PDPVerifier.t.sol index 805b4d9..2c521ad 100644 --- a/test/PDPVerifier.t.sol +++ b/test/PDPVerifier.t.sol @@ -37,7 +37,8 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetCreated(1, address(this)); - uint256 setId = pdpVerifier.createDataSet{value: PDPFees.sybilFee()}(address(listener), empty); + uint256 setId = + pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); assertEq(setId, 1, "First data set ID should be 1"); assertEq(pdpVerifier.getDataSetLeafCount(setId), 0, "Data set leaf count should be 0"); @@ -64,7 +65,8 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { function testDeleteDataSet() public { vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetCreated(1, address(this)); - uint256 setId = pdpVerifier.createDataSet{value: PDPFees.sybilFee()}(address(listener), empty); + uint256 setId = + pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetDeleted(setId, 0); pdpVerifier.deleteDataSet(setId, empty); @@ -75,7 +77,8 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { function testOnlyStorageProviderCanDeleteDataSet() public { vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetCreated(1, address(this)); - uint256 setId = pdpVerifier.createDataSet{value: PDPFees.sybilFee()}(address(listener), empty); + uint256 setId = + pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); // Create a new address to act as a non-storage-provider address nonStorageProvider = address(0x1234); // Expect revert when non-storage-provider tries to delete the data set @@ -94,7 +97,7 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { // TODO: once we have addPieces we should test deletion of a non empty data set function testCannotDeleteNonExistentDataSet() public { // Test with data set ID 0 (which is never valid since IDs start from 1) - vm.expectRevert("Only the storage provider can delete data sets"); + vm.expectRevert("data set not live"); pdpVerifier.deleteDataSet(0, empty); // Test with a data set ID that hasn't been created yet @@ -105,12 +108,13 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { function testMethodsOnDeletedDataSetFails() public { vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetCreated(1, address(this)); - uint256 setId = pdpVerifier.createDataSet{value: PDPFees.sybilFee()}(address(listener), empty); + uint256 setId = + pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetDeleted(setId, 0); pdpVerifier.deleteDataSet(setId, empty); - vm.expectRevert("Only the storage provider can delete data sets"); + vm.expectRevert("data set not live"); pdpVerifier.deleteDataSet(setId, empty); vm.expectRevert("Data set not live"); pdpVerifier.getDataSetStorageProvider(setId); @@ -131,10 +135,10 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { function testGetDataSetID() public { vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetCreated(1, address(this)); - pdpVerifier.createDataSet{value: PDPFees.sybilFee()}(address(listener), empty); + pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetCreated(2, address(this)); - pdpVerifier.createDataSet{value: PDPFees.sybilFee()}(address(listener), empty); + pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); assertEq(3, pdpVerifier.getNextDataSetId(), "Next data set ID should be 3"); assertEq(3, pdpVerifier.getNextDataSetId(), "Next data set ID should be 3"); } @@ -145,10 +149,12 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { // Test that data set IDs start from 1, not 0 assertEq(pdpVerifier.getNextDataSetId(), 1, "Next data set ID should start at 1"); - uint256 firstSetId = pdpVerifier.createDataSet{value: PDPFees.sybilFee()}(address(listener), empty); + uint256 firstSetId = + pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); assertEq(firstSetId, 1, "First data set ID should be 1, not 0"); - uint256 secondSetId = pdpVerifier.createDataSet{value: PDPFees.sybilFee()}(address(listener), empty); + uint256 secondSetId = + pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); assertEq(secondSetId, 2, "Second data set ID should be 2"); assertEq(pdpVerifier.getNextDataSetId(), 3, "Next data set ID should be 3 after creating two data sets"); @@ -156,19 +162,21 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { function testCreateDataSetFeeHandling() public { uint256 sybilFee = PDPFees.sybilFee(); + uint256 deposit = PDPFees.cleanupDeposit(); + uint256 required = sybilFee + deposit; - // Test 1: Fails when sending not enough for sybil fee - vm.expectRevert("sybil fee not met"); - pdpVerifier.createDataSet{value: sybilFee - 1}(address(listener), empty); + // Test 1: Fails when sending not enough for sybil fee + cleanup deposit + vm.expectRevert("insufficient payment"); + pdpVerifier.createDataSet{value: required - 1}(address(listener), empty); - // Test 2: Returns funds over the sybil fee back to the sender + // Test 2: Returns funds over the required amount back to the sender uint256 excessAmount = 1 ether; uint256 initialBalance = address(this).balance; - uint256 setId = pdpVerifier.createDataSet{value: sybilFee + excessAmount}(address(listener), empty); + uint256 setId = pdpVerifier.createDataSet{value: required + excessAmount}(address(listener), empty); uint256 finalBalance = address(this).balance; - uint256 refundedAmount = finalBalance - (initialBalance - sybilFee - excessAmount); + uint256 refundedAmount = finalBalance - (initialBalance - required - excessAmount); assertEq(refundedAmount, excessAmount, "Excess amount should be refunded"); // Additional checks to ensure the data set was created correctly @@ -183,7 +191,7 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { } function testCombinedCreateDataSetAndAddPieces() public { - uint256 sybilFee = PDPFees.sybilFee(); + uint256 createFee = PDPFees.sybilFee() + PDPFees.cleanupDeposit(); bytes memory combinedExtraData = abi.encode(empty, empty); Cids.Cid[] memory pieces = new Cids.Cid[](2); @@ -200,7 +208,7 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { emit IPDPEvents.PiecesAdded(1, expectedPieceIds, pieces); uint256 firstAdded = - pdpVerifier.addPieces{value: sybilFee}(NEW_DATA_SET_SENTINEL, address(listener), pieces, combinedExtraData); + pdpVerifier.addPieces{value: createFee}(NEW_DATA_SET_SENTINEL, address(listener), pieces, combinedExtraData); // Verify the data set was created correctly assertEq(firstAdded, 1, "First piece ID should be 1"); @@ -218,12 +226,12 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { function testNewDataSetSentinelValue() public { assertEq(NEW_DATA_SET_SENTINEL, 0, "Sentinel value should be 0"); - uint256 sybilFee = PDPFees.sybilFee(); + uint256 createFee = PDPFees.sybilFee() + PDPFees.cleanupDeposit(); bytes memory combinedExtraData = abi.encode(empty, empty); Cids.Cid[] memory pieces = new Cids.Cid[](0); uint256 firstAdded = - pdpVerifier.addPieces{value: sybilFee}(NEW_DATA_SET_SENTINEL, address(listener), pieces, combinedExtraData); + pdpVerifier.addPieces{value: createFee}(NEW_DATA_SET_SENTINEL, address(listener), pieces, combinedExtraData); assertEq(firstAdded, 1, "First piece ID should be 1"); assertEq(pdpVerifier.getDataSetLeafCount(firstAdded), 0, "Data set leaf count should be 0"); @@ -252,7 +260,7 @@ contract PDPVerifierStorageProviderTest is MockFVMTest, PieceHelper { } function testStorageProviderTransfer() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); pdpVerifier.proposeDataSetStorageProvider(setId, nextStorageProvider); @@ -280,7 +288,7 @@ contract PDPVerifierStorageProviderTest is MockFVMTest, PieceHelper { } function testStorageProviderProposalReset() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); pdpVerifier.proposeDataSetStorageProvider(setId, nextStorageProvider); @@ -294,7 +302,7 @@ contract PDPVerifierStorageProviderTest is MockFVMTest, PieceHelper { } function testStorageProviderPermissionsRequired() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); vm.prank(nonStorageProvider); @@ -315,7 +323,7 @@ contract PDPVerifierStorageProviderTest is MockFVMTest, PieceHelper { } function testScheduleRemovePiecesOnlyStorageProvider() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieceDataArray = new Cids.Cid[](1); @@ -338,6 +346,8 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { TestingRecordKeeperService listener; bytes empty = new bytes(0); + receive() external payable {} + function setUp() public override { super.setUp(); PDPVerifier pdpVerifierImpl = new PDPVerifier(1, address(0), 1, address(0)); @@ -350,7 +360,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testAddPiece() public { vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetCreated(1, address(this)); - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -380,7 +390,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { } function testAddPiecesToExistingDataSetWithFee() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -407,7 +417,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { } function testAddPiecesToExistingDataSetWrongStorageProvider() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -425,7 +435,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testAddMultiplePieces() public { vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetCreated(1, address(this)); - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](2); @@ -466,7 +476,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { } function testAddBadPiece() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](1); @@ -494,7 +504,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testAddBadPiecesBatched() public { // Add one bad piece, message fails on bad index - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](4); @@ -514,7 +524,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testRemovePiece() public { // Add one piece - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](1); @@ -544,7 +554,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testCannotScheduleRemovalOnNonLiveDataSet() public { // Create a data set - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -564,7 +574,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { } function testRemovePieceBatch() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](3); @@ -600,7 +610,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { } function testSchedulePieceDeletionsDuplicatePrevention() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](3); @@ -642,7 +652,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { } function testMappingClearedAfterRemoval() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](3); @@ -682,7 +692,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testBitmapWithLargePieceIds() public { // Setup: Create dataset and add many pieces - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -716,7 +726,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { } function testRemoveFuturePieces() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](1); @@ -755,7 +765,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testOnlyStorageProviderCanModifyDataSet() public { // Setup a piece we can add - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](1); @@ -796,7 +806,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { } function testNextProvingPeriodChallengeEpochTooSoon() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); // Add a piece to the data set (otherwise nextProvingPeriod fails waiting for leaves) @@ -830,7 +840,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { // Get the NO_CHALLENGE_SCHEDULED constant value for clarity uint256 noChallenge = pdpVerifier.NO_CHALLENGE_SCHEDULED(); - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -852,7 +862,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testNextProvingPeriodRevertsOnEmptyDataSet() public { // Create a new data set - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -864,7 +874,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testEmitDataSetEmptyEvent() public { // Create a data set with one piece - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -908,7 +918,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { function testGetActivePiecesEmpty() public { // Create empty data set and test - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -923,7 +933,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesPagination() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -973,7 +983,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesWithDeleted() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1012,7 +1022,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesEdgeCases() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1055,7 +1065,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesHasMore() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1084,7 +1094,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesLargeSet() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1134,7 +1144,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesByCursorBasic() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1163,7 +1173,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesByCursorWithDeleted() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1197,7 +1207,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesByCursorBeyondRange() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1217,7 +1227,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesByCursorEmpty() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1235,7 +1245,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesByCursorZeroLimit() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1447,7 +1457,7 @@ contract SumTreeAddTest is MockFVMTest, PieceHelper { MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = SumTreeInternalTestPDPVerifier(address(proxy)); listener = new TestingRecordKeeperService(); - testSetId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + testSetId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); } @@ -1813,12 +1823,12 @@ contract PDPListenerIntegrationTest is MockFVMTest, PieceHelper { function testListenerPropagatesErrors() public { badListener.setBadOperation(PDPRecordKeeper.OperationType.CREATE); vm.expectRevert("Failing operation"); - pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(badListener), new Cids.Cid[](0), abi.encode(empty, empty) ); badListener.setBadOperation(PDPRecordKeeper.OperationType.NONE); - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(badListener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1902,7 +1912,7 @@ contract PDPVerifierExtraDataTest is MockFVMTest, PieceHelper { function testExtraDataPropagation() public { // Test CREATE operation - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(extraDataListener), new Cids.Cid[](0), abi.encode(empty, empty) ); assertEq( @@ -1962,7 +1972,7 @@ contract PDPVerifierE2ETest is MockFVMTest, ProofBuilderHelper, PieceHelper { function testCompleteProvingPeriodE2E() public { // Step 1: Create a data set - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -2266,7 +2276,7 @@ contract PDPVerifierFeeTest is MockFVMTest, PieceHelper, ProofBuilderHelper { bytes memory combinedExtra = abi.encode(empty, empty); - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), pieces, combinedExtra ); @@ -2359,7 +2369,7 @@ contract PDPVerifierStorageProviderListenerTest is MockFVMTest { } function testStorageProviderChangedCalledOnStorageProviderTransfer() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); pdpVerifier.proposeDataSetStorageProvider(setId, nextStorageProvider); @@ -2371,7 +2381,7 @@ contract PDPVerifierStorageProviderListenerTest is MockFVMTest { } function testListenerRevertDoesNotRevertMainTx() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); pdpVerifier.proposeDataSetStorageProvider(setId, nextStorageProvider); @@ -2396,7 +2406,8 @@ contract PDPVerifierCIDSearchTest is MockFVMTest, PieceHelper { pdpVerifier = PDPVerifier(address(proxy)); listener = new TestingRecordKeeperService(); - setId = pdpVerifier.createDataSet{value: PDPFees.sybilFee()}(address(listener), empty); + setId = + pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); } function testFindPieceIdsByCid_Unique() public { @@ -2495,7 +2506,8 @@ contract PDPVerifierCIDSearchTest is MockFVMTest, PieceHelper { } function testFindPieceIdsByCid_EmptyDataSet() public { - uint256 emptySetId = pdpVerifier.createDataSet{value: PDPFees.sybilFee()}(address(listener), empty); + uint256 emptySetId = + pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); Cids.Cid memory target = makeSamplePiece(64); uint256[] memory results = pdpVerifier.findPieceIdsByCid(emptySetId, target, 0, 10); assertEq(results.length, 0); @@ -2637,31 +2649,31 @@ contract UsdfcSybilFeeTest is MockFVMTest, PieceHelper { listener = new UsdfcBurningListener(mockPayments); } - // When USDFC is not burned and msg.value > 0, FIL burn fallback succeeds + // When USDFC is not burned, FIL must cover sybil fee + cleanup deposit function testFilBurnFallbackWhenNoUsdfc() public { // Listener deposits nothing — USDFC balance won't increase listener.setDepositAmount(0); - uint256 filSybilFee = PDPFees.sybilFee(); - uint256 setId = pdpVerifier.createDataSet{value: filSybilFee}(address(listener), empty); + uint256 filTotal = PDPFees.sybilFee() + PDPFees.cleanupDeposit(); + uint256 setId = pdpVerifier.createDataSet{value: filTotal}(address(listener), empty); assertEq(setId, 1); } - // When USDFC is insufficient and no FIL sent, tx reverts + // When USDFC is insufficient and FIL doesn't cover both fees, tx reverts function testRevertWhenUsdfcInsufficientAndNoFil() public { - // Listener deposits less than the sybil fee + // Listener deposits less than the sybil fee; no FIL sent → insufficient payment listener.setDepositAmount(SYBIL_FEE - 1); - vm.expectRevert(PDPVerifier.UsdfcSybilFeeNotMet.selector); + vm.expectRevert("insufficient payment"); pdpVerifier.createDataSet(address(listener), empty); } - // When USDFC is sufficiently burned, succeeds without FIL + // When USDFC covers sybil fee, FIL only needs to cover cleanup deposit function testSucceedWhenUsdfcSufficientlyBurned() public { // Listener deposits exactly the sybil fee listener.setDepositAmount(SYBIL_FEE); - uint256 setId = pdpVerifier.createDataSet(address(listener), empty); + uint256 setId = pdpVerifier.createDataSet{value: PDPFees.cleanupDeposit()}(address(listener), empty); assertEq(setId, 1); } @@ -2669,9 +2681,9 @@ contract UsdfcSybilFeeTest is MockFVMTest, PieceHelper { function testAddPieces_FilBurnFallbackWhenNoUsdfc() public { listener.setDepositAmount(0); - uint256 filSybilFee = PDPFees.sybilFee(); + uint256 filTotal = PDPFees.sybilFee() + PDPFees.cleanupDeposit(); bytes memory combinedExtra = abi.encode(empty, empty); - uint256 setId = pdpVerifier.addPieces{value: filSybilFee}( + uint256 setId = pdpVerifier.addPieces{value: filTotal}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), combinedExtra ); assertEq(setId, 1); @@ -2681,7 +2693,7 @@ contract UsdfcSybilFeeTest is MockFVMTest, PieceHelper { listener.setDepositAmount(SYBIL_FEE - 1); bytes memory combinedExtra = abi.encode(empty, empty); - vm.expectRevert(PDPVerifier.UsdfcSybilFeeNotMet.selector); + vm.expectRevert("insufficient payment"); pdpVerifier.addPieces(NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), combinedExtra); } @@ -2689,8 +2701,9 @@ contract UsdfcSybilFeeTest is MockFVMTest, PieceHelper { listener.setDepositAmount(SYBIL_FEE); bytes memory combinedExtra = abi.encode(empty, empty); - uint256 setId = - pdpVerifier.addPieces(NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), combinedExtra); + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( + NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), combinedExtra + ); assertEq(setId, 1); } diff --git a/test/PDPVerifierProofTest.t.sol b/test/PDPVerifierProofTest.t.sol index 9b63035..d503c69 100644 --- a/test/PDPVerifierProofTest.t.sol +++ b/test/PDPVerifierProofTest.t.sol @@ -119,7 +119,7 @@ contract PDPVerifierProofTest is MockFVMTest, ProofBuilderHelper, PieceHelper { function testDataSetLastProvenEpochOnPieceRemoval() public { // Create a data set and verify initial lastProvenEpoch is 0 - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); assertEq(pdpVerifier.getDataSetLastProvenEpoch(setId), 0, "Initial lastProvenEpoch should be 0"); @@ -202,7 +202,7 @@ contract PDPVerifierProofTest is MockFVMTest, ProofBuilderHelper, PieceHelper { } function testProvePossessionFailsWithNoScheduledChallenge() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](1); @@ -224,7 +224,7 @@ contract PDPVerifierProofTest is MockFVMTest, ProofBuilderHelper, PieceHelper { } function testEmptyProofRejected() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); IPDPTypes.Proof[] memory emptyProof = new IPDPTypes.Proof[](0); @@ -409,7 +409,7 @@ contract PDPVerifierProofTest is MockFVMTest, ProofBuilderHelper, PieceHelper { } // Create new data set and add pieces. - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); pdpVerifier.addPieces(setId, address(0), pieces, empty); From 60f6aabaa8493e35d9afb7397a1a3eb3b7de95eb Mon Sep 17 00:00:00 2001 From: William Morriss Date: Fri, 15 May 2026 21:13:55 -0500 Subject: [PATCH 02/22] refactor(fees): replace USDFC sybil fee with cleanup deposit and move PDPVerifier setup to setUp() Assisted-by: Claude:claude-sonnet-4-6 --- src/Fees.sol | 9 - src/PDPVerifier.sol | 81 ++------ src/interfaces/IPDPVerifier.sol | 6 - test/CleanupPieces.t.sol | 10 +- test/ERC1967Proxy.t.sol | 9 +- test/Fees.t.sol | 5 - test/PDPVerifier.t.sol | 322 ++++++++------------------------ test/PDPVerifierProofTest.t.sol | 10 +- 8 files changed, 98 insertions(+), 354 deletions(-) diff --git a/src/Fees.sol b/src/Fees.sol index d9fbf3b..9f5a45b 100644 --- a/src/Fees.sol +++ b/src/Fees.sol @@ -7,9 +7,6 @@ library PDPFees { uint256 constant ATTO_FIL = 1; uint256 constant FIL_TO_ATTO_FIL = 1e18 * ATTO_FIL; - // 0.1 FIL - uint256 constant SYBIL_FEE = FIL_TO_ATTO_FIL / 10; - // 0.1 FIL cleanup bond held per data set, returned to whoever finalizes cleanup uint256 constant CLEANUP_DEPOSIT = FIL_TO_ATTO_FIL / 10; @@ -32,12 +29,6 @@ library PDPFees { return (feePerTiB * rawSize) >> 40; } - // sybil fee adds cost to adding state to the pdp verifier contract to prevent - // wasteful state growth. 0.1 FIL - function sybilFee() internal pure returns (uint256) { - return SYBIL_FEE; - } - // cleanup deposit is held per data set and paid out to whoever completes cleanup. 0.1 FIL function cleanupDeposit() internal pure returns (uint256) { return CLEANUP_DEPOSIT; diff --git a/src/PDPVerifier.sol b/src/PDPVerifier.sol index 3a32d77..68371f7 100644 --- a/src/PDPVerifier.sol +++ b/src/PDPVerifier.sol @@ -13,13 +13,6 @@ import {FVMPay} from "fvm-solidity/FVMPay.sol"; import {FVMRandom} from "fvm-solidity/FVMRandom.sol"; import {IPDPTypes} from "./interfaces/IPDPTypes.sol"; -interface IFilecoinPay { - function accounts(address token, address owner) - external - view - returns (uint256 funds, uint256 lockupCurrent, uint256 lockupRate, uint256 lockupLastSettledAt); -} - /// @title PDPListener /// @notice Interface for PDP Service applications managing data storage. /// @dev This interface exists to provide an extensible hook for applications to use the PDP verification contract @@ -161,14 +154,6 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { FeeStatus private feeStatus; - // USDFC sybil fee support - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - address public immutable USDFC_TOKEN_ADDRESS; - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - uint256 public immutable USDFC_SYBIL_FEE; - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - address public immutable PAYMENTS_CONTRACT_ADDRESS; - // Used for announcing upgrades, packed into one slot struct PlannedUpgrade { // Address of the new implementation contract @@ -187,18 +172,9 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { // Methods /// @custom:oz-upgrades-unsafe-allow constructor - constructor( - uint64 _initializerVersion, - address _usdfcTokenAddress, - uint256 _usdfcSybilFee, - address _paymentsContractAddress - ) { + constructor(uint64 _initializerVersion) { _disableInitializers(); - require(_usdfcSybilFee > 0, "USDFC sybil fee must be greater than 0"); REINITIALIZER_VERSION = _initializerVersion; - USDFC_TOKEN_ADDRESS = _usdfcTokenAddress; - USDFC_SYBIL_FEE = _usdfcSybilFee; - PAYMENTS_CONTRACT_ADDRESS = _paymentsContractAddress; } function initialize(uint256 _challengeFinality) public initializer { @@ -238,41 +214,19 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { require(success, "Burn failed"); } - // Handles fee payment for dataset creation: requires cleanup deposit in FIL always, - // plus sybil fee either via USDFC (usdfcBurned=true) or FIL. Stores the deposit for setId. + // Handles fee payment for dataset creation: requires cleanup deposit in FIL. Stores the deposit for setId. // Must be called after all state changes to avoid re-entrancy issues. - function _handleFeesWithDeposit(uint256 setId, bool usdfcBurned) internal { + function _handleFeesWithDeposit(uint256 setId) internal { uint256 deposit = PDPFees.cleanupDeposit(); cleanupDeposit[setId] = deposit; - - if (usdfcBurned) { - // USDFC covers sybil fee; FIL msg.value must cover just the cleanup deposit - require(msg.value >= deposit, "cleanup deposit required"); - uint256 excess = msg.value - deposit; - if (excess > 0) { - (bool success,) = msg.sender.call{value: excess}(""); - if (!success) revert FilRefundFailed(); - } - } else { - // FIL covers both sybil fee and cleanup deposit - uint256 required = PDPFees.sybilFee() + deposit; - require(msg.value >= required, "insufficient payment"); - burnFee(PDPFees.sybilFee()); - uint256 excess = msg.value - required; - if (excess > 0) { - (bool success,) = msg.sender.call{value: excess}(""); - require(success, "Transfer failed."); - } + require(msg.value >= deposit, "cleanup deposit required"); + uint256 excess = msg.value - deposit; + if (excess > 0) { + (bool success,) = msg.sender.call{value: excess}(""); + require(success, "Transfer failed."); } } - function _getPaymentsUsdfcBalance() internal view returns (uint256) { - if (PAYMENTS_CONTRACT_ADDRESS == address(0) || USDFC_TOKEN_ADDRESS == address(0)) return 0; - (uint256 funds,,,) = - IFilecoinPay(PAYMENTS_CONTRACT_ADDRESS).accounts(USDFC_TOKEN_ADDRESS, PAYMENTS_CONTRACT_ADDRESS); - return funds; - } - // Returns the current challenge finality value function getChallengeFinality() public view returns (uint256) { return challengeFinality; @@ -605,17 +559,14 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { // Parameters: // - listenerAddr: Address of PDPListener contract to receive callbacks (can be address(0) for no listener) // - extraData: Arbitrary bytes passed to listener's dataSetCreated callback - // - msg.value: Must always include cleanup deposit (PDPFees.cleanupDeposit()). When not using USDFC, - // must also include sybil fee (PDPFees.sybilFee()). Excess is refunded. + // - msg.value: Must include cleanup deposit (PDPFees.cleanupDeposit()). Excess is refunded. // // Returns: The newly created data set ID // // Only the storage provider (msg.sender) can call this function. function createDataSet(address listenerAddr, bytes calldata extraData) public payable returns (uint256) { - uint256 balanceBefore = _getPaymentsUsdfcBalance(); uint256 setId = _createDataSet(listenerAddr, extraData); - uint256 balanceAfter = _getPaymentsUsdfcBalance(); - _handleFeesWithDeposit(setId, balanceAfter >= balanceBefore + USDFC_SYBIL_FEE); + _handleFeesWithDeposit(setId); return setId; } @@ -747,7 +698,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { // - extraData: abi.encode(bytes createPayload, bytes addPayload) where: // - createPayload: passed to listener's dataSetCreated callback // - addPayload: passed to listener's piecesAdded callback (if pieces added) - // - msg.value: must include sybil fee (PDPFees.sybilFee()), excess is refunded + // - msg.value: must include cleanup deposit (PDPFees.cleanupDeposit()), excess is refunded // - Returns: the newly created data set ID // // Only the storage provider can call this function. @@ -761,16 +712,14 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { require(listenerAddr != address(0), "listener required for new dataset"); - uint256 balanceBefore = _getPaymentsUsdfcBalance(); uint256 newSetId = _createDataSet(listenerAddr, createPayload); - uint256 balanceAfter = _getPaymentsUsdfcBalance(); // Add pieces to the newly created data set (if any) if (pieceData.length > 0) { _addPiecesToDataSet(newSetId, pieceData, addPayload); } - _handleFeesWithDeposit(newSetId, balanceAfter >= balanceBefore + USDFC_SYBIL_FEE); + _handleFeesWithDeposit(newSetId); return newSetId; } else { // Adding to an existing set; no fee should be sent and listenerAddr must be zero @@ -811,8 +760,6 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { } error IndexedError(uint256 idx, string msg); - error UsdfcSybilFeeNotMet(); - error FilRefundFailed(); function addOnePiece(uint256 setId, uint256 callIdx, Cids.Cid calldata piece) internal returns (uint256) { (uint256 padding, uint8 height,) = Cids.validateCommPv2(piece); @@ -967,10 +914,6 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { return block.timestamp >= feeStatus.transitionTime ? feeStatus.nextFeePerTiB : feeStatus.currentFeePerTiB; } - function FIL_SYBIL_FEE() external pure returns (uint256) { - return PDPFees.sybilFee(); - } - function FIL_CLEANUP_DEPOSIT() external pure returns (uint256) { return PDPFees.cleanupDeposit(); } diff --git a/src/interfaces/IPDPVerifier.sol b/src/interfaces/IPDPVerifier.sol index f9c28ba..528d847 100644 --- a/src/interfaces/IPDPVerifier.sol +++ b/src/interfaces/IPDPVerifier.sol @@ -46,12 +46,6 @@ interface IPDPVerifier is IPDPEvents { // Fee view: returns the current effective fee per TiB function feePerTiB() external view returns (uint96); - // USDFC sybil fee amount - function USDFC_SYBIL_FEE() external view returns (uint256); - - // FIL sybil fee amount - function FIL_SYBIL_FEE() external pure returns (uint256); - // FIL cleanup deposit amount held per data set function FIL_CLEANUP_DEPOSIT() external pure returns (uint256); } diff --git a/test/CleanupPieces.t.sol b/test/CleanupPieces.t.sol index 9e6801f..6d4b70d 100644 --- a/test/CleanupPieces.t.sol +++ b/test/CleanupPieces.t.sol @@ -64,7 +64,7 @@ contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1, address(0), 1, address(0)); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1); bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, CHALLENGE_FINALITY_DELAY); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); @@ -72,7 +72,7 @@ contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { } function _createAndPopulate(uint256 numPieces) internal returns (uint256 setId) { - setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); if (numPieces > 0) { @@ -125,12 +125,12 @@ contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { function testZeroPieceDataSetFinalizesAtDelete() public { uint256 balanceBefore = address(this).balance; - uint256 setId = _createAndPopulate(0); // burns sybilFee, holds cleanupDeposit + uint256 setId = _createAndPopulate(0); // holds cleanupDeposit pdpVerifier.deleteDataSet(setId, empty); // returns cleanupDeposit immediately - // Net cost is only the sybil fee; the cleanup deposit was returned at delete time - assertEq(balanceBefore - address(this).balance, PDPFees.sybilFee(), "net cost is only sybil fee"); + // Net cost is zero; the cleanup deposit was returned at delete time + assertEq(balanceBefore, address(this).balance, "net cost is zero"); assertFalse(pdpVerifier.dataSetLive(setId)); } diff --git a/test/ERC1967Proxy.t.sol b/test/ERC1967Proxy.t.sol index 921c6e7..3202af1 100644 --- a/test/ERC1967Proxy.t.sol +++ b/test/ERC1967Proxy.t.sol @@ -8,6 +8,7 @@ import {MyERC1967Proxy} from "../src/ERC1967Proxy.sol"; contract ERC1967ProxyTest is Test { PDPVerifier public implementation; + PDPVerifier public newImplementation; PDPVerifier public proxy; address owner = address(0x123); @@ -15,7 +16,8 @@ contract ERC1967ProxyTest is Test { // Set owner for testing vm.startPrank(owner); // Deploy implementation contract - implementation = new PDPVerifier(1, address(0), 1, address(0)); + implementation = new PDPVerifier(1); + newImplementation = new PDPVerifier(2); // Deploy proxy pointing to implementation bytes memory initData = abi.encodeWithSelector( @@ -42,9 +44,6 @@ contract ERC1967ProxyTest is Test { function testUpgradeImplementation() public { assertImplementationEquals(address(implementation)); - // Deploy new implementation - PDPVerifier newImplementation = new PDPVerifier(2, address(0), 1, address(0)); - // Announce upgrade first (required by new upgrade pattern) PDPVerifier.PlannedUpgrade memory plan; plan.nextImplementation = address(newImplementation); @@ -64,8 +63,6 @@ contract ERC1967ProxyTest is Test { } function testUpgradeFromNonOwnerNoGood() public { - PDPVerifier newImplementation = new PDPVerifier(2, address(0), 1, address(0)); - // Announce upgrade first (as owner) PDPVerifier.PlannedUpgrade memory plan; plan.nextImplementation = address(newImplementation); diff --git a/test/Fees.t.sol b/test/Fees.t.sol index 8e6804f..e29644d 100644 --- a/test/Fees.t.sol +++ b/test/Fees.t.sol @@ -40,9 +40,4 @@ contract PDPFeesTest is Test { uint256 expectedFee = 0.00023 ether; // 0.00023 FIL in attoFIL assertEq(PDPFees.DEFAULT_FEE_PER_TIB, expectedFee, "DEFAULT_FEE_PER_TIB should be 0.00023 FIL"); } - - function testSybilFee() public pure { - uint256 fee = PDPFees.sybilFee(); - assertEq(fee, PDPFees.SYBIL_FEE, "Sybil fee should match the constant"); - } } diff --git a/test/PDPVerifier.t.sol b/test/PDPVerifier.t.sol index 2c521ad..e964c50 100644 --- a/test/PDPVerifier.t.sol +++ b/test/PDPVerifier.t.sol @@ -23,7 +23,7 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1, address(0), 1, address(0)); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1); uint256 challengeFinality = 2; bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, challengeFinality); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); @@ -37,8 +37,7 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetCreated(1, address(this)); - uint256 setId = - pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); + uint256 setId = pdpVerifier.createDataSet{value: PDPFees.cleanupDeposit()}(address(listener), empty); assertEq(setId, 1, "First data set ID should be 1"); assertEq(pdpVerifier.getDataSetLeafCount(setId), 0, "Data set leaf count should be 0"); @@ -65,8 +64,7 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { function testDeleteDataSet() public { vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetCreated(1, address(this)); - uint256 setId = - pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); + uint256 setId = pdpVerifier.createDataSet{value: PDPFees.cleanupDeposit()}(address(listener), empty); vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetDeleted(setId, 0); pdpVerifier.deleteDataSet(setId, empty); @@ -77,8 +75,7 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { function testOnlyStorageProviderCanDeleteDataSet() public { vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetCreated(1, address(this)); - uint256 setId = - pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); + uint256 setId = pdpVerifier.createDataSet{value: PDPFees.cleanupDeposit()}(address(listener), empty); // Create a new address to act as a non-storage-provider address nonStorageProvider = address(0x1234); // Expect revert when non-storage-provider tries to delete the data set @@ -108,8 +105,7 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { function testMethodsOnDeletedDataSetFails() public { vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetCreated(1, address(this)); - uint256 setId = - pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); + uint256 setId = pdpVerifier.createDataSet{value: PDPFees.cleanupDeposit()}(address(listener), empty); vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetDeleted(setId, 0); @@ -135,10 +131,10 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { function testGetDataSetID() public { vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetCreated(1, address(this)); - pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); + pdpVerifier.createDataSet{value: PDPFees.cleanupDeposit()}(address(listener), empty); vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetCreated(2, address(this)); - pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); + pdpVerifier.createDataSet{value: PDPFees.cleanupDeposit()}(address(listener), empty); assertEq(3, pdpVerifier.getNextDataSetId(), "Next data set ID should be 3"); assertEq(3, pdpVerifier.getNextDataSetId(), "Next data set ID should be 3"); } @@ -149,24 +145,20 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { // Test that data set IDs start from 1, not 0 assertEq(pdpVerifier.getNextDataSetId(), 1, "Next data set ID should start at 1"); - uint256 firstSetId = - pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); + uint256 firstSetId = pdpVerifier.createDataSet{value: PDPFees.cleanupDeposit()}(address(listener), empty); assertEq(firstSetId, 1, "First data set ID should be 1, not 0"); - uint256 secondSetId = - pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); + uint256 secondSetId = pdpVerifier.createDataSet{value: PDPFees.cleanupDeposit()}(address(listener), empty); assertEq(secondSetId, 2, "Second data set ID should be 2"); assertEq(pdpVerifier.getNextDataSetId(), 3, "Next data set ID should be 3 after creating two data sets"); } function testCreateDataSetFeeHandling() public { - uint256 sybilFee = PDPFees.sybilFee(); - uint256 deposit = PDPFees.cleanupDeposit(); - uint256 required = sybilFee + deposit; + uint256 required = PDPFees.cleanupDeposit(); - // Test 1: Fails when sending not enough for sybil fee + cleanup deposit - vm.expectRevert("insufficient payment"); + // Test 1: Fails when sending not enough for cleanup deposit + vm.expectRevert("cleanup deposit required"); pdpVerifier.createDataSet{value: required - 1}(address(listener), empty); // Test 2: Returns funds over the required amount back to the sender @@ -191,7 +183,7 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { } function testCombinedCreateDataSetAndAddPieces() public { - uint256 createFee = PDPFees.sybilFee() + PDPFees.cleanupDeposit(); + uint256 createFee = PDPFees.cleanupDeposit(); bytes memory combinedExtraData = abi.encode(empty, empty); Cids.Cid[] memory pieces = new Cids.Cid[](2); @@ -226,7 +218,7 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { function testNewDataSetSentinelValue() public { assertEq(NEW_DATA_SET_SENTINEL, 0, "Sentinel value should be 0"); - uint256 createFee = PDPFees.sybilFee() + PDPFees.cleanupDeposit(); + uint256 createFee = PDPFees.cleanupDeposit(); bytes memory combinedExtraData = abi.encode(empty, empty); Cids.Cid[] memory pieces = new Cids.Cid[](0); @@ -248,7 +240,7 @@ contract PDPVerifierStorageProviderTest is MockFVMTest, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1, address(0), 1, address(0)); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1); bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, 2); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); @@ -260,7 +252,7 @@ contract PDPVerifierStorageProviderTest is MockFVMTest, PieceHelper { } function testStorageProviderTransfer() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); pdpVerifier.proposeDataSetStorageProvider(setId, nextStorageProvider); @@ -288,7 +280,7 @@ contract PDPVerifierStorageProviderTest is MockFVMTest, PieceHelper { } function testStorageProviderProposalReset() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); pdpVerifier.proposeDataSetStorageProvider(setId, nextStorageProvider); @@ -302,7 +294,7 @@ contract PDPVerifierStorageProviderTest is MockFVMTest, PieceHelper { } function testStorageProviderPermissionsRequired() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); vm.prank(nonStorageProvider); @@ -323,7 +315,7 @@ contract PDPVerifierStorageProviderTest is MockFVMTest, PieceHelper { } function testScheduleRemovePiecesOnlyStorageProvider() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieceDataArray = new Cids.Cid[](1); @@ -350,7 +342,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1, address(0), 1, address(0)); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1); bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, CHALLENGE_FINALITY_DELAY); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); @@ -360,7 +352,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testAddPiece() public { vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetCreated(1, address(this)); - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -390,7 +382,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { } function testAddPiecesToExistingDataSetWithFee() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -417,7 +409,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { } function testAddPiecesToExistingDataSetWrongStorageProvider() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -435,7 +427,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testAddMultiplePieces() public { vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetCreated(1, address(this)); - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](2); @@ -476,7 +468,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { } function testAddBadPiece() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](1); @@ -504,7 +496,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testAddBadPiecesBatched() public { // Add one bad piece, message fails on bad index - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](4); @@ -524,7 +516,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testRemovePiece() public { // Add one piece - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](1); @@ -554,7 +546,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testCannotScheduleRemovalOnNonLiveDataSet() public { // Create a data set - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -574,7 +566,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { } function testRemovePieceBatch() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](3); @@ -610,7 +602,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { } function testSchedulePieceDeletionsDuplicatePrevention() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](3); @@ -652,7 +644,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { } function testMappingClearedAfterRemoval() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](3); @@ -692,7 +684,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testBitmapWithLargePieceIds() public { // Setup: Create dataset and add many pieces - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -726,7 +718,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { } function testRemoveFuturePieces() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](1); @@ -765,7 +757,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testOnlyStorageProviderCanModifyDataSet() public { // Setup a piece we can add - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](1); @@ -806,7 +798,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { } function testNextProvingPeriodChallengeEpochTooSoon() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); // Add a piece to the data set (otherwise nextProvingPeriod fails waiting for leaves) @@ -840,7 +832,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { // Get the NO_CHALLENGE_SCHEDULED constant value for clarity uint256 noChallenge = pdpVerifier.NO_CHALLENGE_SCHEDULED(); - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -862,7 +854,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testNextProvingPeriodRevertsOnEmptyDataSet() public { // Create a new data set - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -874,7 +866,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testEmitDataSetEmptyEvent() public { // Create a data set with one piece - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -908,7 +900,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1, address(0), 1, address(0)); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1); uint256 challengeFinality = 2; bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, challengeFinality); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); @@ -918,7 +910,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { function testGetActivePiecesEmpty() public { // Create empty data set and test - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -933,7 +925,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesPagination() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -983,7 +975,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesWithDeleted() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1022,7 +1014,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesEdgeCases() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1065,7 +1057,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesHasMore() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1094,7 +1086,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesLargeSet() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1144,7 +1136,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesByCursorBasic() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1173,7 +1165,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesByCursorWithDeleted() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1207,7 +1199,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesByCursorBeyondRange() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1227,7 +1219,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesByCursorEmpty() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1245,7 +1237,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { } function testGetActivePiecesByCursorZeroLimit() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1302,7 +1294,7 @@ contract TestingRecordKeeperService is PDPListener, PDPRecordKeeper { } contract SumTreeInternalTestPDPVerifier is PDPVerifier { - constructor() PDPVerifier(1, address(0), 1, address(0)) {} + constructor() PDPVerifier(1) {} function getTestHeightFromIndex(uint256 index) public pure returns (uint256) { return heightFromIndex(index); @@ -1457,7 +1449,7 @@ contract SumTreeAddTest is MockFVMTest, PieceHelper { MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = SumTreeInternalTestPDPVerifier(address(proxy)); listener = new TestingRecordKeeperService(); - testSetId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + testSetId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); } @@ -1813,7 +1805,7 @@ contract PDPListenerIntegrationTest is MockFVMTest, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1, address(0), 1, address(0)); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1); bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, CHALLENGE_FINALITY_DELAY); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); @@ -1823,12 +1815,12 @@ contract PDPListenerIntegrationTest is MockFVMTest, PieceHelper { function testListenerPropagatesErrors() public { badListener.setBadOperation(PDPRecordKeeper.OperationType.CREATE); vm.expectRevert("Failing operation"); - pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(badListener), new Cids.Cid[](0), abi.encode(empty, empty) ); badListener.setBadOperation(PDPRecordKeeper.OperationType.NONE); - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(badListener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -1903,7 +1895,7 @@ contract PDPVerifierExtraDataTest is MockFVMTest, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1, address(0), 1, address(0)); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1); bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, CHALLENGE_FINALITY_DELAY); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); @@ -1912,7 +1904,7 @@ contract PDPVerifierExtraDataTest is MockFVMTest, PieceHelper { function testExtraDataPropagation() public { // Test CREATE operation - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(extraDataListener), new Cids.Cid[](0), abi.encode(empty, empty) ); assertEq( @@ -1959,7 +1951,7 @@ contract PDPVerifierE2ETest is MockFVMTest, ProofBuilderHelper, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1, address(0), 1, address(0)); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1); bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, CHALLENGE_FINALITY_DELAY); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); @@ -1972,7 +1964,7 @@ contract PDPVerifierE2ETest is MockFVMTest, ProofBuilderHelper, PieceHelper { function testCompleteProvingPeriodE2E() public { // Step 1: Create a data set - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); @@ -2100,8 +2092,8 @@ contract PDPVerifierMigrateTest is Test { function setUp() public { bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, 2); - implementation = new PDPVerifier(1, address(0), 1, address(0)); - newImplementation = new PDPVerifier(2, address(0), 1, address(0)); + implementation = new PDPVerifier(1); + newImplementation = new PDPVerifier(2); proxy = new MyERC1967Proxy(address(implementation), initializeData); pdpVerifier = PDPVerifier(address(proxy)); } @@ -2112,12 +2104,9 @@ contract PDPVerifierMigrateTest is Test { assertEq(nextImplementation, address(0)); assertEq(afterEpoch, uint96(0)); - // Deploy new implementation - PDPVerifier newImpl = new PDPVerifier(2, address(0), 1, address(0)); - // Announce upgrade PDPVerifier.PlannedUpgrade memory plan; - plan.nextImplementation = address(newImpl); + plan.nextImplementation = address(newImplementation); plan.afterEpoch = uint96(vm.getBlockNumber()) + 2000; vm.expectEmit(false, false, false, true); @@ -2142,7 +2131,7 @@ contract PDPVerifierMigrateTest is Test { // Can upgrade at afterEpoch vm.roll(plan.afterEpoch); vm.expectEmit(false, false, false, true); - emit PDPVerifier.ContractUpgraded(newImpl.VERSION(), plan.nextImplementation); + emit PDPVerifier.ContractUpgraded(newImplementation.VERSION(), plan.nextImplementation); pdpVerifier.upgradeToAndCall(plan.nextImplementation, migrateData); // After upgrade, nextUpgrade should be cleared @@ -2152,9 +2141,8 @@ contract PDPVerifierMigrateTest is Test { } function testAnnouncePlannedUpgradeOnlyOwner() public { - PDPVerifier newImpl = new PDPVerifier(2, address(0), 1, address(0)); PDPVerifier.PlannedUpgrade memory plan; - plan.nextImplementation = address(newImpl); + plan.nextImplementation = address(newImplementation); plan.afterEpoch = uint96(vm.getBlockNumber()) + 2000; // Non-owner cannot announce upgrade @@ -2173,9 +2161,8 @@ contract PDPVerifierMigrateTest is Test { } function testAnnouncePlannedUpgradeInvalidEpoch() public { - PDPVerifier newImpl = new PDPVerifier(2, address(0), 1, address(0)); PDPVerifier.PlannedUpgrade memory plan; - plan.nextImplementation = address(newImpl); + plan.nextImplementation = address(newImplementation); plan.afterEpoch = uint96(vm.getBlockNumber()); // Must be in the future vm.expectRevert(); @@ -2218,7 +2205,7 @@ contract PDPVerifierFeeTest is MockFVMTest, PieceHelper, ProofBuilderHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1, address(0), 1, address(0)); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1); bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, CHALLENGE_FINALITY_DELAY); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); @@ -2276,7 +2263,7 @@ contract PDPVerifierFeeTest is MockFVMTest, PieceHelper, ProofBuilderHelper { bytes memory combinedExtra = abi.encode(empty, empty); - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), pieces, combinedExtra ); @@ -2358,7 +2345,7 @@ contract PDPVerifierStorageProviderListenerTest is MockFVMTest { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1, address(0), 1, address(0)); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1); bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, 2); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); @@ -2369,7 +2356,7 @@ contract PDPVerifierStorageProviderListenerTest is MockFVMTest { } function testStorageProviderChangedCalledOnStorageProviderTransfer() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); pdpVerifier.proposeDataSetStorageProvider(setId, nextStorageProvider); @@ -2381,7 +2368,7 @@ contract PDPVerifierStorageProviderListenerTest is MockFVMTest { } function testListenerRevertDoesNotRevertMainTx() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); pdpVerifier.proposeDataSetStorageProvider(setId, nextStorageProvider); @@ -2400,14 +2387,13 @@ contract PDPVerifierCIDSearchTest is MockFVMTest, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1, address(0), 1, address(0)); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1); bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, 2); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); listener = new TestingRecordKeeperService(); - setId = - pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); + setId = pdpVerifier.createDataSet{value: PDPFees.cleanupDeposit()}(address(listener), empty); } function testFindPieceIdsByCid_Unique() public { @@ -2506,8 +2492,7 @@ contract PDPVerifierCIDSearchTest is MockFVMTest, PieceHelper { } function testFindPieceIdsByCid_EmptyDataSet() public { - uint256 emptySetId = - pdpVerifier.createDataSet{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}(address(listener), empty); + uint256 emptySetId = pdpVerifier.createDataSet{value: PDPFees.cleanupDeposit()}(address(listener), empty); Cids.Cid memory target = makeSamplePiece(64); uint256[] memory results = pdpVerifier.findPieceIdsByCid(emptySetId, target, 0, 10); assertEq(results.length, 0); @@ -2548,164 +2533,3 @@ contract PDPVerifierCIDSearchTest is MockFVMTest, PieceHelper { assertEq(results[0], 9); } } - -// Mock payments contract that exposes accounts(address,address) returning a funds balance -contract MockPayments { - uint256 public balance; - - function setBalance(uint256 _balance) external { - balance = _balance; - } - - function addBalance(uint256 amount) external { - balance += amount; - } - - // Matches the signature PDPVerifier queries: accounts(address,address) returns (uint256,...) - function accounts(address, address) external view returns (uint256, uint256, uint256, uint256) { - return (balance, 0, 0, 0); - } -} - -// Listener that simulates FWSS depositing USDFC into the payments auction pool during dataSetCreated -contract UsdfcBurningListener is PDPListener, PDPRecordKeeper { - MockPayments public mockPayments; - uint256 public depositAmount; - - constructor(MockPayments _mockPayments) { - mockPayments = _mockPayments; - } - - function setDepositAmount(uint256 amount) external { - depositAmount = amount; - } - - function storageProviderChanged(uint256, address, address, bytes calldata) external override {} - - function dataSetCreated(uint256 dataSetId, address creator, bytes calldata) external override { - // Simulate FWSS depositing the sybil fee into the payments auction pool - if (depositAmount > 0) { - mockPayments.addBalance(depositAmount); - } - receiveDataSetEvent(dataSetId, PDPRecordKeeper.OperationType.CREATE, abi.encode(creator)); - } - - function dataSetDeleted(uint256 dataSetId, uint256 deletedLeafCount, bytes calldata) external override { - receiveDataSetEvent(dataSetId, PDPRecordKeeper.OperationType.DELETE, abi.encode(deletedLeafCount)); - } - - function piecesAdded(uint256 dataSetId, uint256 firstAdded, Cids.Cid[] calldata pieceData, bytes calldata) - external - override - { - receiveDataSetEvent(dataSetId, PDPRecordKeeper.OperationType.ADD, abi.encode(firstAdded, pieceData)); - } - - function piecesScheduledRemove(uint256 dataSetId, uint256[] calldata pieceIds, bytes calldata) external override { - receiveDataSetEvent(dataSetId, PDPRecordKeeper.OperationType.REMOVE_SCHEDULED, abi.encode(pieceIds)); - } - - function possessionProven(uint256 dataSetId, uint256 challengedLeafCount, uint256 seed, uint256 challengeCount) - external - override - { - receiveDataSetEvent( - dataSetId, - PDPRecordKeeper.OperationType.PROVE_POSSESSION, - abi.encode(challengedLeafCount, seed, challengeCount) - ); - } - - function nextProvingPeriod(uint256 dataSetId, uint256 challengeEpoch, uint256 leafCount, bytes calldata) - external - override - { - receiveDataSetEvent( - dataSetId, PDPRecordKeeper.OperationType.NEXT_PROVING_PERIOD, abi.encode(challengeEpoch, leafCount) - ); - } -} - -contract UsdfcSybilFeeTest is MockFVMTest, PieceHelper { - PDPVerifier pdpVerifier; - MockPayments mockPayments; - UsdfcBurningListener listener; - bytes empty = new bytes(0); - - uint256 constant SYBIL_FEE = 0.1e18; - address constant FAKE_USDFC = address(0x1234); - - function setUp() public override { - super.setUp(); - mockPayments = new MockPayments(); - - PDPVerifier pdpVerifierImpl = new PDPVerifier(1, FAKE_USDFC, SYBIL_FEE, address(mockPayments)); - bytes memory initializeData = abi.encodeWithSelector( - PDPVerifier.initialize.selector, - 2 // challengeFinality - ); - MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); - pdpVerifier = PDPVerifier(address(proxy)); - listener = new UsdfcBurningListener(mockPayments); - } - - // When USDFC is not burned, FIL must cover sybil fee + cleanup deposit - function testFilBurnFallbackWhenNoUsdfc() public { - // Listener deposits nothing — USDFC balance won't increase - listener.setDepositAmount(0); - - uint256 filTotal = PDPFees.sybilFee() + PDPFees.cleanupDeposit(); - uint256 setId = pdpVerifier.createDataSet{value: filTotal}(address(listener), empty); - assertEq(setId, 1); - } - - // When USDFC is insufficient and FIL doesn't cover both fees, tx reverts - function testRevertWhenUsdfcInsufficientAndNoFil() public { - // Listener deposits less than the sybil fee; no FIL sent → insufficient payment - listener.setDepositAmount(SYBIL_FEE - 1); - - vm.expectRevert("insufficient payment"); - pdpVerifier.createDataSet(address(listener), empty); - } - - // When USDFC covers sybil fee, FIL only needs to cover cleanup deposit - function testSucceedWhenUsdfcSufficientlyBurned() public { - // Listener deposits exactly the sybil fee - listener.setDepositAmount(SYBIL_FEE); - - uint256 setId = pdpVerifier.createDataSet{value: PDPFees.cleanupDeposit()}(address(listener), empty); - assertEq(setId, 1); - } - - // Same three scenarios via addPieces (new dataset path) - function testAddPieces_FilBurnFallbackWhenNoUsdfc() public { - listener.setDepositAmount(0); - - uint256 filTotal = PDPFees.sybilFee() + PDPFees.cleanupDeposit(); - bytes memory combinedExtra = abi.encode(empty, empty); - uint256 setId = pdpVerifier.addPieces{value: filTotal}( - NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), combinedExtra - ); - assertEq(setId, 1); - } - - function testAddPieces_RevertWhenUsdfcInsufficientAndNoFil() public { - listener.setDepositAmount(SYBIL_FEE - 1); - - bytes memory combinedExtra = abi.encode(empty, empty); - vm.expectRevert("insufficient payment"); - pdpVerifier.addPieces(NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), combinedExtra); - } - - function testAddPieces_SucceedWhenUsdfcSufficientlyBurned() public { - listener.setDepositAmount(SYBIL_FEE); - - bytes memory combinedExtra = abi.encode(empty, empty); - uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( - NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), combinedExtra - ); - assertEq(setId, 1); - } - - receive() external payable {} -} diff --git a/test/PDPVerifierProofTest.t.sol b/test/PDPVerifierProofTest.t.sol index d503c69..c8278ef 100644 --- a/test/PDPVerifierProofTest.t.sol +++ b/test/PDPVerifierProofTest.t.sol @@ -22,7 +22,7 @@ contract PDPVerifierProofTest is MockFVMTest, ProofBuilderHelper, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1, address(0), 1, address(0)); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1); bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, CHALLENGE_FINALITY_DELAY); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); @@ -119,7 +119,7 @@ contract PDPVerifierProofTest is MockFVMTest, ProofBuilderHelper, PieceHelper { function testDataSetLastProvenEpochOnPieceRemoval() public { // Create a data set and verify initial lastProvenEpoch is 0 - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); assertEq(pdpVerifier.getDataSetLastProvenEpoch(setId), 0, "Initial lastProvenEpoch should be 0"); @@ -202,7 +202,7 @@ contract PDPVerifierProofTest is MockFVMTest, ProofBuilderHelper, PieceHelper { } function testProvePossessionFailsWithNoScheduledChallenge() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); Cids.Cid[] memory pieces = new Cids.Cid[](1); @@ -224,7 +224,7 @@ contract PDPVerifierProofTest is MockFVMTest, ProofBuilderHelper, PieceHelper { } function testEmptyProofRejected() public { - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); IPDPTypes.Proof[] memory emptyProof = new IPDPTypes.Proof[](0); @@ -409,7 +409,7 @@ contract PDPVerifierProofTest is MockFVMTest, ProofBuilderHelper, PieceHelper { } // Create new data set and add pieces. - uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee() + PDPFees.cleanupDeposit()}( + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); pdpVerifier.addPieces(setId, address(0), pieces, empty); From 1bee1a3c56c38cf91f81a94d1461afd31de6686d Mon Sep 17 00:00:00 2001 From: William Morriss Date: Mon, 18 May 2026 13:07:57 -0500 Subject: [PATCH 03/22] refactor: non-public constants and do not set zero defaults during init --- src/PDPVerifier.sol | 14 ++++++-------- test/PDPVerifier.t.sol | 8 ++++---- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/PDPVerifier.sol b/src/PDPVerifier.sol index 68371f7..3049c2c 100644 --- a/src/PDPVerifier.sol +++ b/src/PDPVerifier.sol @@ -38,17 +38,17 @@ interface PDPListener { } uint256 constant NEW_DATA_SET_SENTINEL = 0; +// Defaults +uint256 constant NO_CHALLENGE_SCHEDULED = 0; +uint256 constant NO_PROVEN_EPOCH = 0; contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { // Constants uint256 public constant MAX_PIECE_SIZE_LOG2 = 50; uint256 public constant MAX_ENQUEUED_REMOVALS = 2000; - uint256 public constant NO_CHALLENGE_SCHEDULED = 0; - uint256 public constant NO_PROVEN_EPOCH = 0; - // Sentinel written to nextChallengeEpoch[setId] when a data set enters cleanup mode after deleteDataSet. - // Real challenge epochs are block numbers bounded well below type(uint256).max. - uint256 public constant CLEANUP_MODE_SENTINEL = type(uint256).max; - // Number of blocks of SP inactivity after which permissionless deletion/cleanup is allowed (~30 days on mainnet). + + // Cleanup + uint256 private constant CLEANUP_MODE_SENTINEL = type(uint256).max; uint256 public constant INACTIVITY_WINDOW = 86400; // Upgrade sequence number, used by Initializable.reinitializer @@ -540,10 +540,8 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { function _createDataSet(address listenerAddr, bytes memory extraData) internal returns (uint256) { uint256 setId = nextDataSetId++; dataSetLeafCount[setId] = 0; - nextChallengeEpoch[setId] = NO_CHALLENGE_SCHEDULED; // initialized on first call to NextProvingPeriod storageProvider[setId] = msg.sender; dataSetListener[setId] = listenerAddr; - dataSetLastProvenEpoch[setId] = NO_PROVEN_EPOCH; if (listenerAddr != address(0)) { PDPListener(listenerAddr).dataSetCreated(setId, msg.sender, extraData); diff --git a/test/PDPVerifier.t.sol b/test/PDPVerifier.t.sol index e964c50..b899747 100644 --- a/test/PDPVerifier.t.sol +++ b/test/PDPVerifier.t.sol @@ -14,7 +14,7 @@ import {IPDPTypes} from "../src/interfaces/IPDPTypes.sol"; import {IPDPEvents} from "../src/interfaces/IPDPEvents.sol"; import {PieceHelper} from "./PieceHelper.t.sol"; import {ProofBuilderHelper} from "./ProofBuilderHelper.t.sol"; -import {NEW_DATA_SET_SENTINEL} from "../src/PDPVerifier.sol"; +import {NEW_DATA_SET_SENTINEL, NO_CHALLENGE_SCHEDULED} from "../src/PDPVerifier.sol"; contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { TestingRecordKeeperService listener; @@ -522,7 +522,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { Cids.Cid[] memory pieces = new Cids.Cid[](1); pieces[0] = makeSamplePiece(2); pdpVerifier.addPieces(setId, address(0), pieces, empty); - assertEq(pdpVerifier.getNextChallengeEpoch(setId), pdpVerifier.NO_CHALLENGE_SCHEDULED()); // Not updated on first add anymore + assertEq(pdpVerifier.getNextChallengeEpoch(setId), NO_CHALLENGE_SCHEDULED); // Not updated on first add anymore pdpVerifier.nextProvingPeriod(setId, vm.getBlockNumber() + CHALLENGE_FINALITY_DELAY, empty); assertEq(pdpVerifier.getNextChallengeEpoch(setId), vm.getBlockNumber() + CHALLENGE_FINALITY_DELAY); @@ -535,7 +535,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { emit IPDPEvents.PiecesRemoved(setId, toRemove); pdpVerifier.nextProvingPeriod(setId, vm.getBlockNumber() + CHALLENGE_FINALITY_DELAY, empty); // flush - assertEq(pdpVerifier.getNextChallengeEpoch(setId), pdpVerifier.NO_CHALLENGE_SCHEDULED()); + assertEq(pdpVerifier.getNextChallengeEpoch(setId), NO_CHALLENGE_SCHEDULED); assertEq(pdpVerifier.pieceLive(setId, 0), false); assertEq(pdpVerifier.getNextPieceId(setId), 1); assertEq(pdpVerifier.getDataSetLeafCount(setId), 0); @@ -830,7 +830,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function testNextProvingPeriodWithNoData() public { // Get the NO_CHALLENGE_SCHEDULED constant value for clarity - uint256 noChallenge = pdpVerifier.NO_CHALLENGE_SCHEDULED(); + uint256 noChallenge = NO_CHALLENGE_SCHEDULED; uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) From f74971e634ba9925afdafc51ead1a5cf86965ad1 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Mon, 18 May 2026 13:25:22 -0500 Subject: [PATCH 04/22] test(cleanup): verify piece storage slots are cleared after cleanupPieces Assisted-by: Claude:claude-sonnet-4-6 --- test/CleanupPieces.t.sol | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/CleanupPieces.t.sol b/test/CleanupPieces.t.sol index 6d4b70d..664eb59 100644 --- a/test/CleanupPieces.t.sol +++ b/test/CleanupPieces.t.sol @@ -9,6 +9,7 @@ import {PDPFees} from "../src/Fees.sol"; import {PDPRecordKeeper} from "../src/SimplePDPService.sol"; import {PieceHelper} from "./PieceHelper.t.sol"; import {NEW_DATA_SET_SENTINEL} from "../src/PDPVerifier.sol"; +import {PIECE_CIDS_SLOT, PIECE_LEAF_COUNTS_SLOT, SUM_TREE_COUNTS_SLOT} from "../src/PDPVerifierLayout.sol"; contract TestListener is PDPListener, PDPRecordKeeper { function storageProviderChanged(uint256, address, address, bytes calldata) external override {} @@ -84,6 +85,36 @@ contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { } } + // Asserts that pieceCids, pieceLeafCounts, and sumTreeCounts are all zero for + // every piece slot [0, numPieces) on the given data set. Reads raw storage via + // vm.load so that the check works even after the data set is no longer live. + function assertPieceSlotsCleared(uint256 setId, uint256 numPieces) internal view { + // Inner mapping root keyed by setId — shared across all pieces. + bytes32 cidRoot = keccak256(abi.encode(setId, PIECE_CIDS_SLOT)); + bytes32 leafRoot = keccak256(abi.encode(setId, PIECE_LEAF_COUNTS_SLOT)); + bytes32 sumRoot = keccak256(abi.encode(setId, SUM_TREE_COUNTS_SLOT)); + + for (uint256 pieceId = 0; pieceId < numPieces; pieceId++) { + // pieceCids stores a `bytes` value; for a 36-byte CID the header holds length*2+1=73. + // After delete, the header must be zero. + assertEq( + vm.load(address(pdpVerifier), keccak256(abi.encode(pieceId, cidRoot))), + bytes32(0), + "pieceCids header not cleared" + ); + assertEq( + vm.load(address(pdpVerifier), keccak256(abi.encode(pieceId, leafRoot))), + bytes32(0), + "pieceLeafCounts not cleared" + ); + assertEq( + vm.load(address(pdpVerifier), keccak256(abi.encode(pieceId, sumRoot))), + bytes32(0), + "sumTreeCounts not cleared" + ); + } + } + // --- cleanupPieces basics --- function testCleanupPiecesIncrementalBatches() public { @@ -100,6 +131,7 @@ contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { done = pdpVerifier.cleanupPieces(setId, 1); assertTrue(done, "should be done after last piece"); assertEq(address(this).balance - balanceBefore, PDPFees.cleanupDeposit(), "deposit returned on completion"); + assertPieceSlotsCleared(setId, 3); } function testCleanupPiecesInOneCall() public { @@ -110,6 +142,7 @@ contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { bool done = pdpVerifier.cleanupPieces(setId, 100); assertTrue(done); assertEq(address(this).balance - balanceBefore, PDPFees.cleanupDeposit()); + assertPieceSlotsCleared(setId, 2); } function testCleanupPiecesDepositNotPaidUntilComplete() public { @@ -158,6 +191,7 @@ contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { // SP succeeds bool done = pdpVerifier.cleanupPieces(setId, 10); assertTrue(done); + assertPieceSlotsCleared(setId, 1); } function testCleanupPermissionlessAfterInactivityWindow() public { @@ -174,6 +208,7 @@ contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { bool done = pdpVerifier.cleanupPieces(setId, 10); assertTrue(done); assertEq(anyone.balance - balanceBefore, PDPFees.cleanupDeposit(), "third party receives deposit"); + assertPieceSlotsCleared(setId, 1); } function testPermissionlessDeleteAfterInactivity() public { From 1474952793d419f353b3031eee2b161ef34f0822 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Mon, 18 May 2026 17:54:10 -0500 Subject: [PATCH 05/22] perf: check before store --- src/PDPVerifier.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PDPVerifier.sol b/src/PDPVerifier.sol index 3049c2c..5cee34a 100644 --- a/src/PDPVerifier.sol +++ b/src/PDPVerifier.sol @@ -218,8 +218,8 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { // Must be called after all state changes to avoid re-entrancy issues. function _handleFeesWithDeposit(uint256 setId) internal { uint256 deposit = PDPFees.cleanupDeposit(); - cleanupDeposit[setId] = deposit; require(msg.value >= deposit, "cleanup deposit required"); + cleanupDeposit[setId] = deposit; uint256 excess = msg.value - deposit; if (excess > 0) { (bool success,) = msg.sender.call{value: excess}(""); From c3782e215776dd45378bb1a97f1b72d061783ad2 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Mon, 18 May 2026 18:34:37 -0500 Subject: [PATCH 06/22] test(cleanup): verify cleanupPieces rejects live and non-existent data sets Assisted-by: Claude:claude-sonnet-4-6 --- test/CleanupPieces.t.sol | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/CleanupPieces.t.sol b/test/CleanupPieces.t.sol index 664eb59..0669d60 100644 --- a/test/CleanupPieces.t.sol +++ b/test/CleanupPieces.t.sol @@ -242,6 +242,19 @@ contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { pdpVerifier.cleanupPieces(setId, 10); } + function testCleanupPiecesFailsOnLiveZeroPieceDataSet() public { + uint256 setId = _createAndPopulate(0); + // live with no pieces — still not in cleanup mode + vm.expectRevert("data set not in cleanup mode"); + pdpVerifier.cleanupPieces(setId, 10); + } + + function testCleanupPiecesFailsOnNonExistentDataSet() public { + uint256 nonExistent = pdpVerifier.getNextDataSetId(); + vm.expectRevert("data set not in cleanup mode"); + pdpVerifier.cleanupPieces(nonExistent, 10); + } + function testCleanupPiecesMaxPiecesZeroReverts() public { uint256 setId = _createAndPopulate(1); pdpVerifier.deleteDataSet(setId, empty); From 0acf22fe9d0f4cca6cd6c960b4f862a3edfcf5f1 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Mon, 18 May 2026 19:24:48 -0500 Subject: [PATCH 07/22] refactor(cleanup): replace string errors with typed errors, saving 444 bytes Assisted-by: Claude:claude-sonnet-4-6 --- src/PDPVerifier.sol | 27 ++++++++++++++++++--------- test/CleanupPieces.t.sol | 16 ++++++++-------- test/PDPVerifier.t.sol | 10 +++++----- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/PDPVerifier.sol b/src/PDPVerifier.sol index 5cee34a..f402218 100644 --- a/src/PDPVerifier.sol +++ b/src/PDPVerifier.sol @@ -218,12 +218,12 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { // Must be called after all state changes to avoid re-entrancy issues. function _handleFeesWithDeposit(uint256 setId) internal { uint256 deposit = PDPFees.cleanupDeposit(); - require(msg.value >= deposit, "cleanup deposit required"); + require(msg.value >= deposit, CleanupDepositRequired()); cleanupDeposit[setId] = deposit; uint256 excess = msg.value - deposit; if (excess > 0) { (bool success,) = msg.sender.call{value: excess}(""); - require(success, "Transfer failed."); + require(success, TransferFailed()); } } @@ -580,12 +580,12 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { } address sp = storageProvider[setId]; - require(sp != address(0), "data set not live"); - require(nextChallengeEpoch[setId] != CLEANUP_MODE_SENTINEL, "data set already in cleanup"); + require(sp != address(0), DataSetNotLive()); + require(nextChallengeEpoch[setId] != CLEANUP_MODE_SENTINEL, DataSetAlreadyInCleanup()); // Permissionless if the SP has been inactive for more than INACTIVITY_WINDOW blocks. if (block.number <= dataSetLastProvenEpoch[setId] + INACTIVITY_WINDOW) { - require(msg.sender == sp, "Only the storage provider can delete data sets"); + require(msg.sender == sp, OnlyStorageProviderCanDelete()); } uint256 deletedLeafCount = dataSetLeafCount[setId]; @@ -618,18 +618,18 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { // On the final call that clears all pieces, all remaining data set state is also cleared and // the cleanup deposit is transferred to msg.sender. Returns true when cleanup is complete. function cleanupPieces(uint256 setId, uint256 maxPieces) external returns (bool done) { - require(maxPieces > 0, "maxPieces must be greater than 0"); + require(maxPieces > 0, MaxPiecesMustBePositive()); bool isCleanupMode = nextChallengeEpoch[setId] == CLEANUP_MODE_SENTINEL; // Legacy data sets deleted before this feature: storageProvider already zeroed, pieces remain. bool isLegacyDataset = storageProvider[setId] == address(0) && nextPieceId[setId] > 0; - require(isCleanupMode || isLegacyDataset, "data set not in cleanup mode"); + require(isCleanupMode || isLegacyDataset, DataSetNotInCleanupMode()); if (isCleanupMode) { // Same inactivity gate as deleteDataSet, anchored to when cleanup mode was entered. if (block.number <= cleanupModeEpoch[setId] + INACTIVITY_WINDOW) { - require(msg.sender == storageProvider[setId], "Only the storage provider can clean up pieces"); + require(msg.sender == storageProvider[setId], OnlyStorageProviderCanCleanupPieces()); } } @@ -674,7 +674,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { if (deposit > 0) { (bool success,) = msg.sender.call{value: deposit}(""); - require(success, "Deposit transfer failed"); + require(success, DepositTransferFailed()); } } @@ -758,6 +758,15 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { } error IndexedError(uint256 idx, string msg); + error CleanupDepositRequired(); + error DataSetNotLive(); + error DataSetAlreadyInCleanup(); + error OnlyStorageProviderCanDelete(); + error MaxPiecesMustBePositive(); + error DataSetNotInCleanupMode(); + error OnlyStorageProviderCanCleanupPieces(); + error DepositTransferFailed(); + error TransferFailed(); function addOnePiece(uint256 setId, uint256 callIdx, Cids.Cid calldata piece) internal returns (uint256) { (uint256 padding, uint8 height,) = Cids.validateCommPv2(piece); diff --git a/test/CleanupPieces.t.sol b/test/CleanupPieces.t.sol index 0669d60..4406684 100644 --- a/test/CleanupPieces.t.sol +++ b/test/CleanupPieces.t.sol @@ -172,7 +172,7 @@ contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { pdpVerifier.deleteDataSet(setId, empty); // After full finalization, cleanupPieces should revert - vm.expectRevert("data set not in cleanup mode"); + vm.expectRevert(PDPVerifier.DataSetNotInCleanupMode.selector); pdpVerifier.cleanupPieces(setId, 10); } @@ -185,7 +185,7 @@ contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { // block.number (1) <= cleanupModeEpoch + INACTIVITY_WINDOW, so only SP can call address notSp = address(0xBEEF); vm.prank(notSp); - vm.expectRevert("Only the storage provider can clean up pieces"); + vm.expectRevert(PDPVerifier.OnlyStorageProviderCanCleanupPieces.selector); pdpVerifier.cleanupPieces(setId, 10); // SP succeeds @@ -229,7 +229,7 @@ contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { // block.number (1) <= lastProvenEpoch(0) + INACTIVITY_WINDOW, so only SP can delete address notSp = address(0xBEEF); vm.prank(notSp); - vm.expectRevert("Only the storage provider can delete data sets"); + vm.expectRevert(PDPVerifier.OnlyStorageProviderCanDelete.selector); pdpVerifier.deleteDataSet(setId, empty); } @@ -238,20 +238,20 @@ contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { function testCleanupPiecesRequiresCleanupMode() public { uint256 setId = _createAndPopulate(1); // data set is still live — cleanupPieces must revert - vm.expectRevert("data set not in cleanup mode"); + vm.expectRevert(PDPVerifier.DataSetNotInCleanupMode.selector); pdpVerifier.cleanupPieces(setId, 10); } function testCleanupPiecesFailsOnLiveZeroPieceDataSet() public { uint256 setId = _createAndPopulate(0); // live with no pieces — still not in cleanup mode - vm.expectRevert("data set not in cleanup mode"); + vm.expectRevert(PDPVerifier.DataSetNotInCleanupMode.selector); pdpVerifier.cleanupPieces(setId, 10); } function testCleanupPiecesFailsOnNonExistentDataSet() public { uint256 nonExistent = pdpVerifier.getNextDataSetId(); - vm.expectRevert("data set not in cleanup mode"); + vm.expectRevert(PDPVerifier.DataSetNotInCleanupMode.selector); pdpVerifier.cleanupPieces(nonExistent, 10); } @@ -259,7 +259,7 @@ contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { uint256 setId = _createAndPopulate(1); pdpVerifier.deleteDataSet(setId, empty); - vm.expectRevert("maxPieces must be greater than 0"); + vm.expectRevert(PDPVerifier.MaxPiecesMustBePositive.selector); pdpVerifier.cleanupPieces(setId, 0); } @@ -267,7 +267,7 @@ contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { uint256 setId = _createAndPopulate(1); pdpVerifier.deleteDataSet(setId, empty); - vm.expectRevert("data set already in cleanup"); + vm.expectRevert(PDPVerifier.DataSetAlreadyInCleanup.selector); pdpVerifier.deleteDataSet(setId, empty); } diff --git a/test/PDPVerifier.t.sol b/test/PDPVerifier.t.sol index b899747..b27b47c 100644 --- a/test/PDPVerifier.t.sol +++ b/test/PDPVerifier.t.sol @@ -80,7 +80,7 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { address nonStorageProvider = address(0x1234); // Expect revert when non-storage-provider tries to delete the data set vm.prank(nonStorageProvider); - vm.expectRevert("Only the storage provider can delete data sets"); + vm.expectRevert(PDPVerifier.OnlyStorageProviderCanDelete.selector); pdpVerifier.deleteDataSet(setId, empty); // Now verify the storage provider can delete the data set @@ -94,7 +94,7 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { // TODO: once we have addPieces we should test deletion of a non empty data set function testCannotDeleteNonExistentDataSet() public { // Test with data set ID 0 (which is never valid since IDs start from 1) - vm.expectRevert("data set not live"); + vm.expectRevert(PDPVerifier.DataSetNotLive.selector); pdpVerifier.deleteDataSet(0, empty); // Test with a data set ID that hasn't been created yet @@ -110,7 +110,7 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { vm.expectEmit(true, true, false, false); emit IPDPEvents.DataSetDeleted(setId, 0); pdpVerifier.deleteDataSet(setId, empty); - vm.expectRevert("data set not live"); + vm.expectRevert(PDPVerifier.DataSetNotLive.selector); pdpVerifier.deleteDataSet(setId, empty); vm.expectRevert("Data set not live"); pdpVerifier.getDataSetStorageProvider(setId); @@ -158,7 +158,7 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { uint256 required = PDPFees.cleanupDeposit(); // Test 1: Fails when sending not enough for cleanup deposit - vm.expectRevert("cleanup deposit required"); + vm.expectRevert(PDPVerifier.CleanupDepositRequired.selector); pdpVerifier.createDataSet{value: required - 1}(address(listener), empty); // Test 2: Returns funds over the required amount back to the sender @@ -774,7 +774,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { // Try to delete data set as non-storage-provider vm.prank(nonStorageProvider); - vm.expectRevert("Only the storage provider can delete data sets"); + vm.expectRevert(PDPVerifier.OnlyStorageProviderCanDelete.selector); pdpVerifier.deleteDataSet(setId, empty); // Try to schedule removals as non-storage-provider From 900a2d6bda2621ab7f14e5ffb47c5645a0e7a628 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Mon, 18 May 2026 19:35:39 -0500 Subject: [PATCH 08/22] refactor(cleanup): use FVMPay.pay for deposit refunds instead of call{value} Assisted-by: Claude:claude-sonnet-4-6 --- src/PDPVerifier.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/PDPVerifier.sol b/src/PDPVerifier.sol index f402218..b67f5f4 100644 --- a/src/PDPVerifier.sol +++ b/src/PDPVerifier.sol @@ -222,8 +222,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { cleanupDeposit[setId] = deposit; uint256 excess = msg.value - deposit; if (excess > 0) { - (bool success,) = msg.sender.call{value: excess}(""); - require(success, TransferFailed()); + require(FVMPay.pay(msg.sender, excess), TransferFailed()); } } @@ -673,8 +672,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { delete cleanupDeposit[setId]; if (deposit > 0) { - (bool success,) = msg.sender.call{value: deposit}(""); - require(success, DepositTransferFailed()); + require(FVMPay.pay(msg.sender, deposit), DepositTransferFailed()); } } From 370d0990bedfeec7d06cd704f1e1596f6395f8ad Mon Sep 17 00:00:00 2001 From: William Morriss Date: Mon, 18 May 2026 20:08:36 -0500 Subject: [PATCH 09/22] docs: document that FVMPay.pay cannot re-enter via SEND method Assisted-by: Claude:claude-sonnet-4-6 --- src/PDPVerifier.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PDPVerifier.sol b/src/PDPVerifier.sol index b67f5f4..8ceb4d6 100644 --- a/src/PDPVerifier.sol +++ b/src/PDPVerifier.sol @@ -215,7 +215,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { } // Handles fee payment for dataset creation: requires cleanup deposit in FIL. Stores the deposit for setId. - // Must be called after all state changes to avoid re-entrancy issues. + // FVMPay.pay uses the SEND method (0) which transfers value without executing recipient code, so it cannot re-enter. function _handleFeesWithDeposit(uint256 setId) internal { uint256 deposit = PDPFees.cleanupDeposit(); require(msg.value >= deposit, CleanupDepositRequired()); @@ -885,8 +885,8 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { emit PossessionProven(setId, challenges); - // Return the overpayment after doing everything else to avoid re-entrancy issues (all state has been updated by this point). If this - // call fails, the entire operation reverts. + // Return the overpayment. FVMPay.pay uses SEND (method 0) which transfers value without executing recipient code and cannot re-enter. + // If the transfer fails, the entire operation reverts. if (refund > 0) { bool success = FVMPay.pay(msg.sender, refund); require(success, "Transfer failed."); From 82d058c5f56d370665d644387b8967e697e2dfd5 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Tue, 19 May 2026 02:35:50 -0500 Subject: [PATCH 10/22] chore: update layout file --- src/PDPVerifierLayout.json | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/PDPVerifierLayout.json b/src/PDPVerifierLayout.json index a6ec5e6..09c3aa6 100644 --- a/src/PDPVerifierLayout.json +++ b/src/PDPVerifierLayout.json @@ -409,5 +409,45 @@ ], "numberOfBytes": "32" } + }, + { + "label": "cleanupDeposit", + "offset": 0, + "slot": "17", + "type": { + "encoding": "mapping", + "key": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "label": "mapping(uint256 => uint256)", + "numberOfBytes": "32", + "value": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + } + } + }, + { + "label": "cleanupModeEpoch", + "offset": 0, + "slot": "18", + "type": { + "encoding": "mapping", + "key": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "label": "mapping(uint256 => uint256)", + "numberOfBytes": "32", + "value": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + } + } } ] From 168740f64d3d5b2aa5115818f3922444e1254610 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Tue, 19 May 2026 15:38:46 -0500 Subject: [PATCH 11/22] test(cleanup): assert pieceCids dynamic data region is cleared Assisted-by: Claude:claude-sonnet-4-6 --- test/CleanupPieces.t.sol | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/CleanupPieces.t.sol b/test/CleanupPieces.t.sol index 4406684..c1b3ac2 100644 --- a/test/CleanupPieces.t.sol +++ b/test/CleanupPieces.t.sol @@ -95,12 +95,18 @@ contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { bytes32 sumRoot = keccak256(abi.encode(setId, SUM_TREE_COUNTS_SLOT)); for (uint256 pieceId = 0; pieceId < numPieces; pieceId++) { - // pieceCids stores a `bytes` value; for a 36-byte CID the header holds length*2+1=73. + // pieceCids stores a `bytes` value; for a 39-byte CID the header holds length*2+1=79. // After delete, the header must be zero. + bytes32 cidHeaderSlot = keccak256(abi.encode(pieceId, cidRoot)); + assertEq(vm.load(address(pdpVerifier), cidHeaderSlot), bytes32(0), "pieceCids header not cleared"); + // For a 39-byte CID (long encoding), the payload occupies 2 slots at keccak256(headerSlot). + // ceil(39/32) = 2, so both data slots must be zero for storage to be fully reclaimed. + bytes32 cidDataSlot = keccak256(abi.encodePacked(cidHeaderSlot)); + assertEq(vm.load(address(pdpVerifier), cidDataSlot), bytes32(0), "pieceCids data slot 0 not cleared"); assertEq( - vm.load(address(pdpVerifier), keccak256(abi.encode(pieceId, cidRoot))), + vm.load(address(pdpVerifier), bytes32(uint256(cidDataSlot) + 1)), bytes32(0), - "pieceCids header not cleared" + "pieceCids data slot 1 not cleared" ); assertEq( vm.load(address(pdpVerifier), keccak256(abi.encode(pieceId, leafRoot))), From 5b77f601f89af1de65980eec7415a4e500c16f81 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Tue, 19 May 2026 15:57:40 -0500 Subject: [PATCH 12/22] test(fees): assert FIL_CLEANUP_DEPOSIT returns 0.1 FIL Assisted-by: Claude:claude-sonnet-4-6 --- test/Fees.t.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/Fees.t.sol b/test/Fees.t.sol index e29644d..6819224 100644 --- a/test/Fees.t.sol +++ b/test/Fees.t.sol @@ -40,4 +40,8 @@ contract PDPFeesTest is Test { uint256 expectedFee = 0.00023 ether; // 0.00023 FIL in attoFIL assertEq(PDPFees.DEFAULT_FEE_PER_TIB, expectedFee, "DEFAULT_FEE_PER_TIB should be 0.00023 FIL"); } + + function testCleanupDepositConstant() public pure { + assertEq(PDPFees.cleanupDeposit(), 0.1 ether, "FIL_CLEANUP_DEPOSIT should be 0.1 FIL"); + } } From ab0f3e111d8e10dcc6c7139e0cbd129da121fdf6 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Tue, 19 May 2026 16:00:38 -0500 Subject: [PATCH 13/22] chore: simplify constant --- src/Fees.sol | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Fees.sol b/src/Fees.sol index 9f5a45b..16940b2 100644 --- a/src/Fees.sol +++ b/src/Fees.sol @@ -4,11 +4,8 @@ pragma solidity ^0.8.20; /// @title PDPFees /// @notice A library for calculating fees for the PDP. library PDPFees { - uint256 constant ATTO_FIL = 1; - uint256 constant FIL_TO_ATTO_FIL = 1e18 * ATTO_FIL; - // 0.1 FIL cleanup bond held per data set, returned to whoever finalizes cleanup - uint256 constant CLEANUP_DEPOSIT = FIL_TO_ATTO_FIL / 10; + uint256 constant CLEANUP_DEPOSIT = 0.1 ether; // Default FIL-based proof fee: 0.00023 FIL per TiB (used for initialization) // Based on: 0.00067 USD per TiB / 2.88 USD per FIL = 0.00023 FIL per TiB From 5cdb655cc94e866678039e70c5cfb1105bd07ff0 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Wed, 20 May 2026 15:53:50 -0500 Subject: [PATCH 14/22] refactor(verifier): move challengeFinality from storage to immutable constructor arg, add MAX_FINALITY upper bound Assisted-by: Claude:claude-sonnet-4-6 --- src/PDPVerifier.sol | 28 ++++++--- test/CleanupPieces.t.sol | 4 +- test/ERC1967Proxy.t.sol | 9 +-- test/PDPVerifier.t.sol | 62 ++++++++++--------- test/PDPVerifierProofTest.t.sol | 4 +- tools/deploy-calibnet.sh | 4 +- tools/deploy-devnet.sh | 9 ++- tools/deploy-mainnet.sh | 4 +- ...loy-transfer-ownership-upgrade-calibnet.sh | 10 +-- tools/upgrade-contract.sh | 4 +- 10 files changed, 76 insertions(+), 62 deletions(-) diff --git a/src/PDPVerifier.sol b/src/PDPVerifier.sol index 8ceb4d6..348dde9 100644 --- a/src/PDPVerifier.sol +++ b/src/PDPVerifier.sol @@ -107,14 +107,20 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { // randomness sampling for challenge generation. // // The purpose of this delay is to prevent SPs from biasing randomness by running forking attacks. - // Given a small enough challengeFinality an SP can run several trials of challenge sampling and + // Given a small enough CHALLENGE_FINALITY an SP can run several trials of challenge sampling and // fork around samples that don't suit them, grinding the challenge randomness. // For the filecoin L1, a safe value is 150 using the same analysis setting 150 epochs between // PoRep precommit and PoRep provecommit phases. // // We keep this around for future portability to a variety of environments with different assumptions // behind their challenge randomness sampling methods. - uint256 challengeFinality; + + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + uint256 private immutable CHALLENGE_FINALITY; + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + uint256 private constant MAX_FINALITY = 259200; // 90 days of 30-second epochs + + uint256 deprecatedChallengeFinality; // TODO PERF: https://github.com/FILCAT/pdp/issues/16#issuecomment-2329838769 uint64 nextDataSetId; @@ -172,15 +178,15 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { // Methods /// @custom:oz-upgrades-unsafe-allow constructor - constructor(uint64 _initializerVersion) { + constructor(uint64 _initializerVersion, uint256 _challengeFinality) { _disableInitializers(); REINITIALIZER_VERSION = _initializerVersion; + CHALLENGE_FINALITY = _challengeFinality; } - function initialize(uint256 _challengeFinality) public initializer { + function initialize() public initializer { __Ownable_init(msg.sender); __UUPSUpgradeable_init(); - challengeFinality = _challengeFinality; nextDataSetId = 1; // Data sets start at 1 feeStatus.nextFeePerTiB = PDPFees.DEFAULT_FEE_PER_TIB; } @@ -228,7 +234,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { // Returns the current challenge finality value function getChallengeFinality() public view returns (uint256) { - return challengeFinality; + return CHALLENGE_FINALITY; } // Returns the next data set ID @@ -765,6 +771,8 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { error OnlyStorageProviderCanCleanupPieces(); error DepositTransferFailed(); error TransferFailed(); + error InsufficientChallengeDelay(uint256 epochs, uint256 minDelay); + error ExcessiveChallengeDelay(uint256 epochs, uint256 maxDelay); function addOnePiece(uint256 setId, uint256 callIdx, Cids.Cid calldata piece) internal returns (uint256) { (uint256 padding, uint8 height,) = Cids.validateCommPv2(piece); @@ -954,7 +962,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { // and can be deleted. And pieces added in the last proving period must be available for challenging. // // Additionally this method forces sampling of a new challenge. It enforces that the new - // challenge epoch is at least `challengeFinality` epochs in the future. + // challenge epoch is at least `CHALLENGE_FINALITY` epochs in the future. // // Note that this method can be called at any time but the pdpListener will likely consider it // a "fault" or other penalizeable behavior to call this method before calling provePossesion. @@ -991,8 +999,10 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { // Bring added pieces into proving set challengeRange[setId] = dataSetLeafCount[setId]; - if (challengeEpoch < block.number + challengeFinality) { - revert("challenge epoch must be at least challengeFinality epochs in the future"); + { + uint256 delayEpochs = challengeEpoch - block.number; + require(delayEpochs >= CHALLENGE_FINALITY, InsufficientChallengeDelay(delayEpochs, CHALLENGE_FINALITY)); + require(delayEpochs <= MAX_FINALITY, ExcessiveChallengeDelay(delayEpochs, MAX_FINALITY)); } nextChallengeEpoch[setId] = challengeEpoch; diff --git a/test/CleanupPieces.t.sol b/test/CleanupPieces.t.sol index c1b3ac2..188beac 100644 --- a/test/CleanupPieces.t.sol +++ b/test/CleanupPieces.t.sol @@ -65,8 +65,8 @@ contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1); - bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, CHALLENGE_FINALITY_DELAY); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1, CHALLENGE_FINALITY_DELAY); + bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); listener = new TestListener(); diff --git a/test/ERC1967Proxy.t.sol b/test/ERC1967Proxy.t.sol index 3202af1..fef73e5 100644 --- a/test/ERC1967Proxy.t.sol +++ b/test/ERC1967Proxy.t.sol @@ -16,14 +16,11 @@ contract ERC1967ProxyTest is Test { // Set owner for testing vm.startPrank(owner); // Deploy implementation contract - implementation = new PDPVerifier(1); - newImplementation = new PDPVerifier(2); + implementation = new PDPVerifier(1, 150); + newImplementation = new PDPVerifier(2, 150); // Deploy proxy pointing to implementation - bytes memory initData = abi.encodeWithSelector( - PDPVerifier.initialize.selector, - uint256(150) // challengeFinality - ); + bytes memory initData = abi.encodeWithSelector(PDPVerifier.initialize.selector); ERC1967Proxy proxyContract = new MyERC1967Proxy(address(implementation), initData); diff --git a/test/PDPVerifier.t.sol b/test/PDPVerifier.t.sol index b27b47c..ada8d63 100644 --- a/test/PDPVerifier.t.sol +++ b/test/PDPVerifier.t.sol @@ -23,9 +23,9 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1); uint256 challengeFinality = 2; - bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, challengeFinality); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1, challengeFinality); + bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); listener = new TestingRecordKeeperService(); @@ -240,8 +240,8 @@ contract PDPVerifierStorageProviderTest is MockFVMTest, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1); - bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, 2); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1, 2); + bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); listener = new TestingRecordKeeperService(); @@ -342,8 +342,8 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1); - bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, CHALLENGE_FINALITY_DELAY); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1, CHALLENGE_FINALITY_DELAY); + bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); listener = new TestingRecordKeeperService(); @@ -814,7 +814,11 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { uint256 tooSoonEpoch = currentBlock + CHALLENGE_FINALITY_DELAY - 1; // Expect revert with the specific error message - vm.expectRevert("challenge epoch must be at least challengeFinality epochs in the future"); + vm.expectRevert( + abi.encodeWithSelector( + PDPVerifier.InsufficientChallengeDelay.selector, CHALLENGE_FINALITY_DELAY - 1, CHALLENGE_FINALITY_DELAY + ) + ); pdpVerifier.nextProvingPeriod(setId, tooSoonEpoch, ""); // Set challenge epoch to exactly challengeFinality epochs in the future @@ -900,9 +904,9 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1); uint256 challengeFinality = 2; - bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, challengeFinality); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1, challengeFinality); + bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); listener = new TestingRecordKeeperService(); @@ -1294,7 +1298,7 @@ contract TestingRecordKeeperService is PDPListener, PDPRecordKeeper { } contract SumTreeInternalTestPDPVerifier is PDPVerifier { - constructor() PDPVerifier(1) {} + constructor(uint256 _challengeFinality) PDPVerifier(1, _challengeFinality) {} function getTestHeightFromIndex(uint256 index) public pure returns (uint256) { return heightFromIndex(index); @@ -1310,8 +1314,8 @@ contract SumTreeHeightTest is MockFVMTest { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new SumTreeInternalTestPDPVerifier(); - bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, 2); + PDPVerifier pdpVerifierImpl = new SumTreeInternalTestPDPVerifier(2); + bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = SumTreeInternalTestPDPVerifier(address(proxy)); } @@ -1444,8 +1448,8 @@ contract SumTreeAddTest is MockFVMTest, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new SumTreeInternalTestPDPVerifier(); - bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, CHALLENGE_FINALITY_DELAY); + PDPVerifier pdpVerifierImpl = new SumTreeInternalTestPDPVerifier(CHALLENGE_FINALITY_DELAY); + bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = SumTreeInternalTestPDPVerifier(address(proxy)); listener = new TestingRecordKeeperService(); @@ -1805,8 +1809,8 @@ contract PDPListenerIntegrationTest is MockFVMTest, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1); - bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, CHALLENGE_FINALITY_DELAY); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1, CHALLENGE_FINALITY_DELAY); + bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); badListener = new BadListener(); @@ -1895,8 +1899,8 @@ contract PDPVerifierExtraDataTest is MockFVMTest, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1); - bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, CHALLENGE_FINALITY_DELAY); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1, CHALLENGE_FINALITY_DELAY); + bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); extraDataListener = new ExtraDataListener(); @@ -1951,8 +1955,8 @@ contract PDPVerifierE2ETest is MockFVMTest, ProofBuilderHelper, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1); - bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, CHALLENGE_FINALITY_DELAY); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1, CHALLENGE_FINALITY_DELAY); + bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); listener = new TestingRecordKeeperService(); @@ -2091,9 +2095,9 @@ contract PDPVerifierMigrateTest is Test { PDPVerifier pdpVerifier; function setUp() public { - bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, 2); - implementation = new PDPVerifier(1); - newImplementation = new PDPVerifier(2); + bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector); + implementation = new PDPVerifier(1, 2); + newImplementation = new PDPVerifier(2, 2); proxy = new MyERC1967Proxy(address(implementation), initializeData); pdpVerifier = PDPVerifier(address(proxy)); } @@ -2205,8 +2209,8 @@ contract PDPVerifierFeeTest is MockFVMTest, PieceHelper, ProofBuilderHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1); - bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, CHALLENGE_FINALITY_DELAY); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1, CHALLENGE_FINALITY_DELAY); + bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); vm.fee(1 gwei); @@ -2345,8 +2349,8 @@ contract PDPVerifierStorageProviderListenerTest is MockFVMTest { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1); - bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, 2); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1, 2); + bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); listener = new MockStorageProviderChangedListener(); @@ -2387,8 +2391,8 @@ contract PDPVerifierCIDSearchTest is MockFVMTest, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1); - bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, 2); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1, 2); + bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); listener = new TestingRecordKeeperService(); diff --git a/test/PDPVerifierProofTest.t.sol b/test/PDPVerifierProofTest.t.sol index c8278ef..8bfabd0 100644 --- a/test/PDPVerifierProofTest.t.sol +++ b/test/PDPVerifierProofTest.t.sol @@ -22,8 +22,8 @@ contract PDPVerifierProofTest is MockFVMTest, ProofBuilderHelper, PieceHelper { function setUp() public override { super.setUp(); - PDPVerifier pdpVerifierImpl = new PDPVerifier(1); - bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, CHALLENGE_FINALITY_DELAY); + PDPVerifier pdpVerifierImpl = new PDPVerifier(1, CHALLENGE_FINALITY_DELAY); + bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector); MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); pdpVerifier = PDPVerifier(address(proxy)); listener = new TestingRecordKeeperService(); diff --git a/tools/deploy-calibnet.sh b/tools/deploy-calibnet.sh index 77d06fb..3b206ee 100755 --- a/tools/deploy-calibnet.sh +++ b/tools/deploy-calibnet.sh @@ -39,7 +39,7 @@ if [ "$USDFC_TOKEN_ADDRESS" = "$ZERO_ADDRESS" ] || [ "$PAYMENTS_CONTRACT_ADDRESS fi NONCE="$(cast nonce --rpc-url "$RPC_URL" "$ADDR")" -VERIFIER_IMPLEMENTATION_ADDRESS=$(forge create --rpc-url "$RPC_URL" --keystore "$KEYSTORE" --password "$PASSWORD" --broadcast --nonce $NONCE --chain-id 314159 src/PDPVerifier.sol:PDPVerifier --constructor-args "$VERIFIER_INIT_COUNTER" "$USDFC_TOKEN_ADDRESS" "$USDFC_SYBIL_FEE" "$PAYMENTS_CONTRACT_ADDRESS" | grep "Deployed to" | awk '{print $3}') +VERIFIER_IMPLEMENTATION_ADDRESS=$(forge create --rpc-url "$RPC_URL" --keystore "$KEYSTORE" --password "$PASSWORD" --broadcast --nonce $NONCE --chain-id 314159 src/PDPVerifier.sol:PDPVerifier --constructor-args "$VERIFIER_INIT_COUNTER" "$CHALLENGE_FINALITY" "$USDFC_TOKEN_ADDRESS" "$USDFC_SYBIL_FEE" "$PAYMENTS_CONTRACT_ADDRESS" | grep "Deployed to" | awk '{print $3}') if [ -z "$VERIFIER_IMPLEMENTATION_ADDRESS" ]; then echo "Error: Failed to extract PDP verifier contract address" exit 1 @@ -48,7 +48,7 @@ echo "PDP verifier implementation deployed at: $VERIFIER_IMPLEMENTATION_ADDRESS" echo "Deploying PDP verifier proxy" NONCE=$(expr $NONCE + "1") -INIT_DATA=$(cast calldata "initialize(uint256)" $CHALLENGE_FINALITY) +INIT_DATA=$(cast calldata "initialize()") PDP_VERIFIER_ADDRESS=$(forge create --rpc-url "$RPC_URL" --keystore "$KEYSTORE" --password "$PASSWORD" --broadcast --nonce $NONCE --chain-id 314159 src/ERC1967Proxy.sol:MyERC1967Proxy --constructor-args $VERIFIER_IMPLEMENTATION_ADDRESS $INIT_DATA | grep "Deployed to" | awk '{print $3}') echo "PDP verifier deployed at: $PDP_VERIFIER_ADDRESS" diff --git a/tools/deploy-devnet.sh b/tools/deploy-devnet.sh index 131e383..93f8173 100755 --- a/tools/deploy-devnet.sh +++ b/tools/deploy-devnet.sh @@ -26,9 +26,11 @@ sleep 5 ## Sleep for 5 seconds so fund are available and actor is registered NONCE="$(cast nonce --rpc-url "$RPC_URL" "$clientAddr")" VERIFIER_INIT_COUNTER=1 +# Devnet uses 10 epochs (same as Calibration testnet) +CHALLENGE_FINALITY=10 echo "Deploying PDP verifier" # Parse the output of forge create to extract the contract address -VERIFIER_IMPLEMENTATION_ADDRESS=$(forge create --rpc-url "$RPC_URL" --keystore "$KEYSTORE" --password "$PASSWORD" --nonce $NONCE --broadcast src/PDPVerifier.sol:PDPVerifier --constructor-args $VERIFIER_INIT_COUNTER | grep "Deployed to" | awk '{print $3}') +VERIFIER_IMPLEMENTATION_ADDRESS=$(forge create --rpc-url "$RPC_URL" --keystore "$KEYSTORE" --password "$PASSWORD" --nonce $NONCE --broadcast src/PDPVerifier.sol:PDPVerifier --constructor-args $VERIFIER_INIT_COUNTER $CHALLENGE_FINALITY | grep "Deployed to" | awk '{print $3}') if [ -z "$VERIFIER_IMPLEMENTATION_ADDRESS" ]; then echo "Error: Failed to extract PDP verifier contract address" exit 1 @@ -37,11 +39,8 @@ echo "PDP verifier implementation deployed at: $VERIFIER_IMPLEMENTATION_ADDRESS" NONCE=$(expr $NONCE + "1") -# Devnet uses 10 epochs (same as Calibration testnet) -CHALLENGE_FINALITY=10 - echo "Deploying PDP verifier proxy" -INIT_DATA=$(cast calldata "initialize(uint256)" $CHALLENGE_FINALITY) +INIT_DATA=$(cast calldata "initialize()") PDP_VERIFIER_ADDRESS=$(forge create --rpc-url "$RPC_URL" --keystore "$KEYSTORE" --password "$PASSWORD" --nonce $NONCE --broadcast src/ERC1967Proxy.sol:MyERC1967Proxy --constructor-args $VERIFIER_IMPLEMENTATION_ADDRESS $INIT_DATA | grep "Deployed to" | awk '{print $3}') echo "PDP verifier deployed at: $PDP_VERIFIER_ADDRESS" diff --git a/tools/deploy-mainnet.sh b/tools/deploy-mainnet.sh index 7905ff9..02af985 100755 --- a/tools/deploy-mainnet.sh +++ b/tools/deploy-mainnet.sh @@ -39,7 +39,7 @@ if [ "$USDFC_TOKEN_ADDRESS" = "$ZERO_ADDRESS" ] || [ "$PAYMENTS_CONTRACT_ADDRESS fi NONCE="$(cast nonce --rpc-url "$RPC_URL" "$ADDR")" -VERIFIER_IMPLEMENTATION_ADDRESS=$(forge create --rpc-url "$RPC_URL" --keystore "$KEYSTORE" --password "$PASSWORD" --broadcast --nonce $NONCE --chain-id 314 src/PDPVerifier.sol:PDPVerifier --constructor-args "$VERIFIER_INIT_COUNTER" "$USDFC_TOKEN_ADDRESS" "$USDFC_SYBIL_FEE" "$PAYMENTS_CONTRACT_ADDRESS" | grep "Deployed to" | awk '{print $3}') +VERIFIER_IMPLEMENTATION_ADDRESS=$(forge create --rpc-url "$RPC_URL" --keystore "$KEYSTORE" --password "$PASSWORD" --broadcast --nonce $NONCE --chain-id 314 src/PDPVerifier.sol:PDPVerifier --constructor-args "$VERIFIER_INIT_COUNTER" "$CHALLENGE_FINALITY" "$USDFC_TOKEN_ADDRESS" "$USDFC_SYBIL_FEE" "$PAYMENTS_CONTRACT_ADDRESS" | grep "Deployed to" | awk '{print $3}') if [ -z "$VERIFIER_IMPLEMENTATION_ADDRESS" ]; then echo "Error: Failed to extract PDP verifier contract address" exit 1 @@ -48,7 +48,7 @@ echo "PDP verifier implementation deployed at: $VERIFIER_IMPLEMENTATION_ADDRESS" echo "Deploying PDP verifier proxy" NONCE=$(expr $NONCE + "1") -INIT_DATA=$(cast calldata "initialize(uint256)" $CHALLENGE_FINALITY) +INIT_DATA=$(cast calldata "initialize()") PDP_VERIFIER_ADDRESS=$(forge create --rpc-url "$RPC_URL" --keystore "$KEYSTORE" --password "$PASSWORD" --broadcast --nonce $NONCE --chain-id 314 src/ERC1967Proxy.sol:MyERC1967Proxy --constructor-args $VERIFIER_IMPLEMENTATION_ADDRESS $INIT_DATA | grep "Deployed to" | awk '{print $3}') echo "PDP verifier deployed at: $PDP_VERIFIER_ADDRESS" diff --git a/tools/deploy-transfer-ownership-upgrade-calibnet.sh b/tools/deploy-transfer-ownership-upgrade-calibnet.sh index 997f7ce..d605999 100755 --- a/tools/deploy-transfer-ownership-upgrade-calibnet.sh +++ b/tools/deploy-transfer-ownership-upgrade-calibnet.sh @@ -16,8 +16,9 @@ COMPILER_VERSION="${COMPILER_VERSION:-0.8.22}" ##################################### # 1. Create INIT_DATA # ##################################### -echo "Generating calldata for initialize(uint256) with argument 150 ..." -INIT_DATA=$(cast calldata "initialize(uint256)" 150) +CHALLENGE_FINALITY=150 +echo "Generating calldata for initialize() ..." +INIT_DATA=$(cast calldata "initialize()") echo "INIT_DATA = $INIT_DATA" echo @@ -44,7 +45,7 @@ DEPLOY_OUTPUT_VERIFIER=$( --broadcast \ --nonce $NONCE \ src/PDPVerifier.sol:PDPVerifier \ - --constructor-args $VERIFIER_INIT_COUNTER + --constructor-args $VERIFIER_INIT_COUNTER $CHALLENGE_FINALITY ) NONCE=$(expr $NONCE + "1") @@ -121,7 +122,8 @@ echo "========================================" echo "Deploying a new PDPVerifier contract ..." # For the upgrade, compute next initializer counter from the proxy and pass it UPGRADE_INIT_COUNTER=$(expr "$("$SCRIPT_DIR/get-initialized-counter.sh" "$PROXY_ADDRESS")" + 1) -DEPLOY_OUTPUT_VERIFIER_2=$(forge create --nonce $NONCE --broadcast --rpc-url "$FIL_CALIBNET_RPC_URL" --private-key "$FIL_CALIBNET_PRIVATE_KEY" --chain-id "$CHAIN_ID" src/PDPVerifier.sol:PDPVerifier --constructor-args $UPGRADE_INIT_COUNTER) +UPGRADE_CHALLENGE_FINALITY=$(cast call --rpc-url "$FIL_CALIBNET_RPC_URL" "$PROXY_ADDRESS" "getChallengeFinality()(uint256)") +DEPLOY_OUTPUT_VERIFIER_2=$(forge create --nonce $NONCE --broadcast --rpc-url "$FIL_CALIBNET_RPC_URL" --private-key "$FIL_CALIBNET_PRIVATE_KEY" --chain-id "$CHAIN_ID" src/PDPVerifier.sol:PDPVerifier --constructor-args $UPGRADE_INIT_COUNTER $UPGRADE_CHALLENGE_FINALITY) NONCE=$(expr $NONCE + "1") PDP_VERIFIER_ADDRESS_2=$(echo "$DEPLOY_OUTPUT_VERIFIER_2" | grep "Deployed to" | awk '{print $3}') echo "PDPVerifier deployed at: $PDP_VERIFIER_ADDRESS_2" diff --git a/tools/upgrade-contract.sh b/tools/upgrade-contract.sh index be65977..4f06c39 100755 --- a/tools/upgrade-contract.sh +++ b/tools/upgrade-contract.sh @@ -54,6 +54,7 @@ if [ -z "$IMPLEMENTATION_PATH" ]; then fi UPGRADE_INIT_COUNTER=$(expr "$("$SCRIPT_DIR/get-initialized-counter.sh" "$PROXY_ADDRESS")" + 1) +CHALLENGE_FINALITY=$(cast call --rpc-url "$RPC_URL" "$PROXY_ADDRESS" "getChallengeFinality()(uint256)") CONSTRUCTOR_ARGS=("$UPGRADE_INIT_COUNTER") if [ "$IMPLEMENTATION_PATH" != "$PDP_VERIFIER_IMPLEMENTATION_PATH" ]; then @@ -81,10 +82,11 @@ case "$CHAIN_ID" in ;; esac USDFC_SYBIL_FEE="${USDFC_SYBIL_FEE:-100000000000000000}" -CONSTRUCTOR_ARGS=("$UPGRADE_INIT_COUNTER" "$USDFC_TOKEN_ADDRESS" "$USDFC_SYBIL_FEE" "$PAYMENTS_CONTRACT_ADDRESS") +CONSTRUCTOR_ARGS=("$UPGRADE_INIT_COUNTER" "$CHALLENGE_FINALITY" "$USDFC_TOKEN_ADDRESS" "$USDFC_SYBIL_FEE" "$PAYMENTS_CONTRACT_ADDRESS") echo "Using PDPVerifier constructor args:" echo " initializerVersion: $UPGRADE_INIT_COUNTER" +echo " challengeFinality: $CHALLENGE_FINALITY" echo " USDFC_TOKEN_ADDRESS: $USDFC_TOKEN_ADDRESS" echo " USDFC_SYBIL_FEE: $USDFC_SYBIL_FEE" echo " PAYMENTS_CONTRACT_ADDRESS: $PAYMENTS_CONTRACT_ADDRESS" From d3590ac7bd54c092be3c66cc345aa0af45d51966 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Wed, 20 May 2026 16:20:20 -0500 Subject: [PATCH 15/22] fix: max finality should be less than inactivity period --- src/PDPVerifier.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PDPVerifier.sol b/src/PDPVerifier.sol index 348dde9..107759a 100644 --- a/src/PDPVerifier.sol +++ b/src/PDPVerifier.sol @@ -117,8 +117,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { /// @custom:oz-upgrades-unsafe-allow state-variable-immutable uint256 private immutable CHALLENGE_FINALITY; - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - uint256 private constant MAX_FINALITY = 259200; // 90 days of 30-second epochs + uint256 private constant MAX_FINALITY = INACTIVITY_WINDOW / 2; uint256 deprecatedChallengeFinality; @@ -179,6 +178,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { /// @custom:oz-upgrades-unsafe-allow constructor constructor(uint64 _initializerVersion, uint256 _challengeFinality) { + require(_challengeFinality < MAX_FINALITY / 10); _disableInitializers(); REINITIALIZER_VERSION = _initializerVersion; CHALLENGE_FINALITY = _challengeFinality; From aa7e50d9b7b8682e958755ee43f0336e1b981fa4 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Wed, 20 May 2026 16:49:56 -0500 Subject: [PATCH 16/22] fix(verifier): prevent immediate permissionless deletion on dataset creation, last-piece removal, and post-upgrade Assisted-by: Claude:claude-sonnet-4-6 --- src/PDPVerifier.sol | 16 ++++++++++++++-- test/CleanupPieces.t.sol | 4 ++-- test/PDPVerifier.t.sol | 2 +- test/PDPVerifierProofTest.t.sol | 21 +++++++++++++-------- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/PDPVerifier.sol b/src/PDPVerifier.sol index 107759a..aa65c10 100644 --- a/src/PDPVerifier.sol +++ b/src/PDPVerifier.sol @@ -118,6 +118,11 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { /// @custom:oz-upgrades-unsafe-allow state-variable-immutable uint256 private immutable CHALLENGE_FINALITY; uint256 private constant MAX_FINALITY = INACTIVITY_WINDOW / 2; + // Block number when this implementation was deployed. Used as the activity baseline for legacy + // datasets (dataSetLastProvenEpoch == 0) so they cannot be permissionlessly deleted immediately + // after upgrade. + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + uint256 public immutable LEGACY_ACTIVITY_EPOCH; uint256 deprecatedChallengeFinality; @@ -182,6 +187,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { _disableInitializers(); REINITIALIZER_VERSION = _initializerVersion; CHALLENGE_FINALITY = _challengeFinality; + LEGACY_ACTIVITY_EPOCH = block.number; } function initialize() public initializer { @@ -547,6 +553,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { dataSetLeafCount[setId] = 0; storageProvider[setId] = msg.sender; dataSetListener[setId] = listenerAddr; + dataSetLastProvenEpoch[setId] = block.number; if (listenerAddr != address(0)) { PDPListener(listenerAddr).dataSetCreated(setId, msg.sender, extraData); @@ -589,7 +596,12 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { require(nextChallengeEpoch[setId] != CLEANUP_MODE_SENTINEL, DataSetAlreadyInCleanup()); // Permissionless if the SP has been inactive for more than INACTIVITY_WINDOW blocks. - if (block.number <= dataSetLastProvenEpoch[setId] + INACTIVITY_WINDOW) { + // Legacy datasets (lastProvenEpoch == 0) use the implementation deployment block as baseline. + uint256 lastActivity = dataSetLastProvenEpoch[setId]; + if (lastActivity == NO_PROVEN_EPOCH) { + lastActivity = LEGACY_ACTIVITY_EPOCH; + } + if (block.number <= lastActivity + INACTIVITY_WINDOW) { require(msg.sender == sp, OnlyStorageProviderCanDelete()); } @@ -1010,7 +1022,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { // It will be re-set after new data is added and nextProvingPeriod is called. if (dataSetLeafCount[setId] == 0) { emit DataSetEmpty(setId); - dataSetLastProvenEpoch[setId] = NO_PROVEN_EPOCH; + dataSetLastProvenEpoch[setId] = block.number; nextChallengeEpoch[setId] = NO_CHALLENGE_SCHEDULED; } diff --git a/test/CleanupPieces.t.sol b/test/CleanupPieces.t.sol index 188beac..039e955 100644 --- a/test/CleanupPieces.t.sol +++ b/test/CleanupPieces.t.sol @@ -220,8 +220,8 @@ contract PDPVerifierCleanupTest is MockFVMTest, PieceHelper { function testPermissionlessDeleteAfterInactivity() public { uint256 setId = _createAndPopulate(1); - // Roll past the inactivity window from last proven epoch (NO_PROVEN_EPOCH = 0) - vm.roll(pdpVerifier.INACTIVITY_WINDOW() + 1); + // Roll past the inactivity window from the dataset creation block + vm.roll(block.number + pdpVerifier.INACTIVITY_WINDOW() + 1); address anyone = address(0xDEAD); vm.prank(anyone); diff --git a/test/PDPVerifier.t.sol b/test/PDPVerifier.t.sol index ada8d63..46e15ba 100644 --- a/test/PDPVerifier.t.sol +++ b/test/PDPVerifier.t.sol @@ -893,7 +893,7 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { // Verify the data set is indeed empty assertEq(pdpVerifier.getDataSetLeafCount(setId), 0); assertEq(pdpVerifier.getNextChallengeEpoch(setId), 0); - assertEq(pdpVerifier.getDataSetLastProvenEpoch(setId), 0); + assertEq(pdpVerifier.getDataSetLastProvenEpoch(setId), block.number); } } diff --git a/test/PDPVerifierProofTest.t.sol b/test/PDPVerifierProofTest.t.sol index 8bfabd0..fb53977 100644 --- a/test/PDPVerifierProofTest.t.sol +++ b/test/PDPVerifierProofTest.t.sol @@ -118,16 +118,21 @@ contract PDPVerifierProofTest is MockFVMTest, ProofBuilderHelper, PieceHelper { } function testDataSetLastProvenEpochOnPieceRemoval() public { - // Create a data set and verify initial lastProvenEpoch is 0 + // Create a data set and verify initial lastProvenEpoch is set to the creation block + uint256 creationBlock = vm.getBlockNumber(); uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) ); - assertEq(pdpVerifier.getDataSetLastProvenEpoch(setId), 0, "Initial lastProvenEpoch should be 0"); + assertEq( + pdpVerifier.getDataSetLastProvenEpoch(setId), + creationBlock, + "Initial lastProvenEpoch should be block.number at creation" + ); // Mock block.number to 2881 uint256 blockNumber = 2881; vm.roll(blockNumber); - // Add a piece and verify lastProvenEpoch is set to current block number + // Add a piece and call nextProvingPeriod; lastProvenEpoch is unchanged until provePossession Cids.Cid[] memory pieces = new Cids.Cid[](1); pieces[0] = makeSamplePiece(2); @@ -135,8 +140,8 @@ contract PDPVerifierProofTest is MockFVMTest, ProofBuilderHelper, PieceHelper { pdpVerifier.nextProvingPeriod(setId, blockNumber + CHALLENGE_FINALITY_DELAY, empty); assertEq( pdpVerifier.getDataSetLastProvenEpoch(setId), - blockNumber, - "lastProvenEpoch should be set to block.number after first proving period piece" + creationBlock, + "lastProvenEpoch should remain at creation block until provePossession is called" ); // Schedule piece removal @@ -144,12 +149,12 @@ contract PDPVerifierProofTest is MockFVMTest, ProofBuilderHelper, PieceHelper { piecesToRemove[0] = 0; pdpVerifier.schedulePieceDeletions(setId, piecesToRemove, empty); - // Call nextProvingPeriod and verify lastProvenEpoch is reset to 0 + // Call nextProvingPeriod and verify lastProvenEpoch is set to current block after removing last piece pdpVerifier.nextProvingPeriod(setId, blockNumber + CHALLENGE_FINALITY_DELAY, empty); assertEq( pdpVerifier.getDataSetLastProvenEpoch(setId), - 0, - "lastProvenEpoch should be reset to 0 after removing last piece" + blockNumber, + "lastProvenEpoch should be set to block.number after removing last piece" ); } From c40b8e039d7469c045f9dd125cd6296109746840 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Wed, 20 May 2026 18:44:44 -0500 Subject: [PATCH 17/22] feat(tools): allow deprecated-prefix renames in storage layout check Assisted-by: Claude:claude-sonnet-4-6 --- tools/check_storage_layout.sh | 38 ++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/tools/check_storage_layout.sh b/tools/check_storage_layout.sh index 8def622..93163a1 100755 --- a/tools/check_storage_layout.sh +++ b/tools/check_storage_layout.sh @@ -85,6 +85,20 @@ compare_layouts() { local new_entry=$(jq -c --arg l "$label" '.[] | select(.label == $l)' "$new_file") if [ -z "$new_entry" ]; then + # Allow rename where only change is adding the "deprecated" prefix + local deprecated_label="deprecated${label^}" + local deprecated_entry + deprecated_entry=$(jq -c --arg l "$deprecated_label" '.[] | select(.label == $l)' "$new_file") + if [ -n "$deprecated_entry" ]; then + local dep_slot dep_offset dep_type + dep_slot=$(echo "$deprecated_entry" | jq -r '.slot') + dep_offset=$(echo "$deprecated_entry" | jq -r '.offset') + dep_type=$(echo "$deprecated_entry" | jq -cS '.type') + if [ "$dep_slot" = "$slot" ] && [ "$dep_offset" = "$offset" ] && [ "$dep_type" = "$type" ]; then + echo " Renamed: '$label' → '$deprecated_label' (slot $slot, deprecated)" + continue + fi + fi echo " DESTRUCTIVE: Variable '$label' (slot $slot, offset $offset) was removed" >&2 errors=$((errors + 1)) continue @@ -123,7 +137,29 @@ compare_layouts() { local base_match=$(jq -c --arg l "$label" '.[] | select(.label == $l)' "$base_file") if [ -z "$base_match" ]; then - if [ "$slot" -le "$max_base_slot" ]; then + # Check if this is a permitted deprecated rename (already reported in Check 1) + local is_deprecated_rename=false + if [[ "$label" == deprecated* ]]; then + local original_label="${label#deprecated}" + original_label="${original_label,}" + local base_original + base_original=$(jq -c --arg l "$original_label" '.[] | select(.label == $l)' "$base_file") + if [ -n "$base_original" ]; then + local orig_slot orig_offset orig_type entry_offset entry_type + orig_slot=$(echo "$base_original" | jq -r '.slot') + orig_offset=$(echo "$base_original" | jq -r '.offset') + orig_type=$(echo "$base_original" | jq -cS '.type') + entry_offset=$(echo "$entry" | jq -r '.offset') + entry_type=$(echo "$entry" | jq -cS '.type') + if [ "$orig_slot" = "$slot" ] && [ "$orig_offset" = "$entry_offset" ] && [ "$orig_type" = "$entry_type" ]; then + is_deprecated_rename=true + fi + fi + fi + + if $is_deprecated_rename; then + : # already reported in Check 1 + elif [ "$slot" -le "$max_base_slot" ]; then echo " DESTRUCTIVE: New variable '$label' inserted at slot $slot (must be > $max_base_slot)" >&2 errors=$((errors + 1)) else From 6ce12f482d7209030c79deeb8efe0b784508fb43 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Wed, 20 May 2026 19:33:23 -0500 Subject: [PATCH 18/22] chore(layout): regenerate layout files after challengeFinality rename Assisted-by: Claude:claude-sonnet-4-6 --- src/PDPVerifierLayout.json | 2 +- src/PDPVerifierLayout.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PDPVerifierLayout.json b/src/PDPVerifierLayout.json index 09c3aa6..2440ab1 100644 --- a/src/PDPVerifierLayout.json +++ b/src/PDPVerifierLayout.json @@ -1,6 +1,6 @@ [ { - "label": "challengeFinality", + "label": "deprecatedChallengeFinality", "offset": 0, "slot": "0", "type": { diff --git a/src/PDPVerifierLayout.sol b/src/PDPVerifierLayout.sol index 986490f..81a1739 100644 --- a/src/PDPVerifierLayout.sol +++ b/src/PDPVerifierLayout.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.20; // This file is a generated binding and any changes will be lost. // Generated with tools/generate_storage_layout.sh -bytes32 constant CHALLENGE_FINALITY_SLOT = bytes32(uint256(0)); +bytes32 constant DEPRECATED_CHALLENGE_FINALITY_SLOT = bytes32(uint256(0)); bytes32 constant NEXT_DATA_SET_ID_SLOT = bytes32(uint256(1)); bytes32 constant PIECE_CIDS_SLOT = bytes32(uint256(2)); bytes32 constant PIECE_LEAF_COUNTS_SLOT = bytes32(uint256(3)); From ec6f8aaeaa5640aa37a42e56e0544373154efa4e Mon Sep 17 00:00:00 2001 From: William Morriss Date: Thu, 21 May 2026 13:51:04 -0500 Subject: [PATCH 19/22] chore(tools): remove stale USDFC/payments constructor args from deploy scripts Assisted-by: Claude:claude-sonnet-4-6 --- tools/README.md | 6 +++--- tools/deploy-calibnet.sh | 13 ++----------- tools/deploy-mainnet.sh | 13 ++----------- tools/upgrade-contract.sh | 30 +----------------------------- 4 files changed, 8 insertions(+), 54 deletions(-) diff --git a/tools/README.md b/tools/README.md index 68bf7f3..f4440ce 100644 --- a/tools/README.md +++ b/tools/README.md @@ -11,13 +11,13 @@ A place for all tools related to running and developing the PDP contracts. When | Devnet | 10 epochs | ### deploy-devnet.sh -Deploys PDPVerifier to a local filecoin devnet. Assumes lotus binary is in path and local devnet is running with eth API enabled. The keystore will be funded automatically from lotus default address. Accepts optional constructor env overrides: `USDFC_TOKEN_ADDRESS`, `USDFC_SYBIL_FEE`, and `PAYMENTS_CONTRACT_ADDRESS`. By default, devnet uses zero addresses for the USDFC/payment dependencies and keeps FIL fallback enabled. +Deploys PDPVerifier to a local filecoin devnet. Assumes lotus binary is in path and local devnet is running with eth API enabled. The keystore will be funded automatically from lotus default address. ### deploy-calibnet.sh -Deploys PDPVerifier to Filecoin Calibration testnet. Accepts optional constructor env overrides: `USDFC_TOKEN_ADDRESS`, `USDFC_SYBIL_FEE`, and `PAYMENTS_CONTRACT_ADDRESS`. Defaults match the current Calibration warm-storage deployment. +Deploys PDPVerifier to Filecoin Calibration testnet. ### deploy-mainnet.sh -Deploys PDPVerifier to Filecoin mainnet. Accepts optional constructor env overrides: `USDFC_TOKEN_ADDRESS`, `USDFC_SYBIL_FEE`, and `PAYMENTS_CONTRACT_ADDRESS`. Defaults match the current Mainnet warm-storage deployment. +Deploys PDPVerifier to Filecoin mainnet. ### deploy-simple-pdp-service.sh ⚠️ DEPRECATED **As of v2.0.0, SimplePDPService is deprecated.** This optional script allows deployment of SimplePDPService for reference/community use only. Requires an existing PDPVerifier deployment. See `DEPRECATION.md` for details. diff --git a/tools/deploy-calibnet.sh b/tools/deploy-calibnet.sh index 3b206ee..297ccb9 100755 --- a/tools/deploy-calibnet.sh +++ b/tools/deploy-calibnet.sh @@ -20,10 +20,6 @@ fi # Calibration testnet uses 10 epochs (vs 150 on mainnet) CHALLENGE_FINALITY=10 VERIFIER_INIT_COUNTER=1 -ZERO_ADDRESS="0x0000000000000000000000000000000000000000" -USDFC_TOKEN_ADDRESS="${USDFC_TOKEN_ADDRESS:-0xb3042734b608a1B16e9e86B374A3f3e389B4cDf0}" -USDFC_SYBIL_FEE="${USDFC_SYBIL_FEE:-100000000000000000}" -PAYMENTS_CONTRACT_ADDRESS="${PAYMENTS_CONTRACT_ADDRESS:-0x09a0fDc2723fAd1A7b8e3e00eE5DF73841df55a0}" ADDR=$(cast wallet address --keystore "$KEYSTORE" --password "$PASSWORD") echo "Deploying PDP verifier from address $ADDR" @@ -31,15 +27,10 @@ echo "Deploying PDP verifier from address $ADDR" echo "PDPVerifier constructor args:" echo " initializerVersion: $VERIFIER_INIT_COUNTER" -echo " USDFC_TOKEN_ADDRESS: $USDFC_TOKEN_ADDRESS" -echo " USDFC_SYBIL_FEE: $USDFC_SYBIL_FEE" -echo " PAYMENTS_CONTRACT_ADDRESS: $PAYMENTS_CONTRACT_ADDRESS" -if [ "$USDFC_TOKEN_ADDRESS" = "$ZERO_ADDRESS" ] || [ "$PAYMENTS_CONTRACT_ADDRESS" = "$ZERO_ADDRESS" ]; then - echo " note: USDFC-backed fee path disabled; deployment will use FIL fallback only" -fi +echo " challengeFinality: $CHALLENGE_FINALITY" NONCE="$(cast nonce --rpc-url "$RPC_URL" "$ADDR")" -VERIFIER_IMPLEMENTATION_ADDRESS=$(forge create --rpc-url "$RPC_URL" --keystore "$KEYSTORE" --password "$PASSWORD" --broadcast --nonce $NONCE --chain-id 314159 src/PDPVerifier.sol:PDPVerifier --constructor-args "$VERIFIER_INIT_COUNTER" "$CHALLENGE_FINALITY" "$USDFC_TOKEN_ADDRESS" "$USDFC_SYBIL_FEE" "$PAYMENTS_CONTRACT_ADDRESS" | grep "Deployed to" | awk '{print $3}') +VERIFIER_IMPLEMENTATION_ADDRESS=$(forge create --rpc-url "$RPC_URL" --keystore "$KEYSTORE" --password "$PASSWORD" --broadcast --nonce $NONCE --chain-id 314159 src/PDPVerifier.sol:PDPVerifier --constructor-args "$VERIFIER_INIT_COUNTER" "$CHALLENGE_FINALITY" | grep "Deployed to" | awk '{print $3}') if [ -z "$VERIFIER_IMPLEMENTATION_ADDRESS" ]; then echo "Error: Failed to extract PDP verifier contract address" exit 1 diff --git a/tools/deploy-mainnet.sh b/tools/deploy-mainnet.sh index 02af985..3b89d9e 100755 --- a/tools/deploy-mainnet.sh +++ b/tools/deploy-mainnet.sh @@ -20,10 +20,6 @@ fi # Mainnet uses 150 epochs (vs 10 on Calibration testnet) CHALLENGE_FINALITY=150 VERIFIER_INIT_COUNTER=1 -ZERO_ADDRESS="0x0000000000000000000000000000000000000000" -USDFC_TOKEN_ADDRESS="${USDFC_TOKEN_ADDRESS:-0x80B98d3aa09ffff255c3ba4A241111Ff1262F045}" -USDFC_SYBIL_FEE="${USDFC_SYBIL_FEE:-100000000000000000}" -PAYMENTS_CONTRACT_ADDRESS="${PAYMENTS_CONTRACT_ADDRESS:-0x23b1e018F08BB982348b15a86ee926eEBf7F4DAa}" ADDR=$(cast wallet address --keystore "$KEYSTORE" --password "$PASSWORD") echo "Deploying PDP verifier from address $ADDR" @@ -31,15 +27,10 @@ echo "Deploying PDP verifier from address $ADDR" echo "PDPVerifier constructor args:" echo " initializerVersion: $VERIFIER_INIT_COUNTER" -echo " USDFC_TOKEN_ADDRESS: $USDFC_TOKEN_ADDRESS" -echo " USDFC_SYBIL_FEE: $USDFC_SYBIL_FEE" -echo " PAYMENTS_CONTRACT_ADDRESS: $PAYMENTS_CONTRACT_ADDRESS" -if [ "$USDFC_TOKEN_ADDRESS" = "$ZERO_ADDRESS" ] || [ "$PAYMENTS_CONTRACT_ADDRESS" = "$ZERO_ADDRESS" ]; then - echo " note: USDFC-backed fee path disabled; deployment will use FIL fallback only" -fi +echo " challengeFinality: $CHALLENGE_FINALITY" NONCE="$(cast nonce --rpc-url "$RPC_URL" "$ADDR")" -VERIFIER_IMPLEMENTATION_ADDRESS=$(forge create --rpc-url "$RPC_URL" --keystore "$KEYSTORE" --password "$PASSWORD" --broadcast --nonce $NONCE --chain-id 314 src/PDPVerifier.sol:PDPVerifier --constructor-args "$VERIFIER_INIT_COUNTER" "$CHALLENGE_FINALITY" "$USDFC_TOKEN_ADDRESS" "$USDFC_SYBIL_FEE" "$PAYMENTS_CONTRACT_ADDRESS" | grep "Deployed to" | awk '{print $3}') +VERIFIER_IMPLEMENTATION_ADDRESS=$(forge create --rpc-url "$RPC_URL" --keystore "$KEYSTORE" --password "$PASSWORD" --broadcast --nonce $NONCE --chain-id 314 src/PDPVerifier.sol:PDPVerifier --constructor-args "$VERIFIER_INIT_COUNTER" "$CHALLENGE_FINALITY" | grep "Deployed to" | awk '{print $3}') if [ -z "$VERIFIER_IMPLEMENTATION_ADDRESS" ]; then echo "Error: Failed to extract PDP verifier contract address" exit 1 diff --git a/tools/upgrade-contract.sh b/tools/upgrade-contract.sh index 4f06c39..7c73ef9 100755 --- a/tools/upgrade-contract.sh +++ b/tools/upgrade-contract.sh @@ -9,7 +9,6 @@ # Set DRY_RUN=false to actually deploy and broadcast transactions (default is dry-run for safety) DRY_RUN=${DRY_RUN:-true} SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")" -ZERO_ADDRESS="0x0000000000000000000000000000000000000000" PDP_VERIFIER_IMPLEMENTATION_PATH="src/PDPVerifier.sol:PDPVerifier" if [ "$DRY_RUN" = "true" ]; then @@ -55,44 +54,17 @@ fi UPGRADE_INIT_COUNTER=$(expr "$("$SCRIPT_DIR/get-initialized-counter.sh" "$PROXY_ADDRESS")" + 1) CHALLENGE_FINALITY=$(cast call --rpc-url "$RPC_URL" "$PROXY_ADDRESS" "getChallengeFinality()(uint256)") -CONSTRUCTOR_ARGS=("$UPGRADE_INIT_COUNTER") if [ "$IMPLEMENTATION_PATH" != "$PDP_VERIFIER_IMPLEMENTATION_PATH" ]; then echo "Error: Only PDPVerifier upgrades are supported. Got: $IMPLEMENTATION_PATH" exit 1 fi -case "$CHAIN_ID" in - "314") - USDFC_TOKEN_ADDRESS="${USDFC_TOKEN_ADDRESS:-0x80B98d3aa09ffff255c3ba4A241111Ff1262F045}" - PAYMENTS_CONTRACT_ADDRESS="${PAYMENTS_CONTRACT_ADDRESS:-0x23b1e018F08BB982348b15a86ee926eEBf7F4DAa}" - ;; - "314159") - USDFC_TOKEN_ADDRESS="${USDFC_TOKEN_ADDRESS:-0xb3042734b608a1B16e9e86B374A3f3e389B4cDf0}" - PAYMENTS_CONTRACT_ADDRESS="${PAYMENTS_CONTRACT_ADDRESS:-0x09a0fDc2723fAd1A7b8e3e00eE5DF73841df55a0}" - ;; - "31415926") - USDFC_TOKEN_ADDRESS="${USDFC_TOKEN_ADDRESS:-$ZERO_ADDRESS}" - PAYMENTS_CONTRACT_ADDRESS="${PAYMENTS_CONTRACT_ADDRESS:-$ZERO_ADDRESS}" - ;; - *) - echo "Error: Unsupported chain ID for default PDPVerifier constructor args" - echo "Please set USDFC_TOKEN_ADDRESS and PAYMENTS_CONTRACT_ADDRESS explicitly." - exit 1 - ;; -esac -USDFC_SYBIL_FEE="${USDFC_SYBIL_FEE:-100000000000000000}" -CONSTRUCTOR_ARGS=("$UPGRADE_INIT_COUNTER" "$CHALLENGE_FINALITY" "$USDFC_TOKEN_ADDRESS" "$USDFC_SYBIL_FEE" "$PAYMENTS_CONTRACT_ADDRESS") +CONSTRUCTOR_ARGS=("$UPGRADE_INIT_COUNTER" "$CHALLENGE_FINALITY") echo "Using PDPVerifier constructor args:" echo " initializerVersion: $UPGRADE_INIT_COUNTER" echo " challengeFinality: $CHALLENGE_FINALITY" -echo " USDFC_TOKEN_ADDRESS: $USDFC_TOKEN_ADDRESS" -echo " USDFC_SYBIL_FEE: $USDFC_SYBIL_FEE" -echo " PAYMENTS_CONTRACT_ADDRESS: $PAYMENTS_CONTRACT_ADDRESS" -if [ "$USDFC_TOKEN_ADDRESS" = "$ZERO_ADDRESS" ] || [ "$PAYMENTS_CONTRACT_ADDRESS" = "$ZERO_ADDRESS" ]; then - echo " note: USDFC-backed fee path disabled; deployment will use FIL fallback only" -fi if [ "$DRY_RUN" = "true" ]; then echo "🔍 Simulating deployment of new $IMPLEMENTATION_PATH implementation contract" From 9d430137f9215da2cd0f2059e9a6236238a51bdb Mon Sep 17 00:00:00 2001 From: William Morriss Date: Thu, 21 May 2026 14:19:47 -0500 Subject: [PATCH 20/22] test(verifier): add test for challenge epoch before current block Assisted-by: Claude:claude-sonnet-4-6 --- src/PDPVerifier.sol | 2 +- test/PDPVerifier.t.sol | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/PDPVerifier.sol b/src/PDPVerifier.sol index aa65c10..be27269 100644 --- a/src/PDPVerifier.sol +++ b/src/PDPVerifier.sol @@ -1011,7 +1011,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { // Bring added pieces into proving set challengeRange[setId] = dataSetLeafCount[setId]; - { + unchecked { uint256 delayEpochs = challengeEpoch - block.number; require(delayEpochs >= CHALLENGE_FINALITY, InsufficientChallengeDelay(delayEpochs, CHALLENGE_FINALITY)); require(delayEpochs <= MAX_FINALITY, ExcessiveChallengeDelay(delayEpochs, MAX_FINALITY)); diff --git a/test/PDPVerifier.t.sol b/test/PDPVerifier.t.sol index 46e15ba..6b82a8c 100644 --- a/test/PDPVerifier.t.sol +++ b/test/PDPVerifier.t.sol @@ -832,6 +832,26 @@ contract PDPVerifierDataSetMutateTest is MockFVMTest, PieceHelper { assertEq(pdpVerifier.getNextChallengeEpoch(setId), validEpoch); } + function testNextProvingPeriodChallengeEpochBeforeCurrentBlock() public { + uint256 setId = pdpVerifier.addPieces{value: PDPFees.cleanupDeposit()}( + NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty) + ); + Cids.Cid[] memory pieces = new Cids.Cid[](1); + pieces[0] = makeSamplePiece(2); + pdpVerifier.addPieces(setId, address(0), pieces, empty); + + vm.roll(10); + uint256 currentBlock = vm.getBlockNumber(); + uint256 pastEpoch = currentBlock - 1; + + uint256 maxFinality = pdpVerifier.INACTIVITY_WINDOW() / 2; + + vm.expectRevert( + abi.encodeWithSelector(PDPVerifier.ExcessiveChallengeDelay.selector, type(uint256).max, maxFinality) + ); + pdpVerifier.nextProvingPeriod(setId, pastEpoch, ""); + } + function testNextProvingPeriodWithNoData() public { // Get the NO_CHALLENGE_SCHEDULED constant value for clarity uint256 noChallenge = NO_CHALLENGE_SCHEDULED; From ee639ab936562245e203706b54b14cc9198527bf Mon Sep 17 00:00:00 2001 From: William Morriss Date: Thu, 21 May 2026 14:24:08 -0500 Subject: [PATCH 21/22] chore: typed error msg for misconfigured finality --- src/PDPVerifier.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PDPVerifier.sol b/src/PDPVerifier.sol index be27269..f08fc26 100644 --- a/src/PDPVerifier.sol +++ b/src/PDPVerifier.sol @@ -183,7 +183,9 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { /// @custom:oz-upgrades-unsafe-allow constructor constructor(uint64 _initializerVersion, uint256 _challengeFinality) { - require(_challengeFinality < MAX_FINALITY / 10); + require( + _challengeFinality < MAX_FINALITY / 10, InsufficientChallengeDelay(_challengeFinality, MAX_FINALITY / 10) + ); _disableInitializers(); REINITIALIZER_VERSION = _initializerVersion; CHALLENGE_FINALITY = _challengeFinality; From 4b031fb61a5d4040aa22091e8ac92c65e533688e Mon Sep 17 00:00:00 2001 From: William Morriss Date: Thu, 21 May 2026 14:25:15 -0500 Subject: [PATCH 22/22] chore: swap error --- src/PDPVerifier.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/PDPVerifier.sol b/src/PDPVerifier.sol index f08fc26..b73b746 100644 --- a/src/PDPVerifier.sol +++ b/src/PDPVerifier.sol @@ -183,9 +183,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { /// @custom:oz-upgrades-unsafe-allow constructor constructor(uint64 _initializerVersion, uint256 _challengeFinality) { - require( - _challengeFinality < MAX_FINALITY / 10, InsufficientChallengeDelay(_challengeFinality, MAX_FINALITY / 10) - ); + require(_challengeFinality < MAX_FINALITY / 10, ExcessiveChallengeDelay(_challengeFinality, MAX_FINALITY / 10)); _disableInitializers(); REINITIALIZER_VERSION = _initializerVersion; CHALLENGE_FINALITY = _challengeFinality;