diff --git a/position/_helper_test.gno b/position/_helper_test.gno index fdcaf2828..30ccacef0 100644 --- a/position/_helper_test.gno +++ b/position/_helper_test.gno @@ -518,6 +518,13 @@ func MintPositionAll(t *testing.T, caller std.Address) { } +func CreatePoolWithoutFee(t *testing.T) { + std.TestSetRealm(adminRealm) + // set pool create fee to 0 for testing + pl.SetPoolCreationFeeByAdmin(0) + CreatePool(t, barPath, fooPath, fee500, common.TickMathGetSqrtRatioAtTick(0).ToString(), users.Resolve(admin)) +} + func MakeMintPositionWithoutFee(t *testing.T) (uint64, string, string, string) { t.Helper() diff --git a/position/liquidity_management_test.gno b/position/liquidity_management_test.gno index d78debd57..5662fbd61 100644 --- a/position/liquidity_management_test.gno +++ b/position/liquidity_management_test.gno @@ -108,8 +108,3 @@ func TestAddLiquidity(t *testing.T) { }) } } - -func TestSplitOf(t *testing.T) { - // TODO: - -} diff --git a/position/native_token_test.gno b/position/native_token_test.gno index 1a1e935ca..dfface380 100644 --- a/position/native_token_test.gno +++ b/position/native_token_test.gno @@ -289,7 +289,45 @@ func TestIsNative(t *testing.T) { } func TestIsWrappedToken(t *testing.T) { - // TODO: + tests := []struct { + name string + tokenPath string + expected bool + }{ + { + name: "Success - Token is Wrapped WUGNOT", + tokenPath: consts.WRAPPED_WUGNOT, + expected: true, + }, + { + name: "Fail - Token is not Wrapped WUGNOT", + tokenPath: "gno.land/r/demo/ugnot", + expected: false, + }, + { + name: "Fail - Empty tokenPath", + tokenPath: "", + expected: false, + }, + { + name: "Fail - Similar but Different Token Path", + tokenPath: "gno.land/r/demo/Wugnot", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isWrappedToken(tt.tokenPath) + if result != tt.expected { + t.Errorf( + "expected %s but got %s", + strconv.FormatBool(tt.expected), + strconv.FormatBool(result), + ) + } + }) + } } func TestSafeWrapNativeToken(t *testing.T) { diff --git a/position/position.gno b/position/position.gno index 314eccd87..0b054e5c8 100644 --- a/position/position.gno +++ b/position/position.gno @@ -216,7 +216,6 @@ func Mint( mintParams := newMintParams(processedInput, mintInput) tokenId, liquidity, amount0, amount1 := mint(mintParams) - if processedInput.tokenPair.token0IsNative && processedInput.tokenPair.wrappedAmount > amount0.Uint64() { // unwrap leftover wugnot err = unwrap(processedInput.tokenPair.wrappedAmount-amount0.Uint64(), caller) @@ -231,11 +230,9 @@ func Mint( panic(newErrorWithDetail(errWrapUnwrap, err.Error())) } } - poolSqrtPriceX96 := pl.PoolGetSlot0SqrtPriceX96(processedInput.poolPath) prevAddr, prevPkgPath := getPrevAsString() - std.Emit( "Mint", "prevAddr", prevAddr, @@ -1091,24 +1088,6 @@ func Reposition( position.burned = false mustUpdatePosition(tokenId, position) - // // WIP - // pl.Burn( - // token0, - // token1, - // fee, - // tickLower, - // tickUpper, - // ZERO_LIQUIDITY_FOR_FEE_COLLECTION, - // ) - // currentFeeGrowth, err = getCurrentFeeGrowth(position, token0, token1, fee) - // if err != nil { - // panic(newErrorWithDetail(err, "failed to get current fee growth")) - // } - // tokensOwed0, tokensOwed1 := calculateFees(position, currentFeeGrowth) - // position.feeGrowthInside0LastX128 = new(u256.Uint).Set(currentFeeGrowth.feeGrowthInside0LastX128) - // position.feeGrowthInside1LastX128 = new(u256.Uint).Set(currentFeeGrowth.feeGrowthInside1LastX128) - // mustUpdatePosition(tokenId, position) - poolSqrtPriceX96 := pl.PoolGetSlot0SqrtPriceX96(position.poolKey) prevAddr, prevPkgPath := getPrevAsString() diff --git a/position/position_test.gno b/position/position_test.gno index 3a6e2666e..5ccce3279 100644 --- a/position/position_test.gno +++ b/position/position_test.gno @@ -4,6 +4,7 @@ import ( "std" "strconv" "testing" + "time" "gno.land/p/demo/testutils" "gno.land/p/demo/uassert" @@ -11,6 +12,7 @@ import ( "gno.land/r/demo/users" "gno.land/r/gnoswap/v1/consts" + "gno.land/r/gnoswap/v1/gnft" ) var ( @@ -38,24 +40,134 @@ func IsRegistered(t *testing.T, tokenPath string) error { return errInvalidTokenPath } +func newDummyPosition(tokenId uint64) Position { + return Position{ + nonce: u256.NewUint(tokenId), + operator: "user1", + poolKey: "poolKey1", + tickLower: -500, + tickUpper: 500, + liquidity: u256.NewUint(1000), + feeGrowthInside0LastX128: u256.NewUint(10), + feeGrowthInside1LastX128: u256.NewUint(20), + tokensOwed0: u256.NewUint(30), + tokensOwed1: u256.NewUint(40), + burned: false, + } +} + func TestMustGetPosition(t *testing.T) { - t.Skip("TODO: Implement") + tokenId := uint64(1) + position := newDummyPosition(tokenId) + setPosition(tokenId, position) + + t.Run("Success - Get existing position", func(t *testing.T) { + result := MustGetPosition(tokenId) + uassert.Equal(t, position.nonce.ToString(), result.nonce.ToString()) + uassert.Equal(t, position.operator, result.operator) + uassert.Equal(t, position.poolKey, result.poolKey) + uassert.Equal(t, position.tickLower, result.tickLower) + uassert.Equal(t, position.tickUpper, result.tickUpper) + uassert.Equal(t, position.liquidity.ToString(), result.liquidity.ToString()) + uassert.Equal(t, position.feeGrowthInside0LastX128.ToString(), result.feeGrowthInside0LastX128.ToString()) + uassert.Equal(t, position.feeGrowthInside1LastX128.ToString(), result.feeGrowthInside1LastX128.ToString()) + uassert.Equal(t, position.tokensOwed0.ToString(), result.tokensOwed0.ToString()) + uassert.Equal(t, position.tokensOwed1.ToString(), result.tokensOwed1.ToString()) + uassert.Equal(t, position.burned, result.burned) + }) + + t.Run("Fail - Non-existent position", func(t *testing.T) { + uassert.PanicsWithMessage(t, + "[GNOSWAP-POSITION-013] position does not exist || position with tokenId(999) doesn't exist", + func() { + MustGetPosition(999) + }) + }) } func TestGetPosition(t *testing.T) { - t.Skip("TODO: Implement") + tokenId := uint64(1) + position := newDummyPosition(tokenId) + t.Run("Success - Get existing position", func(t *testing.T) { + result := MustGetPosition(tokenId) + uassert.Equal(t, position.nonce.ToString(), result.nonce.ToString()) + uassert.Equal(t, position.operator, result.operator) + uassert.Equal(t, position.poolKey, result.poolKey) + uassert.Equal(t, position.tickLower, result.tickLower) + uassert.Equal(t, position.tickUpper, result.tickUpper) + uassert.Equal(t, position.liquidity.ToString(), result.liquidity.ToString()) + uassert.Equal(t, position.feeGrowthInside0LastX128.ToString(), result.feeGrowthInside0LastX128.ToString()) + uassert.Equal(t, position.feeGrowthInside1LastX128.ToString(), result.feeGrowthInside1LastX128.ToString()) + uassert.Equal(t, position.tokensOwed0.ToString(), result.tokensOwed0.ToString()) + uassert.Equal(t, position.tokensOwed1.ToString(), result.tokensOwed1.ToString()) + uassert.Equal(t, position.burned, result.burned) + removePosition(tokenId) + }) } func TestSetPosition(t *testing.T) { - t.Skip("TODO: Implement") + tokenId := uint64(2) + position := newDummyPosition(tokenId) + + t.Run("Success - Create new position", func(t *testing.T) { + isNew := setPosition(tokenId, position) + uassert.False(t, isNew) + + storedPosition, _ := GetPosition(tokenId) + uassert.Equal(t, position.nonce.ToString(), storedPosition.nonce.ToString()) + uassert.Equal(t, position.operator, storedPosition.operator) + uassert.Equal(t, position.poolKey, storedPosition.poolKey) + uassert.Equal(t, position.tickLower, storedPosition.tickLower) + uassert.Equal(t, position.tickUpper, storedPosition.tickUpper) + uassert.Equal(t, position.liquidity.ToString(), storedPosition.liquidity.ToString()) + uassert.Equal(t, position.feeGrowthInside0LastX128.ToString(), storedPosition.feeGrowthInside0LastX128.ToString()) + uassert.Equal(t, position.feeGrowthInside1LastX128.ToString(), storedPosition.feeGrowthInside1LastX128.ToString()) + uassert.Equal(t, position.tokensOwed0.ToString(), storedPosition.tokensOwed0.ToString()) + uassert.Equal(t, position.tokensOwed1.ToString(), storedPosition.tokensOwed1.ToString()) + uassert.Equal(t, position.burned, storedPosition.burned) + }) + + t.Run("Success - Update existing position", func(t *testing.T) { + position.tickUpper = 1000 + isNew := setPosition(tokenId, position) + uassert.True(t, isNew) + + updatedPosition, _ := GetPosition(tokenId) + uassert.Equal(t, int32(1000), updatedPosition.tickUpper) + }) } func TestRemovePosition(t *testing.T) { - t.Skip("TODO: Implement") + tokenId := uint64(2) + position := newDummyPosition(tokenId) + + t.Run("Success - Remove existing position", func(t *testing.T) { + setPosition(tokenId, position) + removePosition(tokenId) + _, found := GetPosition(tokenId) + uassert.False(t, found) + }) + + t.Run("Success - Remove non-existent position", func(t *testing.T) { + removePosition(tokenId) + _, found := GetPosition(tokenId) + uassert.False(t, found) + }) } func TestExistPosition(t *testing.T) { - t.Skip("TODO: Implement") + tokenId := uint64(2) + position := newDummyPosition(tokenId) + + t.Run("False - Position does not exist", func(t *testing.T) { + uassert.False(t, existPosition(tokenId)) + }) + + t.Run("True - Position exists", func(t *testing.T) { + setPosition(tokenId, position) + uassert.True(t, existPosition(tokenId)) + removePosition(tokenId) + }) } func TestComputePositionKey(t *testing.T) { @@ -587,29 +699,723 @@ func TestMintInternal(t *testing.T) { } func TestMint(t *testing.T) { - t.Skip("TestMint not implemented") + nextId = gnft.TotalSupply() + 1 + tests := []struct { + name string + token0 string + token1 string + fee uint32 + tickLower int32 + tickUpper int32 + amount0 string + amount1 string + minAmount0 string + minAmount1 string + deadline int64 + mintTo std.Address + caller std.Address + expectPanic bool + expectedError string + expectedAmount0 uint64 + expectedAmount1 uint64 + }{ + { + name: "Success - Valid mint request", + token0: barPath, + token1: fooPath, + fee: fee500, + tickLower: -500, + tickUpper: 500, + amount0: "1000000", + amount1: "2000000", + minAmount0: "950000", + minAmount1: "900000", + deadline: time.Now().Add(10 * time.Minute).Unix(), + mintTo: users.Resolve(alice), + caller: users.Resolve(alice), + expectPanic: false, + expectedAmount0: 1000000, + expectedAmount1: 1000000, + }, + { + name: "Fail - Deadline exceeded", + token0: barPath, + token1: fooPath, + fee: fee500, + tickLower: -500, + tickUpper: 500, + amount0: "1000000", + amount1: "2000000", + minAmount0: "950000", + minAmount1: "1900000", + deadline: time.Now().Add(-10 * time.Minute).Unix(), + mintTo: users.Resolve(alice), + caller: users.Resolve(alice), + expectPanic: true, + expectedError: "[GNOSWAP-POSITION-007] transaction expired || transaction too old, now(1234567890) > deadline(1234567290)", + expectedAmount0: 950000, + expectedAmount1: 1900000, + }, + { + name: "Fail - Invalid tick range", + token0: barPath, + token1: fooPath, + fee: fee500, + tickLower: 600, + tickUpper: 500, + amount0: "1000000", + amount1: "2000000", + minAmount0: "950000", + minAmount1: "1900000", + deadline: time.Now().Add(10 * time.Minute).Unix(), + mintTo: users.Resolve(alice), + caller: users.Resolve(alice), + expectPanic: true, + expectedError: "[GNOSWAP-POOL-024] tickLower is greater than or equal to tickUpper || tickLower(600), tickUpper(500)", + expectedAmount0: 950000, + expectedAmount1: 1900000, + }, + { + name: "Fail - Caller not authorized", + token0: barPath, + token1: fooPath, + fee: fee500, + tickLower: -500, + tickUpper: 500, + amount0: "1000000", + amount1: "2000000", + minAmount0: "950000", + minAmount1: "1900000", + deadline: time.Now().Add(10 * time.Minute).Unix(), + mintTo: users.Resolve(admin), + caller: users.Resolve(alice), + expectPanic: true, + expectedError: "[GNOSWAP-POSITION-012] invalid address || (g1v9kxjcm9ta047h6lta047h6lta047h6lzd40gh, g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d)", + expectedAmount0: 950000, + expectedAmount1: 1900000, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(tc.caller)) + if tc.expectPanic { + uassert.PanicsWithMessage(t, + tc.expectedError, + func() { + Mint( + tc.token0, + tc.token1, + tc.fee, + tc.tickLower, + tc.tickUpper, + tc.amount0, + tc.amount1, + tc.minAmount0, + tc.minAmount1, + tc.deadline, + tc.mintTo, + tc.caller, + ) + }) + } else { + tokenId, liquidity, amount0, amount1 := Mint( + tc.token0, + tc.token1, + tc.fee, + tc.tickLower, + tc.tickUpper, + tc.amount0, + tc.amount1, + tc.minAmount0, + tc.minAmount1, + tc.deadline, + tc.mintTo, + tc.caller, + ) + + uassert.Equal(t, uint64(4), tokenId) + uassert.NotEmpty(t, liquidity) + t0, _ := strconv.ParseUint(amount0, 10, 64) + t1, _ := strconv.ParseUint(amount1, 10, 64) + uassert.Equal(t, tc.expectedAmount0, t0) + uassert.Equal(t, tc.expectedAmount1, t1) + } + }) + } } func TestIncreaseLiquidityInternal(t *testing.T) { - t.Skip("TestIncreaseLiquidityInternal not implemented") + std.TestSetRealm(std.NewUserRealm(users.Resolve(admin))) + position := Position{ + poolKey: "gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:500", + tickLower: -10000, + tickUpper: 10000, + liquidity: u256.NewUint(1000000), + feeGrowthInside0LastX128: u256.Zero(), + feeGrowthInside1LastX128: u256.Zero(), + tokensOwed0: u256.Zero(), + tokensOwed1: u256.Zero(), + burned: false, + } + id := getNextId() + setPosition(id, position) + incrementNextId() + + tests := []struct { + name string + params IncreaseLiquidityParams + expectedLiquidity string + expectedAmount0 string + expectedAmount1 string + expectPanic bool + expectedErrorMsg string + }{ + { + name: "Fail - Position Does Not Exist", + params: IncreaseLiquidityParams{ + tokenId: 999, + amount0Desired: u256.NewUint(1000000), + amount1Desired: u256.NewUint(2000000), + amount0Min: u256.NewUint(900000), + amount1Min: u256.NewUint(1800000), + deadline: time.Now().Add(10 * time.Minute).Unix(), + }, + expectPanic: true, + expectedErrorMsg: "[GNOSWAP-POSITION-006] requested data not found || tokenId(999) doesn't exist", + }, + { + name: "Fail - Zero Liquidity", + params: IncreaseLiquidityParams{ + tokenId: 1, + amount0Desired: u256.Zero(), + amount1Desired: u256.Zero(), + amount0Min: u256.NewUint(900000), + amount1Min: u256.NewUint(1800000), + deadline: time.Now().Add(10 * time.Minute).Unix(), + }, + expectPanic: true, + expectedErrorMsg: "[GNOSWAP-POOL-010] zero liquidity", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if tc.expectPanic { + uassert.PanicsWithMessage(t, + tc.expectedErrorMsg, + func() { + increaseLiquidity(tc.params) + }) + } else { + tokenId, liquidity, amount0, amount1, _ := increaseLiquidity(tc.params) + uassert.Equal(t, tc.params.tokenId, tokenId) + uassert.Equal(t, tc.expectedLiquidity, liquidity.ToString()) + uassert.Equal(t, tc.expectedAmount0, amount0.ToString()) + uassert.Equal(t, tc.expectedAmount1, amount1.ToString()) + } + }) + } } func TestIncreaseLiquidity(t *testing.T) { - t.Skip("TestIncreaseLiquidity not implemented") -} + std.TestSetRealm(std.NewUserRealm(users.Resolve(admin))) + tests := []struct { + name string + tokenId uint64 + amount0Desired string + amount1Desired string + amount0Min string + amount1Min string + deadline int64 + expectedAmount0 string + expectedAmount1 string + expectPanic bool + expectedErrorMsg string + }{ + { + name: "Success - Valid Increase", + tokenId: 1, + amount0Desired: "1000000", + amount1Desired: "2000000", + amount0Min: "950000", + amount1Min: "900000", + deadline: time.Now().Add(10 * time.Minute).Unix(), + expectedAmount0: "1000000", + expectedAmount1: "1000000", + expectPanic: false, + }, + { + name: "Fail - Deadline Exceeded", + tokenId: 1, + amount0Desired: "1000000", + amount1Desired: "2000000", + amount0Min: "950000", + amount1Min: "900000", + deadline: time.Now().Add(-10 * time.Minute).Unix(), + expectPanic: true, + expectedErrorMsg: "[GNOSWAP-POSITION-007] transaction expired || transaction too old, now(1234567890) > deadline(1234567290)", + }, + { + name: "Fail - Invalid Amount String", + tokenId: 1, + amount0Desired: "invalid_amount", + amount1Desired: "2000000", + amount0Min: "950000", + amount1Min: "900000", + deadline: time.Now().Add(10 * time.Minute).Unix(), + expectPanic: true, + expectedErrorMsg: "[GNOSWAP-POSITION-005] invalid input data || input string : invalid_amount", + }, + { + name: "Fail - Position Does Not Exist", + tokenId: 999, + amount0Desired: "1000000", + amount1Desired: "2000000", + amount0Min: "950000", + amount1Min: "900000", + deadline: time.Now().Add(10 * time.Minute).Unix(), + expectPanic: true, + expectedErrorMsg: "[GNOSWAP-POSITION-013] position does not exist || position with tokenId(999) doesn't exist", + }, + } -func TestDecreaseLiquidityInternal(t *testing.T) { - t.Skip("TestDecreaseLiquidityInternal not implemented") + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if tc.expectPanic { + uassert.PanicsWithMessage(t, + tc.expectedErrorMsg, + func() { + IncreaseLiquidity( + tc.tokenId, + tc.amount0Desired, + tc.amount1Desired, + tc.amount0Min, + tc.amount1Min, + tc.deadline, + ) + }) + } else { + tokenId, _, amount0, amount1, _ := IncreaseLiquidity( + tc.tokenId, + tc.amount0Desired, + tc.amount1Desired, + tc.amount0Min, + tc.amount1Min, + tc.deadline, + ) + uassert.Equal(t, tc.tokenId, tokenId) + uassert.Equal(t, tc.expectedAmount0, amount0) + uassert.Equal(t, tc.expectedAmount1, amount1) + } + }) + } } func TestDecreaseLiquidity(t *testing.T) { - t.Skip("TestDecreaseLiquidity not implemented") + tests := []struct { + name string + beforeIncrease bool + increaseAmount0 string + increaseAmount1 string + liquidityRatio uint64 + amount0Min string + amount1Min string + deadlineOffset time.Duration + unwrapResult bool + expectPanic bool + expectedErrorMsg string + expectedLiquidity string + expectedFee0 string + expectedFee1 string + }{ + { + name: "Success - Decrease 50% Liquidity", + liquidityRatio: 50, // 50% + amount0Min: "8000", + amount1Min: "15000", + deadlineOffset: time.Hour, + unwrapResult: false, + expectPanic: false, + expectedLiquidity: "1270796", + expectedFee0: "0", + expectedFee1: "0", + }, + { + name: "Success - Decrease 100% Liquidity", + liquidityRatio: 100, // 100% + amount0Min: "10000", + amount1Min: "20000", + deadlineOffset: time.Hour, + unwrapResult: false, + expectPanic: false, + expectedLiquidity: "1270796", + expectedFee0: "0", + expectedFee1: "0", + expectedErrorMsg: "[GNOSWAP-POSITION-017] invalid liquidity ratio || liquidity ratio must in range 1 ~ 100(contain)", + }, + { + name: "Fail - Zero Liquidity", + liquidityRatio: 0, // 0% + amount0Min: "10000", + amount1Min: "20000", + deadlineOffset: time.Hour, + unwrapResult: false, + expectPanic: true, + expectedErrorMsg: "[GNOSWAP-POSITION-017] invalid liquidity ratio || liquidity ratio must in range 1 ~ 100(contain), got 0", + }, + { + name: "Fail - Underflow", + liquidityRatio: 50, // 50% + amount0Min: "200000", + amount1Min: "400000", + deadlineOffset: time.Hour, + unwrapResult: false, + expectPanic: true, + expectedErrorMsg: "[GNOSWAP-POOL-010] zero liquidity || both liquidityDelta and current position's liquidity are zero", + }, + { + name: "Fail - Deadline Exceeded", + liquidityRatio: 50, + amount0Min: "10000", + amount1Min: "20000", + deadlineOffset: -time.Hour, + unwrapResult: false, + expectPanic: true, + expectedErrorMsg: "[GNOSWAP-POSITION-007] transaction expired || transaction too old, now(1234567890) > deadline(1234564290)", + }, + { + name: "Success - Increase and Decrease", + beforeIncrease: true, + increaseAmount0: "500000", + increaseAmount1: "1000000", + liquidityRatio: 50, + amount0Min: "12000", + amount1Min: "25000", + deadlineOffset: time.Hour, + unwrapResult: false, + expectPanic: false, + expectedLiquidity: "635398", // 50% 감소, 증가 후 + expectedFee0: "0", + expectedFee1: "0", + }, + } + + CreatePoolWithoutFee(t) + std.TestSetRealm(std.NewUserRealm(users.Resolve(admin))) + TokenApprove(t, barPath, admin, pool, consts.UINT64_MAX) + TokenApprove(t, fooPath, admin, pool, consts.UINT64_MAX) + tokenId, _, _, _ := Mint( + barPath, + fooPath, + fee500, + -10000, + 10000, + "1000000", + "1000000", + "0", + "0", + time.Now().Add(time.Hour).Unix(), + users.Resolve(admin), + users.Resolve(admin), + ) + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + if !tc.expectPanic { + t.Errorf("unexpected panic: %v", r) + } else { + uassert.Equal(t, tc.expectedErrorMsg, r) + } + } + }() + + deadline := time.Now().Add(tc.deadlineOffset).Unix() + if tc.beforeIncrease { + _, _, _, _, _ = IncreaseLiquidity( + tokenId, + tc.increaseAmount0, + tc.increaseAmount1, + "0", + "0", + deadline, + ) + } + + if tc.expectPanic { + uassert.PanicsWithMessage(t, tc.expectedErrorMsg, func() { + _, liquidity, fee0, fee1, _, _, _ := DecreaseLiquidity( + tokenId, + tc.liquidityRatio, + tc.amount0Min, + tc.amount1Min, + deadline, + tc.unwrapResult, + ) + }) + } else { + _, liquidity, fee0, fee1, _, _, _ := DecreaseLiquidity( + tokenId, + tc.liquidityRatio, + tc.amount0Min, + tc.amount1Min, + deadline, + tc.unwrapResult, + ) + uassert.Equal(t, tc.expectedLiquidity, liquidity) + uassert.Equal(t, tc.expectedFee0, fee0) + uassert.Equal(t, tc.expectedFee1, fee1) + } + }) + } } func TestCollectFees(t *testing.T) { - t.Skip("TestCollectFees not implemented") + tests := []struct { + name string + mintAmount0 string + mintAmount1 string + increaseAmount0 string + increaseAmount1 string + unwrapResult bool + liquidityRatio uint64 + expectedFee0 string + expectedFee1 string + expectedAmount0 string + expectedAmount1 string + expectPanic bool + expectedPanicError string + }{ + { + name: "Success - Collect fee after multiple mints", + mintAmount0: "1000000", + mintAmount1: "2000000", + increaseAmount0: "500000", + increaseAmount1: "1000000", + liquidityRatio: 50, + unwrapResult: false, + expectedFee0: "0", + expectedFee1: "0", + expectedAmount0: "0", + expectedAmount1: "0", + expectPanic: false, + }, + { + name: "Success - Collect fee after full liquidity removal", + mintAmount0: "1000000", + mintAmount1: "2000000", + liquidityRatio: 100, + unwrapResult: true, + expectedFee0: "0", + expectedFee1: "0", + expectedAmount0: "0", + expectedAmount1: "0", + expectPanic: false, + }, + { + name: "Fail - No liquidity to collect", + mintAmount0: "100000", + mintAmount1: "200000", + liquidityRatio: 0, + unwrapResult: false, + expectPanic: true, + expectedPanicError: "[GNOSWAP-POSITION-017] invalid liquidity ratio || liquidity ratio must in range 1 ~ 100(contain), got 0", + }, + } + + CreatePoolWithoutFee(t) + std.TestSetRealm(std.NewUserRealm(users.Resolve(admin))) + TokenApprove(t, barPath, admin, pool, consts.UINT64_MAX) + TokenApprove(t, fooPath, admin, pool, consts.UINT64_MAX) + + // Mint liquidity + tokenId, _, _, _ := Mint( + barPath, + fooPath, + fee500, + -10000, + 10000, + "1000000", + "1000000", + "0", + "0", + time.Now().Add(time.Hour).Unix(), + users.Resolve(admin), + users.Resolve(admin), + ) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + if !tt.expectPanic { + t.Errorf("unexpected panic: %v", r) + } else { + uassert.Equal(t, tt.expectedPanicError, r) + } + } + }() + + // Increase liquidity if specified + if tt.increaseAmount0 != "" && tt.increaseAmount1 != "" { + _, _, _, _, _ = IncreaseLiquidity( + tokenId, + tt.increaseAmount0, + tt.increaseAmount1, + "0", + "0", + time.Now().Add(time.Hour).Unix(), + ) + } + + // Decrease liquidity + _, _, _, _, _, _, _ = DecreaseLiquidity( + tokenId, + tt.liquidityRatio, + "0", + "0", + time.Now().Add(time.Hour).Unix(), + false, + ) + + if tt.expectPanic { + uassert.PanicsWithMessage(t, tt.expectedPanicError, func() { + CollectFee(tokenId, tt.unwrapResult) + }) + } else { + _, fee0, fee1, _, amount0, amount1 := CollectFee(tokenId, tt.unwrapResult) + uassert.Equal(t, tt.expectedFee0, fee0) + uassert.Equal(t, tt.expectedFee1, fee1) + uassert.Equal(t, tt.expectedAmount0, amount0) + uassert.Equal(t, tt.expectedAmount1, amount1) + } + }) + } } func TestReposition(t *testing.T) { - t.Skip("TestReposition not implemented") + tests := []struct { + name string + mintAmount0 string + mintAmount1 string + increaseAmount0 string + increaseAmount1 string + decreaseRatio uint64 + tickLower int32 + tickUpper int32 + expectPanic bool + expectedPanicError string + }{ + { + name: "Success - Reposition after full liquidity removal", + mintAmount0: "1000000", + mintAmount1: "2000000", + increaseAmount0: "500000", + increaseAmount1: "1000000", + decreaseRatio: 100, + tickLower: -5000, + tickUpper: 5000, + expectPanic: false, + }, + { + name: "Fail - Reposition with partial liquidity removal", + mintAmount0: "1000000", + mintAmount1: "2000000", + increaseAmount0: "500000", + increaseAmount1: "1000000", + decreaseRatio: 50, + tickLower: -3000, + tickUpper: 3000, + expectPanic: true, + expectedPanicError: "[GNOSWAP-POSITION-009] position is not clear || position(8) isn't clear(liquidity:3390758, tokensOwed0:0, tokensOwed1:0)", + }, + { + name: "Fail - Reposition on non-existent tokenId", + mintAmount0: "1000000", + mintAmount1: "2000000", + decreaseRatio: 10, + tickLower: -4000, + tickUpper: 4000, + expectPanic: true, + expectedPanicError: "[GNOSWAP-POSITION-009] position is not clear || position(8) isn't clear(liquidity:3051683, tokensOwed0:0, tokensOwed1:0)", + }, + } + + CreatePoolWithoutFee(t) + std.TestSetRealm(std.NewUserRealm(users.Resolve(admin))) + TokenApprove(t, barPath, admin, pool, consts.UINT64_MAX) + TokenApprove(t, fooPath, admin, pool, consts.UINT64_MAX) + + tokenId, _, _, _ := Mint( + barPath, + fooPath, + fee500, + -10000, + 10000, + "1000000", + "1000000", + "0", + "0", + time.Now().Add(time.Hour).Unix(), + users.Resolve(admin), + users.Resolve(admin), + ) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Step 1: Mint a position + + // Step 2: Increase liquidity + if tt.increaseAmount0 != "" && tt.increaseAmount1 != "" { + _, _, _, _, _ = IncreaseLiquidity( + tokenId, + tt.increaseAmount0, + tt.increaseAmount1, + "0", + "0", + time.Now().Add(time.Hour).Unix(), + ) + } + + // Step 3: Decrease liquidity to clear the position + _, _, _, _, _, _, _ = DecreaseLiquidity( + tokenId, + tt.decreaseRatio, + "0", + "0", + time.Now().Add(time.Hour).Unix(), + false, + ) + + // Step 4: Attempt Reposition + if tt.expectPanic { + uassert.PanicsWithMessage(t, tt.expectedPanicError, func() { + Reposition( + tokenId, + tt.tickLower, + tt.tickUpper, + tt.mintAmount0, + tt.mintAmount1, + "0", + "0", + ) + }) + } else { + //tokenId, liquidity.ToString(), tickLower, tickUpper, amount0.ToString(), amount1.ToString() + tid, _, tickL, tickH, _, _ := Reposition( + tokenId, + tt.tickLower, + tt.tickUpper, + tt.mintAmount0, + tt.mintAmount1, + "0", + "0", + ) + uassert.Equal(t, tid, tokenId) + uassert.Equal(t, tickL, tt.tickLower) + uassert.Equal(t, tickH, tt.tickUpper) + } + }) + } } diff --git a/position/utils.gno b/position/utils.gno index e072461e7..67348632a 100644 --- a/position/utils.gno +++ b/position/utils.gno @@ -157,16 +157,6 @@ func assertValidLiquidityRatio(ratio uint64) { } } -// [DEPRECATED] assertOnlyValidAddress panics if the address is invalid. -func assertWrapNativeToken(ugnotSent uint64, prevRealm std.Address) { - if err := wrap(ugnotSent, prevRealm); err != nil { - panic(newErrorWithDetail( - errWrapUnwrap, - ufmt.Sprintf("wrap error: %s", err.Error()), - )) - } -} - // a2u converts std.Address to pusers.AddressOrName. // pusers is a package that contains the user-related functions. // diff --git a/position/utils_test.gno b/position/utils_test.gno index fdc75fdb1..6ac64b850 100644 --- a/position/utils_test.gno +++ b/position/utils_test.gno @@ -351,11 +351,6 @@ func TestAssertValidLiquidityRatio(t *testing.T) { t.Skip("TODO: Implement TestAssertValidLiquidityRatio") } -func TestAssertWrapNativeToken(t *testing.T) { - // TODO: - -} - func TestA2u(t *testing.T) { addr := std.Address("g1lmvrrrr4er2us84h2732sru76c9zl2nvknha8c") @@ -624,20 +619,17 @@ func TestExists(t *testing.T) { }{ { name: "Fail - not exists", - tokenId: 2, + tokenId: 300000, expected: false, }, { name: "Success - exists", - tokenId: 2, - expected: false, + tokenId: 1, + expected: true, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - if tc.expected { - - } got := exists(tc.tokenId) uassert.Equal(t, tc.expected, got) }) @@ -832,10 +824,6 @@ func TestIsOwnerOrOperatorWithStake(t *testing.T) { } } -func TestIsAuthorizedForToken(t *testing.T) { - t.Skip("TODO - Implement TestIsAuthorizedForToken") -} - func TestPoolKeyDivide(t *testing.T) { tests := []struct { name string @@ -904,7 +892,7 @@ func TestPoolKeyDivide(t *testing.T) { } } -func TestSplitOf_Improved(t *testing.T) { +func TestSplitOf(t *testing.T) { tests := []struct { name string poolKey string @@ -926,6 +914,25 @@ func TestSplitOf_Improved(t *testing.T) { expectedError: "[GNOSWAP-POSITION-005] invalid input data || invalid fee(gno.land/r/demo/wugnot-500)", shouldPanic: true, }, + { + name: "Fail - non-numeric fee", + poolKey: "gno.land/r/gnoswap/v1/gns:gno.land/r/demo/wugnot:fee", + expectedError: "[GNOSWAP-POSITION-005] invalid input data || invalid fee(fee)", + shouldPanic: true, + }, + { + name: "Fail - missing fee part", + poolKey: "gno.land/r/gnoswap/v1/gns:gno.land/r/demo/wugnot:", + expectedError: "[GNOSWAP-POSITION-005] invalid input data || invalid fee()", + shouldPanic: true, + }, + { + name: "Fail - insufficient parts", + poolKey: "gno.land/r/gnoswap/v1/gns:gno.land/r/demo", + expectedError: "[GNOSWAP-POSITION-005] invalid input data || invalid poolKey(gno.land/r/gnoswap/v1/gns:gno.land/r/demo)", + shouldPanic: true, + }, + // 정상 케이스 { name: "Success - valid poolKey", poolKey: "gno.land/r/gnoswap/v1/gns:gno.land/r/demo/wugnot:500", @@ -934,6 +941,22 @@ func TestSplitOf_Improved(t *testing.T) { expectedFee: 500, shouldPanic: false, }, + { + name: "Success - poolKey with large fee", + poolKey: "gno.land/r/gnoswap/v1/gns:gno.land/r/demo/wugnot:10000", + expectedPath0: "gno.land/r/gnoswap/v1/gns", + expectedPath1: "gno.land/r/demo/wugnot", + expectedFee: 10000, + shouldPanic: false, + }, + { + name: "Success - poolKey with minimal fee", + poolKey: "gno.land/r/gnoswap/v1/gns:gno.land/r/demo/wugnot:1", + expectedPath0: "gno.land/r/gnoswap/v1/gns", + expectedPath1: "gno.land/r/demo/wugnot", + expectedFee: 1, + shouldPanic: false, + }, } for _, tc := range tests { @@ -944,9 +967,9 @@ func TestSplitOf_Improved(t *testing.T) { }) } else { gotToken0, gotToken1, gotFee := splitOf(tc.poolKey) - uassert.Equal(t, tc.expectedPath0, gotToken0) - uassert.Equal(t, tc.expectedPath1, gotToken1) - uassert.Equal(t, tc.expectedFee, gotFee) + uassert.Equal(t, tc.expectedPath0, gotToken0, "Token0 mismatch") + uassert.Equal(t, tc.expectedPath1, gotToken1, "Token1 mismatch") + uassert.Equal(t, tc.expectedFee, gotFee, "Fee mismatch") } }) }