Ver Fonte

Merge branch 'tb/pulse-scheduler/init-address-reviews' of github.com:pyth-network/pyth-crosschain into tb/pulse-scheduler/init-address-reviews

Tejas Badadare há 7 meses atrás
pai
commit
b2215f027d

+ 5 - 8
target_chains/ethereum/contracts/contracts/pulse/scheduler/IScheduler.sol

@@ -14,10 +14,11 @@ interface IScheduler is SchedulerEvents {
      * @notice Adds a new subscription
      * @param subscriptionParams The parameters for the subscription
      * @return subscriptionId The ID of the newly created subscription
+     * @dev Requires msg.value to be at least the minimum balance for the subscription
      */
     function addSubscription(
         SchedulerState.SubscriptionParams calldata subscriptionParams
-    ) external returns (uint256 subscriptionId);
+    ) external payable returns (uint256 subscriptionId);
 
     /**
      * @notice Gets a subscription's parameters and status
@@ -43,13 +44,9 @@ interface IScheduler is SchedulerEvents {
     function updateSubscription(
         uint256 subscriptionId,
         SchedulerState.SubscriptionParams calldata newSubscriptionParams
-    ) external;
+    ) external payable;
 
-    /**
-     * @notice Deactivates a subscription
-     * @param subscriptionId The ID of the subscription to deactivate
-     */
-    function deactivateSubscription(uint256 subscriptionId) external;
+    // Deactivation is now handled through updateSubscription by setting isActive to false
 
     /**
      * @notice Updates price feeds for a subscription.
@@ -116,7 +113,7 @@ interface IScheduler is SchedulerEvents {
      */
     function getMinimumBalance(
         uint8 numPriceFeeds
-    ) external returns (uint256 minimumBalance);
+    ) external view returns (uint256 minimumBalance);
 
     /**
      * @notice Gets all active subscriptions with their parameters

+ 66 - 16
target_chains/ethereum/contracts/contracts/pulse/scheduler/Scheduler.sol

@@ -22,7 +22,7 @@ abstract contract Scheduler is IScheduler, SchedulerState {
 
     function addSubscription(
         SubscriptionParams memory subscriptionParams
-    ) external override returns (uint256 subscriptionId) {
+    ) external payable override returns (uint256 subscriptionId) {
         if (subscriptionParams.priceIds.length > MAX_PRICE_IDS) {
             revert TooManyPriceIds(
                 subscriptionParams.priceIds.length,
@@ -51,6 +51,19 @@ abstract contract Scheduler is IScheduler, SchedulerState {
                 .maxGasMultiplierCapPct = DEFAULT_MAX_GAS_MULTIPLIER_CAP_PCT;
         }
 
+        // Calculate minimum balance required for this subscription
+        uint256 minimumBalance = this.getMinimumBalance(
+            uint8(subscriptionParams.priceIds.length)
+        );
+
+        // Ensure enough funds were provided
+        if (msg.value < minimumBalance) {
+            revert InsufficientBalance();
+        }
+
+        // Set subscription to active
+        subscriptionParams.isActive = true;
+
         subscriptionId = _state.subscriptionNumber++;
 
         // Store the subscription parameters
@@ -61,7 +74,7 @@ abstract contract Scheduler is IScheduler, SchedulerState {
             subscriptionId
         ];
         status.priceLastUpdatedAt = 0;
-        status.balanceInWei = 0;
+        status.balanceInWei = msg.value;
         status.totalUpdates = 0;
         status.totalSpent = 0;
         status.isActive = true;
@@ -93,11 +106,22 @@ abstract contract Scheduler is IScheduler, SchedulerState {
     function updateSubscription(
         uint256 subscriptionId,
         SubscriptionParams memory newSubscriptionParams
-    ) external override onlyManager(subscriptionId) {
-        if (!_state.subscriptionStatuses[subscriptionId].isActive) {
-            revert InactiveSubscription();
+    ) external payable override onlyManager(subscriptionId) {
+        SubscriptionStatus storage status = _state.subscriptionStatuses[
+            subscriptionId
+        ];
+        bool wasActive = status.isActive;
+        bool willBeActive = newSubscriptionParams.isActive;
+
+        // If subscription is inactive and will remain inactive, no need to validate parameters
+        if (!wasActive && !willBeActive) {
+            // Update subscription parameters
+            _state.subscriptionParams[subscriptionId] = newSubscriptionParams;
+            emit SubscriptionUpdated(subscriptionId);
+            return;
         }
 
+        // Validate parameters for active or to-be-activated subscriptions
         if (newSubscriptionParams.priceIds.length > MAX_PRICE_IDS) {
             revert TooManyPriceIds(
                 newSubscriptionParams.priceIds.length,
@@ -126,24 +150,37 @@ abstract contract Scheduler is IScheduler, SchedulerState {
                 .maxGasMultiplierCapPct = DEFAULT_MAX_GAS_MULTIPLIER_CAP_PCT;
         }
 
-        // Update subscription parameters
-        _state.subscriptionParams[subscriptionId] = newSubscriptionParams;
+        // Handle activation/deactivation
+        if (!wasActive && willBeActive) {
+            // Reactivating a subscription - ensure minimum balance
+            uint256 minimumBalance = this.getMinimumBalance(
+                uint8(newSubscriptionParams.priceIds.length)
+            );
 
-        emit SubscriptionUpdated(subscriptionId);
-    }
+            // Add any funds sent with this transaction
+            status.balanceInWei += msg.value;
 
-    function deactivateSubscription(
-        uint256 subscriptionId
-    ) external override onlyManager(subscriptionId) {
-        if (!_state.subscriptionStatuses[subscriptionId].isActive) {
-            revert InactiveSubscription();
+            // Check if balance meets minimum requirement
+            if (status.balanceInWei < minimumBalance) {
+                revert InsufficientBalance();
+            }
+
+            status.isActive = true;
+            emit SubscriptionActivated(subscriptionId);
+        } else if (wasActive && !willBeActive) {
+            // Deactivating a subscription
+            status.isActive = false;
+            emit SubscriptionDeactivated(subscriptionId);
         }
 
-        _state.subscriptionStatuses[subscriptionId].isActive = false;
+        // Update subscription parameters
+        _state.subscriptionParams[subscriptionId] = newSubscriptionParams;
 
-        emit SubscriptionDeactivated(subscriptionId);
+        emit SubscriptionUpdated(subscriptionId);
     }
 
+    // Removed standalone deactivateSubscription function as it's now handled in updateSubscription
+
     function updatePriceFeeds(
         uint256 subscriptionId,
         bytes[] calldata updateData,
@@ -439,11 +476,24 @@ abstract contract Scheduler is IScheduler, SchedulerState {
         SubscriptionStatus storage status = _state.subscriptionStatuses[
             subscriptionId
         ];
+        SubscriptionParams storage params = _state.subscriptionParams[
+            subscriptionId
+        ];
 
         if (status.balanceInWei < amount) {
             revert InsufficientBalance();
         }
 
+        // If subscription is active, ensure minimum balance is maintained
+        if (status.isActive) {
+            uint256 minimumBalance = this.getMinimumBalance(
+                uint8(params.priceIds.length)
+            );
+            if (status.balanceInWei - amount < minimumBalance) {
+                revert InsufficientBalance();
+            }
+        }
+
         status.balanceInWei -= amount;
 
         (bool sent, ) = msg.sender.call{value: amount}("");

+ 1 - 0
target_chains/ethereum/contracts/contracts/pulse/scheduler/SchedulerEvents.sol

@@ -10,5 +10,6 @@ interface SchedulerEvents {
     );
     event SubscriptionUpdated(uint256 indexed subscriptionId);
     event SubscriptionDeactivated(uint256 indexed subscriptionId);
+    event SubscriptionActivated(uint256 indexed subscriptionId);
     event PricesUpdated(uint256 indexed subscriptionId, uint256 timestamp);
 }

+ 1 - 0
target_chains/ethereum/contracts/contracts/pulse/scheduler/SchedulerState.sol

@@ -32,6 +32,7 @@ contract SchedulerState {
         bytes32[] priceIds;
         address[] readerWhitelist;
         bool whitelistEnabled;
+        bool isActive;
         UpdateCriteria updateCriteria;
         GasConfig gasConfig;
     }

+ 15 - 0
target_chains/ethereum/contracts/contracts/pulse/scheduler/SchedulerUpgradeable.sol

@@ -61,4 +61,19 @@ contract SchedulerUpgradeable is
     function version() public pure returns (string memory) {
         return "1.0.0";
     }
+
+    // Implementation of deactivateSubscription that forwards to updateSubscription
+    function deactivateSubscription(
+        uint256 subscriptionId
+    ) external onlyManager(subscriptionId) {
+        // Get current subscription parameters
+        SchedulerState.SubscriptionParams memory params = _state
+            .subscriptionParams[subscriptionId];
+
+        // Set isActive to false
+        params.isActive = false;
+
+        // Call updateSubscription to handle the deactivation
+        this.updateSubscription(subscriptionId, params);
+    }
 }

+ 248 - 52
target_chains/ethereum/contracts/forge-test/PulseScheduler.t.sol

@@ -3,6 +3,7 @@
 pragma solidity ^0.8.0;
 
 import "forge-std/Test.sol";
+import "forge-std/console.sol";
 import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
 import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
 import "./utils/PulseTestUtils.t.sol";
@@ -116,15 +117,23 @@ contract SchedulerTest is Test, SchedulerEvents, PulseTestUtils {
                 priceIds: priceIds,
                 readerWhitelist: readerWhitelist,
                 whitelistEnabled: true,
+                isActive: true,
                 updateCriteria: updateCriteria,
                 gasConfig: gasConfig
             });
 
-        // Add subscription
+        // Calculate minimum balance
+        uint256 minimumBalance = scheduler.getMinimumBalance(
+            uint8(priceIds.length)
+        );
+
+        // Add subscription with minimum balance
         vm.expectEmit();
         emit SubscriptionCreated(1, address(this));
 
-        uint256 subscriptionId = scheduler.addSubscription(params);
+        uint256 subscriptionId = scheduler.addSubscription{
+            value: minimumBalance
+        }(params);
         assertEq(subscriptionId, 1, "Subscription ID should be 1");
 
         // Verify subscription was added correctly
@@ -165,7 +174,11 @@ contract SchedulerTest is Test, SchedulerEvents, PulseTestUtils {
         );
 
         assertTrue(status.isActive, "Subscription should be active");
-        assertEq(status.balanceInWei, 0, "Initial balance should be 0");
+        assertEq(
+            status.balanceInWei,
+            minimumBalance,
+            "Initial balance should match minimum balance"
+        );
     }
 
     function testUpdateSubscription() public {
@@ -197,6 +210,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseTestUtils {
                 priceIds: newPriceIds,
                 readerWhitelist: newReaderWhitelist,
                 whitelistEnabled: false, // Changed from true
+                isActive: true,
                 updateCriteria: newUpdateCriteria,
                 gasConfig: newGasConfig
             });
@@ -243,27 +257,132 @@ contract SchedulerTest is Test, SchedulerEvents, PulseTestUtils {
         );
     }
 
-    function testDeactivateSubscription() public {
-        // First add a subscription
-        uint256 subscriptionId = addTestSubscription();
+    function testAddSubscriptionInsufficientFunds() public {
+        // Create subscription parameters
+        bytes32[] memory priceIds = createPriceIds();
+        address[] memory readerWhitelist = new address[](1);
+        readerWhitelist[0] = address(reader);
+
+        SchedulerState.UpdateCriteria memory updateCriteria = SchedulerState
+            .UpdateCriteria({
+                updateOnHeartbeat: true,
+                heartbeatSeconds: 60,
+                updateOnDeviation: true,
+                deviationThresholdBps: 100
+            });
+
+        SchedulerState.GasConfig memory gasConfig = SchedulerState.GasConfig({
+            maxGasMultiplierCapPct: 10_000,
+            maxFeeMultiplierCapPct: 10_000
+        });
+
+        SchedulerState.SubscriptionParams memory params = SchedulerState
+            .SubscriptionParams({
+                priceIds: priceIds,
+                readerWhitelist: readerWhitelist,
+                whitelistEnabled: true,
+                isActive: true,
+                updateCriteria: updateCriteria,
+                gasConfig: gasConfig
+            });
+
+        // Calculate minimum balance
+        uint256 minimumBalance = scheduler.getMinimumBalance(
+            uint8(priceIds.length)
+        );
+
+        // Try to add subscription with insufficient funds
+        vm.expectRevert(abi.encodeWithSelector(InsufficientBalance.selector));
+        scheduler.addSubscription{value: minimumBalance - 1 wei}(params);
+    }
+
+    function testActivateDeactivateSubscription() public {
+        // First add a subscription with minimum balance
+        bytes32[] memory priceIds = createPriceIds();
+        address[] memory readerWhitelist = new address[](1);
+        readerWhitelist[0] = address(reader);
+
+        SchedulerState.UpdateCriteria memory updateCriteria = SchedulerState
+            .UpdateCriteria({
+                updateOnHeartbeat: true,
+                heartbeatSeconds: 60,
+                updateOnDeviation: true,
+                deviationThresholdBps: 100
+            });
+
+        SchedulerState.GasConfig memory gasConfig = SchedulerState.GasConfig({
+            maxGasMultiplierCapPct: 10_000,
+            maxFeeMultiplierCapPct: 10_000
+        });
+
+        SchedulerState.SubscriptionParams memory params = SchedulerState
+            .SubscriptionParams({
+                priceIds: priceIds,
+                readerWhitelist: readerWhitelist,
+                whitelistEnabled: true,
+                isActive: true,
+                updateCriteria: updateCriteria,
+                gasConfig: gasConfig
+            });
+
+        uint256 minimumBalance = scheduler.getMinimumBalance(
+            uint8(priceIds.length)
+        );
+        uint256 subscriptionId = scheduler.addSubscription{
+            value: minimumBalance
+        }(params);
+
+        // Deactivate subscription using updateSubscription
+        params.isActive = false;
 
-        // Deactivate subscription
         vm.expectEmit();
         emit SubscriptionDeactivated(subscriptionId);
+        vm.expectEmit();
+        emit SubscriptionUpdated(subscriptionId);
 
-        scheduler.deactivateSubscription(subscriptionId);
+        scheduler.updateSubscription(subscriptionId, params);
 
         // Verify subscription was deactivated
-        (, SchedulerState.SubscriptionStatus memory status) = scheduler
-            .getSubscription(subscriptionId);
+        (
+            SchedulerState.SubscriptionParams memory storedParams,
+            SchedulerState.SubscriptionStatus memory status
+        ) = scheduler.getSubscription(subscriptionId);
 
         assertFalse(status.isActive, "Subscription should be inactive");
+        assertFalse(
+            storedParams.isActive,
+            "Subscription params should show inactive"
+        );
+
+        // Reactivate subscription using updateSubscription
+        params.isActive = true;
+
+        vm.expectEmit();
+        emit SubscriptionActivated(subscriptionId);
+        vm.expectEmit();
+        emit SubscriptionUpdated(subscriptionId);
+
+        scheduler.updateSubscription(subscriptionId, params);
+
+        // Verify subscription was reactivated
+        (storedParams, status) = scheduler.getSubscription(subscriptionId);
+
+        assertTrue(status.isActive, "Subscription should be active");
+        assertTrue(
+            storedParams.isActive,
+            "Subscription params should show active"
+        );
     }
 
     function testAddFunds() public {
         // First add a subscription
         uint256 subscriptionId = addTestSubscription();
 
+        // Get initial balance (which includes minimum balance)
+        (, SchedulerState.SubscriptionStatus memory initialStatus) = scheduler
+            .getSubscription(subscriptionId);
+        uint256 initialBalance = initialStatus.balanceInWei;
+
         // Add funds
         uint256 fundAmount = 1 ether;
         scheduler.addFunds{value: fundAmount}(subscriptionId);
@@ -274,23 +393,57 @@ contract SchedulerTest is Test, SchedulerEvents, PulseTestUtils {
 
         assertEq(
             status.balanceInWei,
-            fundAmount,
-            "Balance should match added funds"
+            initialBalance + fundAmount,
+            "Balance should match initial balance plus added funds"
         );
     }
 
     function testWithdrawFunds() public {
-        // First add a subscription and funds
-        uint256 subscriptionId = addTestSubscription();
-        uint256 fundAmount = 1 ether;
-        scheduler.addFunds{value: fundAmount}(subscriptionId);
+        // First add a subscription with minimum balance
+        bytes32[] memory priceIds = createPriceIds();
+        uint256 minimumBalance = scheduler.getMinimumBalance(
+            uint8(priceIds.length)
+        );
+
+        address[] memory readerWhitelist = new address[](1);
+        readerWhitelist[0] = address(reader);
+
+        SchedulerState.UpdateCriteria memory updateCriteria = SchedulerState
+            .UpdateCriteria({
+                updateOnHeartbeat: true,
+                heartbeatSeconds: 60,
+                updateOnDeviation: true,
+                deviationThresholdBps: 100
+            });
+
+        SchedulerState.GasConfig memory gasConfig = SchedulerState.GasConfig({
+            maxGasMultiplierCapPct: 10_000,
+            maxFeeMultiplierCapPct: 10_000
+        });
+
+        SchedulerState.SubscriptionParams memory params = SchedulerState
+            .SubscriptionParams({
+                priceIds: priceIds,
+                readerWhitelist: readerWhitelist,
+                whitelistEnabled: true,
+                isActive: true,
+                updateCriteria: updateCriteria,
+                gasConfig: gasConfig
+            });
+
+        uint256 subscriptionId = scheduler.addSubscription{
+            value: minimumBalance
+        }(params);
+
+        // Add extra funds
+        uint256 extraFunds = 1 ether;
+        scheduler.addFunds{value: extraFunds}(subscriptionId);
 
         // Get initial balance
         uint256 initialBalance = address(this).balance;
 
-        // Withdraw half the funds
-        uint256 withdrawAmount = fundAmount / 2;
-        scheduler.withdrawFunds(subscriptionId, withdrawAmount);
+        // Withdraw extra funds
+        scheduler.withdrawFunds(subscriptionId, extraFunds);
 
         // Verify funds were withdrawn
         (, SchedulerState.SubscriptionStatus memory status) = scheduler
@@ -298,14 +451,34 @@ contract SchedulerTest is Test, SchedulerEvents, PulseTestUtils {
 
         assertEq(
             status.balanceInWei,
-            fundAmount - withdrawAmount,
-            "Remaining balance incorrect"
+            minimumBalance,
+            "Remaining balance should be minimum balance"
         );
         assertEq(
             address(this).balance,
-            initialBalance + withdrawAmount,
+            initialBalance + extraFunds,
             "Withdrawn amount not received"
         );
+
+        // Try to withdraw below minimum balance
+        vm.expectRevert(abi.encodeWithSelector(InsufficientBalance.selector));
+        scheduler.withdrawFunds(subscriptionId, 1 wei);
+
+        // Deactivate subscription
+        params.isActive = false;
+        scheduler.updateSubscription(subscriptionId, params);
+
+        // Now we should be able to withdraw all funds
+        scheduler.withdrawFunds(subscriptionId, minimumBalance);
+
+        // Verify all funds were withdrawn
+        (, status) = scheduler.getSubscription(subscriptionId);
+
+        assertEq(
+            status.balanceInWei,
+            0,
+            "Balance should be 0 after withdrawing all funds"
+        );
     }
 
     function testUpdatePriceFeedsWorks() public {
@@ -697,11 +870,17 @@ contract SchedulerTest is Test, SchedulerEvents, PulseTestUtils {
                 priceIds: priceIds,
                 readerWhitelist: emptyWhitelist,
                 whitelistEnabled: false, // No whitelist
+                isActive: true,
                 updateCriteria: updateCriteria,
                 gasConfig: gasConfig
             });
 
-        uint256 subscriptionId = scheduler.addSubscription(params);
+        uint256 minimumBalance = scheduler.getMinimumBalance(
+            uint8(priceIds.length)
+        );
+        uint256 subscriptionId = scheduler.addSubscription{
+            value: minimumBalance
+        }(params);
 
         // Update price feeds
         uint256 fundAmount = 1 ether;
@@ -790,13 +969,13 @@ contract SchedulerTest is Test, SchedulerEvents, PulseTestUtils {
     }
 
     function testGetActiveSubscriptions() public {
-        // Add multiple subscriptions with the test contract as manager
-        addTestSubscription();
-        addTestSubscription();
-        uint256 subscriptionId = addTestSubscription();
+        console.log("Starting testGetActiveSubscriptions");
 
-        // Verify we can deactivate our own subscription
-        scheduler.deactivateSubscription(subscriptionId);
+        // Add two subscriptions with the test contract as manager
+        uint256 sub1 = addTestSubscription();
+        uint256 sub2 = addTestSubscription();
+
+        console.log("Added 2 test subscriptions with IDs:", sub1, sub2);
 
         // Create a subscription with pusher as manager
         vm.startPrank(pusher);
@@ -816,27 +995,38 @@ contract SchedulerTest is Test, SchedulerEvents, PulseTestUtils {
             maxFeeMultiplierCapPct: 10_000
         });
 
-        SchedulerState.SubscriptionParams memory params = SchedulerState
+        SchedulerState.SubscriptionParams memory pusherParams = SchedulerState
             .SubscriptionParams({
                 priceIds: priceIds,
                 readerWhitelist: emptyWhitelist,
                 whitelistEnabled: false,
+                isActive: true,
                 updateCriteria: updateCriteria,
                 gasConfig: gasConfig
             });
 
-        scheduler.addSubscription(params);
+        uint256 minimumBalance = scheduler.getMinimumBalance(
+            uint8(priceIds.length)
+        );
+        uint256 pusherSub = scheduler.addSubscription{value: minimumBalance}(
+            pusherParams
+        );
+        console.log("Added pusher subscription with ID:", pusherSub);
         vm.stopPrank();
 
-        // Get active subscriptions - use owner who has admin rights
-        vm.prank(owner);
-        (
-            uint256[] memory activeIds,
-            SchedulerState.SubscriptionParams[] memory activeParams,
-            uint256 totalCount
-        ) = scheduler.getActiveSubscriptions(0, 10); // Start at index 0, get up to 10 results
+        // Get active subscriptions directly - should work without any special permissions
+        uint256[] memory activeIds;
+        SchedulerState.SubscriptionParams[] memory activeParams;
+        uint256 totalCount;
 
-        // Verify active subscriptions
+        (activeIds, activeParams, totalCount) = scheduler
+            .getActiveSubscriptions(0, 10);
+        console.log(
+            "getActiveSubscriptions succeeded, total count:",
+            totalCount
+        );
+
+        // We added 3 subscriptions and all should be active
         assertEq(activeIds.length, 3, "Should have 3 active subscriptions");
         assertEq(
             activeParams.length,
@@ -867,11 +1057,8 @@ contract SchedulerTest is Test, SchedulerEvents, PulseTestUtils {
 
         // Test pagination - get only the first subscription
         vm.prank(owner);
-        (
-            uint256[] memory firstPageIds,
-            SchedulerState.SubscriptionParams[] memory firstPageParams,
-            uint256 firstPageTotal
-        ) = scheduler.getActiveSubscriptions(0, 1);
+        (uint256[] memory firstPageIds, , uint256 firstPageTotal) = scheduler
+            .getActiveSubscriptions(0, 1);
 
         assertEq(
             firstPageIds.length,
@@ -882,11 +1069,8 @@ contract SchedulerTest is Test, SchedulerEvents, PulseTestUtils {
 
         // Test pagination - get the second page
         vm.prank(owner);
-        (
-            uint256[] memory secondPageIds,
-            SchedulerState.SubscriptionParams[] memory secondPageParams,
-            uint256 secondPageTotal
-        ) = scheduler.getActiveSubscriptions(1, 2);
+        (uint256[] memory secondPageIds, , uint256 secondPageTotal) = scheduler
+            .getActiveSubscriptions(1, 2);
 
         assertEq(
             secondPageIds.length,
@@ -935,11 +1119,15 @@ contract SchedulerTest is Test, SchedulerEvents, PulseTestUtils {
                 priceIds: priceIds,
                 readerWhitelist: readerWhitelist,
                 whitelistEnabled: true,
+                isActive: true,
                 updateCriteria: updateCriteria,
                 gasConfig: gasConfig
             });
 
-        return scheduler.addSubscription(params);
+        uint256 minimumBalance = scheduler.getMinimumBalance(
+            uint8(priceIds.length)
+        );
+        return scheduler.addSubscription{value: minimumBalance}(params);
     }
 
     // Helper function to add a test subscription with variable number of feeds
@@ -968,11 +1156,15 @@ contract SchedulerTest is Test, SchedulerEvents, PulseTestUtils {
                 priceIds: priceIds,
                 readerWhitelist: readerWhitelist,
                 whitelistEnabled: true,
+                isActive: true,
                 updateCriteria: updateCriteria,
                 gasConfig: gasConfig
             });
 
-        return scheduler.addSubscription(params);
+        uint256 minimumBalance = scheduler.getMinimumBalance(
+            uint8(priceIds.length)
+        );
+        return scheduler.addSubscription{value: minimumBalance}(params);
     }
 
     // Helper function to add a test subscription with specific update criteria
@@ -993,11 +1185,15 @@ contract SchedulerTest is Test, SchedulerEvents, PulseTestUtils {
                 priceIds: priceIds,
                 readerWhitelist: readerWhitelist,
                 whitelistEnabled: true,
+                isActive: true,
                 updateCriteria: updateCriteria, // Use provided criteria
                 gasConfig: gasConfig
             });
 
-        return scheduler.addSubscription(params);
+        uint256 minimumBalance = scheduler.getMinimumBalance(
+            uint8(priceIds.length)
+        );
+        return scheduler.addSubscription{value: minimumBalance}(params);
     }
 
     // Required to receive ETH when withdrawing funds