Skip to content
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: adding new accounts summary query #2527

Merged
merged 9 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,9 @@ func New(
app.LeverageKeeper = leveragekeeper.NewKeeper(
appCodec,
keys[leveragetypes.ModuleName],
keys[authtypes.StoreKey],
app.BankKeeper,
app.AccountKeeper,
gsk967 marked this conversation as resolved.
Show resolved Hide resolved
app.OracleKeeper,
app.UGovKeeperB.EmergencyGroup,
cast.ToBool(appOpts.Get(leveragetypes.FlagEnableLiquidatorQuery)),
Expand Down
26 changes: 26 additions & 0 deletions proto/umee/leverage/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "umee/leverage/v1/genesis.proto";
import "umee/leverage/v1/leverage.proto";
import "gogoproto/gogo.proto";
import "cosmos/base/v1beta1/coin.proto";
import "cosmos/base/query/v1beta1/pagination.proto";

option go_package = "github.com/umee-network/umee/v6/x/leverage/types";

Expand Down Expand Up @@ -54,6 +55,12 @@ service Query {
option (google.api.http).get = "/umee/leverage/v1/account_summary";
}

// AccountsSummary queries USD values representing an account's total positions and borrowing limits. It requires oracle prices to return successfully.
rpc AccountsSummary(QueryAccountsSummary)
gsk967 marked this conversation as resolved.
Show resolved Hide resolved
returns (QueryAccountsSummaryResponse) {
option (google.api.http).get = "/umee/leverage/v1/accounts_summary";
}

// LiquidationTargets queries a list of all borrower account addresses eligible for liquidation.
rpc LiquidationTargets(QueryLiquidationTargets)
returns (QueryLiquidationTargetsResponse) {
Expand Down Expand Up @@ -341,6 +348,25 @@ message QueryAccountSummaryResponse {
];
}

// QueryAccountsSummary defines the request structure for the AccountsSummary gRPC service handler.
message QueryAccountsSummary {
// pagination defines an optional pagination for the request.
cosmos.base.query.v1beta1.PageRequest pagination = 1;
}

// AccountSummary is holds account_summary with address.
message AccountSummary {
string address = 1;
QueryAccountSummaryResponse account_summary = 2;
}

// QueryAccountsSummaryResponse defines the response structure for the AccountsSummary gRPC service handler.
message QueryAccountsSummaryResponse {
repeated AccountSummary accounts_summary = 1;
// pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

// QueryLiquidationTargets defines the request structure for the LiquidationTargets gRPC service handler.
message QueryLiquidationTargets {}

Expand Down
34 changes: 34 additions & 0 deletions x/leverage/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func GetQueryCmd() *cobra.Command {
QueryMarketSummary(),
QueryAccountBalances(),
QueryAccountSummary(),
QueryAccountsSummary(),
gsk967 marked this conversation as resolved.
Show resolved Hide resolved
QueryLiquidationTargets(),
QueryBadDebts(),
QueryMaxWithdraw(),
Expand Down Expand Up @@ -206,6 +207,39 @@ func QueryAccountSummary() *cobra.Command {
return cmd
}

// QueryAccountsSummary creates a Cobra command to query the USD
// values representing an all account's positions and borrowing limits.
gsk967 marked this conversation as resolved.
Show resolved Hide resolved
func QueryAccountsSummary() *cobra.Command {
cmd := &cobra.Command{
Use: "accounts-summary",
Args: cobra.NoArgs,
Short: "Query the position USD values and borrowing limits for an all accounts.",
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}

pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}

queryClient := types.NewQueryClient(clientCtx)
req := &types.QueryAccountsSummary{
Pagination: pageReq,
}
resp, err := queryClient.AccountsSummary(cmd.Context(), req)
return cli.PrintOrErr(resp, err, clientCtx)
},
}

flags.AddQueryFlagsToCmd(cmd)
flags.AddPaginationFlagsToCmd(cmd, "accounts-summary")

return cmd
}

// QueryLiquidationTargets creates a Cobra command to query for
// all eligible liquidation targets.
func QueryLiquidationTargets() *cobra.Command {
Expand Down
142 changes: 142 additions & 0 deletions x/leverage/keeper/accounts_summary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package keeper

import (
"context"

"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/umee-network/umee/v6/x/leverage/types"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

func (q Querier) accountSummary(ctx sdk.Context, addr sdk.AccAddress) (*types.QueryAccountSummaryResponse, error) {
supplied, err := q.GetAllSupplied(ctx, addr)
if err != nil {
return nil, err
}
collateral := q.GetBorrowerCollateral(ctx, addr)
borrowed := q.GetBorrowerBorrows(ctx, addr)

// the following price calculations use the most recent prices if spot prices are missing
lastSuppliedValue, err := q.VisibleTokenValue(ctx, supplied, types.PriceModeQuery)
if err != nil {
return nil, err
}
lastBorrowedValue, err := q.VisibleTokenValue(ctx, borrowed, types.PriceModeQuery)
if err != nil {
return nil, err
}
lastCollateralValue, err := q.VisibleCollateralValue(ctx, collateral, types.PriceModeQuery)
if err != nil {
return nil, err
}

// these use leverage-like prices: the lower of spot or historic price for supplied tokens and higher for borrowed.
// unlike transactions, this query will use expired prices instead of skipping them.
suppliedValue, err := q.VisibleTokenValue(ctx, supplied, types.PriceModeQueryLow)
if err != nil {
return nil, err
}
collateralValue, err := q.VisibleCollateralValue(ctx, collateral, types.PriceModeQueryLow)
if err != nil {
return nil, err
}
borrowedValue, err := q.VisibleTokenValue(ctx, borrowed, types.PriceModeQueryHigh)
if err != nil {
return nil, err
}

resp := &types.QueryAccountSummaryResponse{
SuppliedValue: suppliedValue,
CollateralValue: collateralValue,
BorrowedValue: borrowedValue,
SpotSuppliedValue: lastSuppliedValue,
SpotCollateralValue: lastCollateralValue,
SpotBorrowedValue: lastBorrowedValue,
}

// values computed from position use the same prices found in leverage logic:
// using the lower of spot or historic prices for each collateral token
// and the higher of spot or historic prices for each borrowed token
// skips collateral tokens with missing prices, but errors on borrow tokens missing prices
// (for oracle errors only the relevant response fields will be left nil)
ap, err := q.GetAccountPosition(ctx, addr, false)
if nonOracleError(err) {
return nil, err
}
if err == nil {
// on missing borrow price, borrow limit is nil
borrowLimit := ap.Limit()
resp.BorrowLimit = &borrowLimit
}

// liquidation threshold shown here as it is used in leverage logic: using spot prices.
// skips borrowed tokens with missing prices, but errors on collateral missing prices
// (for oracle errors only the relevant response fields will be left nil)
ap, err = q.GetAccountPosition(ctx, addr, true)
if nonOracleError(err) {
return nil, err
}
if err == nil {
// on missing collateral price, liquidation threshold is nil
liquidationThreshold := ap.Limit()
resp.LiquidationThreshold = &liquidationThreshold
}

return resp, err
}

// AccountsSummary implements types.QueryServer.
func (q Querier) AccountsSummary(goCtx context.Context, req *types.QueryAccountsSummary) (
*types.QueryAccountsSummaryResponse, error,
) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}
ctx := sdk.UnwrapSDKContext(goCtx)
// get the all accounts
store := ctx.KVStore(q.akStoreKey)
accountsStore := prefix.NewStore(store, authtypes.AddressStoreKeyPrefix)

var accounts []*types.AccountSummary
pageRes, err := query.Paginate(accountsStore, req.Pagination, func(key, value []byte) error {
gsk967 marked this conversation as resolved.
Show resolved Hide resolved
acc, err := q.authKeeper.UnmarshalAccount(value)
if err != nil {
return err
}
accSummary, err := q.accountSummary(ctx, acc.GetAddress())
if err != nil {
return err
}
accounts = append(accounts, &types.AccountSummary{
Address: acc.GetAddress().String(),
AccountSummary: accSummary,
})
return nil
})

return &types.QueryAccountsSummaryResponse{AccountsSummary: accounts, Pagination: pageRes}, err
}

// AccountSummary implements types.QueryServer.
func (q Querier) AccountSummary(
goCtx context.Context,
req *types.QueryAccountSummary,
) (*types.QueryAccountSummaryResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}
if req.Address == "" {
return nil, status.Error(codes.InvalidArgument, "empty address")
}

ctx := sdk.UnwrapSDKContext(goCtx)
addr, err := sdk.AccAddressFromBech32(req.Address)
if err != nil {
return nil, err
}
return q.accountSummary(ctx, addr)
}
94 changes: 0 additions & 94 deletions x/leverage/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,100 +241,6 @@ func (q Querier) AccountBalances(
}, nil
}

