Skip to content
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

GSW-1071 fix unclaimed fee overflow underflow #220

Merged
merged 2 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions _deploy/r/demo/gnoswap/consts/consts.gno
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ const (

GNFT_PATH string = "gno.land/r/demo/gnft"
GNFT_ADDR std.Address = std.DerivePkgAddr(GNFT_PATH)

WUGNOT_PATH string = "gno.land/r/demo/wugnot"
WUGNOT_ADDR std.Address = std.DerivePkgAddr(WUGNOT_PATH)
)

// NUMBER
Expand Down
81 changes: 53 additions & 28 deletions position/_RPC_api.gno
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
pl "gno.land/r/demo/pool"

i256 "gno.land/p/demo/gnoswap/int256"
u256 "gno.land/p/demo/gnoswap/uint256"
)

type RpcPosition struct {
Expand Down Expand Up @@ -123,8 +122,8 @@ func rpcMakePosition(lpTokenId uint64) RpcPosition {
i256.FromUint256(position.liquidity),
)

unclaimedFee0 := u256.Zero()
unclaimedFee1 := u256.Zero()
unclaimedFee0 := i256.Zero()
unclaimedFee1 := i256.Zero()
if !burned {
unclaimedFee0, unclaimedFee1 = unclaimedFee(lpTokenId)
}
Expand All @@ -148,10 +147,12 @@ func rpcMakePosition(lpTokenId uint64) RpcPosition {
}
}

