-
Notifications
You must be signed in to change notification settings - Fork 97
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
feat: fix estimateXX dex queries #NTRN-318 #543
Changes from 9 commits
e3ac929
33b5549
9083cde
8ff24d2
cf0ccf6
fe1ab28
fcf9feb
4d67e6f
d236ce9
4d540e1
8a9aed7
ffcec4f
cd8d9e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package keeper_test | ||
|
||
import ( | ||
"cosmossdk.io/math" | ||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" | ||
|
||
"github.com/neutron-org/neutron/v4/x/dex/types" | ||
) | ||
|
||
func (s *DexTestSuite) TestEstimatePlaceLimitOrderGTC() { | ||
// GIVEN liquidity A<>B | ||
s.SetupMultiplePools( | ||
NewPoolSetup("TokenA", "TokenB", 0, 1, 0, 1), | ||
NewPoolSetup("TokenA", "TokenB", 0, 1, 1, 1), | ||
) | ||
|
||
// WHEN estimate GTC Limit order selling "TokenA" | ||
resp, err := s.App.DexKeeper.EstimatePlaceLimitOrder(s.Ctx, &types.QueryEstimatePlaceLimitOrderRequest{ | ||
TokenIn: "TokenA", | ||
TokenOut: "TokenB", | ||
TickIndexInToOut: 4, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's out of scope of the pr, but message QueryEstimatePlaceLimitOrderRequest {
MsgPlaceLimitOrder msg = 1;
} to not copypaste whole bunch of fields. The only downside i can see you can get rid of |
||
AmountIn: math.NewInt(3_000_000), | ||
OrderType: types.LimitOrderType_GOOD_TIL_CANCELLED, | ||
}) | ||
s.NoError(err) | ||
|
||
// Then estimate is 3 BIG TokenA in with 2 BIG TokenB out from swap | ||
s.True(math.NewInt(3_000_000).Equal(resp.TotalInCoin.Amount), "Got %v", resp.TotalInCoin.Amount) | ||
s.Equal("TokenA", resp.TotalInCoin.Denom) | ||
|
||
s.True(math.NewInt(2_000_301).Equal(resp.SwapInCoin.Amount), "Got %v", resp.SwapInCoin.Amount) | ||
s.Equal("TokenA", resp.SwapInCoin.Denom) | ||
|
||
s.True(math.NewInt(2_000_000).Equal(resp.SwapOutCoin.Amount), "Got %v", resp.SwapOutCoin.Amount) | ||
s.Equal("TokenB", resp.SwapOutCoin.Denom) | ||
|
||
// AND state is not altered | ||
s.assertDexBalanceWithDenom("TokenA", 0) | ||
s.assertDexBalanceWithDenom("TokenB", 2) | ||
|
||
// No events are emitted | ||
s.AssertEventValueNotEmitted(types.TickUpdateEventKey, "Expected no events") | ||
|
||
// Subsequent transactions use the original BankKeeper | ||
// ie. The simulation bankkeeper is not retained giving users unlimited funds | ||
s.assertBobLimitSellFails(sdkerrors.ErrInsufficientFunds, "TokenA", -400_000, 100_000_000) | ||
} | ||
|
||
func (s *DexTestSuite) TestEstimatePlaceLimitOrderFoK() { | ||
// GIVEN liquidity TokenB | ||
s.SetupMultiplePools( | ||
NewPoolSetup("TokenA", "TokenB", 0, 1, 0, 1), | ||
NewPoolSetup("TokenA", "TokenB", 0, 1, 1, 1), | ||
) | ||
|
||
// WHEN estimate FoK Limit order selling "TokenA" | ||
resp, err := s.App.DexKeeper.EstimatePlaceLimitOrder(s.Ctx, &types.QueryEstimatePlaceLimitOrderRequest{ | ||
TokenIn: "TokenA", | ||
TokenOut: "TokenB", | ||
TickIndexInToOut: 4, | ||
AmountIn: math.NewInt(1_500_000), | ||
OrderType: types.LimitOrderType_FILL_OR_KILL, | ||
}) | ||
s.NoError(err) | ||
|
||
// Then estimate is 1.5 BIG TokenA in with 1.5 BIG TokenB out from swap | ||
s.True(math.NewInt(1_500_000).Equal(resp.TotalInCoin.Amount), "Got %v", resp.TotalInCoin.Amount) | ||
s.Equal("TokenA", resp.TotalInCoin.Denom) | ||
|
||
s.True(math.NewInt(1_500_000).Equal(resp.SwapInCoin.Amount), "Got %v", resp.SwapInCoin.Amount) | ||
s.Equal("TokenA", resp.SwapInCoin.Denom) | ||
|
||
s.True(math.NewInt(1_499_800).Equal(resp.SwapOutCoin.Amount), "Got %v", resp.SwapOutCoin.Amount) | ||
s.Equal("TokenB", resp.SwapOutCoin.Denom) | ||
|
||
// AND state is not altered | ||
s.assertDexBalanceWithDenom("TokenA", 0) | ||
s.assertDexBalanceWithDenom("TokenB", 2) | ||
|
||
// No events are emitted | ||
s.AssertEventValueNotEmitted(types.TickUpdateEventKey, "Expected no events") | ||
|
||
// Subsequent transactions use the original BankKeeper | ||
s.assertBobLimitSellFails(sdkerrors.ErrInsufficientFunds, "TokenA", -400_000, 100_000_000) | ||
} | ||
|
||
func (s *DexTestSuite) TestEstimatePlaceLimitOrderFoKFails() { | ||
// GIVEN no liquidity | ||
|
||
// WHEN estimate placeLimitOrder | ||
resp, err := s.App.DexKeeper.EstimatePlaceLimitOrder(s.Ctx, &types.QueryEstimatePlaceLimitOrderRequest{ | ||
TokenIn: "TokenA", | ||
TokenOut: "TokenB", | ||
TickIndexInToOut: 4, | ||
AmountIn: math.NewInt(1_500_000), | ||
OrderType: types.LimitOrderType_IMMEDIATE_OR_CANCEL, | ||
}) | ||
|
||
// THEN error is returned | ||
s.ErrorIs(err, types.ErrLimitPriceNotSatisfied) | ||
s.Nil(resp) | ||
|
||
// Subsequent transactions use the original BankKeeper | ||
s.assertBobLimitSellFails(sdkerrors.ErrInsufficientFunds, "TokenA", -400_000, 100_000_000) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,14 +8,14 @@ import ( | |
"github.com/neutron-org/neutron/v4/x/dex/types" | ||
) | ||
|
||
// TODO: This doesn't run ValidateBasic() checks. | ||
func (k Keeper) EstimateMultiHopSwap( | ||
goCtx context.Context, | ||
req *types.QueryEstimateMultiHopSwapRequest, | ||
) (*types.QueryEstimateMultiHopSwapResponse, error) { | ||
msg := types.MsgMultiHopSwap{ | ||
Creator: req.Creator, | ||
Receiver: req.Receiver, | ||
// Add a random address so that Validate passes. This address is not used for anything within the query | ||
Creator: "neutron1dft8nwxzr0u27wvr2cknpermjkreqvp9fdy0uz", | ||
Receiver: "neutron1dft8nwxzr0u27wvr2cknpermjkreqvp9fdy0uz", | ||
Comment on lines
+17
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this address? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a random valid address. Will add a comment. But let me know if there's a more preferable way to do this. I agree it's a bit odd. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably need to create another message |
||
Routes: req.Routes, | ||
AmountIn: req.AmountIn, | ||
ExitLimitPrice: req.ExitLimitPrice, | ||
|
@@ -28,23 +28,23 @@ func (k Keeper) EstimateMultiHopSwap( | |
ctx := sdk.UnwrapSDKContext(goCtx) | ||
cacheCtx, _ := ctx.CacheContext() | ||
|
||
callerAddr := sdk.MustAccAddressFromBech32(req.Creator) | ||
receiverAddr := sdk.MustAccAddressFromBech32(req.Receiver) | ||
|
||
oldBk := k.bankKeeper | ||
k.bankKeeper = NewSimulationBankKeeper(k.bankKeeper) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we do that bank keeper substitution only because of these module<>bankKeeper interactions? also I can see that we use a cached context to not update the dex keeper's state. if yes, I think we should rethink the way we implemented the code. for example, speaking of the MultiHopSwapCore method: the logic for a simulation and for a real swap is mostly the same: why don't we create a separate method that does only the 1. step and is used in both simulation and real swap? in simulation we only call this method and return the result, but in the real swap we follow it up with the remaining necessary steps? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. more or less the same for PlaceLimitOrderCore There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree, i prefer a pure function that does estimation. |
||
coinOut, err := k.MultiHopSwapCore( | ||
cacheCtx, | ||
req.AmountIn, | ||
req.Routes, | ||
req.ExitLimitPrice, | ||
req.PickBestRoute, | ||
callerAddr, | ||
receiverAddr, | ||
[]byte("caller"), | ||
[]byte("receiver"), | ||
) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// NB: Critically, we do not write the best route's buffered state context since this is only an estimate. | ||
//nolint:staticcheck // Should be unnecessary but out of an abundance of caution we restore the old bankkeeper | ||
k.bankKeeper = oldBk | ||
|
||
return &types.QueryEstimateMultiHopSwapResponse{CoinOut: coinOut}, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets rename the file to grpc_query_estimate_place_limit_order_test to get exact before grpc_query_estimate_place_limit_order.go in listing