Skip to content

Commit

Permalink
Get Contracts by Creator Address (#1021)
Browse files Browse the repository at this point in the history
* add query to query.proto

* add ContractsByCreatorPrefix in keys.go

* add ContractCreatorThirdIndex to keeper.go

* add querier

* cli

* fix test

* linting

* add key test

* no need to change creator when migrate

* add query test

* minor

* add migrate logic

* add more test

* register migration

* minor

* Update x/wasm/client/cli/query.go

Co-authored-by: Alexander Peters <[email protected]>

* nits

* remove IterateAllContract

* Update x/wasm/keeper/genesis_test.go

Co-authored-by: Alexander Peters <[email protected]>

* nit

* nit: func name

* change key

* improve TestIteratorContractByCreator

* fix test

* use IterateContractInfo in migrate2to3

* minor

* move key

* improve test case

* add pagReq in ContractsByCreator query

* ordering query

* add migrate test

* Make ContractsByCreator plural; formatting and minor updates

* Comment why AbsoluteTxPositionLen makes sense

* Migrate 1 to 2

* Set module version

Co-authored-by: Alexander Peters <[email protected]>
Co-authored-by: khanh-notional <[email protected]>
  • Loading branch information
3 people authored Oct 20, 2022
1 parent 2abf812 commit 6d67d5b
Show file tree
Hide file tree
Showing 17 changed files with 1,221 additions and 84 deletions.
37 changes: 37 additions & 0 deletions docs/proto/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@
- [QueryContractInfoResponse](#cosmwasm.wasm.v1.QueryContractInfoResponse)
- [QueryContractsByCodeRequest](#cosmwasm.wasm.v1.QueryContractsByCodeRequest)
- [QueryContractsByCodeResponse](#cosmwasm.wasm.v1.QueryContractsByCodeResponse)
- [QueryContractsByCreatorRequest](#cosmwasm.wasm.v1.QueryContractsByCreatorRequest)
- [QueryContractsByCreatorResponse](#cosmwasm.wasm.v1.QueryContractsByCreatorResponse)
- [QueryParamsRequest](#cosmwasm.wasm.v1.QueryParamsRequest)
- [QueryParamsResponse](#cosmwasm.wasm.v1.QueryParamsResponse)
- [QueryPinnedCodesRequest](#cosmwasm.wasm.v1.QueryPinnedCodesRequest)
Expand Down Expand Up @@ -1131,6 +1133,40 @@ Query/ContractsByCode RPC method



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

### QueryContractsByCreatorRequest
QueryContractsByCreatorRequest is the request type for the
Query/ContractsByCreator RPC method.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `creator_address` | [string](#string) | | CreatorAddress is the address of contract creator |
| `pagination` | [cosmos.base.query.v1beta1.PageRequest](#cosmos.base.query.v1beta1.PageRequest) | | Pagination defines an optional pagination for the request. |






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

### QueryContractsByCreatorResponse
QueryContractsByCreatorResponse is the response type for the
Query/ContractsByCreator 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.QueryParamsRequest"></a>

### QueryParamsRequest
Expand Down Expand Up @@ -1278,6 +1314,7 @@ Query provides defines the gRPC querier service
| `Codes` | [QueryCodesRequest](#cosmwasm.wasm.v1.QueryCodesRequest) | [QueryCodesResponse](#cosmwasm.wasm.v1.QueryCodesResponse) | Codes gets the metadata for all stored wasm codes | GET|/cosmwasm/wasm/v1/code|
| `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}|

<!-- end services -->

Expand Down
25 changes: 25 additions & 0 deletions proto/cosmwasm/wasm/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ service Query {
rpc Params(QueryParamsRequest) returns (QueryParamsResponse) {
option (google.api.http).get = "/cosmwasm/wasm/v1/codes/params";
}

// ContractsByCreator gets the contracts by creator
rpc ContractsByCreator(QueryContractsByCreatorRequest)
returns (QueryContractsByCreatorResponse) {
option (google.api.http).get =
"/cosmwasm/wasm/v1/contracts/creator/{creator_address}";
}
}

// QueryContractInfoRequest is the request type for the Query/ContractInfo RPC
Expand Down Expand Up @@ -236,3 +243,21 @@ message QueryParamsResponse {
// params defines the parameters of the module.
Params params = 1 [ (gogoproto.nullable) = false ];
}

// QueryContractsByCreatorRequest is the request type for the
// Query/ContractsByCreator RPC method.
message QueryContractsByCreatorRequest {
// CreatorAddress is the address of contract creator
string creator_address = 1;
// Pagination defines an optional pagination for the request.
cosmos.base.query.v1beta1.PageRequest pagination = 2;
}

// QueryContractsByCreatorResponse is the response type for the
// Query/ContractsByCreator RPC method.
message QueryContractsByCreatorResponse {
// ContractAddresses result set
repeated string contract_addresses = 1;
// Pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
40 changes: 40 additions & 0 deletions x/wasm/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func GetQueryCmd() *cobra.Command {
GetCmdLibVersion(),
GetCmdQueryParams(),
GetCmdBuildAddress(),
GetCmdListContractsByCreator(),
)
return queryCmd
}
Expand Down Expand Up @@ -528,6 +529,45 @@ func GetCmdListPinnedCode() *cobra.Command {
return cmd
}

// GetCmdListContractsByCreator lists all contracts by creator
func GetCmdListContractsByCreator() *cobra.Command {
cmd := &cobra.Command{
Use: "list-contracts-by-creator [creator]",
Short: "List all contracts by creator",
Long: "\t\tLong: List all contracts by creator,\n",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
_, err = sdk.AccAddressFromBech32(args[0])
if err != nil {
return err
}
pageReq, err := client.ReadPageRequest(withPageKeyDecoded(cmd.Flags()))
if err != nil {
return err
}

queryClient := types.NewQueryClient(clientCtx)
res, err := queryClient.ContractsByCreator(
context.Background(),
&types.QueryContractsByCreatorRequest{
CreatorAddress: args[0],
Pagination: pageReq,
},
)
if err != nil {
return err
}
return clientCtx.PrintProto(res)
},
}
flags.AddQueryFlagsToCmd(cmd)
return cmd
}

type argumentDecoder struct {
// dec is the default decoder
dec func(string) ([]byte, error)
Expand Down
3 changes: 3 additions & 0 deletions x/wasm/keeper/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ func TestGenesisExportImport(t *testing.T) {

// reset contract code index in source DB for comparison with dest DB
wasmKeeper.IterateContractInfo(srcCtx, func(address sdk.AccAddress, info wasmTypes.ContractInfo) bool {
creatorAddress := sdk.MustAccAddressFromBech32(info.Creator)
wasmKeeper.removeFromContractCodeSecondaryIndex(srcCtx, address, wasmKeeper.getLastContractHistoryEntry(srcCtx, address))

prefixStore := prefix.NewStore(srcCtx.KVStore(wasmKeeper.storeKey), types.GetContractCodeHistoryElementPrefix(address))
iter := prefixStore.Iterator(nil, nil)

Expand All @@ -121,6 +123,7 @@ func TestGenesisExportImport(t *testing.T) {
newHistory := x.ResetFromGenesis(dstCtx)
wasmKeeper.storeContractInfo(srcCtx, address, x)
wasmKeeper.addToContractCodeSecondaryIndex(srcCtx, address, newHistory)
wasmKeeper.addToContractCreatorSecondaryIndex(srcCtx, creatorAddress, newHistory.Updated, address)
wasmKeeper.appendToContractHistory(srcCtx, address, newHistory)
iter.Close()
return false
Expand Down
24 changes: 23 additions & 1 deletion x/wasm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ func (k Keeper) instantiate(
// store contract before dispatch so that contract could be called back
historyEntry := contractInfo.InitialHistory(initMsg)
k.addToContractCodeSecondaryIndex(ctx, contractAddress, historyEntry)
k.addToContractCreatorSecondaryIndex(ctx, creator, historyEntry.Updated, contractAddress)
k.appendToContractHistory(ctx, contractAddress, historyEntry)
k.storeContractInfo(ctx, contractAddress, &contractInfo)

Expand Down Expand Up @@ -491,7 +492,6 @@ func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller
if err != nil {
return nil, sdkerrors.Wrap(types.ErrMigrationFailed, err.Error())
}

// delete old secondary index entry
k.removeFromContractCodeSecondaryIndex(ctx, contractAddress, k.getLastContractHistoryEntry(ctx, contractAddress))
// persist migration updates
Expand Down Expand Up @@ -598,6 +598,23 @@ func (k Keeper) removeFromContractCodeSecondaryIndex(ctx sdk.Context, contractAd
ctx.KVStore(k.storeKey).Delete(types.GetContractByCreatedSecondaryIndexKey(contractAddress, entry))
}

// addToContractCreatorSecondaryIndex adds element to the index for contracts-by-creator queries
func (k Keeper) addToContractCreatorSecondaryIndex(ctx sdk.Context, creatorAddress sdk.AccAddress, position *types.AbsoluteTxPosition, contractAddress sdk.AccAddress) {
store := ctx.KVStore(k.storeKey)
store.Set(types.GetContractByCreatorSecondaryIndexKey(creatorAddress, position.Bytes(), contractAddress), []byte{})
}

// IterateContractsByCreator iterates over all contracts with given creator address in order of creation time asc.
func (k Keeper) IterateContractsByCreator(ctx sdk.Context, creator sdk.AccAddress, cb func(address sdk.AccAddress) bool) {
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.GetContractsByCreatorPrefix(creator))
for iter := prefixStore.Iterator(nil, nil); iter.Valid(); iter.Next() {
key := iter.Key()
if cb(key[types.AbsoluteTxPositionLen:]) {
return
}
}
}

// IterateContractsByCode iterates over all contracts with given codeID ASC on code update time.
func (k Keeper) IterateContractsByCode(ctx sdk.Context, codeID uint64, cb func(address sdk.AccAddress) bool) {
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.GetContractByCodeIDSecondaryIndexPrefix(codeID))
Expand Down Expand Up @@ -1053,10 +1070,15 @@ func (k Keeper) importContract(ctx sdk.Context, contractAddr sdk.AccAddress, c *
return sdkerrors.Wrapf(types.ErrDuplicate, "contract: %s", contractAddr)
}

creatorAddress, err := sdk.AccAddressFromBech32(c.Creator)
if err != nil {
return err
}
historyEntry := c.ResetFromGenesis(ctx)
k.appendToContractHistory(ctx, contractAddr, historyEntry)
k.storeContractInfo(ctx, contractAddr, c)
k.addToContractCodeSecondaryIndex(ctx, contractAddr, historyEntry)
k.addToContractCreatorSecondaryIndex(ctx, creatorAddress, historyEntry.Updated, contractAddr)
return k.importContractState(ctx, contractAddr, state)
}

Expand Down
92 changes: 91 additions & 1 deletion x/wasm/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ func TestInstantiate(t *testing.T) {

gasAfter := ctx.GasMeter().GasConsumed()
if types.EnableGasVerification {
require.Equal(t, uint64(0x1964f), gasAfter-gasBefore)
require.Equal(t, uint64(0x1a7bb), gasAfter-gasBefore)
}

// ensure it is stored properly
Expand Down Expand Up @@ -2197,3 +2197,93 @@ func TestCoinBurnerPruneBalances(t *testing.T) {
})
}
}

func TestIteratorAllContract(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
example1 := InstantiateHackatomExampleContract(t, ctx, keepers)
example2 := InstantiateHackatomExampleContract(t, ctx, keepers)
example3 := InstantiateHackatomExampleContract(t, ctx, keepers)
example4 := InstantiateHackatomExampleContract(t, ctx, keepers)

var allContract []string
keepers.WasmKeeper.IterateContractInfo(ctx, func(addr sdk.AccAddress, _ types.ContractInfo) bool {
allContract = append(allContract, addr.String())
return false
})

// IterateContractInfo not ordering
expContracts := []string{example4.Contract.String(), example2.Contract.String(), example1.Contract.String(), example3.Contract.String()}
require.Equal(t, allContract, expContracts)
}

func TestIteratorContractByCreator(t *testing.T) {
// setup test
parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities)
keeper := keepers.ContractKeeper

depositFund := sdk.NewCoins(sdk.NewInt64Coin("denom", 1000000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := DeterministicAccountAddress(t, 1)
keepers.Faucet.Fund(parentCtx, creator, depositFund.Add(depositFund...)...)
mockAddress1 := keepers.Faucet.NewFundedRandomAccount(parentCtx, topUp...)
mockAddress2 := keepers.Faucet.NewFundedRandomAccount(parentCtx, topUp...)
mockAddress3 := keepers.Faucet.NewFundedRandomAccount(parentCtx, topUp...)

contract1ID, _, err := keeper.Create(parentCtx, creator, hackatomWasm, nil)
contract2ID, _, err := keeper.Create(parentCtx, creator, hackatomWasm, nil)

require.NoError(t, err)

initMsgBz := HackatomExampleInitMsg{
Verifier: mockAddress1,
Beneficiary: mockAddress1,
}.GetBytes(t)

depositContract := sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1_000)))

gotAddr1, _, _ := keepers.ContractKeeper.Instantiate(parentCtx, contract1ID, mockAddress1, nil, initMsgBz, "label", depositContract)
ctx := parentCtx.WithBlockHeight(parentCtx.BlockHeight() + 1)
gotAddr2, _, _ := keepers.ContractKeeper.Instantiate(ctx, contract1ID, mockAddress2, nil, initMsgBz, "label", depositContract)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
gotAddr3, _, _ := keepers.ContractKeeper.Instantiate(ctx, contract1ID, gotAddr1, nil, initMsgBz, "label", depositContract)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
gotAddr4, _, _ := keepers.ContractKeeper.Instantiate(ctx, contract2ID, mockAddress2, nil, initMsgBz, "label", depositContract)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
gotAddr5, _, _ := keepers.ContractKeeper.Instantiate(ctx, contract2ID, mockAddress2, nil, initMsgBz, "label", depositContract)

specs := map[string]struct {
creatorAddr sdk.AccAddress
contractsAddr []string
}{
"single contract": {
creatorAddr: mockAddress1,
contractsAddr: []string{gotAddr1.String()},
},
"multiple contracts": {
creatorAddr: mockAddress2,
contractsAddr: []string{gotAddr2.String(), gotAddr4.String(), gotAddr5.String()},
},
"contractAdress": {
creatorAddr: gotAddr1,
contractsAddr: []string{gotAddr3.String()},
},
"no contracts- unknown": {
creatorAddr: mockAddress3,
contractsAddr: nil,
},
}

for name, spec := range specs {
t.Run(name, func(t *testing.T) {
var allContract []string
keepers.WasmKeeper.IterateContractsByCreator(parentCtx, spec.creatorAddr, func(addr sdk.AccAddress) bool {
allContract = append(allContract, addr.String())
return false
})
require.Equal(t,
allContract,
spec.contractsAddr,
)
})
}
}
61 changes: 61 additions & 0 deletions x/wasm/keeper/migrate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package keeper

import (
"bytes"
"encoding/json"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/stretchr/testify/require"

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

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

deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := sdk.AccAddress(bytes.Repeat([]byte{1}, address.Len))
keepers.Faucet.Fund(ctx, creator, deposit...)
example := StoreHackatomExampleContract(t, ctx, keepers)

initMsg := HackatomExampleInitMsg{
Verifier: RandomAccountAddress(t),
Beneficiary: RandomAccountAddress(t),
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)

em := sdk.NewEventManager()

// create with no balance is also legal
gotContractAddr1, _, err := keepers.ContractKeeper.Instantiate(ctx.WithEventManager(em), example.CodeID, creator, nil, initMsgBz, "demo contract 1", nil)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
gotContractAddr2, _, err := keepers.ContractKeeper.Instantiate(ctx.WithEventManager(em), example.CodeID, creator, nil, initMsgBz, "demo contract 1", nil)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
gotContractAddr3, _, err := keepers.ContractKeeper.Instantiate(ctx.WithEventManager(em), example.CodeID, creator, nil, initMsgBz, "demo contract 1", nil)

info1 := wasmKeeper.GetContractInfo(ctx, gotContractAddr1)
info2 := wasmKeeper.GetContractInfo(ctx, gotContractAddr2)
info3 := wasmKeeper.GetContractInfo(ctx, gotContractAddr3)

// remove key
ctx.KVStore(wasmKeeper.storeKey).Delete(types.GetContractByCreatorSecondaryIndexKey(creator, info1.Created.Bytes(), gotContractAddr1))
ctx.KVStore(wasmKeeper.storeKey).Delete(types.GetContractByCreatorSecondaryIndexKey(creator, info2.Created.Bytes(), gotContractAddr2))
ctx.KVStore(wasmKeeper.storeKey).Delete(types.GetContractByCreatorSecondaryIndexKey(creator, info3.Created.Bytes(), gotContractAddr3))

// migrator
migrator := NewMigrator(*wasmKeeper)
migrator.Migrate1to2(ctx)

// check new store
var allContract []string
wasmKeeper.IterateContractsByCreator(ctx, creator, func(addr sdk.AccAddress) bool {
allContract = append(allContract, addr.String())
return false
})

require.Equal(t, []string{gotContractAddr1.String(), gotContractAddr2.String(), gotContractAddr3.String()}, allContract)
}
Loading

0 comments on commit 6d67d5b

Please sign in to comment.