Skip to content

Commit

Permalink
feat: swap queries/msgs returns swap fee and available liq
Browse files Browse the repository at this point in the history
  • Loading branch information
cosmic-vagabond committed Nov 27, 2023
1 parent 6335130 commit cacab86
Show file tree
Hide file tree
Showing 23 changed files with 956 additions and 258 deletions.
9 changes: 9 additions & 0 deletions proto/elys/amm/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,15 @@ message QueryAllDenomLiquidityResponse {
message QuerySwapEstimationRequest {
repeated SwapAmountInRoute routes = 1;
cosmos.base.v1beta1.Coin token_in = 2 [(gogoproto.nullable) = false];
string discount = 3 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
}

message QuerySwapEstimationResponse {
string spot_price = 1 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin token_out = 2 [(gogoproto.nullable) = false ] ;
string swapFee = 3 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string discount = 4 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin available_liquidity = 5 [(gogoproto.nullable) = false ] ;
}

message QuerySlippageTrackRequest {
Expand Down Expand Up @@ -185,15 +189,20 @@ message QuerySwapEstimationByDenomRequest {
cosmos.base.v1beta1.Coin amount = 1 [(gogoproto.nullable) = false];
string denom_in = 2;
string denom_out = 3;
string discount = 4 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
}

message QuerySwapEstimationByDenomResponse {
repeated SwapAmountInRoute in_route = 1;
repeated SwapAmountOutRoute out_route = 2;
string spot_price = 3 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin amount = 4 [(gogoproto.nullable) = false];
string swapFee = 5 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string discount = 6 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin available_liquidity = 7 [(gogoproto.nullable) = false ] ;
}

message QueryAMMPriceRequest {
cosmos.base.v1beta1.Coin token_in = 1 [(gogoproto.nullable) = false];
string discount = 2 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
}
9 changes: 6 additions & 3 deletions proto/elys/amm/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ message MsgSwapExactAmountIn {

message MsgSwapExactAmountInResponse {
string token_out_amount = 1 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false];
string discount = 2 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string swap_fee = 2 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string discount = 3 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
}

message MsgSwapExactAmountOut {
Expand All @@ -81,7 +82,8 @@ message MsgSwapExactAmountOut {

message MsgSwapExactAmountOutResponse {
string token_in_amount = 1 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false];
string discount = 2 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string swap_fee = 2 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string discount = 3 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
}

message MsgFeedMultipleExternalLiquidity {
Expand Down Expand Up @@ -118,5 +120,6 @@ message MsgSwapByDenomResponse {
repeated SwapAmountInRoute in_route = 2;
repeated SwapAmountOutRoute out_route = 3;
string spot_price = 4 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string discount = 5 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string swap_fee = 5 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string discount = 6 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
}
16 changes: 14 additions & 2 deletions x/amm/client/cli/query_swap_estimation.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ func CmdSwapEstimation() *cobra.Command {
})
}

discountStr, err := cmd.Flags().GetString(FlagDiscount)
if err != nil {
return err
}
discount, err := sdk.NewDecFromStr(discountStr)
if err != nil {
return err
}

clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
Expand All @@ -48,8 +57,9 @@ func CmdSwapEstimation() *cobra.Command {
queryClient := types.NewQueryClient(clientCtx)

params := &types.QuerySwapEstimationRequest{
Routes: reqRoutes,
TokenIn: reqTokenIn,
Routes: reqRoutes,
TokenIn: reqTokenIn,
Discount: discount,
}

res, err := queryClient.SwapEstimation(cmd.Context(), params)
Expand All @@ -63,5 +73,7 @@ func CmdSwapEstimation() *cobra.Command {

flags.AddQueryFlagsToCmd(cmd)

cmd.Flags().String(FlagDiscount, "0.0", "discount to apply to the swap fee")

return cmd
}
13 changes: 12 additions & 1 deletion x/amm/client/cli/query_swap_estimation_by_denom.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ func CmdSwapEstimationByDenom() *cobra.Command {
reqDenomIn := args[1]
reqDenomOut := args[2]

discountStr, err := cmd.Flags().GetString(FlagDiscount)
if err != nil {
return err
}
discount, err := sdk.NewDecFromStr(discountStr)
if err != nil {
return err
}

clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
Expand All @@ -34,10 +43,10 @@ func CmdSwapEstimationByDenom() *cobra.Command {
queryClient := types.NewQueryClient(clientCtx)

params := &types.QuerySwapEstimationByDenomRequest{

Amount: reqAmount,
DenomIn: reqDenomIn,
DenomOut: reqDenomOut,
Discount: discount,
}

res, err := queryClient.SwapEstimationByDenom(cmd.Context(), params)
Expand All @@ -51,5 +60,7 @@ func CmdSwapEstimationByDenom() *cobra.Command {

flags.AddQueryFlagsToCmd(cmd)

cmd.Flags().String(FlagDiscount, "0.0", "discount to apply to the swap fee")

return cmd
}
3 changes: 2 additions & 1 deletion x/amm/client/wasm/query_amm_price_by_denom.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ func (oq *Querier) queryAmmPriceByDenom(ctx sdk.Context, query *ammtypes.QueryAM

routes := resp.InRoute
tokenIn := query.TokenIn
discount := query.Discount

spotPrice, _, err := oq.keeper.CalcInRouteSpotPrice(ctx, tokenIn, routes)
spotPrice, _, _, _, _, err := oq.keeper.CalcInRouteSpotPrice(ctx, tokenIn, routes, discount)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to get in route by denom")
}
Expand Down
4 changes: 2 additions & 2 deletions x/amm/keeper/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (k Keeper) ApplySwapRequest(ctx sdk.Context, msg sdk.Msg) error {
if err != nil {
return err
}
_, err = k.RouteExactAmountIn(ctx, sender, msg.Routes, msg.TokenIn, math.Int(msg.TokenOutMinAmount), msg.Discount)
_, _, _, err = k.RouteExactAmountIn(ctx, sender, msg.Routes, msg.TokenIn, math.Int(msg.TokenOutMinAmount), msg.Discount)
if err != nil {
return err
}
Expand All @@ -38,7 +38,7 @@ func (k Keeper) ApplySwapRequest(ctx sdk.Context, msg sdk.Msg) error {
if err != nil {
return err
}
_, err = k.RouteExactAmountOut(ctx, sender, msg.Routes, msg.TokenInMaxAmount, msg.TokenOut, msg.Discount)
_, _, _, err = k.RouteExactAmountOut(ctx, sender, msg.Routes, msg.TokenInMaxAmount, msg.TokenOut, msg.Discount)
if err != nil {
return err
}
Expand Down
39 changes: 32 additions & 7 deletions x/amm/keeper/calc_in_route_spot_price.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import (
)

// CalcInRouteSpotPrice calculates the spot price of the given token and in route
func (k Keeper) CalcInRouteSpotPrice(ctx sdk.Context, tokenIn sdk.Coin, routes []*types.SwapAmountInRoute) (sdk.Dec, sdk.Coin, error) {
func (k Keeper) CalcInRouteSpotPrice(ctx sdk.Context, tokenIn sdk.Coin, routes []*types.SwapAmountInRoute, discount sdk.Dec) (sdk.Dec, sdk.Coin, sdk.Dec, sdk.Dec, sdk.Coin, error) {
if routes == nil || len(routes) == 0 {
return sdk.ZeroDec(), sdk.Coin{}, types.ErrEmptyRoutes
return sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, types.ErrEmptyRoutes
}

// Start with the initial token input
Expand All @@ -17,29 +17,54 @@ func (k Keeper) CalcInRouteSpotPrice(ctx sdk.Context, tokenIn sdk.Coin, routes [
// The final token out denom
var tokenOutDenom string

// Initialize the total discounted swap fee
totalDiscountedSwapFee := sdk.ZeroDec()

// Track the total available liquidity in the pool for final token out denom
var availableLiquidity sdk.Coin

for _, route := range routes {
poolId := route.PoolId
tokenOutDenom = route.TokenOutDenom

pool, found := k.GetPool(ctx, poolId)
if !found {
return sdk.ZeroDec(), sdk.Coin{}, types.ErrPoolNotFound
return sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, types.ErrPoolNotFound
}

// Get Pool swap fee
swapFee := pool.GetPoolParams().SwapFee

// Apply discount to swap fee if applicable
swapFee, _, err := k.ApplyDiscount(ctx, swapFee, discount, k.BrokerAddress(ctx))
if err != nil {
return sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, err
}

// Calculate the total discounted swap fee
totalDiscountedSwapFee = totalDiscountedSwapFee.Add(swapFee)

// Estimate swap
snapshot := k.GetPoolSnapshotOrSet(ctx, pool)
swapResult, err := k.CalcOutAmtGivenIn(ctx, pool.PoolId, k.oracleKeeper, &snapshot, tokensIn, tokenOutDenom, sdk.ZeroDec())
swapResult, err := k.CalcOutAmtGivenIn(ctx, pool.PoolId, k.oracleKeeper, &snapshot, tokensIn, tokenOutDenom, swapFee)

if err != nil {
return sdk.ZeroDec(), sdk.Coin{}, err
return sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, err
}

if swapResult.IsZero() {
return sdk.ZeroDec(), sdk.Coin{}, types.ErrAmountTooLow
return sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, types.ErrAmountTooLow
}

// Use the current swap result as the input for the next iteration
tokensIn = sdk.Coins{swapResult}

// Get the available liquidity for the final token out denom
_, poolAsset, err := pool.GetPoolAssetAndIndex(tokenOutDenom)
if err != nil {
return sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, err
}
availableLiquidity = poolAsset.Token
}

// Calculate the spot price given the initial token in and the final token out
Expand All @@ -51,5 +76,5 @@ func (k Keeper) CalcInRouteSpotPrice(ctx sdk.Context, tokenIn sdk.Coin, routes [
// Construct the token out coin
tokenOut := sdk.NewCoin(tokenOutDenom, tokenOutAmt.TruncateInt())

return spotPrice, tokenOut, nil
return spotPrice, tokenOut, totalDiscountedSwapFee, discount, availableLiquidity, nil
}
8 changes: 4 additions & 4 deletions x/amm/keeper/calc_in_route_spot_price_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestCalcInRouteSpotPrice(t *testing.T) {
accountedPoolKeeper.On("GetAccountedBalance", ctx, uint64(1), "denom1").Return(sdk.NewInt(1000))
accountedPoolKeeper.On("GetAccountedBalance", ctx, uint64(1), "denom2").Return(sdk.NewInt(1000))
routes := []*types.SwapAmountInRoute{{PoolId: 1, TokenOutDenom: "denom2"}}
spotPrice, _, err := k.CalcInRouteSpotPrice(ctx, tokenIn, routes)
spotPrice, _, _, _, _, err := k.CalcInRouteSpotPrice(ctx, tokenIn, routes, sdk.ZeroDec())
require.NoError(t, err)
require.NotZero(t, spotPrice)
accountedPoolKeeper.AssertExpectations(t)
Expand All @@ -34,17 +34,17 @@ func TestCalcInRouteSpotPrice(t *testing.T) {
{PoolId: 2, TokenOutDenom: "baseCurrency"},
{PoolId: 3, TokenOutDenom: "denom3"},
}
spotPrice, _, err = k.CalcInRouteSpotPrice(ctx, tokenIn, routes)
spotPrice, _, _, _, _, err = k.CalcInRouteSpotPrice(ctx, tokenIn, routes, sdk.ZeroDec())
require.NoError(t, err)
require.NotZero(t, spotPrice)
accountedPoolKeeper.AssertExpectations(t)

// Test no routes
_, _, err = k.CalcInRouteSpotPrice(ctx, tokenIn, nil)
_, _, _, _, _, err = k.CalcInRouteSpotPrice(ctx, tokenIn, nil, sdk.ZeroDec())
require.Error(t, err)

// Test invalid pool
routes = []*types.SwapAmountInRoute{{PoolId: 9999, TokenOutDenom: "denom2"}}
_, _, err = k.CalcInRouteSpotPrice(ctx, tokenIn, routes)
_, _, _, _, _, err = k.CalcInRouteSpotPrice(ctx, tokenIn, routes, sdk.ZeroDec())
require.Error(t, err)
}
39 changes: 32 additions & 7 deletions x/amm/keeper/calc_out_route_spot_price.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import (
)

// CalcOutRouteSpotPrice calculates the spot price of the given token and out route
func (k Keeper) CalcOutRouteSpotPrice(ctx sdk.Context, tokenOut sdk.Coin, routes []*types.SwapAmountOutRoute) (sdk.Dec, sdk.Coin, error) {
func (k Keeper) CalcOutRouteSpotPrice(ctx sdk.Context, tokenOut sdk.Coin, routes []*types.SwapAmountOutRoute, discount sdk.Dec) (sdk.Dec, sdk.Coin, sdk.Dec, sdk.Dec, sdk.Coin, error) {
if routes == nil || len(routes) == 0 {
return sdk.ZeroDec(), sdk.Coin{}, types.ErrEmptyRoutes
return sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, types.ErrEmptyRoutes
}

// Start with the initial token input
Expand All @@ -17,29 +17,54 @@ func (k Keeper) CalcOutRouteSpotPrice(ctx sdk.Context, tokenOut sdk.Coin, routes
// The final token in denom
var tokenInDenom string

// Initialize the total discounted swap fee
totalDiscountedSwapFee := sdk.ZeroDec()

// Track the total available liquidity in the pool for final token out denom
var availableLiquidity sdk.Coin

for _, route := range routes {
poolId := route.PoolId
tokenInDenom = route.TokenInDenom

pool, found := k.GetPool(ctx, poolId)
if !found {
return sdk.ZeroDec(), sdk.Coin{}, types.ErrPoolNotFound
return sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, types.ErrPoolNotFound
}

// Get Pool swap fee
swapFee := pool.GetPoolParams().SwapFee

// Apply discount to swap fee if applicable
swapFee, _, err := k.ApplyDiscount(ctx, swapFee, discount, k.BrokerAddress(ctx))
if err != nil {
return sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, err
}

// Calculate the total discounted swap fee
totalDiscountedSwapFee = totalDiscountedSwapFee.Add(swapFee)

// Estimate swap
snapshot := k.GetPoolSnapshotOrSet(ctx, pool)
swapResult, err := k.CalcInAmtGivenOut(ctx, pool.PoolId, k.oracleKeeper, &snapshot, tokensOut, tokenInDenom, sdk.ZeroDec())
swapResult, err := k.CalcInAmtGivenOut(ctx, pool.PoolId, k.oracleKeeper, &snapshot, tokensOut, tokenInDenom, swapFee)

if err != nil {
return sdk.ZeroDec(), sdk.Coin{}, err
return sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, err
}

if swapResult.IsZero() {
return sdk.ZeroDec(), sdk.Coin{}, types.ErrAmountTooLow
return sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, types.ErrAmountTooLow
}

// Use the current swap result as the input for the next iteration
tokensOut = sdk.Coins{swapResult}

// Get the available liquidity for the final token in denom
_, poolAsset, err := pool.GetPoolAssetAndIndex(tokenInDenom)
if err != nil {
return sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, err
}
availableLiquidity = poolAsset.Token
}

// Calculate the spot price given the initial token in and the final token in
Expand All @@ -51,5 +76,5 @@ func (k Keeper) CalcOutRouteSpotPrice(ctx sdk.Context, tokenOut sdk.Coin, routes
// Construct the token out coin
tokenIn := sdk.NewCoin(tokenInDenom, tokenInAmt.TruncateInt())

return spotPrice, tokenIn, nil
return spotPrice, tokenIn, totalDiscountedSwapFee, discount, availableLiquidity, nil
}
8 changes: 4 additions & 4 deletions x/amm/keeper/calc_out_route_spot_price_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func TestCalcOutRouteSpotPrice(t *testing.T) {
accountedPoolKeeper.On("GetAccountedBalance", ctx, uint64(1), "denom2").Return(sdk.NewInt(1000))
accountedPoolKeeper.On("GetAccountedBalance", ctx, uint64(1), "denom1").Return(sdk.NewInt(1000))
routes := []*types.SwapAmountOutRoute{{PoolId: 1, TokenInDenom: "denom1"}}
spotPrice, _, err := k.CalcOutRouteSpotPrice(ctx, tokenOut, routes)
spotPrice, _, _, _, _, err := k.CalcOutRouteSpotPrice(ctx, tokenOut, routes, sdk.ZeroDec())
require.NoError(t, err)
require.NotZero(t, spotPrice)
accountedPoolKeeper.AssertExpectations(t)
Expand All @@ -33,17 +33,17 @@ func TestCalcOutRouteSpotPrice(t *testing.T) {
{PoolId: 3, TokenInDenom: "baseCurrency"},
{PoolId: 2, TokenInDenom: "denom1"},
}
spotPrice, _, err = k.CalcOutRouteSpotPrice(ctx, tokenOut, routes)
spotPrice, _, _, _, _, err = k.CalcOutRouteSpotPrice(ctx, tokenOut, routes, sdk.ZeroDec())
require.NoError(t, err)
require.NotZero(t, spotPrice)
accountedPoolKeeper.AssertExpectations(t)

// Test no routes
_, _, err = k.CalcOutRouteSpotPrice(ctx, tokenOut, nil)
_, _, _, _, _, err = k.CalcOutRouteSpotPrice(ctx, tokenOut, nil, sdk.ZeroDec())
require.Error(t, err)

// Test invalid pool
routes = []*types.SwapAmountOutRoute{{PoolId: 9999, TokenInDenom: "denom2"}}
_, _, err = k.CalcOutRouteSpotPrice(ctx, tokenOut, routes)
_, _, _, _, _, err = k.CalcOutRouteSpotPrice(ctx, tokenOut, routes, sdk.ZeroDec())
require.Error(t, err)
}
Loading

0 comments on commit cacab86

Please sign in to comment.