Skip to content

Commit

Permalink
add tests for swap rouding behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
Julian Compagni Portis committed Nov 10, 2023
1 parent 4981b7a commit 893476b
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 28 deletions.
37 changes: 37 additions & 0 deletions x/dex/keeper/integration_placelimitorder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,43 @@ func (s *DexTestSuite) TestPlaceLimitOrderIoCWithLPNoFill() {
s.assertLimitLiquidityAtTick("TokenA", 1, 0)
}

func (s *DexTestSuite) TestPlaceLimitOrderIoCUnfairPriceFails() {
s.fundAliceBalances(10, 0)
s.fundBobBalances(0, 1)
// GIVEN LP of 1 TokenB at tick -20
s.bobDeposits(NewDeposit(0, 1, -20, 1))
// WHEN alice submits IoC limitOrder for 2 tokenA
_, err := s.limitSellsInt(s.alice, "TokenA", 10, sdkmath.NewInt(2), types.LimitOrderType_IMMEDIATE_OR_CANCEL)

// THEN alice's LimitOrder failswith SwapAmountTooSmall
s.ErrorIs(err, types.ErrSwapAmountTooSmall)

// Nothing is changed
s.assertAliceBalances(10, 0)
s.assertLiquidityAtTick(0, 1, -20, 1)
}

func (s *DexTestSuite) TestPlaceLimitOrderIoCUnfairPriceAbortsEarly() {
s.fundAliceBalances(1, 0)
s.fundBobBalances(0, 2)
// GIVEN LP of 1 TokenB at ticks -20 & -21
s.bobDeposits(
NewDeposit(0, 1, -21, 1),
NewDeposit(0, 1, -20, 1),
)
// WHEN alice submits IoC limitOrder for 999_004 small TokenA
_, err := s.limitSellsInt(s.alice, "TokenA", 10, sdkmath.NewInt(998_004), types.LimitOrderType_IMMEDIATE_OR_CANCEL)

// THEN alice's LimitOrder swaps through the first tick, but does not swap the second tick due to unfair pricing
s.NoError(err)

// The first tick swapped and the second tick is not
s.assertLiquidityAtTickInt(sdkmath.NewInt(998_002), sdkmath.ZeroInt(), -21, 1)
s.assertLiquidityAtTick(0, 1, -20, 1)
// only the first swap is deducted from alices balance
s.assertAliceBalancesInt(sdkmath.NewInt(1998), sdkmath.NewInt(1_000_000))
}

// Just In Time Limit Orders //////////////////////////////////////////////////

