Skip to content

Commit

Permalink
Merge branch 'main' into perf/debruijn
Browse files Browse the repository at this point in the history
  • Loading branch information
JhChoy authored Jan 16, 2024
2 parents 1f0c220 + 42d3796 commit 423a181
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 0 deletions.
106 changes: 106 additions & 0 deletions contracts/Heap.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: -
// License: https://license.clober.io/LICENSE.pdf

pragma solidity ^0.8.0;

import "./SignificantBit.sol";

library Heap {
using SignificantBit for uint256;

error EmptyError();
error AlreadyExistsError();

uint256 public constant B0_BITMAP_KEY = uint256(keccak256("Heap"));
uint256 public constant MAX_UINT_256_MINUS_1 = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe;

function has(mapping(uint256 => uint256) storage heap, uint24 value) internal view returns (bool) {
(uint256 b0b1, uint256 b2) = _split(value);
uint256 mask = 1 << b2;
return heap[b0b1] & mask == mask;
}

function isEmpty(mapping(uint256 => uint256) storage heap) internal view returns (bool) {
return heap[B0_BITMAP_KEY] == 0;
}

function _split(uint24 value) private pure returns (uint256 b0b1, uint8 b2) {
assembly {
b2 := value
b0b1 := shr(8, value)
}
}

function _root(mapping(uint256 => uint256) storage heap) private view returns (uint256 b0b1, uint256 b2) {
uint256 b0 = heap[B0_BITMAP_KEY].leastSignificantBit();
b0b1 = (b0 << 8) | (heap[~b0].leastSignificantBit());
b2 = heap[b0b1].leastSignificantBit();
}

function root(mapping(uint256 => uint256) storage heap) internal view returns (uint24) {
if (isEmpty(heap)) revert EmptyError();

(uint256 b0b1, uint256 b2) = _root(heap);
return uint24((b0b1 << 8) | b2);
}

function push(mapping(uint256 => uint256) storage heap, uint24 value) internal {
(uint256 b0b1, uint256 b2) = _split(value);
uint256 mask = 1 << b2;
uint256 b2Bitmap = heap[b0b1];
if (b2Bitmap & mask > 0) {
revert AlreadyExistsError();
}

heap[b0b1] = b2Bitmap | mask;
if (b2Bitmap == 0) {
mask = 1 << (b0b1 & 0xff);
uint256 b1BitmapKey = ~(b0b1 >> 8);
uint256 b1Bitmap = heap[b1BitmapKey];
heap[b1BitmapKey] = b1Bitmap | mask;
if (b1Bitmap == 0) {
heap[B0_BITMAP_KEY] = heap[B0_BITMAP_KEY] | (1 << ~b1BitmapKey);
}
}
}

function pop(mapping(uint256 => uint256) storage heap) internal {
if (isEmpty(heap)) revert EmptyError();

(uint256 b0b1, uint256 b2) = _root(heap);
uint256 mask = 1 << b2;
uint256 b2Bitmap = heap[b0b1];

heap[b0b1] = b2Bitmap & (~mask);
if (b2Bitmap == mask) {
mask = 1 << (b0b1 & 0xff);
uint256 b1BitmapKey = ~(b0b1 >> 8);
uint256 b1Bitmap = heap[b1BitmapKey];

heap[b1BitmapKey] = b1Bitmap & (~mask);
if (mask == b1Bitmap) {
mask = 1 << (~b1BitmapKey);
heap[B0_BITMAP_KEY] = heap[B0_BITMAP_KEY] & (~mask);
}
}
}

function minGreaterThan(mapping(uint256 => uint256) storage heap, uint24 value) internal view returns (uint24) {
(uint256 b0b1, uint256 b2) = _split(value);
uint256 b2Bitmap = (MAX_UINT_256_MINUS_1 << b2) & heap[b0b1];
if (b2Bitmap == 0) {
uint256 b0 = b0b1 >> 8;
uint256 b1Bitmap = (MAX_UINT_256_MINUS_1 << (b0b1 & 0xff)) & heap[~b0];
if (b1Bitmap == 0) {
uint256 b0Bitmap = (MAX_UINT_256_MINUS_1 << b0) & heap[B0_BITMAP_KEY];
if (b0Bitmap == 0) return 0;
b0 = b0Bitmap.leastSignificantBit();
b1Bitmap = heap[~b0];
}
b0b1 = (b0 << 8) | b1Bitmap.leastSignificantBit();
b2Bitmap = heap[b0b1];
}
b2 = b2Bitmap.leastSignificantBit();
return uint24((b0b1 << 8) | b2);
}
}
42 changes: 42 additions & 0 deletions contracts/interfaces/CloberHeap.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: -
// License: https://license.clober.io/LICENSE.pdf

pragma solidity ^0.8.0;

