-
Notifications
You must be signed in to change notification settings - Fork 609
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(CL): Tick Bitmap #3065
Closed
alexanderbez
wants to merge
12
commits into
concentrated-liquidity-main
from
bez/3059-tick-bitmap-impl
Closed
feat(CL): Tick Bitmap #3065
Changes from 9 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
80221f2
bez: updates
alexanderbez 6e37223
bez: updates
alexanderbez 235a7c3
bez: updates
alexanderbez c3bdc5a
bez: updates
alexanderbez 114057a
bez: updates
alexanderbez d901378
bez: updates
alexanderbez b647443
bez: updates
alexanderbez 013df44
bez: updates
alexanderbez 0a6dc75
bez: updates
alexanderbez 565979b
bez: updates
alexanderbez 367b076
bez: updates
alexanderbez f11314c
bez: updates
alexanderbez File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package concentrated_liquidity | ||
|
||
import ( | ||
"fmt" | ||
"math" | ||
"math/bits" | ||
) | ||
|
||
// TickBitmap defines a bitmap used to represent price ticks. It contains a | ||
// mapping of 64-bit words, where each bit in a word corresponds to a unique | ||
// tick. A set bit, i.e. a bit set to 1, indicates liquidity for that tick. | ||
// Conversely, an unset bit means there is no liquidity for that tick. Note, | ||
// ticks are in the range [−887272, 887272]. | ||
// | ||
// Ref: https://uniswapv3book.com/docs/introduction/uniswap-v3/#ticks | ||
// Ref: https://uniswapv3book.com/docs/milestone_2/tick-bitmap-index/#bitmap | ||
type TickBitmap struct { | ||
bitmap map[int16]uint64 | ||
} | ||
|
||
func NewTickBitmap() *TickBitmap { | ||
return &TickBitmap{ | ||
bitmap: make(map[int16]uint64), | ||
} | ||
} | ||
|
||
// FlipTick flips the tick for the given tick index from false (no liquidity) to | ||
// true (liquidity) or vice versa. The tickSpacing parameter defines the spacing | ||
// between usable ticks and must be a multiple of the tick index. | ||
func (tb *TickBitmap) FlipTick(tickIndex, tickSpacing int32) error { | ||
if tickIndex%tickSpacing != 0 { | ||
return fmt.Errorf("tickIndex %d is not a multiple of tickSpacing %d", tickIndex, tickSpacing) | ||
} | ||
|
||
wordPos, bitPos := tickPosition(tickIndex / tickSpacing) | ||
bitMask := uint64(1 << bitPos) | ||
tb.bitmap[wordPos] ^= bitMask | ||
|
||
return nil | ||
} | ||
|
||
// NextInitializedTickWithinOneWord returns the next initialized tick contained | ||
// in the same word (or adjacent word) as the tick that is either | ||
// to the left (less than or equal to) or right (greater than) of the given tick. | ||
// | ||
// In other words, it returns the next initialized or uninitialized tick up to | ||
// 64 ticks away from the current tick and whether that next tick is initialized, | ||
// as the function only searches within up to 64 ticks. | ||
// | ||
// Ref: https://github.com/Jeiwan/uniswapv3-code/blob/c8777c6462fb5d0f1d681c74f732174ff76880d6/src/lib/TickBitmap.sol#L38-L89 | ||
func (tb *TickBitmap) NextInitializedTickWithinOneWord(tickIndex, tickSpacing int32, lte bool) (next int32, initialized bool) { | ||
compressed := tickIndex / tickSpacing | ||
|
||
// round towards negative infinity | ||
if tickIndex < 0 && tickIndex%tickSpacing != 0 { | ||
compressed-- | ||
} | ||
|
||
if lte { | ||
wordPos, bitPos := tickPosition(compressed) | ||
|
||
// all the 1s at or to the right of the current bitPos | ||
bitMask := uint64((1 << bitPos) - 1 + (1 << bitPos)) | ||
masked := tb.bitmap[wordPos] & bitMask | ||
|
||
// If there are no initialized ticks to the right of or at the current tick, | ||
// return rightmost in the word. | ||
initialized = masked != 0 | ||
|
||
// Note, overflow/underflow is possible, but prevented externally by | ||
// limiting both tickSpacing and tick. | ||
if initialized { | ||
msbIndex := uint8(64 - bits.LeadingZeros64(masked) - 1) | ||
next = (compressed - int32(uint32(bitPos-msbIndex))) * tickSpacing | ||
} else { | ||
next = (compressed - int32(uint32(bitPos))) * tickSpacing | ||
} | ||
|
||
return next, initialized | ||
} | ||
|
||
// Start from the word of the next tick, since the current tick state doesn't | ||
// matter. | ||
wordPos, bitPos := tickPosition(compressed + 1) | ||
|
||
// all the 1s at or to the left of the bitPos | ||
bitMask := uint64(^((1 << bitPos) - 1)) | ||
masked := tb.bitmap[wordPos] & bitMask | ||
|
||
// If there are no initialized ticks to the left of the current tick, return | ||
// leftmost in the word. | ||
initialized = masked != 0 | ||
|
||
// Note, overflow/underflow is possible, but prevented externally by limiting | ||
// both tickSpacing and tick. | ||
if initialized { | ||
lsbIndex := uint8(bits.TrailingZeros64(masked)) | ||
next = (compressed + 1 + int32(uint32((lsbIndex - bitPos)))) * tickSpacing | ||
} else { | ||
next = (compressed + 1 + int32(uint32(math.MaxUint8-bitPos))) * tickSpacing | ||
} | ||
|
||
return next, initialized | ||
} | ||
|
||
// tickPosition returns the word and bit position in the tick bitmap given a | ||
// tick index. | ||
func tickPosition(tickIndex int32) (wordPos int16, bitPos uint8) { | ||
// Perform an arithmetic right shift operation identical to integer division | ||
// by 64. Word position is the integer part of a tick index divided by 64. | ||
wordPos = int16(tickIndex >> 6) | ||
|
||
// find the bit position in the word that corresponds to the tick. | ||
bitPos = uint8(uint32(tickIndex % 64)) | ||
|
||
return wordPos, bitPos | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package concentrated_liquidity_test | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestTickBitmap_FlipTick(t *testing.T) { | ||
|
||
} | ||
|
||
func TestTickBitmap_NextInitializedTickWithinOneWord(t *testing.T) { | ||
|
||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can we serialize and store this structure in the state?
Does it make sense to use a "prefix" map that we can store on disk?
e.g. we make a prefix of the format
bm< tick index >< key separator>< data >
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there are a few ways:
TickBitmap
into a sorted array of arrays, e.g.[[key1, value1], [key2, value2], ...]
. You then encode this array and store it under some prefix,PrefixByte | Encode(array)
. The words are 64-bits and the possible range of ticks is [−887272, 887272]. So that's roughly 1,774,545 possible ticks which can be presented via the 64-bit words. I didn't do the napkin math, but the map should be pretty small, so this isn't bad at all.But then this raises the question how and when do we load it and then save it? I think we could either lazy-load it or load it at BeginBlock and then commit it at each EndBlock?
@ValarDragon wdyt?