func unclaimedFee(tokenId uint64) (*u256.Uint, *u256.Uint) {
func unclaimedFee(tokenId uint64) (*i256.Int, *i256.Int) {
// ref: https://blog.uniswap.org/uniswap-v3-math-primer-2#calculating-uncollected-fees

liquidity := positions[tokenId].liquidity
_liquidity := positions[tokenId].liquidity // u256
liquidity := i256.FromUint256(_liquidity) // i256

tickLower := positions[tokenId].tickLower
tickUpper := positions[tokenId].tickUpper

Expand All @@ -160,23 +161,35 @@ func unclaimedFee(tokenId uint64) (*u256.Uint, *u256.Uint) {

currentTick := pool.PoolGetSlot0Tick()

feeGrowthGlobal0X128 := pool.PoolGetFeeGrowthGlobal0X128()
feeGrowthGlobal1X128 := pool.PoolGetFeeGrowthGlobal1X128()
_feeGrowthGlobal0X128 := pool.PoolGetFeeGrowthGlobal0X128() // u256
feeGrowthGlobal0X128 := i256.FromUint256(_feeGrowthGlobal0X128) // i256

_feeGrowthGlobal1X128 := pool.PoolGetFeeGrowthGlobal1X128() // u256
feeGrowthGlobal1X128 := i256.FromUint256(_feeGrowthGlobal1X128) // i256

_tickUpperFeeGrowthOutside0X128 := pool.PoolGetTickFeeGrowthOutside0X128(tickUpper) // u256
tickUpperFeeGrowthOutside0X128 := i256.FromUint256(_tickUpperFeeGrowthOutside0X128) // i256

_tickUpperFeeGrowthOutside1X128 := pool.PoolGetTickFeeGrowthOutside1X128(tickUpper) // u256
tickUpperFeeGrowthOutside1X128 := i256.FromUint256(_tickUpperFeeGrowthOutside1X128) // i256

_tickLowerFeeGrowthOutside0X128 := pool.PoolGetTickFeeGrowthOutside0X128(tickLower) // u256
tickLowerFeeGrowthOutside0X128 := i256.FromUint256(_tickLowerFeeGrowthOutside0X128) // i256

tickUpperFeeGrowthOutside0X128 := pool.PoolGetTickFeeGrowthOutside0X128(tickUpper)
tickUpperFeeGrowthOutside1X128 := pool.PoolGetTickFeeGrowthOutside1X128(tickUpper)
_tickLowerFeeGrowthOutside1X128 := pool.PoolGetTickFeeGrowthOutside1X128(tickLower) // u256
tickLowerFeeGrowthOutside1X128 := i256.FromUint256(_tickLowerFeeGrowthOutside1X128) // i256

tickLowerFeeGrowthOutside0X128 := pool.PoolGetTickFeeGrowthOutside0X128(tickLower)
tickLowerFeeGrowthOutside1X128 := pool.PoolGetTickFeeGrowthOutside1X128(tickLower)
_feeGrowthInside0LastX128 := positions[tokenId].feeGrowthInside0LastX128 // u256
feeGrowthInside0LastX128 := i256.FromUint256(_feeGrowthInside0LastX128) // i256

feeGrowthInside0LastX128 := positions[tokenId].feeGrowthInside0LastX128
feeGrowthInside1LastX128 := positions[tokenId].feeGrowthInside1LastX128
_feeGrowthInside1LastX128 := positions[tokenId].feeGrowthInside1LastX128 // u256
feeGrowthInside1LastX128 := i256.FromUint256(_feeGrowthInside1LastX128) // i256

var tickLowerFeeGrowthBelow0, tickLowerFeeGrowthBelow1, tickUpperFeeGrowthAbove0, tickUpperFeeGrowthAbove1 *u256.Uint
var tickLowerFeeGrowthBelow0, tickLowerFeeGrowthBelow1, tickUpperFeeGrowthAbove0, tickUpperFeeGrowthAbove1 *i256.Int

if currentTick >= tickUpper {
tickUpperFeeGrowthAbove0 = new(u256.Uint).Sub(feeGrowthGlobal0X128, tickUpperFeeGrowthOutside0X128)
tickUpperFeeGrowthAbove1 = new(u256.Uint).Sub(feeGrowthGlobal1X128, tickUpperFeeGrowthOutside1X128)
tickUpperFeeGrowthAbove0 = subIn256(feeGrowthGlobal0X128, tickUpperFeeGrowthOutside0X128)
tickUpperFeeGrowthAbove1 = subIn256(feeGrowthGlobal1X128, tickUpperFeeGrowthOutside1X128)
} else {
tickUpperFeeGrowthAbove0 = tickUpperFeeGrowthOutside0X128
tickUpperFeeGrowthAbove1 = tickUpperFeeGrowthOutside1X128
Expand All @@ -186,27 +199,39 @@ func unclaimedFee(tokenId uint64) (*u256.Uint, *u256.Uint) {
tickLowerFeeGrowthBelow0 = tickLowerFeeGrowthOutside0X128
tickLowerFeeGrowthBelow1 = tickLowerFeeGrowthOutside1X128
} else {
tickLowerFeeGrowthBelow0 = new(u256.Uint).Sub(feeGrowthGlobal0X128, tickLowerFeeGrowthOutside0X128)
tickLowerFeeGrowthBelow1 = new(u256.Uint).Sub(feeGrowthGlobal1X128, tickLowerFeeGrowthOutside1X128)
tickLowerFeeGrowthBelow0 = subIn256(feeGrowthGlobal0X128, tickLowerFeeGrowthOutside0X128)
tickLowerFeeGrowthBelow1 = subIn256(feeGrowthGlobal1X128, tickLowerFeeGrowthOutside1X128)
}

feeGrowthInside0X128 := new(u256.Uint).Sub(feeGrowthGlobal0X128, tickLowerFeeGrowthBelow0)
feeGrowthInside0X128 = new(u256.Uint).Sub(feeGrowthInside0X128, tickUpperFeeGrowthAbove0)
feeGrowthInside0X128 := subIn256(feeGrowthGlobal0X128, tickLowerFeeGrowthBelow0)
feeGrowthInside0X128 = subIn256(feeGrowthInside0X128, tickUpperFeeGrowthAbove0)

feeGrowthInside1X128 := new(u256.Uint).Sub(feeGrowthGlobal1X128, tickLowerFeeGrowthBelow1)
feeGrowthInside1X128 = new(u256.Uint).Sub(feeGrowthInside1X128, tickUpperFeeGrowthAbove1)
feeGrowthInside1X128 := subIn256(feeGrowthGlobal1X128, tickLowerFeeGrowthBelow1)
feeGrowthInside1X128 = subIn256(feeGrowthInside1X128, tickUpperFeeGrowthAbove1)

value01 := new(u256.Uint).Sub(feeGrowthInside0X128, feeGrowthInside0LastX128)
value02 := new(u256.Uint).Mul(liquidity, value01)
unclaimedFee0 := new(u256.Uint).Div(value02, u256.MustFromDecimal(consts.Q128))
value01 := subIn256(feeGrowthInside0X128, feeGrowthInside0LastX128)
value02 := i256.Zero().Mul(liquidity, value01)
unclaimedFee0 := i256.Zero().Div(value02, i256.MustFromDecimal(consts.Q128))

value11 := new(u256.Uint).Sub(feeGrowthInside1X128, feeGrowthInside1LastX128)
value12 := new(u256.Uint).Mul(liquidity, value11)
unclaimedFee1 := new(u256.Uint).Div(value12, u256.MustFromDecimal(consts.Q128))
value11 := subIn256(feeGrowthInside1X128, feeGrowthInside1LastX128)
value12 := i256.Zero().Mul(liquidity, value11)
unclaimedFee1 := i256.Zero().Div(value12, i256.MustFromDecimal(consts.Q128))

return unclaimedFee0, unclaimedFee1
}

func subIn256(x, y *i256.Int) *i256.Int {
value := i256.Zero()
diff := value.Sub(x, y)

if diff.IsNeg() {
q256 := i256.MustFromDecimal(consts.MAX_UINT256)
return diff.Add(diff, q256)
}

return diff
}

func isBurned(tokenId uint64) bool {
return positions[tokenId].burned
}
227 changes: 227 additions & 0 deletions position/_TEST_/_TEST_position_ZZ_increase_decrease_native_test.gnoA
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
package position

import (
"std"
"testing"

"gno.land/r/demo/gnoswap/common"
"gno.land/r/demo/gnoswap/consts"

"gno.land/r/demo/gns"
"gno.land/r/demo/wugnot"

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

// 1. Create Pool
func TestPoolInitCreatePool(t *testing.T) {
std.TestSetPrevAddr(gsa)

gns.Approve(a2u(consts.POOL_ADDR), consts.POOL_CREATION_FEE)
pl.CreatePool(consts.GNS_PATH, consts.GNOT, fee500, common.TickMathGetSqrtRatioAtTick(10000).ToString()) // x2.71814592682522526700950038502924144268035888671875
// event: {GNOSWAP gno.land/r/demo/pool CreatePool [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_poolPath gno.land/r/demo/gns:gno.land/r/demo/wugnot:500}]}
}

func TestMintPosition(t *testing.T) {
std.TestSetPrevAddr(gsa)

gns.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX)
wugnot.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX)

wugnot.Approve(a2u(consts.POSITION_ADDR), consts.UINT64_MAX) // WRAP

// prepare 50000005ugnot (5 for refund test)
testBanker := std.GetBanker(std.BankerTypeRealmIssue)
testBanker.IssueCoin(gsa, "ugnot", 50000005)

// simulate transfer & decrase
gsaNativeBalance := ugnotBalanceOf(gsa)
shouldEQ(t, gsaNativeBalance, 50000005)

std.TestSetOrigSend(std.Coins{{"ugnot", 50000005}}, nil)
testBanker.RemoveCoin(std.GetOrigCaller(), "ugnot", -50000005)

gsaNativeBalance = ugnotBalanceOf(gsa)
shouldEQ(t, gsaNativeBalance, 0)

gsaOldWugnotBalance := wugnot.BalanceOf(a2u(gsa))
shouldEQ(t, gsaOldWugnotBalance, 0)

tokenId, liquidity, amount0, amount1 := Mint(
consts.GNS_PATH,
consts.GNOT,
fee500,
8000,
12000,
"50000000",
"50000000",
"0",
"0",
max_timeout,
gsa.String(),
)
// event: {GNOSWAP gno.land/r/demo/position Mint [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_poolPath gno.land/r/demo/gns:gno.land/r/demo/wugnot:500} {p_tickLower 8000} {p_tickUpper 12000} {tokenId 1} {liquidity 318704392} {amount0 18394892} {amount1 50000000}]}

shouldEQ(t, tokenId, 1)
shouldEQ(t, getNextId(), 2)
shouldEQ(t, amount0, "18394892")
shouldEQ(t, amount1, "50000000")

position := positions[tokenId]
shouldEQ(t, position.poolKey, "gno.land/r/demo/gns:gno.land/r/demo/wugnot:500")

// SPEND ALL WUGNOT
newOldWugnotBalance := wugnot.BalanceOf(a2u(gsa))
shouldEQ(t, gsaOldWugnotBalance, newOldWugnotBalance)

gsaNativeBalance = ugnotBalanceOf(gsa)
shouldEQ(t, gsaNativeBalance, 5)
// 1. 50000005 ugnot sent
// 2. 50000005 ugnot wrapped to wugnot
// 3. 50000000 wugnot spent to mint (amount1)
// 4. refund 50000005 - 50000000 = 5

}

func TestIncreaseLiquidity(t *testing.T) {
std.TestSetPrevAddr(gsa)

gns.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX)
wugnot.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX)

wugnot.Approve(a2u(consts.POSITION_ADDR), consts.UINT64_MAX) // WRAP

pool := getPoolFromLpTokenId(uint64(1))
oldLiquidity := pool.PoolGetLiquidity()

// prepare 10000005ugnot (5 for refund test)
testBanker := std.GetBanker(std.BankerTypeRealmIssue)
testBanker.IssueCoin(gsa, "ugnot", 10000005)

// simulate transfer & decrase
gsaNativeBalance := ugnotBalanceOf(gsa)
shouldEQ(t, gsaNativeBalance, 10000010)

std.TestSetOrigSend(std.Coins{{"ugnot", 10000005}}, nil)
testBanker.RemoveCoin(std.GetOrigCaller(), "ugnot", -10000005)

gsaNativeBalance = ugnotBalanceOf(gsa)
shouldEQ(t, gsaNativeBalance, 5)

gsaOldWugnotBalance := wugnot.BalanceOf(a2u(gsa))
shouldEQ(t, gsaOldWugnotBalance, 0)

_, _, m0, m1, _ := IncreaseLiquidity( // tokenId, liq, a0, a1, poolPath
uint64(1), // tokenId
"10000000", // amount0Desired
"10000000", // amount1Desired
"0", // amount0Min
"0", // amount1Min
max_timeout, // deadline
)
// event: {GNOSWAP gno.land/r/demo/position IncreaseLiquidity [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_tokenId 1} {poolPath gno.land/r/demo/gns:gno.land/r/demo/wugnot:500} {liquidity 63740878} {amount0 3678979} {amount1 10000000}]}
shouldEQ(t, m0, "3678979")
shouldEQ(t, m1, "10000000")

newLiquidity := pool.PoolGetLiquidity()

shouldEQ(t, newLiquidity.Gt(oldLiquidity), true)

// SPEND ALL WUGNOT
newOldWugnotBalance := wugnot.BalanceOf(a2u(gsa))
shouldEQ(t, gsaOldWugnotBalance, newOldWugnotBalance)

gsaNativeBalance = ugnotBalanceOf(gsa)
shouldEQ(t, gsaNativeBalance, 10)
// 1. 10000005 ugnot sent
// 2. 10000005 ugnot wrapped to wugnot
// 3. 10000000 wugnot spent to mint (amount1)
// 4. refund 10000005 - 10000000 = 5
// 5. user already had 5 ugnot = 5 + 5
}

