Files

362 lines
14 KiB
Solidity

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import {Vm} from "forge-std/Vm.sol";
import {TickBitmap} from "../../src/libraries/TickBitmap.sol";
import {TickMath} from "../../src/libraries/TickMath.sol";
contract TickBitmapTest is Test {
using TickBitmap for mapping(int16 => uint256);
int24 constant INITIALIZED_TICK = 70;
int24 constant TICK_IN_UNINITIALZIED_WORD = 10000;
int24 constant SOLO_INITIALIZED_TICK_IN_WORD = -10000;
mapping(int16 => uint256) public bitmap;
mapping(int16 => uint256) internal emptyBitmap;
function setUp() public {
// set dirty slots beforehand for certain gas tests
int24[10] memory ticks = [SOLO_INITIALIZED_TICK_IN_WORD, -200, -55, -4, INITIALIZED_TICK, 78, 84, 139, 240, 535];
for (uint256 i; i < ticks.length - 1; i++) {
flipTick(ticks[i]);
}
}
function test_fuzz_compress(int24 tick, int24 tickSpacing) public pure {
tickSpacing = int24(bound(tickSpacing, 1, type(int24).max));
int24 compressed = tick / tickSpacing;
if (tick < 0 && tick % tickSpacing != 0) compressed--;
assertEq(TickBitmap.compress(tick, tickSpacing), compressed);
}
function test_fuzz_position(int24 tick) public pure {
(int16 wordPos, uint8 bitPos) = TickBitmap.position(tick);
assertEq(wordPos, tick >> 8);
assertEq(bitPos, uint8(int8(tick % 256)));
}
function test_isInitialized_isFalseAtFirst() public view {
assertEq(isInitialized(1), false);
}
function test_isInitialized_isFlippedByFlipTick() public {
flipTick(1);
assertEq(isInitialized(1), true);
}
function test_isInitialized_isFlippedBackByFlipTick() public {
flipTick(1);
flipTick(1);
assertEq(isInitialized(1), false);
}
function test_isInitialized_isNotChangedByAnotherFlipToADifferentTick() public {
flipTick(2);
assertEq(isInitialized(1), false);
}
function test_isInitialized_isNotChangedByAnotherFlipToADifferentTickOnAnotherWord() public {
flipTick(1 + 256);
assertEq(isInitialized(257), true);
assertEq(isInitialized(1), false);
}
function test_flipTick_flipsOnlyTheSpecifiedTick() public {
flipTick(-230);
assertEq(isInitialized(-230), true);
assertEq(isInitialized(-231), false);
assertEq(isInitialized(-229), false);
assertEq(isInitialized(-230 + 256), false);
assertEq(isInitialized(-230 - 256), false);
flipTick(-230);
assertEq(isInitialized(-230), false);
assertEq(isInitialized(-231), false);
assertEq(isInitialized(-229), false);
assertEq(isInitialized(-230 + 256), false);
assertEq(isInitialized(-230 - 256), false);
assertEq(isInitialized(1), false);
}
function test_flipTick_revertsOnlyItself() public {
flipTick(-230);
flipTick(-259);
flipTick(-229);
flipTick(500);
flipTick(-259);
flipTick(-229);
flipTick(-259);
assertEq(isInitialized(-259), true);
assertEq(isInitialized(-229), false);
}
function test_flipTick_flippingFirstTickInWordToInitialized_gas() public {
vm.startSnapshotGas("flipTick_flippingFirstTickInWordToInitialized");
flipTick(TICK_IN_UNINITIALZIED_WORD);
vm.stopSnapshotGas();
}
function test_flipTick_flippingSecondTickInWordToInitialized_gas() public {
vm.startSnapshotGas("flipTick_flippingSecondTickInWordToInitialized");
flipTick(INITIALIZED_TICK + 1);
vm.stopSnapshotGas();
}
function test_flipTick_flippingATickThatResultsInDeletingAWord_gas() public {
vm.startSnapshotGas("flipTick_flippingATickThatResultsInDeletingAWord");
flipTick(SOLO_INITIALIZED_TICK_IN_WORD);
vm.stopSnapshotGas();
}
function test_fuzz_flipTick(int24 tick, int24 tickSpacing) public {
tickSpacing = int24(bound(tickSpacing, 1, type(int24).max));
if (tick % tickSpacing != 0) {
vm.expectRevert(abi.encodeWithSelector(TickBitmap.TickMisaligned.selector, tick, tickSpacing));
bitmap.flipTick(tick, tickSpacing);
} else {
bool initialized = isInitialized(tick, tickSpacing);
bitmap.flipTick(tick, tickSpacing);
assertEq(isInitialized(tick, tickSpacing), !initialized);
// flip again
bitmap.flipTick(tick, tickSpacing);
assertEq(isInitialized(tick, tickSpacing), initialized);
}
}
function test_nextInitializedTickWithinOneWord_lteFalse_returnsTickToRightIfAtInitializedTick() public view {
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(78, 1, false);
assertEq(next, 84);
assertEq(initialized, true);
}
function test_nextInitializedTickWithinOneWord_lteFalse_returnsTickToRightIfAtInitializedTick2() public view {
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(-55, 1, false);
assertEq(next, -4);
assertEq(initialized, true);
}
function test_nextInitializedTickWithinOneWord_lteFalse_returnsTheTickDirectlyToTheRight() public view {
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(77, 1, false);
assertEq(next, 78);
assertEq(initialized, true);
}
function test_nextInitializedTickWithinOneWord_lteFalse_returnsTheTickDirectlyToTheRight2() public view {
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(-56, 1, false);
assertEq(next, -55);
assertEq(initialized, true);
}
function test_nextInitializedTickWithinOneWord_lteFalse_returnsTheNextWordsInitializedTickIfOnTheRightBoundary()
public
view
{
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(255, 1, false);
assertEq(next, 511);
assertEq(initialized, false);
}
function test_nextInitializedTickWithinOneWord_lteFalse_returnsTheNextWordsInitializedTickIfOnTheRightBoundary2()
public
view
{
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(-257, 1, false);
assertEq(next, -200);
assertEq(initialized, true);
}
function test_nextInitializedTickWithinOneWord_lteFalse_returnsTheNextInitializedTickFromTheNextWord() public {
flipTick(340);
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(328, 1, false);
assertEq(next, 340);
assertEq(initialized, true);
}
function test_nextInitializedTickWithinOneWord_lteFalse_doesNotExceedBoundary() public view {
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(508, 1, false);
assertEq(next, 511);
assertEq(initialized, false);
}
function test_nextInitializedTickWithinOneWord_lteFalse_skipsEntireWord() public view {
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(255, 1, false);
assertEq(next, 511);
assertEq(initialized, false);
}
function test_nextInitializedTickWithinOneWord_lteFalse_skipsHalfWord() public view {
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(383, 1, false);
assertEq(next, 511);
assertEq(initialized, false);
}
function test_nextInitializedTickWithinOneWord_lteFalse_onBoundary_gas() public {
vm.startSnapshotGas("nextInitializedTickWithinOneWord_lteFalse_onBoundary");
bitmap.nextInitializedTickWithinOneWord(255, 1, false);
vm.stopSnapshotGas();
}
function test_nextInitializedTickWithinOneWord_lteFalse_justBelowBoundary_gas() public {
vm.startSnapshotGas("nextInitializedTickWithinOneWord_lteFalse_justBelowBoundary");
bitmap.nextInitializedTickWithinOneWord(254, 1, false);
vm.stopSnapshotGas();
}
function test_nextInitializedTickWithinOneWord_lteFalse_forEntireWord_gas() public {
vm.startSnapshotGas("nextInitializedTickWithinOneWord_lteFalse_forEntireWord");
bitmap.nextInitializedTickWithinOneWord(768, 1, false);
vm.stopSnapshotGas();
}
function test_nextInitializedTickWithinOneWord_lteTrue_returnsSameTickIfInitialized() public view {
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(78, 1, true);
assertEq(next, 78);
assertEq(initialized, true);
}
function test_nextInitializedTickWithinOneWord_lteTrue_returnsTickDirectlyToTheLeftOfInputTickIfNotInitialized()
public
view
{
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(79, 1, true);
assertEq(next, 78);
assertEq(initialized, true);
}
function test_nextInitializedTickWithinOneWord_lteTrue_willNotExceedTheWordBoundary() public view {
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(258, 1, true);
assertEq(next, 256);
assertEq(initialized, false);
}
function test_nextInitializedTickWithinOneWord_lteTrue_atTheWordBoundary() public view {
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(256, 1, true);
assertEq(next, 256);
assertEq(initialized, false);
}
function test_nextInitializedTickWithinOneWord_lteTrue_wordBoundaryLess1nextInitializedTickInNextWord()
public
view
{
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(72, 1, true);
assertEq(next, 70);
assertEq(initialized, true);
}
function test_nextInitializedTickWithinOneWord_lteTrue_wordBoundary() public view {
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(-257, 1, true);
assertEq(next, -512);
assertEq(initialized, false);
}
function test_nextInitializedTickWithinOneWord_lteTrue_entireEmptyWord() public view {
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(1023, 1, true);
assertEq(next, 768);
assertEq(initialized, false);
}
function test_nextInitializedTickWithinOneWord_lteTrue_halfwayThroughEmptyWord() public view {
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(900, 1, true);
assertEq(next, 768);
assertEq(initialized, false);
}
function test_nextInitializedTickWithinOneWord_lteTrue_boundaryIsInitialized() public {
flipTick(329);
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(456, 1, true);
assertEq(next, 329);
assertEq(initialized, true);
}
function test_nextInitializedTickWithinOneWord_lteTrue_onBoundary_gas() public {
vm.startSnapshotGas("nextInitializedTickWithinOneWord_lteTrue_onBoundary_gas");
bitmap.nextInitializedTickWithinOneWord(256, 1, true);
vm.stopSnapshotGas();
}
function test_nextInitializedTickWithinOneWord_lteTrue_justBelowBoundary_gas() public {
vm.startSnapshotGas("nextInitializedTickWithinOneWord_lteTrue_justBelowBoundary");
bitmap.nextInitializedTickWithinOneWord(255, 1, true);
vm.stopSnapshotGas();
}
function test_nextInitializedTickWithinOneWord_lteTrue_forEntireWord_gas() public {
vm.startSnapshotGas("nextInitializedTickWithinOneWord_lteTrue_forEntireWord");
bitmap.nextInitializedTickWithinOneWord(1024, 1, true);
vm.stopSnapshotGas();
}
function test_fuzz_nextInitializedTickWithinOneWord(int24 tick, bool lte) public view {
// assume tick is at least one word inside type(int24).(max | min)
vm.assume(lte ? tick >= -8388352 : tick < 8388351);
(int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(tick, 1, lte);
if (lte) {
assertLe(next, tick);
assertLe(tick - next, 256);
// all the ticks between the input tick and the next tick should be uninitialized
for (int24 i = tick; i > next; i--) {
assertTrue(!isInitialized(i));
}
assertEq(isInitialized(next), initialized);
} else {
assertGt(next, tick);
assertLe(next - tick, 256);
// all the ticks between the input tick and the next tick should be uninitialized
for (int24 i = tick + 1; i < next; i++) {
assertTrue(!isInitialized(i));
}
assertEq(isInitialized(next), initialized);
}
}
function test_fuzz_nextInitializedTickWithinOneWord_onEmptyBitmap(
int24 tick,
int24 tickSpacing,
uint8 nextBitPos,
bool lte
) public {
tick = int24(bound(tick, TickMath.MIN_TICK, TickMath.MAX_TICK));
tickSpacing = int24(bound(tickSpacing, TickMath.MIN_TICK_SPACING, TickMath.MAX_TICK_SPACING));
int24 compressed = TickBitmap.compress(tick, tickSpacing);
if (!lte) ++compressed;
(int16 wordPos, uint8 bitPos) = TickBitmap.position(compressed);
if (lte) {
nextBitPos = uint8(bound(nextBitPos, 0, bitPos));
} else {
nextBitPos = uint8(bound(nextBitPos, bitPos, 255));
}
// Choose the next initialized tick within one word at random and flip it.
int24 nextInitializedTick = ((int24(wordPos) << 8) + int24(uint24(nextBitPos))) * tickSpacing;
emptyBitmap.flipTick(nextInitializedTick, tickSpacing);
(int24 next, bool initialized) = emptyBitmap.nextInitializedTickWithinOneWord(tick, tickSpacing, lte);
assertEq(initialized, true);
assertEq(next, nextInitializedTick);
}
function isInitialized(int24 tick, int24 tickSpacing) internal view returns (bool) {
unchecked {
if (tick % tickSpacing != 0) return false;
(int16 wordPos, uint8 bitPos) = TickBitmap.position(tick / tickSpacing);
return bitmap[wordPos] & (1 << bitPos) != 0;
}
}
function isInitialized(int24 tick) internal view returns (bool) {
return isInitialized(tick, 1);
}
function flipTick(int24 tick) internal {
bitmap.flipTick(tick, 1);
}
}