Skip to content

Commit

Permalink
Add values function to iterate tree
Browse files Browse the repository at this point in the history
  • Loading branch information
Vectorized committed Jan 8, 2024
1 parent 502cc1e commit e5ffc80
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 18 deletions.
42 changes: 24 additions & 18 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -911,25 +911,31 @@ ReceiverTest:testOnERC1155BatchReceived() (gas: 48975)
ReceiverTest:testOnERC1155Received() (gas: 46717)
ReceiverTest:testOnERC721Received() (gas: 64108)
ReceiverTest:test__codesize() (gas: 3310)
RedBlackTreeLibTest:testRedBlackTreeBenchUint160() (gas: 3438446)
RedBlackTreeLibTest:testRedBlackTreeBenchUint256() (gas: 5850739)
RedBlackTreeLibTest:testRedBlackTreeBenchUint160() (gas: 3438464)
RedBlackTreeLibTest:testRedBlackTreeBenchUint256() (gas: 5850774)
RedBlackTreeLibTest:testRedBlackTreeClear() (gas: 57543)
RedBlackTreeLibTest:testRedBlackTreeClear(uint256) (runs: 256, μ: 276020, ~: 214270)
RedBlackTreeLibTest:testRedBlackTreeInsertAndRemove(uint256) (runs: 256, μ: 651581, ~: 521468)
RedBlackTreeLibTest:testRedBlackTreeInsertAndRemove2(uint256) (runs: 256, μ: 427519, ~: 388876)
RedBlackTreeLibTest:testRedBlackTreeInsertAndRemove3() (gas: 21537948)
RedBlackTreeLibTest:testRedBlackTreeInsertBenchStep() (gas: 3711319)
RedBlackTreeLibTest:testRedBlackTreeInsertBenchUint160() (gas: 3476937)
RedBlackTreeLibTest:testRedBlackTreeInsertBenchUint256() (gas: 6376001)
RedBlackTreeLibTest:testRedBlackTreeNearest(uint256) (runs: 256, μ: 235558, ~: 224902)
RedBlackTreeLibTest:testRedBlackTreeNearestAfter(uint256) (runs: 256, μ: 252294, ~: 246502)
RedBlackTreeLibTest:testRedBlackTreeNearestBefore(uint256) (runs: 256, μ: 223420, ~: 192904)
RedBlackTreeLibTest:testRedBlackTreePointers() (gas: 91901)
RedBlackTreeLibTest:testRedBlackTreeRejectsEmptyValue() (gas: 3194)
RedBlackTreeLibTest:testRedBlackTreeRemoveViaPointer() (gas: 58199)
RedBlackTreeLibTest:testRedBlackTreeTreeFullReverts() (gas: 50293)
RedBlackTreeLibTest:testRedBlackTreeTryInsertAndRemove() (gas: 56127)
RedBlackTreeLibTest:test__codesize() (gas: 13237)
RedBlackTreeLibTest:testRedBlackTreeClear(uint256) (runs: 256, μ: 303560, ~: 216103)
RedBlackTreeLibTest:testRedBlackTreeInsertAndRemove(uint256) (runs: 256, μ: 640741, ~: 503212)
RedBlackTreeLibTest:testRedBlackTreeInsertAndRemove2(uint256) (runs: 256, μ: 426221, ~: 388854)
RedBlackTreeLibTest:testRedBlackTreeInsertAndRemove3() (gas: 21537965)
RedBlackTreeLibTest:testRedBlackTreeInsertBenchStep() (gas: 3711341)
RedBlackTreeLibTest:testRedBlackTreeInsertBenchUint160() (gas: 3476959)
RedBlackTreeLibTest:testRedBlackTreeInsertBenchUint256() (gas: 6376023)
RedBlackTreeLibTest:testRedBlackTreeInsertOneGas() (gas: 45574)
RedBlackTreeLibTest:testRedBlackTreeInsertTenGas() (gas: 283398)
RedBlackTreeLibTest:testRedBlackTreeInsertThreeGas() (gas: 96286)
RedBlackTreeLibTest:testRedBlackTreeInsertTwoGas() (gas: 69823)
RedBlackTreeLibTest:testRedBlackTreeNearest(uint256) (runs: 256, μ: 232855, ~: 222642)
RedBlackTreeLibTest:testRedBlackTreeNearestAfter(uint256) (runs: 256, μ: 255913, ~: 246502)
RedBlackTreeLibTest:testRedBlackTreeNearestBefore(uint256) (runs: 256, μ: 235523, ~: 200995)
RedBlackTreeLibTest:testRedBlackTreePointers() (gas: 91923)
RedBlackTreeLibTest:testRedBlackTreeRejectsEmptyValue() (gas: 3238)
RedBlackTreeLibTest:testRedBlackTreeRemoveViaPointer() (gas: 58216)
RedBlackTreeLibTest:testRedBlackTreeTreeFullReverts() (gas: 50359)
RedBlackTreeLibTest:testRedBlackTreeTryInsertAndRemove() (gas: 56162)
RedBlackTreeLibTest:testRedBlackTreeValues() (gas: 177033)
RedBlackTreeLibTest:testRedBlackTreeValues(uint256) (runs: 256, μ: 281311, ~: 237749)
RedBlackTreeLibTest:test__codesize() (gas: 14806)
ReentrancyGuardTest:testRecursiveDirectUnguardedCall() (gas: 34256)
ReentrancyGuardTest:testRecursiveIndirectUnguardedCall() (gas: 47773)
ReentrancyGuardTest:testRevertGuardLocked() (gas: 53925)
Expand Down
28 changes: 28 additions & 0 deletions src/utils/RedBlackTreeLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,34 @@ library RedBlackTreeLib {
}
}

