@exponent-labs/market-three-math 0.9.15 → 0.9.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/addLiquidity.d.ts +11 -41
- package/build/addLiquidity.js +169 -201
- package/build/addLiquidity.js.map +1 -1
- package/build/bisect.js +1 -2
- package/build/bisect.js.map +1 -1
- package/build/existingPositionEqualization.d.ts +3 -3
- package/build/existingPositionEqualization.js +45 -66
- package/build/existingPositionEqualization.js.map +1 -1
- package/build/index.d.ts +2 -2
- package/build/index.js +1 -3
- package/build/index.js.map +1 -1
- package/build/liquidityHistogram.js +2 -3
- package/build/liquidityHistogram.js.map +1 -1
- package/build/quote.js +2 -2
- package/build/quote.js.map +1 -1
- package/build/swap.js +2 -2
- package/build/swap.js.map +1 -1
- package/build/swapV2.js +56 -20
- package/build/swapV2.js.map +1 -1
- package/build/types.d.ts +4 -21
- package/build/utils.js +17 -17
- package/build/utils.js.map +1 -1
- package/build/utilsV2.js +10 -7
- package/build/utilsV2.js.map +1 -1
- package/build/withdrawLiquidity.d.ts +1 -1
- package/build/withdrawLiquidity.js +120 -72
- package/build/withdrawLiquidity.js.map +1 -1
- package/build/ytTrades.js +3 -4
- package/build/ytTrades.js.map +1 -1
- package/build/ytTradesLegacy.js +3 -4
- package/build/ytTradesLegacy.js.map +1 -1
- package/package.json +2 -2
- package/src/addLiquidity.ts +203 -246
- package/src/existingPositionEqualization.test.ts +33 -0
- package/src/existingPositionEqualization.ts +52 -83
- package/src/index.ts +0 -4
- package/src/swap.ts +1 -0
- package/src/swapV2.ts +96 -18
- package/src/types.ts +4 -23
- package/src/utilsV2.ts +9 -4
- package/src/withdrawLiquidity.test.ts +189 -0
- package/src/withdrawLiquidity.ts +148 -89
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { PublicKey } from "@solana/web3.js"
|
|
2
|
+
|
|
3
|
+
import type { LpPositionCLMM, MarketThree, Tick, Ticks } from "@exponent-labs/exponent-fetcher"
|
|
4
|
+
|
|
5
|
+
import { getPtAndSyOnWithdrawLiquidity } from "./withdrawLiquidity"
|
|
6
|
+
|
|
7
|
+
const PRECISE_NUMBER_DENOM = 1_000_000_000_000n
|
|
8
|
+
const SENTINEL_TICK_INDEX = 0xffffffff
|
|
9
|
+
const PUBLIC_KEY = PublicKey.default
|
|
10
|
+
|
|
11
|
+
const preciseRaw = (value: bigint) => value * PRECISE_NUMBER_DENOM
|
|
12
|
+
|
|
13
|
+
const marketEmissions: MarketThree["emissions"] = { trackers: [] }
|
|
14
|
+
|
|
15
|
+
const makeTick = (overrides: Partial<Tick> = {}): Tick => ({
|
|
16
|
+
apyBasePoints: 1,
|
|
17
|
+
impliedRate: 1.0,
|
|
18
|
+
principalPt: 0n,
|
|
19
|
+
principalSy: 0n,
|
|
20
|
+
principalShareSupply: 0n,
|
|
21
|
+
liquidityNet: 0n,
|
|
22
|
+
liquidityGross: 0n,
|
|
23
|
+
feeGrowthOutsidePt: 0n,
|
|
24
|
+
feeGrowthOutsideSy: 0n,
|
|
25
|
+
farms: [],
|
|
26
|
+
emissions: [],
|
|
27
|
+
lastSplitEpoch: 0n,
|
|
28
|
+
splitParentEpoch: 0n,
|
|
29
|
+
frozenLiquidity: 0n,
|
|
30
|
+
splitParentIdx: SENTINEL_TICK_INDEX,
|
|
31
|
+
...overrides,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const makeTicks = (ticksTree: Tick[]): Ticks => ({
|
|
35
|
+
currentTick: 1,
|
|
36
|
+
ticksTree,
|
|
37
|
+
market: PUBLIC_KEY,
|
|
38
|
+
feeGrowthIndexGlobalPt: 0n,
|
|
39
|
+
feeGrowthIndexGlobalSy: 0n,
|
|
40
|
+
currentPrefixSum: 0n,
|
|
41
|
+
currentSpotPrice: 1,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const makePosition = (overrides: Partial<LpPositionCLMM> = {}): LpPositionCLMM => ({
|
|
45
|
+
owner: PUBLIC_KEY,
|
|
46
|
+
market: PUBLIC_KEY,
|
|
47
|
+
feeInsideLastPt: 0n,
|
|
48
|
+
feeInsideLastSy: 0n,
|
|
49
|
+
lpBalance: 1n,
|
|
50
|
+
tokensOwedSy: 0n,
|
|
51
|
+
tokensOwedPt: 0n,
|
|
52
|
+
lowerTickIdx: 1,
|
|
53
|
+
upperTickIdx: 2,
|
|
54
|
+
farms: [],
|
|
55
|
+
shareTrackers: [
|
|
56
|
+
{
|
|
57
|
+
tickIdx: 1,
|
|
58
|
+
rightTickIdx: 2,
|
|
59
|
+
splitEpoch: 0n,
|
|
60
|
+
lpShare: PRECISE_NUMBER_DENOM / 2n,
|
|
61
|
+
emissions: [],
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
crossingSplit: {
|
|
65
|
+
crossLeftIdx: SENTINEL_TICK_INDEX,
|
|
66
|
+
crossRightIdx: SENTINEL_TICK_INDEX,
|
|
67
|
+
lpBalanceCrossing: 0n,
|
|
68
|
+
isActive: false,
|
|
69
|
+
},
|
|
70
|
+
...overrides,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
describe("withdraw liquidity math", () => {
|
|
74
|
+
it("uses precise Number-style principal math for fractional shares", () => {
|
|
75
|
+
const ticks = makeTicks([
|
|
76
|
+
makeTick({
|
|
77
|
+
principalPt: 7n,
|
|
78
|
+
principalSy: 11n,
|
|
79
|
+
principalShareSupply: PRECISE_NUMBER_DENOM,
|
|
80
|
+
}),
|
|
81
|
+
makeTick({ apyBasePoints: 2, impliedRate: 2 }),
|
|
82
|
+
])
|
|
83
|
+
|
|
84
|
+
expect(getPtAndSyOnWithdrawLiquidity(marketEmissions, ticks, makePosition(), 1n)).toEqual({
|
|
85
|
+
totalPtOut: 3n,
|
|
86
|
+
totalSyOut: 5n,
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it("returns full principal when burned shares cover the supply", () => {
|
|
91
|
+
const ticks = makeTicks([
|
|
92
|
+
makeTick({
|
|
93
|
+
principalPt: 7n,
|
|
94
|
+
principalSy: 11n,
|
|
95
|
+
principalShareSupply: PRECISE_NUMBER_DENOM,
|
|
96
|
+
}),
|
|
97
|
+
makeTick({ apyBasePoints: 2, impliedRate: 2 }),
|
|
98
|
+
])
|
|
99
|
+
const position = makePosition({
|
|
100
|
+
shareTrackers: [
|
|
101
|
+
{
|
|
102
|
+
tickIdx: 1,
|
|
103
|
+
rightTickIdx: 2,
|
|
104
|
+
splitEpoch: 0n,
|
|
105
|
+
lpShare: PRECISE_NUMBER_DENOM,
|
|
106
|
+
emissions: [],
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
expect(getPtAndSyOnWithdrawLiquidity(marketEmissions, ticks, position, 1n)).toEqual({
|
|
112
|
+
totalPtOut: 7n,
|
|
113
|
+
totalSyOut: 11n,
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it("rejects burn shares that exceed tick supply", () => {
|
|
118
|
+
const ticks = makeTicks([
|
|
119
|
+
makeTick({
|
|
120
|
+
principalPt: 7n,
|
|
121
|
+
principalSy: 11n,
|
|
122
|
+
principalShareSupply: PRECISE_NUMBER_DENOM / 2n,
|
|
123
|
+
}),
|
|
124
|
+
makeTick({ apyBasePoints: 2, impliedRate: 2 }),
|
|
125
|
+
])
|
|
126
|
+
const position = makePosition({
|
|
127
|
+
shareTrackers: [
|
|
128
|
+
{
|
|
129
|
+
tickIdx: 1,
|
|
130
|
+
rightTickIdx: 2,
|
|
131
|
+
splitEpoch: 0n,
|
|
132
|
+
lpShare: PRECISE_NUMBER_DENOM,
|
|
133
|
+
emissions: [],
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
expect(() => getPtAndSyOnWithdrawLiquidity(marketEmissions, ticks, position, 1n)).toThrow(
|
|
139
|
+
"withdraw burn shares exceed tick supply",
|
|
140
|
+
)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it("projects split shares through split parent epoch instead of immediate successor", () => {
|
|
144
|
+
const ticks = makeTicks([
|
|
145
|
+
makeTick({
|
|
146
|
+
apyBasePoints: 100,
|
|
147
|
+
impliedRate: 1.0,
|
|
148
|
+
principalPt: 50n,
|
|
149
|
+
principalShareSupply: preciseRaw(50n),
|
|
150
|
+
lastSplitEpoch: 1n,
|
|
151
|
+
}),
|
|
152
|
+
makeTick({
|
|
153
|
+
apyBasePoints: 125,
|
|
154
|
+
impliedRate: 1.25,
|
|
155
|
+
principalPt: 999n,
|
|
156
|
+
principalShareSupply: preciseRaw(50n),
|
|
157
|
+
splitParentEpoch: 99n,
|
|
158
|
+
splitParentIdx: 1,
|
|
159
|
+
}),
|
|
160
|
+
makeTick({
|
|
161
|
+
apyBasePoints: 150,
|
|
162
|
+
impliedRate: 1.5,
|
|
163
|
+
principalPt: 300n,
|
|
164
|
+
principalShareSupply: preciseRaw(50n),
|
|
165
|
+
splitParentEpoch: 0n,
|
|
166
|
+
splitParentIdx: 1,
|
|
167
|
+
}),
|
|
168
|
+
makeTick({
|
|
169
|
+
apyBasePoints: 200,
|
|
170
|
+
impliedRate: 2.0,
|
|
171
|
+
}),
|
|
172
|
+
])
|
|
173
|
+
const position = makePosition({
|
|
174
|
+
lpBalance: 100n,
|
|
175
|
+
upperTickIdx: 4,
|
|
176
|
+
shareTrackers: [
|
|
177
|
+
{
|
|
178
|
+
tickIdx: 1,
|
|
179
|
+
rightTickIdx: 4,
|
|
180
|
+
splitEpoch: 0n,
|
|
181
|
+
lpShare: preciseRaw(100n),
|
|
182
|
+
emissions: [],
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
expect(getPtAndSyOnWithdrawLiquidity(marketEmissions, ticks, position, 100n).totalPtOut).toBe(350n)
|
|
188
|
+
})
|
|
189
|
+
})
|
package/src/withdrawLiquidity.ts
CHANGED
|
@@ -1,6 +1,88 @@
|
|
|
1
|
-
import { LpPositionCLMM, MarketThree, Ticks } from "@exponent-labs/exponent-fetcher"
|
|
1
|
+
import type { LpPositionCLMM, MarketThree, Ticks } from "@exponent-labs/exponent-fetcher"
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const PRECISE_NUMBER_DENOM = 1_000_000_000_000n
|
|
4
|
+
const SENTINEL_TICK_INDEX = 0xffffffff
|
|
5
|
+
|
|
6
|
+
const invariantError = (message: string) => new Error(`WithdrawLiquidityInvariantViolated: ${message}`)
|
|
7
|
+
|
|
8
|
+
const getTickByIndex = (ticks: Ticks, tickIdx: number) => {
|
|
9
|
+
const tick = ticks.ticksTree[tickIdx - 1]
|
|
10
|
+
if (!tick || tick.apyBasePoints <= 0) {
|
|
11
|
+
throw invariantError(`missing tick at index ${tickIdx}`)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return tick
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const getTickKey = (ticks: Ticks, tickIdx: number) => getTickByIndex(ticks, tickIdx).apyBasePoints
|
|
18
|
+
|
|
19
|
+
const successorIdx = (ticks: Ticks, tickIdx: number): number | null => {
|
|
20
|
+
const tickKey = getTickKey(ticks, tickIdx)
|
|
21
|
+
let best: { tickIdx: number; key: number } | null = null
|
|
22
|
+
|
|
23
|
+
for (let index = 0; index < ticks.ticksTree.length; index++) {
|
|
24
|
+
const key = ticks.ticksTree[index].apyBasePoints
|
|
25
|
+
if (key <= tickKey) {
|
|
26
|
+
continue
|
|
27
|
+
}
|
|
28
|
+
if (!best || key < best.key) {
|
|
29
|
+
best = { tickIdx: index + 1, key }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return best?.tickIdx ?? null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const splitSuccessorForEpoch = (ticks: Ticks, leftIdx: number, rightIdx: number, splitEpoch: bigint): number | null => {
|
|
37
|
+
let cursor = successorIdx(ticks, leftIdx)
|
|
38
|
+
while (cursor != null && cursor !== SENTINEL_TICK_INDEX && cursor !== rightIdx) {
|
|
39
|
+
const tick = getTickByIndex(ticks, cursor)
|
|
40
|
+
if (tick.splitParentIdx === leftIdx && tick.splitParentEpoch === splitEpoch) {
|
|
41
|
+
return cursor
|
|
42
|
+
}
|
|
43
|
+
cursor = successorIdx(ticks, cursor)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const fastMulRatioRaw = (rawPreciseNumber: bigint, numerator: bigint, denominator: bigint): bigint => {
|
|
50
|
+
if (denominator <= 0n) {
|
|
51
|
+
throw invariantError("fastMulRatio denominator is zero")
|
|
52
|
+
}
|
|
53
|
+
if (numerator === 0n || rawPreciseNumber === 0n) {
|
|
54
|
+
return 0n
|
|
55
|
+
}
|
|
56
|
+
if (numerator === denominator) {
|
|
57
|
+
return rawPreciseNumber
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const q = rawPreciseNumber / denominator
|
|
61
|
+
const r = rawPreciseNumber % denominator
|
|
62
|
+
return q * numerator + (r * numerator) / denominator
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const numberFromRatioRaw = (numerator: bigint, denominator: bigint): bigint => {
|
|
66
|
+
if (denominator <= 0n) {
|
|
67
|
+
throw invariantError("Number::from_ratio denominator is zero")
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (numerator * PRECISE_NUMBER_DENOM) / denominator
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const mulNumberRaw = (leftRaw: bigint, rightRaw: bigint): bigint => (leftRaw * rightRaw) / PRECISE_NUMBER_DENOM
|
|
74
|
+
|
|
75
|
+
const principalOutForBurn = (principal: bigint, burnShares: bigint, shareSupply: bigint): bigint => {
|
|
76
|
+
if (principal === 0n || burnShares === 0n || shareSupply === 0n) {
|
|
77
|
+
return 0n
|
|
78
|
+
}
|
|
79
|
+
if (burnShares >= shareSupply) {
|
|
80
|
+
return principal
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const out = (principal * burnShares) / shareSupply
|
|
84
|
+
return out < principal ? out : principal
|
|
85
|
+
}
|
|
4
86
|
|
|
5
87
|
/**
|
|
6
88
|
* TypeScript version of project_anchor_shares_to_current_ticks from Rust
|
|
@@ -13,13 +95,10 @@ import { getSuccessorTickIdxByIdx } from "./utils"
|
|
|
13
95
|
* @param rootShares - The original share trackers to project
|
|
14
96
|
* @returns Array of projected principal shares
|
|
15
97
|
*
|
|
16
|
-
* Note: Requires TicksExtended with
|
|
17
|
-
* The current exponent-fetcher deserializer would need to be updated to extract these fields.
|
|
98
|
+
* Note: Requires TicksExtended with split-parent metadata and emissions data.
|
|
18
99
|
*/
|
|
19
100
|
type ShareTracker = LpPositionCLMM["shareTrackers"][0]
|
|
20
101
|
function projectAnchorSharesToCurrentTicks(ticks: Ticks, rootShares: ShareTracker[]): ShareTracker[] {
|
|
21
|
-
const SENTINEL = 0xffffffff // Sentinel value for tree traversal (matches Rust implementation)
|
|
22
|
-
|
|
23
102
|
// Deep clone the trackers to avoid mutating the original objects (important for React Query caching)
|
|
24
103
|
const stack: ShareTracker[] = rootShares.map((s) => ({
|
|
25
104
|
tickIdx: s.tickIdx,
|
|
@@ -32,14 +111,7 @@ function projectAnchorSharesToCurrentTicks(ticks: Ticks, rootShares: ShareTracke
|
|
|
32
111
|
|
|
33
112
|
while (stack.length > 0) {
|
|
34
113
|
const principalShare = stack.pop()!
|
|
35
|
-
|
|
36
|
-
// Find the tick node for this share
|
|
37
|
-
const tickNode = ticks.ticksTree.at(principalShare.tickIdx - 1) ?? null
|
|
38
|
-
|
|
39
|
-
if (!tickNode) {
|
|
40
|
-
//? Tick node not found for tickIdx
|
|
41
|
-
continue
|
|
42
|
-
}
|
|
114
|
+
const tickNode = getTickByIndex(ticks, principalShare.tickIdx)
|
|
43
115
|
|
|
44
116
|
const lastSplitEpoch = tickNode.lastSplitEpoch
|
|
45
117
|
|
|
@@ -47,60 +119,54 @@ function projectAnchorSharesToCurrentTicks(ticks: Ticks, rootShares: ShareTracke
|
|
|
47
119
|
if (principalShare.splitEpoch < lastSplitEpoch) {
|
|
48
120
|
const rightIndex = principalShare.rightTickIdx
|
|
49
121
|
|
|
50
|
-
if (rightIndex
|
|
51
|
-
|
|
52
|
-
const splitedIndex = getSuccessorTickIdxByIdx(ticks, principalShare.tickIdx)
|
|
53
|
-
|
|
54
|
-
if (splitedIndex === null) {
|
|
55
|
-
//? No successor tick found for splitting
|
|
56
|
-
continue
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const tickSpotPrice = tickNode.impliedRate
|
|
60
|
-
const rightTickNode = ticks.ticksTree.at(rightIndex - 1) ?? null
|
|
61
|
-
const splitedTickNode = ticks.ticksTree.at(splitedIndex - 1) ?? null
|
|
62
|
-
|
|
63
|
-
if (!rightTickNode || !splitedTickNode) {
|
|
64
|
-
//? Could not find right or split tick nodes
|
|
65
|
-
continue
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Calculate the proportions based on spot price ranges
|
|
69
|
-
const splitedFullRange = rightTickNode.impliedRate - tickSpotPrice
|
|
70
|
-
const currentSplitRange = splitedTickNode.impliedRate - tickSpotPrice
|
|
71
|
-
|
|
72
|
-
// Calculate how much LP share goes to the left portion
|
|
73
|
-
const leftShare = Math.floor(Number(principalShare.lpShare) * (currentSplitRange / splitedFullRange))
|
|
74
|
-
|
|
75
|
-
// Create new emission trackers with staged reset to 0
|
|
76
|
-
const newEmissions: ShareTracker["emissions"] = principalShare.emissions.map((tracker) => ({
|
|
77
|
-
staged: 0n,
|
|
78
|
-
lastSeenIndex: tracker.lastSeenIndex,
|
|
79
|
-
}))
|
|
80
|
-
|
|
81
|
-
// Calculate the migrated share (right portion)
|
|
82
|
-
const migratedShare = principalShare.lpShare - BigInt(leftShare)
|
|
83
|
-
|
|
84
|
-
// Update the current share to be the left portion
|
|
85
|
-
principalShare.lpShare = BigInt(leftShare)
|
|
86
|
-
principalShare.rightTickIdx = splitedIndex
|
|
87
|
-
|
|
88
|
-
// Push the right portion back onto the stack for further processing
|
|
89
|
-
stack.push({
|
|
90
|
-
tickIdx: splitedIndex,
|
|
91
|
-
rightTickIdx: rightIndex,
|
|
92
|
-
splitEpoch: principalShare.splitEpoch,
|
|
93
|
-
lpShare: migratedShare,
|
|
94
|
-
emissions: newEmissions,
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
// Update the split epoch to mark this share as processed
|
|
98
|
-
principalShare.splitEpoch = lastSplitEpoch
|
|
99
|
-
} else {
|
|
100
|
-
// Error: we have a split range but no right index
|
|
101
|
-
//? No right index for split range
|
|
102
|
-
continue
|
|
122
|
+
if (rightIndex === SENTINEL_TICK_INDEX) {
|
|
123
|
+
throw invariantError("no right index for split range")
|
|
103
124
|
}
|
|
125
|
+
|
|
126
|
+
const splitedIndex = splitSuccessorForEpoch(ticks, principalShare.tickIdx, rightIndex, principalShare.splitEpoch)
|
|
127
|
+
if (splitedIndex == null) {
|
|
128
|
+
throw invariantError("no split successor for parent epoch")
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const tickSpotPrice = tickNode.impliedRate
|
|
132
|
+
const rightTickNode = getTickByIndex(ticks, rightIndex)
|
|
133
|
+
const splitedTickNode = getTickByIndex(ticks, splitedIndex)
|
|
134
|
+
|
|
135
|
+
const splitedFullRange = rightTickNode.impliedRate - tickSpotPrice
|
|
136
|
+
const currentSplitRange = splitedTickNode.impliedRate - tickSpotPrice
|
|
137
|
+
|
|
138
|
+
const [leftShare, migratedShare] =
|
|
139
|
+
splitedFullRange <= 0
|
|
140
|
+
? [0n, principalShare.lpShare]
|
|
141
|
+
: (() => {
|
|
142
|
+
const ratio = Math.max(0, Math.min(1, currentSplitRange / splitedFullRange))
|
|
143
|
+
const scaledNum = BigInt(Math.round(ratio * Number(PRECISE_NUMBER_DENOM)))
|
|
144
|
+
const rightScaledNum = PRECISE_NUMBER_DENOM - scaledNum
|
|
145
|
+
return [
|
|
146
|
+
fastMulRatioRaw(principalShare.lpShare, scaledNum, PRECISE_NUMBER_DENOM),
|
|
147
|
+
fastMulRatioRaw(principalShare.lpShare, rightScaledNum, PRECISE_NUMBER_DENOM),
|
|
148
|
+
]
|
|
149
|
+
})()
|
|
150
|
+
|
|
151
|
+
const newEmissions: ShareTracker["emissions"] = principalShare.emissions.map((tracker) => ({
|
|
152
|
+
staged: 0n,
|
|
153
|
+
lastSeenIndex: tracker.lastSeenIndex,
|
|
154
|
+
}))
|
|
155
|
+
|
|
156
|
+
principalShare.lpShare = leftShare
|
|
157
|
+
principalShare.rightTickIdx = splitedIndex
|
|
158
|
+
|
|
159
|
+
stack.push({
|
|
160
|
+
tickIdx: splitedIndex,
|
|
161
|
+
rightTickIdx: rightIndex,
|
|
162
|
+
splitEpoch: principalShare.splitEpoch,
|
|
163
|
+
lpShare: migratedShare,
|
|
164
|
+
emissions: newEmissions,
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
principalShare.splitEpoch += 1n
|
|
168
|
+
stack.push(principalShare)
|
|
169
|
+
continue
|
|
104
170
|
}
|
|
105
171
|
|
|
106
172
|
// Add the processed share to the result
|
|
@@ -138,14 +204,7 @@ function updateLpPositionShares(
|
|
|
138
204
|
// Iterate through each share and update
|
|
139
205
|
for (const share of recomputedShares) {
|
|
140
206
|
const myShares = share.lpShare
|
|
141
|
-
|
|
142
|
-
// Find the tick node in the ticks tree
|
|
143
|
-
const tickNode = ticks.ticksTree.at(share.tickIdx - 1) ?? null
|
|
144
|
-
|
|
145
|
-
if (!tickNode) {
|
|
146
|
-
//? Tick node not found for provided tickIdx
|
|
147
|
-
continue
|
|
148
|
-
}
|
|
207
|
+
const tickNode = getTickByIndex(ticks, share.tickIdx)
|
|
149
208
|
|
|
150
209
|
// Update tick emissions with market emission indices
|
|
151
210
|
// In Rust: node_mut.value.update_tick_emissions(market_emission_indices)
|
|
@@ -202,28 +261,28 @@ function calculatePtSyRemoval(
|
|
|
202
261
|
): { totalPtOut: bigint; totalSyOut: bigint } {
|
|
203
262
|
let totalPtOut = 0n
|
|
204
263
|
let totalSyOut = 0n
|
|
264
|
+
const isFullRemoval = liquidityToRemove === position.lpBalance
|
|
265
|
+
|
|
266
|
+
if (liquidityToRemove <= 0n || liquidityToRemove > position.lpBalance) {
|
|
267
|
+
throw invariantError("invalid liquidity amount")
|
|
268
|
+
}
|
|
205
269
|
|
|
206
270
|
for (const share of position.shareTrackers) {
|
|
207
271
|
const myShares = share.lpShare
|
|
208
|
-
const tickNode = ticks
|
|
209
|
-
|
|
210
|
-
if (!tickNode) {
|
|
211
|
-
continue
|
|
212
|
-
}
|
|
272
|
+
const tickNode = getTickByIndex(ticks, share.tickIdx)
|
|
213
273
|
|
|
214
274
|
const supply = tickNode.principalShareSupply
|
|
215
275
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
276
|
+
const burnShares = isFullRemoval
|
|
277
|
+
? myShares
|
|
278
|
+
: mulNumberRaw(myShares, numberFromRatioRaw(liquidityToRemove, position.lpBalance))
|
|
219
279
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
280
|
+
if (burnShares > supply) {
|
|
281
|
+
throw invariantError("withdraw burn shares exceed tick supply")
|
|
282
|
+
}
|
|
223
283
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const syOut = supply > 0n ? (tickNode.principalSy * burnShares) / supply : 0n
|
|
284
|
+
const ptOut = principalOutForBurn(tickNode.principalPt, burnShares, supply)
|
|
285
|
+
const syOut = principalOutForBurn(tickNode.principalSy, burnShares, supply)
|
|
227
286
|
|
|
228
287
|
totalPtOut += ptOut
|
|
229
288
|
totalSyOut += syOut
|