@@ -14,7 +14,7 @@ import {CHALLENGES_PER_PROOF, FilecoinWarmStorageService} from "../src/FilecoinW
1414import {FilecoinWarmStorageServiceStateView} from "../src/FilecoinWarmStorageServiceStateView.sol " ;
1515import {SignatureVerificationLib} from "../src/lib/SignatureVerificationLib.sol " ;
1616import {FilecoinWarmStorageServiceStateLibrary} from "../src/lib/FilecoinWarmStorageServiceStateLibrary.sol " ;
17- import {FilecoinPayV1} from "@fws-payments/FilecoinPayV1.sol " ;
17+ import {FilecoinPayV1, IValidator } from "@fws-payments/FilecoinPayV1.sol " ;
1818import {MockERC20, MockPDPVerifier} from "./mocks/SharedMocks.sol " ;
1919import {IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol " ;
2020import {Errors} from "../src/Errors.sol " ;
@@ -4218,3 +4218,289 @@ contract FilecoinWarmStorageServiceUpgradeTest is Test {
42184218 warmStorageService.migrate (address (0 ));
42194219 }
42204220}
4221+
4222+ /**
4223+ * @notice Tests for validatePayment function - ensures optimized implementation
4224+ * maintains same behavior as the original loop-based version
4225+ */
4226+
4227+
4228+ contract ValidatePaymentTest is FilecoinWarmStorageServiceTest {
4229+ /**
4230+ * @notice Test: All epochs proven - should pay full amount
4231+ */
4232+ function testValidatePayment_AllEpochsProven () public {
4233+ uint256 dataSetId = createDataSetForServiceProviderTest (sp1, client, "Test " );
4234+
4235+ // Start proving
4236+ (uint64 maxProvingPeriod , uint256 challengeWindow ,,) = viewContract.getPDPConfig ();
4237+
4238+ // Capture activation epoch BEFORE calling nextProvingPeriod
4239+
4240+ // uint256 _activationEpoch = block.number; // using this line is causing the activationEpoch variable to change every time we call vm.roll (some bug maybe) so better to do use the following line :
4241+ uint256 _activationEpoch = 1 ; // because in testing environment, starting block number is 1
4242+ uint256 firstChallengeEpoch = _activationEpoch + maxProvingPeriod - (challengeWindow / 2 );
4243+
4244+ vm.prank (address (mockPDPVerifier));
4245+ pdpServiceWithPayments.nextProvingPeriod (dataSetId, firstChallengeEpoch, 100 , "" );
4246+
4247+ uint256 firstDeadline = _activationEpoch + maxProvingPeriod;
4248+
4249+ // Submit proof for period 0
4250+ vm.roll (firstChallengeEpoch);
4251+ vm.prank (address (mockPDPVerifier));
4252+ pdpServiceWithPayments.possessionProven (dataSetId, 100 , 12345 , CHALLENGES_PER_PROOF);
4253+
4254+ // Move just past the first deadline
4255+ vm.roll (firstDeadline + 1 );
4256+
4257+ uint256 secondDeadline = firstDeadline + maxProvingPeriod;
4258+ uint256 challengeEpoch1 = secondDeadline - (challengeWindow / 2 );
4259+
4260+ vm.prank (address (mockPDPVerifier));
4261+ pdpServiceWithPayments.nextProvingPeriod (dataSetId, challengeEpoch1, 100 , "" );
4262+
4263+ // Submit proof for period 1
4264+ vm.roll (challengeEpoch1);
4265+ vm.prank (address (mockPDPVerifier));
4266+ pdpServiceWithPayments.possessionProven (dataSetId, 100 , 12345 , CHALLENGES_PER_PROOF);
4267+
4268+ // Move to period 2
4269+ vm.roll (secondDeadline + 1 );
4270+ uint256 thirdDeadline = secondDeadline + maxProvingPeriod;
4271+ uint256 challengeEpoch2 = thirdDeadline - (challengeWindow / 2 );
4272+
4273+ vm.prank (address (mockPDPVerifier));
4274+ pdpServiceWithPayments.nextProvingPeriod (dataSetId, challengeEpoch2, 100 , "" );
4275+
4276+ // Submit proof for period 2
4277+ vm.roll (challengeEpoch2);
4278+ vm.prank (address (mockPDPVerifier));
4279+ pdpServiceWithPayments.possessionProven (dataSetId, 100 , 12345 , CHALLENGES_PER_PROOF);
4280+
4281+ // Now validate payment for epochs within these proven periods
4282+ FilecoinWarmStorageService.DataSetInfoView memory info = viewContract.getDataSet (dataSetId);
4283+ uint256 fromEpoch = _activationEpoch - 1 ; // exclusive start
4284+ uint256 toEpoch = _activationEpoch + (maxProvingPeriod * 3 ) - 1 ; // inclusive end, all 3 periods
4285+ uint256 proposedAmount = 1000e6 ;
4286+
4287+ // Move past the periods we're validating, so that toEpoch becomes less than block.number
4288+ vm.roll (toEpoch + 1 );
4289+ vm.prank (address (payments));
4290+ IValidator.ValidationResult memory result =
4291+ pdpServiceWithPayments.validatePayment (info.pdpRailId, proposedAmount, fromEpoch, toEpoch, 0 );
4292+
4293+ // Should pay full amount since all epochs are proven
4294+ assertEq (result.modifiedAmount, proposedAmount, "Should pay full amount " );
4295+ assertEq (result.settleUpto, toEpoch, "Should settle to end epoch " );
4296+ }
4297+
4298+ /**
4299+ * @notice Test: No epochs proven - should pay nothing
4300+ */
4301+ function testValidatePayment_NoEpochsProven () public {
4302+ uint256 dataSetId = createDataSetForServiceProviderTest (sp1, client, "Test " );
4303+
4304+ // Start proving but don't submit any proofs
4305+ (uint64 maxProvingPeriod , uint256 challengeWindow ,,) = viewContract.getPDPConfig ();
4306+ uint256 challengeEpoch = block .number + maxProvingPeriod - (challengeWindow / 2 );
4307+
4308+ vm.prank (address (mockPDPVerifier));
4309+ pdpServiceWithPayments.nextProvingPeriod (dataSetId, challengeEpoch, 100 , "" );
4310+
4311+ uint256 activationEpoch = 1 ; // same reason as the above test
4312+
4313+ // Move forward 3 periods without submitting proofs
4314+ vm.roll (activationEpoch + (maxProvingPeriod * 3 ));
4315+
4316+ // Validate payment
4317+ FilecoinWarmStorageService.DataSetInfoView memory info = viewContract.getDataSet (dataSetId);
4318+ uint256 fromEpoch = activationEpoch - 1 ; // exclusive
4319+ uint256 toEpoch = activationEpoch + (maxProvingPeriod * 3 ) - 1 ;
4320+ uint256 proposedAmount = 1000e6 ;
4321+
4322+ vm.prank (address (payments));
4323+ IValidator.ValidationResult memory result =
4324+ pdpServiceWithPayments.validatePayment (info.pdpRailId, proposedAmount, fromEpoch, toEpoch, 0 );
4325+
4326+ // Should pay nothing
4327+ assertEq (result.modifiedAmount, 0 , "Should pay nothing " );
4328+ assertEq (result.settleUpto, fromEpoch, "Should not settle " );
4329+ assertEq (result.note, "No proven epochs in the requested range " );
4330+ }
4331+
4332+ /**
4333+ * @notice Test: Some epochs proven - should pay proportionally
4334+ */
4335+ function testValidatePayment_SomeEpochsProven () public {
4336+ uint256 dataSetId = createDataSetForServiceProviderTest (sp1, client, "Test " );
4337+
4338+ // Start proving
4339+ (uint64 maxProvingPeriod , uint256 challengeWindow ,,) = viewContract.getPDPConfig ();
4340+ uint256 firstChallengeEpoch = block .number + maxProvingPeriod - (challengeWindow / 2 );
4341+
4342+ vm.prank (address (mockPDPVerifier));
4343+ pdpServiceWithPayments.nextProvingPeriod (dataSetId, firstChallengeEpoch, 100 , "" );
4344+
4345+ uint256 activationEpoch = 1 ;
4346+
4347+ // Submit proof for period 0
4348+ vm.roll (firstChallengeEpoch);
4349+ vm.prank (address (mockPDPVerifier));
4350+ pdpServiceWithPayments.possessionProven (dataSetId, 100 , 12345 , CHALLENGES_PER_PROOF);
4351+
4352+ // Move to period 1 - DON'T submit proof
4353+ uint256 deadline0 = activationEpoch + maxProvingPeriod;
4354+ vm.roll (deadline0 + 1 );
4355+ uint256 challengeEpoch1 = deadline0 + 1 + maxProvingPeriod - (challengeWindow / 2 );
4356+ vm.prank (address (mockPDPVerifier));
4357+ pdpServiceWithPayments.nextProvingPeriod (dataSetId, challengeEpoch1, 100 , "" );
4358+
4359+ // Skip proof for period 1
4360+
4361+ // Move to period 2 and submit proof
4362+ uint256 deadline1 = deadline0 + maxProvingPeriod;
4363+ vm.roll (deadline1 + 1 );
4364+ uint256 challengeEpoch2 = deadline1 + 1 + maxProvingPeriod - (challengeWindow / 2 );
4365+ vm.prank (address (mockPDPVerifier));
4366+ pdpServiceWithPayments.nextProvingPeriod (dataSetId, challengeEpoch2, 100 , "" );
4367+
4368+ vm.roll (challengeEpoch2);
4369+ vm.prank (address (mockPDPVerifier));
4370+ pdpServiceWithPayments.possessionProven (dataSetId, 100 , 12345 , CHALLENGES_PER_PROOF);
4371+
4372+ // Validate payment for all 3 periods
4373+ FilecoinWarmStorageService.DataSetInfoView memory info = viewContract.getDataSet (dataSetId);
4374+ uint256 fromEpoch = activationEpoch - 1 ;
4375+ uint256 toEpoch = activationEpoch + (maxProvingPeriod * 3 ) - 1 ;
4376+ uint256 proposedAmount = 3000e6 ;
4377+
4378+ vm.roll (toEpoch + 1 );
4379+
4380+ vm.prank (address (payments));
4381+ IValidator.ValidationResult memory result =
4382+ pdpServiceWithPayments.validatePayment (info.pdpRailId, proposedAmount, fromEpoch, toEpoch, 0 );
4383+
4384+ // Should pay 2/3 of amount (2 proven periods out of 3)
4385+ uint256 totalEpochs = toEpoch - fromEpoch;
4386+ uint256 provenEpochs = maxProvingPeriod * 2 ;
4387+ uint256 expectedAmount = (proposedAmount * provenEpochs) / totalEpochs;
4388+
4389+ assertEq (result.modifiedAmount, expectedAmount, "Should pay for 2/3 of epochs " );
4390+ assertTrue (result.settleUpto > fromEpoch, "Should settle past start " );
4391+ }
4392+
4393+ /**
4394+ * @notice Test: Proving never activated - should pay nothing
4395+ */
4396+ function testValidatePayment_ProvingNeverActivated () public {
4397+ uint256 dataSetId = createDataSetForServiceProviderTest (sp1, client, "Test " );
4398+
4399+ // Don't start proving at all
4400+ FilecoinWarmStorageService.DataSetInfoView memory info = viewContract.getDataSet (dataSetId);
4401+ uint256 fromEpoch = block .number ;
4402+ uint256 toEpoch = block .number + 1000 ;
4403+ uint256 proposedAmount = 1000e6 ;
4404+
4405+ vm.prank (address (payments));
4406+ IValidator.ValidationResult memory result =
4407+ pdpServiceWithPayments.validatePayment (info.pdpRailId, proposedAmount, fromEpoch, toEpoch, 0 );
4408+
4409+ assertEq (result.modifiedAmount, 0 , "Should pay nothing " );
4410+ assertEq (result.settleUpto, fromEpoch, "Should not settle " );
4411+ assertEq (result.note, "Proving never activated for this data set " );
4412+ }
4413+
4414+ /**
4415+ * @notice Test: Request range before activation - should pay nothing
4416+ */
4417+ function testValidatePayment_BeforeActivation () public {
4418+ uint256 dataSetId = createDataSetForServiceProviderTest (sp1, client, "Test " );
4419+
4420+ // Move forward to create some block height
4421+ vm.roll (block .number + 1000 );
4422+
4423+ // Start proving
4424+ (uint64 maxProvingPeriod , uint256 challengeWindow ,,) = viewContract.getPDPConfig ();
4425+ uint256 challengeEpoch = block .number + maxProvingPeriod - (challengeWindow / 2 );
4426+
4427+ vm.prank (address (mockPDPVerifier));
4428+ pdpServiceWithPayments.nextProvingPeriod (dataSetId, challengeEpoch, 100 , "" );
4429+
4430+ uint256 activationEpoch = block .number ;
4431+
4432+ // Try to validate for epochs before activation
4433+ FilecoinWarmStorageService.DataSetInfoView memory info = viewContract.getDataSet (dataSetId);
4434+ uint256 fromEpoch = activationEpoch - 500 ;
4435+ uint256 toEpoch = activationEpoch - 100 ;
4436+ uint256 proposedAmount = 1000e6 ;
4437+
4438+ vm.prank (address (payments));
4439+ IValidator.ValidationResult memory result =
4440+ pdpServiceWithPayments.validatePayment (info.pdpRailId, proposedAmount, fromEpoch, toEpoch, 0 );
4441+
4442+ assertEq (result.modifiedAmount, 0 , "Should pay nothing for pre-activation epochs " );
4443+ assertEq (result.settleUpto, fromEpoch, "Should not settle " );
4444+ }
4445+
4446+ /**
4447+ * @notice Test: Partial period coverage - epochs span within a proven period
4448+ */
4449+ function testValidatePayment_PartialPeriodCoverage () public {
4450+ uint256 dataSetId = createDataSetForServiceProviderTest (sp1, client, "Test " );
4451+
4452+ // Start proving
4453+ (uint64 maxProvingPeriod , uint256 challengeWindow ,,) = viewContract.getPDPConfig ();
4454+ uint256 challengeEpoch = block .number + maxProvingPeriod - (challengeWindow / 2 );
4455+
4456+ vm.prank (address (mockPDPVerifier));
4457+ pdpServiceWithPayments.nextProvingPeriod (dataSetId, challengeEpoch, 100 , "" );
4458+
4459+ uint256 activationEpoch = 1 ;
4460+
4461+ // Submit proof for period 0
4462+ vm.roll (challengeEpoch);
4463+ vm.prank (address (mockPDPVerifier));
4464+ pdpServiceWithPayments.possessionProven (dataSetId, 100 , 12345 , CHALLENGES_PER_PROOF);
4465+
4466+ // Validate payment for middle portion of period 0
4467+ FilecoinWarmStorageService.DataSetInfoView memory info = viewContract.getDataSet (dataSetId);
4468+ uint256 fromEpoch = activationEpoch + 100 ; // Start 100 epochs into period
4469+ uint256 toEpoch = activationEpoch + maxProvingPeriod - 100 ; // End 100 epochs before period ends
4470+ uint256 proposedAmount = 1000e6 ;
4471+
4472+ vm.roll (toEpoch + 1 );
4473+
4474+ vm.prank (address (payments));
4475+ IValidator.ValidationResult memory result =
4476+ pdpServiceWithPayments.validatePayment (info.pdpRailId, proposedAmount, fromEpoch, toEpoch, 0 );
4477+
4478+ // Since the period is proven, should pay full amount for the requested range
4479+ assertEq (result.modifiedAmount, proposedAmount, "Should pay full amount for proven period " );
4480+ assertEq (result.settleUpto, toEpoch, "Should settle to end of range " );
4481+ }
4482+
4483+ /**
4484+ * @notice Test: Invalid rail ID - should revert
4485+ */
4486+ function testValidatePayment_InvalidRailId () public {
4487+ uint256 invalidRailId = 999999 ;
4488+
4489+ vm.prank (address (payments));
4490+ vm.expectRevert (abi.encodeWithSelector (Errors.RailNotAssociated.selector , invalidRailId));
4491+ pdpServiceWithPayments.validatePayment (invalidRailId, 1000e6 , 100 , 200 , 0 );
4492+ }
4493+
4494+ /**
4495+ * @notice Test: Invalid epoch range - should revert
4496+ */
4497+ function testValidatePayment_InvalidEpochRange () public {
4498+ uint256 dataSetId = createDataSetForServiceProviderTest (sp1, client, "Test " );
4499+ FilecoinWarmStorageService.DataSetInfoView memory info = viewContract.getDataSet (dataSetId);
4500+
4501+ // fromEpoch >= toEpoch
4502+ vm.prank (address (payments));
4503+ vm.expectRevert (abi.encodeWithSelector (Errors.InvalidEpochRange.selector , 200 , 200 ));
4504+ pdpServiceWithPayments.validatePayment (info.pdpRailId, 1000e6 , 200 , 200 , 0 );
4505+ }
4506+ }
0 commit comments