Skip to content

Commit

Permalink
feat: adding new accounts summary query (#2527)
Browse files Browse the repository at this point in the history
* WIP: add accounts_summary grpc query

* add cli query for accounts summary

* fix the lint

* fix the test issue

* update the changelog

* rename the account summaries

* only getting accounts which did supply
  • Loading branch information
gsk967 authored May 17, 2024
1 parent 94a34f5 commit 84f3562
Show file tree
Hide file tree
Showing 11 changed files with 1,132 additions and 244 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
- [2472](https://github.com/umee-network/umee/pull/2472) un-wire the `crisis` module from umee app.
- [2500](https://github.com/umee-network/umee/pull/2500) (x/leverage): add Rewards Auction fees and `params.rewards_auction_factor`.
- [2506](https://github.com/umee-network/umee/pull/2506) (ics20): support leverage/MsgRepay in Memo
- [2527](https://github.com/umee-network/umee/pull/2527) (x/leverage):add `accounts_summary` grpc-web api and cli query to leverage module.

### Improvements

Expand Down
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,
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";
}

// AccountSummaries queries USD values representing an account's total positions and borrowing limits. It requires oracle prices to return successfully.
rpc AccountSummaries(QueryAccountSummaries)
returns (QueryAccountSummariesResponse) {
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 {
];
}

// QueryAccountSummaries defines the request structure for the AccountSummaries gRPC service handler.
message QueryAccountSummaries {
// 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;
}

// QueryAccountSummariesResponse defines the response structure for the AccountSummaries gRPC service handler.
message QueryAccountSummariesResponse {
repeated AccountSummary account_summaries = 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(),
QueryAccountSummaries(),
QueryLiquidationTargets(),
QueryBadDebts(),
QueryMaxWithdraw(),
Expand Down Expand Up @@ -206,6 +207,39 @@ func QueryAccountSummary() *cobra.Command {
return cmd
}

// QueryAccountSummaries creates a Cobra command to query the USD
// values representing an all account's positions and borrowing limits.
func QueryAccountSummaries() *cobra.Command {
cmd := &cobra.Command{
Use: "account-summaries",
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.QueryAccountSummaries{
Pagination: pageReq,
}
resp, err := queryClient.AccountSummaries(cmd.Context(), req)
return cli.PrintOrErr(resp, err, clientCtx)
},
}

flags.AddQueryFlagsToCmd(cmd)
flags.AddPaginationFlagsToCmd(cmd, "account-summaries")

return cmd
}

// QueryLiquidationTargets creates a Cobra command to query for
// all eligible liquidation targets.
func QueryLiquidationTargets() *cobra.Command {
Expand Down
153 changes: 153 additions & 0 deletions x/leverage/keeper/accounts_summary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
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/util/coin"
"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, nil
}

// AccountSummaries implements types.QueryServer.
func (q Querier) AccountSummaries(goCtx context.Context, req *types.QueryAccountSummaries) (
*types.QueryAccountSummariesResponse, 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 {
acc, err := q.authKeeper.UnmarshalAccount(value)
if err != nil {
return err
}
balance := q.bankKeeper.GetAllBalances(ctx, acc.GetAddress())
hasUToken := false
for _, c := range balance {
if coin.HasUTokenPrefix(c.Denom) {
hasUToken = true
break
}
}
if hasUToken {
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.QueryAccountSummariesResponse{AccountSummaries: 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

0 comments on commit 84f3562

Please sign in to comment.