/// @dev Returns an array of all the values in the tree in ascending sorted order.
/// WARNING! This function can exhaust the block gas limit if the tree is big.
function values(Tree storage tree) internal view returns (uint256[] memory result) {
uint256 nodes = _nodes(tree);
/// @solidity memory-safe-assembly
assembly {
function visit(current_) {
let cursor_ := or(mload(0x00), current_)
let packed_ := sload(cursor_)
let left_ := and(packed_, _BITMASK_KEY)
if left_ { visit(left_) }
let value_ := shr(_BITPOS_PACKED_VALUE, packed_)
if iszero(value_) { value_ := sload(or(cursor_, _BIT_FULL_VALUE_SLOT)) }
mstore(mload(0x20), value_)
mstore(0x20, add(0x20, mload(0x20)))
let right_ := and(shr(_BITPOS_RIGHT, packed_), _BITMASK_KEY)
if right_ { visit(right_) }
}
result := mload(0x40)
let rootPacked := sload(nodes)
mstore(result, and(rootPacked, _BITMASK_KEY)) // Length of `result`.
mstore(0x00, nodes) // Cache the nodes pointer in scratch space.
mstore(0x20, add(result, 0x20)) // Cache the results offset in scratch space.
mstore(0x40, add(mload(0x20), shl(5, mload(result)))) // Allocate memory.
visit(shr(128, rootPacked))
}
}

/// @dev Returns a pointer to the value `x`.
/// If the value `x` is not in the tree, the returned pointer will be empty.
function find(Tree storage tree, uint256 x) internal view returns (bytes32 result) {
Expand Down
73 changes: 73 additions & 0 deletions test/RedBlackTree.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,79 @@ contract RedBlackTreeLibTest is SoladyTest {
}
}

function testRedBlackTreeInsertOneGas() public {
unchecked {
for (uint256 i; i != 1; ++i) {
tree.insert(i + 1);
}
}
}

function testRedBlackTreeInsertTwoGas() public {
unchecked {
for (uint256 i; i != 2; ++i) {
tree.insert(i + 1);
}
}
}

function testRedBlackTreeInsertThreeGas() public {
unchecked {
for (uint256 i; i != 3; ++i) {
tree.insert(i + 1);
}
}
}

function testRedBlackTreeInsertTenGas() public {
unchecked {
for (uint256 i; i != 10; ++i) {
tree.insert(i + 1);
}
}
}

function testRedBlackTreeValues() public {
testRedBlackTreeValues(3);
}

function testRedBlackTreeValues(uint256 n) public {
unchecked {
n = n & 7;
while (true) {
uint256[] memory values = new uint256[](n);
for (uint256 i; i != n; ++i) {
values[i] = 1 | _random();
tree.tryInsert(values[i]);
}
LibSort.sort(values);
LibSort.uniquifySorted(values);
uint256[] memory retrieved = tree.values();
_checkMemory();
assertEq(retrieved, values);
n = values.length;
if (_random() & 3 == 0) {
tree.clear();
assertEq(tree.values(), new uint256[](0));
}
if (_random() & 3 == 0) {
LibPRNG.PRNG memory prng = LibPRNG.PRNG(_random());
prng.shuffle(values);
for (uint256 i; i != n; ++i) {
tree.tryRemove(values[i]);
}
assertEq(tree.values(), new uint256[](0));
}
if (_random() & 7 == 0) {
tree.clear();
n += _random() & 15;
continue;
}
break;
}
}
}

function testRedBlackTreeRejectsEmptyValue() public {
vm.expectRevert(RedBlackTreeLib.ValueIsEmpty.selector);
tree.insert(0);
Expand Down

0 comments on commit e5ffc80

Please sign in to comment.