From 9bd5df12a5301748bdc673f7de461c56909fe553 Mon Sep 17 00:00:00 2001 From: rmagrys Date: Thu, 19 Mar 2026 12:15:22 +0100 Subject: [PATCH 1/8] sc --- src/PoRepMarket.sol | 57 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/src/PoRepMarket.sol b/src/PoRepMarket.sol index 2e12dce..b5a42d6 100644 --- a/src/PoRepMarket.sol +++ b/src/PoRepMarket.sol @@ -154,6 +154,8 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable error EmptyManifestLocation(); error TooLongManifestLocation(); error InvalidClientSmartContractAddress(); + error EmptyDealDuration(); + error InvalidDealDuration(); /** * @notice Constructor @@ -201,19 +203,9 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable SLITypes.DealTerms calldata terms, string calldata manifestLocation ) external { - if (requirements.retrievabilityBps > 10_000) { - revert InvalidRetrievabilityBps(requirements.retrievabilityBps); - } - if (requirements.indexingPct > 100) { - revert InvalidIndexingPct(requirements.indexingPct); - } - - if (bytes(manifestLocation).length == 0) { - revert EmptyManifestLocation(); - } - if (bytes(manifestLocation).length > 2048) { - revert TooLongManifestLocation(); - } + _ensureCorrectManifestLocation(manifestLocation); + _ensureCorrectRequirements(requirements); + _ensureCorrectTerms(terms); DealProposalsStorage storage $ = s(); @@ -474,6 +466,45 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable if (dp.state != expectedState) revert DealNotInExpectedState(dp.dealId, dp.state, expectedState); } + /** + * @notice Ensures the requirements are correct + * @param requirements The SLI thresholds for the deal + */ + function _ensureCorrectRequirements(SLITypes.SLIThresholds calldata requirements) internal pure { + if (requirements.retrievabilityBps > 10_000) { + revert InvalidRetrievabilityBps(requirements.retrievabilityBps); + } + if (requirements.indexingPct > 100) { + revert InvalidIndexingPct(requirements.indexingPct); + } + } + + /** + * @notice Ensures the terms are correct + * @param terms The terms for the deal + */ + function _ensureCorrectTerms(SLITypes.DealTerms calldata terms) internal pure { + if (terms.durationDays == 0) { + revert EmptyDealDuration(); + } + if (terms.durationDays % 30 != 0) { + revert InvalidDealDuration(); + } + } + + /** + * @notice Ensures the manifest location is correct + * @param manifestLocation The manifest location for the deal + */ + function _ensureCorrectManifestLocation(string calldata manifestLocation) internal pure { + if (bytes(manifestLocation).length == 0) { + revert EmptyManifestLocation(); + } + if (bytes(manifestLocation).length > 2048) { + revert TooLongManifestLocation(); + } + } + // solhint-disable no-empty-blocks /** * @notice Authorizes an upgrade From bff36515741d78b642460272d0b6aaf0db2be437 Mon Sep 17 00:00:00 2001 From: rmagrys Date: Thu, 19 Mar 2026 12:15:29 +0100 Subject: [PATCH 2/8] test --- test/PoRepMarket.t.sol | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/test/PoRepMarket.t.sol b/test/PoRepMarket.t.sol index c7263f5..1890705 100644 --- a/test/PoRepMarket.t.sol +++ b/test/PoRepMarket.t.sol @@ -34,7 +34,7 @@ contract PoRepMarketTest is Test { SLITypes.SLIThresholds({retrievabilityBps: 80, bandwidthMbps: 500, latencyMs: 200, indexingPct: 90}); SLITypes.DealTerms internal defaultTerms = - SLITypes.DealTerms({dealSizeBytes: 1024, pricePerSector: 100, durationDays: 365}); + SLITypes.DealTerms({dealSizeBytes: 1024, pricePerSector: 100, durationDays: 360}); string public expectedManifestLocation = "https://example.com/manifest"; @@ -556,6 +556,22 @@ contract PoRepMarketTest is Test { poRepMarket.updateManifestLocation(dealId, ""); } + function testProposeDealRevertsWhenDealDurationIsZero() public { + SLITypes.DealTerms memory badTerms = + SLITypes.DealTerms({durationDays: 0, dealSizeBytes: 1024, pricePerSector: 100}); + vm.prank(clientAddress); + vm.expectRevert(abi.encodeWithSelector(PoRepMarket.EmptyDealDuration.selector)); + poRepMarket.proposeDeal(defaultRequirements, badTerms, expectedManifestLocation); + } + + function testProposeDealRevertsWhenDealDurationIsNotMultiplicatioveOf30() public { + SLITypes.DealTerms memory badTerms = + SLITypes.DealTerms({durationDays: 31, dealSizeBytes: 1024, pricePerSector: 100}); + vm.prank(clientAddress); + vm.expectRevert(abi.encodeWithSelector(PoRepMarket.InvalidDealDuration.selector)); + poRepMarket.proposeDeal(defaultRequirements, badTerms, expectedManifestLocation); + } + function testUpdateManifestLocationRevertsUnauthorisedCaller() public { vm.prank(clientAddress); poRepMarket.proposeDeal(defaultRequirements, defaultTerms, expectedManifestLocation); From e36fe49287fcd619faa6d365fea83733aab5f1b5 Mon Sep 17 00:00:00 2001 From: rmagrys Date: Thu, 19 Mar 2026 12:15:33 +0100 Subject: [PATCH 3/8] abi --- abis/PoRepMarket.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/abis/PoRepMarket.json b/abis/PoRepMarket.json index ca56025..5983cef 100644 --- a/abis/PoRepMarket.json +++ b/abis/PoRepMarket.json @@ -1048,6 +1048,11 @@ "name": "ERC1967NonPayable", "inputs": [] }, + { + "type": "error", + "name": "EmptyDealDuration", + "inputs": [] + }, { "type": "error", "name": "EmptyManifestLocation", @@ -1063,6 +1068,11 @@ "name": "InvalidClientSmartContractAddress", "inputs": [] }, + { + "type": "error", + "name": "InvalidDealDuration", + "inputs": [] + }, { "type": "error", "name": "InvalidIndexingPct", From e75a59aa160e8101dd14b9cddf2295f02539d5df Mon Sep 17 00:00:00 2001 From: rmagrys Date: Thu, 19 Mar 2026 12:41:21 +0100 Subject: [PATCH 4/8] cr fix --- abis/PoRepMarket.json | 5 ----- src/PoRepMarket.sol | 3 +-- test/PoRepMarket.t.sol | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/abis/PoRepMarket.json b/abis/PoRepMarket.json index 5983cef..a5a285e 100644 --- a/abis/PoRepMarket.json +++ b/abis/PoRepMarket.json @@ -1048,11 +1048,6 @@ "name": "ERC1967NonPayable", "inputs": [] }, - { - "type": "error", - "name": "EmptyDealDuration", - "inputs": [] - }, { "type": "error", "name": "EmptyManifestLocation", diff --git a/src/PoRepMarket.sol b/src/PoRepMarket.sol index b5a42d6..fd290fe 100644 --- a/src/PoRepMarket.sol +++ b/src/PoRepMarket.sol @@ -154,7 +154,6 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable error EmptyManifestLocation(); error TooLongManifestLocation(); error InvalidClientSmartContractAddress(); - error EmptyDealDuration(); error InvalidDealDuration(); /** @@ -485,7 +484,7 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable */ function _ensureCorrectTerms(SLITypes.DealTerms calldata terms) internal pure { if (terms.durationDays == 0) { - revert EmptyDealDuration(); + revert InvalidDealDuration(); } if (terms.durationDays % 30 != 0) { revert InvalidDealDuration(); diff --git a/test/PoRepMarket.t.sol b/test/PoRepMarket.t.sol index 1890705..5d63d78 100644 --- a/test/PoRepMarket.t.sol +++ b/test/PoRepMarket.t.sol @@ -560,7 +560,7 @@ contract PoRepMarketTest is Test { SLITypes.DealTerms memory badTerms = SLITypes.DealTerms({durationDays: 0, dealSizeBytes: 1024, pricePerSector: 100}); vm.prank(clientAddress); - vm.expectRevert(abi.encodeWithSelector(PoRepMarket.EmptyDealDuration.selector)); + vm.expectRevert(abi.encodeWithSelector(PoRepMarket.InvalidDealDuration.selector)); poRepMarket.proposeDeal(defaultRequirements, badTerms, expectedManifestLocation); } From f094aaaf0f4158178b6c37be8705daa92f591ed2 Mon Sep 17 00:00:00 2001 From: rmagrys Date: Fri, 20 Mar 2026 11:09:06 +0100 Subject: [PATCH 5/8] cr fix --- abis/PoRepMarket.json | 13 +++++++++++++ src/PoRepMarket.sol | 8 ++++++++ test/PoRepMarket.t.sol | 9 +++++++++ 3 files changed, 30 insertions(+) diff --git a/abis/PoRepMarket.json b/abis/PoRepMarket.json index a5a285e..a1d5d9f 100644 --- a/abis/PoRepMarket.json +++ b/abis/PoRepMarket.json @@ -17,6 +17,19 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "MAX_DEAL_DURATION_DAYS", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint32", + "internalType": "uint32" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "UPGRADER_ROLE", diff --git a/src/PoRepMarket.sol b/src/PoRepMarket.sol index fd290fe..52cfaf2 100644 --- a/src/PoRepMarket.sol +++ b/src/PoRepMarket.sol @@ -25,6 +25,11 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable */ bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); + /** + * @notice Maximum deal duration in days + */ + uint32 public constant MAX_DEAL_DURATION_DAYS = 1278; + /// @custom:storage-location erc7201:porepmarket.storage.DealProposalsStorage struct DealProposalsStorage { mapping(uint256 dealId => PoRepTypes.DealProposal) _dealProposals; @@ -489,6 +494,9 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable if (terms.durationDays % 30 != 0) { revert InvalidDealDuration(); } + if (terms.durationDays > MAX_DEAL_DURATION_DAYS) { + revert InvalidDealDuration(); + } } /** diff --git a/test/PoRepMarket.t.sol b/test/PoRepMarket.t.sol index 5d63d78..b0f4cb3 100644 --- a/test/PoRepMarket.t.sol +++ b/test/PoRepMarket.t.sol @@ -572,6 +572,15 @@ contract PoRepMarketTest is Test { poRepMarket.proposeDeal(defaultRequirements, badTerms, expectedManifestLocation); } + function testProposeDealRevertsWhenDealDurationExceedsMaximum() public { + SLITypes.DealTerms memory badTerms = SLITypes.DealTerms({ + durationDays: poRepMarket.MAX_DEAL_DURATION_DAYS() + 1, dealSizeBytes: 1024, pricePerSector: 100 + }); + vm.prank(clientAddress); + vm.expectRevert(abi.encodeWithSelector(PoRepMarket.InvalidDealDuration.selector)); + poRepMarket.proposeDeal(defaultRequirements, badTerms, expectedManifestLocation); + } + function testUpdateManifestLocationRevertsUnauthorisedCaller() public { vm.prank(clientAddress); poRepMarket.proposeDeal(defaultRequirements, defaultTerms, expectedManifestLocation); From 3a5f43124b7d4ad04e5d2231eb329569e848ddb2 Mon Sep 17 00:00:00 2001 From: rmagrys Date: Fri, 20 Mar 2026 11:38:39 +0100 Subject: [PATCH 6/8] cr fix --- src/PoRepMarket.sol | 4 ++-- test/PoRepMarket.t.sol | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PoRepMarket.sol b/src/PoRepMarket.sol index 52cfaf2..672a197 100644 --- a/src/PoRepMarket.sol +++ b/src/PoRepMarket.sol @@ -491,10 +491,10 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable if (terms.durationDays == 0) { revert InvalidDealDuration(); } - if (terms.durationDays % 30 != 0) { + if (terms.durationDays > MAX_DEAL_DURATION_DAYS) { revert InvalidDealDuration(); } - if (terms.durationDays > MAX_DEAL_DURATION_DAYS) { + if (terms.durationDays % 30 != 0) { revert InvalidDealDuration(); } } diff --git a/test/PoRepMarket.t.sol b/test/PoRepMarket.t.sol index b0f4cb3..344eb11 100644 --- a/test/PoRepMarket.t.sol +++ b/test/PoRepMarket.t.sol @@ -574,7 +574,7 @@ contract PoRepMarketTest is Test { function testProposeDealRevertsWhenDealDurationExceedsMaximum() public { SLITypes.DealTerms memory badTerms = SLITypes.DealTerms({ - durationDays: poRepMarket.MAX_DEAL_DURATION_DAYS() + 1, dealSizeBytes: 1024, pricePerSector: 100 + durationDays: poRepMarket.MAX_DEAL_DURATION_DAYS() + 12, dealSizeBytes: 1024, pricePerSector: 100 }); vm.prank(clientAddress); vm.expectRevert(abi.encodeWithSelector(PoRepMarket.InvalidDealDuration.selector)); From 8fc43772ad2dcd3d214a6dd26cafdbd7d52a651c Mon Sep 17 00:00:00 2001 From: rmagrys Date: Fri, 20 Mar 2026 14:42:32 +0100 Subject: [PATCH 7/8] nat-spec --- src/PoRepMarket.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PoRepMarket.sol b/src/PoRepMarket.sol index 672a197..768738e 100644 --- a/src/PoRepMarket.sol +++ b/src/PoRepMarket.sol @@ -26,7 +26,9 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); /** - * @notice Maximum deal duration in days + * @notice Maximum Filecoin storage deal duration: 1278 days (~3.5 years), + * per FIP-0052 (NV21 actor policy update). + * https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0052.md */ uint32 public constant MAX_DEAL_DURATION_DAYS = 1278; From 870a03c3188dc862389ee8181d5343ba0d8b33a9 Mon Sep 17 00:00:00 2001 From: rmagrys Date: Fri, 20 Mar 2026 14:48:47 +0100 Subject: [PATCH 8/8] added one more link --- src/PoRepMarket.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PoRepMarket.sol b/src/PoRepMarket.sol index 768738e..f92981e 100644 --- a/src/PoRepMarket.sol +++ b/src/PoRepMarket.sol @@ -28,7 +28,9 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable /** * @notice Maximum Filecoin storage deal duration: 1278 days (~3.5 years), * per FIP-0052 (NV21 actor policy update). + * References: * https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0052.md + * https://github.com/filecoin-project/core-devs/blob/master/Network%20Upgrades/v21.md */ uint32 public constant MAX_DEAL_DURATION_DAYS = 1278;