func (s *DexTestSuite) TestPlaceLimitOrderJITFills() {
Expand Down
114 changes: 89 additions & 25 deletions x/dex/keeper/liquidity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func (s *DexTestSuite) TestSwap0To1NoLiquidity() {
s.placeGTCLimitOrder("TokenA", 1000, 10)

// WHEN swap 10 of tokenB
tokenIn, tokenOut := s.swap("TokenA", "TokenB", 10)
tokenIn, tokenOut := s.swapSuccess("TokenA", "TokenB", 10)

// THEN swap should do nothing
s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(0o_000_000), tokenOut, sdkmath.NewInt(0o_000_000))
Expand All @@ -31,7 +31,7 @@ func (s *DexTestSuite) TestSwap1To0NoLiquidity() {
s.placeGTCLimitOrder("TokenB", 1000, 10)

// WHEN swap 10 of tokenB
tokenIn, tokenOut := s.swap("TokenB", "TokenA", 10)
tokenIn, tokenOut := s.swapSuccess("TokenB", "TokenA", 10)

// THEN swap should do nothing
s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(0o_000_000), tokenOut, sdkmath.NewInt(0o_000_000))
Expand All @@ -40,14 +40,73 @@ func (s *DexTestSuite) TestSwap1To0NoLiquidity() {
s.assertCurr1To0(math.MinInt64)
}

func (s *DexTestSuite) TestSwapZeroInFailsLowTick() {
// GIVEN liquidity tokenA at tick -20,002
s.addDeposit(NewDeposit(10000, 0, -20_000, 2))

// WHEN swap 7 tokenB
_, _, _, err := s.swap("TokenB", "TokenA", sdkmath.NewInt(7))

Check failure on line 48 in x/dex/keeper/liquidity_test.go

View workflow job for this annotation

GitHub Actions / lint

declaration has 3 blank identifiers (dogsled)

// THEN swap would give an InAmount and outAmount of 0 and fail
// Floor(7 * 1.0001^-20,000) = 0
s.ErrorIs(err, types.ErrSwapAmountTooSmall)
}

func (s *DexTestSuite) TestSwapUnfairPriceFailsLowTick() {
// GIVEN liquidity tokenA at tick -20,002
s.addDeposit(NewDeposit(10000, 0, -20_000, 2))

// WHEN swap 8 tokenB
_, _, _, err := s.swap("TokenB", "TokenA", sdkmath.NewInt(8))

Check failure on line 60 in x/dex/keeper/liquidity_test.go

View workflow job for this annotation

GitHub Actions / lint

declaration has 3 blank identifiers (dogsled)

// THEN swap fails because maker is selling at an unfair true price
// AmountOut = Floor(8 * 1.0001^-20,000) = 1
// AmountIn = Floor(1 * 1.0001^20000) = 7
// TruePrice = AmountOut/AmountIn = 1/7 = .142857
// BookPrice = 0.135348817 (thus maker is getting a price 5.5% worse than expected)
s.ErrorIs(err, types.ErrSwapAmountTooSmall)
}

func (s *DexTestSuite) TestSwapUnfairPriceFailsHighTick() {
// GIVEN liquidity tokenA at tick 159,680
s.addDeposit(NewDeposit(2000, 0, 159681, 1))

// WHEN swap 8 tokenB
_, _, _, err := s.swap("TokenB", "TokenA", sdk.NewInt(200))

Check failure on line 75 in x/dex/keeper/liquidity_test.go

View workflow job for this annotation

GitHub Actions / lint

declaration has 3 blank identifiers (dogsled)

// THEN swap fails because maker is selling at an unfair true price
// AmountOut = Floor(200 * 1.0001^159,680) = 1,719,877,698
// AmountIn = Floor(1,719,877,697 * 1.0001^-159,680) = 199
// TruePrice = AmountOut/AmountIn = 1,719,877,698/199 = 8,642,601
// BookPrice = 8,599,388 (thus maker is getting a price .502% worse than expected)
s.ErrorIs(err, types.ErrSwapAmountTooSmall)
}

func (s *DexTestSuite) TestSwapUnfairPriceAbortEarly() {
// GIVEN liquidity tokenA at tick 159,681 and 159,680
s.addDeposits(
NewDeposit(2580, 0, 159682, 1),
NewDeposit(2000, 0, 159681, 1),
)

// WHEN swap 700 BIgTokenB
tokenIn, tokenOut, orderFilled, err := s.swap("TokenB", "TokenA", sdk.NewInt(499))

// THEN swap works on the first tick, but aborts on the second tick because of Unfair price condition
s.NoError(err)
s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(299), tokenOut, sdkmath.NewInt(2580000000))
s.False(orderFilled)
s.assertTickBalancesInt(sdkmath.NewInt(2_000_000_000), sdkmath.NewInt(299))
}

// swaps against LPs only /////////////////////////////////////////////////////

