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: Provide source, builder and codehash information in store code proposal message #1072

Merged
merged 20 commits into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from 17 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
6 changes: 6 additions & 0 deletions docs/proto/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,9 @@ and instantiate the contract.
| `label` | [string](#string) | | Label is optional metadata to be stored with a constract instance. |
| `msg` | [bytes](#bytes) | | Msg json encoded message to be passed to the contract on instantiation |
| `funds` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | Funds coins that are transferred to the contract on instantiation |
| `source` | [string](#string) | | Source is the URL where the code is hosted |
| `builder` | [string](#string) | | Builder is the docker image used to build the code deterministically, used for smart contract verification |
| `code_hash` | [bytes](#bytes) | | CodeHash is the SHA256 sum of the code outputted by builder, used for smart contract verification |



Expand All @@ -1024,6 +1027,9 @@ StoreCodeProposal gov proposal content type to submit WASM code to the system
| `wasm_byte_code` | [bytes](#bytes) | | WASMByteCode can be raw or gzip compressed |
| `instantiate_permission` | [AccessConfig](#cosmwasm.wasm.v1.AccessConfig) | | InstantiatePermission to apply on contract creation, optional |
| `unpin_code` | [bool](#bool) | | UnpinCode code on upload, optional |
| `source` | [string](#string) | | Source is the URL where the code is hosted |
| `builder` | [string](#string) | | Builder is the docker image used to build the code deterministically, used for smart contract verification |
| `code_hash` | [bytes](#bytes) | | CodeHash is the SHA256 sum of the code outputted by builder, used for smart contract verification |



Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/cosmos/iavl v0.19.4
github.com/cosmos/ibc-go/v3 v3.3.1
github.com/cosmos/interchain-accounts v0.1.0
github.com/docker/distribution v2.8.1+incompatible
alpe marked this conversation as resolved.
Show resolved Hide resolved
github.com/dvsekhvalnov/jose2go v1.5.0
github.com/gogo/protobuf v1.3.3
github.com/golang/protobuf v1.5.2
Expand Down Expand Up @@ -95,6 +96,7 @@ require (
github.com/minio/highwayhash v1.0.2 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WA
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
Expand Down Expand Up @@ -587,6 +589,7 @@ github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034=
github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
Expand Down
16 changes: 16 additions & 0 deletions proto/cosmwasm/wasm/v1/proposal.proto
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ message StoreCodeProposal {
AccessConfig instantiate_permission = 7;
// UnpinCode code on upload, optional
bool unpin_code = 8;
// Source is the URL where the code is hosted
string source = 9;
orkunkl marked this conversation as resolved.
Show resolved Hide resolved
// Builder is the docker image used to build the code deterministically, used
// for smart contract verification
string builder = 10;
// CodeHash is the SHA256 sum of the code outputted by builder, used for smart
// contract verification
bytes code_hash = 11;
}

// InstantiateContractProposal gov proposal content type to instantiate a
Expand Down Expand Up @@ -199,4 +207,12 @@ message StoreAndInstantiateContractProposal {
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
// Source is the URL where the code is hosted
string source = 11;
// Builder is the docker image used to build the code deterministically, used
// for smart contract verification
string builder = 12;
// CodeHash is the SHA256 sum of the code outputted by builder, used for smart
// contract verification
bytes code_hash = 13;
}
74 changes: 72 additions & 2 deletions x/wasm/client/cli/gov_tx.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package cli

import (
"bytes"
"crypto/sha256"
"fmt"
"net/url"
"strconv"
"strings"

"github.com/docker/distribution/reference"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -13,13 +18,14 @@ import (
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/pkg/errors"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"

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

func ProposalStoreCodeCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "wasm-store [wasm file] --title [text] --description [text] --run-as [address]",
Use: "wasm-store [wasm file] --title [text] --description [text] --run-as [address] --unpin-code [unpin_code] --source [source] --builder [builder] --code-hash [code_hash]",
Short: "Submit a wasm binary proposal",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
Expand All @@ -45,13 +51,20 @@ func ProposalStoreCodeCmd() *cobra.Command {
return err
}

source, builder, codeHash, err := parseVerificationFlags(src.WASMByteCode, cmd.Flags())
if err != nil {
return err
}
content := types.StoreCodeProposal{
Title: proposalTitle,
Description: proposalDescr,
RunAs: runAs,
WASMByteCode: src.WASMByteCode,
InstantiatePermission: src.InstantiatePermission,
UnpinCode: unpinCode,
Source: source,
Builder: builder,
CodeHash: codeHash,
}

msg, err := govtypes.NewMsgSubmitProposal(&content, deposit, clientCtx.GetFromAddress())
Expand All @@ -73,6 +86,9 @@ func ProposalStoreCodeCmd() *cobra.Command {
cmd.Flags().String(flagInstantiateByAddress, "", "Only this address can instantiate a contract instance from the code, optional")
cmd.Flags().Bool(flagUnpinCode, false, "Unpin code on upload, optional")
cmd.Flags().StringSlice(flagInstantiateByAnyOfAddress, []string{}, "Any of the addresses can instantiate a contract from the code, optional")
cmd.Flags().String(flagSource, "", "Code Source URL is a valid absolute HTTPS URI to the contract's source code,")
cmd.Flags().String(flagBuilder, "", "Builder is a valid docker image name with tag, such as \"cosmwasm/workspace-optimizer:0.12.9\"")
cmd.Flags().BytesHex(flagCodeHash, nil, "CodeHash is the sha256 hash of the wasm code")

// proposal flags
cmd.Flags().String(cli.FlagTitle, "", "Title of proposal")
Expand All @@ -81,6 +97,48 @@ func ProposalStoreCodeCmd() *cobra.Command {
return cmd
}

func parseVerificationFlags(wasm []byte, flags *flag.FlagSet) (string, string, []byte, error) {
source, err := flags.GetString(flagSource)
if err != nil {
return "", "", nil, fmt.Errorf("source: %s", err)
}
builder, err := flags.GetString(flagBuilder)
if err != nil {
return "", "", nil, fmt.Errorf("builder: %s", err)
}
codeHash, err := flags.GetBytesHex(flagCodeHash)
if err != nil {
return "", "", nil, fmt.Errorf("codeHash: %s", err)
}

// if any set require others to be set
if len(source) != 0 || len(builder) != 0 || len(codeHash) != 0 {
if source == "" {
return "", "", nil, fmt.Errorf("source is required")
}
if _, err = url.ParseRequestURI(source); err != nil {
return "", "", nil, fmt.Errorf("source: %s", err)
}
if builder == "" {
return "", "", nil, fmt.Errorf("builder is required")
}
if _, err := reference.ParseDockerRef(builder); err != nil {
return "", "", nil, fmt.Errorf("builder: %s", err)
}
if len(codeHash) == 0 {
return "", "", nil, fmt.Errorf("code hash is required")
}
// wasm is unzipped in parseStoreCodeArgs
// checksum generation will be decoupled here
// reference https://github.com/CosmWasm/wasmvm/issues/359
alpe marked this conversation as resolved.
Show resolved Hide resolved
checksum := sha256.Sum256(wasm)
if !bytes.Equal(checksum[:], codeHash) {
return "", "", nil, fmt.Errorf("code-hash mismatch: %X, checksum: %X", codeHash, checksum)
}
}
return source, builder, codeHash, nil
}

func ProposalInstantiateContractCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "instantiate-contract [code_id_int64] [json_encoded_init_args] --label [text] --title [text] --description [text] --run-as [address] --admin [address,optional] --amount [coins,optional]",
Expand Down Expand Up @@ -143,7 +201,8 @@ func ProposalInstantiateContractCmd() *cobra.Command {

func ProposalStoreAndInstantiateContractCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "store-instantiate [wasm file] [json_encoded_init_args] --label [text] --title [text] --description [text] --run-as [address] --admin [address,optional] --amount [coins,optional]",
Use: "store-instantiate [wasm file] [json_encoded_init_args] --label [text] --title [text] --description [text] --run-as [address]" +
"--unpin-code [unpin_code,optional] --source [source,optional] --builder [builder,optional] --code-hash [code_hash,optional] --admin [address,optional] --amount [coins,optional]",
Short: "Submit and instantiate a wasm contract proposal",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
Expand All @@ -169,6 +228,11 @@ func ProposalStoreAndInstantiateContractCmd() *cobra.Command {
return err
}

source, builder, codeHash, err := parseVerificationFlags(src.WASMByteCode, cmd.Flags())
if err != nil {
return err
}

amountStr, err := cmd.Flags().GetString(flagAmount)
if err != nil {
return fmt.Errorf("amount: %s", err)
Expand Down Expand Up @@ -208,6 +272,9 @@ func ProposalStoreAndInstantiateContractCmd() *cobra.Command {
WASMByteCode: src.WASMByteCode,
InstantiatePermission: src.InstantiatePermission,
UnpinCode: unpinCode,
Source: source,
Builder: builder,
CodeHash: codeHash,
Admin: adminStr,
Label: label,
Msg: []byte(args[1]),
Expand All @@ -232,6 +299,9 @@ func ProposalStoreAndInstantiateContractCmd() *cobra.Command {
cmd.Flags().String(flagInstantiateNobody, "", "Nobody except the governance process can instantiate a contract from the code, optional")
cmd.Flags().String(flagInstantiateByAddress, "", "Only this address can instantiate a contract instance from the code, optional")
cmd.Flags().Bool(flagUnpinCode, false, "Unpin code on upload, optional")
cmd.Flags().String(flagSource, "", "Code Source URL is a valid absolute HTTPS URI to the contract's source code,")
cmd.Flags().String(flagBuilder, "", "Builder is a valid docker image name with tag, such as \"cosmwasm/workspace-optimizer:0.12.9\"")
cmd.Flags().BytesHex(flagCodeHash, nil, "CodeHash is the sha256 hash of the wasm code")
cmd.Flags().StringSlice(flagInstantiateByAnyOfAddress, []string{}, "Any of the addresses can instantiate a contract from the code, optional")
cmd.Flags().String(flagAmount, "", "Coins to send to the contract during instantiation")
cmd.Flags().String(flagLabel, "", "A human-readable name for this contract in lists")
Expand Down
62 changes: 62 additions & 0 deletions x/wasm/client/cli/gov_tx_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cli

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -95,3 +96,64 @@ func TestParseAccessConfigUpdates(t *testing.T) {
})
}
}

// TODO: move tests
func TestParseCodeInfoFlags(t *testing.T) {
correctSource := "https://github.com/CosmWasm/wasmd/blob/main/x/wasm/keeper/testdata/hackatom.wasm"
correctBuilderRef := "cosmwasm/workspace-optimizer:0.12.9"

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

checksumStr := "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5"

specs := map[string]struct {
args []string
expErr bool
}{
"source missing": {
args: []string{"--builder=" + correctBuilderRef, "--code-hash=" + checksumStr},
expErr: true,
},
"builder missing": {
args: []string{"--code-source-url=" + correctSource, "--code-hash=" + checksumStr},
expErr: true,
},
"code hash missing": {
args: []string{"--code-source-url=" + correctSource, "--builder=" + correctBuilderRef},
expErr: true,
},
"source format wrong": {
args: []string{"--code-source-url=" + "format_wrong", "--builder=" + correctBuilderRef, "--code-hash=" + checksumStr},
expErr: true,
},
"builder format wrong": {
args: []string{"--code-source-url=" + correctSource, "--builder=" + "format//", "--code-hash=" + checksumStr},
expErr: true,
},
"code hash wrong": {
args: []string{"--code-source-url=" + correctSource, "--builder=" + correctBuilderRef, "--code-hash=" + "AA"},
expErr: true,
},
"happy path, none set": {
args: []string{},
expErr: false,
},
"happy path all set": {
args: []string{"--code-source-url=" + correctSource, "--builder=" + correctBuilderRef, "--code-hash=" + checksumStr},
expErr: false,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
flags := ProposalStoreCodeCmd().Flags()
require.NoError(t, flags.Parse(spec.args))
_, _, _, gotErr := parseVerificationFlags(wasmBin, flags)
if spec.expErr {
require.Error(t, gotErr)
return
}
require.NoError(t, gotErr)
})
}
}
3 changes: 3 additions & 0 deletions x/wasm/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import (
const (
flagAmount = "amount"
flagLabel = "label"
flagSource = "code-source-url"
flagBuilder = "builder"
alpe marked this conversation as resolved.
Show resolved Hide resolved
flagCodeHash = "code-hash"
flagAdmin = "admin"
flagNoAdmin = "no-admin"
flagFixMsg = "fix-msg"
Expand Down
Loading