Make contract addresses predictable

This commit is contained in:
Alex Peters
2022-08-26 15:06:03 +02:00
parent d9f9f91d13
commit ccb2fdd0b6
43 changed files with 1169 additions and 507 deletions

View File

@@ -103,7 +103,7 @@ func GenesisInstantiateContractCmd(defaultNodeHome string, genesisMutator Genesi
}
return genesisMutator.AlterWasmModuleState(cmd, func(state *types.GenesisState, appState map[string]json.RawMessage) error {
// simple sanity check that sender has some balance although it may be consumed by appState previous message already
// simple sanity check that sender has some balance, although it may be consumed by appState previous message already
switch ok, err := hasAccountBalance(cmd, appState, senderAddr, msg.Funds); {
case err != nil:
return err
@@ -112,7 +112,7 @@ func GenesisInstantiateContractCmd(defaultNodeHome string, genesisMutator Genesi
}
// does code id exists?
codeInfos, err := GetAllCodes(state)
codeInfos := GetAllCodes(state)
if err != nil {
return err
}
@@ -171,7 +171,7 @@ func GenesisExecuteContractCmd(defaultNodeHome string, genesisMutator GenesisMut
}
return genesisMutator.AlterWasmModuleState(cmd, func(state *types.GenesisState, appState map[string]json.RawMessage) error {
// simple sanity check that sender has some balance although it may be consumed by appState previous message already
// simple sanity check that sender has some balance, although it may be consumed by appState previous message already
switch ok, err := hasAccountBalance(cmd, appState, senderAddr, msg.Funds); {
case err != nil:
return err
@@ -211,7 +211,7 @@ func GenesisListCodesCmd(defaultNodeHome string, genReader GenesisReader) *cobra
if err != nil {
return err
}
all, err := GetAllCodes(g.WasmModuleState)
all := GetAllCodes(g.WasmModuleState)
if err != nil {
return err
}
@@ -236,7 +236,10 @@ func GenesisListContractsCmd(defaultNodeHome string, genReader GenesisReader) *c
return err
}
state := g.WasmModuleState
all := GetAllContracts(state)
all, err := GetAllContracts(state)
if err != nil {
return err
}
return printJSONOutput(cmd, all)
},
}
@@ -245,7 +248,7 @@ func GenesisListContractsCmd(defaultNodeHome string, genReader GenesisReader) *c
return cmd
}
// clientCtx marshaller works only with proto or bytes so we marshal the output ourself
// clientCtx marshaller works only with proto or bytes, so we marshal the output ourselves
func printJSONOutput(cmd *cobra.Command, obj interface{}) error {
clientCtx := client.GetClientContextFromCmd(cmd)
bz, err := json.MarshalIndent(obj, "", " ")
@@ -260,7 +263,7 @@ type CodeMeta struct {
Info types.CodeInfo `json:"info"`
}
func GetAllCodes(state *types.GenesisState) ([]CodeMeta, error) {
func GetAllCodes(state *types.GenesisState) []CodeMeta {
all := make([]CodeMeta, len(state.Codes))
for i, c := range state.Codes {
all[i] = CodeMeta{
@@ -277,10 +280,7 @@ func GetAllCodes(state *types.GenesisState) ([]CodeMeta, error) {
accessConfig = *msg.InstantiatePermission
} else {
// default
creator, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
return nil, fmt.Errorf("sender: %s", err)
}
creator := sdk.MustAccAddressFromBech32(msg.Sender)
accessConfig = state.Params.InstantiateDefaultPermission.With(creator)
}
hash := sha256.Sum256(msg.WASMByteCode)
@@ -295,7 +295,7 @@ func GetAllCodes(state *types.GenesisState) ([]CodeMeta, error) {
seq++
}
}
return all, nil
return all
}
type ContractMeta struct {
@@ -303,7 +303,18 @@ type ContractMeta struct {
Info types.ContractInfo `json:"info"`
}
func GetAllContracts(state *types.GenesisState) []ContractMeta {
// returns nil when not found
func codeHashByID(state *types.GenesisState, codeID uint64) []byte {
codes := GetAllCodes(state)
for _, v := range codes {
if v.CodeID == codeID {
return v.Info.CodeHash
}
}
return nil
}
func GetAllContracts(state *types.GenesisState) ([]ContractMeta, error) {
all := make([]ContractMeta, len(state.Contracts))
for i, c := range state.Contracts {
all[i] = ContractMeta{
@@ -312,11 +323,18 @@ func GetAllContracts(state *types.GenesisState) []ContractMeta {
}
}
// add inflight
seq := contractSeqValue(state)
for _, m := range state.GenMsgs {
if msg := m.GetInstantiateContract(); msg != nil {
senderAddr, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
panic(fmt.Sprintf("unsupported address %q: %s", msg.Sender, err))
}
codeHash := codeHashByID(state, msg.CodeID)
if codeHash == nil {
return nil, types.ErrNotFound.Wrapf("hash for code-id: %d", msg.CodeID)
}
all = append(all, ContractMeta{
ContractAddress: keeper.BuildContractAddress(msg.CodeID, seq).String(),
ContractAddress: keeper.BuildContractAddress(codeHash, senderAddr, msg.Label).String(),
Info: types.ContractInfo{
CodeID: msg.CodeID,
Creator: msg.Sender,
@@ -324,10 +342,9 @@ func GetAllContracts(state *types.GenesisState) []ContractMeta {
Label: msg.Label,
},
})
seq++
}
}
return all
return all, nil
}
func hasAccountBalance(cmd *cobra.Command, appState map[string]json.RawMessage, sender sdk.AccAddress, coins sdk.Coins) (bool, error) {
@@ -354,13 +371,19 @@ func hasContract(state *types.GenesisState, contractAddr string) bool {
return true
}
}
seq := contractSeqValue(state)
for _, m := range state.GenMsgs {
if msg := m.GetInstantiateContract(); msg != nil {
if keeper.BuildContractAddress(msg.CodeID, seq).String() == contractAddr {
senderAddr, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
panic(fmt.Sprintf("unsupported address %q: %s", msg.Sender, err))
}
hash := codeHashByID(state, msg.CodeID)
if hash == nil {
panic(fmt.Sprintf("unknown code id: %d", msg.CodeID))
}
if keeper.BuildContractAddress(hash, senderAddr, msg.Label).String() == contractAddr {
return true
}
seq++
}
}
return false
@@ -453,19 +476,6 @@ func (x DefaultGenesisIO) AlterWasmModuleState(cmd *cobra.Command, callback func
return genutil.ExportGenesisFile(g.GenDoc, g.GenesisFile)
}
// contractSeqValue reads the contract sequence from the genesis or
// returns default start value used in the keeper
func contractSeqValue(state *types.GenesisState) uint64 {
var seq uint64 = 1
for _, s := range state.Sequences {
if bytes.Equal(s.IDKey, types.KeyLastInstanceID) {
seq = s.Value
break
}
}
return seq
}
// codeSeqValue reads the code sequence from the genesis or
// returns default start value used in the keeper
func codeSeqValue(state *types.GenesisState) uint64 {

View File

@@ -1,12 +1,15 @@
package cli
import (
"bytes"
"context"
"encoding/json"
"os"
"path"
"testing"
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/hd"
@@ -363,7 +366,8 @@ func TestInstantiateContractCmd(t *testing.T) {
}
func TestExecuteContractCmd(t *testing.T) {
const firstContractAddress = "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr"
mySenderAddr := sdk.AccAddress(bytes.Repeat([]byte{1}, address.Len))
myFirstContractAddress := keeper.BuildContractAddress([]byte("myCodeHash"), mySenderAddr, "my").String()
minimalWasmGenesis := types.GenesisState{
Params: types.DefaultParams(),
}
@@ -390,7 +394,7 @@ func TestExecuteContractCmd(t *testing.T) {
},
Contracts: []types.Contract{
{
ContractAddress: firstContractAddress,
ContractAddress: myFirstContractAddress,
ContractInfo: types.ContractInfoFixture(func(info *types.ContractInfo) {
info.Created = nil
}),
@@ -399,53 +403,34 @@ func TestExecuteContractCmd(t *testing.T) {
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{firstContractAddress, `{}`})
cmd.SetArgs([]string{myFirstContractAddress, `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", myWellFundedAccount)
},
expMsgCount: 1,
},
"all good with contract from genesis store messages without initial sequence": {
"all good with contract from genesis store messages": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
Codes: []types.Code{
{
CodeID: 1,
CodeInfo: types.CodeInfoFixture(),
CodeID: 1,
CodeInfo: types.CodeInfoFixture(func(info *types.CodeInfo) {
info.CodeHash = []byte("myCodeHash")
}),
CodeBytes: wasmIdent,
},
},
GenMsgs: []types.GenesisState_GenMsgs{
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: types.MsgInstantiateContractFixture()}},
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: types.MsgInstantiateContractFixture(
func(m *types.MsgInstantiateContract) {
m.Sender = mySenderAddr.String()
m.Label = "my"
})}},
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{firstContractAddress, `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", myWellFundedAccount)
},
expMsgCount: 2,
},
"all good with contract from genesis store messages and contract sequence set": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
Codes: []types.Code{
{
CodeID: 1,
CodeInfo: types.CodeInfoFixture(),
CodeBytes: wasmIdent,
},
},
GenMsgs: []types.GenesisState_GenMsgs{
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: types.MsgInstantiateContractFixture()}},
},
Sequences: []types.Sequence{
{IDKey: types.KeyLastInstanceID, Value: 100},
},
},
mutator: func(cmd *cobra.Command) {
// See TestBuildContractAddress in keeper_test.go
cmd.SetArgs([]string{"cosmos1mujpjkwhut9yjw4xueyugc02evfv46y0dtmnz4lh8xxkkdapym9stu5qm8", `{}`})
cmd.SetArgs([]string{myFirstContractAddress, `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", myWellFundedAccount)
},
@@ -472,7 +457,7 @@ func TestExecuteContractCmd(t *testing.T) {
},
Contracts: []types.Contract{
{
ContractAddress: firstContractAddress,
ContractAddress: myFirstContractAddress,
ContractInfo: types.ContractInfoFixture(func(info *types.ContractInfo) {
info.Created = nil
}),
@@ -481,7 +466,7 @@ func TestExecuteContractCmd(t *testing.T) {
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{firstContractAddress, `{}`})
cmd.SetArgs([]string{myFirstContractAddress, `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", keeper.RandomBech32AccountAddress(t))
},
@@ -499,7 +484,7 @@ func TestExecuteContractCmd(t *testing.T) {
},
Contracts: []types.Contract{
{
ContractAddress: firstContractAddress,
ContractAddress: myFirstContractAddress,
ContractInfo: types.ContractInfoFixture(func(info *types.ContractInfo) {
info.Created = nil
}),
@@ -508,7 +493,7 @@ func TestExecuteContractCmd(t *testing.T) {
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{firstContractAddress, `{}`})
cmd.SetArgs([]string{myFirstContractAddress, `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", myWellFundedAccount)
flagSet.Set("amount", "100stake")
@@ -527,7 +512,7 @@ func TestExecuteContractCmd(t *testing.T) {
},
Contracts: []types.Contract{
{
ContractAddress: firstContractAddress,
ContractAddress: myFirstContractAddress,
ContractInfo: types.ContractInfoFixture(func(info *types.ContractInfo) {
info.Created = nil
}),
@@ -536,7 +521,7 @@ func TestExecuteContractCmd(t *testing.T) {
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{firstContractAddress, `{}`})
cmd.SetArgs([]string{myFirstContractAddress, `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", keeper.RandomBech32AccountAddress(t))
flagSet.Set("amount", "10stake")
@@ -565,6 +550,9 @@ func TestExecuteContractCmd(t *testing.T) {
}
func TestGetAllContracts(t *testing.T) {
creatorAddr1 := sdk.AccAddress(bytes.Repeat([]byte{1}, address.Len))
creatorAddr2 := sdk.AccAddress(bytes.Repeat([]byte{2}, address.Len))
specs := map[string]struct {
src types.GenesisState
exp []ContractMeta
@@ -595,68 +583,55 @@ func TestGetAllContracts(t *testing.T) {
},
"read from message state": {
src: types.GenesisState{
Codes: []types.Code{{CodeID: 1, CodeInfo: types.CodeInfo{CodeHash: []byte("firstCodeHash")}}},
GenMsgs: []types.GenesisState_GenMsgs{
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &types.MsgInstantiateContract{Label: "first"}}},
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &types.MsgInstantiateContract{Label: "second"}}},
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &types.MsgInstantiateContract{Sender: creatorAddr1.String(), Label: "first", CodeID: 1}}},
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &types.MsgInstantiateContract{Sender: creatorAddr2.String(), Label: "second", CodeID: 1}}},
},
},
exp: []ContractMeta{
{
ContractAddress: keeper.BuildContractAddress(0, 1).String(),
Info: types.ContractInfo{Label: "first"},
ContractAddress: keeper.BuildContractAddress([]byte("firstCodeHash"), creatorAddr1, "first").String(),
Info: types.ContractInfo{Creator: creatorAddr1.String(), Label: "first", CodeID: 1},
},
{
ContractAddress: keeper.BuildContractAddress(0, 2).String(),
Info: types.ContractInfo{Label: "second"},
},
},
},
"read from message state with contract sequence": {
src: types.GenesisState{
Sequences: []types.Sequence{
{IDKey: types.KeyLastInstanceID, Value: 100},
},
GenMsgs: []types.GenesisState_GenMsgs{
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &types.MsgInstantiateContract{Label: "hundred"}}},
},
},
exp: []ContractMeta{
{
ContractAddress: keeper.BuildContractAddress(0, 100).String(),
Info: types.ContractInfo{Label: "hundred"},
ContractAddress: keeper.BuildContractAddress([]byte("firstCodeHash"), creatorAddr2, "second").String(),
Info: types.ContractInfo{Creator: creatorAddr2.String(), Label: "second", CodeID: 1},
},
},
},
"read from contract and message state with contract sequence": {
src: types.GenesisState{
Codes: []types.Code{
{CodeID: 1, CodeInfo: types.CodeInfo{CodeHash: []byte("firstCodeHash")}},
{CodeID: 100, CodeInfo: types.CodeInfo{CodeHash: []byte("otherCodeHash")}},
},
Contracts: []types.Contract{
{
ContractAddress: "first-contract",
ContractInfo: types.ContractInfo{Label: "first"},
ContractAddress: keeper.BuildContractAddress([]byte("firstCodeHash"), creatorAddr1, "first").String(),
ContractInfo: types.ContractInfo{Label: "first", CodeID: 1},
},
},
Sequences: []types.Sequence{
{IDKey: types.KeyLastInstanceID, Value: 100},
},
GenMsgs: []types.GenesisState_GenMsgs{
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &types.MsgInstantiateContract{Label: "hundred"}}},
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &types.MsgInstantiateContract{Sender: creatorAddr1.String(), Label: "hundred", CodeID: 100}}},
},
},
exp: []ContractMeta{
{
ContractAddress: "first-contract",
Info: types.ContractInfo{Label: "first"},
ContractAddress: keeper.BuildContractAddress([]byte("firstCodeHash"), creatorAddr1, "first").String(),
Info: types.ContractInfo{Label: "first", CodeID: 1},
},
{
ContractAddress: keeper.BuildContractAddress(0, 100).String(),
Info: types.ContractInfo{Label: "hundred"},
ContractAddress: keeper.BuildContractAddress([]byte("otherCodeHash"), creatorAddr1, "hundred").String(),
Info: types.ContractInfo{Creator: creatorAddr1.String(), Label: "hundred", CodeID: 100},
},
},
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
got := GetAllContracts(&spec.src)
got, err := GetAllContracts(&spec.src)
require.NoError(t, err)
assert.Equal(t, spec.exp, got)
})
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"github.com/CosmWasm/wasmd/x/wasm/keeper"
"github.com/CosmWasm/wasmd/x/wasm/types"
)
@@ -39,6 +40,7 @@ func GetQueryCmd() *cobra.Command {
GetCmdListPinnedCode(),
GetCmdLibVersion(),
GetCmdQueryParams(),
GetCmdBuildAddress(),
)
return queryCmd
}
@@ -63,6 +65,33 @@ func GetCmdLibVersion() *cobra.Command {
return cmd
}
// GetCmdBuildAddress build a contract address
func GetCmdBuildAddress() *cobra.Command {
cmd := &cobra.Command{
Use: "build-address [code-hash] [creator-address] [label]",
Short: "build contract address",
Aliases: []string{"address"},
Args: cobra.ExactArgs(3),
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])
if err != nil {
return fmt.Errorf("creator: %s", err)
}
label := args[2]
if err := types.ValidateLabel(label); err != nil {
return fmt.Errorf("label: %s", err)
}
cmd.Println(keeper.BuildContractAddress(codeHash, creator, label).String())
return nil
},
}
return cmd
}
// GetCmdListCode lists all wasm code uploaded
func GetCmdListCode() *cobra.Command {
cmd := &cobra.Command{
@@ -117,6 +146,9 @@ func GetCmdListContractByCode() *cobra.Command {
if err != nil {
return err
}
if codeID == 0 {
return errors.New("empty code id")
}
pageReq, err := client.ReadPageRequest(withPageKeyDecoded(cmd.Flags()))
if err != nil {