package cli import ( "errors" "fmt" "io/ioutil" "strconv" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/spf13/cobra" flag "github.com/spf13/pflag" wasmUtils "github.com/CosmWasm/wasmd/x/wasm/client/utils" "github.com/CosmWasm/wasmd/x/wasm/types" ) const ( flagAmount = "amount" flagLabel = "label" flagAdmin = "admin" flagNoAdmin = "no-admin" flagRunAs = "run-as" flagInstantiateByEverybody = "instantiate-everybody" flagInstantiateByAddress = "instantiate-only-address" flagProposalType = "type" ) // GetTxCmd returns the transaction commands for this module func GetTxCmd() *cobra.Command { txCmd := &cobra.Command{ Use: types.ModuleName, Short: "Wasm transaction subcommands", DisableFlagParsing: true, SuggestionsMinimumDistance: 2, RunE: client.ValidateCmd, } txCmd.AddCommand( StoreCodeCmd(), InstantiateContractCmd(), ExecuteContractCmd(), MigrateContractCmd(), UpdateContractAdminCmd(), ClearContractAdminCmd(), ) return txCmd } // StoreCodeCmd will upload code to be reused. func StoreCodeCmd() *cobra.Command { cmd := &cobra.Command{ Use: "store [wasm file]", Short: "Upload a wasm binary", Aliases: []string{"upload", "st", "s"}, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } msg, err := parseStoreCodeArgs(args[0], clientCtx.GetFromAddress(), cmd.Flags()) if err != nil { return err } if err = msg.ValidateBasic(); err != nil { return err } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) }, } cmd.Flags().String(flagInstantiateByEverybody, "", "Everybody can instantiate a contract from the code, optional") cmd.Flags().String(flagInstantiateByAddress, "", "Only this address can instantiate a contract instance from the code, optional") flags.AddTxFlagsToCmd(cmd) return cmd } func parseStoreCodeArgs(file string, sender sdk.AccAddress, flags *flag.FlagSet) (types.MsgStoreCode, error) { wasm, err := ioutil.ReadFile(file) if err != nil { return types.MsgStoreCode{}, err } // gzip the wasm file if wasmUtils.IsWasm(wasm) { wasm, err = wasmUtils.GzipIt(wasm) if err != nil { return types.MsgStoreCode{}, err } } else if !wasmUtils.IsGzip(wasm) { return types.MsgStoreCode{}, fmt.Errorf("invalid input file. Use wasm binary or gzip") } var perm *types.AccessConfig onlyAddrStr, err := flags.GetString(flagInstantiateByAddress) if err != nil { return types.MsgStoreCode{}, fmt.Errorf("instantiate by address: %s", err) } if onlyAddrStr != "" { allowedAddr, err := sdk.AccAddressFromBech32(onlyAddrStr) if err != nil { return types.MsgStoreCode{}, sdkerrors.Wrap(err, flagInstantiateByAddress) } x := types.AccessTypeOnlyAddress.With(allowedAddr) perm = &x } else { everybodyStr, err := flags.GetString(flagInstantiateByEverybody) if err != nil { return types.MsgStoreCode{}, fmt.Errorf("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 } } } msg := types.MsgStoreCode{ Sender: sender.String(), WASMByteCode: wasm, InstantiatePermission: perm, } return msg, nil } // InstantiateContractCmd will instantiate a contract from previously uploaded code. func InstantiateContractCmd() *cobra.Command { cmd := &cobra.Command{ Use: "instantiate [code_id_int64] [json_encoded_init_args] --label [text] --admin [address,optional] --amount [coins,optional]", Short: "Instantiate a wasm contract", Aliases: []string{"start", "init", "inst", "i"}, Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } msg, err := parseInstantiateArgs(args[0], args[1], clientCtx.GetFromAddress(), cmd.Flags()) if err != nil { return err } if err := msg.ValidateBasic(); err != nil { return err } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) }, } 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") cmd.Flags().String(flagAdmin, "", "Address of an admin") cmd.Flags().Bool(flagNoAdmin, false, "You must set this explicitly if you don't want an admin") flags.AddTxFlagsToCmd(cmd) return cmd } func parseInstantiateArgs(rawCodeID, initMsg string, sender sdk.AccAddress, flags *flag.FlagSet) (types.MsgInstantiateContract, error) { // get the id of the code to instantiate codeID, err := strconv.ParseUint(rawCodeID, 10, 64) if err != nil { return types.MsgInstantiateContract{}, err } amountStr, err := flags.GetString(flagAmount) if err != nil { return types.MsgInstantiateContract{}, fmt.Errorf("amount: %s", err) } amount, err := sdk.ParseCoinsNormalized(amountStr) if err != nil { return types.MsgInstantiateContract{}, fmt.Errorf("amount: %s", err) } label, err := flags.GetString(flagLabel) if err != nil { return types.MsgInstantiateContract{}, fmt.Errorf("label: %s", err) } if label == "" { return types.MsgInstantiateContract{}, errors.New("label is required on all contracts") } adminStr, err := flags.GetString(flagAdmin) if err != nil { return types.MsgInstantiateContract{}, fmt.Errorf("admin: %s", err) } noAdmin, err := flags.GetBool(flagNoAdmin) if err != nil { return types.MsgInstantiateContract{}, fmt.Errorf("no-admin: %s", err) } // ensure sensible admin is set (or explicitly immutable) if adminStr == "" && !noAdmin { return types.MsgInstantiateContract{}, fmt.Errorf("you must set an admin or explicitly pass --no-admin to make it immutible (wasmd issue #719)") } if adminStr != "" && noAdmin { return types.MsgInstantiateContract{}, fmt.Errorf("you set an admin and passed --no-admin, those cannot both be true") } // build and sign the transaction, then broadcast to Tendermint msg := types.MsgInstantiateContract{ Sender: sender.String(), CodeID: codeID, Label: label, Funds: amount, Msg: []byte(initMsg), Admin: adminStr, } return msg, nil } // ExecuteContractCmd will instantiate a contract from previously uploaded code. func ExecuteContractCmd() *cobra.Command { cmd := &cobra.Command{ Use: "execute [contract_addr_bech32] [json_encoded_send_args] --amount [coins,optional]", Short: "Execute a command on a wasm contract", Aliases: []string{"run", "call", "exec", "ex", "e"}, Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } msg, err := parseExecuteArgs(args[0], args[1], clientCtx.GetFromAddress(), cmd.Flags()) if err != nil { return err } if err := msg.ValidateBasic(); err != nil { return err } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) }, } cmd.Flags().String(flagAmount, "", "Coins to send to the contract along with command") flags.AddTxFlagsToCmd(cmd) return cmd } func parseExecuteArgs(contractAddr string, execMsg string, sender sdk.AccAddress, flags *flag.FlagSet) (types.MsgExecuteContract, error) { amountStr, err := flags.GetString(flagAmount) if err != nil { return types.MsgExecuteContract{}, fmt.Errorf("amount: %s", err) } amount, err := sdk.ParseCoinsNormalized(amountStr) if err != nil { return types.MsgExecuteContract{}, err } return types.MsgExecuteContract{ Sender: sender.String(), Contract: contractAddr, Funds: amount, Msg: []byte(execMsg), }, nil }