Skip to content

Commit

Permalink
fix: price impact calculation (#316)
Browse files Browse the repository at this point in the history
* fix: price impact calculation

* test: fix

* refactor: calc price impact only if decimals value provided
  • Loading branch information
cosmic-vagabond authored Dec 21, 2023
1 parent 025ff94 commit 064f047
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 76 deletions.
94 changes: 54 additions & 40 deletions x/amm/keeper/calc_swap_estimation_by_denom.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package keeper

import (
"math"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/elys-network/elys/x/amm/types"
)

const (
lowestAmountForInitialSpotPriceCalc = 10 // lowest amount to use for initial spot price calculation
)

// CalcSwapEstimationByDenom calculates the swap estimation by denom
func (k Keeper) CalcSwapEstimationByDenom(
ctx sdk.Context,
Expand All @@ -18,6 +16,7 @@ func (k Keeper) CalcSwapEstimationByDenom(
baseCurrency string,
discount sdk.Dec,
overrideSwapFee sdk.Dec,
decimals uint64,
) (
inRoute []*types.SwapAmountInRoute,
outRoute []*types.SwapAmountOutRoute,
Expand All @@ -30,50 +29,65 @@ func (k Keeper) CalcSwapEstimationByDenom(
priceImpact sdk.Dec,
err error,
) {
// if amount denom is equal to denomIn, calculate swap estimation by denomIn
var (
initialSpotPrice sdk.Dec
)

// Initialize return variables
inRoute, outRoute = nil, nil
outAmount, availableLiquidity = sdk.Coin{}, sdk.Coin{}
spotPrice, swapFeeOut, discountOut, weightBonus, priceImpact = sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()

// Determine the correct route based on the amount's denom
if amount.Denom == denomIn {
inRoute, err := k.CalcInRouteByDenom(ctx, denomIn, denomOut, baseCurrency)
if err != nil {
return nil, nil, sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), err
}
initialSpotPrice, _, _, _, _, _, err := k.CalcInRouteSpotPrice(ctx, sdk.NewInt64Coin(amount.Denom, lowestAmountForInitialSpotPriceCalc), inRoute, discount, overrideSwapFee)
if err != nil {
return nil, nil, sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), err
}
// check if initialSpotPrice is zero to avoid division by zero
if initialSpotPrice.IsZero() {
return nil, nil, sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), types.ErrInitialSpotPriceIsZero
}
spotPrice, tokenOut, swapFeeOut, _, availableLiquidity, weightBonus, err := k.CalcInRouteSpotPrice(ctx, amount, inRoute, discount, overrideSwapFee)
if err != nil {
return nil, nil, sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), err
}
priceImpact := initialSpotPrice.Sub(spotPrice).Quo(initialSpotPrice)
return inRoute, nil, tokenOut, spotPrice, swapFeeOut, discount, availableLiquidity, weightBonus, priceImpact, nil
inRoute, err = k.CalcInRouteByDenom(ctx, denomIn, denomOut, baseCurrency)
} else if amount.Denom == denomOut {
outRoute, err = k.CalcOutRouteByDenom(ctx, denomOut, denomIn, baseCurrency)
} else {
err = types.ErrInvalidDenom
return
}

// if amount denom is equal to denomOut, calculate swap estimation by denomOut
if amount.Denom == denomOut {
outRoute, err := k.CalcOutRouteByDenom(ctx, denomOut, denomIn, baseCurrency)
if err != nil {
return nil, nil, sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), err
if err != nil {
return
}

// Calculate initial spot price and price impact if decimals is not zero
if decimals != 0 {
lowestAmountForInitialSpotPriceCalc := int64(math.Pow10(int(decimals)))
initialCoin := sdk.NewInt64Coin(amount.Denom, lowestAmountForInitialSpotPriceCalc)

if amount.Denom == denomIn {
initialSpotPrice, _, _, _, _, _, err = k.CalcInRouteSpotPrice(ctx, initialCoin, inRoute, discount, overrideSwapFee)
} else {
initialSpotPrice, _, _, _, _, _, err = k.CalcOutRouteSpotPrice(ctx, initialCoin, outRoute, discount, overrideSwapFee)
}
initialSpotPrice, _, _, _, _, _, err := k.CalcOutRouteSpotPrice(ctx, sdk.NewInt64Coin(amount.Denom, lowestAmountForInitialSpotPriceCalc), outRoute, discount, overrideSwapFee)

if err != nil {
return nil, nil, sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), err
return
}
// check if initialSpotPrice is zero to avoid division by zero
if initialSpotPrice.IsZero() {
return nil, nil, sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), types.ErrInitialSpotPriceIsZero
err = types.ErrInitialSpotPriceIsZero
return
}
spotPrice, tokenIn, swapFeeOut, _, availableLiquidity, weightBonus, err := k.CalcOutRouteSpotPrice(ctx, amount, outRoute, discount, overrideSwapFee)
if err != nil {
return nil, nil, sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), err
}
priceImpact := initialSpotPrice.Sub(spotPrice).Quo(initialSpotPrice)
return nil, outRoute, tokenIn, spotPrice, swapFeeOut, discount, availableLiquidity, weightBonus, priceImpact, nil
}

