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

add topN endpoint to supply module (disabled by default) #1800

Merged
merged 17 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
51 changes: 49 additions & 2 deletions docs/swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5220,10 +5220,8 @@ paths:
properties:
supply:
type: string
format: uint64
circulating_supply:
type: string
format: uint64
default:
description: An unexpected error response.
schema:
Expand All @@ -5248,3 +5246,52 @@ paths:
format: byte
tags:
- QuerySupply
/quicksilver/supply/v1/topn/{n}:
get:
operationId: TopN
responses:
'200':
description: A successful response.
schema:
type: object
properties:
accounts:
type: array
items:
type: object
properties:
address:
type: string
balance:
type: string
default:
description: An unexpected error response.
schema:
type: object
properties:
error:
type: string
code:
type: integer
format: int32
message:
type: string
details:
type: array
items:
type: object
properties:
type_url:
type: string
value:
type: string
format: byte
parameters:
- name: 'n'
in: path
required: true
type: string
format: uint64
tags:
- QuerySupply

5 changes: 5 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0/go.mod h1:b3g59n2Y+T5xmc
github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190129172621-c8b1d7a94ddf/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo=
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0 h1:oVLqHXhnYtUwM89y9T1fXGaK9wTkXHgNp8/ZNMQzUxE=
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.2/go.mod h1:itPGVDKf9cC/ov4MdvJ2QZ0khw4bfoo9jzwTJlaxy2k=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM=
github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
Expand Down Expand Up @@ -836,6 +837,7 @@ github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB
github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o=
Expand Down Expand Up @@ -1010,6 +1012,7 @@ github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPz
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA=
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
github.com/golangci/modinfo v0.3.4/go.mod h1:wytF1M5xl9u0ij8YSvhkEVPP3M5Mc7XLl1pxH3B2aUM=
github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc=
github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg=
github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks=
Expand Down Expand Up @@ -1531,6 +1534,7 @@ github.com/sagikazarmark/crypt v0.19.0/go.mod h1:c6vimRziqqERhtSe0MhIvzE1w54FrCH
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da h1:p3Vo3i64TCLY7gIfzeQaUJ+kppEO5WQG3cL8iE8tGHU=
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk=
Expand All @@ -1544,6 +1548,7 @@ github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNX
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shirou/gopsutil/v4 v4.24.12/go.mod h1:DCtMPAad2XceTeIAbGyVfycbYQNBGk2P8cvDi7/VN9o=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
Expand Down
35 changes: 33 additions & 2 deletions proto/quicksilver/supply/v1/query.proto
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
syntax = "proto3";
package quicksilver.supply.v1;

import "cosmos/base/v1beta1/coin.proto";
import "cosmos_proto/cosmos.proto";
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";

option go_package = "github.com/quicksilver-zone/quicksilver/x/supply/types";
Expand All @@ -11,10 +14,38 @@ service Query {
rpc Supply(QuerySupplyRequest) returns (QuerySupplyResponse) {
option (google.api.http).get = "/quicksilver/supply/v1/supply";
}

rpc TopN(QueryTopNRequest) returns (QueryTopNResponse) {
option (google.api.http).get = "/quicksilver/supply/v1/topn/{n}";
}
}

message QuerySupplyRequest {}
message QuerySupplyResponse {
uint64 supply = 1;
uint64 circulating_supply = 2;
string supply = 1 [
(cosmos_proto.scalar) = "cosmos.Int",
(gogoproto.customtype) = "cosmossdk.io/math.Int",
(gogoproto.nullable) = false
];
string circulating_supply = 2 [
(cosmos_proto.scalar) = "cosmos.Int",
(gogoproto.customtype) = "cosmossdk.io/math.Int",
(gogoproto.nullable) = false
];
}

message Account {
string address = 1;
string balance = 2 [
(cosmos_proto.scalar) = "cosmos.Int",
(gogoproto.customtype) = "cosmossdk.io/math.Int",
(gogoproto.nullable) = false
];
}

message QueryTopNRequest {
uint64 n = 1;
}
message QueryTopNResponse {
repeated Account accounts = 1;
}
14 changes: 13 additions & 1 deletion x/supply/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,19 @@ func (q Querier) Supply(c context.Context, _ *types.QuerySupplyRequest) (*types.
"quick1e22za5qrqqp488h5p7vw2pfx8v0y4u444ufeuw", // ingenuity
})

