Skip to content

Commit fb15906

Browse files
fix(pulse): enforce min bal for all active updated subs (#2642)
* fix: enforce minbalance for all active sub updates * test: fix test * test: remove unnecessary addFunds call
1 parent 795e914 commit fb15906

File tree

2 files changed

+78
-9
lines changed

2 files changed

+78
-9
lines changed

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,8 @@ abstract contract Scheduler is IScheduler, SchedulerState {
9898
}
9999
_validateSubscriptionParams(newParams);
100100

101-
// Check minimum balance if number of feeds increases and subscription remains active
102-
if (
103-
willBeActive &&
104-
newParams.priceIds.length > currentParams.priceIds.length
105-
) {
101+
// Check minimum balance if subscription remains active
102+
if (willBeActive) {
106103
uint256 minimumBalance = this.getMinimumBalance(
107104
uint8(newParams.priceIds.length)
108105
);

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

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,9 +1246,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
12461246
scheduler.updatePriceFeeds(subscriptionId, updateData);
12471247
}
12481248

1249-
function testUpdateSubscriptionEnforcesMinimumBalanceOnAddingFeeds()
1250-
public
1251-
{
1249+
function testUpdateSubscriptionEnforcesMinimumBalance() public {
12521250
// Setup: Create subscription with 2 feeds, funded exactly to minimum
12531251
uint8 initialNumFeeds = 2;
12541252
uint256 subscriptionId = addTestSubscriptionWithFeeds(
@@ -1317,7 +1315,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
13171315
newParams_deact.priceIds = createPriceIds(newNumFeeds_deact);
13181316
newParams_deact.isActive = false; // Deactivate
13191317

1320-
// Action 3: Update (should succeed even with insufficient funds for 4 feeds)
1318+
// Action 3: Update (should succeed even with insufficient min balance for 4 feeds)
13211319
scheduler.updateSubscription(subId_deact, newParams_deact);
13221320

13231321
// Verification 3: Subscription should be inactive and have 4 feeds
@@ -1334,6 +1332,80 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
13341332
newNumFeeds_deact,
13351333
"Number of price feeds should be updated even when deactivating"
13361334
);
1335+
1336+
// Scenario 4: Reducing number of feeds still checks minimum balance
1337+
// Create a subscription with 2 feeds funded to minimum
1338+
uint8 initialNumFeeds_reduce = 2;
1339+
uint256 subId_reduce = addTestSubscriptionWithFeeds(
1340+
scheduler,
1341+
initialNumFeeds_reduce,
1342+
address(reader)
1343+
);
1344+
1345+
// Deplete the balance by updating price feeds multiple times
1346+
uint64 publishTime = SafeCast.toUint64(block.timestamp);
1347+
for (uint i = 0; i < 50; i++) {
1348+
// Advance publish time by 60s for each update to satisfy update criteria
1349+
(
1350+
PythStructs.PriceFeed[] memory priceFeeds_reduce,
1351+
uint64[] memory slots_reduce
1352+
) = createMockPriceFeedsWithSlots(publishTime + (i * 60), 2);
1353+
mockParsePriceFeedUpdatesWithSlots(
1354+
pyth,
1355+
priceFeeds_reduce,
1356+
slots_reduce
1357+
);
1358+
bytes[] memory updateData_reduce = createMockUpdateData(
1359+
priceFeeds_reduce
1360+
);
1361+
vm.prank(pusher);
1362+
scheduler.updatePriceFeeds(subId_reduce, updateData_reduce);
1363+
}
1364+
1365+
// Check that balance is now below minimum for 1 feed
1366+
(, SchedulerState.SubscriptionStatus memory status_reduce) = scheduler
1367+
.getSubscription(subId_reduce);
1368+
uint256 minBalanceForOneFeed = scheduler.getMinimumBalance(1);
1369+
assertTrue(
1370+
status_reduce.balanceInWei < minBalanceForOneFeed,
1371+
"Balance should be below minimum for 1 feed"
1372+
);
1373+
1374+
// Prepare params to reduce feeds from 2 to 1
1375+
(
1376+
SchedulerState.SubscriptionParams memory currentParams_reduce,
1377+
1378+
) = scheduler.getSubscription(subId_reduce);
1379+
SchedulerState.SubscriptionParams
1380+
memory newParams_reduce = currentParams_reduce;
1381+
newParams_reduce.priceIds = new bytes32[](1);
1382+
newParams_reduce.priceIds[0] = currentParams_reduce.priceIds[0];
1383+
1384+
// Action 4: Update should fail due to insufficient balance
1385+
vm.expectRevert(abi.encodeWithSelector(InsufficientBalance.selector));
1386+
scheduler.updateSubscription(subId_reduce, newParams_reduce);
1387+
1388+
// Add funds to cover minimum balance for 1 feed
1389+
uint256 additionalFunds = minBalanceForOneFeed -
1390+
status_reduce.balanceInWei +
1391+
0.01 ether;
1392+
1393+
// Now the update should succeed
1394+
scheduler.updateSubscription{value: additionalFunds}(
1395+
subId_reduce,
1396+
newParams_reduce
1397+
);
1398+
1399+
// Verify the subscription now has 1 feed
1400+
(
1401+
SchedulerState.SubscriptionParams memory updatedParams_reduce,
1402+
1403+
) = scheduler.getSubscription(subId_reduce);
1404+
assertEq(
1405+
updatedParams_reduce.priceIds.length,
1406+
1,
1407+
"Number of price feeds should be reduced to 1"
1408+
);
13371409
}
13381410

13391411
function testGetPricesUnsafeAllFeeds() public {

0 commit comments

Comments
 (0)