Skip to content

Commit ff58d77

Browse files
feat(pulse): add maximum deposit limit for permanent subscriptions (#2675)
* feat(pulse): add maximum deposit limit for permanent subscriptions Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz> * fix: remove unused variable in PulseScheduler.t.sol Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz> * fix: update deposit limit check to only check incoming deposit amount Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz> * fix: remove redundant access * fix: update isActive check in addFunds function Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz> * fix: format Scheduler.sol Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Tejas Badadare <tejas@dourolabs.xyz> Co-authored-by: Tejas Badadare <17058023+tejasbadadare@users.noreply.github.com>
1 parent 8f6e5da commit ff58d77

File tree

4 files changed

+128
-2
lines changed

4 files changed

+128
-2
lines changed

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ abstract contract Scheduler is IScheduler, SchedulerState {
4242
revert InsufficientBalance();
4343
}
4444

45+
// Check deposit limit for permanent subscriptions
46+
if (subscriptionParams.isPermanent && msg.value > MAX_DEPOSIT_LIMIT) {
47+
revert MaxDepositLimitExceeded();
48+
}
49+
4550
// Set subscription to active
4651
subscriptionParams.isActive = true;
4752

@@ -519,11 +524,23 @@ abstract contract Scheduler is IScheduler, SchedulerState {
519524
/// BALANCE MANAGEMENT
520525

521526
function addFunds(uint256 subscriptionId) external payable override {
522-
if (!_state.subscriptionParams[subscriptionId].isActive) {
527+
SubscriptionParams storage params = _state.subscriptionParams[
528+
subscriptionId
529+
];
530+
SubscriptionStatus storage status = _state.subscriptionStatuses[
531+
subscriptionId
532+
];
533+
534+
if (!params.isActive) {
523535
revert InactiveSubscription();
524536
}
525537

526-
_state.subscriptionStatuses[subscriptionId].balanceInWei += msg.value;
538+
// Check deposit limit for permanent subscriptions
539+
if (params.isPermanent && msg.value > MAX_DEPOSIT_LIMIT) {
540+
revert MaxDepositLimitExceeded();
541+
}
542+
543+
status.balanceInWei += msg.value;
527544
}
528545

529546
function withdrawFunds(

target_chains/ethereum/contracts/contracts/pulse/SchedulerErrors.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ error DuplicateWhitelistAddress(address addr);
3636

3737
// Payment errors
3838
error KeeperPaymentFailed();
39+
error MaxDepositLimitExceeded();

target_chains/ethereum/contracts/contracts/pulse/SchedulerState.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ contract SchedulerState {
99
uint8 public constant MAX_PRICE_IDS_PER_SUBSCRIPTION = 255;
1010
/// Maximum number of addresses in the reader whitelist
1111
uint8 public constant MAX_READER_WHITELIST_SIZE = 255;
12+
/// Maximum deposit limit for permanent subscriptions in wei
13+
uint256 public constant MAX_DEPOSIT_LIMIT = 100 ether;
1214

1315
/// Maximum time in the past (relative to current block timestamp)
1416
/// for which a price update timestamp is considered valid

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

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,112 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
783783
scheduler.updateSubscription(subscriptionId, params);
784784
}
785785

786+
function testPermanentSubscriptionDepositLimit() public {
787+
// Test 1: Creating a permanent subscription with deposit exceeding MAX_DEPOSIT_LIMIT should fail
788+
SchedulerState.SubscriptionParams
789+
memory params = createDefaultSubscriptionParams(2, address(reader));
790+
params.isPermanent = true;
791+
792+
uint256 maxDepositLimit = 100 ether; // Same as MAX_DEPOSIT_LIMIT in SchedulerState
793+
uint256 excessiveDeposit = maxDepositLimit + 1 ether;
794+
vm.deal(address(this), excessiveDeposit);
795+
796+
vm.expectRevert(
797+
abi.encodeWithSelector(MaxDepositLimitExceeded.selector)
798+
);
799+
scheduler.createSubscription{value: excessiveDeposit}(params);
800+
801+
// Test 2: Creating a permanent subscription with deposit within MAX_DEPOSIT_LIMIT should succeed
802+
uint256 validDeposit = maxDepositLimit;
803+
vm.deal(address(this), validDeposit);
804+
805+
uint256 subscriptionId = scheduler.createSubscription{
806+
value: validDeposit
807+
}(params);
808+
809+
// Verify subscription was created correctly
810+
(
811+
SchedulerState.SubscriptionParams memory storedParams,
812+
SchedulerState.SubscriptionStatus memory status
813+
) = scheduler.getSubscription(subscriptionId);
814+
815+
assertTrue(
816+
storedParams.isPermanent,
817+
"Subscription should be permanent"
818+
);
819+
assertEq(
820+
status.balanceInWei,
821+
validDeposit,
822+
"Balance should match deposit amount"
823+
);
824+
825+
// Test 3: Adding funds to a permanent subscription with deposit exceeding MAX_DEPOSIT_LIMIT should fail
826+
uint256 largeAdditionalFunds = maxDepositLimit + 1;
827+
vm.deal(address(this), largeAdditionalFunds);
828+
829+
vm.expectRevert(
830+
abi.encodeWithSelector(MaxDepositLimitExceeded.selector)
831+
);
832+
scheduler.addFunds{value: largeAdditionalFunds}(subscriptionId);
833+
834+
// Test 4: Adding funds to a permanent subscription within MAX_DEPOSIT_LIMIT should succeed
835+
// Create a non-permanent subscription to test partial funding
836+
SchedulerState.SubscriptionParams
837+
memory nonPermanentParams = createDefaultSubscriptionParams(
838+
2,
839+
address(reader)
840+
);
841+
uint256 minimumBalance = scheduler.getMinimumBalance(
842+
uint8(nonPermanentParams.priceIds.length)
843+
);
844+
vm.deal(address(this), minimumBalance);
845+
846+
uint256 nonPermanentSubId = scheduler.createSubscription{
847+
value: minimumBalance
848+
}(nonPermanentParams);
849+
850+
// Add funds to the non-permanent subscription (should be within limit)
851+
uint256 validAdditionalFunds = 5 ether;
852+
vm.deal(address(this), validAdditionalFunds);
853+
854+
scheduler.addFunds{value: validAdditionalFunds}(nonPermanentSubId);
855+
856+
// Verify funds were added correctly
857+
(
858+
,
859+
SchedulerState.SubscriptionStatus memory nonPermanentStatus
860+
) = scheduler.getSubscription(nonPermanentSubId);
861+
862+
assertEq(
863+
nonPermanentStatus.balanceInWei,
864+
minimumBalance + validAdditionalFunds,
865+
"Balance should be increased by the funded amount"
866+
);
867+
868+
// Test 5: Non-permanent subscriptions should not be subject to the deposit limit
869+
uint256 largeDeposit = maxDepositLimit * 2;
870+
vm.deal(address(this), largeDeposit);
871+
872+
SchedulerState.SubscriptionParams
873+
memory unlimitedParams = createDefaultSubscriptionParams(
874+
2,
875+
address(reader)
876+
);
877+
uint256 unlimitedSubId = scheduler.createSubscription{
878+
value: largeDeposit
879+
}(unlimitedParams);
880+
881+
// Verify subscription was created with the large deposit
882+
(, SchedulerState.SubscriptionStatus memory unlimitedStatus) = scheduler
883+
.getSubscription(unlimitedSubId);
884+
885+
assertEq(
886+
unlimitedStatus.balanceInWei,
887+
largeDeposit,
888+
"Non-permanent subscription should accept large deposits"
889+
);
890+
}
891+
786892
function testAnyoneCanAddFunds() public {
787893
// Create a subscription
788894
uint256 subscriptionId = addTestSubscription(

0 commit comments

Comments
 (0)