return &types.QuerySupplyResponse{Supply: supply.Amount.Uint64(), CirculatingSupply: circulatingSupply.Uint64()}, nil
return &types.QuerySupplyResponse{Supply: supply.Amount, CirculatingSupply: circulatingSupply}, nil
}
return nil, fmt.Errorf("endpoint disabled")
}

func (q Querier) TopN(c context.Context, req *types.QueryTopNRequest) (*types.QueryTopNResponse, error) {
ctx := sdk.UnwrapSDKContext(c)

if q.endpointEnabled {

accounts := q.Keeper.TopN(ctx, q.stakingKeeper.BondDenom(ctx), req.N)

return &types.QueryTopNResponse{Accounts: accounts}, nil
}
return nil, fmt.Errorf("endpoint disabled")
}
112 changes: 112 additions & 0 deletions x/supply/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package keeper_test

import (
"cosmossdk.io/math"

sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/quicksilver-zone/quicksilver/utils/addressutils"
"github.com/quicksilver-zone/quicksilver/x/supply/keeper"
"github.com/quicksilver-zone/quicksilver/x/supply/types"
)

func (suite *KeeperTestSuite) TestKeeper_Supply_Disabled() {
suite.Run("Params", func() {
ctx := suite.chainA.GetContext()
k := suite.GetQuicksilverApp(suite.chainA).SupplyKeeper
querier := keeper.NewQuerier(k)
_, err := querier.Supply(ctx, &types.QuerySupplyRequest{})
suite.Error(err)
})
}

func (suite *KeeperTestSuite) TestKeeper_Supply() {
suite.Run("Params", func() {
ctx := suite.chainA.GetContext()

k := suite.GetQuicksilverApp(suite.chainA).SupplyKeeper
k.Enable(ctx, true)
s, ok := math.NewIntFromString("100000000000120393424")
suite.True(ok)
cs, ok := math.NewIntFromString("100000000000004000000")
suite.True(ok)
want := types.QuerySupplyResponse{
Supply: s,
CirculatingSupply: cs,
}
querier := keeper.NewQuerier(k)
got, err := querier.Supply(ctx, &types.QuerySupplyRequest{})
suite.NoError(err)
suite.NotNil(got)
suite.Equal(want, *got)
})
}

func (suite *KeeperTestSuite) TestKeeper_Supply_Excluded_Account() {
suite.Run("Params", func() {
ctx := suite.chainA.GetContext()

k := suite.GetQuicksilverApp(suite.chainA).SupplyKeeper
bk := suite.GetQuicksilverApp(suite.chainA).BankKeeper
stk := suite.GetQuicksilverApp(suite.chainA).StakingKeeper
mk := suite.GetQuicksilverApp(suite.chainA).MintKeeper
k.Enable(ctx, true)
s, ok := math.NewIntFromString("100000000000125393424") // this included the 5stake minted below.
suite.True(ok)
cs, ok := math.NewIntFromString("100000000000004000000") // this does not include the new stake, as it is excluded.
suite.True(ok)
addr := addressutils.MustAccAddressFromBech32("quick1yxe3vmd2ypjf0fs4cejnmv2559tqq5x5cc5nyh", "")
amount, ok := math.NewIntFromString("5000000")
suite.True(ok)
// add coins to an excluded account.
err := mk.MintCoins(ctx, sdk.NewCoins(sdk.NewCoin(stk.BondDenom(ctx), amount)))
suite.NoError(err)
err = bk.SendCoinsFromModuleToAccount(ctx, "mint", addr, sdk.NewCoins(sdk.NewCoin(stk.BondDenom(ctx), amount)))
suite.NoError(err)
want := types.QuerySupplyResponse{
Supply: s,
CirculatingSupply: cs,
}
querier := keeper.NewQuerier(k)
got, err := querier.Supply(ctx, &types.QuerySupplyRequest{})
suite.NoError(err)
suite.NotNil(got)
suite.Equal(want, *got)
})
}

func (suite *KeeperTestSuite) TestKeeper_TopN_Disabled() {
suite.Run("Params", func() {
ctx := suite.chainA.GetContext()
k := suite.GetQuicksilverApp(suite.chainA).SupplyKeeper
querier := keeper.NewQuerier(k)
_, err := querier.TopN(ctx, &types.QueryTopNRequest{N: 5})
suite.Error(err)
})
}

