diff --git a/tests/system/cli.go b/tests/system/cli.go index 8d0ab87f9e..7aeddb38ab 100644 --- a/tests/system/cli.go +++ b/tests/system/cli.go @@ -2,6 +2,7 @@ package system import ( "fmt" + "io" "os/exec" "path/filepath" "strconv" @@ -196,6 +197,10 @@ func (c WasmdCli) CustomQuery(args ...string) string { // execute shell command func (c WasmdCli) run(args []string) (output string, ok bool) { + return c.runWithInput(args, nil) +} + +func (c WasmdCli) runWithInput(args []string, input io.Reader) (output string, ok bool) { if c.Debug { c.t.Logf("+++ running `%s %s`", c.execBinary, strings.Join(args, " ")) } @@ -207,6 +212,7 @@ func (c WasmdCli) run(args []string) (output string, ok bool) { }() cmd := exec.Command(locateExecutable("wasmd"), args...) //nolint:gosec cmd.Dir = workDir + cmd.Stdin = input return cmd.CombinedOutput() }() ok = c.assertErrorFn(c.t, gotErr, string(gotOut)) @@ -256,13 +262,22 @@ func (c WasmdCli) WasmExecute(contractAddr, msg, from string, args ...string) st // AddKey add key to default keyring. Returns address func (c WasmdCli) AddKey(name string) string { - cmd := c.withKeyringFlags("keys", "add", name, "--no-backup") + cmd := c.withKeyringFlags("keys", "add", name) //, "--no-backup") out, _ := c.run(cmd) addr := gjson.Get(out, "address").String() require.NotEmpty(c.t, addr, "got %q", out) return addr } +// AddKeyFromSeed recovers the key from given seed and add it to default keyring. Returns address +func (c WasmdCli) AddKeyFromSeed(name, mnemoic string) string { + cmd := c.withKeyringFlags("keys", "add", name, "--recover") + out, _ := c.runWithInput(cmd, strings.NewReader(mnemoic)) + addr := gjson.Get(out, "address").String() + require.NotEmpty(c.t, addr, "got %q", out) + return addr +} + // GetKeyAddr returns address func (c WasmdCli) GetKeyAddr(name string) string { cmd := c.withKeyringFlags("keys", "show", name, "-a") diff --git a/tests/system/fraud_test.go b/tests/system/fraud_test.go index 3fcde936a8..9e5e4d61d5 100644 --- a/tests/system/fraud_test.go +++ b/tests/system/fraud_test.go @@ -13,6 +13,7 @@ import ( ) func TestRecursiveMsgsExternalTrigger(t *testing.T) { + sut.ResetChain(t) const maxBlockGas = 2_000_000 sut.ModifyGenesisJSON(t, SetConsensusMaxGas(t, maxBlockGas)) sut.StartChain(t) diff --git a/tests/system/genesis_io.go b/tests/system/genesis_io.go index 841c18c924..23e54f09e8 100644 --- a/tests/system/genesis_io.go +++ b/tests/system/genesis_io.go @@ -31,3 +31,15 @@ func GetGenesisBalance(rawGenesis []byte, addr string) sdk.Coins { } return r } + +// SetCodeUploadPermission sets the code upload permissions +func SetCodeUploadPermission(t *testing.T, permission string, addresses ...string) GenesisMutator { + return func(genesis []byte) []byte { + t.Helper() + state, err := sjson.Set(string(genesis), "app_state.wasm.params.code_upload_access.permission", permission) + require.NoError(t, err) + state, err = sjson.Set(state, "app_state.wasm.params.code_upload_access.addresses", addresses) + require.NoError(t, err) + return []byte(state) + } +} diff --git a/tests/system/permissioned_test.go b/tests/system/permissioned_test.go new file mode 100644 index 0000000000..b2da9b6444 --- /dev/null +++ b/tests/system/permissioned_test.go @@ -0,0 +1,62 @@ +//go:build system_test + +package system + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tidwall/gjson" +) + +func TestGrantStoreCodePermissionedChain(t *testing.T) { + cli := NewWasmdCLI(t, sut, verbose) + // set params to restrict chain + const chainAuthorityAddress = "wasm1pvuujjdk0xt043ga0j9nrfh5u8pzj4rpplyqkm" + sut.ModifyGenesisJSON(t, SetCodeUploadPermission(t, "AnyOfAddresses", chainAuthorityAddress)) + + recoveredAddress := cli.AddKeyFromSeed("chain_authority", "aisle ship absurd wedding arch admit fringe foam cluster tide trim aisle salad shiver tackle palm glance wrist valley hamster couch crystal frozen chronic") + require.Equal(t, chainAuthorityAddress, recoveredAddress) + devAccount := cli.AddKey("dev_account") + + sut.ModifyGenesisCLI(t, + []string{"genesis", "add-genesis-account", chainAuthorityAddress, "100000000stake"}, + ) + sut.ModifyGenesisCLI(t, + []string{"genesis", "add-genesis-account", devAccount, "100000000stake"}, + ) + + sut.StartChain(t) + + // query params + rsp := cli.CustomQuery("q", "wasm", "params") + permission := gjson.Get(rsp, "code_upload_access.permission").String() + addrRes := gjson.Get(rsp, "code_upload_access.addresses").Array() + require.Equal(t, 1, len(addrRes)) + + require.Equal(t, permission, "AnyOfAddresses") + require.Equal(t, chainAuthorityAddress, addrRes[0].Str) + + // chain_authority grant upload permission to dev_account + rsp = cli.CustomCommand("tx", "wasm", "grant", "store-code", devAccount, "*:*", "--from="+chainAuthorityAddress) + RequireTxSuccess(t, rsp) + + // dev_account store code fails as the address is not in the code-upload accept-list + rsp = cli.CustomCommand("tx", "wasm", "store", "./testdata/hackatom.wasm.gzip", "--from="+devAccount, "--gas=1500000", "--fees=2stake") + RequireTxFailure(t, rsp) + + // create tx should work for addresses in the accept-list + args := cli.withTXFlags("tx", "wasm", "store", "./testdata/hackatom.wasm.gzip", "--from="+chainAuthorityAddress, "--generate-only") + tx, ok := cli.run(args) + require.True(t, ok) + + pathToTx := filepath.Join(t.TempDir(), "tx.json") + err := os.WriteFile(pathToTx, []byte(tx), os.FileMode(0o744)) + require.NoError(t, err) + + // store code via authz execution uses the given grant and should succeed + rsp = cli.CustomCommand("tx", "authz", "exec", pathToTx, "--from="+devAccount, "--gas=1500000", "--fees=2stake") + RequireTxSuccess(t, rsp) +} diff --git a/x/wasm/client/cli/tx.go b/x/wasm/client/cli/tx.go index 2fa1b94165..863730e9f2 100644 --- a/x/wasm/client/cli/tx.go +++ b/x/wasm/client/cli/tx.go @@ -67,8 +67,7 @@ func GetTxCmd() *cobra.Command { MigrateContractCmd(), UpdateContractAdminCmd(), ClearContractAdminCmd(), - GrantAuthorizationCmd(), - GrantStoreCodeAuthorizationCmd(), + GrantCmd(), UpdateInstantiateConfigCmd(), SubmitProposalCmd(), ) @@ -415,17 +414,31 @@ func parseExecuteArgs(contractAddr, execMsg string, sender sdk.AccAddress, flags }, nil } +func GrantCmd() *cobra.Command { + txCmd := &cobra.Command{ + Use: "grant", + Short: "Grant a authz permission", + DisableFlagParsing: true, + SilenceUsage: true, + } + txCmd.AddCommand( + GrantAuthorizationCmd(), + GrantStoreCodeAuthorizationCmd(), + ) + return txCmd +} + func GrantAuthorizationCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "grant [grantee] [message_type=\"execution\"|\"migration\"] [contract_addr_bech32] --allow-raw-msgs [msg1,msg2,...] --allow-msg-keys [key1,key2,...] --allow-all-messages", - Short: "Grant authorization to an address", + Use: "contract [message_type=\"execution\"|\"migration\"] [grantee] [contract_addr_bech32] --allow-raw-msgs [msg1,msg2,...] --allow-msg-keys [key1,key2,...] --allow-all-messages", + Short: "Grant authorization to interact with a contract on behalf of you", Long: fmt.Sprintf(`Grant authorization to an address. Examples: -$ %s tx grant execution --allow-all-messages --max-calls 1 --no-token-transfer --expiration 1667979596 +$ %s tx grant contract execution --allow-all-messages --max-calls 1 --no-token-transfer --expiration 1667979596 -$ %s tx grant execution --allow-all-messages --max-funds 100000uwasm --expiration 1667979596 +$ %s tx grant contract execution --allow-all-messages --max-funds 100000uwasm --expiration 1667979596 -$ %s tx grant execution --allow-all-messages --max-calls 5 --max-funds 100000uwasm --expiration 1667979596 +$ %s tx grant contract execution --allow-all-messages --max-calls 5 --max-funds 100000uwasm --expiration 1667979596 `, version.AppName, version.AppName, version.AppName), Args: cobra.ExactArgs(3), RunE: func(cmd *cobra.Command, args []string) error { @@ -560,15 +573,15 @@ $ %s tx grant execution --allow-all-messages --ma func GrantStoreCodeAuthorizationCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "grant [grantee] store-code [code_hash:permission]", - Short: "Grant authorization to an address", + Use: "store-code [grantee] [code_hash:permission]", + Short: "Grant authorization to upload contract code on behalf of you", Long: fmt.Sprintf(`Grant authorization to an address. Examples: -$ %s tx grant store-code 13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5:everybody 1wqrtry681b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5:nobody --expiration 1667979596 +$ %s tx grant store-code 13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5:everybody 1wqrtry681b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5:nobody --expiration 1667979596 -$ %s tx grant store-code *:%s1l2rsakp388kuv9k8qzq6lrm9taddae7fpx59wm,%s1vx8knpllrj7n963p9ttd80w47kpacrhuts497x +$ %s tx grant store-code *:%s1l2rsakp388kuv9k8qzq6lrm9taddae7fpx59wm,%s1vx8knpllrj7n963p9ttd80w47kpacrhuts497x `, version.AppName, version.AppName, version.AppName, version.AppName), - Args: cobra.MinimumNArgs(3), + Args: cobra.MinimumNArgs(2), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { @@ -580,11 +593,7 @@ $ %s tx grant store-code *:%s1l2rsakp388kuv9k8qzq6lrm9taddae7fpx5 return err } - if args[1] != "store-code" { - return fmt.Errorf("%s authorization type not supported", args[1]) - } - - grants, err := parseStoreCodeGrants(args[2:]) + grants, err := parseStoreCodeGrants(args[1:]) if err != nil { return err } diff --git a/x/wasm/types/authz.go b/x/wasm/types/authz.go index 7f49a8aea9..7a55dda860 100644 --- a/x/wasm/types/authz.go +++ b/x/wasm/types/authz.go @@ -51,7 +51,7 @@ func (a *StoreCodeAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authztype } code := storeMsg.WASMByteCode - permission := *storeMsg.InstantiatePermission + permission := storeMsg.InstantiatePermission if ioutils.IsGzip(code) { gasRegister, ok := GasRegisterFromContext(ctx) @@ -127,7 +127,7 @@ func (g CodeGrant) ValidateBasic() error { } // Accept checks if checksum and permission match the grant -func (g CodeGrant) Accept(checksum []byte, permission AccessConfig) bool { +func (g CodeGrant) Accept(checksum []byte, permission *AccessConfig) bool { if !strings.EqualFold(string(g.CodeHash), CodehashWildcard) && !bytes.EqualFold(g.CodeHash, checksum) { return false }