Skip to content

Commit b61a081

Browse files
committed
add changes and fix _findProvenEpochs
1 parent ab288c1 commit b61a081

File tree

3 files changed

+296
-8
lines changed

3 files changed

+296
-8
lines changed

service_contracts/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ foundry.lock
1414

1515
# Ignore IDEs
1616
.idea
17+
new_changes.txt

service_contracts/src/FilecoinWarmStorageService.sol

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,7 +1429,7 @@ contract FilecoinWarmStorageService is
14291429
require(dataSetId != 0, Errors.RailNotAssociated(railId));
14301430

14311431
// Calculate the total number of epochs in the requested range
1432-
uint256 totalEpochsRequested = toEpoch - fromEpoch;
1432+
uint256 totalEpochsRequested = toEpoch - fromEpoch; // total epochs = (toEpoch) - (fromEpoch + 1) - 1 == toEpoch - fromEpoch
14331433
require(totalEpochsRequested > 0, Errors.InvalidEpochRange(fromEpoch, toEpoch));
14341434

14351435
// If proving wasn't ever activated for this data set, don't pay anything
@@ -1480,8 +1480,8 @@ contract FilecoinWarmStorageService is
14801480
uint256 currentPeriod = getProvingPeriodForEpoch(dataSetId, block.number);
14811481
if (toEpoch >= activationEpoch && toEpoch < block.number) {
14821482
// if `toEpoch` lies after activation, and `fromEpoch` lies before activation, then update the `fromEpoch`, as follows :
1483-
if (fromEpoch < activationEpoch) {
1484-
fromEpoch = activationEpoch - 1; // we have done -1 because starting epoch is considered as `fromEpoch + 1`
1483+
if (fromEpoch < activationEpoch - 1) {
1484+
fromEpoch = activationEpoch - 1;
14851485
}
14861486

14871487
uint256 startingPeriod = getProvingPeriodForEpoch(dataSetId, fromEpoch + 1);
@@ -1490,15 +1490,16 @@ contract FilecoinWarmStorageService is
14901490
// lets handle first period separately
14911491
uint256 startingPeriod_deadline = _calcPeriodDeadline(dataSetId, startingPeriod);
14921492

1493+
14931494
if (toEpoch < startingPeriod_deadline) {
14941495
// alternative way to check the same : `startingPeriod == endingPeriod`
14951496
if (_isPeriodProven(dataSetId, startingPeriod, currentPeriod)) {
1496-
provenEpochCount = (toEpoch - fromEpoch);
1497+
provenEpochCount = (toEpoch - fromEpoch); // epochs : (`from + 1` -> `to`) (both inclusive)
14971498
lastProvenEpoch = toEpoch;
14981499
}
14991500
} else {
15001501
if (_isPeriodProven(dataSetId, startingPeriod, currentPeriod)) {
1501-
provenEpochCount += (startingPeriod_deadline - fromEpoch);
1502+
provenEpochCount += (startingPeriod_deadline - fromEpoch); // epochs : (`from + 1` -> `deadline`)
15021503
}
15031504

15041505
// now loop through the proving periods between endingPeriod and startingPeriod.
@@ -1511,8 +1512,8 @@ contract FilecoinWarmStorageService is
15111512

15121513
// now handle the last period separately
15131514
if (_isPeriodProven(dataSetId, endingPeriod, currentPeriod)) {
1514-
// then the epochs to add = `endingPeriod_starting` to `toEpoch`. But since `endingPeriod_starting` is simply the ending of its previous period, so
1515-
provenEpochCount += (toEpoch - _calcPeriodDeadline(dataSetId, endingPeriod - 1));
1515+
// then the epochs to add = `endingPeriod_starting` to `toEpoch`. But since `endingPeriod_starting` is simply the ending of its previous period + 1, so epochs : (`deadline + 1` -> `to`)
1516+
provenEpochCount += (toEpoch - _calcPeriodDeadline(dataSetId, endingPeriod - 1));
15161517
lastProvenEpoch = toEpoch;
15171518
}
15181519
}

service_contracts/test/FilecoinWarmStorageService.t.sol

Lines changed: 287 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {CHALLENGES_PER_PROOF, FilecoinWarmStorageService} from "../src/FilecoinW
1414
import {FilecoinWarmStorageServiceStateView} from "../src/FilecoinWarmStorageServiceStateView.sol";
1515
import {SignatureVerificationLib} from "../src/lib/SignatureVerificationLib.sol";
1616
import {FilecoinWarmStorageServiceStateLibrary} from "../src/lib/FilecoinWarmStorageServiceStateLibrary.sol";
17-
import {FilecoinPayV1} from "@fws-payments/FilecoinPayV1.sol";
17+
import {FilecoinPayV1, IValidator} from "@fws-payments/FilecoinPayV1.sol";
1818
import {MockERC20, MockPDPVerifier} from "./mocks/SharedMocks.sol";
1919
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
2020
import {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

Comments
 (0)