Skip to content

Commit

Permalink
feat(wasmbinding)!: whitelisted stargate queries for QueryRequest::St…
Browse files Browse the repository at this point in the history
…argate: auth, bank, gov, tokenfactory, epochs, inflation, oracle, sudo, devgas (#1646)

* fix(tokenfactory)!: Fix bug in MsgBurn on total supply tracking

* chore: rm stablecoin. How does this keep getting merged lol

* test: add export statements for the gRPC query service descriptions in each module

* feat(wasmbinding): whitelisted stargate queries for QueryRequest::Stargate

* changelog

* fix changelog

* refactor!: make the epoch infos name consistent

* docs,test(stargate_query): leave an in-depth explainer above the function

* refactor: pR comments: earlier return + remove duplicate hardcoded paths

* test: proto package may have more than 3 'parts'. Use len - 1 instead

* docs: fix small documentation typos
  • Loading branch information
Unique-Divine authored Oct 25, 2023
1 parent ac000f2 commit 2ae0475
Show file tree
Hide file tree
Showing 28 changed files with 450 additions and 8,058 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#1616](https://github.com/NibiruChain/nibiru/pull/1616) - fix(app)!: Add custom wasm snapshotter for proper state exports
* [#1617](https://github.com/NibiruChain/nibiru/pull/1617) - fix(app)!: non-nil snapshot manager is not guaranteed in testapp
* [#1645](https://github.com/NibiruChain/nibiru/pull/1645) - fix(tokenfactory)!: token supply in bank keeper must be correct after MsgBurn.
* [#1646](https://github.com/NibiruChain/nibiru/pull/1646) - feat(wasmbinding)!: whitelisted stargate queries for QueryRequest::Stargate: auth, bank, gov, tokenfactory, epochs, inflation, oracle, sudo, devgas

### Improvements

Expand Down
4 changes: 3 additions & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,9 @@ func GetWasmOpts(nibiru NibiruApp, appOpts servertypes.AppOptions) []wasm.Option
}

// Add the bindings to the app's set of []wasm.Option.
wasmOpts = append(wasmOpts, wasmbinding.RegisterWasmOptions(
wasmOpts = append(wasmOpts, wasmbinding.NibiruWasmOptions(
nibiru.GRPCQueryRouter(),
nibiru.appCodec,
nibiru.PerpKeeperV2,
nibiru.SudoKeeper,
nibiru.OracleKeeper,
Expand Down
12 changes: 6 additions & 6 deletions app/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ type AppKeepers struct {
the app, so we can SetRouter on it correctly. */
ibcKeeper *ibckeeper.Keeper
ibcFeeKeeper ibcfeekeeper.Keeper
/* transferKeeper is for cross-chain fungible token transfers. */
transferKeeper ibctransferkeeper.Keeper
/* ibcTransferKeeper is for cross-chain fungible token transfers. */
ibcTransferKeeper ibctransferkeeper.Keeper

// make scoped keepers public for test purposes
ScopedIBCKeeper capabilitykeeper.ScopedKeeper
Expand Down Expand Up @@ -434,7 +434,7 @@ func (app *NibiruApp) InitKeepers(
app.ibcKeeper.ChannelKeeper,
&app.ibcKeeper.PortKeeper,
app.ScopedWasmKeeper,
app.transferKeeper,
app.ibcTransferKeeper,
app.MsgServiceRouter(),
app.GRPCQueryRouter(),
wasmDir,
Expand Down Expand Up @@ -478,7 +478,7 @@ func (app *NibiruApp) InitKeepers(
/* Create IBC module and a static IBC router */
ibcRouter := porttypes.NewRouter()

app.transferKeeper = ibctransferkeeper.NewKeeper(
app.ibcTransferKeeper = ibctransferkeeper.NewKeeper(
appCodec,
keys[ibctransfertypes.StoreKey],
/* paramSubspace */ app.GetSubspace(ibctransfertypes.ModuleName),
Expand Down Expand Up @@ -508,7 +508,7 @@ func (app *NibiruApp) InitKeepers(

// create IBC module from bottom to top of stack
var transferStack porttypes.IBCModule
transferStack = ibctransfer.NewIBCModule(app.transferKeeper)
transferStack = ibctransfer.NewIBCModule(app.ibcTransferKeeper)
transferStack = ibcfee.NewIBCMiddleware(transferStack, app.ibcFeeKeeper)

// Add transfer stack to IBC Router
Expand Down Expand Up @@ -599,7 +599,7 @@ func (app *NibiruApp) initAppModules(
// ibc
evidence.NewAppModule(app.evidenceKeeper),
ibc.NewAppModule(app.ibcKeeper),
ibctransfer.NewAppModule(app.transferKeeper),
ibctransfer.NewAppModule(app.ibcTransferKeeper),
ibcfee.NewAppModule(app.ibcFeeKeeper),

// wasm
Expand Down
6 changes: 3 additions & 3 deletions proto/nibiru/epochs/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ option go_package = "github.com/NibiruChain/nibiru/x/epochs/types";
// Query defines the gRPC querier service.
service Query {
// EpochInfos provide running epochInfos
rpc EpochInfos(QueryEpochsInfoRequest) returns (QueryEpochsInfoResponse) {
rpc EpochInfos(QueryEpochInfosRequest) returns (QueryEpochInfosResponse) {
option (google.api.http).get = "/nibiru/epochs/v1beta1/epochs";
}
// CurrentEpoch provide current epoch of specified identifier
Expand All @@ -21,8 +21,8 @@ service Query {
}
}

message QueryEpochsInfoRequest {}
message QueryEpochsInfoResponse {
message QueryEpochInfosRequest {}
message QueryEpochInfosResponse {
repeated nibiru.epochs.v1.EpochInfo epochs = 1
[ (gogoproto.nullable) = false ];
}
Expand Down
150 changes: 150 additions & 0 deletions wasmbinding/stargate_query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package wasmbinding

import (
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"

devgas "github.com/NibiruChain/nibiru/x/devgas/v1/types"
epochs "github.com/NibiruChain/nibiru/x/epochs/types"
inflation "github.com/NibiruChain/nibiru/x/inflation/types"
oracle "github.com/NibiruChain/nibiru/x/oracle/types"
sudotypes "github.com/NibiruChain/nibiru/x/sudo/types"
tokenfactory "github.com/NibiruChain/nibiru/x/tokenfactory/types"

auth "github.com/cosmos/cosmos-sdk/x/auth/types"
bank "github.com/cosmos/cosmos-sdk/x/bank/types"
gov "github.com/cosmos/cosmos-sdk/x/gov/types/v1"

ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types"
ibcclienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types"
ibcconnectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types"
)

/*
WasmAcceptedStargateQueries: Specifies which `QueryRequest::Stargate` types
can be sent to the application.
### On Stargate Queries:
A Stargate query is encoded the same way as abci_query, with path and protobuf
encoded request data. The format is defined in
[ADR-21](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-021-protobuf-query-encoding.md).
- The response is protobuf encoded data directly without a JSON response wrapper.
The caller is responsible for compiling the proper protobuf definitions for both
requests and responses.
```rust
enum QueryRequest {
Stargate {
/// this is the fully qualified service path used for routing,
/// eg. custom/cosmos_sdk.x.bank.v1.Query/QueryBalance
path: String,
/// this is the expected protobuf message type (not any), binary encoded
data: Binary,
},
// ...
}
```
### Relationship with Protobuf Message:
A protobuf message with type URL "/cosmos.bank.v1beta1.QueryBalanceResponse"
communicates a lot of information. From this type URL, we know:
- The protobuf message has package "cosmos.bank.v1beta1"
- The protobuf message has name "QueryBalanceResponse"
That is, a type URL is of the form "/[PB_MSG.PACKAGE]/[PB_MSG.NAME]"
The `QueryRequest::Stargate.path` is defined based on method name of the gRPC
service description, not the type URL. In this example:
- The service name is "cosmos.bank.v1beta1.Query"
- The method name for this request on that service is "Balance"
This results in the expected `Stargate.path` of "/[SERVICE_NAME]/[METHOD]".
By convention, the gRPC query service corresponding to a package is always
"[PB_MSG.PACKAGE].Query".
Given only the `PB_MSG.PACKAGE` and the `PB_MSG.NAME` of either the query
request or response, we should know the `QueryRequest::Stargate.path`
deterministically.
*/
func WasmAcceptedStargateQueries() wasmkeeper.AcceptedStargateQueries {
return wasmkeeper.AcceptedStargateQueries{
// ibc
"/ibc.core.client.v1.Query/ClientState": &ibcclienttypes.QueryClientStateResponse{},
"/ibc.core.client.v1.Query/ConsensusState": &ibcclienttypes.QueryConsensusStateResponse{},
"/ibc.core.connection.v1.Query/Connection": &ibcconnectiontypes.QueryConnectionResponse{},
"/ibc.core.connection.v1.Query/Connections": &ibcconnectiontypes.QueryConnectionsResponse{},
"/ibc.core.connection.v1.Query/ClientConnections": &ibcconnectiontypes.QueryClientConnectionsResponse{},
"/ibc.core.connection.v1.Query/ConnectionConsensusState": &ibcconnectiontypes.QueryConnectionConsensusStateResponse{},
"/ibc.core.connection.v1.Query/ConnectionParams": &ibcconnectiontypes.QueryConnectionParamsResponse{},

// ibc transfer
"/ibc.applications.transfer.v1.Query/DenomTrace": &ibctransfertypes.QueryDenomTraceResponse{},
"/ibc.applications.transfer.v1.Query/Params": &ibctransfertypes.QueryParamsResponse{},
"/ibc.applications.transfer.v1.Query/DenomHash": &ibctransfertypes.QueryDenomHashResponse{},
"/ibc.applications.transfer.v1.Query/EscrowAddress": &ibctransfertypes.QueryEscrowAddressResponse{},
"/ibc.applications.transfer.v1.Query/TotalEscrowForDenom": &ibctransfertypes.QueryTotalEscrowForDenomResponse{},

// cosmos auth
"/cosmos.auth.v1beta1.Query/Account": new(auth.QueryAccountResponse),
"/cosmos.auth.v1beta1.Query/Params": new(auth.QueryParamsResponse),

// cosmos bank
"/cosmos.bank.v1beta1.Query/Balance": new(bank.QueryBalanceResponse),
"/cosmos.bank.v1beta1.Query/DenomMetadata": new(bank.QueryDenomMetadataResponse),
"/cosmos.bank.v1beta1.Query/Params": new(bank.QueryParamsResponse),
"/cosmos.bank.v1beta1.Query/SupplyOf": new(bank.QuerySupplyOfResponse),
"/cosmos.bank.v1beta1.Query/AllBalances": new(bank.QueryAllBalancesResponse),

// cosmos gov
"/cosmos.gov.v1.Query/Proposal": new(gov.QueryProposalResponse),
"/cosmos.gov.v1.Query/Params": new(gov.QueryParamsResponse),
"/cosmos.gov.v1.Query/Vote": new(gov.QueryVoteResponse),

// nibiru tokenfactory
"/nibiru.tokenfactory.v1.Query/Denoms": new(tokenfactory.QueryDenomsResponse),
"/nibiru.tokenfactory.v1.Query/Params": new(tokenfactory.QueryParamsResponse),
"/nibiru.tokenfactory.v1.Query/DenomInfo": new(tokenfactory.QueryDenomInfoResponse),

// nibiru epochs
"/nibiru.epochs.v1.Query/EpochInfos": new(epochs.QueryEpochInfosResponse),
"/nibiru.epochs.v1.Query/CurrentEpoch": new(epochs.QueryCurrentEpochResponse),

// nibiru inflation
"/nibiru.inflation.v1.Query/Period": new(inflation.QueryPeriodResponse),
"/nibiru.inflation.v1.Query/EpochMintProvision": new(inflation.QueryEpochMintProvisionResponse),
"/nibiru.inflation.v1.Query/SkippedEpochs": new(inflation.QuerySkippedEpochsResponse),
"/nibiru.inflation.v1.Query/CirculatingSupply": new(inflation.QueryCirculatingSupplyResponse),
"/nibiru.inflation.v1.Query/InflationRate": new(inflation.QueryInflationRateResponse),
"/nibiru.inflation.v1.Query/Params": new(inflation.QueryParamsResponse),

// nibiru oracle
"/nibiru.oracle.v1.Query/ExchangeRate": new(oracle.QueryExchangeRateResponse),
"/nibiru.oracle.v1.Query/ExchangeRateTwap": new(oracle.QueryExchangeRateResponse),
"/nibiru.oracle.v1.Query/ExchangeRates": new(oracle.QueryExchangeRatesResponse),
"/nibiru.oracle.v1.Query/Actives": new(oracle.QueryActivesResponse),
"/nibiru.oracle.v1.Query/VoteTargets": new(oracle.QueryVoteTargetsResponse),
"/nibiru.oracle.v1.Query/FeederDelegation": new(oracle.QueryFeederDelegationResponse),
"/nibiru.oracle.v1.Query/MissCounter": new(oracle.QueryMissCounterResponse),
"/nibiru.oracle.v1.Query/AggregatePrevote": new(oracle.QueryAggregatePrevoteResponse),
"/nibiru.oracle.v1.Query/AggregatePrevotes": new(oracle.QueryAggregatePrevotesResponse),
"/nibiru.oracle.v1.Query/AggregateVote": new(oracle.QueryAggregateVoteResponse),
"/nibiru.oracle.v1.Query/AggregateVotes": new(oracle.QueryAggregateVotesResponse),
"/nibiru.oracle.v1.Query/Params": new(oracle.QueryParamsResponse),

// nibiru sudo
"/nibiru.sudo.v1.Query/QuerySudoers": new(sudotypes.QuerySudoersResponse),

// nibiru devgas
"/nibiru.devgas.v1.Query/FeeShares": new(devgas.QueryFeeSharesResponse),
"/nibiru.devgas.v1.Query/FeeShare": new(devgas.QueryFeeShareResponse),
"/nibiru.devgas.v1.Query/Params": new(devgas.QueryParamsResponse),
"/nibiru.devgas.v1.Query/FeeSharesByWithdrawer": new(devgas.QueryFeeSharesByWithdrawerResponse),

// TODO: for post v1
// nibiru.perp

// TODO: for post v1
// nibiru.spot
}
}
116 changes: 116 additions & 0 deletions wasmbinding/stargate_query_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package wasmbinding_test

import (
"fmt"
"strings"
"testing"

"github.com/cosmos/gogoproto/proto"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc"

"github.com/NibiruChain/nibiru/wasmbinding"

"github.com/NibiruChain/nibiru/x/common/set"

devgas "github.com/NibiruChain/nibiru/x/devgas/v1/types"
epochs "github.com/NibiruChain/nibiru/x/epochs/types"
inflation "github.com/NibiruChain/nibiru/x/inflation/types"
oracle "github.com/NibiruChain/nibiru/x/oracle/types"
sudotypes "github.com/NibiruChain/nibiru/x/sudo/types"
tokenfactory "github.com/NibiruChain/nibiru/x/tokenfactory/types"
)

/*
TestWasmAcceptedStargateQueries: Verifies that the query paths registered in
the Wasm keeper's StargateQuerier are the official method names in the gRPC
query service of each path's respective module.
> ℹ️ "All stargate query paths must be actual GRPC query service methods"
Please see the function doc comment for WasmAcceptedStargateQueries in
stargate_query.go to understand in detail what invariants this test checks
for.
Given only the `PB_MSG.PACKAGE` and the `PB_MSG.NAME` of either the query
request or response, we should know the `QueryRequest::Stargate.path`
deterministically.
*/
func TestWasmAcceptedStargateQueries(t *testing.T) {
t.Log("stargateQueryPaths: Add nibiru query paths from GRPC service descriptions")
queryServiceDescriptions := []grpc.ServiceDesc{
epochs.GrpcQueryServiceDesc(),
devgas.GrpcQueryServiceDesc(),
inflation.GrpcQueryServiceDesc(),
oracle.GrpcQueryServiceDesc(),
sudotypes.GrpcQueryServiceDesc(),
tokenfactory.GrpcQueryServiceDesc(),
}
stargateQueryPaths := set.New[string]()
for _, serviceDesc := range queryServiceDescriptions {
for _, queryMethod := range serviceDesc.Methods {
stargateQueryPaths.Add(
fmt.Sprintf("/%v/%v", serviceDesc.ServiceName, queryMethod.MethodName),
)
}
}

t.Log("stargateQueryPaths: Add cosmos and ibc query paths")
// The GRPC service descriptions aren't exported as copies from the
// Cosmos-SDK and remain private vars. Maybe we could ask the maintainers to
// export them in the future.
for queryPath := range wasmbinding.WasmAcceptedStargateQueries() {
stargateQueryPaths.Add(queryPath)
}

// It's not required for the response type and the method description of the
// stargate query's gRPC path to match up exactly as expected. The exception
// to this convention is when our response type isn't stripped of its
// "Response" suffix and "Query" prefix is not the same as the method name.
// This happens when "QueryAAARequest" does not return a "QueryAAAResponse".
exceptionPaths := set.New[string]("/nibiru.oracle.v1.QueryExchangeRateResponse")

gotQueryPaths := []string{}
for queryPath, protobufResponse := range wasmbinding.WasmAcceptedStargateQueries() {
gotQueryPaths = append(gotQueryPaths, queryPath)

// Show that the underlying protobuf name and query paths coincide.
pbQueryResponseTypeUrl := "/" + proto.MessageName(protobufResponse)
isExceptionPath := exceptionPaths.Has(pbQueryResponseTypeUrl)
splitResponse := strings.Split(pbQueryResponseTypeUrl, "Response")
assert.Lenf(t, splitResponse, 2, "typeUrl: %v",
splitResponse, pbQueryResponseTypeUrl)

// Get proto message "package" from the response type
typeUrlMinusSuffix := splitResponse[0]
typeUrlPartsFromProtoMsg := strings.Split(typeUrlMinusSuffix, ".")
lenOfParts := len(typeUrlPartsFromProtoMsg)
assert.GreaterOrEqual(t, lenOfParts, 4, typeUrlPartsFromProtoMsg)
protoMessagePackage := typeUrlPartsFromProtoMsg[:lenOfParts-1]

// Get proto message "package" from the query path
typeUrlPartsFromQueryPath := strings.Split(queryPath, ".")
assert.GreaterOrEqual(t, len(typeUrlPartsFromQueryPath), 4, typeUrlPartsFromQueryPath)
queryPathProtoPackage := typeUrlPartsFromQueryPath[:lenOfParts-1]

// Verify that the packages match
assert.Equalf(t, queryPathProtoPackage, protoMessagePackage,
"package names inconsistent:\nfrom query path: %v\nfrom protobuf object: %v",
queryPath, pbQueryResponseTypeUrl,
)

// Verify that the method names match too.
if isExceptionPath {
continue
}
methodNameFromPb := strings.TrimLeft(typeUrlPartsFromProtoMsg[3], "Query")
methodNameFromPath := strings.TrimLeft(typeUrlPartsFromQueryPath[3], "Query/")
assert.Equalf(t, methodNameFromPb, methodNameFromPath,
"method names inconsistent:\nfrom query path: %v\nfrom protobuf object: %v",
queryPath, pbQueryResponseTypeUrl,
)
}

t.Log("All stargate query paths must be actual GRPC query service methods")
assert.ElementsMatch(t, stargateQueryPaths.ToSlice(), gotQueryPaths)
}
13 changes: 12 additions & 1 deletion wasmbinding/wasm.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,32 @@ package wasmbinding
import (
"github.com/CosmWasm/wasmd/x/wasm"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"

"github.com/NibiruChain/nibiru/x/sudo/keeper"

oraclekeeper "github.com/NibiruChain/nibiru/x/oracle/keeper"
perpv2keeper "github.com/NibiruChain/nibiru/x/perp/v2/keeper"
)

func RegisterWasmOptions(
// NibiruWasmOptions: Wasm Options are extension points to instantiate the Wasm
// keeper with non-default values
func NibiruWasmOptions(
grpcQueryRouter *baseapp.GRPCQueryRouter,
appCodec codec.Codec,
perpv2 perpv2keeper.Keeper,
sudoKeeper keeper.Keeper,
oracleKeeper oraclekeeper.Keeper,
) []wasm.Option {
wasmQueryPlugin := NewQueryPlugin(perpv2, oracleKeeper)
wasmQueryOption := wasmkeeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{
Custom: CustomQuerier(wasmQueryPlugin),
Stargate: wasmkeeper.AcceptListStargateQuerier(
WasmAcceptedStargateQueries(),
grpcQueryRouter,
appCodec,
),
})

wasmExecuteOption := wasmkeeper.WithMessageHandlerDecorator(
Expand Down
Loading

0 comments on commit 2ae0475

Please sign in to comment.