Skip to content

Add an new endpoint for querying contracts by code id and creator #2317

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
40 changes: 40 additions & 0 deletions docs/proto/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@
- [QueryContractHistoryResponse](#cosmwasm.wasm.v1.QueryContractHistoryResponse)
- [QueryContractInfoRequest](#cosmwasm.wasm.v1.QueryContractInfoRequest)
- [QueryContractInfoResponse](#cosmwasm.wasm.v1.QueryContractInfoResponse)
- [QueryContractsByCodeAndCreatorRequest](#cosmwasm.wasm.v1.QueryContractsByCodeAndCreatorRequest)
- [QueryContractsByCodeAndCreatorResponse](#cosmwasm.wasm.v1.QueryContractsByCodeAndCreatorResponse)
- [QueryContractsByCodeRequest](#cosmwasm.wasm.v1.QueryContractsByCodeRequest)
- [QueryContractsByCodeResponse](#cosmwasm.wasm.v1.QueryContractsByCodeResponse)
- [QueryContractsByCreatorRequest](#cosmwasm.wasm.v1.QueryContractsByCreatorRequest)
Expand Down Expand Up @@ -1233,6 +1235,43 @@ method



<a name="cosmwasm.wasm.v1.QueryContractsByCodeAndCreatorRequest"></a>

### QueryContractsByCodeAndCreatorRequest
QueryContractsByCodeAndCreatorRequest is the request type for the
Query/ContractsByCodeAndCreator RPC method.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `creator_address` | [string](#string) | | CreatorAddress is the address of contract creator |
| `code_id` | [uint64](#uint64) | | CodeID is the code id of the contract

grpc-gateway_out does not support Go style CodeID |
| `pagination` | [cosmos.base.query.v1beta1.PageRequest](#cosmos.base.query.v1beta1.PageRequest) | | Pagination defines an optional pagination for the request. |






<a name="cosmwasm.wasm.v1.QueryContractsByCodeAndCreatorResponse"></a>

### QueryContractsByCodeAndCreatorResponse
QueryContractsByCodeAndCreatorResponse is the response type for the
Query/ContractsByCodeAndCreator RPC method.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `contract_addresses` | [string](#string) | repeated | ContractAddresses result set |
| `pagination` | [cosmos.base.query.v1beta1.PageResponse](#cosmos.base.query.v1beta1.PageResponse) | | Pagination defines the pagination in the response. |






<a name="cosmwasm.wasm.v1.QueryContractsByCodeRequest"></a>

### QueryContractsByCodeRequest
Expand Down Expand Up @@ -1478,6 +1517,7 @@ Query provides defines the gRPC querier service
| `PinnedCodes` | [QueryPinnedCodesRequest](#cosmwasm.wasm.v1.QueryPinnedCodesRequest) | [QueryPinnedCodesResponse](#cosmwasm.wasm.v1.QueryPinnedCodesResponse) | PinnedCodes gets the pinned code ids | GET|/cosmwasm/wasm/v1/codes/pinned|
| `Params` | [QueryParamsRequest](#cosmwasm.wasm.v1.QueryParamsRequest) | [QueryParamsResponse](#cosmwasm.wasm.v1.QueryParamsResponse) | Params gets the module params | GET|/cosmwasm/wasm/v1/codes/params|
| `ContractsByCreator` | [QueryContractsByCreatorRequest](#cosmwasm.wasm.v1.QueryContractsByCreatorRequest) | [QueryContractsByCreatorResponse](#cosmwasm.wasm.v1.QueryContractsByCreatorResponse) | ContractsByCreator gets the contracts by creator | GET|/cosmwasm/wasm/v1/contracts/creator/{creator_address}|
| `ContractsByCodeAndCreator` | [QueryContractsByCodeAndCreatorRequest](#cosmwasm.wasm.v1.QueryContractsByCodeAndCreatorRequest) | [QueryContractsByCodeAndCreatorResponse](#cosmwasm.wasm.v1.QueryContractsByCodeAndCreatorResponse) | ContractsByCodeAndCreator gets the contracts by code and creator | GET|/cosmwasm/wasm/v1/contracts/code_and_creator/{creator_address}/{code_id}|
| `WasmLimitsConfig` | [QueryWasmLimitsConfigRequest](#cosmwasm.wasm.v1.QueryWasmLimitsConfigRequest) | [QueryWasmLimitsConfigResponse](#cosmwasm.wasm.v1.QueryWasmLimitsConfigResponse) | WasmLimitsConfig gets the configured limits for static validation of Wasm files, encoded in JSON. | GET|/cosmwasm/wasm/v1/wasm-limits-config|
| `BuildAddress` | [QueryBuildAddressRequest](#cosmwasm.wasm.v1.QueryBuildAddressRequest) | [QueryBuildAddressResponse](#cosmwasm.wasm.v1.QueryBuildAddressResponse) | BuildAddress builds a contract address | GET|/cosmwasm/wasm/v1/contract/build_address|

Expand Down
30 changes: 30 additions & 0 deletions proto/cosmwasm/wasm/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ service Query {
"/cosmwasm/wasm/v1/contracts/creator/{creator_address}";
}

// ContractsByCodeAndCreator gets the contracts by code and creator
rpc ContractsByCodeAndCreator(QueryContractsByCodeAndCreatorRequest)
returns (QueryContractsByCodeAndCreatorResponse) {
option (cosmos.query.v1.module_query_safe) = true;
option (google.api.http).get =
"/cosmwasm/wasm/v1/contracts/code_and_creator/{creator_address}/"
"{code_id}";
}

// WasmLimitsConfig gets the configured limits for static validation of Wasm
// files, encoded in JSON.
rpc WasmLimitsConfig(QueryWasmLimitsConfigRequest)
Expand Down Expand Up @@ -324,6 +333,27 @@ message QueryContractsByCreatorResponse {
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

// QueryContractsByCodeAndCreatorRequest is the request type for the
// Query/ContractsByCodeAndCreator RPC method.
message QueryContractsByCodeAndCreatorRequest {
// CreatorAddress is the address of contract creator
string creator_address = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
// CodeID is the code id of the contract
uint64 code_id = 2; // grpc-gateway_out does not support Go style CodeID
// Pagination defines an optional pagination for the request.
cosmos.base.query.v1beta1.PageRequest pagination = 3;
}

// QueryContractsByCodeAndCreatorResponse is the response type for the
// Query/ContractsByCodeAndCreator RPC method.
message QueryContractsByCodeAndCreatorResponse {
// ContractAddresses result set
repeated string contract_addresses = 1
[ (cosmos_proto.scalar) = "cosmos.AddressString" ];
// Pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

// QueryWasmLimitsConfigRequest is the request type for the
// Query/WasmLimitsConfig RPC method.
message QueryWasmLimitsConfigRequest {}
Expand Down
43 changes: 43 additions & 0 deletions x/wasm/keeper/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,49 @@
}, nil
}

func (q GrpcQuerier) ContractsByCodeAndCreator(c context.Context, req *types.QueryContractsByCodeAndCreatorRequest) (*types.QueryContractsByCodeAndCreatorResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}
paginationParams, err := ensurePaginationParams(req.Pagination)
if err != nil {
return nil, err
}

ctx := sdk.UnwrapSDKContext(c)
contracts := make([]string, 0)

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

codeIdPrefixStore := prefix.NewStore(runtime.KVStoreAdapter(q.storeService.OpenKVStore(ctx)), types.GetContractByCodeIDSecondaryIndexPrefix(req.CodeId))
creatorPrefixStore := prefix.NewStore(runtime.KVStoreAdapter(q.storeService.OpenKVStore(ctx)), types.GetContractsByCreatorPrefix(creatorAddress))

// Find contracts that match both creator and code ID
pageRes, err := query.FilteredPaginate(codeIdPrefixStore, paginationParams, func(key, _ []byte, accumulate bool) (bool, error) {
if accumulate {
// Extract contract address from creator store key
contractAddr := key[types.AbsoluteTxPositionLen:]

// Check if this contract exists in the code ID store
if creatorPrefixStore.Has(key) {
contracts = append(contracts, sdk.AccAddress(contractAddr).String())
}
}
return true, nil
})
if err != nil {
return nil, err

Check warning on line 459 in x/wasm/keeper/querier.go

View check run for this annotation

Codecov / codecov/patch

x/wasm/keeper/querier.go#L459

Added line #L459 was not covered by tests
}

return &types.QueryContractsByCodeAndCreatorResponse{
ContractAddresses: contracts,
Pagination: pageRes,
}, nil
}

// max limit to pagination queries
const maxResultEntries = 100

Expand Down
106 changes: 106 additions & 0 deletions x/wasm/keeper/querier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,112 @@ func TestQueryContractsByCreatorList(t *testing.T) {
}
}

func TestQueryContractsByCodeAndCreatorList(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)

deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 1000000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 500))
creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...)
anyAddr := keepers.Faucet.NewFundedRandomAccount(ctx, topUp...)

wasmCode, err := os.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)

codeID, _, err := keepers.ContractKeeper.Create(ctx, creator, wasmCode, nil)
require.NoError(t, err)

_, bob := keyPubAddr()
initMsg := HackatomExampleInitMsg{
Verifier: anyAddr,
Beneficiary: bob,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)

// manage some realistic block settings
var h int64 = 10
setBlock := func(ctx sdk.Context, height int64) sdk.Context {
ctx = ctx.WithBlockHeight(height)
meter := storetypes.NewGasMeter(1000000)
ctx = ctx.WithGasMeter(meter)
ctx = ctx.WithBlockGasMeter(meter)
return ctx
}

var allExpectedContracts []string
// create 10 contracts with real block/gas setup
for i := 0; i < 10; i++ {
ctx = setBlock(ctx, h)
h++
contract, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, initMsgBz, fmt.Sprintf("contract %d", i), topUp)
allExpectedContracts = append(allExpectedContracts, contract.String())
require.NoError(t, err)
}

specs := map[string]struct {
srcQuery *types.QueryContractsByCodeAndCreatorRequest
expContractAddr []string
expErr error
}{
"query all": {
srcQuery: &types.QueryContractsByCodeAndCreatorRequest{
CodeId: codeID,
CreatorAddress: creator.String(),
},
expContractAddr: allExpectedContracts,
expErr: nil,
},
"with pagination offset": {
srcQuery: &types.QueryContractsByCodeAndCreatorRequest{
CodeId: codeID,
CreatorAddress: creator.String(),
Pagination: &query.PageRequest{
Offset: 1,
},
},
expErr: errLegacyPaginationUnsupported,
},
"with pagination limit": {
srcQuery: &types.QueryContractsByCodeAndCreatorRequest{
CodeId: codeID,
CreatorAddress: creator.String(),
Pagination: &query.PageRequest{
Limit: 1,
},
},
expContractAddr: allExpectedContracts[0:1],
expErr: nil,
},
"nil creator": {
srcQuery: &types.QueryContractsByCodeAndCreatorRequest{
Pagination: &query.PageRequest{},
},
expContractAddr: allExpectedContracts,
expErr: errors.New("empty address string is not allowed"),
},
"nil req": {
srcQuery: nil,
expContractAddr: allExpectedContracts,
expErr: status.Error(codes.InvalidArgument, "empty request"),
},
}

q := Querier(keepers.WasmKeeper)
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
got, gotErr := q.ContractsByCodeAndCreator(ctx, spec.srcQuery)
if spec.expErr != nil {
require.Error(t, gotErr)
assert.ErrorContains(t, gotErr, spec.expErr.Error())
return
}
require.NoError(t, gotErr)
require.NotNil(t, got)
assert.Equal(t, spec.expContractAddr, got.ContractAddresses)
})
}
}

func fromBase64(s string) []byte {
r, err := base64.StdEncoding.DecodeString(s)
if err != nil {
Expand Down
Loading
Loading