Skip to content

Commit

Permalink
Add Build Address gRPC Query (#1753)
Browse files Browse the repository at this point in the history
* add and generate build address query protos

* change init args to bytes

* implement BuildAddress grpc query

* Add unit tests

* Update x/wasm/keeper/querier_test.go

Co-authored-by: pinosu <95283998+pinosu@users.noreply.github.com>

* more verbose error messages

* make CmdBuildAddress use query client instead of keeper

* src instead of srcQuery in tests

* add more test cases

* add test for invalid init args

---------

Co-authored-by: pinosu <95283998+pinosu@users.noreply.github.com>
  • Loading branch information
NotJeremyLiu and pinosu authored Jan 9, 2024
1 parent d0dde1a commit 651abcf
Show file tree
Hide file tree
Showing 7 changed files with 911 additions and 110 deletions.
38 changes: 38 additions & 0 deletions docs/proto/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
- [CodeInfoResponse](#cosmwasm.wasm.v1.CodeInfoResponse)
- [QueryAllContractStateRequest](#cosmwasm.wasm.v1.QueryAllContractStateRequest)
- [QueryAllContractStateResponse](#cosmwasm.wasm.v1.QueryAllContractStateResponse)
- [QueryBuildAddressRequest](#cosmwasm.wasm.v1.QueryBuildAddressRequest)
- [QueryBuildAddressResponse](#cosmwasm.wasm.v1.QueryBuildAddressResponse)
- [QueryCodeRequest](#cosmwasm.wasm.v1.QueryCodeRequest)
- [QueryCodeResponse](#cosmwasm.wasm.v1.QueryCodeResponse)
- [QueryCodesRequest](#cosmwasm.wasm.v1.QueryCodesRequest)
Expand Down Expand Up @@ -1018,6 +1020,41 @@ Query/AllContractState RPC method



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

### QueryBuildAddressRequest
QueryBuildAddressRequest is the request type for the Query/BuildAddress RPC
method.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `code_hash` | [string](#string) | | CodeHash is the hash of the code |
| `creator_address` | [string](#string) | | CreatorAddress is the address of the contract instantiator |
| `salt` | [string](#string) | | Salt is a hex encoded salt |
| `init_args` | [bytes](#bytes) | | InitArgs are optional json encoded init args to be used in contract address building if provided |






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

### QueryBuildAddressResponse
QueryBuildAddressResponse is the response type for the Query/BuildAddress RPC
method.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `address` | [string](#string) | | Address is the contract address |






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

### QueryCodeRequest
Expand Down Expand Up @@ -1363,6 +1400,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}|
| `BuildAddress` | [QueryBuildAddressRequest](#cosmwasm.wasm.v1.QueryBuildAddressRequest) | [QueryBuildAddressResponse](#cosmwasm.wasm.v1.QueryBuildAddressResponse) | BuildAddress builds a contract address | GET|/cosmwasm/wasm/v1/contract/build_address|

<!-- end services -->

Expand Down
27 changes: 27 additions & 0 deletions proto/cosmwasm/wasm/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ service Query {
option (google.api.http).get =
"/cosmwasm/wasm/v1/contracts/creator/{creator_address}";
}

// BuildAddress builds a contract address
rpc BuildAddress(QueryBuildAddressRequest)
returns (QueryBuildAddressResponse) {
option (google.api.http).get = "/cosmwasm/wasm/v1/contract/build_address";
}
}

// QueryContractInfoRequest is the request type for the Query/ContractInfo RPC
Expand Down Expand Up @@ -268,4 +274,25 @@ message QueryContractsByCreatorResponse {
[ (cosmos_proto.scalar) = "cosmos.AddressString" ];
// Pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

// QueryBuildAddressRequest is the request type for the Query/BuildAddress RPC
// method.
message QueryBuildAddressRequest {
// CodeHash is the hash of the code
string code_hash = 1;
// CreatorAddress is the address of the contract instantiator
string creator_address = 2 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
// Salt is a hex encoded salt
string salt = 3;
// InitArgs are optional json encoded init args to be used in contract address
// building if provided
bytes init_args = 4;
}

// QueryBuildAddressResponse is the response type for the Query/BuildAddress RPC
// method.
message QueryBuildAddressResponse {
// Address is the contract address
string address = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
}
41 changes: 19 additions & 22 deletions x/wasm/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"github.com/cosmos/cosmos-sdk/client/flags"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/CosmWasm/wasmd/x/wasm/keeper"
"github.com/CosmWasm/wasmd/x/wasm/types"
)

Expand Down Expand Up @@ -78,32 +77,30 @@ func GetCmdBuildAddress() *cobra.Command {
Aliases: []string{"address"},
Args: cobra.RangeArgs(3, 4),
RunE: func(cmd *cobra.Command, args []string) error {
codeHash, err := hex.DecodeString(args[0])
if err != nil {
return fmt.Errorf("code-hash: %s", err)
}
creator, err := sdk.AccAddressFromBech32(args[1])
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return fmt.Errorf("creator: %s", err)
}
salt, err := hex.DecodeString(args[2])
switch {
case err != nil:
return fmt.Errorf("salt: %s", err)
case len(salt) == 0:
return errors.New("empty salt")
return err
}

if len(args) == 3 {
cmd.Println(keeper.BuildContractAddressPredictable(codeHash, creator, salt, []byte{}).String())
return nil
var initArgs []byte
if len(args) == 4 {
initArgs = types.RawContractMessage(args[3])
}
msg := types.RawContractMessage(args[3])
if err := msg.ValidateBasic(); err != nil {
return fmt.Errorf("init message: %s", err)

queryClient := types.NewQueryClient(clientCtx)
res, err := queryClient.BuildAddress(
context.Background(),
&types.QueryBuildAddressRequest{
CodeHash: args[0],
CreatorAddress: args[1],
Salt: args[2],
InitArgs: initArgs,
},
)
if err != nil {
return err
}
cmd.Println(keeper.BuildContractAddressPredictable(codeHash, creator, salt, msg).String())
return nil
return clientCtx.PrintProto(res)
},
SilenceUsage: true,
}
Expand Down
35 changes: 35 additions & 0 deletions x/wasm/keeper/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package keeper
import (
"context"
"encoding/binary"
"encoding/hex"
"fmt"
"runtime/debug"

"google.golang.org/grpc/codes"
Expand Down Expand Up @@ -406,3 +408,36 @@ func ensurePaginationParams(req *query.PageRequest) (*query.PageRequest, error)
}
return req, nil
}

func (q GrpcQuerier) BuildAddress(c context.Context, req *types.QueryBuildAddressRequest) (*types.QueryBuildAddressResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}
codeHash, err := hex.DecodeString(req.CodeHash)
if err != nil {
return nil, fmt.Errorf("invalid code hash: %w", err)
}
creator, err := sdk.AccAddressFromBech32(req.CreatorAddress)
if err != nil {
return nil, fmt.Errorf("invalid creator address: %w", err)
}
salt, err := hex.DecodeString(req.Salt)
if err != nil {
return nil, fmt.Errorf("invalid salt: %w", err)
}
if len(salt) == 0 {
return nil, status.Error(codes.InvalidArgument, "empty salt")
}
if req.InitArgs == nil {
return &types.QueryBuildAddressResponse{
Address: BuildContractAddressPredictable(codeHash, creator, salt, []byte{}).String(),
}, nil
}
initMsg := types.RawContractMessage(req.InitArgs)
if err := initMsg.ValidateBasic(); err != nil {
return nil, err
}
return &types.QueryBuildAddressResponse{
Address: BuildContractAddressPredictable(codeHash, creator, salt, initMsg).String(),
}, nil
}
100 changes: 100 additions & 0 deletions x/wasm/keeper/querier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1047,3 +1047,103 @@ func TestEnsurePaginationParams(t *testing.T) {
})
}
}

func TestQueryBuildAddress(t *testing.T) {
specs := map[string]struct {
src *types.QueryBuildAddressRequest
exp *types.QueryBuildAddressResponse
expErr error
}{
"empty request": {
src: nil,
expErr: status.Error(codes.InvalidArgument, "empty request"),
},
"invalid code hash": {
src: &types.QueryBuildAddressRequest{
CodeHash: "invalid",
CreatorAddress: "cosmos100dejzacpanrldpjjwksjm62shqhyss44jf5xz",
Salt: "61",
InitArgs: nil,
},
expErr: fmt.Errorf("invalid code hash"),
},
"invalid creator address": {
src: &types.QueryBuildAddressRequest{
CodeHash: "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5",
CreatorAddress: "invalid",
Salt: "61",
InitArgs: nil,
},
expErr: fmt.Errorf("invalid creator address"),
},
"invalid salt": {
src: &types.QueryBuildAddressRequest{
CodeHash: "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5",
CreatorAddress: "cosmos100dejzacpanrldpjjwksjm62shqhyss44jf5xz",
Salt: "invalid",
InitArgs: nil,
},
expErr: fmt.Errorf("invalid salt"),
},
"empty salt": {
src: &types.QueryBuildAddressRequest{
CodeHash: "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5",
CreatorAddress: "cosmos100dejzacpanrldpjjwksjm62shqhyss44jf5xz",
Salt: "",
InitArgs: nil,
},
expErr: status.Error(codes.InvalidArgument, "empty salt"),
},
"invalid init args": {
src: &types.QueryBuildAddressRequest{
CodeHash: "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5",
CreatorAddress: "cosmos100dejzacpanrldpjjwksjm62shqhyss44jf5xz",
Salt: "61",
InitArgs: []byte(`invalid`),
},
expErr: fmt.Errorf("invalid"),
},
"valid - without init args": {
src: &types.QueryBuildAddressRequest{
CodeHash: "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5",
CreatorAddress: "cosmos100dejzacpanrldpjjwksjm62shqhyss44jf5xz",
Salt: "61",
InitArgs: nil,
},
exp: &types.QueryBuildAddressResponse{
Address: "cosmos165fz7lnnt6e08knjqsz6fnz9drs7gewezyq3pl5uspc3zgt5lldq4ge3pl",
},
expErr: nil,
},
"valid - with init args": {
src: &types.QueryBuildAddressRequest{
CodeHash: "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5",
CreatorAddress: "cosmos100dejzacpanrldpjjwksjm62shqhyss44jf5xz",
Salt: "61",
InitArgs: []byte(`{"verifier":"cosmos100dejzacpanrldpjjwksjm62shqhyss44jf5xz"}`),
},
exp: &types.QueryBuildAddressResponse{
Address: "cosmos150kq3ggdvc9lftcv6ns75t3v6lcpxdmvuwtqr6e9fc029z6h4maqepgss6",
},
expErr: nil,
},
}

ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
keeper := keepers.WasmKeeper

q := Querier(keeper)
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
got, gotErr := q.BuildAddress(ctx, spec.src)
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.exp.Address, got.Address)
})
}
}
Loading

0 comments on commit 651abcf

Please sign in to comment.