Skip to content

Commit

Permalink
Provide source, builder and codehash information in store code propos…
Browse files Browse the repository at this point in the history
…al message
  • Loading branch information
orkunkl committed Nov 15, 2022
1 parent c0a482e commit b102cfa
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 6 deletions.
3 changes: 3 additions & 0 deletions docs/proto/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,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
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.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
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.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec=
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
8 changes: 8 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;
// 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
57 changes: 57 additions & 0 deletions x/wasm/client/cli/gov_tx.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package cli

import (
"bytes"
"crypto/sha256"
"fmt"
"github.com/docker/distribution/reference"
"net/url"
"strconv"
"strings"

Expand All @@ -13,6 +17,7 @@ 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"
)
Expand Down Expand Up @@ -45,13 +50,20 @@ func ProposalStoreCodeCmd() *cobra.Command {
return err
}

source, builder, codeHash, err := parseCodeInfoFlags(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 +85,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, []byte{}, "CodeHash is the sha256 hash of the wasm code")

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

func parseCodeInfoFlags(wasm []byte, flags *flag.FlagSet) (string, string, []byte, error) {
source, err := flags.GetString(flagSource)
if err != nil {
return "", "", []byte{}, fmt.Errorf("source: %s", err)
}
builder, err := flags.GetString(flagBuilder)
if err != nil {
return "", "", []byte{}, fmt.Errorf("builder: %s", err)
}
codeHash, err := flags.GetBytesHex(flagCodeHash)
if err != nil {
return "", "", []byte{}, 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 "", "", []byte{}, fmt.Errorf("source is required")
}
if _, err = url.ParseRequestURI(source); err != nil {
return "", "", []byte{}, fmt.Errorf("source: %s", err)
}
if builder == "" {
return "", "", []byte{}, fmt.Errorf("builder is required")
}
if _, err := reference.ParseDockerRef(builder); err != nil {
return "", "", []byte{}, fmt.Errorf("builder: %s", err)
}
if len(codeHash) == 0 {
return "", "", []byte{}, 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
checksum := sha256.Sum256(wasm)
if !bytes.Equal(checksum[:], codeHash[:]) {
return "", "", []byte{}, 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
61 changes: 61 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,63 @@ func TestParseAccessConfigUpdates(t *testing.T) {
})
}
}

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 := parseCodeInfoFlags(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"
flagCodeHash = "code-hash"
flagAdmin = "admin"
flagNoAdmin = "no-admin"
flagFixMsg = "fix-msg"
Expand Down
74 changes: 69 additions & 5 deletions x/wasm/client/proposal_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ func TestGovRestHandlers(t *testing.T) {
"run_as": "cosmos100dejzacpanrldpjjwksjm62shqhyss44jf5xz",
"wasm_byte_code": []byte("valid wasm byte code"),
"source": "https://example.com/",
"builder": "my/builder:tag",
"builder": "cosmwasm/workspace-optimizer:v0.12.9",
"code_hash": "2badc56e12711c6387b3b7f9842f1e17a244f5fe354900920362f6668f266125",
"instantiate_permission": dict{
"permission": "OnlyAddress",
"address": "cosmos1ve557a5g9yw2g2z57js3pdmcvd5my6g8ze20np",
Expand All @@ -82,7 +83,8 @@ func TestGovRestHandlers(t *testing.T) {
"run_as": "cosmos100dejzacpanrldpjjwksjm62shqhyss44jf5xz",
"wasm_byte_code": []byte("valid wasm byte code"),
"source": "https://example.com/",
"builder": "my/builder:tag",
"builder": "cosmwasm/workspace-optimizer:v0.12.9",
"code_hash": "2badc56e12711c6387b3b7f9842f1e17a244f5fe354900920362f6668f266125",
"deposit": []dict{{"denom": "ustake", "amount": "10"}},
"proposer": "cosmos1ve557a5g9yw2g2z57js3pdmcvd5my6g8ze20np",
"base_req": aBaseReq,
Expand All @@ -98,7 +100,8 @@ func TestGovRestHandlers(t *testing.T) {
"run_as": "cosmos100dejzacpanrldpjjwksjm62shqhyss44jf5xz",
"wasm_byte_code": []byte("valid wasm byte code"),
"source": "https://example.com/",
"builder": "my/builder:tag",
"builder": "cosmwasm/workspace-optimizer:v0.12.9",
"code_hash": "2badc56e12711c6387b3b7f9842f1e17a244f5fe354900920362f6668f266125",
"instantiate_permission": dict{
"permission": "Nobody",
"address": "cosmos1ve557a5g9yw2g2z57js3pdmcvd5my6g8ze20np",
Expand All @@ -118,7 +121,8 @@ func TestGovRestHandlers(t *testing.T) {
"run_as": "cosmos100dejzacpanrldpjjwksjm62shqhyss44jf5xz",
"wasm_byte_code": []byte("valid wasm byte code"),
"source": "https://example.com/",
"builder": "my/builder:tag",
"code_hash": "2badc56e12711c6387b3b7f9842f1e17a244f5fe354900920362f6668f266125",
"builder": "cosmwasm/workspace-optimizer:v0.12.9",
"instantiate_permission": dict{
"permission": "OnlyAddress",
"address": "cosmos1ve557a5g9yw2g2z57js3pdmcvd5my6g8ze20np",
Expand All @@ -137,8 +141,68 @@ func TestGovRestHandlers(t *testing.T) {
"type": "store-code",
"run_as": "cosmos100dejzacpanrldpjjwksjm62shqhyss44jf5xz",
"wasm_byte_code": "",
"builder": "cosmwasm/workspace-optimizer:v0.12.9",
"source": "https://example.com/",
"builder": "my/builder:tag",
"code_hash": "2badc56e12711c6387b3b7f9842f1e17a244f5fe354900920362f6668f266125",
"instantiate_permission": dict{
"permission": "OnlyAddress",
"address": "cosmos1ve557a5g9yw2g2z57js3pdmcvd5my6g8ze20np",
},
"deposit": []dict{{"denom": "ustake", "amount": "10"}},
"proposer": "cosmos1ve557a5g9yw2g2z57js3pdmcvd5my6g8ze20np",
"base_req": aBaseReq,
},
expCode: http.StatusBadRequest,
},
"store-code with incomplete content data: no builder": {
srcPath: "/gov/proposals/wasm_store_code",
srcBody: dict{
"title": "Test Proposal",
"description": "My proposal",
"type": "store-code",
"run_as": "cosmos100dejzacpanrldpjjwksjm62shqhyss44jf5xz",
"wasm_byte_code": "",
"source": "https://example.com/",
"code_hash": "2badc56e12711c6387b3b7f9842f1e17a244f5fe354900920362f6668f266125",
"instantiate_permission": dict{
"permission": "OnlyAddress",
"address": "cosmos1ve557a5g9yw2g2z57js3pdmcvd5my6g8ze20np",
},
"deposit": []dict{{"denom": "ustake", "amount": "10"}},
"proposer": "cosmos1ve557a5g9yw2g2z57js3pdmcvd5my6g8ze20np",
"base_req": aBaseReq,
},
expCode: http.StatusBadRequest,
},
"store-code with incomplete content data: no code hash": {
srcPath: "/gov/proposals/wasm_store_code",
srcBody: dict{
"title": "Test Proposal",
"description": "My proposal",
"type": "store-code",
"run_as": "cosmos100dejzacpanrldpjjwksjm62shqhyss44jf5xz",
"wasm_byte_code": "",
"builder": "cosmwasm/workspace-optimizer:v0.12.9",
"source": "https://example.com/",
"instantiate_permission": dict{
"permission": "OnlyAddress",
"address": "cosmos1ve557a5g9yw2g2z57js3pdmcvd5my6g8ze20np",
},
"deposit": []dict{{"denom": "ustake", "amount": "10"}},
"proposer": "cosmos1ve557a5g9yw2g2z57js3pdmcvd5my6g8ze20np",
"base_req": aBaseReq,
},
expCode: http.StatusBadRequest,
},
"store-code with incomplete content data: no source": {
srcPath: "/gov/proposals/wasm_store_code",
srcBody: dict{
"title": "Test Proposal",
"description": "My proposal",
"type": "store-code",
"run_as": "cosmos100dejzacpanrldpjjwksjm62shqhyss44jf5xz",
"wasm_byte_code": "",
"builder": "cosmwasm/workspace-optimizer:v0.12.9",
"instantiate_permission": dict{
"permission": "OnlyAddress",
"address": "cosmos1ve557a5g9yw2g2z57js3pdmcvd5my6g8ze20np",
Expand Down
11 changes: 11 additions & 0 deletions x/wasm/client/rest/gov.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ type StoreCodeProposalJSONReq struct {

// UnpinCode indicates if the code should not be pinned as part of the proposal.
UnpinCode bool `json:"unpin_code" yaml:"unpin_code"`

// Source is the URL where the code is hosted
Source string `json:"source" yaml:"source"`
// Builder is the docker image used to build the code deterministically, used for smart
// contract verification
Builder string `json:"builder" yaml:"builder"`
// CodeHash is the SHA256 sum of the code outputted by optimizer, used for smart contract verification
CodeHash []byte `json:"code_hash" yaml:"code_hash"`
}

func (s StoreCodeProposalJSONReq) Content() govtypes.Content {
Expand All @@ -40,6 +48,9 @@ func (s StoreCodeProposalJSONReq) Content() govtypes.Content {
WASMByteCode: s.WASMByteCode,
InstantiatePermission: s.InstantiatePermission,
UnpinCode: s.UnpinCode,
Source: s.Source,
Builder: s.Builder,
CodeHash: s.CodeHash,
}
}

Expand Down
Loading

0 comments on commit b102cfa

Please sign in to comment.