// if amount denom is neither equal to denomIn nor denomOut, return error
return nil, nil, sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), types.ErrInvalidDenom
// Calculate final spot price and other outputs
if amount.Denom == denomIn {
spotPrice, outAmount, swapFeeOut, _, availableLiquidity, weightBonus, err = k.CalcInRouteSpotPrice(ctx, amount, inRoute, discount, overrideSwapFee)
} else {
spotPrice, outAmount, swapFeeOut, _, availableLiquidity, weightBonus, err = k.CalcOutRouteSpotPrice(ctx, amount, outRoute, discount, overrideSwapFee)
}

if err != nil {
return
}

// Calculate price impact if decimals is not zero
if decimals != 0 {
priceImpact = initialSpotPrice.Sub(spotPrice).Quo(initialSpotPrice)
}

// Return the calculated values
return
}
12 changes: 9 additions & 3 deletions x/amm/keeper/calc_swap_estimation_by_denom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ func (suite *KeeperTestSuite) TestCalcSwapEstimationByDenom() {
amount,
ptypes.Elys, "uusda", ptypes.BaseCurrency,
sdk.ZeroDec(),
sdk.ZeroDec())
sdk.ZeroDec(),
1,
)
suite.Require().NoError(err)
suite.Require().NotNil(inRoute)
suite.Require().Nil(outRoute)
Expand All @@ -110,7 +112,9 @@ func (suite *KeeperTestSuite) TestCalcSwapEstimationByDenom() {
amount,
ptypes.Elys, "uusda", ptypes.BaseCurrency,
sdk.ZeroDec(),
sdk.ZeroDec())
sdk.ZeroDec(),
1,
)
suite.Require().NoError(err)
suite.Require().Nil(inRoute)
suite.Require().NotNil(outRoute)
Expand All @@ -123,6 +127,8 @@ func (suite *KeeperTestSuite) TestCalcSwapEstimationByDenom() {
suite.ctx, amount,
ptypes.Elys, "uusda", ptypes.BaseCurrency,
sdk.ZeroDec(),
sdk.ZeroDec())
sdk.ZeroDec(),
1,
)
suite.Require().Error(err)
}
3 changes: 2 additions & 1 deletion x/amm/keeper/msg_server_swap_by_denom.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ func (k msgServer) SwapByDenom(goCtx context.Context, msg *types.MsgSwapByDenom)
return nil, err
}

// retrieve base currency denom
entry, found := k.assetProfileKeeper.GetEntry(ctx, ptypes.BaseCurrency)
if !found {
return nil, errorsmod.Wrapf(assetprofiletypes.ErrAssetProfileNotFound, "asset %s not found", ptypes.BaseCurrency)
}
baseCurrency := entry.Denom

inRoute, outRoute, _, spotPrice, _, _, _, _, _, err := k.CalcSwapEstimationByDenom(ctx, msg.Amount, msg.DenomIn, msg.DenomOut, baseCurrency, msg.Discount, sdk.ZeroDec())
inRoute, outRoute, _, spotPrice, _, _, _, _, _, err := k.CalcSwapEstimationByDenom(ctx, msg.Amount, msg.DenomIn, msg.DenomOut, baseCurrency, msg.Discount, sdk.ZeroDec(), 0)
if err != nil {
return nil, err
}
Expand Down
8 changes: 8 additions & 0 deletions x/amm/keeper/msg_server_swap_by_denom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
"github.com/elys-network/elys/x/amm/keeper"
"github.com/elys-network/elys/x/amm/types"
assetprofiletypes "github.com/elys-network/elys/x/assetprofile/types"
ptypes "github.com/elys-network/elys/x/parameter/types"
)