func (suite *KeeperTestSuite) TestKeeper_TopN() {
suite.Run("Params", func() {
ctx := suite.chainA.GetContext()

k := suite.GetQuicksilverApp(suite.chainA).SupplyKeeper
k.Enable(ctx, true)
bk := suite.GetQuicksilverApp(suite.chainA).BankKeeper
stk := suite.GetQuicksilverApp(suite.chainA).StakingKeeper
mk := suite.GetQuicksilverApp(suite.chainA).MintKeeper
querier := keeper.NewQuerier(k)
// all accounts (random addrs) have 10000000000000000000 +/- 4 stake. Lets create an account that'll sit at the top of the topN.
addr := addressutils.GenerateAccAddressForTest()
amount, ok := math.NewIntFromString("20000000000000000004")
suite.True(ok)
err := mk.MintCoins(ctx, sdk.NewCoins(sdk.NewCoin(stk.BondDenom(ctx), amount)))
suite.NoError(err)
err = bk.SendCoinsFromModuleToAccount(ctx, "mint", addr, sdk.NewCoins(sdk.NewCoin(stk.BondDenom(ctx), amount)))
suite.NoError(err)
topN, err := querier.TopN(ctx, &types.QueryTopNRequest{N: 5})
suite.NoError(err)
suite.Equal(5, len(topN.Accounts))
suite.Equal(addr.String(), topN.Accounts[0].Address)
suite.Equal(amount, topN.Accounts[0].Balance)
})
}
52 changes: 51 additions & 1 deletion x/supply/keeper/keeper.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package keeper

import (
"bytes"
"fmt"
"sort"

"github.com/tendermint/tendermint/libs/log"

Expand All @@ -13,6 +15,7 @@
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

"github.com/quicksilver-zone/quicksilver/utils/addressutils"
"github.com/quicksilver-zone/quicksilver/x/supply/types"
)

Expand Down Expand Up @@ -62,11 +65,15 @@
return ctx.Logger().With("module", "x/"+types.ModuleName)
}

func (k *Keeper) Enable(ctx sdk.Context, enabled bool) {
k.endpointEnabled = enabled
}

func (k Keeper) CalculateCirculatingSupply(ctx sdk.Context, baseDenom string, excludeAddresses []string) math.Int {
nonCirculating := math.ZeroInt()
k.accountKeeper.IterateAccounts(ctx, func(account authtypes.AccountI) (stop bool) {
for _, addr := range excludeAddresses {
if addr == account.GetAddress().String() {
if bytes.Equal(addressutils.MustAccAddressFromBech32(addr, ""), account.GetAddress()) {
// matched excluded address
nonCirculating = nonCirculating.Add(k.bankKeeper.GetBalance(ctx, account.GetAddress(), baseDenom).Amount)
return false
Expand All @@ -88,3 +95,46 @@

return k.bankKeeper.GetSupply(ctx, baseDenom).Amount.Sub(nonCirculating)
}

func (k Keeper) TopN(ctx sdk.Context, baseDenom string, n uint64) []*types.Account {
accountMap := map[string]math.Int{}

modMap := map[string]bool{}

for _, mod := range k.moduleAccounts {
modMap[k.accountKeeper.GetModuleAddress(mod).String()] = true
}

k.accountKeeper.IterateAccounts(ctx, func(account authtypes.AccountI) (stop bool) {
if modMap[account.GetAddress().String()] {
return false
}
balance := k.bankKeeper.GetBalance(ctx, account.GetAddress(), baseDenom).Amount
accountMap[account.GetAddress().String()] = balance
return false
})

k.stakingKeeper.IterateAllDelegations(ctx, func(delegation stakingtypes.Delegation) (stop bool) {
if modMap[delegation.GetDelegatorAddr().String()] {
return false
}

Check warning on line 120 in x/supply/keeper/keeper.go

View check run for this annotation

Codecov / codecov/patch

x/supply/keeper/keeper.go#L119-L120

Added lines #L119 - L120 were not covered by tests
balance := delegation.GetShares().TruncateInt()
accountMap[delegation.GetDelegatorAddr().String()] = accountMap[delegation.GetDelegatorAddr().String()].Add(balance)
return false
})

accountSlice := []*types.Account{}
for addr, balance := range accountMap {
accountSlice = append(accountSlice, &types.Account{Address: addr, Balance: balance})
}

sort.Slice(accountSlice, func(i, j int) bool {
return accountSlice[i].Balance.GT(accountSlice[j].Balance)
})

if n > uint64(len(accountSlice)) {
n = uint64(len(accountSlice))
}

Check warning on line 137 in x/supply/keeper/keeper.go

View check run for this annotation

Codecov / codecov/patch

x/supply/keeper/keeper.go#L136-L137

Added lines #L136 - L137 were not covered by tests

return accountSlice[:n]
}
Loading
Loading