/**
* @title Octopus Heap
* @notice Heap data structure that can hold up to 256 uint16 values.
* Each value is stored by having the quotient divided by 2^8 added to the heap,
* and the remainder expressed as a flag on a bitmap.
*/
interface CloberHeap {
/**
* @notice Checks if the specified value is present in the heap.
* @param value The value to check for.
* @return True if the value is present in the heap, false otherwise.
*/
function has(uint24 value) external view returns (bool);

/**
* @notice Checks if the heap is empty.
* @return True if the heap is empty, false otherwise.
*/
function isEmpty() external view returns (bool);

/**
* @notice Pushes a value onto the heap.
* @param value The value to push onto the heap.
*/
function push(uint24 value) external;

/**
* @notice Pops a value from the heap.
*/
function pop() external;

/**
* @notice Returns the root value of the heap.
* @return The root value of the heap.
*/
function root() external view returns (uint24);
}
37 changes: 37 additions & 0 deletions contracts/mocks/HeapWrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: -
// License: https://license.clober.io/LICENSE.pdf

pragma solidity ^0.8.0;

import "../Heap.sol";
import "../interfaces/CloberHeap.sol";

contract HeapWrapper is CloberHeap {
using Heap for mapping(uint256 => uint256);

mapping(uint256 => uint256) private _heap;

function has(uint24 value) external view returns (bool) {
return _heap.has(value);
}

function isEmpty() external view returns (bool) {
return _heap.isEmpty();
}

function push(uint24 value) external {
_heap.push(value);
}

function pop() external {
_heap.pop();
}

function root() external view returns (uint24) {
return _heap.root();
}

function minGreaterThan(uint24 value) external view returns (uint24) {
return _heap.minGreaterThan(value);
}
}
99 changes: 99 additions & 0 deletions test/foundry/Heap.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: -
// License: https://license.clober.io/LICENSE.pdf

pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "../../contracts/mocks/HeapWrapper.sol";

contract HeapTest is Test {
uint16 private constant _MAX_HEAP_SIZE = type(uint16).max;

uint24 private _min;

HeapWrapper testWrapper;

function setUp() public {
testWrapper = new HeapWrapper();
_min = type(uint24).max;
}

function _push(uint24[] memory numbers) private returns (uint24[] memory elements) {
uint256 length;
for (uint256 i = 0; i < numbers.length; ++i) {
uint24 number = numbers[i];
if (testWrapper.has(number)) continue;
if (number < _min) _min = number;

assertFalse(testWrapper.has(number), "BEFORE_PUSH");
testWrapper.push(number);
numbers[length] = number;
length += 1;
assertTrue(testWrapper.has(number), "AFTER_PUSH");
assertEq(testWrapper.root(), _min, "ASSERT_MIN");
}

elements = new uint24[](length);
for (uint256 i = 0; i < length; ++i) {
elements[i] = numbers[i];
}
}

function testPop(uint24[] calldata numbers) public {
vm.assume(1 <= numbers.length && numbers.length <= _MAX_HEAP_SIZE);
assertTrue(testWrapper.isEmpty(), "HAS_TO_BE_EMPTY");
uint24[] memory elements = _push(numbers);
uint256 length = elements.length;

for (uint256 i = 0; i < length; i++) {
for (uint256 j = i + 1; j < length; j++) {
if (elements[i] > elements[j]) {
uint24 temp = elements[j];
elements[j] = elements[i];
elements[i] = temp;
}
}
}

for (uint256 i = 0; i < length - 1; i++) {
if (elements[i] > 0) {
assertTrue(testWrapper.minGreaterThan(elements[i] - 1) == elements[i], "WRONG_MIN");
}
assertTrue(testWrapper.minGreaterThan(elements[i]) == elements[i + 1], "WRONG_MIN");
}
assertTrue(testWrapper.minGreaterThan(elements[length - 1]) == 0, "NO_MORE_MIN_VALUE");

assertFalse(testWrapper.isEmpty(), "HAS_TO_BE_OCCUPIED");
while (!testWrapper.isEmpty()) {
_min = testWrapper.root();
assertTrue(testWrapper.has(_min), "HEAP_HAS_ROOT");
uint256 min;
if (length == 1) {
assertTrue(testWrapper.minGreaterThan(_min) == 0, "NO_MORE_MIN_VALUE");
} else {
min = testWrapper.minGreaterThan(_min);
}
testWrapper.pop();
length -= 1;
if (length > 0) assertTrue(testWrapper.root() == min, "WRONG_MIN");

assertFalse(testWrapper.has(_min), "ROOT_HAS_BEEN_POPPED");
if (testWrapper.isEmpty()) break;
assertGt(testWrapper.root(), _min, "ROOT_HAS_TO_BE_MIN");
}
}

function testPushExistNumber(uint24 number) public {
testWrapper.push(number);
vm.expectRevert(abi.encodeWithSelector(Heap.AlreadyExistsError.selector));
testWrapper.push(number);
}

function testPopWhenEmpty() public {
vm.expectRevert(abi.encodeWithSelector(Heap.EmptyError.selector));
testWrapper.pop();

vm.expectRevert(abi.encodeWithSelector(Heap.EmptyError.selector));
testWrapper.root();
}
}

0 comments on commit 423a181

Please sign in to comment.