From 4f2f2b36cc971754e3cc79b4e398f6743e7aaf39 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 07:59:52 +0000 Subject: [PATCH 1/4] Initial plan From be69a885789422868c696529face6da68f1cf285 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 08:09:01 +0000 Subject: [PATCH 2/4] Implement flat fee mechanism with updatable fee Co-authored-by: jennijuju <42981373+jennijuju@users.noreply.github.com> --- src/Fees.sol | 25 +++++++++++++++--- src/PDPVerifier.sol | 64 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 72 insertions(+), 17 deletions(-) diff --git a/src/Fees.sol b/src/Fees.sol index c9dcf24..8d1c92e 100644 --- a/src/Fees.sol +++ b/src/Fees.sol @@ -10,6 +10,13 @@ library PDPFees { // 0.1 FIL uint256 constant SYBIL_FEE = FIL_TO_ATTO_FIL / 10; + // Fixed PDP proof fee per TiB (0.00023 FIL = 0.00023 * 1e18 AttoFIL = 230000000000000000 AttoFIL) + // Based on fixed conversion rate: 1 FIL = 2.88 USD, with fee = 0.00067 USD / 2.88 USD = 0.00023 FIL per TiB + uint256 public constant FEE_PER_TIB = 230000000000000000; + + // 1 TiB in bytes (2^40) + uint256 constant TIB_IN_BYTES = 2 ** 40; + // 2 USD/Tib/month is the current reward earned by Storage Providers uint256 constant ESTIMATED_MONTHLY_TIB_STORAGE_REWARD_USD = 2; // 1% of reward per period @@ -19,9 +26,6 @@ library PDPFees { // 5% of reward per period for gas limit right bound uint256 constant GAS_LIMIT_RIGHT_PERCENTAGE = 5; uint256 constant USD_DECIMALS = 1e18; - - // 1 TiB in bytes (2^40) - uint256 constant TIB_IN_BYTES = 2 ** 40; // Number of epochs per month (30 days * 2880 epochs per day) uint256 constant EPOCHS_PER_MONTH = 86400; @@ -74,6 +78,21 @@ library PDPFees { } } + /// @notice Calculates the proof fee based on a fixed fee per TiB. + /// @param rawSize The raw size of the proof in bytes. + /// @param feePerTiB The fee per TiB in AttoFIL (can be overridden or use default). + /// @return proof fee in AttoFIL + /// @dev The proof fee is calculated as: (rawSize / TIB_IN_BYTES) * feePerTiB + function flatProofFee(uint256 rawSize, uint256 feePerTiB) internal pure returns (uint256) { + require(rawSize > 0, "failed to validate: raw size must be greater than 0"); + require(feePerTiB > 0, "failed to validate: fee per TiB must be greater than 0"); + + // Calculate fee based on dataset size in TiB + // fee = (rawSize / TIB_IN_BYTES) * feePerTiB + // To avoid precision loss, we compute: (rawSize * feePerTiB) / TIB_IN_BYTES + return (rawSize * feePerTiB) / TIB_IN_BYTES; + } + // 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) { diff --git a/src/PDPVerifier.sol b/src/PDPVerifier.sol index 3e4850d..00701e1 100644 --- a/src/PDPVerifier.sol +++ b/src/PDPVerifier.sol @@ -66,7 +66,8 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { event PiecesAdded(uint256 indexed setId, uint256[] pieceIds, Cids.Cid[] pieceCids); event PiecesRemoved(uint256 indexed setId, uint256[] pieceIds); - event ProofFeePaid(uint256 indexed setId, uint256 fee, uint64 price, int32 expo); + event ProofFeePaid(uint256 indexed setId, uint256 fee); + event FeeUpdateProposed(uint256 currentFee, uint256 newFee, uint256 effectiveTime); event PossessionProven(uint256 indexed setId, IPDPTypes.PieceIdAndOffset[] challenges); event NextProvingPeriod(uint256 indexed setId, uint256 challengeEpoch, uint256 leafCount); @@ -143,6 +144,11 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { mapping(uint256 => address) storageProvider; mapping(uint256 => address) dataSetProposedStorageProvider; mapping(uint256 => uint256) dataSetLastProvenEpoch; + + // Fee update mechanism + uint256 public proofFeePerTiB; + uint256 public proposedProofFeePerTiB; + uint256 public feeUpdateEffectiveTime; // Methods @@ -158,11 +164,15 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { nextDataSetId = 1; // Data sets start at 1 } - string public constant VERSION = "2.1.0"; + string public constant VERSION = "2.2.0"; event ContractUpgraded(string version, address implementation); - function migrate() external onlyOwner reinitializer(2) { + function migrate() external onlyOwner reinitializer(3) { + // Initialize the proof fee per TiB with the default value + proofFeePerTiB = PDPFees.FEE_PER_TIB; + proposedProofFeePerTiB = 0; + feeUpdateEffectiveTime = 0; emit ContractUpgraded(VERSION, ERC1967Utils.getImplementation()); } @@ -174,6 +184,37 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { require(success, "Burn failed"); } + /// @notice Get the currently active proof fee per TiB + /// @return The active proof fee per TiB in AttoFIL + function getActiveProofFeePerTiB() public view returns (uint256) { + // If there's a proposed fee and it's now effective, return it + if (proposedProofFeePerTiB > 0 && block.timestamp >= feeUpdateEffectiveTime) { + return proposedProofFeePerTiB; + } + // Otherwise return the current fee (or default if not set) + return proofFeePerTiB > 0 ? proofFeePerTiB : PDPFees.FEE_PER_TIB; + } + + /// @notice Propose a new proof fee per TiB (owner only) + /// @param newFeePerTiB The new fee per TiB in AttoFIL + /// @dev The new fee becomes effective 7 days after proposal + function updateProofFee(uint256 newFeePerTiB) external onlyOwner { + require(newFeePerTiB > 0, "Fee must be greater than 0"); + + uint256 currentFee = getActiveProofFeePerTiB(); + + // Apply any pending fee update before proposing a new one + if (proposedProofFeePerTiB > 0 && block.timestamp >= feeUpdateEffectiveTime) { + proofFeePerTiB = proposedProofFeePerTiB; + } + + // Set the new proposed fee with 7 days delay (7 * 24 * 60 * 60 = 604800 seconds) + proposedProofFeePerTiB = newFeePerTiB; + feeUpdateEffectiveTime = block.timestamp + 604800; + + emit FeeUpdateProposed(currentFee, newFeePerTiB, feeUpdateEffectiveTime); + } + // Returns the current challenge finality value function getChallengeFinality() public view returns (uint256) { return challengeFinality; @@ -599,23 +640,18 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { function calculateProofFee(uint256 setId, uint256 estimatedGasFee) public view returns (uint256) { uint256 rawSize = 32 * challengeRange[setId]; - (uint64 filUsdPrice, int32 filUsdPriceExpo) = getFILUSDPrice(); - - return PDPFees.proofFeeWithGasFeeBound( - estimatedGasFee, filUsdPrice, filUsdPriceExpo, rawSize, block.number - dataSetLastProvenEpoch[setId] - ); + uint256 feePerTiB = getActiveProofFeePerTiB(); + + return PDPFees.flatProofFee(rawSize, feePerTiB); } function calculateAndBurnProofFee(uint256 setId, uint256 gasUsed) internal returns (uint256 refund) { - uint256 estimatedGasFee = gasUsed * block.basefee; uint256 rawSize = 32 * challengeRange[setId]; - (uint64 filUsdPrice, int32 filUsdPriceExpo) = getFILUSDPrice(); + uint256 feePerTiB = getActiveProofFeePerTiB(); - uint256 proofFee = PDPFees.proofFeeWithGasFeeBound( - estimatedGasFee, filUsdPrice, filUsdPriceExpo, rawSize, block.number - dataSetLastProvenEpoch[setId] - ); + uint256 proofFee = PDPFees.flatProofFee(rawSize, feePerTiB); burnFee(proofFee); - emit ProofFeePaid(setId, proofFee, filUsdPrice, filUsdPriceExpo); + emit ProofFeePaid(setId, proofFee); return msg.value - proofFee; // burnFee asserts that proofFee <= msg.value; } From 760e72c86c97f36a32c6668b5e562266c76a8d38 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 08:11:53 +0000 Subject: [PATCH 3/4] Add comprehensive tests for flat fee calculation and fee update mechanism Co-authored-by: jennijuju <42981373+jennijuju@users.noreply.github.com> --- src/interfaces/IPDPEvents.sol | 3 +- test/Fees.t.sol | 69 ++++++++++++++++++ test/PDPVerifier.t.sol | 130 ++++++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 1 deletion(-) diff --git a/src/interfaces/IPDPEvents.sol b/src/interfaces/IPDPEvents.sol index 9d30af3..0e0cb92 100644 --- a/src/interfaces/IPDPEvents.sol +++ b/src/interfaces/IPDPEvents.sol @@ -15,7 +15,8 @@ interface IPDPEvents { event DataSetEmpty(uint256 indexed setId); event PiecesAdded(uint256 indexed setId, uint256[] pieceIds, Cids.Cid[] pieceCids); event PiecesRemoved(uint256 indexed setId, uint256[] pieceIds); - event ProofFeePaid(uint256 indexed setId, uint256 fee, uint64 price, int32 expo); + event ProofFeePaid(uint256 indexed setId, uint256 fee); + event FeeUpdateProposed(uint256 currentFee, uint256 newFee, uint256 effectiveTime); event PossessionProven(uint256 indexed setId, IPDPTypes.PieceIdAndOffset[] challenges); event NextProvingPeriod(uint256 indexed setId, uint256 challengeEpoch, uint256 leafCount); event ContractUpgraded(string version, address newImplementation); diff --git a/test/Fees.t.sol b/test/Fees.t.sol index 93f6044..683176f 100644 --- a/test/Fees.t.sol +++ b/test/Fees.t.sol @@ -197,4 +197,73 @@ contract PDPFeesTest is Test { uint256 expectedFee = 1; // Should be gasLimitRight - estimatedGasFee = 1 assertEq(fee, expectedFee, "Fee should be 1 when estimatedGasFee is just below right boundary"); } + + // Tests for new flat fee calculation + function testFlatProofFeeOneTiB() public pure { + uint256 rawSize = PDPFees.TIB_IN_BYTES; // 1 TiB + uint256 feePerTiB = PDPFees.FEE_PER_TIB; + + uint256 fee = PDPFees.flatProofFee(rawSize, feePerTiB); + + assertEq(fee, PDPFees.FEE_PER_TIB, "Fee for 1 TiB should equal FEE_PER_TIB"); + } + + function testFlatProofFeeTwoTiB() public pure { + uint256 rawSize = 2 * PDPFees.TIB_IN_BYTES; // 2 TiB + uint256 feePerTiB = PDPFees.FEE_PER_TIB; + + uint256 fee = PDPFees.flatProofFee(rawSize, feePerTiB); + + assertEq(fee, 2 * PDPFees.FEE_PER_TIB, "Fee for 2 TiB should be twice FEE_PER_TIB"); + } + + function testFlatProofFeeHalfTiB() public pure { + uint256 rawSize = PDPFees.TIB_IN_BYTES / 2; // 0.5 TiB + uint256 feePerTiB = PDPFees.FEE_PER_TIB; + + uint256 fee = PDPFees.flatProofFee(rawSize, feePerTiB); + + assertEq(fee, PDPFees.FEE_PER_TIB / 2, "Fee for 0.5 TiB should be half of FEE_PER_TIB"); + } + + function testFlatProofFeeSmallSize() public pure { + uint256 rawSize = 32 * 1000; // 1000 leaves (32 bytes each) + uint256 feePerTiB = PDPFees.FEE_PER_TIB; + + uint256 fee = PDPFees.flatProofFee(rawSize, feePerTiB); + uint256 expectedFee = (rawSize * feePerTiB) / PDPFees.TIB_IN_BYTES; + + assertEq(fee, expectedFee, "Fee should be proportional to size"); + assertTrue(fee > 0, "Fee should be positive even for small datasets"); + } + + function testFlatProofFeeLargeSize() public pure { + uint256 rawSize = 100 * PDPFees.TIB_IN_BYTES; // 100 TiB + uint256 feePerTiB = PDPFees.FEE_PER_TIB; + + uint256 fee = PDPFees.flatProofFee(rawSize, feePerTiB); + + assertEq(fee, 100 * PDPFees.FEE_PER_TIB, "Fee for 100 TiB should be 100x FEE_PER_TIB"); + } + + function testFlatProofFeeCustomRate() public pure { + uint256 rawSize = PDPFees.TIB_IN_BYTES; // 1 TiB + uint256 customFee = 500000000000000000; // 0.0005 FIL per TiB + + uint256 fee = PDPFees.flatProofFee(rawSize, customFee); + + assertEq(fee, customFee, "Fee should use custom rate"); + } + + /// forge-config: default.allow_internal_expect_revert = true + function testFlatProofFeeZeroSize() public { + vm.expectRevert("failed to validate: raw size must be greater than 0"); + PDPFees.flatProofFee(0, PDPFees.FEE_PER_TIB); + } + + /// forge-config: default.allow_internal_expect_revert = true + function testFlatProofFeeZeroRate() public { + vm.expectRevert("failed to validate: fee per TiB must be greater than 0"); + PDPFees.flatProofFee(PDPFees.TIB_IN_BYTES, 0); + } } diff --git a/test/PDPVerifier.t.sol b/test/PDPVerifier.t.sol index 1e929e6..45f6303 100644 --- a/test/PDPVerifier.t.sol +++ b/test/PDPVerifier.t.sol @@ -1969,3 +1969,133 @@ contract PDPVerifierStorageProviderListenerTest is Test { pdpVerifier.claimDataSetStorageProvider(setId, empty); } } + +contract PDPVerifierFeeUpdateTest is Test { + PDPVerifier pdpVerifier; + address owner; + address notOwner; + + function setUp() public { + PDPVerifier pdpVerifierImpl = new PDPVerifier(); + uint256 challengeFinality = 2; + bytes memory initializeData = abi.encodeWithSelector(PDPVerifier.initialize.selector, challengeFinality); + MyERC1967Proxy proxy = new MyERC1967Proxy(address(pdpVerifierImpl), initializeData); + pdpVerifier = PDPVerifier(address(proxy)); + + // Call migrate to initialize fee variables + pdpVerifier.migrate(); + + owner = address(this); + notOwner = address(0x1234); + } + + function testInitialFeeIsDefault() public view { + uint256 activeFee = pdpVerifier.getActiveProofFeePerTiB(); + assertEq(activeFee, PDPFees.FEE_PER_TIB, "Initial fee should equal default FEE_PER_TIB"); + } + + function testUpdateProofFeeAsOwner() public { + uint256 newFee = 500000000000000000; // 0.0005 FIL per TiB + uint256 currentTime = block.timestamp; + + vm.expectEmit(true, true, true, true); + emit IPDPEvents.FeeUpdateProposed(PDPFees.FEE_PER_TIB, newFee, currentTime + 604800); + + pdpVerifier.updateProofFee(newFee); + + // Fee should not be effective yet + assertEq(pdpVerifier.getActiveProofFeePerTiB(), PDPFees.FEE_PER_TIB, "Fee should not change immediately"); + + // Proposed fee should be set + assertEq(pdpVerifier.proposedProofFeePerTiB(), newFee, "Proposed fee should be set"); + assertEq(pdpVerifier.feeUpdateEffectiveTime(), currentTime + 604800, "Effective time should be set"); + } + + function testUpdateProofFeeNotOwner() public { + uint256 newFee = 500000000000000000; + + vm.prank(notOwner); + vm.expectRevert(); + pdpVerifier.updateProofFee(newFee); + } + + function testFeeBecomesEffectiveAfter7Days() public { + uint256 newFee = 500000000000000000; + uint256 currentTime = block.timestamp; + + pdpVerifier.updateProofFee(newFee); + + // Still not effective + assertEq(pdpVerifier.getActiveProofFeePerTiB(), PDPFees.FEE_PER_TIB); + + // Advance time by 7 days + vm.warp(currentTime + 604800); + + // Now it should be effective + assertEq(pdpVerifier.getActiveProofFeePerTiB(), newFee, "Fee should be effective after 7 days"); + } + + function testFeeNotEffectiveBefore7Days() public { + uint256 newFee = 500000000000000000; + uint256 currentTime = block.timestamp; + + pdpVerifier.updateProofFee(newFee); + + // Advance time by 6 days + vm.warp(currentTime + 604800 - 1); + + // Should still be old fee + assertEq(pdpVerifier.getActiveProofFeePerTiB(), PDPFees.FEE_PER_TIB, "Fee should not be effective before 7 days"); + } + + function testUpdateProofFeeZeroReverts() public { + vm.expectRevert("Fee must be greater than 0"); + pdpVerifier.updateProofFee(0); + } + + function testMultipleFeeUpdates() public { + uint256 newFee1 = 500000000000000000; + uint256 newFee2 = 700000000000000000; + uint256 currentTime = block.timestamp; + + // First update + pdpVerifier.updateProofFee(newFee1); + + // Advance time by 7 days to make first update effective + vm.warp(currentTime + 604800); + assertEq(pdpVerifier.getActiveProofFeePerTiB(), newFee1); + + // Second update (should apply the pending first update and set new one) + pdpVerifier.updateProofFee(newFee2); + + // Should still see newFee1 as active + assertEq(pdpVerifier.getActiveProofFeePerTiB(), newFee1); + + // Advance another 7 days + vm.warp(currentTime + 604800 + 604800); + + // Now newFee2 should be active + assertEq(pdpVerifier.getActiveProofFeePerTiB(), newFee2); + } + + function testProposeFeeUpdateBeforePreviousBecameEffective() public { + uint256 newFee1 = 500000000000000000; + uint256 newFee2 = 700000000000000000; + uint256 currentTime = block.timestamp; + + // First update + pdpVerifier.updateProofFee(newFee1); + + // Advance time by 3 days (not enough for first to be effective) + vm.warp(currentTime + 3 days); + + // Second update (should keep first as proposed until it's effective) + pdpVerifier.updateProofFee(newFee2); + + // Should still see default fee + assertEq(pdpVerifier.getActiveProofFeePerTiB(), PDPFees.FEE_PER_TIB); + + // The new proposed fee should be newFee2 + assertEq(pdpVerifier.proposedProofFeePerTiB(), newFee2); + } +} From 22e8243e51fdf42871d6c1d6266be33952417bef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Oct 2025 12:04:37 +0000 Subject: [PATCH 4/4] Fix linting: remove trailing whitespace and revert VERSION to 2.1.0 Co-authored-by: rjan90 <8628857+rjan90@users.noreply.github.com> --- src/Fees.sol | 2 +- src/PDPVerifier.sol | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Fees.sol b/src/Fees.sol index 8d1c92e..3960201 100644 --- a/src/Fees.sol +++ b/src/Fees.sol @@ -86,7 +86,7 @@ library PDPFees { function flatProofFee(uint256 rawSize, uint256 feePerTiB) internal pure returns (uint256) { require(rawSize > 0, "failed to validate: raw size must be greater than 0"); require(feePerTiB > 0, "failed to validate: fee per TiB must be greater than 0"); - + // Calculate fee based on dataset size in TiB // fee = (rawSize / TIB_IN_BYTES) * feePerTiB // To avoid precision loss, we compute: (rawSize * feePerTiB) / TIB_IN_BYTES diff --git a/src/PDPVerifier.sol b/src/PDPVerifier.sol index 00701e1..5eefe57 100644 --- a/src/PDPVerifier.sol +++ b/src/PDPVerifier.sol @@ -144,7 +144,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { mapping(uint256 => address) storageProvider; mapping(uint256 => address) dataSetProposedStorageProvider; mapping(uint256 => uint256) dataSetLastProvenEpoch; - + // Fee update mechanism uint256 public proofFeePerTiB; uint256 public proposedProofFeePerTiB; @@ -164,7 +164,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { nextDataSetId = 1; // Data sets start at 1 } - string public constant VERSION = "2.2.0"; + string public constant VERSION = "2.1.0"; event ContractUpgraded(string version, address implementation); @@ -200,18 +200,18 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { /// @dev The new fee becomes effective 7 days after proposal function updateProofFee(uint256 newFeePerTiB) external onlyOwner { require(newFeePerTiB > 0, "Fee must be greater than 0"); - + uint256 currentFee = getActiveProofFeePerTiB(); - + // Apply any pending fee update before proposing a new one if (proposedProofFeePerTiB > 0 && block.timestamp >= feeUpdateEffectiveTime) { proofFeePerTiB = proposedProofFeePerTiB; } - + // Set the new proposed fee with 7 days delay (7 * 24 * 60 * 60 = 604800 seconds) proposedProofFeePerTiB = newFeePerTiB; feeUpdateEffectiveTime = block.timestamp + 604800; - + emit FeeUpdateProposed(currentFee, newFeePerTiB, feeUpdateEffectiveTime); } @@ -641,7 +641,7 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { function calculateProofFee(uint256 setId, uint256 estimatedGasFee) public view returns (uint256) { uint256 rawSize = 32 * challengeRange[setId]; uint256 feePerTiB = getActiveProofFeePerTiB(); - + return PDPFees.flatProofFee(rawSize, feePerTiB); }