From 7b82a43a72fdd79213650b52280d58274bde3c50 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Wed, 15 May 2024 22:40:25 +0900 Subject: [PATCH 1/2] GSW-1069 fix: one click staking for native ugnot coin (wrapping issue) --- .../_TEST_0_INIT_VARIABLE_AND_HELPER_test.gno | 11 ++ .../_TEST_staker_mint_and_stake_test.gn | 92 ----------- .../_TEST_staker_mint_and_stake_test.gno | 156 ++++++++++++++++++ staker/mint_stake.gno | 21 +++ staker/utils.gno | 10 ++ 5 files changed, 198 insertions(+), 92 deletions(-) delete mode 100644 staker/_TEST_/_TEST_staker_mint_and_stake_test.gn create mode 100644 staker/_TEST_/_TEST_staker_mint_and_stake_test.gno diff --git a/staker/_TEST_/_TEST_0_INIT_VARIABLE_AND_HELPER_test.gno b/staker/_TEST_/_TEST_0_INIT_VARIABLE_AND_HELPER_test.gno index 9a28b234..f0b3ee74 100644 --- a/staker/_TEST_/_TEST_0_INIT_VARIABLE_AND_HELPER_test.gno +++ b/staker/_TEST_/_TEST_0_INIT_VARIABLE_AND_HELPER_test.gno @@ -27,6 +27,17 @@ var ( ) /* HELPER */ +func ugnotBalanceOf(addr std.Address) uint64 { + testBanker := std.GetBanker(std.BankerTypeRealmIssue) + + coins := testBanker.GetCoins(addr) + if len(coins) == 0 { + return 0 + } + + return uint64(testBanker.GetCoins(addr)[0].Amount) +} + func shouldEQ(t *testing.T, got, expected interface{}) { if got != expected { t.Errorf("got %v, expected %v", got, expected) diff --git a/staker/_TEST_/_TEST_staker_mint_and_stake_test.gn b/staker/_TEST_/_TEST_staker_mint_and_stake_test.gn deleted file mode 100644 index ed19a6e0..00000000 --- a/staker/_TEST_/_TEST_staker_mint_and_stake_test.gn +++ /dev/null @@ -1,92 +0,0 @@ -package staker - -import ( - "std" - "testing" - - pl "gno.land/r/demo/pool" - pn "gno.land/r/demo/position" - - "gno.land/r/demo/bar" - "gno.land/r/demo/gnft" - "gno.land/r/demo/gns" - "gno.land/r/demo/qux" - - "gno.land/r/demo/gnoswap/consts" -) - -func init() { - // init pool tiers - // tier 1 - poolTiers["gno.land/r/demo/bar:gno.land/r/demo/qux:500"] = 1 // DEV - - // tier 2 - poolTiers["GNS/USDT_500"] = 2 - poolTiers["ATOM/GNS_500"] = 2 - - // tier 3 - poolTiers["ATOM/GNOT_500"] = 3 - poolTiers["ATOM/USDT_500"] = 3 - poolTiers["ATOM/WETH_500"] = 3 -} - -func TestPoolInitCreatePool(t *testing.T) { - std.TestSetPrevAddr(test1) - for i := 0; i < (5 * 1); i++ { - gns.Faucet() - } - gns.Approve(a2u(consts.POOL_ADDR), consts.POOL_CREATION_FEE) - std.TestSkipHeights(1) - - pl.CreatePool(barPath, quxPath, 500, "130621891405341611593710811006") // tick 10_000 ≈ x2.7 - std.TestSkipHeights(1) -} - -func TestMintAndStake(t *testing.T) { - std.TestSetPrevAddr(test1) - bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - qux.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - std.TestSkipHeights(2) - - lpTokenId, liquidity, amount0, amount1, poolPath := MintAndStake( - barPath, // token0 - quxPath, // token1 - fee500, // fee - int32(9000), // tickLower - int32(11000), // tickUpper - "1000", // amount0Desired - "1000", // amount1Desired - "1", // amount0Min - "1", // amount1Min - max_timeout, - ) - std.TestSkipHeights(1) -} - -func TestPositionCollectFee(t *testing.T) { - std.TestSetPrevAddr(test1) - pn.CollectFee(1) // lpTokenId - std.TestSkipHeights(1) -} - -func TestCollectReward(t *testing.T) { - // internal reward distribution - std.TestSetPrevAddr(consts.INTERNAL_REWARD_ACCOUNT) - gns.Approve(a2u(consts.STAKER_ADDR), consts.UINT64_MAX) - - std.TestSetPrevAddr(test1) - CollectReward(1) // lpTokenId - std.TestSkipHeights(1) -} - -func TestUnstakeToken(t *testing.T) { - ownerOfLp1 := gnft.OwnerOf(tid(1)) - shouldEQ(t, ownerOfLp1, a2u(consts.STAKER_ADDR)) - - std.TestSetPrevAddr(test1) - UnstakeToken(1) // lpTokenId - std.TestSkipHeights(1) - - ownerOfLp1 = gnft.OwnerOf(tid(1)) - shouldEQ(t, ownerOfLp1, a2u(test1)) -} diff --git a/staker/_TEST_/_TEST_staker_mint_and_stake_test.gno b/staker/_TEST_/_TEST_staker_mint_and_stake_test.gno new file mode 100644 index 00000000..fc384250 --- /dev/null +++ b/staker/_TEST_/_TEST_staker_mint_and_stake_test.gno @@ -0,0 +1,156 @@ +package staker + +import ( + "std" + "testing" + + pl "gno.land/r/demo/pool" + + "gno.land/r/demo/bar" + "gno.land/r/demo/gns" + "gno.land/r/demo/qux" + "gno.land/r/demo/wugnot" + + "gno.land/r/demo/gnoswap/consts" +) + +func init() { + // init pool tiers + // tier 1 + poolTiers["gno.land/r/demo/bar:gno.land/r/demo/qux:500"] = 1 // DEV + + // tier 2 + poolTiers["GNS/USDT_500"] = 2 + poolTiers["ATOM/GNS_500"] = 2 + + // tier 3 + poolTiers["ATOM/GNOT_500"] = 3 + poolTiers["ATOM/USDT_500"] = 3 + poolTiers["ATOM/WETH_500"] = 3 +} + +func TestPoolInitCreatePool(t *testing.T) { + std.TestSetPrevAddr(gsa) + + gns.Approve(a2u(consts.POOL_ADDR), consts.POOL_CREATION_FEE) + std.TestSkipHeights(1) + + pl.CreatePool(barPath, quxPath, 500, "130621891405341611593710811006") // tick 10_000 ≈ x2.7 + // --- event: {GNOSWAP gno.land/r/demo/pool CreatePool [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_poolPath gno.land/r/demo/bar:gno.land/r/demo/qux:500}]} + + pl.CreatePool(consts.GNOT, consts.GNS_PATH, 500, "130621891405341611593710811006") // tick 10_000 ≈ x2.7 + // --- 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}]} + + std.TestSkipHeights(1) +} + +func TestMintAndStakeGRC20Pair(t *testing.T) { + std.TestSetPrevAddr(gsa) + bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) + qux.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) + std.TestSkipHeights(2) + + lpTokenId, liquidity, amount0, amount1, poolPath := MintAndStake( + barPath, // token0 + quxPath, // token1 + fee500, // fee + int32(9000), // tickLower + int32(11000), // tickUpper + "1000", // amount0Desired + "1000", // amount1Desired + "1", // amount0Min + "1", // amount1Min + max_timeout, + ) + // event: {GNOSWAP gno.land/r/demo/position Mint [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm gno.land/r/demo/staker} {p_poolPath gno.land/r/demo/bar:gno.land/r/demo/qux:500} {p_tickLower 9000} {p_tickUpper 11000} {tokenId 1} {liquidity 12437} {amount0 368} {amount1 1000}]} + // event: {GNOSWAP gno.land/r/demo/staker StakeToken [{p_tokenId 1} {poolPath gno.land/r/demo/bar:gno.land/r/demo/qux:500} {amount0 368} {amount1 1000}]} + shouldEQ(t, lpTokenId, uint64(1)) + + std.TestSkipHeights(1) +} + +func TestMintAndStakeNative(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) + std.TestSkipHeights(2) + + // prepare 100005ugnot (5 for refund test) + testBanker := std.GetBanker(std.BankerTypeRealmIssue) + testBanker.IssueCoin(gsa, "ugnot", 100005) + + // simulate transfer & decrase + gsaNativeBalance := ugnotBalanceOf(gsa) + shouldEQ(t, gsaNativeBalance, 100005) + + std.TestSetOrigSend(std.Coins{{"ugnot", 100005}}, nil) + testBanker.RemoveCoin(std.GetOrigCaller(), "ugnot", -100005) + + gsaNativeBalance = ugnotBalanceOf(gsa) + shouldEQ(t, gsaNativeBalance, 0) + + gsaOldWugnotBalance := wugnot.BalanceOf(a2u(gsa)) + shouldEQ(t, gsaOldWugnotBalance, 0) + + lpTokenId, liquidity, amount0, amount1, poolPath := MintAndStake( + consts.GNOT, // token0 + consts.GNS_PATH, // token1 + fee500, // fee + int32(9000), // tickLower + int32(11000), // tickUpper + "100000", // amount0Desired + "100000", // amount1Desired + "1", // amount0Min + "1", // amount1Min + max_timeout, + ) + // event: {GNOSWAP gno.land/r/demo/position Mint [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm gno.land/r/demo/staker} {p_poolPath gno.land/r/demo/gns:gno.land/r/demo/wugnot:500} {p_tickLower -11000} {p_tickUpper -9000} {tokenId 2} {liquidity 1243732} {amount0 100000} {amount1 36790}]} + // event: {GNOSWAP gno.land/r/demo/staker StakeToken [{p_tokenId 2} {poolPath gno.land/r/demo/gns:gno.land/r/demo/wugnot:500} {amount0 100000} {amount1 36790}]} + + shouldEQ(t, lpTokenId, uint64(2)) + std.TestSkipHeights(1) + + // SPEND ALL WUGNOT + newOldWugnotBalance := wugnot.BalanceOf(a2u(gsa)) + shouldEQ(t, gsaOldWugnotBalance, 0) + + gsaNativeBalance = ugnotBalanceOf(gsa) + shouldEQ(t, gsaNativeBalance, 63215) + // 1. 100005 ugnot sent + // 2. 100005 ugnot wrapped to wugnot + // 3. 36790 wugnot spent to mint (amount1) + // 4. refund 100005 - 36790 = 63215 + +} + +/* +func TestPositionCollectFee(t *testing.T) { + std.TestSetPrevAddr(gsa) + pn.CollectFee(1) // lpTokenId + std.TestSkipHeights(1) +} + +func TestCollectReward(t *testing.T) { + // internal reward distribution + std.TestSetPrevAddr(consts.INTERNAL_REWARD_ACCOUNT) + gns.Approve(a2u(consts.STAKER_ADDR), consts.UINT64_MAX) + + std.TestSetPrevAddr(gsa) + CollectReward(1) // lpTokenId + std.TestSkipHeights(1) +} + +func TestUnstakeToken(t *testing.T) { + ownerOfLp1 := gnft.OwnerOf(tid(1)) + shouldEQ(t, ownerOfLp1, a2u(consts.STAKER_ADDR)) + + std.TestSetPrevAddr(gsa) + UnstakeToken(1) // lpTokenId + std.TestSkipHeights(1) + + ownerOfLp1 = gnft.OwnerOf(tid(1)) + shouldEQ(t, ownerOfLp1, a2u(gsa)) +} + +*/ diff --git a/staker/mint_stake.gno b/staker/mint_stake.gno index 7645b4c1..bbbd5169 100644 --- a/staker/mint_stake.gno +++ b/staker/mint_stake.gno @@ -1,8 +1,12 @@ package staker import ( + "std" + pn "gno.land/r/demo/position" + "gno.land/p/demo/ufmt" + "gno.land/r/demo/gnoswap/consts" ) @@ -20,6 +24,23 @@ func MintAndStake( amount1Min string, // *u256.Uint deadline int64, ) (uint64, string, string, string, string) { // tokenId, liquidity, amount0, amount1, poolPath ( *u256.Uint x3) + + // if one click native + if token0 == consts.GNOT || token1 == consts.GNOT { + // check sent ugnot + sent := std.GetOrigSend() + ugnotSent := uint64(sent.AmountOf("ugnot")) + + // not enough ugnot sent + if ugnotSent < consts.UGNOT_MINT_DEPOSIT_TO_WRAP { + panic(ufmt.Sprintf("[STAKER] mint_stake.gno__MintAndStake() || too less(%d) ugnot sent (minimum:%d)", ugnotSent, consts.UGNOT_MINT_DEPOSIT_TO_WRAP)) + } + + // send it over to position to wrap + banker := std.GetBanker(std.BankerTypeRealmSend) + banker.SendCoins(consts.STAKER_ADDR, consts.POSITION_ADDR, std.Coins{{"ugnot", int64(ugnotSent)}}) + } + tokenId, liquidity, amount0, amount1 := pn.Mint( token0, token1, diff --git a/staker/utils.gno b/staker/utils.gno index ce01a082..2a305fb5 100644 --- a/staker/utils.gno +++ b/staker/utils.gno @@ -81,6 +81,16 @@ func uint64ToStr(i uint64) string { return strconv.FormatInt(int64(i), 10) } +func strToUint64(s string) uint64 { + i, err := strconv.Atoi(s) + + if err != nil { + panic(ufmt.Sprintf("[STAKER] utils.gno__strToUint64() || failed to convert string(%s) to uint64", s)) + } + + return uint64(i) +} + func boolToStr(b bool) string { if b { return "true" From 1f7bf37cb2fae09a92dc5fa0a19e137acf827f29 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Thu, 16 May 2024 16:19:46 +0900 Subject: [PATCH 2/2] fix: unclaimed fee calculation --- _deploy/r/demo/gnoswap/consts/consts.gno | 3 + position/_RPC_api.gno | 81 ++++--- ...tion_ZZ_increase_decrease_native_test.gnoA | 227 ++++++++++++++++++ ...n_test.gnoA => _TEST_position_ZZ_test.gno} | 107 ++++++++- ...st_two_position_used_single_swap_test.gnoA | 1 + position/position.gno | 27 ++- .../_TEST_/_TEST_0_INIT_VARS_HELPERS_test.gno | 11 + ..._route_1route_1hop_native_in_out_test.gno} | 114 ++++++--- 8 files changed, 500 insertions(+), 71 deletions(-) create mode 100644 position/_TEST_/_TEST_position_ZZ_increase_decrease_native_test.gnoA rename position/_TEST_/{_TEST_position_test.gnoA => _TEST_position_ZZ_test.gno} (50%) rename router/_TEST_/{_TEST_router_swap_route_1route_1hop_native_in_out_test.gnoA => _TEST_router_swap_route_1route_1hop_native_in_out_test.gno} (64%) diff --git a/_deploy/r/demo/gnoswap/consts/consts.gno b/_deploy/r/demo/gnoswap/consts/consts.gno index e60aad64..61e7ebb2 100644 --- a/_deploy/r/demo/gnoswap/consts/consts.gno +++ b/_deploy/r/demo/gnoswap/consts/consts.gno @@ -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 diff --git a/position/_RPC_api.gno b/position/_RPC_api.gno index d257d10c..e9c1201f 100644 --- a/position/_RPC_api.gno +++ b/position/_RPC_api.gno @@ -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 { @@ -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) } @@ -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 @@ -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 @@ -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 } diff --git a/position/_TEST_/_TEST_position_ZZ_increase_decrease_native_test.gnoA b/position/_TEST_/_TEST_position_ZZ_increase_decrease_native_test.gnoA new file mode 100644 index 00000000..c9e3749e --- /dev/null +++ b/position/_TEST_/_TEST_position_ZZ_increase_decrease_native_test.gnoA @@ -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") +} diff --git a/position/_TEST_/_TEST_position_test.gnoA b/position/_TEST_/_TEST_position_ZZ_test.gno similarity index 50% rename from position/_TEST_/_TEST_position_test.gnoA rename to position/_TEST_/_TEST_position_ZZ_test.gno index b4e9a024..4263bd2a 100644 --- a/position/_TEST_/_TEST_position_test.gnoA +++ b/position/_TEST_/_TEST_position_ZZ_test.gno @@ -13,6 +13,9 @@ import ( "gno.land/r/demo/gnft" "gno.land/r/demo/gns" + + // u256 "gno.land/p/demo/gnoswap/uint256" + i256 "gno.land/p/demo/gnoswap/int256" ) func TestPoolInitCreatePool(t *testing.T) { @@ -20,7 +23,7 @@ func TestPoolInitCreatePool(t *testing.T) { gns.Approve(a2u(consts.POOL_ADDR), consts.POOL_CREATION_FEE) pl.CreatePool(barPath, fooPath, fee500, "130621891405341611593710811006") // tick = 10000 - // event: {GNOSWAP gno.land/r/demo/pool CreatePool [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_poolPath gno.land/r/demo/bar:gno.land/r/demo/foo:500}]} + // --- event: {GNOSWAP gno.land/r/demo/pool CreatePool [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_poolPath gno.land/r/demo/bar:gno.land/r/demo/foo:500}]} } @@ -42,7 +45,7 @@ func TestMintPosition01InRange(t *testing.T) { max_timeout, gsa.String(), ) - // event: {GNOSWAP gno.land/r/demo/position Mint [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_poolPath gno.land/r/demo/bar:gno.land/r/demo/foo:500} {p_tickLower 8000} {p_tickUpper 12000} {tokenId 1} {liquidity 318704392} {amount0 18394892} {amount1 50000000}]} + // --- event: {GNOSWAP gno.land/r/demo/position Mint [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_poolPath gno.land/r/demo/bar:gno.land/r/demo/foo:500} {p_tickLower 8000} {p_tickUpper 12000} {tokenId 1} {liquidity 318704392} {amount0 18394892} {amount1 50000000}]} shouldEQ(t, tokenId, 1) shouldEQ(t, getNextId(), 2) @@ -113,7 +116,14 @@ func TestCollectFeeBeforeSwap(t *testing.T) { shouldEQ(t, poolPath, "gno.land/r/demo/bar:gno.land/r/demo/foo:500") } -func TestSwap(t *testing.T) { +func TestUnclaimedFee0(t *testing.T) { + amount0, amount1 := unclaimedFee(1) + + shouldEQ(t, amount0.ToString(), "0") + shouldEQ(t, amount1.ToString(), "0") +} + +func TestSwap1(t *testing.T) { std.TestSetPrevAddr(gsa) bar.Approve(a2u(consts.POOL_ADDR), 1234567) @@ -129,24 +139,89 @@ func TestSwap(t *testing.T) { consts.MIN_PRICE, gsa.String(), ) - // event: {GNOSWAP gno.land/r/demo/pool Swap [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm gno.land/r/demo/router} {p_poolPath gno.land/r/demo/bar:gno.land/r/demo/foo:500} {p_zeroForOne true} {p_amountSpecified 1234567} {p_sqrtPriceLimitX96 4295128740} {p_payer g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {p_recipient g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {amount0 1234567} {amount1 -3332779} {protocol_fee0 0} {protocol_fee1 0}]} + // --- event: {GNOSWAP gno.land/r/demo/pool Swap [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm gno.land/r/demo/router} {p_poolPath gno.land/r/demo/bar:gno.land/r/demo/foo:500} {p_zeroForOne true} {p_amountSpecified 1234567} {p_sqrtPriceLimitX96 4295128740} {p_payer g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {p_recipient g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {amount0 1234567} {amount1 -3332779} {protocol_fee0 0} {protocol_fee1 0}]} shouldEQ(t, amount0, "1234567") shouldEQ(t, amount1, "-3332779") } +func TestUnclaimedFee1(t *testing.T) { + amount0, amount1 := unclaimedFee(1) + + shouldEQ(t, amount0.ToString(), "617") + shouldEQ(t, amount1.ToString(), "0") +} + +func TestSwap2(t *testing.T) { + std.TestSetPrevAddr(gsa) + bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) + + std.TestSetPrevRealm(consts.ROUTER_PATH) + std.TestSetOrigCaller(gsa) + amount0, amount1 := pl.Swap( + barPath, + fooPath, + fee500, + gsa.String(), + true, + "20000000", // + consts.MIN_PRICE, + gsa.String(), + ) + // --- event: {GNOSWAP gno.land/r/demo/pool Swap [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm gno.land/r/demo/router} {p_poolPath gno.land/r/demo/bar:gno.land/r/demo/foo:500} {p_zeroForOne true} {p_amountSpecified 20000000} {p_sqrtPriceLimitX96 4295128740} {p_payer g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {p_recipient g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {amount0 20000000} {amount1 -48647018} {protocol_fee0 0} {protocol_fee1 0}]} + + shouldEQ(t, amount0, "20000000") + shouldEQ(t, amount1, "-48647018") +} + +func TestUnclaimedFee2(t *testing.T) { + amount0, amount1 := unclaimedFee(1) + + shouldEQ(t, amount0.ToString(), "10170") + shouldEQ(t, amount1.ToString(), "0") +} + +func TestSwap3(t *testing.T) { + std.TestSetPrevAddr(gsa) + foo.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) + + std.TestSetPrevRealm(consts.ROUTER_PATH) + std.TestSetOrigCaller(gsa) + amount0, amount1 := pl.Swap( + fooPath, + barPath, + fee500, + gsa.String(), + false, + "20000000", // + consts.MAX_PRICE, + gsa.String(), + ) + // event: {GNOSWAP gno.land/r/demo/pool Swap [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm gno.land/r/demo/router} {p_poolPath gno.land/r/demo/bar:gno.land/r/demo/foo:500} {p_zeroForOne true} {p_amountSpecified 1234567} {p_sqrtPriceLimitX96 4295128740} {p_payer g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {p_recipient g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {amount0 1234567} {amount1 -3332779} {protocol_fee0 0} {protocol_fee1 0}]} + + shouldEQ(t, amount0, "-8692001") + shouldEQ(t, amount1, "20000000") +} + +func TestUnclaimedFee3(t *testing.T) { + amount0, amount1 := unclaimedFee(1) + + shouldEQ(t, amount0.ToString(), "10170") + shouldEQ(t, amount1.ToString(), "9009") +} + func TestCollectFeeAfterSwap(t *testing.T) { std.TestSetPrevAddr(gsa) foo.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) // COLLECT_FEE bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) // COLLECT_FEE 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/bar} {p_token1Path gno.land/r/demo/foo} {fee0Amount 6} {fee1Amount 0}]} - // event: {GNOSWAP gno.land/r/demo/position CollectFee [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_tokenId 1} {fee0 617} {fee1 0} {poolPath gno.land/r/demo/bar:gno.land/r/demo/foo:500}]} + // --- 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/bar} {p_token1Path gno.land/r/demo/foo} {fee0Amount 101} {fee1Amount 90}]} + // --- event: {GNOSWAP gno.land/r/demo/position CollectFee [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_tokenId 1} {fee0 10069} {fee1 8919} {poolPath gno.land/r/demo/bar:gno.land/r/demo/foo:500}]} shouldEQ(t, tokenId, uint64(1)) - shouldNEQ(t, fee0, "0") // this is input token - shouldEQ(t, fee1, "0") // this it output token + shouldEQ(t, fee0, "10069") // this is input token + shouldEQ(t, fee1, "8919") // this it output token shouldEQ(t, poolPath, "gno.land/r/demo/bar:gno.land/r/demo/foo:500") } @@ -157,9 +232,9 @@ func TestDecreaseLiquidityUpperPosition(t *testing.T) { shouldEQ(t, ownerOfPosition, std.GetOrigCaller()) tokenId, liquidity, fee0, fee1, amount0, amount1, poolPath := DecreaseLiquidity(uint64(3), 100, "0", "0", max_timeout, false) - // event: {GNOSWAP gno.land/r/demo/pool HandleWithdrawalFee [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm gno.land/r/demo/position} {p_tokenId 3} {p_token0Path gno.land/r/demo/bar} {p_token1Path gno.land/r/demo/foo} {fee0Amount 0} {fee1Amount 0}]} - // event: {GNOSWAP gno.land/r/demo/position CollectFee [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_tokenId 3} {fee0 0} {fee1 0} {poolPath gno.land/r/demo/bar:gno.land/r/demo/foo:500}]} - // event: {GNOSWAP gno.land/r/demo/position DecreaseLiquidity [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_tokenId 3} {p_liquidityRatio 100} {poolPath gno.land/r/demo/bar:gno.land/r/demo/foo:500} {liquidity 957388253} {fee0 0} {fee1 0} {amount0 49999999} {amount1 0}]} + // --- event: {GNOSWAP gno.land/r/demo/pool HandleWithdrawalFee [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm gno.land/r/demo/position} {p_tokenId 3} {p_token0Path gno.land/r/demo/bar} {p_token1Path gno.land/r/demo/foo} {fee0Amount 0} {fee1Amount 0}]} + // --- event: {GNOSWAP gno.land/r/demo/position CollectFee [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_tokenId 3} {fee0 0} {fee1 0} {poolPath gno.land/r/demo/bar:gno.land/r/demo/foo:500}]} + // --- event: {GNOSWAP gno.land/r/demo/position DecreaseLiquidity [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_tokenId 3} {p_liquidityRatio 100} {poolPath gno.land/r/demo/bar:gno.land/r/demo/foo:500} {liquidity 957388253} {fee0 0} {fee1 0} {amount0 49999999} {amount1 0}]} shouldEQ(t, tokenId, uint64(3)) shouldEQ(t, amount0, "49999999") @@ -168,3 +243,13 @@ func TestDecreaseLiquidityUpperPosition(t *testing.T) { ownerOfPosition = gnft.OwnerOf(tid(3)) shouldEQ(t, ownerOfPosition, std.GetOrigCaller()) } + +func TestSubIn256(t *testing.T) { + a := i256.Zero() + b := i256.One() + + c := subIn256(a, b) + if c.ToString() != "115792089237316195423570985008687907853269984665640564039457584007913129639934" { + t.Errorf("subIn256 error") + } +} diff --git a/position/_TEST_/_TEST_position_test_two_position_used_single_swap_test.gnoA b/position/_TEST_/_TEST_position_test_two_position_used_single_swap_test.gnoA index 5ecbc7a8..54285447 100644 --- a/position/_TEST_/_TEST_position_test_two_position_used_single_swap_test.gnoA +++ b/position/_TEST_/_TEST_position_test_two_position_used_single_swap_test.gnoA @@ -144,3 +144,4 @@ func TestCollectFeeAfterSwapPos2(t *testing.T) { shouldEQ(t, fee1, "0") shouldEQ(t, poolPath, "gno.land/r/demo/bar:gno.land/r/demo/foo:500") } + diff --git a/position/position.gno b/position/position.gno index e1040845..edcba841 100644 --- a/position/position.gno +++ b/position/position.gno @@ -217,8 +217,31 @@ func IncreaseLiquidity( deadline: deadline, } + // wrap if target pool has wugnot + position := positions[tokenId] + pToken0, pToken1, _ := poolKeyDivide(position.poolKey) + + isToken0Wugnot := pToken0 == consts.WRAPPED_WUGNOT + isToken1Wugnot := pToken1 == consts.WRAPPED_WUGNOT + + userOldWugnotBalance := wugnot.BalanceOf(a2u(std.GetOrigCaller())) // before wrap, user's origin wugnot balance + if isToken0Wugnot || isToken1Wugnot { + sent := std.GetOrigSend() + ugnotSent := uint64(sent.AmountOf("ugnot")) + wrap(ugnotSent) + } + + // INCREASE tokneId, liquidity, amount0, amount1, poolPath := increaseLiquidity(increaseLiquidityParams) + // unwrap left + if isToken0Wugnot || isToken1Wugnot { + userNewWugnotBalance := wugnot.BalanceOf(a2u(std.GetOrigCaller())) + + leftOver := userNewWugnotBalance - userOldWugnotBalance + unwrap(leftOver) + } + std.Emit( "GNOSWAP", "m_origCaller", origCaller(), @@ -542,8 +565,8 @@ func CollectFee(tokenId uint64) (uint64, string, string, string) { // tokenId, t "m_origCaller", origCaller(), "m_prevRealm", prevRealm(), "p_tokenId", uint64ToStr(tokenId), - "fee0", tokensOwed0.ToString(), - "fee1", tokensOwed1.ToString(), + "fee0", withoutFee0, + "fee1", withoutFee1, "poolPath", position.poolKey, ) diff --git a/router/_TEST_/_TEST_0_INIT_VARS_HELPERS_test.gno b/router/_TEST_/_TEST_0_INIT_VARS_HELPERS_test.gno index a7402d4c..ee72a056 100644 --- a/router/_TEST_/_TEST_0_INIT_VARS_HELPERS_test.gno +++ b/router/_TEST_/_TEST_0_INIT_VARS_HELPERS_test.gno @@ -60,3 +60,14 @@ func shouldPanic(t *testing.T, f func()) { }() f() } + +func ugnotBalanceOf(addr std.Address) uint64 { + testBanker := std.GetBanker(std.BankerTypeRealmIssue) + + coins := testBanker.GetCoins(addr) + if len(coins) == 0 { + return 0 + } + + return uint64(testBanker.GetCoins(addr)[0].Amount) +} diff --git a/router/_TEST_/_TEST_router_swap_route_1route_1hop_native_in_out_test.gnoA b/router/_TEST_/_TEST_router_swap_route_1route_1hop_native_in_out_test.gno similarity index 64% rename from router/_TEST_/_TEST_router_swap_route_1route_1hop_native_in_out_test.gnoA rename to router/_TEST_/_TEST_router_swap_route_1route_1hop_native_in_out_test.gno index 43b998af..a5407e88 100644 --- a/router/_TEST_/_TEST_router_swap_route_1route_1hop_native_in_out_test.gnoA +++ b/router/_TEST_/_TEST_router_swap_route_1route_1hop_native_in_out_test.gno @@ -18,6 +18,14 @@ import ( "gno.land/r/demo/wugnot" ) +/* DEBUG +func TestEnables(t *testing.T) { + // testBanker := std.GetBanker(std.BankerTypeRealmIssue) + // testBanker.IssueCoin(consts.POSITION_ADDR, "ugnot", 1) + // testBanker.IssueCoin(consts.WUGNOT_ADDR, "ugnot", 1) +} +*/ + func TestCreatePool(t *testing.T) { std.TestSetPrevAddr(gsa) @@ -29,7 +37,7 @@ func TestCreatePool(t *testing.T) { pl.CreatePool(bazPath, quxPath, fee500, "130621891405341611593710811006") // tick = 10_000, ratio = 2.71814592682522526700950038502924144268035888671875 // event: {GNOSWAP gno.land/r/demo/pool CreatePool [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_poolPath gno.land/r/demo/baz:gno.land/r/demo/qux:500}]} - pl.CreatePool(quxPath, consts.WRAPPED_WUGNOT, fee500, "130621891405341611593710811006") // tick = 10_000, ratio = 2.71814592682522526700950038502924144268035888671875 + pl.CreatePool(quxPath, consts.GNOT, fee500, "130621891405341611593710811006") // tick = 10_000, ratio = 2.71814592682522526700950038502924144268035888671875 // event: {GNOSWAP gno.land/r/demo/pool CreatePool [{m_origCaller g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq} {m_prevRealm } {p_poolPath gno.land/r/demo/qux:gno.land/r/demo/wugnot:500}]} // 1 bar ≈ 19.683 gnot @@ -81,16 +89,16 @@ func TestPositionMintQuxGnot(t *testing.T) { std.TestIssueCoins(gsa, std.Coins{{"ugnot", 1000009}}) std.TestSetOrigSend(std.Coins{{"ugnot", 1000009}}, nil) testBanker := std.GetBanker(std.BankerTypeRealmIssue) - testBanker.RemoveCoin(std.GetOrigCaller(), "ugnot", 1000009) + testBanker.RemoveCoin(std.GetOrigCaller(), "ugnot", -1000009) - // Deposit(wrap) - std.TestSetPrevAddr(gsa) - wugnot.Deposit() + // MOCK POSITION TO HAVE UGNOT + testBanker.IssueCoin(consts.POSITION_ADDR, "ugnot", 1000009) qux.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 unwrap - tokenId, liquidity, amount0, amount1 := pn.Mint(quxPath, consts.WRAPPED_WUGNOT, fee500, int32(9000), int32(11000), "100000", "100000", "0", "0", max_timeout, gsa.String()) + tokenId, liquidity, amount0, amount1 := pn.Mint(quxPath, consts.GNOT, fee500, int32(9000), int32(11000), "100000", "100000", "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/qux:gno.land/r/demo/wugnot:500} {p_tickLower 9000} {p_tickUpper 11000} {tokenId 3} {liquidity 1243732} {amount0 36790} {amount1 100000}]} shouldEQ(t, tokenId, uint64(3)) @@ -98,58 +106,104 @@ func TestPositionMintQuxGnot(t *testing.T) { shouldEQ(t, amount1, "100000") } +// DRY BUY NATIVE func TestDrySwapRouteBarGnotExactIn(t *testing.T) { std.TestSetOrigCaller(gsa) dryResult := DrySwapRoute( - barPath, // inputToken - consts.WRAPPED_WUGNOT, // outputToken - "1000", // amountSpecified - "EXACT_IN", // swapType + barPath, // inputToken + consts.GNOT, // outputToken + "1000", // amountSpecified + "EXACT_IN", // swapType "gno.land/r/demo/bar:gno.land/r/demo/baz:500*POOL*gno.land/r/demo/baz:gno.land/r/demo/qux:500*POOL*gno.land/r/demo/qux:gno.land/r/demo/wugnot:500", // strRouteArr "100", // quoteArr ) shouldEQ(t, dryResult, "19740") } -func TestDrySwapRouteBarGnotExactOut(t *testing.T) { - std.TestSetOrigCaller(gsa) +// BUY NATIVE +func TestSwapRouteBarGnotExactIn(t *testing.T) { + std.TestSetPrevAddr(gsa) - dryResult := DrySwapRoute( - barPath, // inputToken - consts.WRAPPED_WUGNOT, // outputToken - "20000", // amountSpecified - "EXACT_OUT", // swapType + bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) + wugnot.Approve(a2u(consts.ROUTER_ADDR), consts.UINT64_MAX) // for output + wugnot.Approve(a2u(consts.ROUTER_ADDR), consts.UINT64_MAX) // for unwrap + + oldWugnot := wugnot.BalanceOf(a2u(gsa)) + shouldEQ(t, oldWugnot, 0) + + oldUgnot := ugnotBalanceOf(gsa) + shouldEQ(t, oldUgnot, 900009) + + std.TestSetOrigCaller(gsa) + amountIn, amountOut := SwapRoute( + barPath, // inputToken + consts.GNOT, // outputToken + "1000", // amountSpecified + "EXACT_IN", // swapType "gno.land/r/demo/bar:gno.land/r/demo/baz:500*POOL*gno.land/r/demo/baz:gno.land/r/demo/qux:500*POOL*gno.land/r/demo/qux:gno.land/r/demo/wugnot:500", // strRouteArr "100", // quoteArr + "0", // tokenAmountLimit ) - shouldEQ(t, dryResult, "1014") + shouldEQ(t, amountIn, "1000") + shouldEQ(t, amountOut, "-19711") + + newWugnot := wugnot.BalanceOf(a2u(gsa)) + shouldEQ(t, newWugnot, 0) + + newUgnot := ugnotBalanceOf(gsa) + shouldEQ(t, newUgnot, 919720) // 900009 + 19711 } +// DRY SELL NATIVE func TestDrySwapRouteGnotBarExactIn(t *testing.T) { std.TestSetOrigCaller(gsa) dryResult := DrySwapRoute( - consts.WRAPPED_WUGNOT, // intputToken - barPath, // outputToken - "5000", // amountSpecified - "EXACT_IN", // swapType + consts.GNOT, // intputToken + barPath, // outputToken + "5000", // amountSpecified + "EXACT_IN", // swapType "gno.land/r/demo/wugnot:gno.land/r/demo/qux:500*POOL*gno.land/r/demo/qux:gno.land/r/demo/baz:500*POOL*gno.land/r/demo/baz:gno.land/r/demo/bar:500", // strRouteArr "100", // quoteArr ) - shouldEQ(t, dryResult, "247") + shouldEQ(t, dryResult, "254") } -func TestDrySwapRouteGnotBarExactOut(t *testing.T) { +// SELL NATIVE +func TestSwapRouteGnotBarExactIn(t *testing.T) { std.TestSetOrigCaller(gsa) - dryResult := DrySwapRoute( - consts.WRAPPED_WUGNOT, // intputToken - barPath, // outputToken - "100", // amountSpecified - "EXACT_OUT", // swapType + wugnot.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) // for input + bar.Approve(a2u(consts.ROUTER_ADDR), consts.UINT64_MAX) // for output + + oldWugnot := wugnot.BalanceOf(a2u(gsa)) + shouldEQ(t, oldWugnot, 0) + + oldUgnot := ugnotBalanceOf(gsa) + shouldEQ(t, oldUgnot, 919720) + + // send + std.TestSetOrigSend(std.Coins{{"ugnot", 5000}}, nil) + testBanker := std.GetBanker(std.BankerTypeRealmIssue) + testBanker.RemoveCoin(std.GetOrigCaller(), "ugnot", -5000) + + amountIn, amountOut := SwapRoute( + consts.GNOT, // intputToken + barPath, // outputToken + "5000", // amountSpecified + "EXACT_IN", // swapType "gno.land/r/demo/wugnot:gno.land/r/demo/qux:500*POOL*gno.land/r/demo/qux:gno.land/r/demo/baz:500*POOL*gno.land/r/demo/baz:gno.land/r/demo/bar:500", // strRouteArr "100", // quoteArr + "0", ) - shouldEQ(t, dryResult, "2027") + shouldEQ(t, amountIn, "5000") + shouldEQ(t, amountOut, "-254") + + newWugnot := wugnot.BalanceOf(a2u(gsa)) + shouldEQ(t, newWugnot, 0) + + newUgnot := ugnotBalanceOf(gsa) + shouldEQ(t, newUgnot, 915720) + }