Skip to content

Commit 9daf260

Browse files
fix(pulse): ensure subscription balance is greater than minimum balance after adding funds (#2680)
* feat(pulse): add maximum deposit limit for permanent subscriptions Co-Authored-By: Tejas Badadare <[email protected]> * fix: remove unused variable in PulseScheduler.t.sol Co-Authored-By: Tejas Badadare <[email protected]> * fix: update deposit limit check to only check incoming deposit amount Co-Authored-By: Tejas Badadare <[email protected]> * fix: remove redundant access * fix: ensure subscription balance is greater than minimum balance after adding funds Co-Authored-By: Tejas Badadare <[email protected]> * fix: format code to pass CI Co-Authored-By: Tejas Badadare <[email protected]> * test: improve addFunds minimum balance test Co-Authored-By: Tejas Badadare <[email protected]> * test: fix unused variable warnings in PulseScheduler tests Co-Authored-By: Tejas Badadare <[email protected]> * test: fix unused variable in PulseScheduler test Co-Authored-By: Tejas Badadare <[email protected]> * test: remove duplicate test functions Co-Authored-By: Tejas Badadare <[email protected]> * ci: fmt * test: fix testAddFundsEnforcesMinimumBalance --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Tejas Badadare <[email protected]> Co-authored-by: Tejas Badadare <[email protected]> Co-authored-by: Tejas Badadare <[email protected]>
1 parent 379cdd7 commit 9daf260

File tree

2 files changed

+124
-0
lines changed

2 files changed

+124
-0
lines changed

target_chains/ethereum/contracts/contracts/pulse/Scheduler.sol

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,16 @@ abstract contract Scheduler is IScheduler, SchedulerState {
541541
}
542542

543543
status.balanceInWei += msg.value;
544+
545+
// If subscription is active, ensure minimum balance is maintained
546+
if (params.isActive) {
547+
uint256 minimumBalance = this.getMinimumBalance(
548+
uint8(params.priceIds.length)
549+
);
550+
if (status.balanceInWei < minimumBalance) {
551+
revert InsufficientBalance();
552+
}
553+
}
544554
}
545555

546556
function withdrawFunds(

target_chains/ethereum/contracts/forge-test/PulseScheduler.t.sol

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,120 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
546546
);
547547
}
548548

549+
function testAddFundsWithInactiveSubscriptionReverts() public {
550+
// Create a subscription with minimum balance
551+
uint256 subscriptionId = addTestSubscription(
552+
scheduler,
553+
address(reader)
554+
);
555+
556+
// Get subscription parameters and calculate minimum balance
557+
(SchedulerState.SubscriptionParams memory params, ) = scheduler
558+
.getSubscription(subscriptionId);
559+
uint256 minimumBalance = scheduler.getMinimumBalance(
560+
uint8(params.priceIds.length)
561+
);
562+
563+
// Deactivate the subscription
564+
SchedulerState.SubscriptionParams memory testParams = params;
565+
testParams.isActive = false;
566+
scheduler.updateSubscription(subscriptionId, testParams);
567+
568+
// Withdraw funds to get below minimum
569+
uint256 withdrawAmount = minimumBalance - 1 wei;
570+
scheduler.withdrawFunds(subscriptionId, withdrawAmount);
571+
572+
// Verify balance is now below minimum
573+
(
574+
SchedulerState.SubscriptionParams memory testUpdatedParams,
575+
SchedulerState.SubscriptionStatus memory testUpdatedStatus
576+
) = scheduler.getSubscription(subscriptionId);
577+
assertEq(
578+
testUpdatedStatus.balanceInWei,
579+
1 wei,
580+
"Balance should be 1 wei after withdrawal"
581+
);
582+
583+
// Try to add funds to inactive subscription (should fail with InactiveSubscription)
584+
vm.expectRevert(abi.encodeWithSelector(InactiveSubscription.selector));
585+
scheduler.addFunds{value: 1 wei}(subscriptionId);
586+
587+
// Try to reactivate with insufficient balance (should fail)
588+
testUpdatedParams.isActive = true;
589+
vm.expectRevert(abi.encodeWithSelector(InsufficientBalance.selector));
590+
scheduler.updateSubscription(subscriptionId, testUpdatedParams);
591+
}
592+
593+
function testAddFundsEnforcesMinimumBalance() public {
594+
uint256 subscriptionId = addTestSubscriptionWithFeeds(
595+
scheduler,
596+
2,
597+
address(reader)
598+
);
599+
(SchedulerState.SubscriptionParams memory params, ) = scheduler
600+
.getSubscription(subscriptionId);
601+
uint256 minimumBalance = scheduler.getMinimumBalance(
602+
uint8(params.priceIds.length)
603+
);
604+
605+
// Send multiple price updates to drain the balance below minimum
606+
for (uint i = 0; i < 5; i++) {
607+
// Advance time to satisfy heartbeat criteria
608+
vm.warp(block.timestamp + 60);
609+
610+
// Create price feeds with current timestamp
611+
uint64 publishTime = SafeCast.toUint64(block.timestamp);
612+
PythStructs.PriceFeed[] memory priceFeeds;
613+
uint64[] memory slots;
614+
(priceFeeds, slots) = createMockPriceFeedsWithSlots(
615+
publishTime,
616+
params.priceIds.length
617+
);
618+
619+
// Mock Pyth response
620+
mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds, slots);
621+
bytes[] memory updateData = createMockUpdateData(priceFeeds);
622+
623+
// Perform update
624+
vm.prank(pusher);
625+
scheduler.updatePriceFeeds(subscriptionId, updateData);
626+
}
627+
628+
// Verify balance is now below minimum
629+
(
630+
,
631+
SchedulerState.SubscriptionStatus memory statusAfterUpdates
632+
) = scheduler.getSubscription(subscriptionId);
633+
assertTrue(
634+
statusAfterUpdates.balanceInWei < minimumBalance,
635+
"Balance should be below minimum after updates"
636+
);
637+
638+
// Try to add funds that would still leave balance below minimum
639+
// Expect a revert with InsufficientBalance
640+
uint256 insufficientFunds = minimumBalance -
641+
statusAfterUpdates.balanceInWei -
642+
1;
643+
vm.expectRevert(abi.encodeWithSelector(InsufficientBalance.selector));
644+
scheduler.addFunds{value: insufficientFunds}(subscriptionId);
645+
646+
// Add sufficient funds to get back above minimum
647+
uint256 sufficientFunds = minimumBalance -
648+
statusAfterUpdates.balanceInWei +
649+
1;
650+
scheduler.addFunds{value: sufficientFunds}(subscriptionId);
651+
652+
// Verify balance is now above minimum
653+
(
654+
,
655+
SchedulerState.SubscriptionStatus memory statusAfterAddingFunds
656+
) = scheduler.getSubscription(subscriptionId);
657+
assertTrue(
658+
statusAfterAddingFunds.balanceInWei >= minimumBalance,
659+
"Balance should be at or above minimum after adding sufficient funds"
660+
);
661+
}
662+
549663
function testWithdrawFunds() public {
550664
// Add a subscription and get the parameters
551665
uint256 subscriptionId = addTestSubscription(

0 commit comments

Comments
 (0)