func TestDecreaseLiquidityWrapped(t *testing.T) {
std.TestSetPrevRealm("")
std.TestSetOrigCaller(gsa)

oldLiquidity := getPoolFromLpTokenId(uint64(1)).PoolGetLiquidity()

userWugnotBalance := wugnot.BalanceOf(a2u(gsa))
shouldEQ(t, userWugnotBalance, 0)

userUgnotBalance := ugnotBalanceOf(gsa)
shouldEQ(t, userUgnotBalance, 10)

_, _, _, _, a0, a1, _ := DecreaseLiquidity( // tokenId, liquidity, fee0, fee1, amount0, amount1, poolPath
uint64(1), // tokenId
20, // liquidityRatio
"0", // amount0Min
"0", // amount1Min
max_timeout, // deadline
false, // unwrapResult
)
// --- event: {GNOSWAP gno.land/r/demo/pool HandleWithdrawalFee [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm gno.land/r/demo/position} {p_tokenId 1} {p_token0Path gno.land/r/demo/gns} {p_token1Path gno.land/r/demo/wugnot} {fee0Amount 0} {fee1Amount 0}]}
// --- event: {GNOSWAP gno.land/r/demo/position CollectFee [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_tokenId 1} {fee0 0} {fee1 0} {poolPath gno.land/r/demo/gns:gno.land/r/demo/wugnot:500}]}
// --- event: {GNOSWAP gno.land/r/demo/position DecreaseLiquidity [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_tokenId 1} {p_liquidityRatio 20} {poolPath gno.land/r/demo/gns:gno.land/r/demo/wugnot:500} {liquidity 76489054} {fee0 0} {fee1 0} {amount0 4414773} {amount1 11999999}]}

userWugnotBalance = wugnot.BalanceOf(a2u(gsa)) // wrapped result, so wunogt increased
shouldEQ(t, userWugnotBalance, 11999999)

userUgnotBalance = ugnotBalanceOf(gsa) // wrapped result, so ugnot didn't change
shouldEQ(t, userUgnotBalance, 10)

newLiquidity := getPoolFromLpTokenId(uint64(1)).PoolGetLiquidity()
shouldEQ(t, true, newLiquidity.Lt(oldLiquidity))

// check fee left
tokenId, fee0, fee1, poolPath := CollectFee(1)
// --- event: {GNOSWAP gno.land/r/demo/pool HandleWithdrawalFee [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm gno.land/r/demo/position} {p_tokenId 1} {p_token0Path gno.land/r/demo/gns} {p_token1Path gno.land/r/demo/wugnot} {fee0Amount 0} {fee1Amount 0}]}
// --- event: {GNOSWAP gno.land/r/demo/position CollectFee [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_tokenId 1} {fee0 0} {fee1 0} {poolPath gno.land/r/demo/gns:gno.land/r/demo/wugnot:500}]}

shouldEQ(t, tokenId, uint64(1))
shouldEQ(t, fee0, "0")
shouldEQ(t, fee1, "0")
}

