diff --git a/proto/neutron/dex/params.proto b/proto/neutron/dex/params.proto index 70c65c293..8e7bfe2c6 100644 --- a/proto/neutron/dex/params.proto +++ b/proto/neutron/dex/params.proto @@ -8,5 +8,12 @@ option go_package = "github.com/neutron-org/neutron/x/dex/types"; // Params defines the parameters for the module. message Params { option (gogoproto.goproto_stringer) = false; - repeated uint64 fee_tiers = 1; + repeated uint64 fee_tiers = 1; + string max_true_taker_spread = 2 [ + (gogoproto.moretags) = "yaml:\"max_true_taker_spread\"", + (gogoproto.customtype) = "github.com/neutron-org/neutron/utils/math.PrecDec", + (gogoproto.nullable) = false, + (gogoproto.jsontag) = "max_true_taker_spread" + ]; + } diff --git a/tests/ibc/gmp_swap_forward_test.go b/tests/ibc/gmp_swap_forward_test.go index 8199afe7c..652e4f211 100644 --- a/tests/ibc/gmp_swap_forward_test.go +++ b/tests/ibc/gmp_swap_forward_test.go @@ -99,10 +99,11 @@ func (s *IBCTestSuite) TestGMPSwapAndForward_Success() { // Check that the funds are moved out of the acc on providerChain s.assertProviderBalance(s.providerAddr, nativeDenom, newProviderBalNative.Sub(ibcTransferAmount)) - // Check that the amountIn is deduced from the neutron account - s.assertNeutronBalance(s.neutronAddr, s.providerToNeutronDenom, math.ZeroInt()) - // Check that neutron account did not keep any of the transfer denom - s.assertNeutronBalance(s.neutronAddr, nativeDenom, genesisWalletAmount.Sub(swapAmount)) + // Check that the amountIn is deducted from the neutron override account + overrideAddr := s.ReceiverOverrideAddr(s.neutronTransferPath.EndpointA.ChannelID, s.providerAddr.String()) + s.assertNeutronBalance(overrideAddr, s.providerToNeutronDenom, math.OneInt()) + // Check that neutron override account did not keep any of the transfer denom + s.assertNeutronBalance(overrideAddr, nativeDenom, math.ZeroInt()) transferDenomPath := transfertypes.GetPrefixedDenom( transfertypes.PortID, diff --git a/tests/ibc/swap_forward_test.go b/tests/ibc/swap_forward_test.go index 41f643de6..070378e10 100644 --- a/tests/ibc/swap_forward_test.go +++ b/tests/ibc/swap_forward_test.go @@ -109,10 +109,11 @@ func (s *IBCTestSuite) TestSwapAndForward_Success() { newProviderBalNative.Sub(ibcTransferAmount), ) - // Check that the amountIn is deducted from the neutron account - s.assertNeutronBalance(s.neutronAddr, s.providerToNeutronDenom, math.ZeroInt()) + // Check that the amountIn is deducted from the neutron overrid receiver account + overrideAddr := s.ReceiverOverrideAddr(s.neutronTransferPath.EndpointA.ChannelID, s.providerAddr.String()) + s.assertNeutronBalance(overrideAddr, s.providerToNeutronDenom, math.OneInt()) // Check that neutron account did not keep any of the transfer denom - s.assertNeutronBalance(s.neutronAddr, nativeDenom, genesisWalletAmount.Sub(swapAmount)) + s.assertNeutronBalance(overrideAddr, nativeDenom, math.ZeroInt()) transferDenomPath := transfertypes.GetPrefixedDenom( transfertypes.PortID, @@ -284,7 +285,7 @@ func (s *IBCTestSuite) TestSwapAndForward_UnwindIBCDenomSuccess() { s.providerToNeutronDenom, depositAmount, depositAmount, - 0, + 1, 1, s.neutronAddr) @@ -293,8 +294,8 @@ func (s *IBCTestSuite) TestSwapAndForward_UnwindIBCDenomSuccess() { postDepositNeutronBalNative := genesisWalletAmount.Sub(depositAmount) s.assertNeutronBalance(s.neutronAddr, nativeDenom, postDepositNeutronBalNative) - swapAmount := math.NewInt(100000) - expectedAmountOut := math.NewInt(99990) + swapAmount := math.NewInt(100_000) + expectedAmountOut := math.NewInt(99980) retries := uint8(0) @@ -357,7 +358,7 @@ func (s *IBCTestSuite) TestSwapAndForward_UnwindIBCDenomSuccess() { s.providerAddr, s.neutronAddr, transferDenomNeutronProvider, - ibcTransferAmount, + swapAmount, string(metadataBz), ) @@ -366,6 +367,9 @@ func (s *IBCTestSuite) TestSwapAndForward_UnwindIBCDenomSuccess() { s.Assert().NoError(err) s.coordinator.CommitBlock(s.neutronChain) + // Check that the amountIn is deducted from the neutron override receiever account + overrideAddr := s.ReceiverOverrideAddr(s.neutronTransferPath.EndpointA.ChannelID, s.providerAddr.String()) + s.assertNeutronBalance(overrideAddr, nativeDenom, math.OneInt()) // Check that the funds are now present on the provider chainer s.assertProviderBalance( s.providerAddr, @@ -589,6 +593,16 @@ func (s *IBCTestSuite) TestSwapAndForward_ForwardFails() { s.assertNeutronBalance(overrideAddr, s.providerToNeutronDenom, math.ZeroInt()) s.assertNeutronBalance(overrideAddr, nativeDenom, math.ZeroInt()) + // Check that nothing made it to chainB + transferDenomPath := transfertypes.GetPrefixedDenom( + transfertypes.PortID, + s.neutronChainBPath.EndpointA.ChannelID, + nativeDenom, + ) + transferDenomNeutronChainB := transfertypes.ParseDenomTrace(transferDenomPath).IBCDenom() + + s.assertChainBBalance(chainBAddr, transferDenomNeutronChainB, math.ZeroInt()) + // Check that the refund takes place and the funds are moved back to the account on Gaia s.assertProviderBalance(s.providerAddr, nativeDenom, genesisWalletAmount.Sub(depositAmount)) } diff --git a/tests/ibc/swap_test.go b/tests/ibc/swap_test.go index 8c7ac2432..44e4c2be4 100644 --- a/tests/ibc/swap_test.go +++ b/tests/ibc/swap_test.go @@ -83,7 +83,8 @@ func (s *IBCTestSuite) TestIBCSwapMiddleware_Success() { s.assertNeutronBalance(s.neutronAddr, nativeDenom, postDepositNeutronBalNative.Add(expectedOut)) // Check that all of the IBC transfer denom have been used up - s.assertNeutronBalance(s.neutronAddr, s.providerToNeutronDenom, math.ZeroInt()) + overrideAddr := s.ReceiverOverrideAddr(s.neutronTransferPath.EndpointA.ChannelID, s.providerAddr.String()) + s.assertNeutronBalance(overrideAddr, s.providerToNeutronDenom, math.OneInt()) } // TestIBCSwapMiddleware_FailRefund asserts that the IBC swap middleware works as intended with Neutron running as a diff --git a/x/dex/keeper/integration_deposit_doublesided_test.go b/x/dex/keeper/integration_deposit_doublesided_test.go index 951ffb004..35c9e3020 100644 --- a/x/dex/keeper/integration_deposit_doublesided_test.go +++ b/x/dex/keeper/integration_deposit_doublesided_test.go @@ -232,19 +232,19 @@ func (s *DexTestSuite) TestDepositValueAccural() { s.bobLimitSells("TokenA", 10, 1000, types.LimitOrderType_IMMEDIATE_OR_CANCEL) } } - s.assertLiquidityAtTickInt(math.NewInt(110516593), math.ZeroInt(), 0, 10) - s.assertDexBalancesInt(math.NewInt(110516593), math.ZeroInt()) + s.assertLiquidityAtTickInt(math.NewInt(110516491), math.ZeroInt(), 0, 10) + s.assertDexBalancesInt(math.NewInt(110516491), math.ZeroInt()) - s.assertLiquidityAtTickInt(math.NewInt(110516593), math.ZeroInt(), 0, 10) + s.assertLiquidityAtTickInt(math.NewInt(110516491), math.ZeroInt(), 0, 10) // Carol deposits 100TokenA @tick0 s.carolDeposits(NewDeposit(100, 0, 0, 10)) - s.assertLiquidityAtTickInt(math.NewInt(210516593), math.ZeroInt(), 0, 10) - s.assertAccountSharesInt(s.carol, 0, 10, math.NewInt(90484150)) + s.assertLiquidityAtTickInt(math.NewInt(210516491), math.ZeroInt(), 0, 10) + s.assertAccountSharesInt(s.carol, 0, 10, math.NewInt(90484233)) // Alice gets back 100% of the accrued trade value s.aliceWithdraws(NewWithdrawal(100, 0, 10)) - s.assertAliceBalancesInt(math.NewInt(110516593), math.NewInt(0)) + s.assertAliceBalancesInt(math.NewInt(110516491), math.NewInt(0)) // AND carol get's back only what she put in - s.carolWithdraws(NewWithdrawalInt(math.NewInt(90484150), 0, 10)) + s.carolWithdraws(NewWithdrawalInt(math.NewInt(90484233), 0, 10)) s.assertCarolBalances(100, 1) } diff --git a/x/dex/keeper/integration_deposit_singlesided_test.go b/x/dex/keeper/integration_deposit_singlesided_test.go index 661747407..cdf8ca9f9 100644 --- a/x/dex/keeper/integration_deposit_singlesided_test.go +++ b/x/dex/keeper/integration_deposit_singlesided_test.go @@ -277,7 +277,7 @@ func (s *DexTestSuite) TestDepositSingleSidedCreatingArbToken0() { // Bob arbs s.bobLimitSells("TokenB", -1, 50, types.LimitOrderType_IMMEDIATE_OR_CANCEL) s.bobLimitSells("TokenA", 1, 10) - s.assertBobBalancesInt(sdkmath.NewInt(50_000_000), sdkmath.NewInt(53_294_995)) + s.assertBobBalancesInt(sdkmath.NewInt(50_000_000), sdkmath.NewInt(53_294_996)) } func (s *DexTestSuite) TestDepositSingleSidedCreatingArbToken1() { @@ -302,7 +302,7 @@ func (s *DexTestSuite) TestDepositSingleSidedCreatingArbToken1() { // Bob arbs s.bobLimitSells("TokenA", -1, 50, types.LimitOrderType_IMMEDIATE_OR_CANCEL) s.bobLimitSells("TokenB", -1, 10) - s.assertBobBalancesInt(sdkmath.NewInt(53_295_665), sdkmath.NewInt(50_000_000)) + s.assertBobBalancesInt(sdkmath.NewInt(53_295_666), sdkmath.NewInt(50_000_000)) } func (s *DexTestSuite) TestDepositSingleSidedMultiA() { diff --git a/x/dex/keeper/integration_multihopswap_test.go b/x/dex/keeper/integration_multihopswap_test.go index 776b13e7f..4ce918b03 100644 --- a/x/dex/keeper/integration_multihopswap_test.go +++ b/x/dex/keeper/integration_multihopswap_test.go @@ -50,9 +50,9 @@ func (s *DexTestSuite) TestMultiHopSwapSingleRoute() { // GIVEN liquidity in pools A<>B, B<>C, C<>D, s.SetupMultiplePools( - NewPoolSetup("TokenA", "TokenB", 0, 100, 0, 1), - NewPoolSetup("TokenB", "TokenC", 0, 100, 0, 1), - NewPoolSetup("TokenC", "TokenD", 0, 100, 0, 1), + NewPoolSetup("TokenA", "TokenB", 0, 100, -1, 1), + NewPoolSetup("TokenB", "TokenC", 0, 100, -1, 1), + NewPoolSetup("TokenC", "TokenD", 0, 100, -1, 1), ) // WHEN alice multihopswaps A<>B => B<>C => C<>D, @@ -61,12 +61,12 @@ func (s *DexTestSuite) TestMultiHopSwapSingleRoute() { // THEN alice gets out 99 TokenD s.assertAccountBalanceWithDenom(s.alice, "TokenA", 0) - s.assertAccountBalanceWithDenomInt(s.alice, "TokenD", math.NewInt(99_970_003)) + s.assertAccountBalanceWithDenom(s.alice, "TokenD", 100) s.assertDexBalanceWithDenom("TokenA", 100) s.assertDexBalanceWithDenom("TokenB", 100) s.assertDexBalanceWithDenom("TokenC", 100) - s.assertDexBalanceWithDenomInt("TokenD", math.NewInt(29_997)) + s.assertDexBalanceWithDenom("TokenD", 0) } func (s *DexTestSuite) TestMultiHopSwapInsufficientLiquiditySingleRoute() { @@ -116,15 +116,15 @@ func (s *DexTestSuite) TestMultiHopSwapMultiRouteOneGood() { // GIVEN viable liquidity in pools A<>B, B<>E, E<>X s.SetupMultiplePools( - NewPoolSetup("TokenA", "TokenB", 0, 100, 0, 1), + NewPoolSetup("TokenA", "TokenB", 0, 100, -1, 1), NewPoolSetup("TokenB", "TokenC", 0, 100, 0, 1), NewPoolSetup("TokenC", "TokenX", 0, 50, 0, 1), NewPoolSetup("TokenC", "TokenX", 0, 50, 2200, 1), NewPoolSetup("TokenB", "TokenD", 0, 100, 0, 1), NewPoolSetup("TokenD", "TokenX", 0, 50, 0, 1), NewPoolSetup("TokenD", "TokenX", 0, 50, 2200, 1), - NewPoolSetup("TokenB", "TokenE", 0, 100, 0, 1), - NewPoolSetup("TokenE", "TokenX", 0, 100, 0, 1), + NewPoolSetup("TokenB", "TokenE", 0, 100, -1, 1), + NewPoolSetup("TokenE", "TokenX", 0, 100, -1, 1), ) // WHEN alice multihopswaps with three routes the first two routes fail and the third works @@ -137,26 +137,26 @@ func (s *DexTestSuite) TestMultiHopSwapMultiRouteOneGood() { // THEN swap succeeds through route A<>B, B<>E, E<>X s.assertAccountBalanceWithDenom(s.alice, "TokenA", 0) - s.assertAccountBalanceWithDenomInt(s.alice, "TokenX", math.NewInt(99_970_003)) - s.assertLiquidityAtTickWithDenomInt( + s.assertAccountBalanceWithDenomInt(s.alice, "TokenX", math.NewInt(100_000_000)) + s.assertLiquidityAtTickWithDenom( &types.PairID{Token0: "TokenA", Token1: "TokenB"}, - math.NewInt(99_999_999), - math.NewInt(10_000), + 100, 0, + -1, 1, ) - s.assertLiquidityAtTickWithDenomInt( + s.assertLiquidityAtTickWithDenom( &types.PairID{Token0: "TokenB", Token1: "TokenE"}, - math.NewInt(99_990_000), - math.NewInt(19_999), + 100, 0, + -1, 1, ) - s.assertLiquidityAtTickWithDenomInt( + s.assertLiquidityAtTickWithDenom( &types.PairID{Token0: "TokenE", Token1: "TokenX"}, - math.NewInt(99_980_001), - math.NewInt(29_997), + 100, 0, + -1, 1, ) @@ -276,14 +276,14 @@ func (s *DexTestSuite) TestMultiHopSwapMultiRouteFindBestRoute() { s.assertAccountBalanceWithDenomInt(s.alice, "TokenX", math.NewInt(134_943_366)) s.assertLiquidityAtTickWithDenomInt( &types.PairID{Token0: "TokenA", Token1: "TokenB"}, - math.NewInt(99_999_999), + math.NewInt(99_999_998), math.NewInt(10000), 0, 1, ) s.assertLiquidityAtTickWithDenomInt( &types.PairID{Token0: "TokenB", Token1: "TokenE"}, - math.NewInt(99_990_000), + math.NewInt(99_989_999), math.NewInt(19_999), 0, 1, @@ -291,7 +291,7 @@ func (s *DexTestSuite) TestMultiHopSwapMultiRouteFindBestRoute() { s.assertLiquidityAtTickWithDenomInt( &types.PairID{Token0: "TokenE", Token1: "TokenX"}, - math.NewInt(99_980_001), + math.NewInt(99_980_000), math.NewInt(865_056_634), -3000, 1, @@ -369,7 +369,7 @@ func (s *DexTestSuite) TestMultiHopSwapLongRouteWithCache() { s.assertAccountBalanceWithDenomInt(s.alice, "TokenX", math.NewInt(99_880_066)) s.assertLiquidityAtTickWithDenomInt( &types.PairID{Token0: "TokenM", Token1: "TokenX"}, - math.NewInt(99_890_055), + math.NewInt(99_890_054), math.NewInt(119_934), 0, 1, diff --git a/x/dex/keeper/integration_placelimitorder_test.go b/x/dex/keeper/integration_placelimitorder_test.go index 0fc08221f..40c2760c5 100644 --- a/x/dex/keeper/integration_placelimitorder_test.go +++ b/x/dex/keeper/integration_placelimitorder_test.go @@ -389,7 +389,7 @@ func (s *DexTestSuite) TestPlaceLimitOrderFoK0TotalAmountInNotUsed() { // WHEN alice submits FoK limitOrder for 9998 it succeeds // even though trueAmountIn < specifiedAmountIn due to rounding s.aliceLimitSells("TokenA", 21000, 9998, types.LimitOrderType_FILL_OR_KILL) - s.assertAliceBalancesInt(sdkmath.NewInt(5), sdkmath.NewInt(1_353_046_854)) + s.assertAliceBalancesInt(sdkmath.NewInt(7), sdkmath.NewInt(1_353_046_854)) } func (s *DexTestSuite) TestPlaceLimitOrderFoK1TotalAmountInNotUsed() { @@ -403,7 +403,7 @@ func (s *DexTestSuite) TestPlaceLimitOrderFoK1TotalAmountInNotUsed() { // WHEN alice submits FoK limitOrder for 9998 it succeeds // even though trueAmountIn < specifiedAmountIn due to rounding s.aliceLimitSells("TokenB", -21000, 9998, types.LimitOrderType_FILL_OR_KILL) - s.assertAliceBalancesInt(sdkmath.NewInt(135_3046_854), sdkmath.NewInt(5)) + s.assertAliceBalancesInt(sdkmath.NewInt(135_3046_854), sdkmath.NewInt(7)) } func (s *DexTestSuite) TestPlaceLimitOrderFoKMaxOutUsed() { @@ -417,7 +417,7 @@ func (s *DexTestSuite) TestPlaceLimitOrderFoKMaxOutUsed() { s.aliceLimitSellsWithMaxOut("TokenB", 0, 50, 20) // THEN alice swap ~19 BIGTokenB and gets back 20 BIGTokenA - s.assertAliceBalancesInt(sdkmath.NewInt(20_000_000), sdkmath.NewInt(31_162_769)) + s.assertAliceBalancesInt(sdkmath.NewInt(20_000_000), sdkmath.NewInt(31_162_770)) } func (s *DexTestSuite) TestPlaceLimitOrderFoKMaxOutUsedMultiTick() { @@ -433,7 +433,7 @@ func (s *DexTestSuite) TestPlaceLimitOrderFoKMaxOutUsedMultiTick() { s.aliceLimitSellsWithMaxOut("TokenB", 0, 50, 20) // THEN alice swap ~19 BIGTokenB and gets back 20 BIGTokenA - s.assertAliceBalancesInt(sdkmath.NewInt(20_000_000), sdkmath.NewInt(31_165_594)) + s.assertAliceBalancesInt(sdkmath.NewInt(20_000_000), sdkmath.NewInt(31_165_596)) } // Immediate Or Cancel LimitOrders //////////////////////////////////////////////////////////////////// @@ -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() { @@ -542,7 +579,7 @@ func (s *DexTestSuite) TestPlaceLimitOrderJITBehindEnemyLines() { s.assertLimitLiquidityAtTick("TokenA", 1, 0) // Alice can withdraw ~10 BIGTokenB s.aliceWithdrawsLimitSell(trancheKey) - s.assertAliceBalancesInt(sdkmath.ZeroInt(), sdkmath.NewInt(9999000)) + s.assertAliceBalancesInt(sdkmath.ZeroInt(), sdkmath.NewInt(9998999)) } func (s *DexTestSuite) TestPlaceLimitOrderJITNextBlock() { diff --git a/x/dex/keeper/liquidity.go b/x/dex/keeper/liquidity.go index 1b86c504b..c50b68e00 100644 --- a/x/dex/keeper/liquidity.go +++ b/x/dex/keeper/liquidity.go @@ -15,6 +15,7 @@ func (k Keeper) Swap( maxAmountMakerDenom *math.Int, limitPrice *math_utils.PrecDec, ) (totalTakerCoin, totalMakerCoin sdk.Coin, orderFilled bool, err error) { + params := k.GetParams(ctx) useMaxOut := maxAmountMakerDenom != nil var remainingMakerDenom *math.Int if useMaxOut { @@ -41,6 +42,21 @@ func (k Keeper) Swap( } inAmount, outAmount := liq.Swap(remainingTakerDenom, remainingMakerDenom) + + // If (due to rounding) the actual price given to the maker is demonstrably unfair + // we do not save the results of the swap and we exit. + // While the decrease in price quality for the maker is semi-linear with the amount + // being swapped, it is possible that the next swap could yield a "fair" price. + // Nonethless, once the remainingTakerDenom gets small enough to start causing unfair swaps + // it is much simpler to just abort. + if inAmount.IsZero() || isUnfairTruePrice(params.MaxTrueTakerSpread, inAmount, outAmount, liq) { + // If they've already swapped just end the swap + if remainingTakerDenom.LT(maxAmountTakerDenom) { + break + } + // If they have not swapped anything return informative error + return sdk.Coin{}, sdk.Coin{}, false, types.ErrSwapAmountTooSmall + } k.SaveLiquidity(ctx, liq) remainingTakerDenom = remainingTakerDenom.Sub(inAmount) @@ -49,7 +65,12 @@ func (k Keeper) Swap( // break if remainingTakerDenom will yield less than 1 tokenOut at current price // this avoids unnecessary iteration since outAmount will always be 0 going forward // this also catches the normal exit case where remainingTakerDenom == 0 - if liq.Price().MulInt(remainingTakerDenom).LT(math_utils.OnePrecDec()) { + + // NOTE: In theory this check should be: price * remainingTakerDenom < 1 + // but due to rounding and inaccuracy of fixed decimal math, it is possible + // for liq.swap to use the full the amount of taker liquidity and have a leftover + // amount amount of the taker Denom > than 1 token worth of maker denom + if liq.Price().MulInt(remainingTakerDenom).LT(math_utils.NewPrecDec(2)) { orderFilled = true break } @@ -108,3 +129,16 @@ func (k Keeper) SaveLiquidity(sdkCtx sdk.Context, liquidityI types.Liquidity) { panic("Invalid liquidity type") } } + +func isUnfairTruePrice( + maxTrueTakerSpread math_utils.PrecDec, + inAmount, outAmount math.Int, + liq types.Liquidity, +) bool { + bookPrice := liq.Price() + truePrice := math_utils.NewPrecDecFromInt(outAmount).QuoInt(inAmount) + priceDiffFromExpected := truePrice.Sub(bookPrice) + pctDiff := priceDiffFromExpected.Quo(bookPrice) + + return pctDiff.GT(maxTrueTakerSpread) +} diff --git a/x/dex/keeper/liquidity_test.go b/x/dex/keeper/liquidity_test.go index 15f176b0a..ac194e231 100644 --- a/x/dex/keeper/liquidity_test.go +++ b/x/dex/keeper/liquidity_test.go @@ -17,7 +17,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)) @@ -32,7 +32,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)) @@ -41,6 +41,62 @@ 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 + + // THEN swap would give an InAmount and outAmount of 0 and fail + // Floor(7 * 1.0001^-20,000) = 0 + s.swapIntFails(types.ErrSwapAmountTooSmall, "TokenB", "TokenA", sdkmath.NewInt(7)) +} + +func (s *DexTestSuite) TestSwapUnfairPriceFailsLowTick() { + // GIVEN liquidity tokenA at tick -20,002 + s.addDeposit(NewDeposit(10000, 0, -20_000, 2)) + + // WHEN swap 8 tokenB + + // 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.swapIntFails(types.ErrSwapAmountTooSmall, "TokenB", "TokenA", sdkmath.NewInt(8)) +} + +func (s *DexTestSuite) TestSwapUnfairPriceFailsHighTick() { + // GIVEN liquidity tokenA at tick 159,680 + s.addDeposit(NewDeposit(2000, 0, 159681, 1)) + + // WHEN swap 200 tokenB + + // 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.swapIntFails(types.ErrSwapAmountTooSmall, "TokenB", "TokenA", sdk.NewInt(200)) +} + +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.swapInt("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() { @@ -48,13 +104,13 @@ func (s *DexTestSuite) TestSwap0To1PartialFillLP() { 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) s.Assert().Equal("TokenB", tokenOut.Denom) - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(10_001_000), tokenOut, sdkmath.NewInt(10_000_000)) - s.assertTickBalancesInt(sdkmath.NewInt(10_001_000), sdkmath.ZeroInt()) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(10_000_999), tokenOut, sdkmath.NewInt(10_000_000)) + s.assertTickBalancesInt(sdkmath.NewInt(10_000_999), sdkmath.ZeroInt()) s.assertCurr0To1(math.MaxInt64) s.assertCurr1To0(-1) @@ -65,13 +121,13 @@ 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) s.Assert().Equal("TokenA", tokenOut.Denom) - s.assertSwapOutputInt(tokenIn, sdk.NewInt(10_001_000), tokenOut, sdk.NewInt(10_000_000)) - s.assertTickBalancesInt(sdk.ZeroInt(), sdk.NewInt(10001000)) + s.assertSwapOutputInt(tokenIn, sdk.NewInt(10_000_999), tokenOut, sdk.NewInt(10_000_000)) + s.assertTickBalancesInt(sdk.ZeroInt(), sdk.NewInt(10_000_999)) s.assertCurr0To1(1) s.assertCurr1To0(math.MinInt64) @@ -82,13 +138,13 @@ 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) s.Assert().Equal("TokenB", tokenOut.Denom) - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(100_000_000), tokenOut, sdkmath.NewInt(97_970_970)) - s.assertTickBalancesInt(sdkmath.NewInt(100_000_000), sdkmath.NewInt(20_29_030)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(99_999_999), tokenOut, sdkmath.NewInt(97_970_970)) + s.assertTickBalancesInt(sdkmath.NewInt(99_999_999), sdkmath.NewInt(20_29_030)) s.assertCurr0To1(205) s.assertCurr1To0(195) @@ -99,14 +155,14 @@ 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) s.Assert().Equal("TokenA", tokenOut.Denom) // NOTE: Given rounding for amountOut, amountIn does not use the full maxAmount - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(99_999_998), tokenOut, sdkmath.NewInt(13_533_528)) - s.assertTickBalancesInt(sdkmath.NewInt(86_466_472), sdkmath.NewInt(99_999_998)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(99_999_997), tokenOut, sdkmath.NewInt(13_533_528)) + s.assertTickBalancesInt(sdkmath.NewInt(86_466_472), sdkmath.NewInt(99_999_997)) s.assertCurr0To1(-19_999) s.assertCurr1To0(-20_001) @@ -117,13 +173,13 @@ 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) s.Assert().Equal("TokenB", tokenOut.Denom) - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(99_999_996), tokenOut, sdkmath.NewInt(12_246_928)) - s.assertTickBalancesInt(sdkmath.NewInt(99_999_996), sdkmath.NewInt(87_753_072)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(99_999_995), tokenOut, sdkmath.NewInt(12_246_928)) + s.assertTickBalancesInt(sdkmath.NewInt(99_999_995), sdkmath.NewInt(87_753_072)) s.assertCurr0To1(21_000) s.assertCurr1To0(19_000) @@ -134,13 +190,13 @@ 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) s.Assert().Equal("TokenA", tokenOut.Denom) - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(100_000_000), tokenOut, sdkmath.NewInt(668_525_935)) - s.assertTickBalancesInt(sdkmath.NewInt(331_474_065), sdkmath.NewInt(100_000_000)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(99_999_999), tokenOut, sdkmath.NewInt(668_525_935)) + s.assertTickBalancesInt(sdkmath.NewInt(331_474_065), sdkmath.NewInt(99_999_999)) s.assertCurr0To1(21_000) s.assertCurr1To0(19_000) @@ -155,13 +211,13 @@ 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) s.Assert().Equal("TokenB", tokenOut.Denom) - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(40_604_647), tokenOut, sdkmath.NewInt(300_000_000)) - s.assertTickBalancesInt(sdkmath.NewInt(40_604_647), sdkmath.ZeroInt()) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(40_604_644), tokenOut, sdkmath.NewInt(300_000_000)) + s.assertTickBalancesInt(sdkmath.NewInt(40_604_644), sdkmath.ZeroInt()) s.assertCurr0To1(math.MaxInt64) s.assertCurr1To0(-20_001) @@ -176,13 +232,13 @@ 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) s.Assert().Equal("TokenA", tokenOut.Denom) - s.assertSwapOutputInt(tokenIn, sdk.NewInt(40604647), tokenOut, sdk.NewInt(300000000)) - s.assertTickBalancesInt(sdk.ZeroInt(), sdk.NewInt(40604647)) + s.assertSwapOutputInt(tokenIn, sdk.NewInt(40604644), tokenOut, sdk.NewInt(300000000)) + s.assertTickBalancesInt(sdk.ZeroInt(), sdk.NewInt(40604644)) s.assertCurr0To1(20_001) s.assertCurr1To0(math.MinInt64) @@ -198,13 +254,13 @@ 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) s.Assert().Equal("TokenB", tokenOut.Denom) - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(399_180_884), tokenOut, sdkmath.NewInt(400_000_000)) - s.assertTickBalancesInt(sdkmath.NewInt(399_180_884), sdkmath.ZeroInt()) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(399_180_880), tokenOut, sdkmath.NewInt(400_000_000)) + s.assertTickBalancesInt(sdkmath.NewInt(399_180_880), sdkmath.ZeroInt()) s.assertCurr0To1(math.MaxInt64) s.assertCurr1To0(-21) @@ -220,13 +276,13 @@ 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) s.Assert().Equal("TokenA", tokenOut.Denom) - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(399_180_884), tokenOut, sdkmath.NewInt(400_000_000)) - s.assertTickBalancesInt(sdkmath.ZeroInt(), sdkmath.NewInt(399_180_884)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(399_180_880), tokenOut, sdkmath.NewInt(400_000_000)) + s.assertTickBalancesInt(sdkmath.ZeroInt(), sdkmath.NewInt(399_180_880)) s.assertCurr0To1(21) s.assertCurr1To0(math.MinInt64) @@ -240,8 +296,8 @@ func (s *DexTestSuite) TestSwap0To1LPMaxAmountUsed() { tokenIn, tokenOut := s.swapWithMaxOut("TokenA", "TokenB", 50, 5) // THEN swap should return ~5 BIGTokenA in and 5 BIGTokenB out - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(5_000_500), tokenOut, sdkmath.NewInt(5_000_000)) - s.assertTickBalancesInt(sdkmath.NewInt(5_000_500), sdkmath.NewInt(5_000_000)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(5_000_499), tokenOut, sdkmath.NewInt(5_000_000)) + s.assertTickBalancesInt(sdkmath.NewInt(5_000_499), sdkmath.NewInt(5_000_000)) } func (s *DexTestSuite) TestSwap1To0LPMaxAmountUsed() { @@ -252,8 +308,8 @@ func (s *DexTestSuite) TestSwap1To0LPMaxAmountUsed() { tokenIn, tokenOut := s.swapWithMaxOut("TokenB", "TokenA", 50, 5) // THEN swap should return ~5 BIGTokenB in and 5 BIGTokenA out - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(5_000_500), tokenOut, sdkmath.NewInt(5_000_000)) - s.assertTickBalancesInt(sdkmath.NewInt(5_000_000), sdkmath.NewInt(5_000_500)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(5_000_499), tokenOut, sdkmath.NewInt(5_000_000)) + s.assertTickBalancesInt(sdkmath.NewInt(5_000_000), sdkmath.NewInt(5_000_499)) } func (s *DexTestSuite) TestSwap0To1LPMaxAmountNotUsed() { @@ -264,8 +320,8 @@ func (s *DexTestSuite) TestSwap0To1LPMaxAmountNotUsed() { tokenIn, tokenOut := s.swapWithMaxOut("TokenA", "TokenB", 8, 15) // THEN swap should return 8 BIGTokenA in and ~8 BIGTokenB out - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(8_000_000), tokenOut, sdkmath.NewInt(7_999_200)) - s.assertTickBalancesInt(sdkmath.NewInt(8_000_000), sdkmath.NewInt(20_00_800)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(7_999_999), tokenOut, sdkmath.NewInt(7_999_200)) + s.assertTickBalancesInt(sdkmath.NewInt(7_999_999), sdkmath.NewInt(20_00_800)) } func (s *DexTestSuite) TestSwap1To0LPMaxAmountNotUsed() { @@ -276,8 +332,8 @@ func (s *DexTestSuite) TestSwap1To0LPMaxAmountNotUsed() { tokenIn, tokenOut := s.swapWithMaxOut("TokenB", "TokenA", 8, 15) // THEN swap should return 8 BIGTokenB in and ~8 BIGTokenA out - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(8_000_000), tokenOut, sdkmath.NewInt(7_999_200)) - s.assertTickBalancesInt(sdkmath.NewInt(2000800), sdkmath.NewInt(8_000_000)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(7_999_999), tokenOut, sdkmath.NewInt(7_999_200)) + s.assertTickBalancesInt(sdkmath.NewInt(2000800), sdkmath.NewInt(7_999_999)) } func (s *DexTestSuite) TestSwap0To1LPMaxAmountUsedMultiTick() { @@ -294,8 +350,8 @@ func (s *DexTestSuite) TestSwap0To1LPMaxAmountUsedMultiTick() { tokenIn, tokenOut := s.swapWithMaxOut("TokenA", "TokenB", 50, 20) // THEN swap should return ~20 BIGTokenA in and 20 BIGTokenB out - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(20_005_003), tokenOut, sdkmath.NewInt(20_000_000)) - s.assertTickBalancesInt(sdkmath.NewInt(20_005_003), sdkmath.NewInt(30_000_000)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(20_004_999), tokenOut, sdkmath.NewInt(20_000_000)) + s.assertTickBalancesInt(sdkmath.NewInt(20_004_999), sdkmath.NewInt(30_000_000)) } func (s *DexTestSuite) TestSwap1To0LPMaxAmountUsedMultiTick() { @@ -312,8 +368,8 @@ func (s *DexTestSuite) TestSwap1To0LPMaxAmountUsedMultiTick() { tokenIn, tokenOut := s.swapWithMaxOut("TokenB", "TokenA", 50, 20) // THEN swap should return ~ 20 BIGTokenB in and 20 BIGTokenA out - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(19_994_002), tokenOut, sdkmath.NewInt(20_000_000)) - s.assertTickBalancesInt(sdkmath.NewInt(30_000_000), sdkmath.NewInt(19_994_002)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(19_994_001), tokenOut, sdkmath.NewInt(20_000_000)) + s.assertTickBalancesInt(sdkmath.NewInt(30_000_000), sdkmath.NewInt(19_994_001)) } func (s *DexTestSuite) TestSwap0To1LPMaxAmountNotUsedMultiTick() { @@ -330,8 +386,8 @@ func (s *DexTestSuite) TestSwap0To1LPMaxAmountNotUsedMultiTick() { tokenIn, tokenOut := s.swapWithMaxOut("TokenA", "TokenB", 19, 20) // THEN swap should return 19 BIGTokenA in and 19 BIGTokenB out - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(19_000_000), tokenOut, sdkmath.NewInt(18_995_399)) - s.assertTickBalancesInt(sdkmath.NewInt(19_000_000), sdkmath.NewInt(31_004_601)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(18_999_999), tokenOut, sdkmath.NewInt(18_995_402)) + s.assertTickBalancesInt(sdkmath.NewInt(18_999_999), sdkmath.NewInt(31_004_598)) } // swaps against LOs only ///////////////////////////////////////////////////// @@ -341,13 +397,13 @@ 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) s.Assert().Equal("TokenB", tokenOut.Denom) - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(11_051_654), tokenOut, sdkmath.NewInt(10_000_000)) - s.assertTickBalancesInt(sdkmath.NewInt(11_051_654), sdkmath.ZeroInt()) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(11_051_653), tokenOut, sdkmath.NewInt(10_000_000)) + s.assertTickBalancesInt(sdkmath.NewInt(11_051_653), sdkmath.ZeroInt()) } func (s *DexTestSuite) TestSwap1To0PartialFillLO() { @@ -355,13 +411,13 @@ 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) s.Assert().Equal("TokenA", tokenOut.Denom) - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(11_051_654), tokenOut, sdkmath.NewInt(10_000_000)) - s.assertTickBalancesInt(sdkmath.ZeroInt(), sdkmath.NewInt(11_051_654)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(11_051_653), tokenOut, sdkmath.NewInt(10_000_000)) + s.assertTickBalancesInt(sdkmath.ZeroInt(), sdkmath.NewInt(11_051_653)) s.assertCurr0To1(math.MaxInt64) s.assertCurr1To0(math.MinInt64) @@ -372,13 +428,13 @@ 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) s.Assert().Equal("TokenB", tokenOut.Denom) - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(99_999_999), tokenOut, sdkmath.NewInt(36_789_783)) - s.assertTickBalancesInt(sdkmath.NewInt(99_999_999), sdkmath.NewInt(63_210_217)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(99_999_998), tokenOut, sdkmath.NewInt(36_789_783)) + s.assertTickBalancesInt(sdkmath.NewInt(99_999_998), sdkmath.NewInt(63_210_217)) s.assertCurr0To1(10_000) s.assertCurr1To0(math.MinInt64) @@ -389,13 +445,13 @@ 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) s.Assert().Equal("TokenA", tokenOut.Denom) - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(10_000_000), tokenOut, sdkmath.NewInt(3_678_978)) - s.assertTickBalancesInt(sdkmath.NewInt(96_321_022), sdkmath.NewInt(10_000_000)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(9_999_999), tokenOut, sdkmath.NewInt(3_678_978)) + s.assertTickBalancesInt(sdkmath.NewInt(96_321_022), sdkmath.NewInt(9_999_999)) s.assertCurr0To1(math.MaxInt64) s.assertCurr1To0(-10_000) @@ -408,13 +464,13 @@ 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) s.Assert().Equal("TokenB", tokenOut.Denom) - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(300_000_000), tokenOut, sdkmath.NewInt(271_428_295)) - s.assertTickBalancesInt(sdkmath.NewInt(300_000_000), sdkmath.NewInt(28_571_705)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(299_999_999), tokenOut, sdkmath.NewInt(271_428_297)) + s.assertTickBalancesInt(sdkmath.NewInt(299_999_999), sdkmath.NewInt(28_571_703)) s.assertCurr0To1(1_002) s.assertCurr1To0(math.MinInt64) @@ -427,13 +483,13 @@ 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) s.Assert().Equal("TokenA", tokenOut.Denom) - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(300_000_000), tokenOut, sdkmath.NewInt(271_428_295)) - s.assertTickBalancesInt(sdkmath.NewInt(28_571_705), sdkmath.NewInt(300_000_000)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(299_999_999), tokenOut, sdkmath.NewInt(271_428_297)) + s.assertTickBalancesInt(sdkmath.NewInt(28_571_703), sdkmath.NewInt(299_999_999)) s.assertCurr0To1(math.MaxInt64) s.assertCurr1To0(-1_002) @@ -447,8 +503,8 @@ func (s *DexTestSuite) TestSwap0To1LOMaxAmountUsed() { tokenIn, tokenOut := s.swapWithMaxOut("TokenA", "TokenB", 50, 5) // THEN swap should return ~5 BIGTokenA in and 5 BIGTokenB out - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(5_000_500), tokenOut, sdkmath.NewInt(5_000_000)) - s.assertTickBalancesInt(sdkmath.NewInt(5_000_500), sdkmath.NewInt(5_000_000)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(5_000_499), tokenOut, sdkmath.NewInt(5_000_000)) + s.assertTickBalancesInt(sdkmath.NewInt(5_000_499), sdkmath.NewInt(5_000_000)) } func (s *DexTestSuite) TestSwap1To0LOMaxAmountUsed() { @@ -471,8 +527,8 @@ func (s *DexTestSuite) TestSwap0To1LOMaxAmountNotUsed() { tokenIn, tokenOut := s.swapWithMaxOut("TokenA", "TokenB", 8, 15) // THEN swap should return 8 BIGTokenA in and ~8 BIGTokenB out - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(8_000_000), tokenOut, sdkmath.NewInt(7_999_200)) - s.assertTickBalancesInt(sdkmath.NewInt(8_000_000), sdkmath.NewInt(2_000_800)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(7_999_999), tokenOut, sdkmath.NewInt(7_999_200)) + s.assertTickBalancesInt(sdkmath.NewInt(7_999_999), sdkmath.NewInt(2_000_800)) } func (s *DexTestSuite) TestSwap1To0LOMaxAmountNotUsed() { @@ -499,8 +555,8 @@ func (s *DexTestSuite) TestSwap0To1LOMaxAmountUsedMultiTick() { tokenIn, tokenOut := s.swapWithMaxOut("TokenA", "TokenB", 50, 20) // THEN swap should return ~20 BIGTokenA in and 20 TokenB out - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(20_003_002), tokenOut, sdkmath.NewInt(20_000_000)) - s.assertTickBalancesInt(sdkmath.NewInt(20_003_002), sdkmath.NewInt(30_000_000)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(20_002_999), tokenOut, sdkmath.NewInt(20_000_000)) + s.assertTickBalancesInt(sdkmath.NewInt(20_002_999), sdkmath.NewInt(30_000_000)) } func (s *DexTestSuite) TestSwap1To0LOMaxAmountUsedMultiTick() { @@ -515,8 +571,8 @@ func (s *DexTestSuite) TestSwap1To0LOMaxAmountUsedMultiTick() { tokenIn, tokenOut := s.swapWithMaxOut("TokenB", "TokenA", 50, 20) // THEN swap should return ~20 BIGTokenB in and 20 BIGTokenA out - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(19_992_002), tokenOut, sdkmath.NewInt(20_000_000)) - s.assertTickBalancesInt(sdkmath.NewInt(30_000_000), sdkmath.NewInt(19_992_002)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(19_992_001), tokenOut, sdkmath.NewInt(20_000_000)) + s.assertTickBalancesInt(sdkmath.NewInt(30_000_000), sdkmath.NewInt(19_992_001)) } func (s *DexTestSuite) TestSwap0To1LOMaxAmountNotUsedMultiTick() { @@ -531,8 +587,8 @@ func (s *DexTestSuite) TestSwap0To1LOMaxAmountNotUsedMultiTick() { tokenIn, tokenOut := s.swapWithMaxOut("TokenA", "TokenB", 19, 20) // THEN swap should return 19 BIGTokenA in and ~19 BIGTokenB out - s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(19_000_000), tokenOut, sdkmath.NewInt(18_997_299)) - s.assertTickBalancesInt(sdkmath.NewInt(19_000_000), sdkmath.NewInt(31_002_701)) + s.assertSwapOutputInt(tokenIn, sdkmath.NewInt(18_999_999), tokenOut, sdkmath.NewInt(18_997_301)) + s.assertTickBalancesInt(sdkmath.NewInt(18_999_999), sdkmath.NewInt(31_002_699)) } // Swap LO and LP //////////////////////////////////////////////////////////// @@ -584,20 +640,48 @@ func (s *DexTestSuite) placeGTCLimitOrder( s.App.DexKeeper.SaveTranche(s.Ctx, tranche) } -func (s *DexTestSuite) swap( +func (s *DexTestSuite) swapInt( 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) swapIntFails( + targetErr error, + tokenIn string, + tokenOut string, + maxAmountIn sdkmath.Int, +) { + tradePairID, err := types.NewTradePairID(tokenIn, tokenOut) + s.Assert().NoError(err) + coinIn, coinOut, _, err := s.App.DexKeeper.Swap( + s.Ctx, + tradePairID, + maxAmountIn, + nil, + nil, + ) + s.Assert().Equal(coinIn, sdk.Coin{}) + s.Assert().Equal(coinOut, sdk.Coin{}) + s.ErrorIs(err, targetErr) +} + +func (s *DexTestSuite) swapSuccess( + tokenIn string, + tokenOut string, + maxAmountIn int64, +) (coinIn, coinOut sdk.Coin) { + coinIn, coinOut, _, err := s.swapInt(tokenIn, tokenOut, sdk.NewInt(maxAmountIn).Mul(denomMultiple)) s.Assert().NoError(err) return coinIn, coinOut } @@ -692,9 +776,6 @@ func (s *DexTestSuite) assertTickBalancesInt(expectedABalance, expectedBBalance //nolint:unused 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) diff --git a/x/dex/keeper/msg_server_test.go b/x/dex/keeper/msg_server_test.go index 999c4d817..056c63da2 100644 --- a/x/dex/keeper/msg_server_test.go +++ b/x/dex/keeper/msg_server_test.go @@ -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 @@ -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, diff --git a/x/dex/types/errors.go b/x/dex/types/errors.go index 323f6fdca..d56308015 100644 --- a/x/dex/types/errors.go +++ b/x/dex/types/errors.go @@ -184,4 +184,9 @@ var ( 1153, "Can only provide a single deposit amount for each tick, fee pair", ) + ErrSwapAmountTooSmall = sdkerrors.Register( + ModuleName, + 1154, + "Swap amount too small; creates unfair spread for liquidity providers", + ) ) diff --git a/x/dex/types/limit_order_tranche.go b/x/dex/types/limit_order_tranche.go index 6e9a7dc74..383741837 100644 --- a/x/dex/types/limit_order_tranche.go +++ b/x/dex/types/limit_order_tranche.go @@ -148,7 +148,7 @@ func (t *LimitOrderTranche) Swap(maxAmountTakerIn math.Int, maxAmountMakerOut *m } outAmount = utils.MinIntArr(possibleOutAmounts) - inAmount = math_utils.NewPrecDecFromInt(outAmount).Quo(t.PriceTakerToMaker).Ceil().TruncateInt() + inAmount = math_utils.NewPrecDecFromInt(outAmount).Quo(t.PriceTakerToMaker).TruncateInt() *fillTokenIn = fillTokenIn.Add(inAmount) *totalTokenIn = totalTokenIn.Add(inAmount) diff --git a/x/dex/types/params.go b/x/dex/types/params.go index 8bb7fdc0b..b62659eac 100644 --- a/x/dex/types/params.go +++ b/x/dex/types/params.go @@ -4,14 +4,16 @@ import ( fmt "fmt" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + math_utils "github.com/neutron-org/neutron/utils/math" "gopkg.in/yaml.v2" ) var _ paramtypes.ParamSet = (*Params)(nil) var ( - KeyFeeTiers = []byte("FeeTiers") - DefaultFeeTiers = []uint64{0, 1, 2, 3, 4, 5, 10, 20, 50, 100, 150, 200} + KeyFeeTiers = []byte("FeeTiers") + DefaultFeeTiers = []uint64{0, 1, 2, 3, 4, 5, 10, 20, 50, 100, 150, 200} + DefaultMaxTrueTakerSpread = math_utils.MustNewPrecDecFromStr("0.005") ) // ParamKeyTable the param key table for launch module @@ -20,13 +22,16 @@ func ParamKeyTable() paramtypes.KeyTable { } // NewParams creates a new Params instance -func NewParams(feeTiers []uint64) Params { - return Params{FeeTiers: feeTiers} +func NewParams(feeTiers []uint64, maxTrueTakerSpread math_utils.PrecDec) Params { + return Params{ + FeeTiers: feeTiers, + MaxTrueTakerSpread: maxTrueTakerSpread, + } } // DefaultParams returns a default set of parameters func DefaultParams() Params { - return NewParams(DefaultFeeTiers) + return NewParams(DefaultFeeTiers, DefaultMaxTrueTakerSpread) } // ParamSetPairs get the params.ParamSet diff --git a/x/dex/types/params.pb.go b/x/dex/types/params.pb.go index 8ec299924..4333552ee 100644 --- a/x/dex/types/params.pb.go +++ b/x/dex/types/params.pb.go @@ -7,6 +7,7 @@ import ( fmt "fmt" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" + github_com_neutron_org_neutron_utils_math "github.com/neutron-org/neutron/utils/math" io "io" math "math" math_bits "math/bits" @@ -25,7 +26,8 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // Params defines the parameters for the module. type Params struct { - FeeTiers []uint64 `protobuf:"varint,1,rep,packed,name=fee_tiers,json=feeTiers,proto3" json:"fee_tiers,omitempty"` + FeeTiers []uint64 `protobuf:"varint,1,rep,packed,name=fee_tiers,json=feeTiers,proto3" json:"fee_tiers,omitempty"` + MaxTrueTakerSpread github_com_neutron_org_neutron_utils_math.PrecDec `protobuf:"bytes,2,opt,name=max_true_taker_spread,json=maxTrueTakerSpread,proto3,customtype=github.com/neutron-org/neutron/utils/math.PrecDec" json:"max_true_taker_spread" yaml:"max_true_taker_spread"` } func (m *Params) Reset() { *m = Params{} } @@ -74,18 +76,24 @@ func init() { func init() { proto.RegisterFile("neutron/dex/params.proto", fileDescriptor_84a6bffcfc21009c) } var fileDescriptor_84a6bffcfc21009c = []byte{ - // 173 bytes of a gzipped FileDescriptorProto + // 272 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xc8, 0x4b, 0x2d, 0x2d, 0x29, 0xca, 0xcf, 0xd3, 0x4f, 0x49, 0xad, 0xd0, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x86, 0xca, 0xe8, 0xa5, 0xa4, 0x56, 0x48, 0x89, 0xa4, 0xe7, - 0xa7, 0xe7, 0x83, 0xc5, 0xf5, 0x41, 0x2c, 0x88, 0x12, 0x25, 0x6d, 0x2e, 0xb6, 0x00, 0xb0, 0x16, - 0x21, 0x69, 0x2e, 0xce, 0xb4, 0xd4, 0xd4, 0xf8, 0x92, 0xcc, 0xd4, 0xa2, 0x62, 0x09, 0x46, 0x05, - 0x66, 0x0d, 0x96, 0x20, 0x8e, 0xb4, 0xd4, 0xd4, 0x10, 0x10, 0xdf, 0x8a, 0x65, 0xc6, 0x02, 0x79, - 0x06, 0x27, 0x97, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, - 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0xd2, 0x4a, 0xcf, - 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x87, 0x5a, 0xaa, 0x9b, 0x5f, 0x94, 0x0e, - 0x63, 0xeb, 0x57, 0x80, 0x1d, 0x57, 0x52, 0x59, 0x90, 0x5a, 0x9c, 0xc4, 0x06, 0xb6, 0xd9, 0x18, - 0x10, 0x00, 0x00, 0xff, 0xff, 0x6a, 0x44, 0xce, 0x46, 0xb8, 0x00, 0x00, 0x00, + 0xa7, 0xe7, 0x83, 0xc5, 0xf5, 0x41, 0x2c, 0x88, 0x12, 0xa5, 0xcb, 0x8c, 0x5c, 0x6c, 0x01, 0x60, + 0x3d, 0x42, 0xd2, 0x5c, 0x9c, 0x69, 0xa9, 0xa9, 0xf1, 0x25, 0x99, 0xa9, 0x45, 0xc5, 0x12, 0x8c, + 0x0a, 0xcc, 0x1a, 0x2c, 0x41, 0x1c, 0x69, 0xa9, 0xa9, 0x21, 0x20, 0xbe, 0xd0, 0x52, 0x46, 0x2e, + 0xd1, 0xdc, 0xc4, 0x8a, 0xf8, 0x92, 0xa2, 0xd2, 0xd4, 0xf8, 0x92, 0xc4, 0xec, 0xd4, 0xa2, 0xf8, + 0xe2, 0x82, 0xa2, 0xd4, 0xc4, 0x14, 0x09, 0x26, 0x05, 0x46, 0x0d, 0x4e, 0xa7, 0xa2, 0x13, 0xf7, + 0xe4, 0x19, 0x6e, 0xdd, 0x93, 0x37, 0x4c, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, + 0xd5, 0x87, 0xda, 0xae, 0x9b, 0x5f, 0x94, 0x0e, 0x63, 0xeb, 0x97, 0x96, 0x64, 0xe6, 0x14, 0xeb, + 0xe7, 0x26, 0x96, 0x64, 0xe8, 0x05, 0x14, 0xa5, 0x26, 0xbb, 0xa4, 0x26, 0xbf, 0xba, 0x27, 0x8f, + 0xdd, 0xe4, 0x4f, 0xf7, 0xe4, 0x65, 0x2a, 0x13, 0x73, 0x73, 0xac, 0x94, 0xb0, 0x4a, 0x2b, 0x05, + 0x09, 0xe5, 0x26, 0x56, 0x84, 0x14, 0x95, 0xa6, 0x86, 0x80, 0x44, 0x83, 0xc1, 0x82, 0x56, 0x2c, + 0x33, 0x16, 0xc8, 0x33, 0x38, 0xb9, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, + 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, + 0x94, 0x16, 0x01, 0xf7, 0x55, 0x80, 0x43, 0xb1, 0xa4, 0xb2, 0x20, 0xb5, 0x38, 0x89, 0x0d, 0x1c, + 0x44, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe4, 0xa1, 0x2f, 0xd0, 0x61, 0x01, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -108,6 +116,16 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size := m.MaxTrueTakerSpread.Size() + i -= size + if _, err := m.MaxTrueTakerSpread.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 if len(m.FeeTiers) > 0 { dAtA2 := make([]byte, len(m.FeeTiers)*10) var j1 int @@ -153,6 +171,8 @@ func (m *Params) Size() (n int) { } n += 1 + sovParams(uint64(l)) + l } + l = m.MaxTrueTakerSpread.Size() + n += 1 + l + sovParams(uint64(l)) return n } @@ -267,6 +287,40 @@ func (m *Params) Unmarshal(dAtA []byte) error { } else { return fmt.Errorf("proto: wrong wireType = %d for field FeeTiers", wireType) } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxTrueTakerSpread", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MaxTrueTakerSpread.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/dex/types/pool.go b/x/dex/types/pool.go index 72e73e9ae..9b0536fd3 100644 --- a/x/dex/types/pool.go +++ b/x/dex/types/pool.go @@ -97,7 +97,6 @@ func (p *Pool) Swap( amountMakerOut = utils.MinIntArr(possibleAmountsMakerOut) amountTakerIn = math_utils.NewPrecDecFromInt(amountMakerOut). Quo(makerReserves.PriceTakerToMaker). - Ceil(). TruncateInt() takerReserves.ReservesMakerDenom = takerReserves.ReservesMakerDenom.Add(amountTakerIn)