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

Introduce new AccessType to allow a set of addresses #974

Merged
merged 5 commits into from
Sep 8, 2022
Merged
Show file tree
Hide file tree
Changes from all 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: 4 additions & 2 deletions docs/proto/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ AccessConfig access control type.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `permission` | [AccessType](#cosmwasm.wasm.v1.AccessType) | | |
| `address` | [string](#string) | | |
| `address` | [string](#string) | | Address Deprecated: replaced by addresses |
| `addresses` | [string](#string) | repeated | |



Expand Down Expand Up @@ -240,8 +241,9 @@ AccessType permission types
| ---- | ------ | ----------- |
| ACCESS_TYPE_UNSPECIFIED | 0 | AccessTypeUnspecified placeholder for empty value |
| ACCESS_TYPE_NOBODY | 1 | AccessTypeNobody forbidden |
| ACCESS_TYPE_ONLY_ADDRESS | 2 | AccessTypeOnlyAddress restricted to an address |
| ACCESS_TYPE_ONLY_ADDRESS | 2 | AccessTypeOnlyAddress restricted to a single address Deprecated: use AccessTypeAnyOfAddresses instead |
| ACCESS_TYPE_EVERYBODY | 3 | AccessTypeEverybody unrestricted |
| ACCESS_TYPE_ANY_OF_ADDRESSES | 4 | AccessTypeAnyOfAddresses allow any of the addresses |



Expand Down
10 changes: 9 additions & 1 deletion proto/cosmwasm/wasm/v1/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@ enum AccessType {
// AccessTypeNobody forbidden
ACCESS_TYPE_NOBODY = 1
[ (gogoproto.enumvalue_customname) = "AccessTypeNobody" ];
// AccessTypeOnlyAddress restricted to an address
// AccessTypeOnlyAddress restricted to a single address
// Deprecated: use AccessTypeAnyOfAddresses instead
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why deprecate?
I think it is fine to use the simpler version for the cases where it works

ACCESS_TYPE_ONLY_ADDRESS = 2
[ (gogoproto.enumvalue_customname) = "AccessTypeOnlyAddress" ];
// AccessTypeEverybody unrestricted
ACCESS_TYPE_EVERYBODY = 3
[ (gogoproto.enumvalue_customname) = "AccessTypeEverybody" ];
// AccessTypeAnyOfAddresses allow any of the addresses
alpe marked this conversation as resolved.
Show resolved Hide resolved
ACCESS_TYPE_ANY_OF_ADDRESSES = 4
[ (gogoproto.enumvalue_customname) = "AccessTypeAnyOfAddresses" ];
}

// AccessTypeParam
Expand All @@ -37,7 +41,11 @@ message AccessTypeParam {
message AccessConfig {
option (gogoproto.goproto_stringer) = true;
AccessType permission = 1 [ (gogoproto.moretags) = "yaml:\"permission\"" ];

// Address
// Deprecated: replaced by addresses
alpe marked this conversation as resolved.
Show resolved Hide resolved
string address = 2 [ (gogoproto.moretags) = "yaml:\"address\"" ];
repeated string addresses = 3 [ (gogoproto.moretags) = "yaml:\"addresses\"" ];
}

// Params defines the set of wasm parameters.
Expand Down
1 change: 1 addition & 0 deletions x/wasm/client/cli/genesis_msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func GenesisStoreCodeCmd(defaultNodeHome string, genesisMutator GenesisMutator)
cmd.Flags().String(flagInstantiateByEverybody, "", "Everybody can instantiate a contract from the code, optional")
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().StringSlice(flagInstantiateByAnyOfAddress, []string{}, "Any of the addresses can instantiate a contract from the code, optional")

cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|kwallet|pass|test)")
Expand Down
35 changes: 23 additions & 12 deletions x/wasm/client/cli/gov_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -597,27 +597,38 @@ func ProposalUnpinCodesCmd() *cobra.Command {
return cmd
}

func parseAccessConfig(config string) (types.AccessConfig, error) {
switch config {
func parseAccessConfig(raw string) (c types.AccessConfig, err error) {
switch raw {
case "nobody":
return types.AllowNobody, nil
case "everybody":
return types.AllowEverybody, nil
default:
address, err := sdk.AccAddressFromBech32(config)
if err != nil {
return types.AccessConfig{}, fmt.Errorf("unable to parse address %s", config)
parts := strings.Split(raw, ",")
addrs := make([]sdk.AccAddress, len(parts))
for i, v := range parts {
addr, err := sdk.AccAddressFromBech32(v)
if err != nil {
return types.AccessConfig{}, fmt.Errorf("unable to parse address %q: %s", v, err)
}
addrs[i] = addr
}
return types.AccessTypeOnlyAddress.With(address), nil
defer func() { // convert panic in ".With" to error for better output
if r := recover(); r != nil {
err = r.(error)
}
}()
cfg := types.AccessTypeAnyOfAddresses.With(addrs...)
return cfg, cfg.ValidateBasic()
}
}

func parseAccessConfigUpdates(args []string) ([]types.AccessConfigUpdate, error) {
updates := make([]types.AccessConfigUpdate, len(args))
for i, c := range args {
// format: code_id,access_config
// access_config: nobody|everybody|address
parts := strings.Split(c, ",")
// format: code_id:access_config
// access_config: nobody|everybody|address(es)
parts := strings.Split(c, ":")
if len(parts) != 2 {
return nil, fmt.Errorf("invalid format")
}
Expand All @@ -642,15 +653,15 @@ func parseAccessConfigUpdates(args []string) ([]types.AccessConfigUpdate, error)
func ProposalUpdateInstantiateConfigCmd() *cobra.Command {
bech32Prefix := sdk.GetConfig().GetBech32AccountAddrPrefix()
cmd := &cobra.Command{
Use: "update-instantiate-config [code-id,permission]...",
Use: "update-instantiate-config [code-id:permission]...",
Short: "Submit an update instantiate config proposal.",
Args: cobra.MinimumNArgs(1),
Long: strings.TrimSpace(
fmt.Sprintf(`Submit an update instantiate config proposal for multiple code ids.

Example:
$ %s tx gov submit-proposal update-instantiate-config 1,nobody 2,everybody 3,%s1l2rsakp388kuv9k8qzq6lrm9taddae7fpx59wm
`, version.AppName, bech32Prefix)),
$ %s tx gov submit-proposal update-instantiate-config 1:nobody 2:everybody 3:%s1l2rsakp388kuv9k8qzq6lrm9taddae7fpx59wm,%s1vx8knpllrj7n963p9ttd80w47kpacrhuts497x
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I think this should work fine.

`, version.AppName, bech32Prefix, bech32Prefix)),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
Expand Down
97 changes: 97 additions & 0 deletions x/wasm/client/cli/gov_tx_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package cli

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

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

func TestParseAccessConfigUpdates(t *testing.T) {
specs := map[string]struct {
src []string
exp []types.AccessConfigUpdate
expErr bool
}{
"nobody": {
src: []string{"1:nobody"},
exp: []types.AccessConfigUpdate{{
CodeID: 1,
InstantiatePermission: types.AccessConfig{Permission: types.AccessTypeNobody},
}},
},
"everybody": {
src: []string{"1:everybody"},
exp: []types.AccessConfigUpdate{{
CodeID: 1,
InstantiatePermission: types.AccessConfig{Permission: types.AccessTypeEverybody},
}},
},
"any of addresses - single": {
src: []string{"1:cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x"},
exp: []types.AccessConfigUpdate{
{
CodeID: 1,
InstantiatePermission: types.AccessConfig{
Permission: types.AccessTypeAnyOfAddresses,
Addresses: []string{"cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x"},
},
},
},
},
"any of addresses - multiple": {
src: []string{"1:cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x,cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr"},
exp: []types.AccessConfigUpdate{
{
CodeID: 1,
InstantiatePermission: types.AccessConfig{
Permission: types.AccessTypeAnyOfAddresses,
Addresses: []string{"cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x", "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr"},
},
},
},
},
"multiple code ids with different permissions": {
src: []string{"1:cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x,cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", "2:nobody"},
exp: []types.AccessConfigUpdate{
{
CodeID: 1,
InstantiatePermission: types.AccessConfig{
Permission: types.AccessTypeAnyOfAddresses,
Addresses: []string{"cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x", "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr"},
},
}, {
CodeID: 2,
InstantiatePermission: types.AccessConfig{
Permission: types.AccessTypeNobody,
},
},
},
},
"any of addresses - empty list": {
src: []string{"1:"},
expErr: true,
},
"any of addresses - invalid address": {
src: []string{"1:foo"},
expErr: true,
},
"any of addresses - duplicate address": {
src: []string{"1:cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x,cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x"},
expErr: true,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
got, gotErr := parseAccessConfigUpdates(spec.src)
if spec.expErr {
require.Error(t, gotErr)
return
}
require.NoError(t, gotErr)
assert.Equal(t, spec.exp, got)
})
}
}
109 changes: 66 additions & 43 deletions x/wasm/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ import (
)

const (
flagAmount = "amount"
flagLabel = "label"
flagAdmin = "admin"
flagNoAdmin = "no-admin"
flagRunAs = "run-as"
flagInstantiateByEverybody = "instantiate-everybody"
flagInstantiateNobody = "instantiate-nobody"
flagInstantiateByAddress = "instantiate-only-address"
flagUnpinCode = "unpin-code"
flagAmount = "amount"
flagLabel = "label"
flagAdmin = "admin"
flagNoAdmin = "no-admin"
flagRunAs = "run-as"
flagInstantiateByEverybody = "instantiate-everybody"
flagInstantiateNobody = "instantiate-nobody"
flagInstantiateByAddress = "instantiate-only-address"
flagInstantiateByAnyOfAddress = "instantiate-anyof-addresses"
flagUnpinCode = "unpin-code"
)

// GetTxCmd returns the transaction commands for this module
Expand Down Expand Up @@ -75,7 +76,8 @@ func StoreCodeCmd() *cobra.Command {

cmd.Flags().String(flagInstantiateByEverybody, "", "Everybody can instantiate a contract from the code, optional")
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().String(flagInstantiateByAddress, "", "Deprecated: Only this address can instantiate a contract from the code, optional")
cmd.Flags().StringSlice(flagInstantiateByAnyOfAddress, []string{}, "Any of the addresses can instantiate a contract from the code, optional")
flags.AddTxFlagsToCmd(cmd)
return cmd
}
Expand All @@ -97,55 +99,76 @@ func parseStoreCodeArgs(file string, sender sdk.AccAddress, flags *flag.FlagSet)
return types.MsgStoreCode{}, fmt.Errorf("invalid input file. Use wasm binary or gzip")
}

var perm *types.AccessConfig
perm, err := parseAccessConfigFlags(flags)
if err != nil {
return types.MsgStoreCode{}, err
}

msg := types.MsgStoreCode{
Sender: sender.String(),
WASMByteCode: wasm,
InstantiatePermission: perm,
}
return msg, nil
}

func parseAccessConfigFlags(flags *flag.FlagSet) (*types.AccessConfig, error) {
addrs, err := flags.GetStringSlice(flagInstantiateByAnyOfAddress)
if err != nil {
return nil, fmt.Errorf("flag any of: %s", err)
}
if len(addrs) != 0 {
acceptedAddrs := make([]sdk.AccAddress, len(addrs))
for i, v := range addrs {
acceptedAddrs[i], err = sdk.AccAddressFromBech32(v)
if err != nil {
return nil, fmt.Errorf("parse %q: %w", v, err)
}
}
x := types.AccessTypeAnyOfAddresses.With(acceptedAddrs...)
return &x, nil
}

onlyAddrStr, err := flags.GetString(flagInstantiateByAddress)
if err != nil {
return types.MsgStoreCode{}, fmt.Errorf("instantiate by address: %s", err)
return nil, fmt.Errorf("instantiate by address: %s", err)
}
if onlyAddrStr != "" {
allowedAddr, err := sdk.AccAddressFromBech32(onlyAddrStr)
if err != nil {
return types.MsgStoreCode{}, sdkerrors.Wrap(err, flagInstantiateByAddress)
return nil, sdkerrors.Wrap(err, flagInstantiateByAddress)
}
x := types.AccessTypeOnlyAddress.With(allowedAddr)
perm = &x
} else {
everybodyStr, err := flags.GetString(flagInstantiateByEverybody)
return &x, nil
}
everybodyStr, err := flags.GetString(flagInstantiateByEverybody)
if err != nil {
return nil, fmt.Errorf("instantiate by everybody: %s", err)
}
if everybodyStr != "" {
ok, err := strconv.ParseBool(everybodyStr)
if err != nil {
return types.MsgStoreCode{}, fmt.Errorf("instantiate by everybody: %s", err)
return nil, fmt.Errorf("boolean value expected for instantiate by everybody: %s", err)
}
if everybodyStr != "" {
ok, err := strconv.ParseBool(everybodyStr)
if err != nil {
return types.MsgStoreCode{}, fmt.Errorf("boolean value expected for instantiate by everybody: %s", err)
}
if ok {
perm = &types.AllowEverybody
}
if ok {
return &types.AllowEverybody, nil
}
}

nobodyStr, err := flags.GetString(flagInstantiateNobody)
nobodyStr, err := flags.GetString(flagInstantiateNobody)
if err != nil {
return nil, fmt.Errorf("instantiate by nobody: %s", err)
}
if nobodyStr != "" {
ok, err := strconv.ParseBool(nobodyStr)
if err != nil {
return types.MsgStoreCode{}, fmt.Errorf("instantiate by nobody: %s", err)
return nil, fmt.Errorf("boolean value expected for instantiate by nobody: %s", err)
}
if nobodyStr != "" {
ok, err := strconv.ParseBool(nobodyStr)
if err != nil {
return types.MsgStoreCode{}, fmt.Errorf("boolean value expected for instantiate by nobody: %s", err)
}
if ok {
perm = &types.AllowNobody
}
if ok {
return &types.AllowNobody, nil
}

}

msg := types.MsgStoreCode{
Sender: sender.String(),
WASMByteCode: wasm,
InstantiatePermission: perm,
}
return msg, nil
return nil, nil
}

// InstantiateContractCmd will instantiate a contract from previously uploaded code.
Expand Down
Loading