func (q Querier) AccountSummary(
goCtx context.Context,
req *types.QueryAccountSummary,
) (*types.QueryAccountSummaryResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}
if req.Address == "" {
return nil, status.Error(codes.InvalidArgument, "empty address")
}

ctx := sdk.UnwrapSDKContext(goCtx)

addr, err := sdk.AccAddressFromBech32(req.Address)
if err != nil {
return nil, err
}

supplied, err := q.GetAllSupplied(ctx, addr)
if err != nil {
return nil, err
}
collateral := q.GetBorrowerCollateral(ctx, addr)
borrowed := q.GetBorrowerBorrows(ctx, addr)

// the following price calculations use the most recent prices if spot prices are missing
lastSuppliedValue, err := q.VisibleTokenValue(ctx, supplied, types.PriceModeQuery)
if err != nil {
return nil, err
}
lastBorrowedValue, err := q.VisibleTokenValue(ctx, borrowed, types.PriceModeQuery)
if err != nil {
return nil, err
}
lastCollateralValue, err := q.VisibleCollateralValue(ctx, collateral, types.PriceModeQuery)
if err != nil {
return nil, err
}

// these use leverage-like prices: the lower of spot or historic price for supplied tokens and higher for borrowed.
// unlike transactions, this query will use expired prices instead of skipping them.
suppliedValue, err := q.VisibleTokenValue(ctx, supplied, types.PriceModeQueryLow)
if err != nil {
return nil, err
}
collateralValue, err := q.VisibleCollateralValue(ctx, collateral, types.PriceModeQueryLow)
if err != nil {
return nil, err
}
borrowedValue, err := q.VisibleTokenValue(ctx, borrowed, types.PriceModeQueryHigh)
if err != nil {
return nil, err
}