func TestDecreaseLiquidityUnwrapped(t *testing.T) {
std.TestSetPrevRealm("")
std.TestSetOrigCaller(gsa)

oldLiquidity := getPoolFromLpTokenId(uint64(1)).PoolGetLiquidity()

userWugnotBalance := wugnot.BalanceOf(a2u(gsa))
shouldEQ(t, userWugnotBalance, 11999999)

userUgnotBalance := ugnotBalanceOf(gsa)
shouldEQ(t, userUgnotBalance, 10)

_, _, _, _, a0, a1, _ := DecreaseLiquidity( // tokenId, liquidity, fee0, fee1, amount0, amount1, poolPath
uint64(1), // tokenId
50, // liquidityRatio
"0", // amount0Min
"0", // amount1Min
max_timeout, // deadline
true, // unwrapResult
)
// --- event: {GNOSWAP gno.land/r/demo/pool HandleWithdrawalFee [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm gno.land/r/demo/position} {p_tokenId 1} {p_token0Path gno.land/r/demo/gns} {p_token1Path gno.land/r/demo/wugnot} {fee0Amount 0} {fee1Amount 0}]}
// --- event: {GNOSWAP gno.land/r/demo/position CollectFee [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_tokenId 1} {fee0 0} {fee1 0} {poolPath gno.land/r/demo/gns:gno.land/r/demo/wugnot:500}]}
// --- event: {GNOSWAP gno.land/r/demo/position DecreaseLiquidity [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_tokenId 1} {p_liquidityRatio 20} {poolPath gno.land/r/demo/gns:gno.land/r/demo/wugnot:500} {liquidity 76489054} {fee0 0} {fee1 0} {amount0 4414773} {amount1 11999999}]}

userWugnotBalance = wugnot.BalanceOf(a2u(gsa)) // unwrapped result, so wugnot didn't change
shouldEQ(t, userWugnotBalance, 11999999)

userUgnotBalance = ugnotBalanceOf(gsa) // unwrapped result, so ugnot decreased
shouldEQ(t, userUgnotBalance, 24000009)

newLiquidity := getPoolFromLpTokenId(uint64(1)).PoolGetLiquidity()
shouldEQ(t, true, newLiquidity.Lt(oldLiquidity))

// check fee left
tokenId, fee0, fee1, poolPath := CollectFee(1)
// --- event: {GNOSWAP gno.land/r/demo/pool HandleWithdrawalFee [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm gno.land/r/demo/position} {p_tokenId 1} {p_token0Path gno.land/r/demo/gns} {p_token1Path gno.land/r/demo/wugnot} {fee0Amount 0} {fee1Amount 0}]}
// --- event: {GNOSWAP gno.land/r/demo/position CollectFee [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_tokenId 1} {fee0 0} {fee1 0} {poolPath gno.land/r/demo/gns:gno.land/r/demo/wugnot:500}]}

shouldEQ(t, tokenId, uint64(1))
shouldEQ(t, fee0, "0")
shouldEQ(t, fee1, "0")
}
Loading
Loading