Expand Down Expand Up @@ -54,6 +55,13 @@ func (suite *KeeperTestSuite) TestMsgServerSwapByDenom() {
suite.Run(tc.desc, func() {
suite.SetupTest()

// set asset profile
suite.app.AssetprofileKeeper.SetEntry(suite.ctx, assetprofiletypes.Entry{
BaseDenom: ptypes.Elys,
Denom: ptypes.Elys,
Decimals: 6,
})

// bootstrap accounts
sender := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address())
poolAddr := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address())
Expand Down
10 changes: 9 additions & 1 deletion x/amm/keeper/query_swap_estimation_by_denom.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,23 @@ func (k Keeper) SwapEstimationByDenom(goCtx context.Context, req *types.QuerySwa

ctx := sdk.UnwrapSDKContext(goCtx)

// retrieve base currency denom
entry, found := k.assetProfileKeeper.GetEntry(ctx, ptypes.BaseCurrency)
if !found {
return nil, errorsmod.Wrapf(assetprofiletypes.ErrAssetProfileNotFound, "asset %s not found", ptypes.BaseCurrency)
}
baseCurrency := entry.Denom

// retrieve denom in decimals
entry, found = k.assetProfileKeeper.GetEntryByDenom(ctx, req.DenomIn)
if !found {
return nil, errorsmod.Wrapf(assetprofiletypes.ErrAssetProfileNotFound, "asset %s not found", req.DenomIn)
}
decimals := entry.Decimals

_ = baseCurrency

inRoute, outRoute, amount, spotPrice, swapFee, discount, availableLiquidity, weightBonus, priceImpact, err := k.CalcSwapEstimationByDenom(ctx, req.Amount, req.DenomIn, req.DenomOut, baseCurrency, req.Discount, sdk.ZeroDec())
inRoute, outRoute, amount, spotPrice, swapFee, discount, availableLiquidity, weightBonus, priceImpact, err := k.CalcSwapEstimationByDenom(ctx, req.Amount, req.DenomIn, req.DenomOut, baseCurrency, req.Discount, sdk.ZeroDec(), decimals)
if err != nil {
return nil, err
}
Expand Down
2 changes: 2 additions & 0 deletions x/amm/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ type AssetProfileKeeper interface {
SetEntry(ctx sdk.Context, entry atypes.Entry)
// GetEntry returns a entry from its index
GetEntry(ctx sdk.Context, baseDenom string) (val atypes.Entry, found bool)
// GetEntryByDenom returns a entry from its denom value
GetEntryByDenom(ctx sdk.Context, denom string) (val atypes.Entry, found bool)
}

// AccountedPoolKeeper defines the expected interfaces
Expand Down
18 changes: 18 additions & 0 deletions x/assetprofile/keeper/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,24 @@ func (k Keeper) GetEntry(ctx sdk.Context, baseDenom string) (val types.Entry, fo
return val, true
}

// GetEntryByDenom returns a entry from its denom value
func (k Keeper) GetEntryByDenom(ctx sdk.Context, denom string) (val types.Entry, found bool) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.EntryKeyPrefix))
iterator := sdk.KVStorePrefixIterator(store, []byte{})

defer iterator.Close()

for ; iterator.Valid(); iterator.Next() {
var val types.Entry
k.cdc.MustUnmarshal(iterator.Value(), &val)
if val.Denom == denom {
return val, true
}
}

return types.Entry{}, false
}

// RemoveEntry removes a entry from the store
func (k Keeper) RemoveEntry(ctx sdk.Context, baseDenom string) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.EntryKeyPrefix))
Expand Down
3 changes: 2 additions & 1 deletion x/margin/keeper/query_open_estimation.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func (k Keeper) OpenEstimation(goCtx context.Context, req *types.QueryOpenEstima
// get swap fee param
swapFee := k.GetSwapFee(ctx)

// retrieve base currency denom
entry, found := k.assetProfileKeeper.GetEntry(ctx, ptypes.BaseCurrency)
if !found {
return nil, errorsmod.Wrapf(assetprofiletypes.ErrAssetProfileNotFound, "asset %s not found", ptypes.BaseCurrency)
Expand All @@ -37,7 +38,7 @@ func (k Keeper) OpenEstimation(goCtx context.Context, req *types.QueryOpenEstima
leveragedAmount := sdk.NewDecFromBigInt(req.Collateral.Amount.BigInt()).Mul(req.Leverage).TruncateInt()
leveragedCoin := sdk.NewCoin(req.Collateral.Denom, leveragedAmount)

_, _, positionSize, openPrice, swapFee, discount, availableLiquidity, _, _, err := k.amm.CalcSwapEstimationByDenom(ctx, leveragedCoin, req.Collateral.Denom, req.TradingAsset, baseCurrency, req.Discount, swapFee)
_, _, positionSize, openPrice, swapFee, discount, availableLiquidity, _, _, err := k.amm.CalcSwapEstimationByDenom(ctx, leveragedCoin, req.Collateral.Denom, req.TradingAsset, baseCurrency, req.Discount, swapFee, 0)
if err != nil {
return nil, err
}
Expand Down
3 changes: 3 additions & 0 deletions x/margin/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ type AmmKeeper interface {
baseCurrency string,
discount sdk.Dec,
overrideSwapFee sdk.Dec,
decimals uint64,
) (
inRoute []*ammtypes.SwapAmountInRoute,
outRoute []*ammtypes.SwapAmountOutRoute,
Expand Down Expand Up @@ -182,4 +183,6 @@ type BankKeeper interface {
type AssetProfileKeeper interface {
// GetEntry returns a entry from its index
GetEntry(ctx sdk.Context, baseDenom string) (val atypes.Entry, found bool)
// GetEntryByDenom returns a entry from its denom value
GetEntryByDenom(ctx sdk.Context, denom string) (val atypes.Entry, found bool)
}
Loading

0 comments on commit 064f047

Please sign in to comment.