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

Autoswap accounting fixes #NTRN-385 #685

Merged
merged 21 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
4 changes: 3 additions & 1 deletion x/dex/keeper/core_helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

neutronapp "github.com/neutron-org/neutron/v4/app"
"github.com/neutron-org/neutron/v4/testutil"
math_utils "github.com/neutron-org/neutron/v4/utils/math"
"github.com/neutron-org/neutron/v4/x/dex/types"
)

Expand Down Expand Up @@ -65,7 +66,8 @@ func (s *CoreHelpersTestSuite) setLPAtFee1Pool(tickIndex int64, amountA, amountB

existingShares := s.app.BankKeeper.GetSupply(s.ctx, pool.GetPoolDenom()).Amount

totalShares := pool.CalcSharesMinted(amountAInt, amountBInt, existingShares)
depositAmountAsToken0 := types.CalcAmountAsToken0(amountAInt, amountBInt, pool.MustCalcPrice1To0Center())
totalShares := pool.CalcSharesMinted(depositAmountAsToken0, existingShares, math_utils.ZeroPrecDec())

err = s.app.DexKeeper.MintShares(s.ctx, s.alice, sdk.NewCoins(totalShares))
s.Require().NoError(err)
Expand Down
4 changes: 2 additions & 2 deletions x/dex/keeper/grpc_query_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestPoolQuerySingle(t *testing.T) {
desc: "First",
request: &types.QueryPoolRequest{
PairId: "TokenA<>TokenB",
TickIndex: msgs[0].CenterTickIndex(),
TickIndex: msgs[0].CenterTickIndexToken1(),
Fee: msgs[0].Fee(),
},
response: &types.QueryPoolResponse{Pool: msgs[0]},
Expand All @@ -34,7 +34,7 @@ func TestPoolQuerySingle(t *testing.T) {
desc: "Second",
request: &types.QueryPoolRequest{
PairId: "TokenA<>TokenB",
TickIndex: msgs[1].CenterTickIndex(),
TickIndex: msgs[1].CenterTickIndexToken1(),
Fee: msgs[1].Fee(),
},
response: &types.QueryPoolResponse{Pool: msgs[1]},
Expand Down
171 changes: 82 additions & 89 deletions x/dex/keeper/integration_deposit_autoswap_unit_test.go
Original file line number Diff line number Diff line change
@@ -1,109 +1,102 @@
package keeper_test

import "github.com/neutron-org/neutron/v4/x/dex/types"
import (
"cosmossdk.io/math"
)

func (s *DexTestSuite) TestAutoswapperWithdraws() {
func (s *DexTestSuite) TestAutoswapSingleSided0To1() {
s.fundAliceBalances(50, 50)
s.fundBobBalances(50, 50)

// GIVEN
// create spread around -1, 1
bobDep0 := 10
bobDep1 := 10
tickIndex := 200
fee := 5

bobSharesMinted := s.calcSharesMinted(int64(tickIndex), int64(bobDep0), int64(bobDep1))

s.bobDeposits(NewDeposit(bobDep0, bobDep1, tickIndex, fee))
s.assertBobBalances(40, 40)
s.assertDexBalances(10, 10)

// Alice deposits at a different balance ratio
s.aliceDeposits(NewDepositWithOptions(12, 5, tickIndex, fee, types.DepositOptions{DisableAutoswap: false}))
s.assertAliceBalances(38, 45)
s.assertDexBalances(22, 15)

// Calculated expected amounts out
autoswapSharesMinted := s.calcAutoswapSharesMinted(int64(tickIndex), uint64(fee), 7, 0, 5, 5, bobSharesMinted.Int64(), bobSharesMinted.Int64())
// totalShares := autoswapSharesMinted.Add(math.NewInt(20))

aliceExpectedBalance0, aliceExpectedBalance1, dexExpectedBalance0, dexExpectedBalance1 := s.calcExpectedBalancesAfterWithdrawOnePool(autoswapSharesMinted, s.alice, int64(tickIndex), uint64(fee))

s.aliceWithdraws(NewWithdrawalInt(autoswapSharesMinted, int64(tickIndex), uint64(fee)))

s.assertAliceBalancesInt(aliceExpectedBalance0, aliceExpectedBalance1)
s.assertDexBalancesInt(dexExpectedBalance0, dexExpectedBalance1)
s.fundBobBalances(50, 0)

// GIVEN a pool with double-sided liquidity
s.aliceDeposits(NewDeposit(50, 50, 2000, 2))
s.assertAccountSharesInt(s.alice, 2000, 2, math.NewInt(111069527))

// WHEN bob deposits only TokenA
s.bobDeposits(NewDeposit(50, 0, 2000, 2))
s.assertPoolLiquidity(100, 50, 2000, 2)

// THEN his deposit is autoswapped
// He receives 49.985501 shares
// depositValue = depositAmount - (autoswapedAmountAsToken0 * fee)
// = 50 - 50 * (1 - 1.0001^-2)
// = 49.9900014998
// SharesIssued = depositValue * existing shares / (existingValue + autoSwapFee)
// = 49.9900014998 * 111.069527 / (111.069527 + 0.0099985002)
// = 49.985501

s.assertAccountSharesInt(s.bob, 2000, 2, math.NewInt(49985501))
}

func (s *DexTestSuite) TestAutoswapOtherDepositorWithdraws() {
func (s *DexTestSuite) TestAutoswapSingleSided1To0() {
s.fundAliceBalances(50, 50)
s.fundBobBalances(50, 50)

// GIVEN
// create spread around -1, 1
bobDep0 := 10
bobDep1 := 10
tickIndex := 150
fee := 10

bobSharesMinted := s.calcSharesMinted(int64(tickIndex), int64(bobDep0), int64(bobDep1))

s.bobDeposits(NewDeposit(bobDep0, bobDep1, tickIndex, fee))
s.assertBobBalances(40, 40)
s.assertDexBalances(10, 10)

// Alice deposits at a different balance ratio
s.aliceDeposits(NewDepositWithOptions(10, 7, tickIndex, fee, types.DepositOptions{DisableAutoswap: false}))
s.assertAliceBalances(40, 43)
s.assertDexBalances(20, 17)

// Calculated expected amounts out

bobExpectedBalance0, bobExpectedBalance1, dexExpectedBalance0, dexExpectedBalance1 := s.calcExpectedBalancesAfterWithdrawOnePool(bobSharesMinted, s.bob, int64(tickIndex), uint64(fee))

s.bobWithdraws(NewWithdrawalInt(bobSharesMinted, int64(tickIndex), uint64(fee)))

s.assertBobBalancesInt(bobExpectedBalance0, bobExpectedBalance1)
s.assertDexBalancesInt(dexExpectedBalance0, dexExpectedBalance1)
s.fundBobBalances(0, 50)

// GIVEN a pool with double-sided liquidity
s.aliceDeposits(NewDeposit(50, 50, 2000, 2))
s.assertAccountSharesInt(s.alice, 2000, 2, math.NewInt(111069527))

// WHEN bob deposits only TokenB
s.bobDeposits(NewDeposit(0, 50, 2000, 2))
s.assertPoolLiquidity(50, 100, 2000, 2)

// THEN his deposit is autoswapped
// He receives 61.0 shares
// depositAmountAsToken0 = 50 * 1.0001^2000 = 61.06952725039
// depositValue = depositAmountAsToken0 - (autoswapedAmountAsToken0 * fee)
// = 61.06952725039 - 61.06952725039 * (1 - 1.0001^-2)
// = 61.05731517678
// SharesIssued = depositValue * existing shares / (existingValue + autoSwapFee)
// = 61.05731517678 * 111.069527 / (111.069527 + 0.01221207361)
// = 61.050602

s.assertAccountSharesInt(s.bob, 2000, 2, math.NewInt(61050602))
}

func (s *DexTestSuite) TestAutoswapBothWithdraws() {
s.fundAliceBalances(50, 50)
func (s *DexTestSuite) TestAutoswapDoubleSided0To1() {
s.fundAliceBalances(30, 50)
s.fundBobBalances(50, 50)

// GIVEN
// create spread around -1, 1
bobDep0 := 10
bobDep1 := 10
tickIndex := 10000
fee := 5

s.bobDeposits(NewDeposit(bobDep0, bobDep1, tickIndex, fee))
bobSharesMinted := s.getAccountShares(s.bob, "TokenA", "TokenB", int64(tickIndex), uint64(fee))
s.assertBobBalances(40, 40)
s.assertDexBalances(10, 10)
// GIVEN a pool with double-sided liquidity
s.aliceDeposits(NewDeposit(30, 50, -4000, 10))
s.assertAccountSharesInt(s.alice, -4000, 10, math.NewInt(63516672))

// Alice deposits at a different balance ratio
s.aliceDeposits(NewDepositWithOptions(10, 5, tickIndex, fee, types.DepositOptions{DisableAutoswap: false}))
s.assertAliceBalances(40, 45)
s.assertDexBalances(20, 15)
// WHEN bob deposits a ratio of 1:1 tokenA and B
s.bobDeposits(NewDeposit(50, 50, -4000, 10))
s.assertPoolLiquidity(80, 100, -4000, 10)

// Calculated expected amounts out
autoswapSharesMinted := s.getAccountShares(s.alice, "TokenA", "TokenB", int64(tickIndex), uint64(fee))
// totalShares := autoswapSharesMinted.Add(math.NewInt(20))
// THEN his deposit is autoswapped
// He receives 83.5 shares
// depositValue = depositAmountAsToken0 - (autoswapedAmountAsToken0 * fee)
// = 83.5166725838 - 20 * (1 - 1.0001^-10)
// = 83.4966835794
// SharesIssued = depositValue * existing shares / (existingValue + autoSwapFee)
// = 83.4966835794 * 63.516672 / (63.5166725838 + 0.0199890044)
// = 83.470414

bobExpectedBalance0, bobExpectedBalance1, dexExpectedBalance0, dexExpectedBalance1 := s.calcExpectedBalancesAfterWithdrawOnePool(bobSharesMinted, s.bob, int64(tickIndex), uint64(fee))
s.assertAccountSharesInt(s.bob, -4000, 10, math.NewInt(83470414))
}

s.bobWithdraws(NewWithdrawalInt(bobSharesMinted, int64(tickIndex), uint64(fee)))
func (s *DexTestSuite) TestAutoswapDoubleSided1To0() {
s.fundAliceBalances(50, 30)
s.fundBobBalances(50, 50)

s.assertBobBalancesInt(bobExpectedBalance0, bobExpectedBalance1)
s.assertDexBalancesInt(dexExpectedBalance0, dexExpectedBalance1)
// GIVEN a pool with double-sided liquidity
s.aliceDeposits(NewDeposit(50, 30, -4000, 10))
s.assertAccountSharesInt(s.alice, -4000, 10, math.NewInt(70110003))

aliceExpectedBalance0, aliceExpectedBalance1, dexExpectedBalance0, dexExpectedBalance1 := s.calcExpectedBalancesAfterWithdrawOnePool(autoswapSharesMinted, s.alice, int64(tickIndex), uint64(fee))
// WHEN bob deposits a ratio of 1:1 tokenA and B
s.bobDeposits(NewDeposit(50, 50, -4000, 10))
s.assertPoolLiquidity(100, 80, -4000, 10)

s.aliceWithdraws(NewWithdrawalInt(autoswapSharesMinted, int64(tickIndex), uint64(fee)))
// THEN his deposit is autoswapped
// He receives 83.5 shares
// depositValue = depositAmountAsToken0 - (autoswapedAmountAsToken0 * fee)
// = 83.5166725838 - 13.4066690335 * (1 - 1.0001^-10)
// = 83.5032732855
// SharesIssued = depositValue * existing shares / (existingValue + autoSwapFee)
// = 83.5032732855 * 70.110003 / (70.1100035503 + 0.01339929831)
// = 83.487316

s.assertAliceBalancesInt(aliceExpectedBalance0, aliceExpectedBalance1)
s.assertDexBalancesInt(dexExpectedBalance0, dexExpectedBalance1)
s.assertAccountSharesInt(s.bob, -4000, 10, math.NewInt(83487316))
}
2 changes: 1 addition & 1 deletion x/dex/keeper/integration_deposit_multi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func (s *DexTestSuite) TestDepositMultiCompleteFailure() {
s.assertAliceDepositFails(
err,
NewDeposit(5, 0, 2, 1),
NewDeposit(0, 5, 0, 1), // fails
NewDepositWithOptions(0, 5, 0, 1, types.DepositOptions{DisableAutoswap: true}), // fails
)
}

Expand Down
2 changes: 1 addition & 1 deletion x/dex/keeper/integration_deposit_singlesided_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ func (s *DexTestSuite) TestDepositSingleSidedZeroTrueAmountsFail() {
// second deposit's ratio is different than pool after the first, so amounts will be rounded to 0,0 and tx will fail

err := types.ErrZeroTrueDeposit
s.assertAliceDepositFails(err, NewDeposit(0, 5, 0, 1))
s.assertAliceDepositFails(err, NewDepositWithOptions(0, 5, 0, 1, types.DepositOptions{DisableAutoswap: true}))
}

func (s *DexTestSuite) TestDepositNilOptions() {
Expand Down
6 changes: 6 additions & 0 deletions x/dex/keeper/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

v3 "github.com/neutron-org/neutron/v4/x/dex/migrations/v3"
v4 "github.com/neutron-org/neutron/v4/x/dex/migrations/v4"
v5 "github.com/neutron-org/neutron/v4/x/dex/migrations/v5"
)

// Migrator is a struct for handling in-place store migrations.
Expand All @@ -26,3 +27,8 @@ func (m Migrator) Migrate2to3(ctx sdk.Context) error {
func (m Migrator) Migrate3to4(ctx sdk.Context) error {
return v4.MigrateStore(ctx, m.keeper.cdc, m.keeper.storeKey)
}

// Migrate4to5 migrates from version 4 to 5.
func (m Migrator) Migrate4to5(ctx sdk.Context) error {
return v5.MigrateStore(ctx, m.keeper)
}
71 changes: 0 additions & 71 deletions x/dex/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1473,77 +1473,6 @@ func (s *DexTestSuite) assertNLimitOrderExpiration(expected int) {
s.Assert().Equal(expected, len(exps))
}

func (s *DexTestSuite) calcAutoswapSharesMinted(
centerTick int64,
fee uint64,
residual0, residual1, balanced0, balanced1, totalShares, valuePool int64,
) sdkmath.Int {
residual0Int, residual1Int, balanced0Int, balanced1Int, totalSharesInt, valuePoolInt := sdkmath.NewInt(residual0),
sdkmath.NewInt(residual1),
sdkmath.NewInt(balanced0),
sdkmath.NewInt(balanced1),
sdkmath.NewInt(totalShares),
sdkmath.NewInt(valuePool)

// residualValue = 1.0001^-f * residualAmount0 + 1.0001^{i-f} * residualAmount1
// balancedValue = balancedAmount0 + 1.0001^{i} * balancedAmount1
// value = residualValue + balancedValue
// shares minted = value * totalShares / valuePool

centerPrice := types.MustCalcPrice(-1 * centerTick)
leftPrice := types.MustCalcPrice(-1 * (centerTick - int64(fee)))
discountPrice := types.MustCalcPrice(-1 * int64(fee))

balancedValue := math_utils.NewPrecDecFromInt(balanced0Int).
Add(centerPrice.MulInt(balanced1Int)).
TruncateInt()
residualValue := discountPrice.MulInt(residual0Int).
Add(leftPrice.Mul(math_utils.NewPrecDecFromInt(residual1Int))).
TruncateInt()
valueMint := balancedValue.Add(residualValue)

return valueMint.Mul(totalSharesInt).Quo(valuePoolInt)
}

func (s *DexTestSuite) calcSharesMinted(centerTick, amount0Int, amount1Int int64) sdkmath.Int {
amount0, amount1 := sdkmath.NewInt(amount0Int), sdkmath.NewInt(amount1Int)
centerPrice := types.MustCalcPrice(-1 * centerTick)

return math_utils.NewPrecDecFromInt(amount0).Add(centerPrice.Mul(math_utils.NewPrecDecFromInt(amount1))).TruncateInt()
}

func (s *DexTestSuite) calcExpectedBalancesAfterWithdrawOnePool(
sharesMinted sdkmath.Int,
account sdk.AccAddress,
tickIndex int64,
fee uint64,
) (sdkmath.Int, sdkmath.Int, sdkmath.Int, sdkmath.Int) {
dexCurrentBalance0 := s.App.BankKeeper.GetBalance(
s.Ctx,
s.App.AccountKeeper.GetModuleAddress("dex"),
"TokenA",
).Amount
dexCurrentBalance1 := s.App.BankKeeper.GetBalance(
s.Ctx,
s.App.AccountKeeper.GetModuleAddress("dex"),
"TokenB",
).Amount
currentBalance0 := s.App.BankKeeper.GetBalance(s.Ctx, account, "TokenA").Amount
currentBalance1 := s.App.BankKeeper.GetBalance(s.Ctx, account, "TokenB").Amount
amountPool0, amountPool1 := s.getLiquidityAtTick(tickIndex, fee)
poolShares := s.getPoolShares("TokenA", "TokenB", tickIndex, fee)

amountOut0 := amountPool0.Mul(sharesMinted).Quo(poolShares)
amountOut1 := amountPool1.Mul(sharesMinted).Quo(poolShares)

expectedBalance0 := currentBalance0.Add(amountOut0)
expectedBalance1 := currentBalance1.Add(amountOut1)
dexExpectedBalance0 := dexCurrentBalance0.Sub(amountOut0)
dexExpectedBalance1 := dexCurrentBalance1.Sub(amountOut1)

return expectedBalance0, expectedBalance1, dexExpectedBalance0, dexExpectedBalance1
}

func (s *DexTestSuite) nextBlockWithTime(blockTime time.Time) {
newCtx := s.Ctx.WithBlockTime(blockTime)
s.Ctx = newCtx
Expand Down
17 changes: 17 additions & 0 deletions x/dex/keeper/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,20 @@ func (k Keeper) SetPoolCount(ctx sdk.Context, count uint64) {
binary.BigEndian.PutUint64(bz, count)
store.Set(byteKey, bz)
}

func (k Keeper) GetAllPoolShareholders(ctx sdk.Context) map[uint64][]types.PoolShareholder {
result := make(map[uint64][]types.PoolShareholder)
balances := k.bankKeeper.GetAccountsBalances(ctx)
for _, balance := range balances {
for _, coin := range balance.Coins {
poolID, err := types.ParsePoolIDFromDenom(coin.Denom)
if err != nil {
continue
pr0n00gler marked this conversation as resolved.
Show resolved Hide resolved
}
shareholderInfo := types.PoolShareholder{Address: balance.Address, Shares: coin.Amount}
result[poolID] = append(result[poolID], shareholderInfo)

}
}
return result
}
4 changes: 2 additions & 2 deletions x/dex/keeper/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func TestGetPoolIDByParams(t *testing.T) {
id0, found := keeper.GetPoolIDByParams(
ctx,
items[0].LowerTick0.Key.TradePairId.MustPairID(),
items[0].CenterTickIndex(),
items[0].CenterTickIndexToken1(),
items[0].Fee(),
)
require.True(t, found)
Expand All @@ -83,7 +83,7 @@ func TestGetPoolIDByParams(t *testing.T) {
id1, found := keeper.GetPoolIDByParams(
ctx,
items[1].LowerTick0.Key.TradePairId.MustPairID(),
items[1].CenterTickIndex(),
items[1].CenterTickIndexToken1(),
items[1].Fee(),
)
require.True(t, found)
Expand Down
Loading
Loading