Skip to content

Commit

Permalink
GSW-792 feat: do not burn nft even entire liquidity is removed
Browse files Browse the repository at this point in the history
  • Loading branch information
r3v4s committed Feb 26, 2024
1 parent 35127dd commit 5ab7987
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 144 deletions.
13 changes: 12 additions & 1 deletion position/_RPC_api.gno
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

type RpcPosition struct {
LpTokenId uint64 `json:"lpTokenId"`
Burned bool `json:"burned"`
Operator string `json:"operator"`
PoolKey string `json:"poolKey"`
TickLower int32 `json:"tickLower"`
Expand Down Expand Up @@ -68,6 +69,8 @@ func rpcMakePosition(lpTokenId uint64) RpcPosition {
panic(ufmt.Sprintf("[POSITION] getter_api.gno__rpcMakePosition() || position not found for lpTokenId(%d)", lpTokenId))
}

burned := isBurned(lpTokenId)

pool := pl.GetPoolFromPoolPath(position.poolKey)
currentX96 := pool.PoolGetSlot0SqrtPriceX96()
lowerX96 := common.TickMathGetSqrtRatioAtTick(position.tickLower)
Expand All @@ -80,10 +83,14 @@ func rpcMakePosition(lpTokenId uint64) RpcPosition {
position.liquidity,
)

unclaimedFee0, unclaimedFee1 := unclaimedFee(lpTokenId)
var unclaimedFee0, unclaimedFee1 bigint // if position is burned, unclaimedFee0 and unclaimedFee1 will be 0
if !burned {
unclaimedFee0, unclaimedFee1 := unclaimedFee(lpTokenId)
}

return RpcPosition{
LpTokenId: lpTokenId,
Burned: burned,
Operator: position.operator.String(),
PoolKey: position.poolKey,
TickLower: position.tickLower,
Expand Down Expand Up @@ -150,3 +157,7 @@ func unclaimedFee(tokenId uint64) (bigint, bigint) {

return unclaimedFee0, unclaimedFee1
}

func isBurned(tokenId uint64) bool {
return positions[tokenId].burned
}
6 changes: 4 additions & 2 deletions position/_TEST_INIT_basic_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

"gno.land/r/demo/wugnot"

nft "gno.land/r/demo/gnft"
"gno.land/r/demo/gnft"
)

var (
Expand All @@ -28,6 +28,8 @@ var (
fee100 = uint16(100)
fee500 = uint16(500)
fee3000 = uint16(3000)

max_timeout = bigint(9999999999)
)

func init() {
Expand Down Expand Up @@ -97,7 +99,7 @@ func ugnotBalanceOf(addr std.Address) uint64 {
}

func isOwner(t *testing.T, tokenId uint64, addr std.Address) bool {
owner := nft.OwnerOf(tid(tokenId))
owner := gnft.OwnerOf(tid(tokenId))

if owner == addr {
return true
Expand Down
201 changes: 87 additions & 114 deletions position/_TEST_position_test.gno
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
package position

import (
"encoding/gjson"
"std"
"testing"

"encoding/gjson"
"gno.land/p/demo/common"

pl "gno.land/r/demo/pool"
)

var (
test_tickLower = int32(9000)
test_tickUpper = int32(11000)
test_liquidityExpect = bigint(1000)
"gno.land/r/demo/gnft"
)

// 1. Init & Create Pool
Expand All @@ -21,128 +18,104 @@ func TestPoolInitCreatePool(t *testing.T) {
pl.InitManual()

std.TestSetOrigCaller(test1)
pl.CreatePool(fooPath, barPath, fee500, 130621891405341611593710811006)

// fee
// 500 = 0.05% // USv3 default
// 3000 = 0.3% // USv3 default
// 10000 = 1% // USv3 default
pl.CreatePool(barPath, fooPath, fee500, common.TickMathGetSqrtRatioAtTick(10000)) // x2.71814592682522526700950038502924144268035888671875

// sqrtPrice
// 130621891405341611593710811006 // tick = 10000
shouldPanic(t, func() { pl.CreatePool(fooPath, barPath, fee500, 130621891405341611593710811006) })
shouldPanic(t, func() { pl.CreatePool(fooPath, barPath, fee500, common.TickMathGetSqrtRatioAtTick(10000)) })
}

// 2. Mint LP and Get GNFT
func TestMint(t *testing.T) {
// test1 mints
// will get tid 1 nft
{
std.TestSetOrigCaller(test1)

tTokenId, tLiquidity, tAmount0, tAmount1 := Mint(
fooPath,
barPath,
fee500,
test_tickLower,
test_tickUpper,
bigint(50000000),
bigint(50000000),
bigint(1),
bigint(1),
bigint(1234567890),
)

isOwner(t, tTokenId, test1)
shouldEQ(t, tTokenId, 1)
shouldEQ(t, getNextId(), 2)
}

// test1 mints
// will get tid 2 nft
{
std.TestSetOrigCaller(test1)

tTokenId, tLiquidity, tAmount0, tAmount1 := Mint(
fooPath,
barPath,
fee500,
test_tickLower,
test_tickUpper,
bigint(2000),
bigint(2000),
bigint(1),
bigint(1),
bigint(1234567890),
)

isOwner(t, tTokenId, test1)
shouldEQ(t, tTokenId, 2)
shouldEQ(t, getNextId(), 3)
}

// test1 mints out of range => token1 will be 0
// will get tid 3 nft
{
std.TestSetOrigCaller(test1)

tTokenId, tLiquidity, tAmount0, tAmount1 := Mint(
fooPath,
barPath,
fee500,
int32(1000),
int32(2000),
bigint(1000),
bigint(1000),
bigint(0),
bigint(0),
bigint(1234567890),
)
shouldEQ(t, tAmount0, bigint(1000))
shouldEQ(t, tAmount1, bigint(0))

isOwner(t, tTokenId, test1)
shouldEQ(t, tTokenId, 3)
shouldEQ(t, getNextId(), 4)
}

// test1 mints out of range => token0 will be 0
// will get tid 4 nft
{
std.TestSetOrigCaller(test1)

tTokenId, tLiquidity, tAmount0, tAmount1 := Mint(
fooPath,
barPath,
fee500,
int32(-34000),
int32(-32000),
bigint(1000),
bigint(1000),
bigint(0),
bigint(0),
bigint(1234567890),
)
shouldEQ(t, tAmount0, bigint(1000))
shouldEQ(t, tAmount1, bigint(0))

isOwner(t, tTokenId, test1)
shouldEQ(t, tTokenId, 4)
shouldEQ(t, getNextId(), 5)
}
func TestMintPosition01InRange(t *testing.T) {
std.TestSetOrigCaller(test1)

tokenId, liquidity, amount0, amount1 := Mint(
barPath,
fooPath,
fee500,
8000,
12000,
bigint(50000000),
bigint(50000000),
bigint(0),
bigint(0),
max_timeout,
)
shouldEQ(t, tokenId, 1)
shouldEQ(t, getNextId(), 2)
shouldEQ(t, amount0, bigint(18394891))
shouldEQ(t, amount1, bigint(50000000))
}

func TestCollectFee(t *testing.T) {
// collect fee from tid 1 ( no swap, no fee)
func TestMintPosition02LowerRange(t *testing.T) {
std.TestSetOrigCaller(test1)

tokenId, liquidity, amount0, amount1 := Mint(
barPath,
fooPath,
fee500,
5000,
8000,
bigint(50000000),
bigint(50000000),
bigint(0),
bigint(0),
max_timeout,
)
shouldEQ(t, tokenId, 2)
shouldEQ(t, getNextId(), 3)
shouldEQ(t, amount0, bigint(0))
shouldEQ(t, amount1, bigint(50000000))
}

func TestMintPosition03UpperRange(t *testing.T) {
std.TestSetOrigCaller(test1)

tokenId, liquidity, amount0, amount1 := Mint(
barPath,
fooPath,
fee500,
12000,
14000,
bigint(50000000),
bigint(50000000),
bigint(0),
bigint(0),
max_timeout,
)
shouldEQ(t, tokenId, 3)
shouldEQ(t, getNextId(), 4)
shouldEQ(t, amount0, bigint(50000000))
shouldEQ(t, amount1, bigint(0))
}

func TestCollectFeeBeforeSwap(t *testing.T) {
tokenId, fee0, fee1, poolPath := CollectFee(1)
shouldEQ(t, tokenId, uint64(1))
shouldEQ(t, fee0, bigint(0))
shouldEQ(t, fee1, bigint(0))
shouldEQ(t, poolPath, "gno.land/r/demo/bar:gno.land/r/demo/foo:500")
}

func TestBurnUpperPosition(t *testing.T) {
std.TestSetOrigCaller(test1)

ownerOfPosition := gnft.OwnerOf(tid(3))
shouldEQ(t, ownerOfPosition, GetOrigCaller())

tokenId, liquidity, amount0, amount1, poolPath := Burn(3)
shouldEQ(t, tokenId, uint64(3))
shouldEQ(t, amount0, bigint(50000000))
shouldEQ(t, amount1, bigint(0))

ownerOfPosition = gnft.OwnerOf(tid(3))
shouldEQ(t, ownerOfPosition, GetOrigCaller())
}

func TestApiGetPositions(t *testing.T) {
gpss := ApiGetPositions()
jsonStr := gjson.Parse(gpss)
shouldEQ(t, len(jsonStr.Get("response").Array()), 4)
jsonArr := jsonStr.Get("response").Array()
shouldEQ(t, len(jsonArr), 3)

shouldEQ(t, jsonArr[0].Get("burned").Bool(), false)
shouldEQ(t, jsonArr[2].Get("burned").Bool(), true)
}
21 changes: 1 addition & 20 deletions position/gno.mod
Original file line number Diff line number Diff line change
@@ -1,20 +1 @@
module gno.land/r/demo/position

require (
gno.land/p/demo/common v0.0.0-latest
gno.land/p/demo/grc/grc721 v0.0.0-latest
gno.land/p/demo/ufmt v0.0.0-latest
gno.land/r/demo/bar v0.0.0-latest
gno.land/r/demo/baz v0.0.0-latest
gno.land/r/demo/consts v0.0.0-latest
gno.land/r/demo/foo v0.0.0-latest
gno.land/r/demo/fred v0.0.0-latest
gno.land/r/demo/gnft v0.0.0-latest
gno.land/r/demo/gns v0.0.0-latest
gno.land/r/demo/obl v0.0.0-latest
gno.land/r/demo/pool v0.0.0-latest
gno.land/r/demo/qux v0.0.0-latest
gno.land/r/demo/thud v0.0.0-latest
gno.land/r/demo/users v0.0.0-latest
gno.land/r/demo/wugnot v0.0.0-latest
)
module gno.land/r/demo/position
10 changes: 5 additions & 5 deletions position/nft_helper.gno
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import (
"gno.land/p/demo/ufmt"
"gno.land/r/demo/consts"

nft "gno.land/r/demo/gnft"
"gno.land/r/demo/gnft"
)

func nftBalanceOf(addr std.Address) bigint {
return bigint(nft.BalanceOf(a2u(addr)))
return bigint(gnft.BalanceOf(a2u(addr)))
}

func exists(tokenId uint64) bool {
Expand All @@ -24,7 +24,7 @@ func exists(tokenId uint64) bool {

// exists method in grc721 is private
// we don't have much choice but to use ownerOf
owner := nft.OwnerOf(tid(tokenId))
owner := gnft.OwnerOf(tid(tokenId))
if owner == consts.ZERO_ADDRESS {
panic(ufmt.Sprintf("[POSITION] nft_helper.gno__exists() || tokenId(%s) doesn't exist__ZeroAddressOwner", tokenId))
return false
Expand All @@ -43,7 +43,7 @@ func isApprovedOrOwner(addr std.Address, tokenId uint64) bool {
}

// check owner first
owner := nft.OwnerOf(tid)
owner := gnft.OwnerOf(tid)
if addr == owner {
return true
}
Expand All @@ -57,7 +57,7 @@ func isApprovedOrOwner(addr std.Address, tokenId uint64) bool {
}

// if not owner, check whether approved in actual grc721 contract
operator, ok := nft.GetApproved(tid)
operator, ok := gnft.GetApproved(tid)
if ok && addr == operator {
return true
}
Expand Down
12 changes: 10 additions & 2 deletions position/position.gno
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ func Burn(tokenId uint64) (uint64, bigint, bigint, bigint, string) { // tokenId,

positions[tokenId] = position

// MUST BE BURN NFT
burnNFT(tokenId)
// burnNFT(tokenId) // DO NOT BURN NFT
burnPosition(tokenId) // JUST UPDATE FLAG

return tokenId, positionLiquidity, collectAmount0, collectAmount1, position.poolKey
}
Expand All @@ -197,6 +197,14 @@ func burnNFT(tokenId uint64) {
gnft.Burn(tid(tokenId))
}

func burnPosition(tokenId uint64) {
position := positions[tokenId]
require(position.isClear(), ufmt.Sprintf("[POSITION] position.gno__burnPosition() || position(tokenId:%d) isn't clear(liquidity:%d, tokensOwed0:%d, tokensOwed1:%d)", tokenId, position.liquidity, position.tokensOwed0, position.tokensOwed1))

position.burned = true
positions[tokenId] = position
}

func isAuthorizedForToken(tokenId uint64) {
require(isApprovedOrOwner(PrevRealmAddr(), tokenId), ufmt.Sprintf("[POSITION] position.gno__isAuthorizedForToken() || caller(%s) is not approved or owner of tokenId(%d)", PrevRealmAddr(), tokenId))
}
Expand Down
Loading

0 comments on commit 5ab7987

Please sign in to comment.