func (s *DexTestSuite) TestSwap0To1PartialFillLP() {
// GIVEN 10 tokenB LP @ tick 0 fee 1
s.addDeposit(NewDeposit(0, 10, 0, 1))

// WHEN swap 20 of tokenA
tokenIn, tokenOut := s.swap("TokenA", "TokenB", 20)
tokenIn, tokenOut := s.swapSuccess("TokenA", "TokenB", 20)

// THEN swap should return ~10 BIGTokenA in and 10 BIGTokenB out
s.Assert().Equal("TokenA", tokenIn.Denom)
Expand All @@ -64,7 +123,7 @@ func (s *DexTestSuite) TestSwap1To0PartialFillLP() {
s.addDeposit(NewDeposit(10, 0, 0, 1))

// WHEN swap 20 of tokenB
tokenIn, tokenOut := s.swap("TokenB", "TokenA", 20)
tokenIn, tokenOut := s.swapSuccess("TokenB", "TokenA", 20)

// THEN swap should return ~10 BIGTokenB in and 10 BIGTokenA out
s.Assert().Equal("TokenB", tokenIn.Denom)
Expand All @@ -81,7 +140,7 @@ func (s *DexTestSuite) TestSwap0To1FillLP() {
s.addDeposit(NewDeposit(0, 100, 200, 5))

// WHEN swap 100 of tokenA
tokenIn, tokenOut := s.swap("TokenA", "TokenB", 100)
tokenIn, tokenOut := s.swapSuccess("TokenA", "TokenB", 100)

// THEN swap should return 100 BIGTokenA in and ~98 BIGTokenB out
s.Assert().Equal("TokenA", tokenIn.Denom)
Expand All @@ -98,7 +157,7 @@ func (s *DexTestSuite) TestSwap1To0FillLP() {
s.addDeposit(NewDeposit(100, 0, -20_000, 1))

// WHEN swap 100 of tokenB
tokenIn, tokenOut := s.swap("TokenB", "TokenA", 100)
tokenIn, tokenOut := s.swapSuccess("TokenB", "TokenA", 100)

// THEN swap should return ~99 BIGTokenB in and ~14 BIGTokenA out
s.Assert().Equal("TokenB", tokenIn.Denom)
Expand All @@ -116,7 +175,7 @@ func (s *DexTestSuite) TestSwap0To1FillLPHighFee() {
s.addDeposit(NewDeposit(0, 100, 20_000, 1_000))

// WHEN swap 100 of tokenA
tokenIn, tokenOut := s.swap("TokenA", "TokenB", 100)
tokenIn, tokenOut := s.swapSuccess("TokenA", "TokenB", 100)

// THEN swap should return ~99 BIGTokenA in and ~12 BIGTokenB out
s.Assert().Equal("TokenA", tokenIn.Denom)
Expand All @@ -133,7 +192,7 @@ func (s *DexTestSuite) TestSwap1To0FillLPHighFee() {
s.addDeposit(NewDeposit(1000, 0, 20_000, 1000))

// WHEN swap 100 of tokenB
tokenIn, tokenOut := s.swap("TokenB", "TokenA", 100)
tokenIn, tokenOut := s.swapSuccess("TokenB", "TokenA", 100)

// THEN swap should return 100 BIGTokenB in and ~668 BIGTokenA out
s.Assert().Equal("TokenB", tokenIn.Denom)
Expand All @@ -154,7 +213,7 @@ func (s *DexTestSuite) TestSwap0To1PartialFillMultipleLP() {
)

// WHEN swap 100 of tokenA
tokenIn, tokenOut := s.swap("TokenA", "TokenB", 100)
tokenIn, tokenOut := s.swapSuccess("TokenA", "TokenB", 100)

// THEN swap should return ~40 BIGTokenA in and 300 TokenB out
s.Assert().Equal("TokenA", tokenIn.Denom)
Expand All @@ -175,7 +234,7 @@ func (s *DexTestSuite) TestSwap1To0PartialFillMultipleLP() {
)

// WHEN swap 100 of tokenB
tokenIn, tokenOut := s.swap("TokenB", "TokenA", 100)
tokenIn, tokenOut := s.swapSuccess("TokenB", "TokenA", 100)

// THEN swap should return ~41 BIGTokenB in and 300 BIGTokenA out
s.Assert().Equal("TokenB", tokenIn.Denom)
Expand All @@ -197,7 +256,7 @@ func (s *DexTestSuite) TestSwap0To1FillMultipleLP() {
)

// WHEN swap 100 of tokenA
tokenIn, tokenOut := s.swap("TokenA", "TokenB", 400)
tokenIn, tokenOut := s.swapSuccess("TokenA", "TokenB", 400)

// THEN swap should return ~399 BIGTokenA in and 400 BIGTokenB out
s.Assert().Equal("TokenA", tokenIn.Denom)
Expand All @@ -219,7 +278,7 @@ func (s *DexTestSuite) TestSwap1To0FillMultipleLP() {
)

// WHEN swap 400 of tokenB
tokenIn, tokenOut := s.swap("TokenB", "TokenA", 400)
tokenIn, tokenOut := s.swapSuccess("TokenB", "TokenA", 400)

// THEN swap should return 400 BIGTokenB in and ~400 BIGTokenA out
s.Assert().Equal("TokenB", tokenIn.Denom)
Expand Down Expand Up @@ -340,7 +399,7 @@ func (s *DexTestSuite) TestSwap0To1PartialFillLO() {
s.placeGTCLimitOrder("TokenB", 10, 1_000)

// WHEN swap 20 of tokenA
tokenIn, tokenOut := s.swap("TokenA", "TokenB", 20)
tokenIn, tokenOut := s.swapSuccess("TokenA", "TokenB", 20)

// THEN swap should return ~11 BIGTokenA in and 10 BIGTokenB out
s.Assert().Equal("TokenA", tokenIn.Denom)
Expand All @@ -354,7 +413,7 @@ func (s *DexTestSuite) TestSwap1To0PartialFillLO() {
s.placeGTCLimitOrder("TokenA", 10, -1_000)

// WHEN swap 20 of tokenB
tokenIn, tokenOut := s.swap("TokenB", "TokenA", 20)
tokenIn, tokenOut := s.swapSuccess("TokenB", "TokenA", 20)

// THEN swap should return ~11 BIGTokenB in and 10 BIGTokenA out
s.Assert().Equal("TokenB", tokenIn.Denom)
Expand All @@ -371,7 +430,7 @@ func (s *DexTestSuite) TestSwap0To1FillLO() {
s.placeGTCLimitOrder("TokenB", 100, 10_000)

// WHEN swap 100 of tokenA
tokenIn, tokenOut := s.swap("TokenA", "TokenB", 100)
tokenIn, tokenOut := s.swapSuccess("TokenA", "TokenB", 100)

// THEN swap should return ~99 BIGTokenA in and ~37 BIGTokenB out
s.Assert().Equal("TokenA", tokenIn.Denom)
Expand All @@ -388,7 +447,7 @@ func (s *DexTestSuite) TestSwap1To0FillLO() {
s.placeGTCLimitOrder("TokenA", 100, -10_000)

// WHEN swap 10 of tokenB
tokenIn, tokenOut := s.swap("TokenB", "TokenA", 10)
tokenIn, tokenOut := s.swapSuccess("TokenB", "TokenA", 10)

// THEN swap should return 10 BIGTokenB in and ~4 BIGTokenA out
s.Assert().Equal("TokenB", tokenIn.Denom)
Expand All @@ -407,7 +466,7 @@ func (s *DexTestSuite) TestSwap0To1FillMultipleLO() {
s.placeGTCLimitOrder("TokenB", 100, 1_002)

// WHEN swap 300 of tokenA
tokenIn, tokenOut := s.swap("TokenA", "TokenB", 300)
tokenIn, tokenOut := s.swapSuccess("TokenA", "TokenB", 300)

// THEN swap should return 300 BIGTokenA in and ~271 BIGTokenB out
s.Assert().Equal("TokenA", tokenIn.Denom)
Expand All @@ -426,7 +485,7 @@ func (s *DexTestSuite) TestSwap1To0FillMultipleLO() {
s.placeGTCLimitOrder("TokenA", 100, -1_002)

// WHEN swap 300 of tokenB
tokenIn, tokenOut := s.swap("TokenB", "TokenA", 300)
tokenIn, tokenOut := s.swapSuccess("TokenB", "TokenA", 300)

// THEN swap should return 300 BIGTokenB in and ~271 BIGTokenB out
s.Assert().Equal("TokenB", tokenIn.Denom)
Expand Down Expand Up @@ -586,17 +645,25 @@ func (s *DexTestSuite) placeGTCLimitOrder(
func (s *DexTestSuite) swap(
tokenIn string,
tokenOut string,
maxAmountIn int64,
) (coinIn, coinOut sdk.Coin) {
maxAmountIn sdkmath.Int,
) (coinIn, coinOut sdk.Coin, filled bool, err error) {
tradePairID, err := types.NewTradePairID(tokenIn, tokenOut)
s.Assert().NoError(err)
coinIn, coinOut, _, err = s.App.DexKeeper.Swap(
return s.App.DexKeeper.Swap(
s.Ctx,
tradePairID,
sdkmath.NewInt(maxAmountIn).Mul(denomMultiple),
maxAmountIn,
nil,
nil,
)
}

func (s *DexTestSuite) swapSuccess(
tokenIn string,
tokenOut string,
maxAmountIn int64,
) (coinIn, coinOut sdk.Coin) {
coinIn, coinOut, _, err := s.swap(tokenIn, tokenOut, sdk.NewInt(maxAmountIn).Mul(denomMultiple))
s.Assert().NoError(err)
return coinIn, coinOut
}
Expand Down Expand Up @@ -689,9 +756,6 @@ func (s *DexTestSuite) assertTickBalancesInt(expectedABalance, expectedBBalance
}

func (s *DexTestSuite) assertTickBalances(expectedABalance, expectedBBalance int64) {
// NOTE: We can't just check the actual DEX bank balances since we are testing swap
// before any transfers take place. Instead we have to sum up the total amount of coins
// at each tick
expectedAInt := sdkmath.NewInt(expectedABalance).Mul(denomMultiple)
expectedBInt := sdkmath.NewInt(expectedBBalance).Mul(denomMultiple)
s.assertTickBalancesInt(expectedAInt, expectedBInt)
Expand Down
15 changes: 12 additions & 3 deletions x/dex/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,10 +394,10 @@ func (s *DexTestSuite) limitSellsWithMaxOut(
return msg.TrancheKey
}

func (s *DexTestSuite) limitSells(
func (s *DexTestSuite) limitSellsInt(
account sdk.AccAddress,
tokenIn string,
tickIndexNormalized, amountIn int,
tickIndexNormalized int, amountIn sdkmath.Int,
orderTypeOpt ...types.LimitOrderType,
) (string, error) {
var orderType types.LimitOrderType
Expand All @@ -415,13 +415,22 @@ func (s *DexTestSuite) limitSells(
TokenIn: tradePairID.TakerDenom,
TokenOut: tradePairID.MakerDenom,
TickIndexInToOut: tickIndexTakerToMaker,
AmountIn: sdkmath.NewInt(int64(amountIn)).Mul(denomMultiple),
AmountIn: amountIn,
OrderType: orderType,
})

return msg.TrancheKey, err
}

func (s *DexTestSuite) limitSells(
account sdk.AccAddress,
tokenIn string,
tickIndexNormalized, amountIn int,
orderTypeOpt ...types.LimitOrderType,
) (string, error) {
return s.limitSellsInt(account, tokenIn, tickIndexNormalized, sdkmath.NewInt(int64(amountIn)).Mul(denomMultiple), orderTypeOpt...)
}

func (s *DexTestSuite) limitSellsGoodTil(
account sdk.AccAddress,
tokenIn string,
Expand Down

0 comments on commit 893476b

Please sign in to comment.