Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions src/FilecoinPayV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1596,6 +1596,7 @@ contract FilecoinPayV1 is ReentrancyGuard {
rail.settledUpTo = 0;
rail.endEpoch = 0;
rail.commissionRateBps = 0;
rail.rateChangeQueue.clearEmpty();
}

function updateOperatorRateUsage(OperatorApproval storage approval, uint256 oldRate, uint256 newRate) internal {
Expand Down
47 changes: 27 additions & 20 deletions src/RateChangeQueue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
pragma solidity ^0.8.27;

library RateChangeQueue {
error EmptyQueue();

struct RateChange {
// The payment rate to apply
uint256 rate;
Expand All @@ -18,33 +20,38 @@ library RateChangeQueue {
queue.changes.push(RateChange(rate, untilEpoch));
}

function dequeue(Queue storage queue) internal returns (RateChange memory) {
function dequeue(Queue storage queue) internal returns (RateChange memory change) {
RateChange[] storage c = queue.changes;
require(queue.head < c.length, "Queue is empty");
RateChange memory change = c[queue.head];
delete c[queue.head];

if (isEmpty(queue)) {
queue.head = 0;
// The array is already empty, waste no time zeroing it.
assembly {
sstore(c.slot, 0)
}
} else {
queue.head++;
require(queue.head < c.length, EmptyQueue());
unchecked {
change = c[queue.head];
delete c[queue.head++];
}
}

return change;
// Clears the storage of the Queue
// If the queue isEmpty, all queue storage will be cleared
// Otherwise, the queue is functionally emptied but pending RateChange are not cleared from storage
function clearEmpty(Queue storage queue) internal {
queue.head = 0;
RateChange[] storage c = queue.changes;
assembly ("memory-safe") {
sstore(c.slot, 0)
}
}

function peek(Queue storage queue) internal view returns (RateChange memory) {
require(queue.head < queue.changes.length, "Queue is empty");
return queue.changes[queue.head];
function peek(Queue storage queue) internal view returns (RateChange memory change) {
require(queue.head < queue.changes.length, EmptyQueue());
unchecked {
change = queue.changes[queue.head];
}
}

function peekTail(Queue storage queue) internal view returns (RateChange memory) {
require(queue.head < queue.changes.length, "Queue is empty");
return queue.changes[queue.changes.length - 1];
function peekTail(Queue storage queue) internal view returns (RateChange memory change) {
require(queue.head < queue.changes.length, EmptyQueue());
unchecked {
change = queue.changes[queue.changes.length - 1];
}
}

function isEmpty(Queue storage queue) internal view returns (bool) {
Expand Down
11 changes: 8 additions & 3 deletions test/RateChangeQueue.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,19 @@ contract RateChangeQueueTest is Test {
// Queue should now be empty
assertTrue(RateChangeQueue.isEmpty(queue()));
assertEq(RateChangeQueue.size(queue()), 0);

// Empty queue should have reset to head = 0
queue().clearEmpty();
assertEq(queue().head, 0);
assertEq(queue().changes.length, 0);
}

/// forge-config: default.allow_internal_expect_revert = true
function testEmptyQueueDequeue() public {
createEmptyQueue();

// Test dequeue on empty queue
vm.expectRevert("Queue is empty");
vm.expectRevert(abi.encodeWithSelector(RateChangeQueue.EmptyQueue.selector));
RateChangeQueue.dequeue(queue());
}

Expand All @@ -133,7 +138,7 @@ contract RateChangeQueueTest is Test {
createEmptyQueue();

// Test peek on empty queue
vm.expectRevert("Queue is empty");
vm.expectRevert(abi.encodeWithSelector(RateChangeQueue.EmptyQueue.selector));
RateChangeQueue.peek(queue());
}

Expand All @@ -142,7 +147,7 @@ contract RateChangeQueueTest is Test {
createEmptyQueue();

// Test peekTail on empty queue
vm.expectRevert("Queue is empty");
vm.expectRevert(abi.encodeWithSelector(RateChangeQueue.EmptyQueue.selector));
RateChangeQueue.peekTail(queue());
}

Expand Down