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(evmutil): Add DeployedCosmosCoinContracts query #1605

Merged
merged 11 commits into from
Jun 2, 2023
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
- (evmutil) [#1598] Track deployed ERC20 contract addresses for representing cosmos coins in module state
- (evmutil) [#1603] Add MsgConvertCosmosCoinToERC20 for converting an sdk.Coin to an ERC20 in the EVM
- (evmutil) [#1604] Emit events for MsgConvertCosmosCoinToERC20: `message` & `convert_cosmos_coin_to_erc20`
- (evmutil) [#1605] Add query for deployed ERC20 contracts representing Cosmos coins in the EVM

### Client Breaking
- (evmutil) [#1603] Renamed error `ErrConversionNotEnabled` to `ErrEVMConversionNotEnabled`
Expand Down Expand Up @@ -250,6 +251,7 @@ the [changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.38.4/CHANGELOG.md).
- [#257](https://github.com/Kava-Labs/kava/pulls/257) Include scripts to run
large-scale simulations remotely using aws-batch

[#1605]: https://github.com/Kava-Labs/kava/pull/1605
[#1604]: https://github.com/Kava-Labs/kava/pull/1604
[#1603]: https://github.com/Kava-Labs/kava/pull/1603
[#1598]: https://github.com/Kava-Labs/kava/pull/1598
Expand Down
52 changes: 52 additions & 0 deletions docs/core/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,9 @@
- [Params](#kava.evmutil.v1beta1.Params)

- [kava/evmutil/v1beta1/query.proto](#kava/evmutil/v1beta1/query.proto)
- [DeployedCosmosCoinContract](#kava.evmutil.v1beta1.DeployedCosmosCoinContract)
- [QueryDeployedCosmosCoinContractsRequest](#kava.evmutil.v1beta1.QueryDeployedCosmosCoinContractsRequest)
- [QueryDeployedCosmosCoinContractsResponse](#kava.evmutil.v1beta1.QueryDeployedCosmosCoinContractsResponse)
- [QueryParamsRequest](#kava.evmutil.v1beta1.QueryParamsRequest)
- [QueryParamsResponse](#kava.evmutil.v1beta1.QueryParamsResponse)

Expand Down Expand Up @@ -3779,6 +3782,54 @@ Params defines the evmutil module params



<a name="kava.evmutil.v1beta1.DeployedCosmosCoinContract"></a>

### DeployedCosmosCoinContract
DeployedCosmosCoinContract defines a deployed token contract to the evm representing a native cosmos-sdk coin


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `cosmos_denom` | [string](#string) | | |
| `address` | [string](#string) | | |






<a name="kava.evmutil.v1beta1.QueryDeployedCosmosCoinContractsRequest"></a>

### QueryDeployedCosmosCoinContractsRequest
QueryDeployedCosmosCoinContractsRequest defines the request type for Query/DeployedCosmosCoinContracts method.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `cosmos_denoms` | [string](#string) | repeated | optional query param to only return specific denoms in the list denoms that do not have deployed contracts will be omitted from the result must request fewer than 100 denoms at a time. |
| `pagination` | [cosmos.base.query.v1beta1.PageRequest](#cosmos.base.query.v1beta1.PageRequest) | | pagination defines an optional pagination for the request. |






<a name="kava.evmutil.v1beta1.QueryDeployedCosmosCoinContractsResponse"></a>

### QueryDeployedCosmosCoinContractsResponse
QueryDeployedCosmosCoinContractsResponse defines the response type for the Query/DeployedCosmosCoinContracts method.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `deployed_cosmos_coin_contracts` | [DeployedCosmosCoinContract](#kava.evmutil.v1beta1.DeployedCosmosCoinContract) | repeated | deployed_cosmos_coin_contracts is a list of cosmos-sdk coin denom and its deployed contract address |
| `pagination` | [cosmos.base.query.v1beta1.PageResponse](#cosmos.base.query.v1beta1.PageResponse) | | pagination defines the pagination in the response. |






<a name="kava.evmutil.v1beta1.QueryParamsRequest"></a>

### QueryParamsRequest
Expand Down Expand Up @@ -3818,6 +3869,7 @@ Query defines the gRPC querier service for evmutil module
| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint |
| ----------- | ------------ | ------------- | ------------| ------- | -------- |
| `Params` | [QueryParamsRequest](#kava.evmutil.v1beta1.QueryParamsRequest) | [QueryParamsResponse](#kava.evmutil.v1beta1.QueryParamsResponse) | Params queries all parameters of the evmutil module. | GET|/kava/evmutil/v1beta1/params|
| `DeployedCosmosCoinContracts` | [QueryDeployedCosmosCoinContractsRequest](#kava.evmutil.v1beta1.QueryDeployedCosmosCoinContractsRequest) | [QueryDeployedCosmosCoinContractsResponse](#kava.evmutil.v1beta1.QueryDeployedCosmosCoinContractsResponse) | DeployedCosmosCoinContracts queries a list cosmos coin denom and their deployed erc20 address | GET|/kava/evmutil/v1beta1/deployed_cosmos_coin_contracts|

<!-- end services -->

Expand Down
37 changes: 35 additions & 2 deletions proto/kava/evmutil/v1beta1/query.proto
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
syntax = "proto3";
package kava.evmutil.v1beta1;

import "cosmos/base/query/v1beta1/pagination.proto";
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "kava/evmutil/v1beta1/genesis.proto";

option go_package = "github.com/kava-labs/kava/x/evmutil/types";
option (gogoproto.equal_all) = true;
option (gogoproto.verbose_equal_all) = true;

// Query defines the gRPC querier service for evmutil module
service Query {
// Params queries all parameters of the evmutil module.
rpc Params(QueryParamsRequest) returns (QueryParamsResponse) {
option (google.api.http).get = "/kava/evmutil/v1beta1/params";
}

// DeployedCosmosCoinContracts queries a list cosmos coin denom and their deployed erc20 address
rpc DeployedCosmosCoinContracts(QueryDeployedCosmosCoinContractsRequest) returns (QueryDeployedCosmosCoinContractsResponse) {
option (google.api.http).get = "/kava/evmutil/v1beta1/deployed_cosmos_coin_contracts";
}
}

// QueryParamsRequest defines the request type for querying x/evmutil parameters.
Expand All @@ -24,3 +28,32 @@ message QueryParamsRequest {}
message QueryParamsResponse {
Params params = 1 [(gogoproto.nullable) = false];
}

// QueryDeployedCosmosCoinContractsRequest defines the request type for Query/DeployedCosmosCoinContracts method.
message QueryDeployedCosmosCoinContractsRequest {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

// optional query param to only return specific denoms in the list
// denoms that do not have deployed contracts will be omitted from the result
// must request fewer than 100 denoms at a time.
repeated string cosmos_denoms = 1;

// pagination defines an optional pagination for the request.
cosmos.base.query.v1beta1.PageRequest pagination = 2;
}

// QueryDeployedCosmosCoinContractsResponse defines the response type for the Query/DeployedCosmosCoinContracts method.
message QueryDeployedCosmosCoinContractsResponse {
// deployed_cosmos_coin_contracts is a list of cosmos-sdk coin denom and its deployed contract address
repeated DeployedCosmosCoinContract deployed_cosmos_coin_contracts = 1 [(gogoproto.nullable) = false];

// pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

// DeployedCosmosCoinContract defines a deployed token contract to the evm representing a native cosmos-sdk coin
message DeployedCosmosCoinContract {
string cosmos_denom = 1;
string address = 2 [(gogoproto.customtype) = "InternalEVMAddress"];
}
42 changes: 42 additions & 0 deletions x/evmutil/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/types/query"
"github.com/cosmos/cosmos-sdk/version"
"github.com/spf13/cobra"

Expand All @@ -24,6 +25,7 @@ func GetQueryCmd() *cobra.Command {

cmds := []*cobra.Command{
QueryParamsCmd(),
QueryDeployedCosmosCoinContractsCmd(),
}

for _, cmd := range cmds {
Expand Down Expand Up @@ -61,3 +63,43 @@ func QueryParamsCmd() *cobra.Command {
},
}
}

func QueryDeployedCosmosCoinContractsCmd() *cobra.Command {
var cosmosDenoms []string
cmdName := "deployed-cosmos-coin-contracts"
q := fmt.Sprintf("%[1]s q %[2]s %s", version.AppName, types.ModuleName, cmdName)
cmd := &cobra.Command{
Use: fmt.Sprintf("%s [--denoms denom1,denom2] [flags]", cmdName),
Short: "Query for deployed ERC20 contract addresses representing cosmos coins in the EVM",
Example: fmt.Sprintf("Query all:\n %s\n\nQuery by denom:\n %s --denoms denom1,denom2", q, q),
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}

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

queryClient := types.NewQueryClient(clientCtx)
request := types.QueryDeployedCosmosCoinContractsRequest{
CosmosDenoms: cosmosDenoms,
Pagination: page,
}
res, err := queryClient.DeployedCosmosCoinContracts(context.Background(), &request)
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddPaginationFlagsToCmd(cmd, cmdName)
cmd.Flags().StringSliceVar(&cosmosDenoms, "denoms", []string{}, fmt.Sprintf("(optional) Cosmos denoms to get addresses for. If no contract is deployed, the result will be omitted. Limit %d per query.", query.DefaultLimit))

return cmd
}
78 changes: 78 additions & 0 deletions x/evmutil/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"

"github.com/kava-labs/kava/x/evmutil/types"
)
Expand All @@ -33,3 +35,79 @@ func (s queryServer) Params(stdCtx context.Context, req *types.QueryParamsReques

return &types.QueryParamsResponse{Params: params}, nil
}

// DeployedCosmosCoinContracts gets contract addresses for deployed erc20 contracts
// representing cosmos-sdk coins
func (s queryServer) DeployedCosmosCoinContracts(
goCtx context.Context,
req *types.QueryDeployedCosmosCoinContractsRequest,
) (res *types.QueryDeployedCosmosCoinContractsResponse, err error) {
if req == nil {
return nil, status.Errorf(codes.InvalidArgument, "empty request")
}

ctx := sdk.UnwrapSDKContext(goCtx)
if len(req.CosmosDenoms) > 0 {
res, err = getDeployedCosmosCoinContractsByDenoms(&s.keeper, ctx, req.CosmosDenoms)
} else {
// requesting no sdk denoms is a request for all denoms
res, err = getAllDeployedCosmosCoinContractsPage(&s.keeper, ctx, req.Pagination)
}

return res, err
}

// getAllDeployedCosmosCoinContractsPage gets a page of deployed contracts (no filtering)
func getAllDeployedCosmosCoinContractsPage(
k *Keeper, ctx sdk.Context, pagination *query.PageRequest,
) (*types.QueryDeployedCosmosCoinContractsResponse, error) {
contracts := make([]types.DeployedCosmosCoinContract, 0)
contractStore := prefix.NewStore(
ctx.KVStore(k.storeKey),
types.DeployedCosmosCoinContractKeyPrefix,
)

pageRes, err := query.FilteredPaginate(contractStore, pagination,
func(key []byte, value []byte, accumulate bool) (bool, error) {
if !accumulate {
return true, nil
}
address := types.BytesToInternalEVMAddress(value)
contract := types.DeployedCosmosCoinContract{
CosmosDenom: string(key),
Address: &address,
}
contracts = append(contracts, contract)
return true, nil
})
if err != nil {
return &types.QueryDeployedCosmosCoinContractsResponse{}, err
}

return &types.QueryDeployedCosmosCoinContractsResponse{
DeployedCosmosCoinContracts: contracts,
Pagination: pageRes,
}, nil
}

func getDeployedCosmosCoinContractsByDenoms(
k *Keeper, ctx sdk.Context, denoms []string,
) (*types.QueryDeployedCosmosCoinContractsResponse, error) {
if len(denoms) > query.DefaultLimit {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice glad this is limited and tested. This is a user parameter that causes a db lookup, so without a limit this endpoint could be used for a denial of service.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation is an interesting problem. It feels like there are cases where a range & filter would be superior over direct lookups, but definitely not for small numbers of filters.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, range & filter would allow pagination on this endpoint when requested with a >100 denoms query, too. but i made the tradeoff with the assumption that the most common use cases were either

  1. looking up all contract addresses (no denoms in query)
  2. looking up contract address for only a handful of contracts (limited number of denoms in query)

i think the majority of the time, 2) is going to be more efficient with direct lookups. a larger factor was that this split between a method that paginates all results & a method that only does direct lookups on a list felt easier to read in the code than the single PaginateFilter that handled both cases.
a nice bonus is that denoms that exist are returned in the order they were requested in (though omitting missing denoms mean there's no guarantee that search index == result index)

// forego dealing with pagination by rejecting reqs for >100 denoms
return nil, status.Errorf(codes.InvalidArgument, "maximum of %d denoms allowed per request", query.DefaultLimit)
}

contracts := make([]types.DeployedCosmosCoinContract, 0, len(denoms))
for _, denom := range denoms {
address, found := k.GetDeployedCosmosCoinContract(ctx, denom)
if !found {
continue
}
contracts = append(contracts, types.NewDeployedCosmosCoinContract(denom, address))
}

return &types.QueryDeployedCosmosCoinContractsResponse{
DeployedCosmosCoinContracts: contracts,
}, nil
}
Loading