diff --git a/.github/workflows/gas.yml b/.github/workflows/gas.yml new file mode 100644 index 00000000..51e33250 --- /dev/null +++ b/.github/workflows/gas.yml @@ -0,0 +1,45 @@ +name: Gas Snapshot + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +jobs: + gas-snapshot: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: v1.7.1 + + - name: Delete gas snapshot + run: rm -f service_contracts/.gas-snapshot + + - name: Regenerate gas snapshot + run: | + export PATH="/home/runner/.config/.foundry/bin:$PATH" + make -C service_contracts test + + - name: Dump submodules + run: | + git submodule foreach --recursive ' + depth=$(echo "$displaypath" | tr -cd "/" | wc -c) + indent=$(printf " %.0s" $(seq 1 $depth)) + printf "%s%s → %s\n" "$indent" "$displaypath" "$sha1" + ' + + - name: Check for uncommitted changes + run: | + git add service_contracts/.gas-snapshot + git diff --cached --exit-code service_contracts/.gas-snapshot || { + echo "Gas snapshot is out of date. Run 'make test' locally and commit the updated .gas-snapshot file." + exit 1 + } diff --git a/.gitmodules b/.gitmodules index 5abd0615..33461a4b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,7 +6,7 @@ url = https://github.com/OpenZeppelin/openzeppelin-contracts [submodule "service_contracts/lib/fws-payments"] path = service_contracts/lib/fws-payments - url = https://github.com/FilOzone/fws-payments + url = https://github.com/FilOzone/filecoin-pay [submodule "service_contracts/lib/pdp"] path = service_contracts/lib/pdp url = https://github.com/FilOzone/pdp @@ -16,3 +16,9 @@ [submodule "service_contracts/lib/openzeppelin-contracts-upgradeable"] path = service_contracts/lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "service_contracts/lib/fvm-solidity"] + path = service_contracts/lib/fvm-solidity + url = https://github.com/filecoin-project/fvm-solidity +[submodule "service_contracts/lib/solady"] + path = service_contracts/lib/solady + url = https://github.com/Vectorized/solady diff --git a/service_contracts/.gas-snapshot b/service_contracts/.gas-snapshot new file mode 100644 index 00000000..a58a248c --- /dev/null +++ b/service_contracts/.gas-snapshot @@ -0,0 +1,863 @@ +AbandonmentTest:testAbandonmentBlockedBeforeWindowByFWSS() (gas: 1456995) +AbandonmentTest:testAbandonmentBlockedBeforeWindowByPDPVerifier() (gas: 1132772) +AbandonmentTest:testAbandonmentClears() (gas: 1462361) +AbandonmentTest:testAbandonmentClearsActivatedFaultedDataSet() (gas: 2165944) +AbandonmentTest:testAbandonmentClearsActivatedProvenThenFaultedDataSet() (gas: 2222753) +AbandonmentTest:testAbandonmentClearsCDN() (gas: 2248033) +AbandonmentTest:testAbandonmentClearsCDNActivatedFaultedDataSet() (gas: 2975860) +AbandonmentTest:testAbandonmentClearsCDNPreFinalized() (gas: 2408941) +AbandonmentTest:testAbandonmentClearsCDNPreTerminated() (gas: 2373954) +AbandonmentTest:testAbandonment_underfundedPayer() (gas: 2013943) +AbandonmentTest:testAbandonment_underfundedPayer_lockupFixedReleased() (gas: 2402462) +AbandonmentTest:testTerminateService_underfundedPayer() (gas: 1745769) +BigEndianTest:test_decode() (gas: 10223) +BigEndianTest:test_encode() (gas: 392495) +BloomSetTest:testDifferent() (gas: 29550) +BloomSetTest:testIdentical() (gas: 23854) +ClientDataSetPaginationTest:testAddPiecesNonceIndependentFromFirstAdded() (gas: 1706712) +ClientDataSetPaginationTest:testAddPiecesNonceReplayProtection() (gas: 1524229) +ClientDataSetPaginationTest:testAddPiecesNonceUniquePerPayer() (gas: 2267317) +ClientDataSetPaginationTest:testAddPieces_RateUpdatedImmediately() (gas: 1622136) +ClientDataSetPaginationTest:testAnnouncePlannedUpgrade() (gas: 12658130) +ClientDataSetPaginationTest:testAnnouncePlannedUpgradeOnlyOwner() (gas: 5061549) +ClientDataSetPaginationTest:testAnnounceUpgradePlan() (gas: 12686455) +ClientDataSetPaginationTest:testAnnounceUpgradePlanOnlyOwner() (gas: 5060305) +ClientDataSetPaginationTest:testClientDataSetsPaginated_ConsistencyWithUnpaginated() (gas: 6444799) +ClientDataSetPaginationTest:testClientDataSetsPaginated_Empty() (gas: 24321) +ClientDataSetPaginationTest:testClientDataSetsPaginated_FullPage() (gas: 4630731) +ClientDataSetPaginationTest:testClientDataSetsPaginated_LimitExceedsRemaining() (gas: 2893514) +ClientDataSetPaginationTest:testClientDataSetsPaginated_LimitZeroReturnsAll() (gas: 4660515) +ClientDataSetPaginationTest:testClientDataSetsPaginated_OffsetBeyondLength() (gas: 2884685) +ClientDataSetPaginationTest:testClientDataSetsPaginated_SecondPage() (gas: 4625691) +ClientDataSetPaginationTest:testCreateDataSetAddPieces() (gas: 3164678) +ClientDataSetPaginationTest:testCreateDataSetCreatesRail() (gas: 2348883) +ClientDataSetPaginationTest:testCreateDataSetNoCDN() (gas: 3093298) +ClientDataSetPaginationTest:testCreateDataSetWithCDN_EmitsCDNPaymentRailsToppedUp() (gas: 2089614) +ClientDataSetPaginationTest:testCreateDataSetWithCDN_VerifyDefaultBehavior() (gas: 2161143) +ClientDataSetPaginationTest:testCreateDataSetWithoutCDN_NoCDNPaymentRailsToppedUpEvent() (gas: 1283642) +ClientDataSetPaginationTest:testDataSetMetaDataWithAllBoundaries() (gas: 8217900) +ClientDataSetPaginationTest:testDataSetMetadataEmpty() (gas: 1061009) +ClientDataSetPaginationTest:testDataSetMetadataKeyCountBoundaries() (gas: 5559601) +ClientDataSetPaginationTest:testDataSetMetadataKeyLengthBoundaries() (gas: 2580782) +ClientDataSetPaginationTest:testDataSetMetadataStorage() (gas: 1175201) +ClientDataSetPaginationTest:testDataSetMetadataStorageMultipleDataSets() (gas: 2057328) +ClientDataSetPaginationTest:testDataSetMetadataStorageMultipleKeys() (gas: 1392162) +ClientDataSetPaginationTest:testDataSetMetadataValueLengthBoundaries() (gas: 2745920) +ClientDataSetPaginationTest:testDatasetFee_AllSizesIncludeFee() (gas: 7756) +ClientDataSetPaginationTest:testDatasetFee_ExactlyPoint024USDFC() (gas: 6475) +ClientDataSetPaginationTest:testDatasetFee_IsAdditive() (gas: 5937) +ClientDataSetPaginationTest:testEmptyStringMetadata() (gas: 2295912) +ClientDataSetPaginationTest:testFilecoinServiceDeployedEvent() (gas: 5338689) +ClientDataSetPaginationTest:testGetClientDataSetsLength_AfterCreation() (gas: 4618273) +ClientDataSetPaginationTest:testGetClientDataSetsLength_Empty() (gas: 23127) +ClientDataSetPaginationTest:testGetClientDataSetsPaginated_ConsistencyWithUnpaginated() (gas: 5933384) +ClientDataSetPaginationTest:testGetClientDataSetsPaginated_Empty() (gas: 23102) +ClientDataSetPaginationTest:testGetClientDataSetsPaginated_ExcludesDeletedDataSets() (gas: 4369882) +ClientDataSetPaginationTest:testGetClientDataSetsPaginated_FullPage() (gas: 4728574) +ClientDataSetPaginationTest:testGetClientDataSetsPaginated_IncludesTerminatedDataSets() (gas: 3233726) +ClientDataSetPaginationTest:testGetClientDataSetsPaginated_LimitZeroReturnsAll() (gas: 3883736) +ClientDataSetPaginationTest:testGetClientDataSetsPaginated_OffsetBeyondLength() (gas: 2885361) +ClientDataSetPaginationTest:testGetClientDataSetsPaginated_SecondPage() (gas: 4692334) +ClientDataSetPaginationTest:testGetClientDataSets_EmptyClient() (gas: 27610) +ClientDataSetPaginationTest:testGetClientDataSets_ExcludesDeletedDataSets() (gas: 3569742) +ClientDataSetPaginationTest:testGetClientDataSets_MultipleDataSets() (gas: 2097126) +ClientDataSetPaginationTest:testGetClientDataSets_SingleDataSet() (gas: 1173515) +ClientDataSetPaginationTest:testGetClientDataSets_TerminatedDataSets() (gas: 3396288) +ClientDataSetPaginationTest:testGetDataSetSizeInBytes_ReturnsRawSize() (gas: 26372) +ClientDataSetPaginationTest:testGetEffectiveRatesValues() (gas: 17397) +ClientDataSetPaginationTest:testGetPieceMetadata() (gas: 2210564) +ClientDataSetPaginationTest:testGetPieceMetadata_NonExistentDataSet() (gas: 23355) +ClientDataSetPaginationTest:testGetPieceMetadata_NonExistentKey() (gas: 1950841) +ClientDataSetPaginationTest:testGetPieceMetdataAllKeys() (gas: 2162712) +ClientDataSetPaginationTest:testGetServicePriceValues() (gas: 24578) +ClientDataSetPaginationTest:testGlobalParameters() (gas: 25045) +ClientDataSetPaginationTest:testInitialState() (gas: 97750) +ClientDataSetPaginationTest:testInsufficientFunds_AddPiecesFailsImmediately() (gas: 1494222) +ClientDataSetPaginationTest:testInsufficientFunds_BelowMinimum() (gas: 687713) +ClientDataSetPaginationTest:testInsufficientFunds_ExactMinimum() (gas: 1156063) +ClientDataSetPaginationTest:testInsufficientFunds_JustAboveMinimum() (gas: 1157315) +ClientDataSetPaginationTest:testMetadataQueries() (gas: 5015785) +ClientDataSetPaginationTest:testNonceCannotBeReusedAcrossOperations() (gas: 1539559) +ClientDataSetPaginationTest:testOperatorApproval_AllSufficient() (gas: 1156445) +ClientDataSetPaginationTest:testOperatorApproval_InsufficientLockupAllowance() (gas: 704667) +ClientDataSetPaginationTest:testOperatorApproval_InsufficientMaxLockupPeriod() (gas: 702948) +ClientDataSetPaginationTest:testOperatorApproval_InsufficientRateAllowance() (gas: 702759) +ClientDataSetPaginationTest:testOperatorApproval_NotApproved() (gas: 622300) +ClientDataSetPaginationTest:testPieceMetadataAllBoundaries() (gas: 7011887) +ClientDataSetPaginationTest:testPieceMetadataArrayMismatchErrors() (gas: 1294654) +ClientDataSetPaginationTest:testPieceMetadataCannotBeAddedByNonPDPVerifier() (gas: 1783381) +ClientDataSetPaginationTest:testPieceMetadataCannotBeCalledWithMoreKeys() (gas: 1213836) +ClientDataSetPaginationTest:testPieceMetadataCannotBeCalledWithMoreValues() (gas: 1215712) +ClientDataSetPaginationTest:testPieceMetadataEmptyMetadataForAllPieces() (gas: 1459615) +ClientDataSetPaginationTest:testPieceMetadataForSameKeyCannotRewrite() (gas: 2310904) +ClientDataSetPaginationTest:testPieceMetadataKeyCountBoundaries() (gas: 4184715) +ClientDataSetPaginationTest:testPieceMetadataKeyLengthBoundaries() (gas: 3884496) +ClientDataSetPaginationTest:testPieceMetadataPerPieceDifferentMetadata() (gas: 2156333) +ClientDataSetPaginationTest:testPieceMetadataStorageAndRetrieval() (gas: 2161950) +ClientDataSetPaginationTest:testPieceMetadataValueLengthBoundaries() (gas: 4047797) +ClientDataSetPaginationTest:testProvenPeriods() (gas: 416252897) +ClientDataSetPaginationTest:testRailTerminated_DoesNotOverwritePdpEndEpoch() (gas: 1907313) +ClientDataSetPaginationTest:testRailTerminated_RevertsIfCallerNotPaymentsContract() (gas: 1117739) +ClientDataSetPaginationTest:testRailTerminated_RevertsIfRailNotAssociated() (gas: 41417) +ClientDataSetPaginationTest:testRailTerminated_RevertsIfTerminatorNotServiceContract() (gas: 1115755) +ClientDataSetPaginationTest:testRailTerminated_SetsPdpEndEpochAndEmitsEvent() (gas: 1823385) +ClientDataSetPaginationTest:testRegression_CDNDataSetCleanup() (gas: 6625836) +ClientDataSetPaginationTest:testServiceNameAndDescriptionValidation() (gas: 21032021) +ClientDataSetPaginationTest:testServiceProviderChangedRevertsIfNewServiceProviderZeroAddress() (gas: 1142797) +ClientDataSetPaginationTest:testServiceProviderChangedRevertsIfOldServiceProviderMismatch() (gas: 1124709) +ClientDataSetPaginationTest:testServiceProviderChangedRevertsIfUnauthorizedCaller() (gas: 1126472) +ClientDataSetPaginationTest:testServiceProviderChangedSuccessDecoupled() (gas: 1154993) +ClientDataSetPaginationTest:testServiceProviderChangedWithArbitraryExtraData() (gas: 1158054) +ClientDataSetPaginationTest:testSettleFilBeamPaymentRails_AfterServiceTermination() (gas: 2346385) +ClientDataSetPaginationTest:testSettleFilBeamPaymentRails_AfterTermination() (gas: 2266778) +ClientDataSetPaginationTest:testSettleFilBeamPaymentRails_BothAmounts() (gas: 2134131) +ClientDataSetPaginationTest:testSettleFilBeamPaymentRails_DataSetWithEmptyCDNMetadata() (gas: 2032331) +ClientDataSetPaginationTest:testSettleFilBeamPaymentRails_DataSetWithoutCDN() (gas: 1074832) +ClientDataSetPaginationTest:testSettleFilBeamPaymentRails_EmitsCorrectEvents() (gas: 2133663) +ClientDataSetPaginationTest:testSettleFilBeamPaymentRails_FailsWhenLockupLessThanSettlement() (gas: 1880200) +ClientDataSetPaginationTest:testSettleFilBeamPaymentRails_FailsWithInsufficientLockup() (gas: 1732481) +ClientDataSetPaginationTest:testSettleFilBeamPaymentRails_InvalidDataSetId() (gas: 45926) +ClientDataSetPaginationTest:testSettleFilBeamPaymentRails_NoEventsForZeroAmounts() (gas: 1712336) +ClientDataSetPaginationTest:testSettleFilBeamPaymentRails_OnlyCacheMissAmount() (gas: 2059065) +ClientDataSetPaginationTest:testSettleFilBeamPaymentRails_OnlyCdnAmount() (gas: 2060654) +ClientDataSetPaginationTest:testSettleFilBeamPaymentRails_OnlyfilBeamController() (gas: 1734399) +ClientDataSetPaginationTest:testSettleFilBeamPaymentRails_ProcessesPaymentsCorrectly() (gas: 2177194) +ClientDataSetPaginationTest:testSettleFilBeamPaymentRails_RevertIfNotController() (gas: 1735479) +ClientDataSetPaginationTest:testSettleFilBeamPaymentRails_ZeroAmounts() (gas: 1709844) +ClientDataSetPaginationTest:testTerminateCDNServiceLifecycle() (gas: 2618903) +ClientDataSetPaginationTest:testTerminateCDNService_AfterExternalCDNRailFinalization() (gas: 2073461) +ClientDataSetPaginationTest:testTerminateCDNService_AfterExternalCDNRailTermination() (gas: 2026937) +ClientDataSetPaginationTest:testTerminateCDNService_checkPDPPaymentRate() (gas: 2642585) +ClientDataSetPaginationTest:testTerminateCDNService_dataSetHasNoCDNEnabled() (gas: 1087337) +ClientDataSetPaginationTest:testTerminateServiceLifecycle() (gas: 3405516) +ClientDataSetPaginationTest:testTerminateService_AfterExternalCDNRailFinalization() (gas: 2706185) +ClientDataSetPaginationTest:testTerminateService_AfterExternalCDNRailTermination() (gas: 2553427) +ClientDataSetPaginationTest:testTerminateService_directPayer_emitsApprover() (gas: 1410259) +ClientDataSetPaginationTest:testTerminateService_directSP_emitsApprover() (gas: 1447310) +ClientDataSetPaginationTest:testTerminateService_extraData_callerNotServiceProvider() (gas: 1155084) +ClientDataSetPaginationTest:testTerminateService_sessionKey() (gas: 1500358) +ClientDataSetPaginationTest:testTerminateService_sessionKey_expiredKey() (gas: 1231339) +ClientDataSetPaginationTest:testTerminateService_sessionKey_unauthorizedKey() (gas: 1176493) +ClientDataSetPaginationTest:testTerminateService_sessionKey_wrongPermission() (gas: 1231406) +ClientDataSetPaginationTest:testTerminateService_signatureApprover() (gas: 1665599) +ClientDataSetPaginationTest:testTopUpCDNPaymentRails_IncrementalTopUps() (gas: 2172293) +ClientDataSetPaginationTest:testTopUpCDNPaymentRails_InvalidDataSetId() (gas: 43718) +ClientDataSetPaginationTest:testTopUpCDNPaymentRails_OnlyPayerCanTopUp() (gas: 1891422) +ClientDataSetPaginationTest:testTopUpCDNPaymentRails_RequiresCDNEnabled() (gas: 1074239) +ClientDataSetPaginationTest:testTopUpCDNPaymentRails_RevertsAfterCDNTermination() (gas: 1983846) +ClientDataSetPaginationTest:testTopUpCDNPaymentRails_RevertsAfterServiceTermination() (gas: 2218969) +ClientDataSetPaginationTest:testTopUpCDNPaymentRails_RevertsForIndividuallyTerminatedRails() (gas: 2340093) +ClientDataSetPaginationTest:testTopUpCDNPaymentRails_SucceedsBeforeTermination() (gas: 2389654) +ClientDataSetPaginationTest:testTopUpCDNPaymentRails_Success() (gas: 2011710) +ClientDataSetPaginationTest:testTopUpCDNPaymentRails_ZeroAmounts() (gas: 1896466) +ClientDataSetPaginationTest:testTransferCDNController() (gas: 132399) +ClientDataSetPaginationTest:testTransferCDNController_revertsIfZeroAddress() (gas: 40196) +ClientDataSetPaginationTest:testUpdatePaymentRates_NextProvingPeriodAfterRemovalUsesRawSize() (gas: 2059450) +ClientDataSetPaginationTest:testUpdatePaymentRates_PiecesAddedUsesRawSize() (gas: 1580045) +ExtsloadTest:test_extsload() (gas: 97579) +ExtsloadTest:test_extsloadStruct() (gas: 120613) +FilecoinWarmStorageServiceOwnerTest:testOwnerFieldSetCorrectlyOnDataSetCreation() (gas: 1148365) +FilecoinWarmStorageServiceOwnerTest:testStorageProviderChangedRevertsForUnregisteredProvider() (gas: 1105036) +FilecoinWarmStorageServiceOwnerTest:testStorageProviderChangedRevertsForWrongOldOwner() (gas: 1109318) +FilecoinWarmStorageServiceOwnerTest:testStorageProviderChangedSucceedsForAnyRegisteredProvider() (gas: 1107274) +FilecoinWarmStorageServiceOwnerTest:testStorageProviderChangedUpdatesOnlyOwnerField() (gas: 1138649) +FilecoinWarmStorageServiceSignatureTest:testRecoverSignerInvalidLength() (gas: 13126) +FilecoinWarmStorageServiceSignatureTest:testRecoverSignerInvalidValue() (gas: 13067) +FilecoinWarmStorageServiceSignatureTest:testRecoverSignerWithValidSignature() (gas: 24427) +FilecoinWarmStorageServiceSignatureTest:testRecoverSignerWithWrongSigner() (gas: 27079) +FilecoinWarmStorageServiceTest:testAddPiecesNonceIndependentFromFirstAdded() (gas: 1706580) +FilecoinWarmStorageServiceTest:testAddPiecesNonceReplayProtection() (gas: 1523855) +FilecoinWarmStorageServiceTest:testAddPiecesNonceUniquePerPayer() (gas: 2267119) +FilecoinWarmStorageServiceTest:testAddPieces_RateUpdatedImmediately() (gas: 1621762) +FilecoinWarmStorageServiceTest:testAnnouncePlannedUpgrade() (gas: 12658042) +FilecoinWarmStorageServiceTest:testAnnouncePlannedUpgradeOnlyOwner() (gas: 5061395) +FilecoinWarmStorageServiceTest:testAnnounceUpgradePlan() (gas: 12686367) +FilecoinWarmStorageServiceTest:testAnnounceUpgradePlanOnlyOwner() (gas: 5060151) +FilecoinWarmStorageServiceTest:testCreateDataSetAddPieces() (gas: 3164502) +FilecoinWarmStorageServiceTest:testCreateDataSetCreatesRail() (gas: 2348504) +FilecoinWarmStorageServiceTest:testCreateDataSetNoCDN() (gas: 3093210) +FilecoinWarmStorageServiceTest:testCreateDataSetWithCDN_EmitsCDNPaymentRailsToppedUp() (gas: 2089240) +FilecoinWarmStorageServiceTest:testCreateDataSetWithCDN_VerifyDefaultBehavior() (gas: 2160989) +FilecoinWarmStorageServiceTest:testCreateDataSetWithoutCDN_NoCDNPaymentRailsToppedUpEvent() (gas: 1283510) +FilecoinWarmStorageServiceTest:testDataSetMetaDataWithAllBoundaries() (gas: 8217812) +FilecoinWarmStorageServiceTest:testDataSetMetadataEmpty() (gas: 1060767) +FilecoinWarmStorageServiceTest:testDataSetMetadataKeyCountBoundaries() (gas: 5559601) +FilecoinWarmStorageServiceTest:testDataSetMetadataKeyLengthBoundaries() (gas: 2580694) +FilecoinWarmStorageServiceTest:testDataSetMetadataStorage() (gas: 1174849) +FilecoinWarmStorageServiceTest:testDataSetMetadataStorageMultipleDataSets() (gas: 2057196) +FilecoinWarmStorageServiceTest:testDataSetMetadataStorageMultipleKeys() (gas: 1391986) +FilecoinWarmStorageServiceTest:testDataSetMetadataValueLengthBoundaries() (gas: 2745568) +FilecoinWarmStorageServiceTest:testDatasetFee_AllSizesIncludeFee() (gas: 7404) +FilecoinWarmStorageServiceTest:testDatasetFee_ExactlyPoint024USDFC() (gas: 6233) +FilecoinWarmStorageServiceTest:testDatasetFee_IsAdditive() (gas: 5783) +FilecoinWarmStorageServiceTest:testEmptyStringMetadata() (gas: 2295780) +FilecoinWarmStorageServiceTest:testFilecoinServiceDeployedEvent() (gas: 5338491) +FilecoinWarmStorageServiceTest:testGetClientDataSets_EmptyClient() (gas: 27258) +FilecoinWarmStorageServiceTest:testGetClientDataSets_ExcludesDeletedDataSets() (gas: 3569600) +FilecoinWarmStorageServiceTest:testGetClientDataSets_MultipleDataSets() (gas: 2096729) +FilecoinWarmStorageServiceTest:testGetClientDataSets_SingleDataSet() (gas: 1173471) +FilecoinWarmStorageServiceTest:testGetClientDataSets_TerminatedDataSets() (gas: 3396046) +FilecoinWarmStorageServiceTest:testGetDataSetSizeInBytes_ReturnsRawSize() (gas: 25998) +FilecoinWarmStorageServiceTest:testGetEffectiveRatesValues() (gas: 17265) +FilecoinWarmStorageServiceTest:testGetPieceMetadata() (gas: 2210256) +FilecoinWarmStorageServiceTest:testGetPieceMetadata_NonExistentDataSet() (gas: 23355) +FilecoinWarmStorageServiceTest:testGetPieceMetadata_NonExistentKey() (gas: 1950467) +FilecoinWarmStorageServiceTest:testGetPieceMetdataAllKeys() (gas: 2162360) +FilecoinWarmStorageServiceTest:testGetServicePriceValues() (gas: 24270) +FilecoinWarmStorageServiceTest:testGlobalParameters() (gas: 24979) +FilecoinWarmStorageServiceTest:testInitialState() (gas: 97508) +FilecoinWarmStorageServiceTest:testInsufficientFunds_AddPiecesFailsImmediately() (gas: 1494178) +FilecoinWarmStorageServiceTest:testInsufficientFunds_BelowMinimum() (gas: 687559) +FilecoinWarmStorageServiceTest:testInsufficientFunds_ExactMinimum() (gas: 1155887) +FilecoinWarmStorageServiceTest:testInsufficientFunds_JustAboveMinimum() (gas: 1156963) +FilecoinWarmStorageServiceTest:testMetadataQueries() (gas: 5015609) +FilecoinWarmStorageServiceTest:testNonceCannotBeReusedAcrossOperations() (gas: 1539405) +FilecoinWarmStorageServiceTest:testOperatorApproval_AllSufficient() (gas: 1156225) +FilecoinWarmStorageServiceTest:testOperatorApproval_InsufficientLockupAllowance() (gas: 704293) +FilecoinWarmStorageServiceTest:testOperatorApproval_InsufficientMaxLockupPeriod() (gas: 702772) +FilecoinWarmStorageServiceTest:testOperatorApproval_InsufficientRateAllowance() (gas: 702583) +FilecoinWarmStorageServiceTest:testOperatorApproval_NotApproved() (gas: 622146) +FilecoinWarmStorageServiceTest:testPieceMetadataAllBoundaries() (gas: 7011821) +FilecoinWarmStorageServiceTest:testPieceMetadataArrayMismatchErrors() (gas: 1294522) +FilecoinWarmStorageServiceTest:testPieceMetadataCannotBeAddedByNonPDPVerifier() (gas: 1783139) +FilecoinWarmStorageServiceTest:testPieceMetadataCannotBeCalledWithMoreKeys() (gas: 1213836) +FilecoinWarmStorageServiceTest:testPieceMetadataCannotBeCalledWithMoreValues() (gas: 1215558) +FilecoinWarmStorageServiceTest:testPieceMetadataEmptyMetadataForAllPieces() (gas: 1459439) +FilecoinWarmStorageServiceTest:testPieceMetadataForSameKeyCannotRewrite() (gas: 2310750) +FilecoinWarmStorageServiceTest:testPieceMetadataKeyCountBoundaries() (gas: 4184627) +FilecoinWarmStorageServiceTest:testPieceMetadataKeyLengthBoundaries() (gas: 3884254) +FilecoinWarmStorageServiceTest:testPieceMetadataPerPieceDifferentMetadata() (gas: 2156201) +FilecoinWarmStorageServiceTest:testPieceMetadataStorageAndRetrieval() (gas: 2161576) +FilecoinWarmStorageServiceTest:testPieceMetadataValueLengthBoundaries() (gas: 4047423) +FilecoinWarmStorageServiceTest:testProvenPeriods() (gas: 416252721) +FilecoinWarmStorageServiceTest:testRailTerminated_DoesNotOverwritePdpEndEpoch() (gas: 1906961) +FilecoinWarmStorageServiceTest:testRailTerminated_RevertsIfCallerNotPaymentsContract() (gas: 1117651) +FilecoinWarmStorageServiceTest:testRailTerminated_RevertsIfRailNotAssociated() (gas: 41351) +FilecoinWarmStorageServiceTest:testRailTerminated_RevertsIfTerminatorNotServiceContract() (gas: 1115623) +FilecoinWarmStorageServiceTest:testRailTerminated_SetsPdpEndEpochAndEmitsEvent() (gas: 1823231) +FilecoinWarmStorageServiceTest:testRegression_CDNDataSetCleanup() (gas: 6625484) +FilecoinWarmStorageServiceTest:testServiceNameAndDescriptionValidation() (gas: 21032021) +FilecoinWarmStorageServiceTest:testServiceProviderChangedRevertsIfNewServiceProviderZeroAddress() (gas: 1142599) +FilecoinWarmStorageServiceTest:testServiceProviderChangedRevertsIfOldServiceProviderMismatch() (gas: 1124577) +FilecoinWarmStorageServiceTest:testServiceProviderChangedRevertsIfUnauthorizedCaller() (gas: 1126230) +FilecoinWarmStorageServiceTest:testServiceProviderChangedSuccessDecoupled() (gas: 1154905) +FilecoinWarmStorageServiceTest:testServiceProviderChangedWithArbitraryExtraData() (gas: 1157878) +FilecoinWarmStorageServiceTest:testSettleFilBeamPaymentRails_AfterServiceTermination() (gas: 2346297) +FilecoinWarmStorageServiceTest:testSettleFilBeamPaymentRails_AfterTermination() (gas: 2266690) +FilecoinWarmStorageServiceTest:testSettleFilBeamPaymentRails_BothAmounts() (gas: 2133999) +FilecoinWarmStorageServiceTest:testSettleFilBeamPaymentRails_DataSetWithEmptyCDNMetadata() (gas: 2032155) +FilecoinWarmStorageServiceTest:testSettleFilBeamPaymentRails_DataSetWithoutCDN() (gas: 1074524) +FilecoinWarmStorageServiceTest:testSettleFilBeamPaymentRails_EmitsCorrectEvents() (gas: 2133575) +FilecoinWarmStorageServiceTest:testSettleFilBeamPaymentRails_FailsWhenLockupLessThanSettlement() (gas: 1880046) +FilecoinWarmStorageServiceTest:testSettleFilBeamPaymentRails_FailsWithInsufficientLockup() (gas: 1732349) +FilecoinWarmStorageServiceTest:testSettleFilBeamPaymentRails_InvalidDataSetId() (gas: 45574) +FilecoinWarmStorageServiceTest:testSettleFilBeamPaymentRails_NoEventsForZeroAmounts() (gas: 1712138) +FilecoinWarmStorageServiceTest:testSettleFilBeamPaymentRails_OnlyCacheMissAmount() (gas: 2058889) +FilecoinWarmStorageServiceTest:testSettleFilBeamPaymentRails_OnlyCdnAmount() (gas: 2060280) +FilecoinWarmStorageServiceTest:testSettleFilBeamPaymentRails_OnlyfilBeamController() (gas: 1734047) +FilecoinWarmStorageServiceTest:testSettleFilBeamPaymentRails_ProcessesPaymentsCorrectly() (gas: 2177062) +FilecoinWarmStorageServiceTest:testSettleFilBeamPaymentRails_RevertIfNotController() (gas: 1735281) +FilecoinWarmStorageServiceTest:testSettleFilBeamPaymentRails_ZeroAmounts() (gas: 1709712) +FilecoinWarmStorageServiceTest:testTerminateCDNServiceLifecycle() (gas: 2618727) +FilecoinWarmStorageServiceTest:testTerminateCDNService_AfterExternalCDNRailFinalization() (gas: 2073373) +FilecoinWarmStorageServiceTest:testTerminateCDNService_AfterExternalCDNRailTermination() (gas: 2026937) +FilecoinWarmStorageServiceTest:testTerminateCDNService_checkPDPPaymentRate() (gas: 2642431) +FilecoinWarmStorageServiceTest:testTerminateCDNService_dataSetHasNoCDNEnabled() (gas: 1087139) +FilecoinWarmStorageServiceTest:testTerminateServiceLifecycle() (gas: 3405384) +FilecoinWarmStorageServiceTest:testTerminateService_AfterExternalCDNRailFinalization() (gas: 2705833) +FilecoinWarmStorageServiceTest:testTerminateService_AfterExternalCDNRailTermination() (gas: 2553119) +FilecoinWarmStorageServiceTest:testTerminateService_directPayer_emitsApprover() (gas: 1410193) +FilecoinWarmStorageServiceTest:testTerminateService_directSP_emitsApprover() (gas: 1447178) +FilecoinWarmStorageServiceTest:testTerminateService_extraData_callerNotServiceProvider() (gas: 1154776) +FilecoinWarmStorageServiceTest:testTerminateService_sessionKey() (gas: 1500226) +FilecoinWarmStorageServiceTest:testTerminateService_sessionKey_expiredKey() (gas: 1231185) +FilecoinWarmStorageServiceTest:testTerminateService_sessionKey_unauthorizedKey() (gas: 1176163) +FilecoinWarmStorageServiceTest:testTerminateService_sessionKey_wrongPermission() (gas: 1231208) +FilecoinWarmStorageServiceTest:testTerminateService_signatureApprover() (gas: 1665401) +FilecoinWarmStorageServiceTest:testTopUpCDNPaymentRails_IncrementalTopUps() (gas: 2172205) +FilecoinWarmStorageServiceTest:testTopUpCDNPaymentRails_InvalidDataSetId() (gas: 43410) +FilecoinWarmStorageServiceTest:testTopUpCDNPaymentRails_OnlyPayerCanTopUp() (gas: 1891290) +FilecoinWarmStorageServiceTest:testTopUpCDNPaymentRails_RequiresCDNEnabled() (gas: 1074085) +FilecoinWarmStorageServiceTest:testTopUpCDNPaymentRails_RevertsAfterCDNTermination() (gas: 1983824) +FilecoinWarmStorageServiceTest:testTopUpCDNPaymentRails_RevertsAfterServiceTermination() (gas: 2218925) +FilecoinWarmStorageServiceTest:testTopUpCDNPaymentRails_RevertsForIndividuallyTerminatedRails() (gas: 2339807) +FilecoinWarmStorageServiceTest:testTopUpCDNPaymentRails_SucceedsBeforeTermination() (gas: 2389566) +FilecoinWarmStorageServiceTest:testTopUpCDNPaymentRails_Success() (gas: 2011688) +FilecoinWarmStorageServiceTest:testTopUpCDNPaymentRails_ZeroAmounts() (gas: 1896268) +FilecoinWarmStorageServiceTest:testTransferCDNController() (gas: 132333) +FilecoinWarmStorageServiceTest:testTransferCDNController_revertsIfZeroAddress() (gas: 40174) +FilecoinWarmStorageServiceTest:testUpdatePaymentRates_NextProvingPeriodAfterRemovalUsesRawSize() (gas: 2059318) +FilecoinWarmStorageServiceTest:testUpdatePaymentRates_PiecesAddedUsesRawSize() (gas: 1579693) +FilecoinWarmStorageServiceUpgradeTest:testConfigureProvingPeriod() (gas: 2060999) +FilecoinWarmStorageServiceUpgradeTest:testConfigureProvingPeriodWithInvalidParameters() (gas: 130708) +FilecoinWarmStorageServiceUpgradeTest:testMigrate() (gas: 69746) +FilecoinWarmStorageServiceUpgradeTest:testMigrateOnlyOnce() (gas: 79567) +FilecoinWarmStorageServiceUpgradeTest:testMigrateWithViewContract() (gas: 2044659) +FilecoinWarmStorageServiceUpgradeTest:testNextPDPChallengeWindowStartThroughView() (gas: 2025594) +FilecoinWarmStorageServiceUpgradeTest:testSetViewContract() (gas: 2048427) +OpFeesTest:testAddPiecesNonceIndependentFromFirstAdded() (gas: 1706690) +OpFeesTest:testAddPiecesNonceReplayProtection() (gas: 1524229) +OpFeesTest:testAddPiecesNonceUniquePerPayer() (gas: 2267295) +OpFeesTest:testAddPieces_RateUpdatedImmediately() (gas: 1622134) +OpFeesTest:testAnnouncePlannedUpgrade() (gas: 12658634) +OpFeesTest:testAnnouncePlannedUpgradeOnlyOwner() (gas: 5061549) +OpFeesTest:testAnnounceUpgradePlan() (gas: 12686958) +OpFeesTest:testAnnounceUpgradePlanOnlyOwner() (gas: 5060283) +OpFeesTest:testCreateDataSetAddPieces() (gas: 3164792) +OpFeesTest:testCreateDataSetCreatesRail() (gas: 2348812) +OpFeesTest:testCreateDataSetNoCDN() (gas: 3093500) +OpFeesTest:testCreateDataSetWithCDN_EmitsCDNPaymentRailsToppedUp() (gas: 2089658) +OpFeesTest:testCreateDataSetWithCDN_VerifyDefaultBehavior() (gas: 2161132) +OpFeesTest:testCreateDataSetWithoutCDN_NoCDNPaymentRailsToppedUpEvent() (gas: 1283620) +OpFeesTest:testDataSetMetaDataWithAllBoundaries() (gas: 8218169) +OpFeesTest:testDataSetMetadataEmpty() (gas: 1060987) +OpFeesTest:testDataSetMetadataKeyCountBoundaries() (gas: 5559601) +OpFeesTest:testDataSetMetadataKeyLengthBoundaries() (gas: 2580937) +OpFeesTest:testDataSetMetadataStorage() (gas: 1175201) +OpFeesTest:testDataSetMetadataStorageMultipleDataSets() (gas: 2057306) +OpFeesTest:testDataSetMetadataStorageMultipleKeys() (gas: 1392162) +OpFeesTest:testDataSetMetadataValueLengthBoundaries() (gas: 2745876) +OpFeesTest:testDatasetFee_AllSizesIncludeFee() (gas: 7734) +OpFeesTest:testDatasetFee_ExactlyPoint024USDFC() (gas: 6453) +OpFeesTest:testDatasetFee_IsAdditive() (gas: 5915) +OpFeesTest:testEmptyStringMetadata() (gas: 2295890) +OpFeesTest:testFilecoinServiceDeployedEvent() (gas: 5338667) +OpFeesTest:testGetClientDataSets_EmptyClient() (gas: 27566) +OpFeesTest:testGetClientDataSets_ExcludesDeletedDataSets() (gas: 3569871) +OpFeesTest:testGetClientDataSets_MultipleDataSets() (gas: 2097081) +OpFeesTest:testGetClientDataSets_SingleDataSet() (gas: 1173471) +OpFeesTest:testGetClientDataSets_TerminatedDataSets() (gas: 3396310) +OpFeesTest:testGetDataSetSizeInBytes_ReturnsRawSize() (gas: 26350) +OpFeesTest:testGetEffectiveRatesValues() (gas: 17375) +OpFeesTest:testGetPieceMetadata() (gas: 2210506) +OpFeesTest:testGetPieceMetadata_NonExistentDataSet() (gas: 23355) +OpFeesTest:testGetPieceMetadata_NonExistentKey() (gas: 1950841) +OpFeesTest:testGetPieceMetdataAllKeys() (gas: 2162690) +OpFeesTest:testGetServicePriceValues() (gas: 24578) +OpFeesTest:testGlobalParameters() (gas: 25023) +OpFeesTest:testInitialState() (gas: 97750) +OpFeesTest:testInsufficientFunds_AddPiecesFailsImmediately() (gas: 1494251) +OpFeesTest:testInsufficientFunds_BelowMinimum() (gas: 687713) +OpFeesTest:testInsufficientFunds_ExactMinimum() (gas: 1156099) +OpFeesTest:testInsufficientFunds_JustAboveMinimum() (gas: 1157307) +OpFeesTest:testMetadataQueries() (gas: 5015785) +OpFeesTest:testNonceCannotBeReusedAcrossOperations() (gas: 1539537) +OpFeesTest:testOperatorApproval_AllSufficient() (gas: 1156423) +OpFeesTest:testOperatorApproval_InsufficientLockupAllowance() (gas: 704667) +OpFeesTest:testOperatorApproval_InsufficientMaxLockupPeriod() (gas: 702948) +OpFeesTest:testOperatorApproval_InsufficientRateAllowance() (gas: 702737) +OpFeesTest:testOperatorApproval_NotApproved() (gas: 622314) +OpFeesTest:testPieceMetadataAllBoundaries() (gas: 7011865) +OpFeesTest:testPieceMetadataArrayMismatchErrors() (gas: 1294632) +OpFeesTest:testPieceMetadataCannotBeAddedByNonPDPVerifier() (gas: 1783402) +OpFeesTest:testPieceMetadataCannotBeCalledWithMoreKeys() (gas: 1213836) +OpFeesTest:testPieceMetadataCannotBeCalledWithMoreValues() (gas: 1215712) +OpFeesTest:testPieceMetadataEmptyMetadataForAllPieces() (gas: 1459593) +OpFeesTest:testPieceMetadataForSameKeyCannotRewrite() (gas: 2310874) +OpFeesTest:testPieceMetadataKeyCountBoundaries() (gas: 4185177) +OpFeesTest:testPieceMetadataKeyLengthBoundaries() (gas: 3884474) +OpFeesTest:testPieceMetadataPerPieceDifferentMetadata() (gas: 2156311) +OpFeesTest:testPieceMetadataStorageAndRetrieval() (gas: 2161928) +OpFeesTest:testPieceMetadataValueLengthBoundaries() (gas: 4047775) +OpFeesTest:testProvenPeriods() (gas: 416252897) +OpFeesTest:testRailTerminated_DoesNotOverwritePdpEndEpoch() (gas: 1907269) +OpFeesTest:testRailTerminated_RevertsIfCallerNotPaymentsContract() (gas: 1118005) +OpFeesTest:testRailTerminated_RevertsIfRailNotAssociated() (gas: 41395) +OpFeesTest:testRailTerminated_RevertsIfTerminatorNotServiceContract() (gas: 1115733) +OpFeesTest:testRailTerminated_SetsPdpEndEpochAndEmitsEvent() (gas: 1823363) +OpFeesTest:testRegression_CDNDataSetCleanup() (gas: 6625813) +OpFeesTest:testServiceNameAndDescriptionValidation() (gas: 21032021) +OpFeesTest:testServiceProviderChangedRevertsIfNewServiceProviderZeroAddress() (gas: 1142797) +OpFeesTest:testServiceProviderChangedRevertsIfOldServiceProviderMismatch() (gas: 1124687) +OpFeesTest:testServiceProviderChangedRevertsIfUnauthorizedCaller() (gas: 1126507) +OpFeesTest:testServiceProviderChangedSuccessDecoupled() (gas: 1155246) +OpFeesTest:testServiceProviderChangedWithArbitraryExtraData() (gas: 1158054) +OpFeesTest:testSettleFilBeamPaymentRails_AfterServiceTermination() (gas: 2346643) +OpFeesTest:testSettleFilBeamPaymentRails_AfterTermination() (gas: 2267036) +OpFeesTest:testSettleFilBeamPaymentRails_BothAmounts() (gas: 2134109) +OpFeesTest:testSettleFilBeamPaymentRails_DataSetWithEmptyCDNMetadata() (gas: 2032331) +OpFeesTest:testSettleFilBeamPaymentRails_DataSetWithoutCDN() (gas: 1074832) +OpFeesTest:testSettleFilBeamPaymentRails_EmitsCorrectEvents() (gas: 2133891) +OpFeesTest:testSettleFilBeamPaymentRails_FailsWhenLockupLessThanSettlement() (gas: 1880178) +OpFeesTest:testSettleFilBeamPaymentRails_FailsWithInsufficientLockup() (gas: 1732459) +OpFeesTest:testSettleFilBeamPaymentRails_InvalidDataSetId() (gas: 45926) +OpFeesTest:testSettleFilBeamPaymentRails_NoEventsForZeroAmounts() (gas: 1712336) +OpFeesTest:testSettleFilBeamPaymentRails_OnlyCacheMissAmount() (gas: 2059065) +OpFeesTest:testSettleFilBeamPaymentRails_OnlyCdnAmount() (gas: 2060654) +OpFeesTest:testSettleFilBeamPaymentRails_OnlyfilBeamController() (gas: 1734355) +OpFeesTest:testSettleFilBeamPaymentRails_ProcessesPaymentsCorrectly() (gas: 2177172) +OpFeesTest:testSettleFilBeamPaymentRails_RevertIfNotController() (gas: 1735593) +OpFeesTest:testSettleFilBeamPaymentRails_ZeroAmounts() (gas: 1709822) +OpFeesTest:testTerminateCDNServiceLifecycle() (gas: 2618960) +OpFeesTest:testTerminateCDNService_AfterExternalCDNRailFinalization() (gas: 2073685) +OpFeesTest:testTerminateCDNService_AfterExternalCDNRailTermination() (gas: 2026937) +OpFeesTest:testTerminateCDNService_checkPDPPaymentRate() (gas: 2642563) +OpFeesTest:testTerminateCDNService_dataSetHasNoCDNEnabled() (gas: 1087315) +OpFeesTest:testTerminateServiceLifecycle() (gas: 3405494) +OpFeesTest:testTerminateService_AfterExternalCDNRailFinalization() (gas: 2706141) +OpFeesTest:testTerminateService_AfterExternalCDNRailTermination() (gas: 2553427) +OpFeesTest:testTerminateService_directPayer_emitsApprover() (gas: 1410237) +OpFeesTest:testTerminateService_directSP_emitsApprover() (gas: 1447266) +OpFeesTest:testTerminateService_extraData_callerNotServiceProvider() (gas: 1155108) +OpFeesTest:testTerminateService_sessionKey() (gas: 1500336) +OpFeesTest:testTerminateService_sessionKey_expiredKey() (gas: 1231396) +OpFeesTest:testTerminateService_sessionKey_unauthorizedKey() (gas: 1176528) +OpFeesTest:testTerminateService_sessionKey_wrongPermission() (gas: 1231463) +OpFeesTest:testTerminateService_signatureApprover() (gas: 1665599) +OpFeesTest:testTopUpCDNPaymentRails_IncrementalTopUps() (gas: 2172249) +OpFeesTest:testTopUpCDNPaymentRails_InvalidDataSetId() (gas: 43718) +OpFeesTest:testTopUpCDNPaymentRails_OnlyPayerCanTopUp() (gas: 1891400) +OpFeesTest:testTopUpCDNPaymentRails_RequiresCDNEnabled() (gas: 1074217) +OpFeesTest:testTopUpCDNPaymentRails_RevertsAfterCDNTermination() (gas: 1983824) +OpFeesTest:testTopUpCDNPaymentRails_RevertsAfterServiceTermination() (gas: 2218947) +OpFeesTest:testTopUpCDNPaymentRails_RevertsForIndividuallyTerminatedRails() (gas: 2340115) +OpFeesTest:testTopUpCDNPaymentRails_SucceedsBeforeTermination() (gas: 2389654) +OpFeesTest:testTopUpCDNPaymentRails_Success() (gas: 2011688) +OpFeesTest:testTopUpCDNPaymentRails_ZeroAmounts() (gas: 1896444) +OpFeesTest:testTransferCDNController() (gas: 132377) +OpFeesTest:testTransferCDNController_revertsIfZeroAddress() (gas: 40174) +OpFeesTest:testUpdatePaymentRates_NextProvingPeriodAfterRemovalUsesRawSize() (gas: 2059500) +OpFeesTest:testUpdatePaymentRates_PiecesAddedUsesRawSize() (gas: 1579985) +OpFeesTest:test_addPiecesFee_immediateFlush() (gas: 1604154) +OpFeesTest:test_addPiecesFee_largeBatch_revertsInsufficientFunds() (gas: 8709069) +OpFeesTest:test_addPiecesFee_largeBatch_triggersReplenishment() (gas: 9080496) +OpFeesTest:test_addPieces_depleted_reserve_reverts() (gas: 8765788) +OpFeesTest:test_createTerminate_createFeeFlushes() (gas: 1534192) +OpFeesTest:test_multipleRemovalFees_accumulate() (gas: 2454610) +OpFeesTest:test_nextProvingPeriod_postTermination_withExhaustedReserve() (gas: 4933605) +OpFeesTest:test_schedulePieceRemoval_depleted_reserve_reverts_remainingPaid() (gas: 8914374) +OpFeesTest:test_scheduledRemovalFee_deferredThenFlushed() (gas: 2134439) +OpFeesTest:test_terminateFee_chargedOnConsentCase() (gas: 1982178) +OpFeesTest:test_terminateFee_flushedWithoutRemovals() (gas: 1981519) +OpFeesTest:test_terminateFee_notChargedOnPayerDirectCall() (gas: 1844681) +OpFeesTest:test_terminateFee_notChargedOnSpInitiated() (gas: 1843632) +OpFeesTest:test_topUpLifecycleReserve_bit97_truncated() (gas: 1330140) +OpFeesTest:test_topUpLifecycleReserve_callerNotPayer_reverts() (gas: 1149888) +OpFeesTest:test_topUpLifecycleReserve_nonExistentDataSet_reverts() (gas: 42885) +OpFeesTest:test_topUpLifecycleReserve_overflow_reverts() (gas: 1202731) +OpFeesTest:test_topUpLifecycleReserve_terminated_reverts() (gas: 1394913) +PoRepDealActivationTest:testAddPiecesDuplicate() (gas: 1790498) +PoRepDealActivationTest:testAddPiecesUnauthorized() (gas: 1739198) +PoRepDealActivationTest:testCannotActivatePieceTwice() (gas: 2102728) +PoRepDealActivationTest:testExactBalanceSufficient() (gas: 2309879) +PoRepDealActivationTest:testPieceAddedCommitmentTooShort() (gas: 1744207) +PoRepDealActivationTest:testPieceAddedOnlyService() (gas: 1739596) +PoRepDealActivationTest:testPieceAddedUnauthorized() (gas: 1746015) +PoRepDealActivationTest:testPieceAddedWrongProvider() (gas: 1742706) +PoRepDealActivationTest:testSudoWithdrawAfterAmortize() (gas: 2447253) +PoRepDealExtendTest:testExtend() (gas: 200453) +PoRepDealExtendTest:testExtendRevertsAfterExpiry() (gas: 35592) +PoRepDealExtendTest:testExtendRevertsIfNotClient() (gas: 30176) +PoRepDealExtendTest:testExtendRevertsWhenFaulted() (gas: 265734) +PoRepDealExtendTest:testSweepRevertsBeforeExpiry() (gas: 32270) +PoRepDealSectorStatusTest:testBothSectorsRecovered() (gas: 1112078) +PoRepDealSectorStatusTest:testClientCannotDirectlyTerminateRail() (gas: 89979) +PoRepDealSectorStatusTest:testSectorExpiredAfterActivation() (gas: 304131) +PoRepDealSectorStatusTest:testSectorExpiredAfterDealEnd() (gas: 34061) +PoRepDealSectorStatusTest:testSectorExpiredAfterFaulty() (gas: 521954) +PoRepDealSectorStatusTest:testSectorExpiredNotInDeal() (gas: 35784) +PoRepDealSectorStatusTest:testSectorExpiredRevertsIfStillActive() (gas: 50331) +PoRepDealSectorStatusTest:testSectorExpiredRevertsNotYetCompacted() (gas: 50581) +PoRepDealSectorStatusTest:testSectorFaultyAfterDealEnd() (gas: 34279) +PoRepDealSectorStatusTest:testSectorFaultyAlreadyFailed() (gas: 163086) +PoRepDealSectorStatusTest:testSectorFaultyNotInDeal() (gas: 36046) +PoRepDealSectorStatusTest:testSectorFaultyRevertsIfStillActive() (gas: 50256) +PoRepDealSectorStatusTest:testSectorRecoverFaultyAfterActivation() (gas: 856349) +PoRepDealSectorStatusTest:testSectorRecoveredNotFailed() (gas: 33615) +PoRepDealSectorStatusTest:testSectorRecoveredStillFaulty() (gas: 175335) +PoRepDealSectorStatusTest:testSweepUnauthorized() (gas: 216654) +PoRepDealSectorStatusTest:testTerminateClientUnauthorized() (gas: 45562) +PoRepDealSectorStatusTest:testTerminatePayeeReceiverUnauthorized() (gas: 45820) +PoRepDealSectorStatusTest:testTerminateReceiverNonceUnauthorized() (gas: 45540) +PoRepDealSectorStatusTest:testTerminateStrangerUnauthorized() (gas: 45101) +PoRepDealSectorStatusTest:testUpdateLockupsPayeeReceiverUnauthorized() (gas: 45439) +PoRepDealSectorStatusTest:testUpdateLockupsReceiverNonceUnauthorized() (gas: 45335) +PoRepDealSectorStatusTest:testUpdateLockupsStrangerUnauthorized() (gas: 44654) +PoRepPayeeTest:testOwner() (gas: 55721) +PoRepPayeeTest:testSudo() (gas: 94891) +PoRepPayeeTest:testSudoForwardsInnerRevert() (gas: 155667) +PoRepPayeeTest:testSudoUnauthorized() (gas: 107528) +PoRepServiceTest:testCreateDealNotMiner() (gas: 40898) +PoRepServiceTest:testCreateReceiver() (gas: 303363) +PoRepServiceTest:testForbiddenMethod() (gas: 33401) +PoRepServiceTest:testUpdateLockupsNotDeal() (gas: 31362) +PriceListTest:testAddPiecesNonceIndependentFromFirstAdded() (gas: 1706602) +PriceListTest:testAddPiecesNonceReplayProtection() (gas: 1523943) +PriceListTest:testAddPiecesNonceUniquePerPayer() (gas: 2267163) +PriceListTest:testAddPieces_RateUpdatedImmediately() (gas: 1621850) +PriceListTest:testAnnouncePlannedUpgrade() (gas: 12658042) +PriceListTest:testAnnouncePlannedUpgradeOnlyOwner() (gas: 5061417) +PriceListTest:testAnnounceUpgradePlan() (gas: 12686389) +PriceListTest:testAnnounceUpgradePlanOnlyOwner() (gas: 5060173) +PriceListTest:testCreateDataSetAddPieces() (gas: 3164524) +PriceListTest:testCreateDataSetCreatesRail() (gas: 2348570) +PriceListTest:testCreateDataSetNoCDN() (gas: 3093210) +PriceListTest:testCreateDataSetWithCDN_EmitsCDNPaymentRailsToppedUp() (gas: 2089328) +PriceListTest:testCreateDataSetWithCDN_VerifyDefaultBehavior() (gas: 2161011) +PriceListTest:testCreateDataSetWithoutCDN_NoCDNPaymentRailsToppedUpEvent() (gas: 1283532) +PriceListTest:testDataSetMetaDataWithAllBoundaries() (gas: 8217812) +PriceListTest:testDataSetMetadataEmpty() (gas: 1060833) +PriceListTest:testDataSetMetadataKeyCountBoundaries() (gas: 5559601) +PriceListTest:testDataSetMetadataKeyLengthBoundaries() (gas: 2580716) +PriceListTest:testDataSetMetadataStorage() (gas: 1174937) +PriceListTest:testDataSetMetadataStorageMultipleDataSets() (gas: 2057218) +PriceListTest:testDataSetMetadataStorageMultipleKeys() (gas: 1392008) +PriceListTest:testDataSetMetadataValueLengthBoundaries() (gas: 2745656) +PriceListTest:testDatasetFee_AllSizesIncludeFee() (gas: 7492) +PriceListTest:testDatasetFee_ExactlyPoint024USDFC() (gas: 6299) +PriceListTest:testDatasetFee_IsAdditive() (gas: 5805) +PriceListTest:testEmptyStringMetadata() (gas: 2295802) +PriceListTest:testFilecoinServiceDeployedEvent() (gas: 5338557) +PriceListTest:testGetClientDataSets_EmptyClient() (gas: 27324) +PriceListTest:testGetClientDataSets_ExcludesDeletedDataSets() (gas: 3569600) +PriceListTest:testGetClientDataSets_MultipleDataSets() (gas: 2096817) +PriceListTest:testGetClientDataSets_SingleDataSet() (gas: 1173471) +PriceListTest:testGetClientDataSets_TerminatedDataSets() (gas: 3396112) +PriceListTest:testGetDataSetSizeInBytes_ReturnsRawSize() (gas: 26086) +PriceListTest:testGetEffectiveRatesValues() (gas: 17287) +PriceListTest:testGetPieceMetadata() (gas: 2210322) +PriceListTest:testGetPieceMetadata_NonExistentDataSet() (gas: 23355) +PriceListTest:testGetPieceMetadata_NonExistentKey() (gas: 1950555) +PriceListTest:testGetPieceMetdataAllKeys() (gas: 2162448) +PriceListTest:testGetServicePriceValues() (gas: 24357) +PriceListTest:testGlobalParameters() (gas: 24979) +PriceListTest:testInitialState() (gas: 97574) +PriceListTest:testInsufficientFunds_AddPiecesFailsImmediately() (gas: 1494178) +PriceListTest:testInsufficientFunds_BelowMinimum() (gas: 687581) +PriceListTest:testInsufficientFunds_ExactMinimum() (gas: 1155909) +PriceListTest:testInsufficientFunds_JustAboveMinimum() (gas: 1157029) +PriceListTest:testMetadataQueries() (gas: 5015631) +PriceListTest:testNonceCannotBeReusedAcrossOperations() (gas: 1539427) +PriceListTest:testOperatorApproval_AllSufficient() (gas: 1156291) +PriceListTest:testOperatorApproval_InsufficientLockupAllowance() (gas: 704381) +PriceListTest:testOperatorApproval_InsufficientMaxLockupPeriod() (gas: 702794) +PriceListTest:testOperatorApproval_InsufficientRateAllowance() (gas: 702605) +PriceListTest:testOperatorApproval_NotApproved() (gas: 622168) +PriceListTest:testPieceMetadataAllBoundaries() (gas: 7011821) +PriceListTest:testPieceMetadataArrayMismatchErrors() (gas: 1294544) +PriceListTest:testPieceMetadataCannotBeAddedByNonPDPVerifier() (gas: 1783205) +PriceListTest:testPieceMetadataCannotBeCalledWithMoreKeys() (gas: 1213836) +PriceListTest:testPieceMetadataCannotBeCalledWithMoreValues() (gas: 1215580) +PriceListTest:testPieceMetadataEmptyMetadataForAllPieces() (gas: 1459461) +PriceListTest:testPieceMetadataForSameKeyCannotRewrite() (gas: 2310772) +PriceListTest:testPieceMetadataKeyCountBoundaries() (gas: 4184627) +PriceListTest:testPieceMetadataKeyLengthBoundaries() (gas: 3884320) +PriceListTest:testPieceMetadataPerPieceDifferentMetadata() (gas: 2156223) +PriceListTest:testPieceMetadataStorageAndRetrieval() (gas: 2161664) +PriceListTest:testPieceMetadataValueLengthBoundaries() (gas: 4047511) +PriceListTest:testProvenPeriods() (gas: 416252743) +PriceListTest:testRailTerminated_DoesNotOverwritePdpEndEpoch() (gas: 1907027) +PriceListTest:testRailTerminated_RevertsIfCallerNotPaymentsContract() (gas: 1117651) +PriceListTest:testRailTerminated_RevertsIfRailNotAssociated() (gas: 41351) +PriceListTest:testRailTerminated_RevertsIfTerminatorNotServiceContract() (gas: 1115645) +PriceListTest:testRailTerminated_SetsPdpEndEpochAndEmitsEvent() (gas: 1823253) +PriceListTest:testRegression_CDNDataSetCleanup() (gas: 6625572) +PriceListTest:testServiceNameAndDescriptionValidation() (gas: 21032021) +PriceListTest:testServiceProviderChangedRevertsIfNewServiceProviderZeroAddress() (gas: 1142665) +PriceListTest:testServiceProviderChangedRevertsIfOldServiceProviderMismatch() (gas: 1124599) +PriceListTest:testServiceProviderChangedRevertsIfUnauthorizedCaller() (gas: 1126296) +PriceListTest:testServiceProviderChangedSuccessDecoupled() (gas: 1154905) +PriceListTest:testServiceProviderChangedWithArbitraryExtraData() (gas: 1157900) +PriceListTest:testSettleFilBeamPaymentRails_AfterServiceTermination() (gas: 2346297) +PriceListTest:testSettleFilBeamPaymentRails_AfterTermination() (gas: 2266690) +PriceListTest:testSettleFilBeamPaymentRails_BothAmounts() (gas: 2134021) +PriceListTest:testSettleFilBeamPaymentRails_DataSetWithEmptyCDNMetadata() (gas: 2032177) +PriceListTest:testSettleFilBeamPaymentRails_DataSetWithoutCDN() (gas: 1074590) +PriceListTest:testSettleFilBeamPaymentRails_EmitsCorrectEvents() (gas: 2133597) +PriceListTest:testSettleFilBeamPaymentRails_FailsWhenLockupLessThanSettlement() (gas: 1880068) +PriceListTest:testSettleFilBeamPaymentRails_FailsWithInsufficientLockup() (gas: 1732371) +PriceListTest:testSettleFilBeamPaymentRails_InvalidDataSetId() (gas: 45662) +PriceListTest:testSettleFilBeamPaymentRails_NoEventsForZeroAmounts() (gas: 1712204) +PriceListTest:testSettleFilBeamPaymentRails_OnlyCacheMissAmount() (gas: 2058911) +PriceListTest:testSettleFilBeamPaymentRails_OnlyCdnAmount() (gas: 2060368) +PriceListTest:testSettleFilBeamPaymentRails_OnlyfilBeamController() (gas: 1734113) +PriceListTest:testSettleFilBeamPaymentRails_ProcessesPaymentsCorrectly() (gas: 2177084) +PriceListTest:testSettleFilBeamPaymentRails_RevertIfNotController() (gas: 1735347) +PriceListTest:testSettleFilBeamPaymentRails_ZeroAmounts() (gas: 1709734) +PriceListTest:testTerminateCDNServiceLifecycle() (gas: 2618749) +PriceListTest:testTerminateCDNService_AfterExternalCDNRailFinalization() (gas: 2073373) +PriceListTest:testTerminateCDNService_AfterExternalCDNRailTermination() (gas: 2026937) +PriceListTest:testTerminateCDNService_checkPDPPaymentRate() (gas: 2642453) +PriceListTest:testTerminateCDNService_dataSetHasNoCDNEnabled() (gas: 1087183) +PriceListTest:testTerminateServiceLifecycle() (gas: 3405406) +PriceListTest:testTerminateService_AfterExternalCDNRailFinalization() (gas: 2705921) +PriceListTest:testTerminateService_AfterExternalCDNRailTermination() (gas: 2553185) +PriceListTest:testTerminateService_directPayer_emitsApprover() (gas: 1410193) +PriceListTest:testTerminateService_directSP_emitsApprover() (gas: 1447200) +PriceListTest:testTerminateService_extraData_callerNotServiceProvider() (gas: 1154842) +PriceListTest:testTerminateService_sessionKey() (gas: 1500248) +PriceListTest:testTerminateService_sessionKey_expiredKey() (gas: 1231207) +PriceListTest:testTerminateService_sessionKey_unauthorizedKey() (gas: 1176229) +PriceListTest:testTerminateService_sessionKey_wrongPermission() (gas: 1231274) +PriceListTest:testTerminateService_signatureApprover() (gas: 1665467) +PriceListTest:testTopUpCDNPaymentRails_IncrementalTopUps() (gas: 2172205) +PriceListTest:testTopUpCDNPaymentRails_InvalidDataSetId() (gas: 43476) +PriceListTest:testTopUpCDNPaymentRails_OnlyPayerCanTopUp() (gas: 1891312) +PriceListTest:testTopUpCDNPaymentRails_RequiresCDNEnabled() (gas: 1074107) +PriceListTest:testTopUpCDNPaymentRails_RevertsAfterCDNTermination() (gas: 1983824) +PriceListTest:testTopUpCDNPaymentRails_RevertsAfterServiceTermination() (gas: 2218925) +PriceListTest:testTopUpCDNPaymentRails_RevertsForIndividuallyTerminatedRails() (gas: 2339873) +PriceListTest:testTopUpCDNPaymentRails_SucceedsBeforeTermination() (gas: 2389588) +PriceListTest:testTopUpCDNPaymentRails_Success() (gas: 2011688) +PriceListTest:testTopUpCDNPaymentRails_ZeroAmounts() (gas: 1896334) +PriceListTest:testTransferCDNController() (gas: 132333) +PriceListTest:testTransferCDNController_revertsIfZeroAddress() (gas: 40174) +PriceListTest:testUpdatePaymentRates_NextProvingPeriodAfterRemovalUsesRawSize() (gas: 2059340) +PriceListTest:testUpdatePaymentRates_PiecesAddedUsesRawSize() (gas: 1579759) +PriceListTest:test_getPriceList_fees() (gas: 27846) +PriceListTest:test_getPriceList_lockups() (gas: 29658) +PriceListTest:test_getPriceList_rates() (gas: 27047) +PriceListTest:test_getPriceList_token() (gas: 25161) +ProductTypesTest:testPDPKeys() (gas: 14050) +ProviderIdSetTest:testAddDuplicates() (gas: 589153) +ProviderIdSetTest:testAddGet() (gas: 148064159) +ProviderIdSetTest:testAddManyDuplicates() (gas: 4226789) +ProviderIdSetTest:testAddRemoveLargeIds() (gas: 6300584) +ProviderIdSetTest:testOwnable() (gas: 182292) +ProviderIdSetTest:testProviderIdTooLarge() (gas: 134767) +ProviderIdSetTest:testRemoveFIFO() (gas: 57215423) +ProviderIdSetTest:testRemoveLIFO() (gas: 56229153) +ProviderValidationTest:testAddAndRemoveApprovedProvider() (gas: 271033) +ProviderValidationTest:testAddApprovedProviderAlreadyApproved() (gas: 156586) +ProviderValidationTest:testConsistencyBetweenIsApprovedAndGetAll() (gas: 766891) +ProviderValidationTest:testGetApprovedProviders() (gas: 484998) +ProviderValidationTest:testGetApprovedProvidersLength() (gas: 373195) +ProviderValidationTest:testGetApprovedProvidersPaginated() (gas: 562682) +ProviderValidationTest:testGetApprovedProvidersPaginatedConsistency() (gas: 975785) +ProviderValidationTest:testGetApprovedProvidersPaginatedEdgeCases() (gas: 167199) +ProviderValidationTest:testGetApprovedProvidersPaginatedGasEfficiency() (gas: 8157784) +ProviderValidationTest:testGetApprovedProvidersWithSingleProvider() (gas: 177437) +ProviderValidationTest:testOnlyOwnerCanManageApprovedProviders() (gas: 168698) +ProviderValidationTest:testProviderApprovedCanCreateDataset() (gas: 1966195) +ProviderValidationTest:testProviderNotRegistered() (gas: 69843) +ProviderValidationTest:testProviderRegisteredButNotApproved() (gas: 1799228) +ProviderValidationTest:testRemoveApprovedProviderNotInList() (gas: 197826) +ServiceProviderRegistryFullTest:testCanRemoveLastProduct() (gas: 981632) +ServiceProviderRegistryFullTest:testCanReregisterAfterRemoval() (gas: 1737404) +ServiceProviderRegistryFullTest:testCannotAddProductTwice() (gas: 877231) +ServiceProviderRegistryFullTest:testCannotRegisterTwice() (gas: 847320) +ServiceProviderRegistryFullTest:testCannotRemoveAlreadyRemoved() (gas: 912304) +ServiceProviderRegistryFullTest:testCannotUpdateProviderDescriptionIfNotOwner() (gas: 820724) +ServiceProviderRegistryFullTest:testCannotUpdateProviderDescriptionTooLong() (gas: 830167) +ServiceProviderRegistryFullTest:testCannotUpdateRemovedProvider() (gas: 932595) +ServiceProviderRegistryFullTest:testCapabilityUpdateClearsOldValues() (gas: 1372069) +ServiceProviderRegistryFullTest:testDescriptionTooLong() (gas: 106463) +ServiceProviderRegistryFullTest:testDirectMappingAccess() (gas: 1023828) +ServiceProviderRegistryFullTest:testEmptyCapabilityKey() (gas: 263254) +ServiceProviderRegistryFullTest:testEmptyCapabilityValue() (gas: 263325) +ServiceProviderRegistryFullTest:testEventTimestampsEmittedCorrectly() (gas: 1239352) +ServiceProviderRegistryFullTest:testGetActiveProvidersByProductType() (gas: 2404980) +ServiceProviderRegistryFullTest:testGetAllActiveProviders() (gas: 2226624) +ServiceProviderRegistryFullTest:testGetNonExistentProvider() (gas: 57419) +ServiceProviderRegistryFullTest:testGetProduct() (gas: 993861) +ServiceProviderRegistryFullTest:testGetProductCapabilities() (gas: 1075147) +ServiceProviderRegistryFullTest:testGetProductCapability() (gas: 1006166) +ServiceProviderRegistryFullTest:testGetProviderCount() (gas: 1598541) +ServiceProviderRegistryFullTest:testGetProvidersByProductType() (gas: 2405345) +ServiceProviderRegistryFullTest:testInitialState() (gas: 118316) +ServiceProviderRegistryFullTest:testInvalidCapabilityArrayLengthMismatch() (gas: 215179) +ServiceProviderRegistryFullTest:testInvalidCapabilityKeyTooLong() (gas: 256170) +ServiceProviderRegistryFullTest:testInvalidCapabilityValueTooLong() (gas: 258767) +ServiceProviderRegistryFullTest:testMaxCapabilitiesAllowed() (gas: 1899255) +ServiceProviderRegistryFullTest:testMultipleUpdatesInSameBlock() (gas: 1223805) +ServiceProviderRegistryFullTest:testNameTooLongOnRegister() (gas: 118438) +ServiceProviderRegistryFullTest:testNameTooLongOnUpdate() (gas: 863703) +ServiceProviderRegistryFullTest:testOnlyOwnerCanRemove() (gas: 819377) +ServiceProviderRegistryFullTest:testOnlyOwnerCanUpdate() (gas: 840482) +ServiceProviderRegistryFullTest:testProviderHasProduct() (gas: 803738) +ServiceProviderRegistryFullTest:testRegisterMultipleProviders() (gas: 2152021) +ServiceProviderRegistryFullTest:testRegisterProvider() (gas: 1488054) +ServiceProviderRegistryFullTest:testRegisterWithCapabilities() (gas: 1266599) +ServiceProviderRegistryFullTest:testRegisterWithExcessFee() (gas: 110467) +ServiceProviderRegistryFullTest:testRegisterWithInsufficientFee() (gas: 150421) +ServiceProviderRegistryFullTest:testRegisterWithInvalidData() (gas: 473883) +ServiceProviderRegistryFullTest:testRemoveProvider() (gas: 1018547) +ServiceProviderRegistryFullTest:testTooManyCapabilities() (gas: 343283) +ServiceProviderRegistryFullTest:testUpdateProduct() (gas: 1244797) +ServiceProviderRegistryFullTest:testUpdateProviderDescription() (gas: 917375) +ServiceProviderRegistryFullTest:testUpdateWithCapabilities() (gas: 1356142) +ServiceProviderRegistryFullTest:testUpdateWithTooManyCapabilities() (gas: 1007395) +ServiceProviderRegistryPaginationTest:testActiveProviderCountTracking() (gas: 2500676) +ServiceProviderRegistryPaginationTest:testPaginationConsistencyWithGetAll() (gas: 4338591) +ServiceProviderRegistryPaginationTest:testPaginationEdgeLimits() (gas: 2182012) +ServiceProviderRegistryPaginationTest:testPaginationNoProviders() (gas: 45522) +ServiceProviderRegistryPaginationTest:testPaginationPageBoundaries() (gas: 3570980) +ServiceProviderRegistryPaginationTest:testPaginationSingleProvider() (gas: 858041) +ServiceProviderRegistryPaginationTest:testPaginationWithInactiveProviders() (gas: 3707078) +ServiceProviderRegistryPaginationTest:testSequentialPagination() (gas: 6875148) +ServiceProviderRegistryTest:testAnnouncePlannedUpgrade() (gas: 4163061) +ServiceProviderRegistryTest:testAnnouncePlannedUpgradeInvalidImplementation() (gas: 41405) +ServiceProviderRegistryTest:testAnnouncePlannedUpgradeMinimumDelay() (gas: 3987005) +ServiceProviderRegistryTest:testAnnouncePlannedUpgradeOnlyOwner() (gas: 3955047) +ServiceProviderRegistryTest:testAnnounceUpgradePlan() (gas: 4162720) +ServiceProviderRegistryTest:testAnnounceUpgradePlanInvalidImplementation() (gas: 40097) +ServiceProviderRegistryTest:testAnnounceUpgradePlanMinimumDelay() (gas: 3985677) +ServiceProviderRegistryTest:testAnnounceUpgradePlanOnlyOwner() (gas: 3953791) +ServiceProviderRegistryTest:testBeneficiaryIsSetCorrectly() (gas: 824194) +ServiceProviderRegistryTest:testCannotRegisterWithZeroBeneficiary() (gas: 78844) +ServiceProviderRegistryTest:testCannotReinitialize() (gas: 37287) +ServiceProviderRegistryTest:testGetProviderPayeeReturnsCorrectAddress() (gas: 809261) +ServiceProviderRegistryTest:testGetProviderPayeeRevertsForInvalidProviderId() (gas: 30249) +ServiceProviderRegistryTest:testGetProviderWorks() (gas: 819475) +ServiceProviderRegistryTest:testGetProvidersByIdsEmptyArray() (gas: 19120) +ServiceProviderRegistryTest:testGetProvidersByIdsInactiveProvider() (gas: 970734) +ServiceProviderRegistryTest:testGetProvidersByIdsInvalidIds() (gas: 44649) +ServiceProviderRegistryTest:testGetProvidersByIdsMixedValidAndInvalid() (gas: 896787) +ServiceProviderRegistryTest:testGetProvidersByIdsMultipleValidProviders() (gas: 1633635) +ServiceProviderRegistryTest:testGetProvidersByIdsSingleValidProvider() (gas: 881164) +ServiceProviderRegistryTest:testInitialState() (gas: 46555) +ServiceProviderRegistryTest:testIsRegisteredProviderReturnsFalse() (gas: 34866) +ServiceProviderRegistryTest:testOnlyOwnerCanUpgrade() (gas: 4064490) +ServiceProviderRegistryTest:testRegisterProviderWithCapabilities() (gas: 1141463) +ServiceProviderRegistryTest:testTransferOwnership() (gas: 55648) +ValidatePaymentTest:testAddPiecesNonceIndependentFromFirstAdded() (gas: 1706788) +ValidatePaymentTest:testAddPiecesNonceReplayProtection() (gas: 1524437) +ValidatePaymentTest:testAddPiecesNonceUniquePerPayer() (gas: 2267437) +ValidatePaymentTest:testAddPieces_RateUpdatedImmediately() (gas: 1622367) +ValidatePaymentTest:testAnnouncePlannedUpgrade() (gas: 12658676) +ValidatePaymentTest:testAnnouncePlannedUpgradeOnlyOwner() (gas: 5061677) +ValidatePaymentTest:testAnnounceUpgradePlan() (gas: 12686951) +ValidatePaymentTest:testAnnounceUpgradePlanOnlyOwner() (gas: 5060433) +ValidatePaymentTest:testCreateDataSetAddPieces() (gas: 3164914) +ValidatePaymentTest:testCreateDataSetCreatesRail() (gas: 2348978) +ValidatePaymentTest:testCreateDataSetNoCDN() (gas: 3094485) +ValidatePaymentTest:testCreateDataSetWithCDN_EmitsCDNPaymentRailsToppedUp() (gas: 2089855) +ValidatePaymentTest:testCreateDataSetWithCDN_VerifyDefaultBehavior() (gas: 2161264) +ValidatePaymentTest:testCreateDataSetWithoutCDN_NoCDNPaymentRailsToppedUpEvent() (gas: 1283827) +ValidatePaymentTest:testDataSetDeleted_ClearsScheduledPieceMetadataRemovals() (gas: 2276137) +ValidatePaymentTest:testDataSetDeleted_RevertsIfRailNotSettled() (gas: 1524662) +ValidatePaymentTest:testDataSetDeleted_SucceedsAfterRailSettled() (gas: 1988645) +ValidatePaymentTest:testDataSetMetaDataWithAllBoundaries() (gas: 8218260) +ValidatePaymentTest:testDataSetMetadataEmpty() (gas: 1061195) +ValidatePaymentTest:testDataSetMetadataKeyCountBoundaries() (gas: 5560211) +ValidatePaymentTest:testDataSetMetadataKeyLengthBoundaries() (gas: 2580993) +ValidatePaymentTest:testDataSetMetadataStorage() (gas: 1175365) +ValidatePaymentTest:testDataSetMetadataStorageMultipleDataSets() (gas: 2057404) +ValidatePaymentTest:testDataSetMetadataStorageMultipleKeys() (gas: 1392283) +ValidatePaymentTest:testDataSetMetadataValueLengthBoundaries() (gas: 2746064) +ValidatePaymentTest:testDatasetFee_AllSizesIncludeFee() (gas: 7897) +ValidatePaymentTest:testDatasetFee_ExactlyPoint024USDFC() (gas: 6660) +ValidatePaymentTest:testDatasetFee_IsAdditive() (gas: 6056) +ValidatePaymentTest:testEmptyDataset_CannotProveAfterAllPiecesRemoved() (gas: 2044074) +ValidatePaymentTest:testEmptyDataset_CannotProveWhenNoPieces() (gas: 1152520) +ValidatePaymentTest:testEmptyDataset_NoPaymentWhenNoPieces() (gas: 1214434) +ValidatePaymentTest:testEmptyDataset_ReactivationPreservesProvingTimeline() (gas: 2814211) +ValidatePaymentTest:testEmptyDataset_ReactivationRejectsFarFutureChallengeEpoch() (gas: 2312831) +ValidatePaymentTest:testEmptyDataset_ReactivationRejectsPreviouslyProvenPeriod() (gas: 2468902) +ValidatePaymentTest:testEmptyStringMetadata() (gas: 2295966) +ValidatePaymentTest:testFilecoinServiceDeployedEvent() (gas: 5338839) +ValidatePaymentTest:testFullFlow_SPAbandonsService_ClientCanSettleAndCleanup() (gas: 1922743) +ValidatePaymentTest:testGetClientDataSets_EmptyClient() (gas: 27729) +ValidatePaymentTest:testGetClientDataSets_ExcludesDeletedDataSets() (gas: 3570010) +ValidatePaymentTest:testGetClientDataSets_MultipleDataSets() (gas: 2097246) +ValidatePaymentTest:testGetClientDataSets_SingleDataSet() (gas: 1173591) +ValidatePaymentTest:testGetClientDataSets_TerminatedDataSets() (gas: 3396477) +ValidatePaymentTest:testGetDataSetSizeInBytes_ReturnsRawSize() (gas: 26557) +ValidatePaymentTest:testGetEffectiveRatesValues() (gas: 17473) +ValidatePaymentTest:testGetPieceMetadata() (gas: 2210730) +ValidatePaymentTest:testGetPieceMetadata_NonExistentDataSet() (gas: 23364) +ValidatePaymentTest:testGetPieceMetadata_NonExistentKey() (gas: 1951071) +ValidatePaymentTest:testGetPieceMetdataAllKeys() (gas: 2162855) +ValidatePaymentTest:testGetServicePriceValues() (gas: 24742) +ValidatePaymentTest:testGlobalParameters() (gas: 25095) +ValidatePaymentTest:testInitialState() (gas: 97936) +ValidatePaymentTest:testInsufficientFunds_AddPiecesFailsImmediately() (gas: 1494562) +ValidatePaymentTest:testInsufficientFunds_BelowMinimum() (gas: 687833) +ValidatePaymentTest:testInsufficientFunds_ExactMinimum() (gas: 1156183) +ValidatePaymentTest:testInsufficientFunds_JustAboveMinimum() (gas: 1157435) +ValidatePaymentTest:testMetadataQueries() (gas: 5015907) +ValidatePaymentTest:testMultiplePieceMetadataRemovalAtNextProvingPeriod() (gas: 2237285) +ValidatePaymentTest:testNonceCannotBeReusedAcrossOperations() (gas: 1539679) +ValidatePaymentTest:testOperatorApproval_AllSufficient() (gas: 1156587) +ValidatePaymentTest:testOperatorApproval_InsufficientLockupAllowance() (gas: 704875) +ValidatePaymentTest:testOperatorApproval_InsufficientMaxLockupPeriod() (gas: 703068) +ValidatePaymentTest:testOperatorApproval_InsufficientRateAllowance() (gas: 702857) +ValidatePaymentTest:testOperatorApproval_NotApproved() (gas: 622456) +ValidatePaymentTest:testPieceMetadataAllBoundaries() (gas: 7012238) +ValidatePaymentTest:testPieceMetadataArrayMismatchErrors() (gas: 1294709) +ValidatePaymentTest:testPieceMetadataCannotBeAddedByNonPDPVerifier() (gas: 1783589) +ValidatePaymentTest:testPieceMetadataCannotBeCalledWithMoreKeys() (gas: 1214015) +ValidatePaymentTest:testPieceMetadataCannotBeCalledWithMoreValues() (gas: 1215833) +ValidatePaymentTest:testPieceMetadataEmptyMetadataForAllPieces() (gas: 1459713) +ValidatePaymentTest:testPieceMetadataForSameKeyCannotRewrite() (gas: 2311026) +ValidatePaymentTest:testPieceMetadataKeyCountBoundaries() (gas: 4185258) +ValidatePaymentTest:testPieceMetadataKeyLengthBoundaries() (gas: 3884686) +ValidatePaymentTest:testPieceMetadataPerPieceDifferentMetadata() (gas: 2156417) +ValidatePaymentTest:testPieceMetadataRemovalDeferredToNextProvingPeriod() (gas: 2638294) +ValidatePaymentTest:testPieceMetadataStorageAndRetrieval() (gas: 2162137) +ValidatePaymentTest:testPieceMetadataValueLengthBoundaries() (gas: 4047987) +ValidatePaymentTest:testProvenPeriods() (gas: 416253185) +ValidatePaymentTest:testRailTerminated_DoesNotOverwritePdpEndEpoch() (gas: 1907433) +ValidatePaymentTest:testRailTerminated_RevertsIfCallerNotPaymentsContract() (gas: 1117974) +ValidatePaymentTest:testRailTerminated_RevertsIfRailNotAssociated() (gas: 41566) +ValidatePaymentTest:testRailTerminated_RevertsIfTerminatorNotServiceContract() (gas: 1115809) +ValidatePaymentTest:testRailTerminated_SetsPdpEndEpochAndEmitsEvent() (gas: 1823505) +ValidatePaymentTest:testRegression_CDNDataSetCleanup() (gas: 6625985) +ValidatePaymentTest:testServiceNameAndDescriptionValidation() (gas: 21032256) +ValidatePaymentTest:testServiceProviderChangedRevertsIfNewServiceProviderZeroAddress() (gas: 1142938) +ValidatePaymentTest:testServiceProviderChangedRevertsIfOldServiceProviderMismatch() (gas: 1124784) +ValidatePaymentTest:testServiceProviderChangedRevertsIfUnauthorizedCaller() (gas: 1126692) +ValidatePaymentTest:testServiceProviderChangedSuccessDecoupled() (gas: 1155321) +ValidatePaymentTest:testServiceProviderChangedWithArbitraryExtraData() (gas: 1158173) +ValidatePaymentTest:testSettleFilBeamPaymentRails_AfterServiceTermination() (gas: 2346719) +ValidatePaymentTest:testSettleFilBeamPaymentRails_AfterTermination() (gas: 2267112) +ValidatePaymentTest:testSettleFilBeamPaymentRails_BothAmounts() (gas: 2134185) +ValidatePaymentTest:testSettleFilBeamPaymentRails_DataSetWithEmptyCDNMetadata() (gas: 2032451) +ValidatePaymentTest:testSettleFilBeamPaymentRails_DataSetWithoutCDN() (gas: 1074974) +ValidatePaymentTest:testSettleFilBeamPaymentRails_EmitsCorrectEvents() (gas: 2133945) +ValidatePaymentTest:testSettleFilBeamPaymentRails_FailsWhenLockupLessThanSettlement() (gas: 1880320) +ValidatePaymentTest:testSettleFilBeamPaymentRails_FailsWithInsufficientLockup() (gas: 1732557) +ValidatePaymentTest:testSettleFilBeamPaymentRails_InvalidDataSetId() (gas: 46067) +ValidatePaymentTest:testSettleFilBeamPaymentRails_NoEventsForZeroAmounts() (gas: 1712478) +ValidatePaymentTest:testSettleFilBeamPaymentRails_OnlyCacheMissAmount() (gas: 2059185) +ValidatePaymentTest:testSettleFilBeamPaymentRails_OnlyCdnAmount() (gas: 2060884) +ValidatePaymentTest:testSettleFilBeamPaymentRails_OnlyfilBeamController() (gas: 1734519) +ValidatePaymentTest:testSettleFilBeamPaymentRails_ProcessesPaymentsCorrectly() (gas: 2177246) +ValidatePaymentTest:testSettleFilBeamPaymentRails_RevertIfNotController() (gas: 1735735) +ValidatePaymentTest:testSettleFilBeamPaymentRails_ZeroAmounts() (gas: 1709920) +ValidatePaymentTest:testSettleRail_EndEpochAtActivationBoundaryFinalizes() (gas: 2147821) +ValidatePaymentTest:testSettleRail_MultiplePreActivationRateSegmentsSettleWithZeroPayment() (gas: 2085746) +ValidatePaymentTest:testTerminateCDNServiceLifecycle() (gas: 2619082) +ValidatePaymentTest:testTerminateCDNService_AfterExternalCDNRailFinalization() (gas: 2073740) +ValidatePaymentTest:testTerminateCDNService_AfterExternalCDNRailTermination() (gas: 2027143) +ValidatePaymentTest:testTerminateCDNService_checkPDPPaymentRate() (gas: 2642706) +ValidatePaymentTest:testTerminateCDNService_dataSetHasNoCDNEnabled() (gas: 1087457) +ValidatePaymentTest:testTerminateServiceLifecycle() (gas: 3405991) +ValidatePaymentTest:testTerminateService_AfterExternalCDNRailFinalization() (gas: 2706330) +ValidatePaymentTest:testTerminateService_AfterExternalCDNRailTermination() (gas: 2553593) +ValidatePaymentTest:testTerminateService_directPayer_emitsApprover() (gas: 1410408) +ValidatePaymentTest:testTerminateService_directSP_emitsApprover() (gas: 1447342) +ValidatePaymentTest:testTerminateService_extraData_callerNotServiceProvider() (gas: 1155248) +ValidatePaymentTest:testTerminateService_sessionKey() (gas: 1500413) +ValidatePaymentTest:testTerminateService_sessionKey_expiredKey() (gas: 1231516) +ValidatePaymentTest:testTerminateService_sessionKey_unauthorizedKey() (gas: 1176691) +ValidatePaymentTest:testTerminateService_sessionKey_wrongPermission() (gas: 1231605) +ValidatePaymentTest:testTerminateService_signatureApprover() (gas: 1665742) +ValidatePaymentTest:testTopUpCDNPaymentRails_IncrementalTopUps() (gas: 2172514) +ValidatePaymentTest:testTopUpCDNPaymentRails_InvalidDataSetId() (gas: 43881) +ValidatePaymentTest:testTopUpCDNPaymentRails_OnlyPayerCanTopUp() (gas: 1891476) +ValidatePaymentTest:testTopUpCDNPaymentRails_RequiresCDNEnabled() (gas: 1074359) +ValidatePaymentTest:testTopUpCDNPaymentRails_RevertsAfterCDNTermination() (gas: 1984132) +ValidatePaymentTest:testTopUpCDNPaymentRails_RevertsAfterServiceTermination() (gas: 2219247) +ValidatePaymentTest:testTopUpCDNPaymentRails_RevertsForIndividuallyTerminatedRails() (gas: 2340257) +ValidatePaymentTest:testTopUpCDNPaymentRails_SucceedsBeforeTermination() (gas: 2389730) +ValidatePaymentTest:testTopUpCDNPaymentRails_Success() (gas: 2011849) +ValidatePaymentTest:testTopUpCDNPaymentRails_ZeroAmounts() (gas: 1896609) +ValidatePaymentTest:testTransferCDNController() (gas: 132681) +ValidatePaymentTest:testTransferCDNController_revertsIfZeroAddress() (gas: 40348) +ValidatePaymentTest:testUpdatePaymentRates_NextProvingPeriodAfterRemovalUsesRawSize() (gas: 2059583) +ValidatePaymentTest:testUpdatePaymentRates_PiecesAddedUsesRawSize() (gas: 1580156) +ValidatePaymentTest:testValidatePayment_ActivationBoundarySettlesWithZeroPayment() (gas: 1508441) +ValidatePaymentTest:testValidatePayment_AdvancesPastUnprovenPeriodsWithPassedDeadlines() (gas: 1512458) +ValidatePaymentTest:testValidatePayment_AllEpochsProven() (gas: 1837636) +ValidatePaymentTest:testValidatePayment_BeforeActivationSettlesWithZeroPayment() (gas: 1522844) +ValidatePaymentTest:testValidatePayment_BlocksOnUnprovenPeriodWithOpenDeadline() (gas: 1507648) +ValidatePaymentTest:testValidatePayment_FirstPeriodUnproven() (gas: 2004575) +ValidatePaymentTest:testValidatePayment_InvalidEpochRange() (gas: 1176899) +ValidatePaymentTest:testValidatePayment_InvalidRailId() (gas: 25049) +ValidatePaymentTest:testValidatePayment_MixedProvenAndUnprovenWithPassedDeadlines() (gas: 1596865) +ValidatePaymentTest:testValidatePayment_NoEpochsProven() (gas: 1715931) +ValidatePaymentTest:testValidatePayment_PartialPeriodCoverage() (gas: 1628401) +ValidatePaymentTest:testValidatePayment_ProvingNeverActivated() (gas: 1182528) +ValidatePaymentTest:testValidatePayment_SomeEpochsProven() (gas: 1777634) +ValidatePaymentTest:testValidatePayment_ToEpochExactlyOnDeadline() (gas: 1600213) \ No newline at end of file diff --git a/service_contracts/Makefile b/service_contracts/Makefile index d845791d..13b65b7d 100644 --- a/service_contracts/Makefile +++ b/service_contracts/Makefile @@ -8,7 +8,7 @@ CHALLENGE_FINALITY ?= # Default target .PHONY: default -default: build test +default: build # All target including installation .PHONY: all @@ -86,6 +86,7 @@ check-tools: .PHONY: test test: forge test --via-ir -vv + @forge snapshot --isolate --no-match-test "Fuzz" --no-match-contract "MetadataSignatureFixturesTest" # Clean build artifacts .PHONY: clean diff --git a/service_contracts/foundry.toml b/service_contracts/foundry.toml index c2dc49ba..8bc0c296 100644 --- a/service_contracts/foundry.toml +++ b/service_contracts/foundry.toml @@ -9,8 +9,17 @@ solc = "0.8.30" via_ir = true optimizer = true optimizer_runs = 200 +# deterministic compilation output +cbor_metadata = false bytecode_hash = "none" +ignored_error_codes = [ + 2394, # don't need to clear tstore + 2519, # shadowed declarations + 3628, # https://github.com/argotorg/solidity/issues/10159 + 3860, # tests exceed size limit +] + # For dependencies remappings = [ '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/', @@ -19,8 +28,9 @@ remappings = [ '@fws-payments/=lib/fws-payments/src/', '@pdp/=lib/pdp/src/', '@session-key-registry/=lib/session-key-registry/src/', - '@fvm-solidity/=lib/pdp/lib/fvm-solidity/src/', + '@fvm-solidity/=lib/fvm-solidity/src/', '@pythnetwork/pyth-sdk-solidity/=lib/pdp/lib/pyth-sdk-solidity/', + '@solady/=lib/solady/src/', ] # Allow reading test data files diff --git a/service_contracts/lib/fvm-solidity b/service_contracts/lib/fvm-solidity new file mode 160000 index 00000000..2b3412a8 --- /dev/null +++ b/service_contracts/lib/fvm-solidity @@ -0,0 +1 @@ +Subproject commit 2b3412a8555cfa7451199e0ba82c1ca870f2f45b diff --git a/service_contracts/lib/solady b/service_contracts/lib/solady new file mode 160000 index 00000000..acd959aa --- /dev/null +++ b/service_contracts/lib/solady @@ -0,0 +1 @@ +Subproject commit acd959aa4bd04720d640bf4e6a5c71037510cc4b diff --git a/service_contracts/src/PoRepDeal.sol b/service_contracts/src/PoRepDeal.sol new file mode 100644 index 00000000..5dcd3053 --- /dev/null +++ b/service_contracts/src/PoRepDeal.sol @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +pragma solidity ^0.8.30; + +import {FilecoinPayV1} from "@fws-payments/FilecoinPayV1.sol"; +import {FVMSector, SectorStatus} from "@fvm-solidity/FVMSector.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IPoRepService { + function updateLockups(uint64 nonce, uint256 railId, uint256 payment, uint256 remaining) external; + function terminate(uint64 nonce, uint256 railId, uint64 provider, address sender) external; +} + +contract PoRepDeal { + address public immutable SERVICE; + address public immutable CLIENT; + uint64 public immutable PROVIDER; + FilecoinPayV1 private immutable PAYMENTS; + uint256 public immutable RAIL_ID; + IERC20 public immutable TOKEN; + uint256 public immutable TOKENS_PER_BYTE_PER_EPOCH; + uint64 private immutable NONCE; + + struct Info { + uint64 settledEpoch; + uint64 endEpoch; + uint32 faultedSectorCount; + uint96 totalActiveSize; + } + + Info public info; + + enum PieceStatus { + UNAUTHORIZED, + AUTHORIZED, + ACTIVE + } + + // TODO move to child contract + mapping(bytes32 pieceId => PieceStatus) pieces; + + // TODO move to child contract + struct SectorInfo { + uint96 dealSize; + uint8 failed; + } + + mapping(uint256 sectorId => SectorInfo) public sectors; + + error Unauthorized(); + error PieceAlreadyAuthorized(bytes32 pieceDigest); + error WrongProvider(uint64 minerId); + error CommitmentTooShort(uint64 minimumCommitmentEpoch, uint64 endEpoch); + error PieceNotAuthorized(bytes32 pieceDigest); + error DealExpired(); + error DealFaulted(); + error DealNotExpired(); + error SectorNotInDeal(uint64 sectorId); + error SectorAlreadyFailed(uint64 sectorId); + error SectorNotFailed(uint64 sectorId); + error SectorNotFaulty(uint64 sectorId); + error SectorNotActive(uint64 sectorId); + error SectorNotDead(uint64 sectorId); + + constructor( + address service, + address client, + uint64 provider, + FilecoinPayV1 payments, + uint256 railId, + IERC20 token, + uint256 tokensPerBytePerEpoch, + uint64 dealEndEpoch, + uint64 nonce + ) { + SERVICE = service; + CLIENT = client; + PROVIDER = provider; + PAYMENTS = payments; + TOKEN = token; + TOKENS_PER_BYTE_PER_EPOCH = tokensPerBytePerEpoch; + RAIL_ID = railId; + NONCE = nonce; + info.endEpoch = dealEndEpoch; + } + + function _onlyClient() internal view { + require(msg.sender == CLIENT, Unauthorized()); + } + + function _onlyService() internal view { + require(msg.sender == SERVICE, Unauthorized()); + } + + modifier onlyClient() { + _onlyClient(); + _; + } + + modifier onlyService() { + _onlyService(); + _; + } + + // TODO allow provider to addPieces with client authorization + function addPieces(bytes32[] calldata pieceDigests) external onlyClient { + for (uint256 i = 0; i < pieceDigests.length; i++) { + bytes32 pieceDigest = pieceDigests[i]; + require(pieces[pieceDigest] == PieceStatus.UNAUTHORIZED, PieceAlreadyAuthorized(pieceDigest)); + pieces[pieceDigest] = PieceStatus.AUTHORIZED; + } + } + + function pieceAdded( + uint64 minerId, + bytes32 pieceDigest, + uint64 sectorId, + uint64 minimumCommitmentEpoch, + uint64 paddedSize + ) external onlyService { + require(minerId == PROVIDER, WrongProvider(minerId)); + + // this also enforces block.number < info.endEpoch because minimum commitment is 180 days + require(minimumCommitmentEpoch >= info.endEpoch, CommitmentTooShort(minimumCommitmentEpoch, info.endEpoch)); + + require(pieces[pieceDigest] == PieceStatus.AUTHORIZED, PieceNotAuthorized(pieceDigest)); + pieces[pieceDigest] = PieceStatus.ACTIVE; + + sectors[sectorId].dealSize += paddedSize; + + // TODO only amortize once per SectorContentChanged notification + uint256 prevSize = info.totalActiveSize; + uint256 newSize = prevSize + paddedSize; + uint256 prevRate = info.faultedSectorCount > 0 ? 0 : prevSize * TOKENS_PER_BYTE_PER_EPOCH; + uint256 newRate = newSize * TOKENS_PER_BYTE_PER_EPOCH; + amortize((block.number - info.settledEpoch) * prevRate, (info.endEpoch - block.number) * newRate); + info.settledEpoch = uint64(block.number); + info.totalActiveSize = uint96(newSize); + } + + function extend(uint64 epochs) external onlyClient { + require(block.number < info.endEpoch, DealExpired()); + require(info.faultedSectorCount == 0, DealFaulted()); + uint64 newEndEpoch = info.endEpoch + epochs; + uint256 rate = info.totalActiveSize * TOKENS_PER_BYTE_PER_EPOCH; + amortize((block.number - info.settledEpoch) * rate, (newEndEpoch - block.number) * rate); + info.endEpoch = newEndEpoch; + info.settledEpoch = uint64(block.number); + } + + function amortize(uint256 payment, uint256 remaining) internal { + IPoRepService(SERVICE).updateLockups(NONCE, RAIL_ID, payment, remaining); + } + + function amortize() public { + if (info.faultedSectorCount == 0) { + amortizeHealthy(); + return; + } + info.settledEpoch = uint64(block.number); + } + + function amortizeHealthy() internal { + uint256 rate = info.totalActiveSize * TOKENS_PER_BYTE_PER_EPOCH; + if (block.number < info.endEpoch) { + // live + amortize((block.number - info.settledEpoch) * rate, (info.endEpoch - block.number) * rate); + } else { + // expired + amortize((info.endEpoch - info.settledEpoch) * rate, 0); + } + info.settledEpoch = uint64(block.number); + } + + function getInsuranceFunds() internal view returns (uint256 funds) { + (funds,,,) = PAYMENTS.accounts(TOKEN, address(this)); + } + + function payoutBounty(address recipient, uint256 bounty) internal { + if (bounty > 0) { + PAYMENTS.withdrawTo(TOKEN, recipient, bounty); + } + } + + // Pass NO_DEADLINE and NO_PARTITION once the sector has been compacted via CompactPartitions. + function sectorExpired(uint64 sectorId, int64 deadline, int64 partition, address recipient) external { + require(block.number < info.endEpoch, DealExpired()); + require(sectors[sectorId].dealSize > 0, SectorNotInDeal(sectorId)); + require( + FVMSector.validateSectorStatus(PROVIDER, sectorId, SectorStatus.Dead, deadline, partition), + SectorNotDead(sectorId) + ); + + // this is unrecoverable, so terminate + info.endEpoch = uint64(block.number); + amortize(); + terminate(recipient, 0, address(0)); + } + + function sectorFaulty(uint64 sectorId, int64 deadline, int64 partition, address recipient) external { + require(block.number < info.endEpoch, DealExpired()); + + SectorInfo storage sectorInfo = sectors[sectorId]; + require(sectorInfo.dealSize > 0, SectorNotInDeal(sectorId)); + require(sectorInfo.failed == 0, SectorAlreadyFailed(sectorId)); + require( + FVMSector.validateSectorStatus(PROVIDER, sectorId, SectorStatus.Faulty, deadline, partition), + SectorNotFaulty(sectorId) + ); + + sectorInfo.failed = 1; + if (info.faultedSectorCount == 0) { + amortizeHealthy(); + info.faultedSectorCount = 1; + payoutBounty(recipient, getInsuranceFunds() / 2); + } else { + info.faultedSectorCount++; + } + } + + // SPs should call this after DeclareFaultsRecovered and a successful Window PoSt + function sectorRecovered(uint64 sectorId, int64 deadline, int64 partition) external { + SectorInfo storage sectorInfo = sectors[sectorId]; + // a sector cannot have been marked faulty unless it is already in the deal + require(sectorInfo.failed == 1, SectorNotFailed(sectorId)); + require( + FVMSector.validateSectorStatus(PROVIDER, sectorId, SectorStatus.Active, deadline, partition), + SectorNotActive(sectorId) + ); + + sectorInfo.failed = 0; + + if (--info.faultedSectorCount == 0) { + info.settledEpoch = uint64(block.number); + } + } + + function terminate(address recipient, uint64 provider, address receiver) internal { + // if termination is caused by a dead sector, the keeper gets the insurance + // otherwise, the insurance is paid to the order of the PROVIDER + IPoRepService(SERVICE).terminate(NONCE, RAIL_ID, provider, receiver); + payoutBounty(recipient, getInsuranceFunds()); + } + + // After healthy deal termination, the remainder of the insurance funds can be collected by the receiver in exchange for rail cleanup + function sweep(address recipient) external { + require(block.number > info.endEpoch, DealNotExpired()); + amortize(); + terminate(recipient, PROVIDER, msg.sender); + } +} diff --git a/service_contracts/src/PoRepService.sol b/service_contracts/src/PoRepService.sol new file mode 100644 index 00000000..4e31ffc4 --- /dev/null +++ b/service_contracts/src/PoRepService.sol @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +pragma solidity ^0.8.30; + +import {CalldataUtils, CalldataSlice} from "@fvm-solidity/CalldataUtils.sol"; +import {FVMAddress} from "@fvm-solidity/FVMAddress.sol"; +import {CBOR_CODEC} from "@fvm-solidity/FVMCodec.sol"; +import {SECTOR_CONTENT_CHANGED} from "@fvm-solidity/FVMMethod.sol"; +import { + FVMSectorContentChanged, + PieceChangeIter, + SectorChangesHeader, + SectorContentChangedReturn, + SectorReturn +} from "@fvm-solidity/FVMSectorContentChanged.sol"; +import {FVMActor} from "@fvm-solidity/FVMActor.sol"; +import {FVMMiner} from "@fvm-solidity/FVMMiner.sol"; +import {FilecoinPayV1, IValidator} from "@fws-payments/FilecoinPayV1.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {LibRLP} from "@solady/utils/LibRLP.sol"; +import {LibClone} from "@solady/utils/LibClone.sol"; +import {IPoRepService, PoRepDeal} from "./PoRepDeal.sol"; + +error Unauthorized(address caller); +error TerminationForbidden(); + +contract PoRepPayee { + using FVMActor for address; + using FVMMiner for uint64; + + uint64 public immutable MINER; + + constructor() { + MINER = PoRepService(msg.sender).getMiner(); + } + + // Reading owner sounds like it should be `view`, but the precompile will revert if you staticcall this method + function owner() public returns (uint64) { + return MINER.getOwnerActorId(); + } + + function sudo(address payable, bytes calldata) external payable returns (bytes memory) { + require(msg.sender.getActorId() == owner(), Unauthorized(msg.sender)); + assembly ("memory-safe") { + let insize := calldataload(68) + calldatacopy(0, 100, insize) + let success := call(gas(), calldataload(4), callvalue(), 0, insize, 0, 0) + mstore(0, 32) + mstore(32, returndatasize()) + returndatacopy(64, 0, returndatasize()) + if success { return(0, add(64, returndatasize())) } + revert(0, add(64, returndatasize())) + } + } +} + +contract PoRepService is IPoRepService, IValidator { + using FVMAddress for address; + using FVMMiner for uint64; + using FVMSectorContentChanged for uint256; + using CalldataUtils for CalldataSlice; + using LibClone for bytes32; + using LibRLP for address; + + error ForbiddenMethod(uint64 method); + error NotMiner(uint64 provider); + + event DealCreated(address indexed client, uint64 indexed provider, address deal); + + FilecoinPayV1 private immutable PAYMENTS; + + constructor(FilecoinPayV1 payments) { + PAYMENTS = payments; + } + + function getMiner() external view returns (uint64 payee) { + assembly ("memory-safe") { + payee := tload(0) + } + } + + function setMiner(uint64 payee) internal { + assembly ("memory-safe") { + tstore(0, payee) + } + } + + bytes32 constant RECEIVER_INITCODE_HASH = keccak256(type(PoRepPayee).creationCode); + + function getReceiverAddress(uint64 provider) public view returns (address receiver) { + receiver = RECEIVER_INITCODE_HASH.predictDeterministicAddress(bytes32(uint256(provider)), address(this)); + } + + uint64 private nonce; + + function authenticateDeal(uint64 nonce) internal view { + require(msg.sender == address(this).computeAddress(nonce), Unauthorized(msg.sender)); + } + + function createReceiver(uint64 provider) public returns (address receiver) { + receiver = getReceiverAddress(provider); + if (receiver.code.length == 0) { + ++nonce; + setMiner(provider); + new PoRepPayee{salt: bytes32(uint256(provider))}(); + } + } + + function createDeal( + address client, + uint64 provider, + IERC20 token, + uint256 tokensPerBytePerEpoch, + uint64 dealEndEpoch, + uint256 insuranceBps + ) external returns (address deal) { + require(provider.isMiner(), NotMiner(provider)); + + address receiver = createReceiver(provider); + ++nonce; + deal = address(this).computeAddress(nonce); + uint256 railId = PAYMENTS.createRail(token, client, receiver, address(this), insuranceBps, deal); + new PoRepDeal( + address(this), client, provider, PAYMENTS, railId, token, tokensPerBytePerEpoch, dealEndEpoch, nonce + ); + emit DealCreated(client, provider, deal); + } + + function handle_filecoin_method(uint64 method, uint64, bytes calldata) + public + returns (uint32 exitCode, uint64 returnDataCodec, bytes memory returnData) + { + require(method == SECTOR_CONTENT_CHANGED, ForbiddenMethod(method)); + // The minerActor must match up with the deal + uint64 minerActor = msg.sender.safeActorId(); + + uint256 numSectors; + uint256 iter; + (numSectors, iter) = FVMSectorContentChanged.readParamsHeader(); + + SectorContentChangedReturn memory ret; + ret.sectors = new SectorReturn[](numSectors); + SectorChangesHeader memory header; + PieceChangeIter memory piece; + for (uint256 i = 0; i < numSectors; i++) { + iter = iter.readSectorHeader(header); + // require(header.minimumCommitmentEpoch > 0); + FVMSectorContentChanged.initSectorReturn(ret.sectors[i], header.numPieces); + for (uint256 j = 0; j < header.numPieces; j++) { + iter = iter.readPiece(piece); + uint64 nonce = piece.payload.toUint64(); + address deal = address(this).computeAddress(nonce); + PoRepDeal(deal) + .pieceAdded( + minerActor, piece.digest, header.sector, uint64(header.minimumCommitmentEpoch), piece.paddedSize + ); + //PAYMENTS.modifyRailPayment(railId, 0, payment); + //PAYMENTS.modifyRailLockup(railId, 0, remaining); + FVMSectorContentChanged.accept(ret.sectors[i], j); + } + } + return (0, CBOR_CODEC, FVMSectorContentChanged.encodeReturn(ret)); + } + + function updateLockups(uint64 nonce, uint256 railId, uint256 payment, uint256 remaining) external { + authenticateDeal(nonce); + + if (payment > 0) { + PAYMENTS.modifyRailPayment(railId, 0, payment); + } + PAYMENTS.modifyRailLockup(railId, 0, remaining); + } + + function terminate(uint64 nonce, uint256 railId, uint64 provider, address receiver) external { + authenticateDeal(nonce); + if (provider > 0) { + require(getReceiverAddress(provider) == receiver, Unauthorized(receiver)); + } + PAYMENTS.terminateRail(railId); + PAYMENTS.settleRail(railId, block.number); + } + + /** + * IValidator + */ + function validatePayment( + uint256, // railId + uint256 proposedAmount, // the epoch up to and including which the rail has already been settled + uint256, // fromEpoch + uint256 toEpoch, + uint256 // rate + ) + external + pure + returns (ValidationResult memory result) + { + result.modifiedAmount = proposedAmount; + result.settleUpto = toEpoch; + } + + function railTerminated( + uint256, + address terminator, + uint256 /*endEpoch*/ + ) + external + view + { + // require(msg.sender == address(PAYMENTS)); + require(terminator == address(this), TerminationForbidden()); + } +} diff --git a/service_contracts/src/ProviderIdSet.sol b/service_contracts/src/ProviderIdSet.sol index 6e41be1e..988cfab4 100644 --- a/service_contracts/src/ProviderIdSet.sol +++ b/service_contracts/src/ProviderIdSet.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT pragma solidity ^0.8.30; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; diff --git a/service_contracts/test/PoRepDealActivation.t.sol b/service_contracts/test/PoRepDealActivation.t.sol new file mode 100644 index 00000000..e0554c94 --- /dev/null +++ b/service_contracts/test/PoRepDealActivation.t.sol @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +pragma solidity ^0.8.30; + +import {FilecoinPayV1} from "@fws-payments/FilecoinPayV1.sol"; +import {PoRepDeal} from "../src/PoRepDeal.sol"; +import {PoRepPayee, PoRepService} from "../src/PoRepService.sol"; +import {FVMMinerActor} from "@fvm-solidity/mocks/FVMMinerActor.sol"; +import {MockFVMTest} from "@fvm-solidity/mocks/MockFVMTest.sol"; +import {PieceChange, SectorChanges, SectorContentChangedParams} from "@fvm-solidity/FVMSectorContentChanged.sol"; +import {SectorStatus} from "@fvm-solidity/FVMSector.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +IERC20 constant NATIVE_TOKEN = IERC20(address(0)); + +contract PoRepDealActivationTest is MockFVMTest { + uint64 constant MINER_ID = 42; + // 180 days at 30 seconds per epoch + uint64 constant MIN_COMMITMENT_EPOCHS = uint64(180 days / 30); + // 32 GB and 64 GB padded piece sizes + uint64 constant SIZE_32GB = 32 * 1024 * 1024 * 1024; + uint64 constant SIZE_64GB = 64 * 1024 * 1024 * 1024; + // 1 attoFIL per byte per epoch + uint256 constant RATE = 1; + + bytes constant COMMP_CID_1 = hex"0181e203922020cdf33e17483f8397390b0a963ded6e34a18f2fce6daa671716057f905f645b36"; + bytes32 constant COMMP_DIGEST_1 = 0xcdf33e17483f8397390b0a963ded6e34a18f2fce6daa671716057f905f645b36; + FilecoinPayV1 payments; + PoRepService service; + FVMMinerActor miner; + address client; + + function setUp() public override { + super.setUp(); + client = makeAddr("client"); + vm.deal(client, 1000 ether); + + payments = new FilecoinPayV1(); + service = new PoRepService(payments); + miner = mockMiner(MINER_ID); + + // Approve the service as rail operator for the client on native token. + // Allowances are sized for a single 64 GB activation over the minimum commitment duration. + uint256 maxRate = uint256(SIZE_64GB) * RATE; + uint256 maxLockup = maxRate * MIN_COMMITMENT_EPOCHS; + vm.prank(client); + payments.setOperatorApproval( + IERC20(address(0)), address(service), true, maxRate, maxLockup, MIN_COMMITMENT_EPOCHS + ); + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + /// @dev Scan CREATE nonces to find the one that produced `created` from `deployer`. + function _findDealNonce(address deployer, address created) internal pure returns (uint64) { + for (uint64 n = 1; n <= 20; n++) { + if (vm.computeCreateAddress(deployer, n) == created) return n; + } + revert("could not find deal nonce"); + } + + function _predictDealAddress(uint64 provider) internal view returns (address) { + uint64 nextNonce = uint64(vm.getNonce(address(service))); + if (service.getReceiverAddress(provider).code.length == 0) { + nextNonce++; + } + return vm.computeCreateAddress(address(service), nextNonce); + } + + function _createDeal() internal returns (PoRepDeal deal, uint64 endEpoch) { + endEpoch = uint64(block.number) + MIN_COMMITMENT_EPOCHS; + vm.expectEmit(address(service)); + emit PoRepService.DealCreated(client, MINER_ID, _predictDealAddress(MINER_ID)); + deal = PoRepDeal(service.createDeal(client, MINER_ID, NATIVE_TOKEN, RATE, endEpoch, 0)); + } + + function _authorize(PoRepDeal deal, bytes32 digest) internal { + bytes32[] memory cidHashes = new bytes32[](1); + cidHashes[0] = digest; + vm.prank(client); + deal.addPieces(cidHashes); + } + + function _activate(PoRepDeal deal, bytes memory cid, uint64 paddedSize, uint64 sectorId, int64 minEpoch) internal { + uint64 nonce = _findDealNonce(address(service), address(deal)); + + PieceChange[] memory pieces = new PieceChange[](1); + // payload is read back via CalldataUtils.toUint64(), which expects exactly 8 raw big-endian bytes + pieces[0] = PieceChange({data: cid, size: paddedSize, payload: abi.encodePacked(nonce)}); + SectorChanges[] memory sectorChanges = new SectorChanges[](1); + sectorChanges[0] = SectorChanges({sector: sectorId, minimumCommitmentEpoch: minEpoch, added: pieces}); + miner.callSectorContentChanged(address(service), SectorContentChangedParams({sectors: sectorChanges})); + } + + function _deposit(uint256 amount) internal { + vm.prank(client); + payments.deposit{value: amount}(NATIVE_TOKEN, client, amount); + } + + function _setupActiveDeal() internal returns (PoRepDeal deal, uint64 endEpoch) { + _deposit(uint256(SIZE_32GB) * RATE * MIN_COMMITMENT_EPOCHS); + (deal, endEpoch) = _createDeal(); + _authorize(deal, COMMP_DIGEST_1); + _activate(deal, COMMP_CID_1, SIZE_32GB, 1, int64(endEpoch)); + } + + function _makeSectorFaulty(PoRepDeal deal, uint64 sectorId) internal { + miner.mockSectorStatus(sectorId, SectorStatus.Faulty); + miner.mockSectorLocation(sectorId, 0, 0); + deal.sectorFaulty(sectorId, 0, 0, address(this)); + } + + // ------------------------------------------------------------------------- + // addPieces + // ------------------------------------------------------------------------- + + function testAddPiecesUnauthorized() public { + (PoRepDeal deal,) = _createDeal(); + bytes32[] memory digests = new bytes32[](1); + digests[0] = COMMP_DIGEST_1; + vm.expectRevert(PoRepDeal.Unauthorized.selector); + deal.addPieces(digests); + } + + function testAddPiecesDuplicate() public { + (PoRepDeal deal,) = _createDeal(); + _authorize(deal, COMMP_DIGEST_1); + bytes32[] memory digests = new bytes32[](1); + digests[0] = COMMP_DIGEST_1; + vm.prank(client); + vm.expectRevert(abi.encodeWithSelector(PoRepDeal.PieceAlreadyAuthorized.selector, COMMP_DIGEST_1)); + deal.addPieces(digests); + } + + function testPieceAddedWrongProvider() public { + (PoRepDeal deal, uint64 endEpoch) = _createDeal(); + vm.prank(address(service)); + vm.expectRevert(abi.encodeWithSelector(PoRepDeal.WrongProvider.selector, uint64(MINER_ID + 1))); + deal.pieceAdded(MINER_ID + 1, COMMP_DIGEST_1, 1, endEpoch, SIZE_32GB); + } + + function testPieceAddedCommitmentTooShort() public { + (PoRepDeal deal, uint64 endEpoch) = _createDeal(); + vm.prank(address(service)); + vm.expectRevert( + abi.encodeWithSelector(PoRepDeal.CommitmentTooShort.selector, uint64(endEpoch - 1), uint64(endEpoch)) + ); + deal.pieceAdded(MINER_ID, COMMP_DIGEST_1, 1, endEpoch - 1, SIZE_32GB); + } + + function testPieceAddedOnlyService() public { + (PoRepDeal deal, uint64 endEpoch) = _createDeal(); + vm.expectRevert(PoRepDeal.Unauthorized.selector); + deal.pieceAdded(MINER_ID, COMMP_DIGEST_1, 1, endEpoch, SIZE_32GB); + } + + function testPieceAddedUnauthorized() public { + (PoRepDeal deal, uint64 endEpoch) = _createDeal(); + vm.prank(address(service)); + vm.expectRevert(abi.encodeWithSelector(PoRepDeal.PieceNotAuthorized.selector, COMMP_DIGEST_1)); + deal.pieceAdded(MINER_ID, COMMP_DIGEST_1, 1, endEpoch, SIZE_32GB); + } + + /// The same piece (same CID hash) cannot be activated twice in the same deal. + function testCannotActivatePieceTwice() public { + uint256 required = uint256(SIZE_32GB) * RATE * MIN_COMMITMENT_EPOCHS; + _deposit(required); + + (PoRepDeal deal, uint64 endEpoch) = _createDeal(); + _authorize(deal, COMMP_DIGEST_1); + + int64 minEpoch = int64(endEpoch); + + // First activation succeeds. + _activate(deal, COMMP_CID_1, SIZE_32GB, 1, minEpoch); + + // Second activation of the same piece (even into a different sector) must revert because + // PoRepDeal.pieceAdded requires PieceStatus.AUTHORIZED but it is now ACTIVE. + uint64 nonce = _findDealNonce(address(service), address(deal)); + PieceChange[] memory pieces = new PieceChange[](1); + pieces[0] = PieceChange({data: COMMP_CID_1, size: SIZE_32GB, payload: abi.encodePacked(nonce)}); + SectorChanges[] memory sectorChanges = new SectorChanges[](1); + sectorChanges[0] = SectorChanges({sector: 2, minimumCommitmentEpoch: minEpoch, added: pieces}); + + vm.expectRevert(abi.encodeWithSelector(PoRepDeal.PieceNotAuthorized.selector, COMMP_DIGEST_1)); + miner.callSectorContentChanged(address(service), SectorContentChangedParams({sectors: sectorChanges})); + } + + /// A client holding exactly size × rate × duration can successfully lock up and settle a 32 GB deal. + function testExactBalanceSufficient() public { + uint256 required = uint256(SIZE_32GB) * RATE * MIN_COMMITMENT_EPOCHS; + _deposit(required); + + (PoRepDeal deal, uint64 endEpoch) = _createDeal(); + _authorize(deal, COMMP_DIGEST_1); + _activate(deal, COMMP_CID_1, SIZE_32GB, 1, int64(endEpoch)); + + deal.amortize(); + vm.roll(endEpoch); + deal.amortize(); + + vm.roll(endEpoch + 1); + deal.amortize(); + } + + /// After amortizing elapsed earnings into the receiver, the miner owner can sudo-withdraw them. + function testSudoWithdrawAfterAmortize() public { + uint64 OWNER_ID = 151; + address owner = makeAddr("owner"); + ACTOR_PRECOMPILE.mockResolveAddress(owner, OWNER_ID); + miner.mockOwner(OWNER_ID); + + (PoRepDeal deal,) = _setupActiveDeal(); + + uint64 elapsed = 1000; + vm.roll(block.number + elapsed); + deal.amortize(); + + address receiver = service.getReceiverAddress(MINER_ID); + (uint256 receiverFunds,,,) = payments.accounts(NATIVE_TOKEN, receiver); + assertGt(receiverFunds, 0); + + bytes memory withdrawCalldata = + abi.encodeCall(payments.withdrawTo, (NATIVE_TOKEN, payable(owner), receiverFunds)); + vm.prank(owner); + PoRepPayee(receiver).sudo(payable(address(payments)), withdrawCalldata); + + (uint256 remainingFunds,,,) = payments.accounts(NATIVE_TOKEN, receiver); + assertEq(remainingFunds, 0); + assertEq(owner.balance, receiverFunds); + } +} diff --git a/service_contracts/test/PoRepDealExtend.t.sol b/service_contracts/test/PoRepDealExtend.t.sol new file mode 100644 index 00000000..4c9e65d4 --- /dev/null +++ b/service_contracts/test/PoRepDealExtend.t.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +pragma solidity ^0.8.30; + +import {FilecoinPayV1} from "@fws-payments/FilecoinPayV1.sol"; +import {PoRepDeal} from "../src/PoRepDeal.sol"; +import {PoRepService} from "../src/PoRepService.sol"; +import {FVMMinerActor} from "@fvm-solidity/mocks/FVMMinerActor.sol"; +import {MockFVMTest} from "@fvm-solidity/mocks/MockFVMTest.sol"; +import {PieceChange, SectorChanges, SectorContentChangedParams} from "@fvm-solidity/FVMSectorContentChanged.sol"; +import {SectorStatus} from "@fvm-solidity/FVMSector.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +IERC20 constant NATIVE_TOKEN = IERC20(address(0)); + +contract PoRepDealExtendTest is MockFVMTest { + uint64 constant MINER_ID = 42; + uint64 constant SECTOR_ID = 1; + int64 constant DEADLINE = 3; + int64 constant PARTITION = 0; + uint64 constant DAYS_OF_EPOCHS = uint64(1 days / 30); + uint64 constant DURATION = 180 * DAYS_OF_EPOCHS; + uint64 constant EXTENSION = 42 * DAYS_OF_EPOCHS; + uint64 constant SIZE = 32 * 1024 * 1024 * 1024; + uint256 constant RATE = 1; + + bytes constant COMMP_CID_1 = hex"0181e203922020cdf33e17483f8397390b0a963ded6e34a18f2fce6daa671716057f905f645b38"; + bytes32 constant COMMP_DIGEST_1 = 0xcdf33e17483f8397390b0a963ded6e34a18f2fce6daa671716057f905f645b38; + + FilecoinPayV1 payments; + PoRepService service; + FVMMinerActor miner; + address client; + PoRepDeal poRepDeal; + uint64 endEpoch; + + function setUp() public override { + super.setUp(); + client = makeAddr("client"); + vm.deal(client, 1000 ether); + + payments = new FilecoinPayV1(); + service = new PoRepService(payments); + miner = mockMiner(MINER_ID); + + uint256 maxRate = uint256(SIZE) * RATE; + // maxLockup must cover the initial DURATION lockup plus the EXTENSION re-lock + uint256 maxLockup = maxRate * (DURATION + EXTENSION); + vm.prank(client); + payments.setOperatorApproval(NATIVE_TOKEN, address(service), true, maxRate, maxLockup, DURATION + EXTENSION); + + // deposit covers initial lockup (DURATION) plus funds needed to re-lock after settling EXTENSION epochs + uint256 deposit = uint256(SIZE) * RATE * (DURATION + EXTENSION); + vm.prank(client); + payments.deposit{value: deposit}(NATIVE_TOKEN, client, deposit); + + endEpoch = uint64(block.number) + DURATION; + vm.expectEmit(address(service)); + emit PoRepService.DealCreated(client, MINER_ID, _predictDealAddress(MINER_ID)); + poRepDeal = PoRepDeal(service.createDeal(client, MINER_ID, NATIVE_TOKEN, RATE, endEpoch, 0)); + + bytes32[] memory cidHashes = new bytes32[](1); + cidHashes[0] = COMMP_DIGEST_1; + vm.prank(client); + poRepDeal.addPieces(cidHashes); + + miner.mockSector(SECTOR_ID, SectorStatus.Active, DEADLINE, PARTITION, endEpoch); + + uint64 nonce = _findDealNonce(address(service), address(poRepDeal)); + PieceChange[] memory pieces = new PieceChange[](1); + pieces[0] = PieceChange({data: COMMP_CID_1, size: SIZE, payload: abi.encodePacked(nonce)}); + SectorChanges[] memory sectorChanges = new SectorChanges[](1); + sectorChanges[0] = SectorChanges({sector: SECTOR_ID, minimumCommitmentEpoch: int64(endEpoch), added: pieces}); + miner.callSectorContentChanged(address(service), SectorContentChangedParams({sectors: sectorChanges})); + } + + function _findDealNonce(address deployer, address created) internal pure returns (uint64) { + for (uint64 n = 1; n <= 20; n++) { + if (vm.computeCreateAddress(deployer, n) == created) return n; + } + revert("could not find poRepDeal nonce"); + } + + function _predictDealAddress(uint64 provider) internal view returns (address) { + uint64 nextNonce = uint64(vm.getNonce(address(service))); + if (service.getReceiverAddress(provider).code.length == 0) { + nextNonce++; + } + return vm.computeCreateAddress(address(service), nextNonce); + } + + function testExtend() public { + vm.roll(vm.getBlockNumber() + EXTENSION); + + vm.prank(client); + poRepDeal.extend(EXTENSION); + + (, uint64 newEndEpoch,,) = poRepDeal.info(); + assertEq(newEndEpoch, endEpoch + EXTENSION); + + // poRepDeal is still live past the original end epoch + vm.roll(endEpoch + 1); + vm.expectRevert(); + poRepDeal.sweep(address(this)); + } + + function testExtendRevertsAfterExpiry() public { + vm.roll(endEpoch); + vm.prank(client); + vm.expectRevert(PoRepDeal.DealExpired.selector); + poRepDeal.extend(EXTENSION); + } + + function testExtendRevertsWhenFaulted() public { + vm.roll(vm.getBlockNumber() + DAYS_OF_EPOCHS); + miner.mockSectorStatus(SECTOR_ID, SectorStatus.Faulty); + poRepDeal.sectorFaulty(SECTOR_ID, DEADLINE, PARTITION, address(this)); + + vm.prank(client); + vm.expectRevert(PoRepDeal.DealFaulted.selector); + poRepDeal.extend(EXTENSION); + } + + function testExtendRevertsIfNotClient() public { + vm.expectRevert(PoRepDeal.Unauthorized.selector); + poRepDeal.extend(EXTENSION); + } + + function testSweepRevertsBeforeExpiry() public { + vm.expectRevert(PoRepDeal.DealNotExpired.selector); + poRepDeal.sweep(address(this)); + } +} diff --git a/service_contracts/test/PoRepDealSectorStatus.t.sol b/service_contracts/test/PoRepDealSectorStatus.t.sol new file mode 100644 index 00000000..028adf1b --- /dev/null +++ b/service_contracts/test/PoRepDealSectorStatus.t.sol @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +pragma solidity ^0.8.30; + +import {FilecoinPayV1} from "@fws-payments/FilecoinPayV1.sol"; +import {Errors} from "@fws-payments/Errors.sol"; +import {PoRepDeal} from "../src/PoRepDeal.sol"; +import {PoRepPayee, PoRepService, TerminationForbidden, Unauthorized} from "../src/PoRepService.sol"; +import {FVMMinerActor} from "@fvm-solidity/mocks/FVMMinerActor.sol"; +import {MockFVMTest} from "@fvm-solidity/mocks/MockFVMTest.sol"; +import {PieceChange, SectorChanges, SectorContentChangedParams} from "@fvm-solidity/FVMSectorContentChanged.sol"; +import {FVMSector, NO_DEADLINE, NO_PARTITION, SectorStatus} from "@fvm-solidity/FVMSector.sol"; +import {USR_NOT_FOUND} from "@fvm-solidity/FVMErrors.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +IERC20 constant NATIVE_TOKEN = IERC20(address(0)); + +contract PoRepDealSectorStatusTest is MockFVMTest { + uint64 constant MINER_ID = 142; + uint64 constant OWNER_ID = 156; + uint64 constant SECTOR_ID = 1; + int64 constant DEADLINE = 3; + int64 constant PARTITION = 0; + uint64 constant DAYS_OF_EPOCHS = uint64(1 days / 30); + uint64 constant DURATION = 180 * DAYS_OF_EPOCHS; + uint64 constant FAULT_MAX_AGE = 42 * DAYS_OF_EPOCHS; + uint64 constant SIZE = 32 * 1024 * 1024 * 1024; + uint256 constant RATE = 10000; + uint256 constant INSURANCE_BIPS = 50; + + bytes constant COMMP_CID_1 = hex"0181e203922020cdf33e17483f8397390b0a963ded6e34a18f2fce6daa671716057f905f645b36"; + bytes32 constant COMMP_DIGEST_1 = 0xcdf33e17483f8397390b0a963ded6e34a18f2fce6daa671716057f905f645b36; + bytes constant COMMP_CID_2 = hex"0181e203922020adf33e17483f8397390b0a963ded6e34a18f2fce6daa671716057f905f645b36"; + bytes32 constant COMMP_DIGEST_2 = 0xadf33e17483f8397390b0a963ded6e34a18f2fce6daa671716057f905f645b36; + uint64 constant SECTOR_ID_2 = 2; + + FilecoinPayV1 payments; + PoRepService service; + FVMMinerActor miner; + address client; + PoRepDeal poRepDeal; + uint64 endEpoch; + PoRepPayee payee; + + function setUp() public override { + super.setUp(); + client = makeAddr("client"); + vm.deal(client, 1000 ether); + + payments = new FilecoinPayV1(); + service = new PoRepService(payments); + miner = mockMiner(MINER_ID); + ACTOR_PRECOMPILE.mockResolveAddress(address(this), OWNER_ID); + ACTOR_PRECOMPILE.mockResolveAddress(address(miner), MINER_ID); + miner.mockOwner(OWNER_ID); + + uint256 maxRate = uint256(SIZE) * RATE; + uint256 maxLockup = maxRate * DURATION; + vm.prank(client); + payments.setOperatorApproval(IERC20(address(0)), address(service), true, maxRate, maxLockup, DURATION); + + uint256 required = uint256(SIZE) * RATE * DURATION; + vm.prank(client); + payments.deposit{value: required}(NATIVE_TOKEN, client, required); + + endEpoch = uint64(block.number) + DURATION; + vm.expectEmit(address(service)); + emit PoRepService.DealCreated(client, MINER_ID, _predictDealAddress(MINER_ID)); + poRepDeal = PoRepDeal(service.createDeal(client, MINER_ID, NATIVE_TOKEN, RATE, endEpoch, INSURANCE_BIPS)); + payee = PoRepPayee(service.getReceiverAddress(MINER_ID)); + + bytes32[] memory cidHashes = new bytes32[](1); + cidHashes[0] = COMMP_DIGEST_1; + vm.prank(client); + poRepDeal.addPieces(cidHashes); + + // Mock sector as Active with location before activation + miner.mockSector(SECTOR_ID, SectorStatus.Active, DEADLINE, PARTITION, endEpoch); + + uint64 nonce = _findDealNonce(address(service), address(poRepDeal)); + PieceChange[] memory pieces = new PieceChange[](1); + pieces[0] = PieceChange({data: COMMP_CID_1, size: SIZE, payload: abi.encodePacked(nonce)}); + SectorChanges[] memory sectorChanges = new SectorChanges[](1); + sectorChanges[0] = SectorChanges({sector: SECTOR_ID, minimumCommitmentEpoch: int64(endEpoch), added: pieces}); + miner.callSectorContentChanged(address(service), SectorContentChangedParams({sectors: sectorChanges})); + } + + function _findDealNonce(address deployer, address created) internal pure returns (uint64) { + for (uint64 n = 1; n <= 20; n++) { + if (vm.computeCreateAddress(deployer, n) == created) return n; + } + revert("could not find deal nonce"); + } + + function _predictDealAddress(uint64 provider) internal view returns (address) { + uint64 nextNonce = uint64(vm.getNonce(address(service))); + if (service.getReceiverAddress(provider).code.length == 0) { + nextNonce++; + } + return vm.computeCreateAddress(address(service), nextNonce); + } + + function assertRailFinalized() internal { + uint256 railId = poRepDeal.RAIL_ID(); + vm.expectRevert(abi.encodeWithSelector(Errors.RailInactiveOrSettled.selector, railId)); + payments.getRail(railId); + } + + address constant RECIPIENT = address(0x4141414141414141414141414141414141414141); + address constant SWEEPER = address(0x4242424242424242424242424242424242424242); + + // tests the case where nobody flagged the fault but someone did flag the expiry + function testSectorExpiredAfterActivation() public { + vm.roll(vm.getBlockNumber() + FAULT_MAX_AGE); + + miner.mockSectorStatus(SECTOR_ID, SectorStatus.Dead); + poRepDeal.sectorExpired(SECTOR_ID, DEADLINE, PARTITION, RECIPIENT); + + assertEq(RECIPIENT.balance, FAULT_MAX_AGE * SIZE * INSURANCE_BIPS * 199 / 200); + + (uint256 paid,,,) = payments.accounts(NATIVE_TOKEN, address(payee)); + assertEq(paid, FAULT_MAX_AGE * SIZE * RATE * 199 / 200 * (10000 - INSURANCE_BIPS) / 10000); + assertRailFinalized(); + } + + // tests recovery + function testSectorRecoverFaultyAfterActivation() public { + vm.roll(vm.getBlockNumber() + 3 * DAYS_OF_EPOCHS); + + miner.mockSectorStatus(SECTOR_ID, SectorStatus.Faulty); + poRepDeal.sectorFaulty(SECTOR_ID, DEADLINE, PARTITION, RECIPIENT); + + uint256 bounty = RECIPIENT.balance; + assertEq(bounty, 3 * DAYS_OF_EPOCHS * SIZE * INSURANCE_BIPS * 199 / 400); + + vm.roll(vm.getBlockNumber() + FAULT_MAX_AGE / 2); + + miner.mockSector(SECTOR_ID, SectorStatus.Active, DEADLINE, PARTITION, endEpoch); + poRepDeal.sectorRecovered(SECTOR_ID, DEADLINE, PARTITION); + + vm.roll(vm.getBlockNumber() + DURATION); + + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, address(this))); + poRepDeal.sweep(SWEEPER); + + ACTOR_PRECOMPILE.mockResolveAddress(SWEEPER, 999); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, SWEEPER)); + vm.prank(SWEEPER); + payee.sudo(payable(address(poRepDeal)), abi.encodeWithSelector(PoRepDeal.sweep.selector, SWEEPER)); + + payee.sudo(payable(address(poRepDeal)), abi.encodeWithSelector(PoRepDeal.sweep.selector, SWEEPER)); + + uint256 paidEpochs = DURATION - FAULT_MAX_AGE / 2; + assertEq(SWEEPER.balance, paidEpochs * SIZE * INSURANCE_BIPS * 199 / 200 - bounty); + + (uint256 paid,,,) = payments.accounts(NATIVE_TOKEN, address(payee)); + assertEq(paid, paidEpochs * SIZE * RATE * 199 / 200 * (10000 - INSURANCE_BIPS) / 10000); + assertRailFinalized(); + } + + function testBothSectorsRecovered() public { + // Extend approval and deposit to cover two sectors + vm.prank(client); + payments.setOperatorApproval( + NATIVE_TOKEN, + address(service), + true, + uint256(2 * SIZE) * RATE, + uint256(2 * SIZE) * RATE * DURATION, + DURATION + ); + vm.prank(client); + payments.deposit{value: uint256(SIZE) * RATE * DURATION}(NATIVE_TOKEN, client, uint256(SIZE) * RATE * DURATION); + + // Authorize and activate second piece in a second sector + bytes32[] memory cidHashes = new bytes32[](1); + cidHashes[0] = COMMP_DIGEST_2; + vm.prank(client); + poRepDeal.addPieces(cidHashes); + + uint64 nonce = _findDealNonce(address(service), address(poRepDeal)); + miner.mockSector(SECTOR_ID_2, SectorStatus.Active, DEADLINE, PARTITION, endEpoch); + PieceChange[] memory pieces = new PieceChange[](1); + pieces[0] = PieceChange({data: COMMP_CID_2, size: SIZE, payload: abi.encodePacked(nonce)}); + SectorChanges[] memory sectorChanges = new SectorChanges[](1); + sectorChanges[0] = SectorChanges({sector: SECTOR_ID_2, minimumCommitmentEpoch: int64(endEpoch), added: pieces}); + miner.callSectorContentChanged(address(service), SectorContentChangedParams({sectors: sectorChanges})); + + vm.roll(vm.getBlockNumber() + 3 * DAYS_OF_EPOCHS); + + // First fault pays bounty; second does not + miner.mockSectorStatus(SECTOR_ID, SectorStatus.Faulty); + poRepDeal.sectorFaulty(SECTOR_ID, DEADLINE, PARTITION, RECIPIENT); + uint256 bounty = RECIPIENT.balance; + assertGt(bounty, 0); + + miner.mockSectorStatus(SECTOR_ID_2, SectorStatus.Faulty); + poRepDeal.sectorFaulty(SECTOR_ID_2, DEADLINE, PARTITION, RECIPIENT); + assertEq(RECIPIENT.balance, bounty); + + vm.roll(vm.getBlockNumber() + FAULT_MAX_AGE / 2); + + // First recovery leaves the deal faulted + miner.mockSector(SECTOR_ID, SectorStatus.Active, DEADLINE, PARTITION, endEpoch); + poRepDeal.sectorRecovered(SECTOR_ID, DEADLINE, PARTITION); + (,, uint32 faultedCount,) = poRepDeal.info(); + assertEq(faultedCount, 1); + + // Second recovery fully restores the deal + miner.mockSector(SECTOR_ID_2, SectorStatus.Active, DEADLINE, PARTITION, endEpoch); + poRepDeal.sectorRecovered(SECTOR_ID_2, DEADLINE, PARTITION); + (,, faultedCount,) = poRepDeal.info(); + assertEq(faultedCount, 0); + + vm.roll(endEpoch + 1); + payee.sudo(payable(address(poRepDeal)), abi.encodeWithSelector(PoRepDeal.sweep.selector, SWEEPER)); + assertRailFinalized(); + } + + // tests expiration after fault + function testSectorExpiredAfterFaulty() public { + vm.roll(vm.getBlockNumber() + 3 * DAYS_OF_EPOCHS); + + miner.mockSectorStatus(SECTOR_ID, SectorStatus.Faulty); + poRepDeal.sectorFaulty(SECTOR_ID, DEADLINE, PARTITION, RECIPIENT); + + vm.roll(vm.getBlockNumber() + FAULT_MAX_AGE); + + uint256 balanceBefore = RECIPIENT.balance; + assertEq(balanceBefore, 3 * DAYS_OF_EPOCHS * SIZE * INSURANCE_BIPS * 199 / 400); + + miner.mockSectorStatus(SECTOR_ID, SectorStatus.Dead); + poRepDeal.sectorExpired(SECTOR_ID, DEADLINE, PARTITION, RECIPIENT); + + assertEq(RECIPIENT.balance, balanceBefore * 2); + + (uint256 paid,,,) = payments.accounts(NATIVE_TOKEN, address(payee)); + assertEq(paid, 3 * DAYS_OF_EPOCHS * SIZE * RATE * 199 / 200 * (10000 - INSURANCE_BIPS) / 10000); + assertRailFinalized(); + } + + function testSectorExpiredRevertsNotYetCompacted() public { + vm.expectRevert( + abi.encodeWithSelector(FVMSector.ValidateSectorStatusFailed.selector, int256(int32(USR_NOT_FOUND))) + ); + poRepDeal.sectorExpired(SECTOR_ID, NO_DEADLINE, NO_PARTITION, RECIPIENT); + } + + function testSectorExpiredRevertsIfStillActive() public { + vm.expectRevert(abi.encodeWithSelector(PoRepDeal.SectorNotDead.selector, SECTOR_ID)); + poRepDeal.sectorExpired(SECTOR_ID, DEADLINE, PARTITION, RECIPIENT); + } + + function testSectorFaultyRevertsIfStillActive() public { + vm.expectRevert(abi.encodeWithSelector(PoRepDeal.SectorNotFaulty.selector, uint64(SECTOR_ID))); + poRepDeal.sectorFaulty(SECTOR_ID, DEADLINE, PARTITION, RECIPIENT); + } + + function testSectorExpiredAfterDealEnd() public { + vm.roll(endEpoch); + vm.expectRevert(PoRepDeal.DealExpired.selector); + poRepDeal.sectorExpired(SECTOR_ID, DEADLINE, PARTITION, RECIPIENT); + } + + function testSectorExpiredNotInDeal() public { + vm.expectRevert(abi.encodeWithSelector(PoRepDeal.SectorNotInDeal.selector, uint64(99))); + poRepDeal.sectorExpired(99, DEADLINE, PARTITION, RECIPIENT); + } + + function testSectorFaultyAfterDealEnd() public { + vm.roll(endEpoch); + vm.expectRevert(PoRepDeal.DealExpired.selector); + poRepDeal.sectorFaulty(SECTOR_ID, DEADLINE, PARTITION, RECIPIENT); + } + + function testSectorFaultyNotInDeal() public { + vm.expectRevert(abi.encodeWithSelector(PoRepDeal.SectorNotInDeal.selector, uint64(99))); + poRepDeal.sectorFaulty(99, DEADLINE, PARTITION, RECIPIENT); + } + + function testSectorFaultyAlreadyFailed() public { + miner.mockSectorStatus(SECTOR_ID, SectorStatus.Faulty); + poRepDeal.sectorFaulty(SECTOR_ID, DEADLINE, PARTITION, RECIPIENT); + + vm.expectRevert(abi.encodeWithSelector(PoRepDeal.SectorAlreadyFailed.selector, uint64(SECTOR_ID))); + poRepDeal.sectorFaulty(SECTOR_ID, DEADLINE, PARTITION, RECIPIENT); + } + + function testSectorRecoveredNotFailed() public { + vm.expectRevert(abi.encodeWithSelector(PoRepDeal.SectorNotFailed.selector, uint64(SECTOR_ID))); + poRepDeal.sectorRecovered(SECTOR_ID, DEADLINE, PARTITION); + } + + function testSectorRecoveredStillFaulty() public { + miner.mockSectorStatus(SECTOR_ID, SectorStatus.Faulty); + poRepDeal.sectorFaulty(SECTOR_ID, DEADLINE, PARTITION, RECIPIENT); + + vm.expectRevert(abi.encodeWithSelector(PoRepDeal.SectorNotActive.selector, uint64(SECTOR_ID))); + poRepDeal.sectorRecovered(SECTOR_ID, DEADLINE, PARTITION); + } + + // ------------------------------------------------------------------------- + // authenticateDeal authorization tests + // ------------------------------------------------------------------------- + + function testUpdateLockupsPayeeReceiverUnauthorized() public { + uint64 dealNonce = _findDealNonce(address(service), address(poRepDeal)); + uint256 railId = poRepDeal.RAIL_ID(); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, address(payee))); + vm.prank(address(payee)); + service.updateLockups(dealNonce, railId, 0, 0); + } + + function testTerminatePayeeReceiverUnauthorized() public { + uint64 dealNonce = _findDealNonce(address(service), address(poRepDeal)); + uint256 railId = poRepDeal.RAIL_ID(); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, address(payee))); + vm.prank(address(payee)); + service.terminate(dealNonce, railId, uint64(0), address(0)); + } + + // The receiver is deployed via CREATE2, so its nonce slot (dealNonce-1) does NOT map to + // address(payee) under CREATE address derivation. These tests catch a regression where + // the receiver is changed from CREATE2 to CREATE, which would let the owner authenticate + // as a deal and manipulate rails. + function testUpdateLockupsReceiverNonceUnauthorized() public { + uint64 dealNonce = _findDealNonce(address(service), address(poRepDeal)); + uint256 railId = poRepDeal.RAIL_ID(); + uint64 receiverNonce = dealNonce - 1; + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, address(payee))); + vm.prank(address(payee)); + service.updateLockups(receiverNonce, railId, 0, 0); + } + + function testTerminateReceiverNonceUnauthorized() public { + uint64 dealNonce = _findDealNonce(address(service), address(poRepDeal)); + uint256 railId = poRepDeal.RAIL_ID(); + uint64 receiverNonce = dealNonce - 1; + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, address(payee))); + vm.prank(address(payee)); + service.terminate(receiverNonce, railId, uint64(0), address(0)); + } + + function testUpdateLockupsStrangerUnauthorized() public { + uint64 dealNonce = _findDealNonce(address(service), address(poRepDeal)); + uint256 railId = poRepDeal.RAIL_ID(); + address stranger = makeAddr("stranger"); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, stranger)); + vm.prank(stranger); + service.updateLockups(dealNonce, railId, 0, 0); + } + + function testTerminateStrangerUnauthorized() public { + uint64 dealNonce = _findDealNonce(address(service), address(poRepDeal)); + uint256 railId = poRepDeal.RAIL_ID(); + address stranger = makeAddr("stranger"); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, stranger)); + vm.prank(stranger); + service.terminate(dealNonce, railId, uint64(0), address(0)); + } + + function testSweepUnauthorized() public { + vm.roll(endEpoch + 1); + address stranger = makeAddr("stranger"); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, stranger)); + vm.prank(stranger); + poRepDeal.sweep(stranger); + } + + function testTerminateClientUnauthorized() public { + uint64 dealNonce = _findDealNonce(address(service), address(poRepDeal)); + uint256 railId = poRepDeal.RAIL_ID(); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, client)); + vm.prank(client); + service.terminate(dealNonce, railId, uint64(0), address(0)); + } + + function testClientCannotDirectlyTerminateRail() public { + uint256 railId = poRepDeal.RAIL_ID(); + vm.expectRevert(TerminationForbidden.selector); + vm.prank(client); + payments.terminateRail(railId); + } +} diff --git a/service_contracts/test/PoRepService.t.sol b/service_contracts/test/PoRepService.t.sol new file mode 100644 index 00000000..64ef78a0 --- /dev/null +++ b/service_contracts/test/PoRepService.t.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +pragma solidity ^0.8.30; + +import {FilecoinPayV1} from "@fws-payments/FilecoinPayV1.sol"; +import {PoRepPayee, PoRepService, Unauthorized} from "../src/PoRepService.sol"; +import {FVMActor} from "@fvm-solidity/FVMActor.sol"; +import {FVMMinerActor} from "@fvm-solidity/mocks/FVMMinerActor.sol"; +import {MockFVMTest} from "@fvm-solidity/mocks/MockFVMTest.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract RevertingTarget { + error TargetError(uint256 value); + + function run() external pure { + revert TargetError(42); + } +} + +contract PoRepPayeeTest is MockFVMTest { + using FVMActor for address; + + uint64 constant MINER_ID = 1643; + uint64 constant OWNER_ID = 151; + PoRepPayee payee; + + function setUp() public override { + super.setUp(); + + FVMMinerActor miner = mockMiner(MINER_ID); + ACTOR_PRECOMPILE.mockResolveAddress(address(this), OWNER_ID); + ACTOR_PRECOMPILE.mockResolveAddress(address(miner), MINER_ID); + miner.mockOwner(OWNER_ID); + + payee = new PoRepPayee(); + } + + function getMiner() external pure returns (uint64 payee) { + return MINER_ID; + } + + function testOwner() public { + assertEq(payee.MINER(), MINER_ID); + assertEq(payee.owner(), OWNER_ID); + assertEq(address(this).getActorId(), OWNER_ID); + } + + function testSudoUnauthorized() public { + address payable notOwner = payable(0x9999999999999999999999999999999999999999); + vm.deal(notOwner, 1); + ACTOR_PRECOMPILE.mockResolveAddress(notOwner, OWNER_ID + 1); + + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, notOwner)); + vm.prank(notOwner); + payee.sudo{value: 1}(notOwner, bytes("")); + } + + function testSudo() public { + address payable notOwner = payable(0x9999999999999999999999999999999999999999); + assertEq(notOwner.balance, 0); + payee.sudo{value: 1}(notOwner, bytes("")); + assertEq(notOwner.balance, 1); + } + + function testSudoForwardsInnerRevert() public { + RevertingTarget target = new RevertingTarget(); + bytes memory innerRevertData = abi.encodeWithSelector(RevertingTarget.TargetError.selector, uint256(42)); + vm.expectRevert(abi.encode(innerRevertData)); + payee.sudo(payable(address(target)), abi.encodeCall(RevertingTarget.run, ())); + } +} + +contract PoRepServiceTest is MockFVMTest { + FilecoinPayV1 payments; + PoRepService service; + + function setUp() public override { + super.setUp(); + + payments = new FilecoinPayV1(); + service = new PoRepService(payments); + } + + function testCreateReceiver() public { + uint64 minerId = 64; + + address receiver = service.getReceiverAddress(minerId); + assertEq(receiver.code.length, 0); + + address created = service.createReceiver(minerId); + assertEq(receiver, created); + assertNotEq(receiver.code.length, 0); + + assertEq(PoRepPayee(created).MINER(), minerId); + } + + function testCreateDealNotMiner() public { + uint64 notMinerId = 999; + vm.expectRevert(abi.encodeWithSelector(PoRepService.NotMiner.selector, notMinerId)); + service.createDeal(address(this), notMinerId, IERC20(address(0)), 1, uint64(block.number + 1000), 0); + } + + function testForbiddenMethod() public { + uint64 wrongMethod = 999; + vm.expectRevert(abi.encodeWithSelector(PoRepService.ForbiddenMethod.selector, wrongMethod)); + service.handle_filecoin_method(wrongMethod, 0, ""); + } + + function testUpdateLockupsNotDeal() public { + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, address(this))); + service.updateLockups(1, 0, 0, 0); + } +}