diff --git a/position/liquidity_management.gno b/position/liquidity_management.gno index 0604ac36..92cfe729 100644 --- a/position/liquidity_management.gno +++ b/position/liquidity_management.gno @@ -20,7 +20,7 @@ func addLiquidity(params AddLiquidityParams) (bigint, bigint, bigint) { params.amount0Desired, params.amount1Desired, ) - liquidity += 1 // adjust for rounding errors + liquidity += 2 // r3v4_xxx: adjust for rounding errors pToken0, pToken1, pFee := poolKeyDivide(params.poolKey) amount0, amount1 := pl.Mint( diff --git a/position/position.gno b/position/position.gno index 4909b263..0940db3c 100644 --- a/position/position.gno +++ b/position/position.gno @@ -15,10 +15,6 @@ var ( nextId uint64 = 1 ) -func (p Position) isClear() bool { - return p.liquidity == 0 && p.tokensOwed0 == 0 && p.tokensOwed1 == 0 -} - func Mint( token0 string, token1 string, @@ -96,29 +92,127 @@ func mint(params MintParams) (uint64, bigint, bigint, bigint) { return tokenId, liquidity, amount0, amount1 } -func CollectFee(tokenId uint64) (uint64, bigint, bigint, string) { // tokenId, tokensOwed0, tokensOwed1, poolPath +func IncreaseLiquidity( + tokenId uint64, + amount0Desired bigint, + amount1Desired bigint, + amount0Min bigint, + amount1Min bigint, + deadline bigint, +) (uint64, bigint, bigint, bigint) { // tokenId, liquidity, amount0, amount1 + increaseLiquidityParams := IncreaseLiquidityParams{ + tokenId: tokenId, + amount0Desired: amount0Desired, + amount1Desired: amount1Desired, + amount0Min: amount0Min, + amount1Min: amount1Min, + deadline: deadline, + } + + return increaseLiquidity(increaseLiquidityParams) +} + +func increaseLiquidity(params IncreaseLiquidityParams) (uint64, bigint, bigint, bigint) { // verify tokenId exists - require(exists(tokenId), ufmt.Sprintf("[POSITION] position.gno__CollectFee() || tokenId(%d) doesn't exist", tokenId)) + require(exists(params.tokenId), ufmt.Sprintf("[POSITION] position.gno__increaseLiquidity() || tokenId(%d) doesn't exist", params.tokenId)) - // verify owner or approved - isAuthorizedForToken(tokenId) + // MUST BE OWNER TO DECREASE LIQUIDITY + // can not be approved address > staked position can be modified + owner := gnft.OwnerOf(tid(params.tokenId)) + require(owner == GetOrigCaller(), ufmt.Sprintf("[POSITION] position.gno__decreaseLiquidity() || only owner can decrease liquidity__owner(%s) == GetOrigCaller(%s)", owner, GetOrigCaller())) - position := positions[tokenId] - tokensOwed0, tokensOwed1 := position.tokensOwed0, position.tokensOwed1 + checkDeadline(params.deadline) - pToken0, pToken1, pFee := poolKeyDivide(position.poolKey) - pl.Burn(pToken0, pToken1, pFee, position.tickLower, position.tickUpper, 0) // burn '0' liquidity to collect fee + position := positions[params.tokenId] + liquidity, amount0, amount1 := addLiquidity( + AddLiquidityParams{ + poolKey: position.poolKey, + recipient: GetOrigPkgAddr(), // MUST BE POSITION + tickLower: position.tickLower, + tickUpper: position.tickUpper, + amount0Desired: params.amount0Desired, + amount1Desired: params.amount1Desired, + amount0Min: params.amount0Min, + amount1Min: params.amount1Min, + }, + ) + + pool := pl.GetPoolFromPoolPath(position.poolKey) positionKey := positionKeyCompute(GetOrigPkgAddr(), position.tickLower, position.tickUpper) + + feeGrowthInside0LastX128, feeGrowthInside1LastX128 := pool.PoolGetPositionFeeGrowthInside0LastX128(positionKey), pool.PoolGetPositionFeeGrowthInside1LastX128(positionKey) + + position.tokensOwed0 += (feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128) * position.liquidity / consts.Q128 + position.tokensOwed1 += (feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128) * position.liquidity / consts.Q128 + + position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128 + position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128 + position.liquidity += liquidity + + positions[params.tokenId] = position + + return params.tokenId, liquidity, amount0, amount1 +} + +func DecreaseLiquidity( + tokenId uint64, + liquidity bigint, + amount0Min bigint, + amount1Min bigint, + deadline bigint, +) (uint64, bigint, bigint, bigint) { // tokenId, liquidity, amount0, amount1 + decreaseLiquidityParams := DecreaseLiquidityParams{ + tokenId: tokenId, + liquidity: liquidity, + amount0Min: amount0Min, + amount1Min: amount1Min, + deadline: deadline, + } + + return decreaseLiquidity(decreaseLiquidityParams) +} + +func decreaseLiquidity(params DecreaseLiquidityParams) (uint64, bigint, bigint, bigint) { + // verify tokenId + require(exists(params.tokenId), ufmt.Sprintf("[POSITION] position.gno__decreaseLiquidity() || tokenId(%d) doesn't exist", params.tokenId)) + + // MUST BE OWNER TO DECREASE LIQUIDITY ( can not be approved address ) + owner := gnft.OwnerOf(tid(params.tokenId)) + require(owner == GetOrigCaller(), ufmt.Sprintf("[POSITION] position.gno__decreaseLiquidity() || only owner can decrease liquidity__owner(%s) == GetOrigCaller(%s)", owner, GetOrigCaller())) + + checkDeadline(params.deadline) + + require(params.liquidity >= 0, ufmt.Sprintf("[POSITION] position.gno__decreaseLiquidity() || liquidity(%d) >= 0", params.liquidity)) + + position := positions[params.tokenId] + + positionLiquidity := position.liquidity + if positionLiquidity < params.liquidity { + // if too many liquidity requested, decrease to entire liquidity + params.liquidity = positionLiquidity + } + + pToken0, pToken1, pFee := poolKeyDivide(position.poolKey) pool := pl.GetPoolFromPoolPath(position.poolKey) + + burnedAmount0, burnedAmount1 := pl.Burn(pToken0, pToken1, pFee, position.tickLower, position.tickUpper, params.liquidity) + require(burnedAmount0 >= params.amount0Min && burnedAmount1 >= params.amount1Min, ufmt.Sprintf("[POSITION] position.gno__decreaseLiquidity() || burnedAmount0(%d) >= amount0Min(%d) && burnedAmount1(%d) >= amount1Min(%d)", burnedAmount0, params.amount0Min, burnedAmount1, params.amount1Min)) + + positionKey := positionKeyCompute(GetOrigPkgAddr(), position.tickLower, position.tickUpper) feeGrowthInside0LastX128, feeGrowthInside1LastX128 := pool.PoolGetPositionFeeGrowthInside0LastX128(positionKey), pool.PoolGetPositionFeeGrowthInside1LastX128(positionKey) - tokensOwed0 += (feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128) * position.liquidity / consts.Q128 - tokensOwed1 += (feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128) * position.liquidity / consts.Q128 + position.tokensOwed0 += burnedAmount0 + ((feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128) * position.liquidity / consts.Q128) + position.tokensOwed1 += burnedAmount1 + ((feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128) * position.liquidity / consts.Q128) position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128 position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128 + position.liquidity = positionLiquidity - params.liquidity + + positions[params.tokenId] = position + + // GIVE BACK TO USER amount0, amount1 := pl.Collect( pToken0, pToken1, @@ -126,14 +220,71 @@ func CollectFee(tokenId uint64) (uint64, bigint, bigint, string) { // tokenId, t GetOrigCaller(), position.tickLower, position.tickUpper, - tokensOwed0, - tokensOwed1, + burnedAmount0, + burnedAmount1, ) + position.tokensOwed0 -= amount0 + position.tokensOwed1 -= amount1 + + positions[params.tokenId] = position + + if position.isClear() { + // burnNFT(params.tokenId) // actual burn + burnPosition(params.tokenId) // just update flag + } + + return params.tokenId, params.liquidity, amount0, amount1 +} + +func CollectFee(tokenId uint64) (uint64, bigint, bigint, string) { // tokenId, tokensOwed0, tokensOwed1, poolPath + // verify tokenId exists + require(exists(tokenId), ufmt.Sprintf("[POSITION] position.gno__CollectFee() || tokenId(%d) doesn't exist", tokenId)) + + // verify owner or approved + isAuthorizedForToken(tokenId) + + position := positions[tokenId] + + token0, token1, fee := poolKeyDivide(position.poolKey) + burnedAmount0, burnedAmount1 := pl.Burn( + token0, + token1, + fee, + position.tickLower, + position.tickUpper, + 0, // burn '0' liquidity to collect fee + ) + + tokensOwed0, tokensOwed1 := position.tokensOwed0, position.tokensOwed1 + + // r3v4_xxx: DOES THIS NEED FOR COLLECT FEE + // positionKey := positionKeyCompute(GetOrigPkgAddr(), position.tickLower, position.tickUpper) + // pool := pl.GetPoolFromPoolPath(position.poolKey) + // feeGrowthInside0LastX128, feeGrowthInside1LastX128 := pool.PoolGetPositionFeeGrowthInside0LastX128(positionKey), pool.PoolGetPositionFeeGrowthInside1LastX128(positionKey) + + // tokensOwed0 += (feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128) * position.liquidity / consts.Q128 + // tokensOwed1 += (feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128) * position.liquidity / consts.Q128 + + // position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128 + // position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128 + + amount0, amount1 := pl.Collect( + token0, + token1, + fee, + GetOrigCaller(), + position.tickLower, + position.tickUpper, + burnedAmount0, + burnedAmount1, + ) + + // r3v4_xxx: DOES THIS NEED FOR COLLECT FEE position.tokensOwed0, position.tokensOwed1 = tokensOwed0-amount0, tokensOwed1-amount1 positions[tokenId] = position - return tokenId, tokensOwed0, tokensOwed1, position.poolKey + return tokenId, amount0, amount1, position.poolKey } func Burn(tokenId uint64) (uint64, bigint, bigint, bigint, string) { // tokenId, liquidity, amount0, amount1, poolPath @@ -149,8 +300,15 @@ func Burn(tokenId uint64) (uint64, bigint, bigint, bigint, string) { // tokenId, pool := pl.GetPoolFromPoolPath(position.poolKey) // poolKey == poolPath - pToken0, pToken1, pFee := poolKeyDivide(position.poolKey) - burnAmount0, burnAmount1 := pl.Burn(pToken0, pToken1, pFee, position.tickLower, position.tickUpper, positionLiquidity) + token0, token1, fee := poolKeyDivide(position.poolKey) + burnAmount0, burnAmount1 := pl.Burn( + token0, + token1, + fee, + position.tickLower, + position.tickUpper, + positionLiquidity, + ) positionKey := positionKeyCompute(GetOrigPkgAddr(), position.tickLower, position.tickUpper) feeGrowthInside0LastX128, feeGrowthInside1LastX128 := pool.PoolGetPositionFeeGrowthInside0LastX128(positionKey), pool.PoolGetPositionFeeGrowthInside1LastX128(positionKey) @@ -165,9 +323,9 @@ func Burn(tokenId uint64) (uint64, bigint, bigint, bigint, string) { // tokenId, positions[tokenId] = position collectAmount0, collectAmount1 := pl.Collect( - pToken0, - pToken1, - pFee, + token0, + token1, + fee, GetOrigCaller(), position.tickLower, position.tickUpper, @@ -180,7 +338,7 @@ func Burn(tokenId uint64) (uint64, bigint, bigint, bigint, string) { // tokenId, positions[tokenId] = position - // burnNFT(tokenId) // DO NOT BURN NFT + // burnNFT(tokenId) // DO NOT BURN TO SHOW CLOSED POSITION burnPosition(tokenId) // JUST UPDATE FLAG return tokenId, positionLiquidity, collectAmount0, collectAmount1, position.poolKey @@ -223,3 +381,7 @@ func deleteFromPositions(m map[uint64]Position, key uint64) map[uint64]Position return m } + +func (p Position) isClear() bool { + return p.liquidity == 0 && p.tokensOwed0 == 0 && p.tokensOwed1 == 0 +} diff --git a/position/type.gno b/position/type.gno index f7329a4c..33110351 100644 --- a/position/type.gno +++ b/position/type.gno @@ -25,7 +25,7 @@ type Position struct { burned bool } -type MintParams struct { // r3v4_xxx: private? +type MintParams struct { token0 string token1 string fee uint16 @@ -39,7 +39,7 @@ type MintParams struct { // r3v4_xxx: private? deadline bigint } -type AddLiquidityParams struct { // r3v4_xxx: private? +type AddLiquidityParams struct { poolKey string recipient std.Address // XXX de facto: hardcoded to nft manager contract address tickLower int32 @@ -50,7 +50,7 @@ type AddLiquidityParams struct { // r3v4_xxx: private? amount1Min bigint } -type IncreaseLiquidityParams struct { // r3v4_xxx: private? +type IncreaseLiquidityParams struct { tokenId uint64 amount0Desired bigint amount1Desired bigint @@ -59,8 +59,17 @@ type IncreaseLiquidityParams struct { // r3v4_xxx: private? deadline bigint } -type DecreaseLiquidityParams struct { // r3v4_xxx: private? - tokenId uint64 - liquidity bigint - deadline bigint +type DecreaseLiquidityParams struct { + tokenId uint64 + liquidity bigint + amount0Min bigint + amount1Min bigint + deadline bigint +} + +type CollectParams struct { + tokenId uint64 + recipient std.Address + amount0Max bigint + amount1Max bigint }