Skip to content

Commit

Permalink
Implement feed multiple external liquidity endpoint (#182)
Browse files Browse the repository at this point in the history
* implement feed multiple external liquidity endpoint

* Add LiquidityRatioFromPriceDepth function to be able to calculate total estimated liquidity from price depth & use it on external liquidity calculation

* Add unit test on LiquidityRatioFromPriceDepth & resolve message codec registration issue

* add changes on config.yml while testing price-feeder

* - Update assets value calculation script on external liquidity feed function
- Update config.yml to be able to feed external liquidity info locally

* update external liquidity struct

* update openapi yml

* update to use average depth when calculating external liquidity ratio
  • Loading branch information
jelysn authored Sep 15, 2023
1 parent e3b65b5 commit b337ad1
Show file tree
Hide file tree
Showing 13 changed files with 1,233 additions and 132 deletions.
25 changes: 25 additions & 0 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ version: 1
accounts:
- name: treasury
coins:
- 100000000uatom
- 100000000uusdt
- 9000000000000000uelys
- 100000000ueden
mnemonic: olympic slide park figure frost benefit deer reform fly pull price airport submit monitor silk insect uphold convince pupil project ignore roof warfare slight
- name: seed
coins:
- 9000000000000000uelys
Expand Down Expand Up @@ -300,10 +303,22 @@ genesis:
display: "BTC"
bandTicker: "BTC"
elysTicker: "BTC"
decimal: 9
- denom: "wei"
display: "ETH"
bandTicker: "ETH"
elysTicker: "ETH"
decimal: 18
- denom: "uatom"
display: "ATOM"
bandTicker: "ATOM"
elysTicker: "ATOM"
decimal: 6
- denom: "uusdt"
display: "USDT"
bandTicker: "USDT"
elysTicker: "USDT"
decimal: 6
params:
ask_count: "4"
band_channel_source: "channel-0"
Expand All @@ -323,6 +338,16 @@ genesis:
- feeder: "elys12tzylat4udvjj56uuhu3vj2n4vgp7cf9fwna9w"
is_active: true
prices:
- asset: USDT
price: "1.00"
provider: elys12tzylat4udvjj56uuhu3vj2n4vgp7cf9fwna9w
source: elys
timestamp: "1694743319"
- asset: USDC
price: "1.00"
provider: elys12tzylat4udvjj56uuhu3vj2n4vgp7cf9fwna9w
source: elys
timestamp: "1694743319"
burner:
params:
epochIdentifier: day
Expand Down
31 changes: 31 additions & 0 deletions docs/static/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77678,13 +77678,42 @@ definitions:
signatures required by gogoproto.
weight:
type: string
elys.amm.AssetAmountDepth:
type: object
properties:
asset:
type: string
amount:
type: string
depth:
type: string
elys.amm.DenomLiquidity:
type: object
properties:
denom:
type: string
liquidity:
type: string
elys.amm.ExternalLiquidity:
type: object
properties:
poolId:
type: string
format: uint64
amountDepthInfo:
type: array
items:
type: object
properties:
asset:
type: string
amount:
type: string
depth:
type: string
description: >-
ExternalLiquidity defines price, volume, and time information for an
exchange rate.
elys.amm.MsgCreatePoolResponse:
type: object
properties:
Expand All @@ -77708,6 +77737,8 @@ definitions:

NOTE: The amount field is an Int which implements the custom method
signatures required by gogoproto.
elys.amm.MsgFeedMultipleExternalLiquidityResponse:
type: object
elys.amm.MsgJoinPoolResponse:
type: object
properties:
Expand Down
26 changes: 26 additions & 0 deletions proto/elys/amm/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ service Msg {
rpc ExitPool (MsgExitPool ) returns (MsgExitPoolResponse );
rpc SwapExactAmountIn (MsgSwapExactAmountIn ) returns (MsgSwapExactAmountInResponse );
rpc SwapExactAmountOut (MsgSwapExactAmountOut) returns (MsgSwapExactAmountOutResponse);
rpc FeedMultipleExternalLiquidity(MsgFeedMultipleExternalLiquidity) returns (MsgFeedMultipleExternalLiquidityResponse);
}
message MsgCreatePool {
string sender = 1;
Expand Down Expand Up @@ -74,3 +75,28 @@ message MsgSwapExactAmountOutResponse {
string tokenInAmount = 1 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false];
}

message MsgFeedMultipleExternalLiquidity {
string sender = 1;
repeated ExternalLiquidity liquidity = 2 [(gogoproto.nullable) = false];
}
message MsgFeedMultipleExternalLiquidityResponse {}

message AssetAmountDepth {
string asset = 1;
string amount = 2 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
string depth = 3 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
}

// ExternalLiquidity defines price, volume, and time information for an exchange rate.
message ExternalLiquidity {
uint64 poolId = 1;
repeated AssetAmountDepth amountDepthInfo = 2 [
(gogoproto.nullable) = false
];
}
5 changes: 3 additions & 2 deletions x/amm/client/cli/query_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import (

func CmdListPool() *cobra.Command {
cmd := &cobra.Command{
Use: "list-pool",
Short: "list all pool",
Use: "list-pool",
Short: "list all pool",
Example: "elysd query amm list-pool",
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)

Expand Down
7 changes: 4 additions & 3 deletions x/amm/client/cli/tx_create_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ var _ = strconv.Itoa(0)

func CmdCreatePool() *cobra.Command {
cmd := &cobra.Command{
Use: "create-pool [weights] [initial-deposit] [swap-fee] [exit-fee]",
Short: "create a new pool and provide the liquidity to it",
Args: cobra.ExactArgs(6),
Use: "create-pool [weights] [initial-deposit] [swap-fee] [exit-fee]",
Short: "create a new pool and provide the liquidity to it",
Example: `elysd tx amm create-pool 100ueden,100uelys 1000000ueden,1000000uelys 0.00 0.00 --from=treasury --keyring-backend=test --chain-id=elystestnet-1 --yes --gas=1000000`,
Args: cobra.ExactArgs(4),
RunE: func(cmd *cobra.Command, args []string) (err error) {
argWeights, err := sdk.ParseCoinsNormalized(args[0])
if err != nil {
Expand Down
82 changes: 82 additions & 0 deletions x/amm/keeper/msg_server_feed_multiple_external_liquidity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package keeper

import (
"context"
"fmt"

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

func AssetsValue(ctx sdk.Context, oracleKeeper types.OracleKeeper, amountDepthInfo []types.AssetAmountDepth) (sdk.Dec, sdk.Dec, error) {
totalValue := sdk.ZeroDec()
totalDepth := sdk.ZeroDec()
if len(amountDepthInfo) == 0 {
return sdk.ZeroDec(), sdk.ZeroDec(), nil
}
for _, asset := range amountDepthInfo {
price, found := oracleKeeper.GetAssetPrice(ctx, asset.Asset)
if !found {
return sdk.ZeroDec(), sdk.ZeroDec(), fmt.Errorf("asset price not set: %s", asset.Asset)
} else {
v := price.Price.Mul(asset.Amount)
totalValue = totalValue.Add(v)
}
totalDepth = totalDepth.Add(asset.Depth)
}
avgDepth := totalDepth.Quo(sdk.NewDec(int64(len(amountDepthInfo))))
return totalValue, avgDepth, nil
}

func LiquidityRatioFromPriceDepth(depth sdk.Dec) sdk.Dec {
if depth == sdk.OneDec() {
return sdk.OneDec()
}
sqrt, err := sdk.OneDec().Sub(depth).ApproxSqrt()
if err != nil {
panic(err)
}
return sdk.OneDec().Sub(sqrt)
}

func (k msgServer) FeedMultipleExternalLiquidity(goCtx context.Context, msg *types.MsgFeedMultipleExternalLiquidity) (*types.MsgFeedMultipleExternalLiquidityResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

feeder, found := k.oracleKeeper.GetPriceFeeder(ctx, msg.Sender)
if !found {
return nil, oracletypes.ErrNotAPriceFeeder
}

if !feeder.IsActive {
return nil, oracletypes.ErrPriceFeederNotActive
}

for _, el := range msg.Liquidity {
pool, found := k.GetPool(ctx, el.PoolId)
if !found {
return nil, types.ErrInvalidPoolId
}

tvl, err := pool.TVL(ctx, k.oracleKeeper)
if err != nil {
return nil, err
}

elValue, elDepth, err := AssetsValue(ctx, k.oracleKeeper, el.AmountDepthInfo)
if err != nil {
return nil, err
}

elRatio := elValue.Quo(tvl)
elRatio = elRatio.Quo(LiquidityRatioFromPriceDepth(elDepth))
if elRatio.LT(sdk.OneDec()) {
elRatio = sdk.OneDec()
}

pool.PoolParams.ExternalLiquidityRatio = elRatio
k.SetPool(ctx, pool)
}

return &types.MsgFeedMultipleExternalLiquidityResponse{}, nil
}
27 changes: 27 additions & 0 deletions x/amm/keeper/msg_server_feed_multiple_external_liquidity_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package keeper_test

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

func (suite *KeeperTestSuite) TestLiquidityRatioFromPriceDepth() {
depth := sdk.NewDecWithPrec(1, 2) // 1%
suite.Require().Equal("0.005012562893380046", keeper.LiquidityRatioFromPriceDepth(depth).String())
depth = sdk.NewDecWithPrec(2, 2) // 2%
suite.Require().Equal("0.010050506338833466", keeper.LiquidityRatioFromPriceDepth(depth).String())
depth = sdk.NewDecWithPrec(5, 2) // 5%
suite.Require().Equal("0.025320565519103610", keeper.LiquidityRatioFromPriceDepth(depth).String())
depth = sdk.NewDecWithPrec(10, 2) // 10%
suite.Require().Equal("0.051316701949486201", keeper.LiquidityRatioFromPriceDepth(depth).String())
depth = sdk.NewDecWithPrec(30, 2) // 30%
suite.Require().Equal("0.163339973465924452", keeper.LiquidityRatioFromPriceDepth(depth).String())
depth = sdk.NewDecWithPrec(50, 2) // 50%
suite.Require().Equal("0.292893218813452476", keeper.LiquidityRatioFromPriceDepth(depth).String())
depth = sdk.NewDecWithPrec(70, 2) // 70%
suite.Require().Equal("0.452277442494833887", keeper.LiquidityRatioFromPriceDepth(depth).String())
depth = sdk.NewDecWithPrec(90, 2) // 90%
suite.Require().Equal("0.683772233983162067", keeper.LiquidityRatioFromPriceDepth(depth).String())
depth = sdk.NewDecWithPrec(100, 2) // 100%
suite.Require().Equal("1.000000000000000000", keeper.LiquidityRatioFromPriceDepth(depth).String())
}
10 changes: 2 additions & 8 deletions x/amm/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,19 @@ func RegisterCodec(cdc *codec.LegacyAmino) {
cdc.RegisterConcrete(&MsgJoinPool{}, "amm/JoinPool", nil)
cdc.RegisterConcrete(&MsgExitPool{}, "amm/ExitPool", nil)
cdc.RegisterConcrete(&MsgSwapExactAmountIn{}, "amm/SwapExactAmountIn", nil)
cdc.RegisterConcrete(&MsgFeedMultipleExternalLiquidity{}, "amm/FeedMultipleExternalLiquidity", nil)
cdc.RegisterConcrete(&MsgSwapExactAmountOut{}, "amm/SwapExactAmountOut", nil)
// this line is used by starport scaffolding # 2
}

func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
registry.RegisterImplementations((*sdk.Msg)(nil),
&MsgCreatePool{},
)
registry.RegisterImplementations((*sdk.Msg)(nil),
&MsgJoinPool{},
)
registry.RegisterImplementations((*sdk.Msg)(nil),
&MsgExitPool{},
)
registry.RegisterImplementations((*sdk.Msg)(nil),
&MsgSwapExactAmountIn{},
)
registry.RegisterImplementations((*sdk.Msg)(nil),
&MsgSwapExactAmountOut{},
&MsgFeedMultipleExternalLiquidity{},
)
// this line is used by starport scaffolding # 3

Expand Down
3 changes: 3 additions & 0 deletions x/amm/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
atypes "github.com/elys-network/elys/x/assetprofile/types"
oracletypes "github.com/elys-network/elys/x/oracle/types"
)

// AccountKeeper defines the expected account keeper used for simulations (noalias)
Expand All @@ -30,7 +31,9 @@ type BankKeeper interface {

// OracleKeeper defines the expected interface needed to retrieve price info
type OracleKeeper interface {
GetAssetPrice(ctx sdk.Context, asset string) (oracletypes.Price, bool)
GetAssetPriceFromDenom(ctx sdk.Context, denom string) sdk.Dec
GetPriceFeeder(ctx sdk.Context, feeder string) (val oracletypes.PriceFeeder, found bool)
}

// AssetProfileKeeper defines the expected interfaces
Expand Down
45 changes: 45 additions & 0 deletions x/amm/types/message_feed_multiple_external_liquidity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package types

import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)

const TypeMsgFeedMultipleExternalLiquidity = "feed_multiple_external_liquidity"

var _ sdk.Msg = &MsgFeedMultipleExternalLiquidity{}

func NewMsgFeedMultipleExternalLiquidity(sender string) *MsgFeedMultipleExternalLiquidity {
return &MsgFeedMultipleExternalLiquidity{
Sender: sender,
}
}

func (msg *MsgFeedMultipleExternalLiquidity) Route() string {
return RouterKey
}

func (msg *MsgFeedMultipleExternalLiquidity) Type() string {
return TypeMsgSwapExactAmountOut
}

func (msg *MsgFeedMultipleExternalLiquidity) GetSigners() []sdk.AccAddress {
sender, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
panic(err)
}
return []sdk.AccAddress{sender}
}

func (msg *MsgFeedMultipleExternalLiquidity) GetSignBytes() []byte {
bz := ModuleCdc.MustMarshalJSON(msg)
return sdk.MustSortJSON(bz)
}

func (msg *MsgFeedMultipleExternalLiquidity) ValidateBasic() error {
_, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid sender address (%s)", err)
}
return nil
}
Loading

0 comments on commit b337ad1

Please sign in to comment.