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 2 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
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
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.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;
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
58 changes: 58 additions & 0 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,6 +18,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 +51,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 +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, []byte{}, "CodeHash is the sha256 hash of the wasm code")
orkunkl marked this conversation as resolved.
Show resolved Hide resolved

// proposal flags
cmd.Flags().String(cli.FlagTitle, "", "Title of proposal")
Expand All @@ -81,6 +97,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)
orkunkl marked this conversation as resolved.
Show resolved Hide resolved
}
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
alpe marked this conversation as resolved.
Show resolved Hide resolved
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"
alpe marked this conversation as resolved.
Show resolved Hide resolved
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
3 changes: 3 additions & 0 deletions x/wasm/simulation/proposals.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ func SimulateStoreCodeProposal(wasmKeeper WasmKeeper) simtypes.ContentSimulatorF
wasmBz,
&permission,
false,
"",
"",
[]byte{},
orkunkl marked this conversation as resolved.
Show resolved Hide resolved
)
}
}
Expand Down
Loading