Skip to content

Commit

Permalink
Refactor PriorityQueue
Browse files Browse the repository at this point in the history
  • Loading branch information
AllFi committed Dec 13, 2023
1 parent 7168553 commit f823aec
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 77 deletions.
37 changes: 13 additions & 24 deletions src/zkbob/utils/PriorityQueue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,65 +22,54 @@ struct PendingCommitment {
library PriorityQueue {
using PriorityQueue for Queue;

/// @notice Container that stores priority operations
/// @param data The inner mapping that saves priority operation by its index
/// @param head The pointer to the first unprocessed priority operation, equal to the tail if the queue is empty
/// @notice Container that stores pending commitments
/// @param data The inner mapping that saves poending commitment by its index
/// @param head The pointer to the first unprocessed pending commitment, equal to the tail if the queue is empty
/// @param tail The pointer to the free slot
struct Queue {
mapping(uint256 => PendingCommitment) data;
uint256 tail;
uint256 head;
}

/// @notice Returns zero if and only if no operations were processed from the queue
/// @return Index of the oldest priority operation that wasn't processed yet
function getFirstUnprocessedPriorityTx(Queue storage _queue) internal view returns (uint256) {
return _queue.head;
}

/// @return The total number of priority operations that were added to the priority queue, including all processed ones
function getTotalPriorityTxs(Queue storage _queue) internal view returns (uint256) {
return _queue.tail;
}

/// @return The total number of unprocessed priority operations in a priority queue
/// @return The total number of unprocessed pending commitments in a priority queue
function getSize(Queue storage _queue) internal view returns (uint256) {
return uint256(_queue.tail - _queue.head);
}

/// @return Whether the priority queue contains no operations
/// @return Whether the priority queue contains no pending commitments
function isEmpty(Queue storage _queue) internal view returns (bool) {
return _queue.tail == _queue.head;
}

/// @notice Add the priority operation to the end of the priority queue
function pushBack(Queue storage _queue, PendingCommitment memory _operation) internal {
/// @notice Add the pending commitment to the end of the priority queue
function pushBack(Queue storage _queue, PendingCommitment memory _commitment) internal {
// Save value into the stack to avoid double reading from the storage
uint256 tail = _queue.tail;

_queue.data[tail] = _operation;
_queue.data[tail] = _commitment;
_queue.tail = tail + 1;
}

function list(Queue storage _queue) external view returns ( PendingCommitment[] memory) {
function list(Queue storage _queue) internal view returns ( PendingCommitment[] memory) {
PendingCommitment[] memory result = new PendingCommitment[] (_queue.getSize());
for (uint256 index = _queue.head; index < _queue.tail; index++) {
result[index-_queue.head] = _queue.data[index];
}
return result;
}

/// @return The first unprocessed priority operation from the queue
/// @return The first unprocessed pending commitment from the queue
function front(Queue storage _queue) internal view returns (PendingCommitment memory) {
require(!_queue.isEmpty(), "D"); // priority queue is empty
require(!_queue.isEmpty(), "ZkBobPool: queue is empty"); // priority queue is empty

return _queue.data[_queue.head];
}

/// @notice Remove the first unprocessed priority operation from the queue
/// @notice Remove the first unprocessed pending commitment from the queue
/// @return pendingCommitment that was popped from the priority queue
function popFront(Queue storage _queue) internal returns (PendingCommitment memory pendingCommitment) {
require(!_queue.isEmpty(), "s"); // priority queue is empty
require(!_queue.isEmpty(), "ZkBobPool: queue is empty"); // priority queue is empty

// Save value into the stack to avoid double reading from the storage
uint256 head = _queue.head;
Expand Down
135 changes: 82 additions & 53 deletions test/libraries/PriorityQueue.t.sol
Original file line number Diff line number Diff line change
@@ -1,84 +1,113 @@
// SPDX-License-Identifier: CC0-1.0

pragma solidity ^0.8.15;

import "forge-std/Test.sol";
import "../../src/zkbob/utils/PriorityQueue.sol";
import {PriorityQueue, PendingCommitment} from "../../src/zkbob/utils/PriorityQueue.sol";
import "forge-std/console.sol";

contract DummyQueue {
using PriorityQueue for PriorityQueue.Queue;

PriorityQueue.Queue _queue;
address immutable prover1 = address(bytes20(new bytes(20)));
contract PriorityQueueTest is Test {
address immutable prover1 = makeAddr("Prover #1");
DummyQueue _queue;

function list() external view returns (PendingCommitment[] memory) {
return _queue.list();
function setUp() external {
_queue = new DummyQueue();
}

function pushBack(PendingCommitment memory _operation) external {
_queue.pushBack(_operation);
}
function testEmptyQueue() external {
assertEq(_queue.getSize(), 0);
assertEq(_queue.isEmpty(), true);

function head() external view returns (uint256) {
return _queue.head;
}
vm.expectRevert("ZkBobPool: queue is empty");
_queue.popFront();

function tail() external view returns (uint256) {
return _queue.tail;
}
vm.expectRevert("ZkBobPool: queue is empty");
_queue.front();

function popFront() external returns (PendingCommitment memory pendingCommitments) {
return _queue.popFront();
PendingCommitment[] memory ops = _queue.list();
assertEq(0, ops.length);
}
}

contract PriorityQueueTest is Test {
using PriorityQueue for PriorityQueue.Queue;
function testPushBackPopFront() external {
for (uint256 i = 0; i < 100; i++) {
_queue.pushBack(_newOp(i));

address immutable prover1 = makeAddr("Prover #1");
DummyQueue _queueContract;
assertEq(i, _queue.head());
assertEq(i + 1, _queue.tail());
assertEq(1, _queue.getSize());

function setUp() external {
_queueContract = new DummyQueue();
PendingCommitment memory commitment = _queue.front();
_verifyOp(i, commitment);

PendingCommitment[] memory ops = _queue.list();
assertEq(1, ops.length);
_verifyOp(i, ops[0]);

PendingCommitment memory popped = _queue.popFront();
_verifyOp(i, popped);
}
}

function newOp(
function _newOp(
uint256 id
) external view returns (PendingCommitment memory) {
return PendingCommitment(id, prover1, uint64(0), uint64(0));
) internal pure returns (PendingCommitment memory) {
address prover = address(uint160(uint256(keccak256(abi.encodePacked("prover", id)))));
uint64 fee = uint64(uint256(keccak256(abi.encodePacked("fee", id))));
uint64 timestamp = uint64(uint256(keccak256(abi.encodePacked("timestamp", id))));
return PendingCommitment(id, prover, fee, timestamp);
}

function testEmptyQueue() external {
PendingCommitment[] memory ops = _queueContract.list();
assertEq(0, ops.length);
function _verifyOp(
uint256 id,
PendingCommitment memory op
) internal {
address prover = address(uint160(uint256(keccak256(abi.encodePacked("prover", id)))));
uint64 fee = uint64(uint256(keccak256(abi.encodePacked("fee", id))));
uint64 timestamp = uint64(uint256(keccak256(abi.encodePacked("timestamp", id))));

assertEq(op.commitment, id);
assertEq(op.prover, prover);
assertEq(op.fee, fee);
assertEq(op.timestamp, timestamp);
}
}

function testPushBack() external {
_queueContract.pushBack(this.newOp(0));

assertEq(0, _queueContract.head());
assertEq(1, _queueContract.tail());
/**
* @dev Helper contract to test PriorityQueue library
* Without this contract forge coverage doesn't work properly
*/
contract DummyQueue {
PriorityQueue.Queue _queue;

assertEq(1, _queueContract.list().length);
function list() external view returns (PendingCommitment[] memory) {
return PriorityQueue.list(_queue);
}

_queueContract.pushBack(this.newOp(2));
function pushBack(PendingCommitment memory _operation) external {
PriorityQueue.pushBack(_queue, _operation);
}

assertEq(2, _queueContract.list().length);
function head() external view returns (uint256) {
return _queue.head;
}

function testPopFront() external {
_queueContract.pushBack(this.newOp(0));
_queueContract.pushBack(this.newOp(1));
_queueContract.pushBack(this.newOp(2));

assertEq(0, _queueContract.head());
assertEq(3, _queueContract.tail());
function tail() external view returns (uint256) {
return _queue.tail;
}

PendingCommitment memory first = _queueContract.popFront();
function getSize() external view returns (uint256) {
return PriorityQueue.getSize(_queue);
}

assertEq(first.commitment, uint256(0));
function isEmpty() external view returns (bool) {
return PriorityQueue.isEmpty(_queue);
}

assertEq(1, _queueContract.head());
assertEq(3, _queueContract.tail());
function front() external view returns (PendingCommitment memory) {
return PriorityQueue.front(_queue);
}

assertEq(2, _queueContract.list().length);
function popFront() external returns (PendingCommitment memory pendingCommitments) {
return PriorityQueue.popFront(_queue);
}
}
}

0 comments on commit f823aec

Please sign in to comment.