Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions target_chains/ethereum/contracts/contracts/pulse/Scheduler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ abstract contract Scheduler is IScheduler, SchedulerState {
revert InsufficientBalance();
}

// Check deposit limit for permanent subscriptions
if (subscriptionParams.isPermanent && msg.value > MAX_DEPOSIT_LIMIT) {
revert MaxDepositLimitExceeded();
}

// Set subscription to active
subscriptionParams.isActive = true;

Expand Down Expand Up @@ -519,11 +524,33 @@ abstract contract Scheduler is IScheduler, SchedulerState {
/// BALANCE MANAGEMENT

function addFunds(uint256 subscriptionId) external payable override {
if (!_state.subscriptionParams[subscriptionId].isActive) {
SubscriptionParams storage params = _state.subscriptionParams[
subscriptionId
];
SubscriptionStatus storage status = _state.subscriptionStatuses[
subscriptionId
];

if (!params.isActive) {
revert InactiveSubscription();
}

_state.subscriptionStatuses[subscriptionId].balanceInWei += msg.value;
// Check deposit limit for permanent subscriptions
if (params.isPermanent && msg.value > MAX_DEPOSIT_LIMIT) {
revert MaxDepositLimitExceeded();
}

status.balanceInWei += msg.value;

// If subscription is active, ensure minimum balance is maintained
if (params.isActive) {
uint256 minimumBalance = this.getMinimumBalance(
uint8(params.priceIds.length)
);
if (status.balanceInWei < minimumBalance) {
revert InsufficientBalance();
}
}
}

function withdrawFunds(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ error DuplicateWhitelistAddress(address addr);

// Payment errors
error KeeperPaymentFailed();
error MaxDepositLimitExceeded();
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ contract SchedulerState {
uint8 public constant MAX_PRICE_IDS_PER_SUBSCRIPTION = 255;
/// Maximum number of addresses in the reader whitelist
uint8 public constant MAX_READER_WHITELIST_SIZE = 255;
/// Maximum deposit limit for permanent subscriptions in wei
uint256 public constant MAX_DEPOSIT_LIMIT = 100 ether;

/// Maximum time in the past (relative to current block timestamp)
/// for which a price update timestamp is considered valid
Expand Down
245 changes: 245 additions & 0 deletions target_chains/ethereum/contracts/forge-test/PulseScheduler.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,145 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
);
}

function testAddFundsEnforcesMinimumBalance() public {
// First add a subscription with minimum balance
uint256 subscriptionId = addTestSubscription(
scheduler,
address(reader)
);

// Get subscription parameters and initial balance
(
SchedulerState.SubscriptionParams memory params,
SchedulerState.SubscriptionStatus memory status
) = scheduler.getSubscription(subscriptionId);

// Calculate minimum balance
uint256 minimumBalance = scheduler.getMinimumBalance(
uint8(params.priceIds.length)
);

// Verify initial balance is at minimum
assertEq(
status.balanceInWei,
minimumBalance,
"Initial balance should equal minimum balance"
);

// Add some funds to the subscription
uint256 additionalFunds = 0.1 ether;
scheduler.addFunds{value: additionalFunds}(subscriptionId);

// Verify funds were added
(, SchedulerState.SubscriptionStatus memory statusAfterAdd) = scheduler
.getSubscription(subscriptionId);
assertEq(
statusAfterAdd.balanceInWei,
minimumBalance + additionalFunds,
"Balance should be increased by the added funds"
);

// Now create a new subscription but don't fund it fully
SchedulerState.SubscriptionParams
memory newParams = createDefaultSubscriptionParams(
2,
address(reader)
);

// Calculate minimum balance for this new subscription
uint256 newMinimumBalance = scheduler.getMinimumBalance(
uint8(newParams.priceIds.length)
);

// Try to create with insufficient funds
uint256 insufficientFunds = newMinimumBalance - 1 wei;
vm.expectRevert(abi.encodeWithSelector(InsufficientBalance.selector));
scheduler.createSubscription{value: insufficientFunds}(newParams);

// Create with sufficient funds
uint256 newSubscriptionId = scheduler.createSubscription{
value: newMinimumBalance
}(newParams);

// Verify subscription was created with minimum balance
(, SchedulerState.SubscriptionStatus memory newStatus) = scheduler
.getSubscription(newSubscriptionId);
assertEq(
newStatus.balanceInWei,
newMinimumBalance,
"New subscription balance should equal minimum balance"
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not testing balance check in addFunds. here you need to send some updates to drain the balance and then try to add funds

}

function testAddFundsEnforcesMinimumBalanceForPermanentSubscription()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think that this is necessary.

public
{
// Create a non-permanent subscription first
uint256 subscriptionId = addTestSubscription(
scheduler,
address(reader)
);

// Get subscription parameters and initial balance
(
SchedulerState.SubscriptionParams memory params,
SchedulerState.SubscriptionStatus memory status
) = scheduler.getSubscription(subscriptionId);

// Calculate minimum balance
uint256 minimumBalance = scheduler.getMinimumBalance(
uint8(params.priceIds.length)
);

// Verify initial balance is at minimum
assertEq(
status.balanceInWei,
minimumBalance,
"Initial balance should equal minimum balance"
);

// Make it permanent
params.isPermanent = true;
scheduler.updateSubscription(subscriptionId, params);

// Try to add funds - this should succeed since we're adding to a permanent subscription
uint256 additionalFunds = 0.1 ether;
scheduler.addFunds{value: additionalFunds}(subscriptionId);

// Verify funds were added
(, SchedulerState.SubscriptionStatus memory statusAfter) = scheduler
.getSubscription(subscriptionId);
assertEq(
statusAfter.balanceInWei,
minimumBalance + additionalFunds,
"Balance should be increased by the added funds"
);

// Now test the deposit limit for permanent subscriptions
uint256 maxDepositLimit = 100 ether; // MAX_DEPOSIT_LIMIT from SchedulerState

// Try to add funds exceeding the deposit limit
vm.expectRevert(
abi.encodeWithSelector(MaxDepositLimitExceeded.selector)
);
scheduler.addFunds{value: maxDepositLimit + 1}(subscriptionId);

// Add funds within the deposit limit
uint256 validDeposit = 1 ether;
scheduler.addFunds{value: validDeposit}(subscriptionId);

// Verify funds were added
(
,
SchedulerState.SubscriptionStatus memory statusAfterValidDeposit
) = scheduler.getSubscription(subscriptionId);
assertEq(
statusAfterValidDeposit.balanceInWei,
minimumBalance + additionalFunds + validDeposit,
"Balance should be increased by the valid deposit"
);
}

function testWithdrawFunds() public {
// Add a subscription and get the parameters
uint256 subscriptionId = addTestSubscription(
Expand Down Expand Up @@ -779,6 +918,112 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
scheduler.updateSubscription(subscriptionId, params);
}

function testPermanentSubscriptionDepositLimit() public {
// Test 1: Creating a permanent subscription with deposit exceeding MAX_DEPOSIT_LIMIT should fail
SchedulerState.SubscriptionParams
memory params = createDefaultSubscriptionParams(2, address(reader));
params.isPermanent = true;

uint256 maxDepositLimit = 100 ether; // Same as MAX_DEPOSIT_LIMIT in SchedulerState
uint256 excessiveDeposit = maxDepositLimit + 1 ether;
vm.deal(address(this), excessiveDeposit);

vm.expectRevert(
abi.encodeWithSelector(MaxDepositLimitExceeded.selector)
);
scheduler.createSubscription{value: excessiveDeposit}(params);

// Test 2: Creating a permanent subscription with deposit within MAX_DEPOSIT_LIMIT should succeed
uint256 validDeposit = maxDepositLimit;
vm.deal(address(this), validDeposit);

uint256 subscriptionId = scheduler.createSubscription{
value: validDeposit
}(params);

// Verify subscription was created correctly
(
SchedulerState.SubscriptionParams memory storedParams,
SchedulerState.SubscriptionStatus memory status
) = scheduler.getSubscription(subscriptionId);

assertTrue(
storedParams.isPermanent,
"Subscription should be permanent"
);
assertEq(
status.balanceInWei,
validDeposit,
"Balance should match deposit amount"
);

// Test 3: Adding funds to a permanent subscription with deposit exceeding MAX_DEPOSIT_LIMIT should fail
uint256 largeAdditionalFunds = maxDepositLimit + 1;
vm.deal(address(this), largeAdditionalFunds);

vm.expectRevert(
abi.encodeWithSelector(MaxDepositLimitExceeded.selector)
);
scheduler.addFunds{value: largeAdditionalFunds}(subscriptionId);

// Test 4: Adding funds to a permanent subscription within MAX_DEPOSIT_LIMIT should succeed
// Create a non-permanent subscription to test partial funding
SchedulerState.SubscriptionParams
memory nonPermanentParams = createDefaultSubscriptionParams(
2,
address(reader)
);
uint256 minimumBalance = scheduler.getMinimumBalance(
uint8(nonPermanentParams.priceIds.length)
);
vm.deal(address(this), minimumBalance);

uint256 nonPermanentSubId = scheduler.createSubscription{
value: minimumBalance
}(nonPermanentParams);

// Add funds to the non-permanent subscription (should be within limit)
uint256 validAdditionalFunds = 5 ether;
vm.deal(address(this), validAdditionalFunds);

scheduler.addFunds{value: validAdditionalFunds}(nonPermanentSubId);

// Verify funds were added correctly
(
,
SchedulerState.SubscriptionStatus memory nonPermanentStatus
) = scheduler.getSubscription(nonPermanentSubId);

assertEq(
nonPermanentStatus.balanceInWei,
minimumBalance + validAdditionalFunds,
"Balance should be increased by the funded amount"
);

// Test 5: Non-permanent subscriptions should not be subject to the deposit limit
uint256 largeDeposit = maxDepositLimit * 2;
vm.deal(address(this), largeDeposit);

SchedulerState.SubscriptionParams
memory unlimitedParams = createDefaultSubscriptionParams(
2,
address(reader)
);
uint256 unlimitedSubId = scheduler.createSubscription{
value: largeDeposit
}(unlimitedParams);

// Verify subscription was created with the large deposit
(, SchedulerState.SubscriptionStatus memory unlimitedStatus) = scheduler
.getSubscription(unlimitedSubId);

assertEq(
unlimitedStatus.balanceInWei,
largeDeposit,
"Non-permanent subscription should accept large deposits"
);
}

function testAnyoneCanAddFunds() public {
// Create a subscription
uint256 subscriptionId = addTestSubscription(
Expand Down
Loading