getter.gno
14.94 Kb · 432 lines
1package position
2
3import (
4 "errors"
5
6 "gno.land/p/gnoswap/consts/v1"
7 u256 "gno.land/p/gnoswap/uint256/v1"
8 rotree "gno.land/p/nt/bptree/rotree/v0"
9 ufmt "gno.land/p/nt/ufmt/v0"
10
11 pl "gno.land/r/gnoswap/pool"
12 "gno.land/r/gnoswap/position"
13)
14
15// getPosition returns a position for a given position ID.
16// Returns an error if the position doesn't exist.
17func (p *positionV1) getPosition(positionId uint64) (position.Position, error) {
18 pos, exists := p.store.GetPosition(positionId)
19 if !exists {
20 return pos, errors.New(newErrorWithDetail(
21 errPositionDoesNotExist,
22 ufmt.Sprintf("position with position ID(%d) doesn't exist", positionId),
23 ))
24 }
25
26 return pos, nil
27}
28
29// mustGetPosition returns a position for a given position ID.
30// panics if position doesn't exist
31func (p *positionV1) mustGetPosition(positionId uint64) *position.Position {
32 position, err := p.getPosition(positionId)
33 if err != nil {
34 panic(err.Error())
35 }
36
37 return &position
38}
39
40// GetPositions returns a read-only view of every position, keyed by the decimal
41// string form of the position ID.
42//
43// Returns:
44// - positions: read-only tree exposing the position store without mutation methods
45func (p *positionV1) GetPositions() *rotree.ReadOnlyTree {
46 // makeEntrySafeFn is a function that makes an entry safe to read.
47 // But Position is immutable, so we can just return the entry as is.
48 return rotree.Wrap(p.store.GetPositions(), nil)
49}
50
51// ExistPosition reports whether a position exists for a given position ID.
52//
53// Parameters:
54// - positionId: numeric ID to test in the position store
55//
56// Returns:
57// - exists: true when the position store contains positionId
58func (p *positionV1) ExistPosition(positionId uint64) bool {
59 return p.store.HasPosition(positionId)
60}
61
62// IsBurned reports the empty-position marker stored for a position.
63//
64// Parameters:
65// - positionId: numeric ID whose burned marker is requested
66//
67// Returns:
68// - burned: true when the position is marked empty; the NFT record itself may still exist
69// - err: nil on lookup success; errPositionDoesNotExist when the position is absent
70func (p *positionV1) IsBurned(positionId uint64) (bool, error) {
71 position, err := p.getPosition(positionId)
72 if err != nil {
73 return false, err
74 }
75
76 return position.Burned(), nil
77}
78
79// IsInRange reports whether the pool's current tick is inside a position's range.
80//
81// Parameters:
82// - positionId: numeric ID whose tick range and pool are inspected
83//
84// Returns:
85// - inRange: true when tickLower <= current pool tick < tickUpper
86// - err: nil on success; an error when the position or its pool tick cannot be read
87func (p *positionV1) IsInRange(positionId uint64) (bool, error) {
88 position, err := p.getPosition(positionId)
89 if err != nil {
90 return false, err
91 }
92
93 poolPath := position.PoolKey()
94 poolCurrentTick, err := pl.GetSlot0Tick(poolPath)
95 if err != nil {
96 return false, err
97 }
98
99 return position.TickLower() <= poolCurrentTick && poolCurrentTick < position.TickUpper(), nil
100}
101
102// GetPositionOperator returns the address approved to operate on a position.
103//
104// Parameters:
105// - positionId: numeric ID whose operator is requested
106//
107// Returns:
108// - operator: approved operator address, or the empty address when none is set
109// - err: nil on lookup success; errPositionDoesNotExist when the position is absent
110func (p *positionV1) GetPositionOperator(positionId uint64) (address, error) {
111 position, err := p.getPosition(positionId)
112 if err != nil {
113 return address(""), err
114 }
115
116 return position.Operator(), nil
117}
118
119// GetPositionPoolKey returns the pool identifier stored on a position.
120//
121// Parameters:
122// - positionId: numeric ID whose pool key is requested
123//
124// Returns:
125// - poolKey: pool key encoding the position's token pair and fee tier
126// - err: nil on lookup success; errPositionDoesNotExist when the position is absent
127func (p *positionV1) GetPositionPoolKey(positionId uint64) (string, error) {
128 position, err := p.getPosition(positionId)
129 if err != nil {
130 return "", err
131 }
132
133 return position.PoolKey(), nil
134}
135
136// GetPositionTickLower returns the lower tick boundary stored on a position.
137//
138// Parameters:
139// - positionId: numeric ID whose lower tick is requested
140//
141// Returns:
142// - tickLower: lower tick boundary, inclusive in range checks
143// - err: nil on lookup success; errPositionDoesNotExist when the position is absent
144func (p *positionV1) GetPositionTickLower(positionId uint64) (int32, error) {
145 position, err := p.getPosition(positionId)
146 if err != nil {
147 return 0, err
148 }
149
150 return position.TickLower(), nil
151}
152
153// GetPositionTickUpper returns the upper tick boundary stored on a position.
154//
155// Parameters:
156// - positionId: numeric ID whose upper tick is requested
157//
158// Returns:
159// - tickUpper: upper tick boundary, exclusive in range checks
160// - err: nil on lookup success; errPositionDoesNotExist when the position is absent
161func (p *positionV1) GetPositionTickUpper(positionId uint64) (int32, error) {
162 position, err := p.getPosition(positionId)
163 if err != nil {
164 return 0, err
165 }
166
167 return position.TickUpper(), nil
168}
169
170// GetPositionLiquidity returns the decimal-encoded liquidity stored on a position.
171//
172// Parameters:
173// - positionId: numeric ID whose liquidity is requested
174//
175// Returns:
176// - liquidity: decimal string representing the position's current liquidity
177// - err: nil on lookup success; errPositionDoesNotExist when the position is absent
178func (p *positionV1) GetPositionLiquidity(positionId uint64) (string, error) {
179 position, err := p.getPosition(positionId)
180 if err != nil {
181 return "", err
182 }
183
184 return position.Liquidity(), nil
185}
186
187// GetPositionTokenBalances returns balances derived from current pool price,
188// position range, and liquidity; token balances are not stored snapshots.
189//
190// Parameters:
191// - positionId: numeric ID whose current token balances are calculated
192//
193// Returns:
194// - token0Balance: current token0 amount represented by the position's liquidity and range
195// - token1Balance: current token1 amount represented by the position's liquidity and range
196// - err: nil when the position exists; errPositionDoesNotExist when it is absent (pool price lookup failures panic during calculation)
197func (p *positionV1) GetPositionTokenBalances(positionId uint64) (int64, int64, error) {
198 position, err := p.getPosition(positionId)
199 if err != nil {
200 return 0, 0, err
201 }
202
203 balance0, balance1 := calculatePositionBalances(&position)
204 return balance0, balance1, nil
205}
206
207// GetPositionFeeGrowthInside0LastX128 returns the token0 fee-growth checkpoint
208// stored on a position.
209//
210// Parameters:
211// - positionId: numeric ID whose token0 fee-growth checkpoint is requested
212//
213// Returns:
214// - feeGrowthInside0LastX128: decimal-encoded Q128 token0 fee growth inside the position's range at its last update
215// - err: nil on lookup success; errPositionDoesNotExist when the position is absent
216func (p *positionV1) GetPositionFeeGrowthInside0LastX128(positionId uint64) (string, error) {
217 position, err := p.getPosition(positionId)
218 if err != nil {
219 return "", err
220 }
221
222 return position.FeeGrowthInside0LastX128(), nil
223}
224
225// GetPositionFeeGrowthInside1LastX128 returns the token1 fee-growth checkpoint
226// stored on a position.
227//
228// Parameters:
229// - positionId: numeric ID whose token1 fee-growth checkpoint is requested
230//
231// Returns:
232// - feeGrowthInside1LastX128: decimal-encoded Q128 token1 fee growth inside the position's range at its last update
233// - err: nil on lookup success; errPositionDoesNotExist when the position is absent
234func (p *positionV1) GetPositionFeeGrowthInside1LastX128(positionId uint64) (string, error) {
235 position, err := p.getPosition(positionId)
236 if err != nil {
237 return "", err
238 }
239
240 return position.FeeGrowthInside1LastX128(), nil
241}
242
243// GetPositionFeeGrowthInsideLastX128 returns both fee-growth checkpoints stored
244// on a position, in token0 then token1 order.
245//
246// Parameters:
247// - positionId: numeric ID whose fee-growth checkpoints are requested
248//
249// Returns:
250// - feeGrowthInside0LastX128: decimal-encoded Q128 token0 fee growth checkpoint
251// - feeGrowthInside1LastX128: decimal-encoded Q128 token1 fee growth checkpoint
252// - err: nil on lookup success; errPositionDoesNotExist when the position is absent
253func (p *positionV1) GetPositionFeeGrowthInsideLastX128(positionId uint64) (string, string, error) {
254 position, err := p.getPosition(positionId)
255 if err != nil {
256 return "", "", err
257 }
258
259 return position.FeeGrowthInside0LastX128(), position.FeeGrowthInside1LastX128(), nil
260}
261
262// GetPositionTicks returns both tick boundaries stored on a position.
263//
264// Parameters:
265// - positionId: numeric ID whose tick boundaries are requested
266//
267// Returns:
268// - tickLower: lower tick boundary, inclusive in range checks
269// - tickUpper: upper tick boundary, exclusive in range checks
270// - err: nil on lookup success; errPositionDoesNotExist when the position is absent
271func (p *positionV1) GetPositionTicks(positionId uint64) (int32, int32, error) {
272 position, err := p.getPosition(positionId)
273 if err != nil {
274 return 0, 0, err
275 }
276
277 return position.TickLower(), position.TickUpper(), nil
278}
279
280// GetPositionTokensOwed0 returns the stored token0 amount owed to a position.
281//
282// Parameters:
283// - positionId: numeric ID whose token0 owed amount is requested
284//
285// Returns:
286// - tokensOwed0: accrued token0 amount awaiting collection, in token units
287// - err: nil on lookup success; errPositionDoesNotExist when the position is absent
288func (p *positionV1) GetPositionTokensOwed0(positionId uint64) (int64, error) {
289 position, err := p.getPosition(positionId)
290 if err != nil {
291 return 0, err
292 }
293
294 return position.TokensOwed0(), nil
295}
296
297// GetPositionTokensOwed1 returns the stored token1 amount owed to a position.
298//
299// Parameters:
300// - positionId: numeric ID whose token1 owed amount is requested
301//
302// Returns:
303// - tokensOwed1: accrued token1 amount awaiting collection, in token units
304// - err: nil on lookup success; errPositionDoesNotExist when the position is absent
305func (p *positionV1) GetPositionTokensOwed1(positionId uint64) (int64, error) {
306 position, err := p.getPosition(positionId)
307 if err != nil {
308 return 0, err
309 }
310
311 return position.TokensOwed1(), nil
312}
313
314// GetPositionTokensOwed returns both stored token amounts owed to a position,
315// in token0 then token1 order.
316//
317// Parameters:
318// - positionId: numeric ID whose owed amounts are requested
319//
320// Returns:
321// - tokensOwed0: accrued token0 amount awaiting collection, in token units
322// - tokensOwed1: accrued token1 amount awaiting collection, in token units
323// - err: nil on lookup success; errPositionDoesNotExist when the position is absent
324func (p *positionV1) GetPositionTokensOwed(positionId uint64) (int64, int64, error) {
325 position, err := p.getPosition(positionId)
326 if err != nil {
327 return 0, 0, err
328 }
329
330 return position.TokensOwed0(), position.TokensOwed1(), nil
331}
332
333// GetPositionOwner returns the address that owns the position NFT.
334//
335// Parameters:
336// - positionId: numeric ID whose NFT ownership is requested
337//
338// Returns:
339// - owner: current owner address reported by the NFT accessor
340// - err: nil when the NFT accessor finds the token; otherwise its ownership lookup error
341func (p *positionV1) GetPositionOwner(positionId uint64) (address, error) {
342 owner, err := p.nftAccessor.OwnerOf(positionIdFrom(positionId))
343 if err != nil {
344 return address(""), err
345 }
346
347 return owner, nil
348}
349
350// GetUnclaimedFee calculates unclaimed token fees from current in-range
351// fee growth, the position's checkpoints, and its liquidity.
352//
353// Parameters:
354// - positionId: numeric ID whose unclaimed token fees are calculated
355//
356// Returns:
357// - unclaimedFee0: token0 fees accrued since the stored checkpoint, scaled by position liquidity
358// - unclaimedFee1: token1 fees accrued since the stored checkpoint, scaled by position liquidity
359// - err: nil on success; nil fee pointers and the underlying lookup error when position or pool data is unavailable
360func (p *positionV1) GetUnclaimedFee(positionId uint64) (*u256.Uint, *u256.Uint, error) {
361 // ref: https://blog.uniswap.org/uniswap-v3-math-primer-2#calculating-uncollected-fees
362 position, err := p.getPosition(positionId)
363 if err != nil {
364 return nil, nil, err
365 }
366
367 liquidity := u256.MustFromDecimal(position.Liquidity())
368 tickLower := position.TickLower()
369 tickUpper := position.TickUpper()
370
371 poolKey := position.PoolKey()
372
373 currentTick, err := pl.GetSlot0Tick(poolKey)
374 if err != nil {
375 return nil, nil, err
376 }
377
378 feeGrowthGlobal0X128Str, feeGrowthGlobal1X128Str, err := pl.GetFeeGrowthGlobalX128(poolKey)
379 if err != nil {
380 return nil, nil, err
381 }
382 feeGrowthGlobal0X128 := u256.MustFromDecimal(feeGrowthGlobal0X128Str)
383 feeGrowthGlobal1X128 := u256.MustFromDecimal(feeGrowthGlobal1X128Str)
384
385 tickUpperFeeGrowthOutside0X128Str, tickUpperFeeGrowthOutside1X128Str, err := pl.GetTickFeeGrowthOutsideX128(poolKey, tickUpper)
386 if err != nil {
387 return nil, nil, err
388 }
389 tickUpperFeeGrowthOutside0X128 := u256.MustFromDecimal(tickUpperFeeGrowthOutside0X128Str)
390 tickUpperFeeGrowthOutside1X128 := u256.MustFromDecimal(tickUpperFeeGrowthOutside1X128Str)
391
392 tickLowerFeeGrowthOutside0X128Str, tickLowerFeeGrowthOutside1X128Str, err := pl.GetTickFeeGrowthOutsideX128(poolKey, tickLower)
393 if err != nil {
394 return nil, nil, err
395 }
396 tickLowerFeeGrowthOutside0X128 := u256.MustFromDecimal(tickLowerFeeGrowthOutside0X128Str)
397 tickLowerFeeGrowthOutside1X128 := u256.MustFromDecimal(tickLowerFeeGrowthOutside1X128Str)
398
399 feeGrowthInside0LastX128 := u256.MustFromDecimal(position.FeeGrowthInside0LastX128())
400 feeGrowthInside1LastX128 := u256.MustFromDecimal(position.FeeGrowthInside1LastX128())
401
402 var tickLowerFeeGrowthBelow0, tickLowerFeeGrowthBelow1, tickUpperFeeGrowthAbove0, tickUpperFeeGrowthAbove1 *u256.Uint
403
404 if currentTick >= tickUpper {
405 tickUpperFeeGrowthAbove0 = u256.Zero().Sub(feeGrowthGlobal0X128, tickUpperFeeGrowthOutside0X128)
406 tickUpperFeeGrowthAbove1 = u256.Zero().Sub(feeGrowthGlobal1X128, tickUpperFeeGrowthOutside1X128)
407 } else {
408 tickUpperFeeGrowthAbove0 = tickUpperFeeGrowthOutside0X128
409 tickUpperFeeGrowthAbove1 = tickUpperFeeGrowthOutside1X128
410 }
411
412 if currentTick >= tickLower {
413 tickLowerFeeGrowthBelow0 = tickLowerFeeGrowthOutside0X128
414 tickLowerFeeGrowthBelow1 = tickLowerFeeGrowthOutside1X128
415 } else {
416 tickLowerFeeGrowthBelow0 = u256.Zero().Sub(feeGrowthGlobal0X128, tickLowerFeeGrowthOutside0X128)
417 tickLowerFeeGrowthBelow1 = u256.Zero().Sub(feeGrowthGlobal1X128, tickLowerFeeGrowthOutside1X128)
418 }
419
420 feeGrowthInside0X128 := u256.Zero().Sub(feeGrowthGlobal0X128, tickLowerFeeGrowthBelow0)
421 feeGrowthInside0X128 = u256.Zero().Sub(feeGrowthInside0X128, tickUpperFeeGrowthAbove0)
422
423 feeGrowthInside1X128 := u256.Zero().Sub(feeGrowthGlobal1X128, tickLowerFeeGrowthBelow1)
424 feeGrowthInside1X128 = u256.Zero().Sub(feeGrowthInside1X128, tickUpperFeeGrowthAbove1)
425
426 diffGrowthInside0X128 := u256.Zero().Sub(feeGrowthInside0X128, feeGrowthInside0LastX128)
427 unclaimedFee0 := u256.MulDiv(diffGrowthInside0X128, liquidity, consts.Q128())
428
429 diffGrowthInside1X128 := u256.Zero().Sub(feeGrowthInside1X128, feeGrowthInside1LastX128)
430 unclaimedFee1 := u256.MulDiv(diffGrowthInside1X128, liquidity, consts.Q128())
431 return unclaimedFee0, unclaimedFee1, nil
432}