Skip to content

Commit c8d40f2

Browse files
committed
feat: optimize gas when querying active subscriptions
1 parent e2c7e8f commit c8d40f2

File tree

2 files changed

+59
-27
lines changed

2 files changed

+59
-27
lines changed

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

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ abstract contract Scheduler is IScheduler, SchedulerState {
5656
// Map subscription ID to manager
5757
_state.subscriptionManager[subscriptionId] = msg.sender;
5858

59+
_addToActiveSubscriptions(subscriptionId);
60+
5961
emit SubscriptionCreated(subscriptionId, msg.sender);
6062
return subscriptionId;
6163
}
@@ -100,10 +102,12 @@ abstract contract Scheduler is IScheduler, SchedulerState {
100102
}
101103

102104
currentParams.isActive = true;
105+
_addToActiveSubscriptions(subscriptionId);
103106
emit SubscriptionActivated(subscriptionId);
104107
} else if (wasActive && !willBeActive) {
105108
// Deactivating a subscription
106109
currentParams.isActive = false;
110+
_removeFromActiveSubscriptions(subscriptionId);
107111
emit SubscriptionDeactivated(subscriptionId);
108112
}
109113

@@ -691,14 +695,7 @@ abstract contract Scheduler is IScheduler, SchedulerState {
691695
uint256 totalCount
692696
)
693697
{
694-
// Count active subscriptions first to determine total count
695-
// TODO: Optimize this. store numActiveSubscriptions or something.
696-
totalCount = 0;
697-
for (uint256 i = 1; i < _state.subscriptionNumber; i++) {
698-
if (_state.subscriptionParams[i].isActive) {
699-
totalCount++;
700-
}
701-
}
698+
totalCount = _state.activeSubscriptionIds.length;
702699

703700
// If startIndex is beyond the total count, return empty arrays
704701
if (startIndex >= totalCount) {
@@ -715,25 +712,13 @@ abstract contract Scheduler is IScheduler, SchedulerState {
715712
subscriptionIds = new uint256[](resultCount);
716713
subscriptionParams = new SubscriptionParams[](resultCount);
717714

718-
// Find and populate the requested page of active subscriptions
719-
uint256 activeIndex = 0;
720-
uint256 resultIndex = 0;
721-
722-
for (
723-
uint256 i = 1;
724-
i < _state.subscriptionNumber && resultIndex < resultCount;
725-
i++
726-
) {
727-
if (_state.subscriptionParams[i].isActive) {
728-
if (activeIndex >= startIndex) {
729-
subscriptionIds[resultIndex] = i;
730-
subscriptionParams[resultIndex] = _state.subscriptionParams[
731-
i
732-
];
733-
resultIndex++;
734-
}
735-
activeIndex++;
736-
}
715+
// Populate the arrays with the requested page of active subscriptions
716+
for (uint256 i = 0; i < resultCount; i++) {
717+
uint256 subscriptionId = _state.activeSubscriptionIds[
718+
startIndex + i
719+
];
720+
subscriptionIds[i] = subscriptionId;
721+
subscriptionParams[i] = _state.subscriptionParams[subscriptionId];
737722
}
738723

739724
return (subscriptionIds, subscriptionParams, totalCount);
@@ -790,4 +775,45 @@ abstract contract Scheduler is IScheduler, SchedulerState {
790775
}
791776
_;
792777
}
778+
779+
/**
780+
* @notice Adds a subscription to the active subscriptions list.
781+
* @param subscriptionId The ID of the subscription to add.
782+
*/
783+
function _addToActiveSubscriptions(uint256 subscriptionId) internal {
784+
// Only add if not already in the list
785+
if (_state.activeSubscriptionIndex[subscriptionId] == 0) {
786+
_state.activeSubscriptionIds.push(subscriptionId);
787+
_state.activeSubscriptionIndex[subscriptionId] = _state
788+
.activeSubscriptionIds
789+
.length;
790+
}
791+
}
792+
793+
/**
794+
* @notice Removes a subscription from the active subscriptions list.
795+
* @param subscriptionId The ID of the subscription to remove.
796+
*/
797+
function _removeFromActiveSubscriptions(uint256 subscriptionId) internal {
798+
uint256 index = _state.activeSubscriptionIndex[subscriptionId];
799+
800+
// Only remove if it's in the list
801+
if (index > 0) {
802+
// Adjust index to be 0-based instead of 1-based
803+
index = index - 1;
804+
805+
// If it's not the last element, move the last element to its position
806+
if (index < _state.activeSubscriptionIds.length - 1) {
807+
uint256 lastId = _state.activeSubscriptionIds[
808+
_state.activeSubscriptionIds.length - 1
809+
];
810+
_state.activeSubscriptionIds[index] = lastId;
811+
_state.activeSubscriptionIndex[lastId] = index + 1; // 1-based index
812+
}
813+
814+
// Remove the last element
815+
_state.activeSubscriptionIds.pop();
816+
_state.activeSubscriptionIndex[subscriptionId] = 0;
817+
}
818+
}
793819
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ contract SchedulerState {
3535
mapping(uint256 => mapping(bytes32 => PythStructs.PriceFeed)) priceUpdates;
3636
/// Sub ID -> manager address
3737
mapping(uint256 => address) subscriptionManager;
38+
/// Array of active subscription IDs.
39+
/// Gas optimization to avoid scanning through all subscriptions when querying for all active ones.
40+
uint256[] activeSubscriptionIds;
41+
/// Sub ID -> index in activeSubscriptionIds array + 1 (0 means not in array).
42+
/// This lets us avoid a linear scan of `activeSubscriptionIds` when deactivating a subscription.
43+
mapping(uint256 => uint256) activeSubscriptionIndex;
3844
}
3945
State internal _state;
4046

0 commit comments

Comments
 (0)