Skip to content

Commit a0b53a6

Browse files
committed
fix: enforce minbalance for all active sub updates
1 parent edddc0a commit a0b53a6

File tree

2 files changed

+81
-9
lines changed

2 files changed

+81
-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
@@ -102,11 +102,8 @@ abstract contract Scheduler is IScheduler, SchedulerState {
102102
// Validate the new parameters, including setting default gas config
103103
_validateAndPrepareSubscriptionParams(newParams);
104104

105-
// Check minimum balance if number of feeds increases and subscription remains active
106-
if (
107-
willBeActive &&
108-
newParams.priceIds.length > currentParams.priceIds.length
109-
) {
105+
// Check minimum balance if subscription remains active
106+
if (willBeActive) {
110107
uint256 minimumBalance = this.getMinimumBalance(
111108
uint8(newParams.priceIds.length)
112109
);

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

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,9 +1242,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
12421242
scheduler.updatePriceFeeds(subscriptionId, updateData, priceIds);
12431243
}
12441244

1245-
function testUpdateSubscriptionEnforcesMinimumBalanceOnAddingFeeds()
1246-
public
1247-
{
1245+
function testUpdateSubscriptionEnforcesMinimumBalance() public {
12481246
// Setup: Create subscription with 2 feeds, funded exactly to minimum
12491247
uint8 initialNumFeeds = 2;
12501248
uint256 subscriptionId = addTestSubscriptionWithFeeds(
@@ -1313,7 +1311,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
13131311
newParams_deact.priceIds = createPriceIds(newNumFeeds_deact);
13141312
newParams_deact.isActive = false; // Deactivate
13151313

1316-
// Action 3: Update (should succeed even with insufficient funds for 4 feeds)
1314+
// Action 3: Update (should succeed even with insufficient min balance for 4 feeds)
13171315
scheduler.updateSubscription(subId_deact, newParams_deact);
13181316

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

13351410
function testGetPricesUnsafeAllFeeds() public {

0 commit comments

Comments
 (0)