resp := &types.QueryAccountSummaryResponse{
SuppliedValue: suppliedValue,
CollateralValue: collateralValue,
BorrowedValue: borrowedValue,
SpotSuppliedValue: lastSuppliedValue,
SpotCollateralValue: lastCollateralValue,
SpotBorrowedValue: lastBorrowedValue,
}

// values computed from position use the same prices found in leverage logic:
// using the lower of spot or historic prices for each collateral token
// and the higher of spot or historic prices for each borrowed token
// skips collateral tokens with missing prices, but errors on borrow tokens missing prices
// (for oracle errors only the relevant response fields will be left nil)
ap, err := q.GetAccountPosition(ctx, addr, false)
if nonOracleError(err) {
return nil, err
}
if err == nil {
// on missing borrow price, borrow limit is nil
borrowLimit := ap.Limit()
resp.BorrowLimit = &borrowLimit
}

// liquidation threshold shown here as it is used in leverage logic: using spot prices.
// skips borrowed tokens with missing prices, but errors on collateral missing prices
// (for oracle errors only the relevant response fields will be left nil)
ap, err = q.GetAccountPosition(ctx, addr, true)
if nonOracleError(err) {
return nil, err
}
if err == nil {
// on missing collateral price, liquidation threshold is nil
liquidationThreshold := ap.Limit()
resp.LiquidationThreshold = &liquidationThreshold
}

return resp, nil
}

func (q Querier) LiquidationTargets(
goCtx context.Context,
req *types.QueryLiquidationTargets,
Expand Down
5 changes: 5 additions & 0 deletions x/leverage/keeper/internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"

"github.com/umee-network/umee/v6/tests/accs"
"github.com/umee-network/umee/v6/x/leverage/types"
Expand All @@ -22,14 +23,18 @@ type TestKeeper struct {
func NewTestKeeper(
cdc codec.Codec,
storeKey storetypes.StoreKey,
akStoreKey storetypes.StoreKey,
bk types.BankKeeper,
ak authkeeper.AccountKeeper,
ok types.OracleKeeper,
enableLiquidatorQuery bool,
) (Keeper, TestKeeper) {
k := NewKeeper(
cdc,
storeKey,
akStoreKey,
bk,
ak,
ok,
ugovmocks.NewSimpleEmergencyGroupBuilder(),
enableLiquidatorQuery,
Expand Down
Loading
Loading