forked from cerc-io/laconicd-deprecated
wip: port wasm module
This commit is contained in:
@@ -0,0 +1,834 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"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"
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov/client/cli"
|
||||
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
flag "github.com/spf13/pflag"
|
||||
|
||||
"github.com/cerc-io/laconicd/x/wasm/types"
|
||||
)
|
||||
|
||||
func ProposalStoreCodeCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "wasm-store [wasm file] --title [text] --description [text] --run-as [address] --unpin-code [unpin_code] --source [source] --builder [builder] --code-hash [code_hash]",
|
||||
Short: "Submit a wasm binary proposal",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, proposalTitle, proposalDescr, deposit, err := getProposalInfo(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
src, err := parseStoreCodeArgs(args[0], clientCtx.FromAddress, cmd.Flags())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
runAs, err := cmd.Flags().GetString(flagRunAs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("run-as: %s", err)
|
||||
}
|
||||
if len(runAs) == 0 {
|
||||
return errors.New("run-as address is required")
|
||||
}
|
||||
|
||||
unpinCode, err := cmd.Flags().GetBool(flagUnpinCode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
source, builder, codeHash, err := parseVerificationFlags(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())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
cmd.Flags().String(flagRunAs, "", "The address that is stored as code creator")
|
||||
cmd.Flags().Bool(flagUnpinCode, false, "Unpin code on upload, 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, nil, "CodeHash is the sha256 hash of the wasm code")
|
||||
addInstantiatePermissionFlags(cmd)
|
||||
|
||||
// proposal flags
|
||||
cmd.Flags().String(cli.FlagTitle, "", "Title of proposal")
|
||||
cmd.Flags().String(cli.FlagDescription, "", "Description of proposal")
|
||||
cmd.Flags().String(cli.FlagDeposit, "", "Deposit of proposal")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func parseVerificationFlags(wasm []byte, flags *flag.FlagSet) (string, string, []byte, error) {
|
||||
source, err := flags.GetString(flagSource)
|
||||
if err != nil {
|
||||
return "", "", nil, fmt.Errorf("source: %s", err)
|
||||
}
|
||||
builder, err := flags.GetString(flagBuilder)
|
||||
if err != nil {
|
||||
return "", "", nil, fmt.Errorf("builder: %s", err)
|
||||
}
|
||||
codeHash, err := flags.GetBytesHex(flagCodeHash)
|
||||
if err != nil {
|
||||
return "", "", nil, 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 "", "", nil, fmt.Errorf("source is required")
|
||||
}
|
||||
if _, err = url.ParseRequestURI(source); err != nil {
|
||||
return "", "", nil, fmt.Errorf("source: %s", err)
|
||||
}
|
||||
if builder == "" {
|
||||
return "", "", nil, fmt.Errorf("builder is required")
|
||||
}
|
||||
if _, err := reference.ParseDockerRef(builder); err != nil {
|
||||
return "", "", nil, fmt.Errorf("builder: %s", err)
|
||||
}
|
||||
if len(codeHash) == 0 {
|
||||
return "", "", nil, 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 "", "", nil, 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]",
|
||||
Short: "Submit an instantiate wasm contract proposal",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, proposalTitle, proposalDescr, deposit, err := getProposalInfo(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
src, err := parseInstantiateArgs(args[0], args[1], clientCtx.Keyring, clientCtx.FromAddress, cmd.Flags())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
runAs, err := cmd.Flags().GetString(flagRunAs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("run-as: %s", err)
|
||||
}
|
||||
if len(runAs) == 0 {
|
||||
return errors.New("run-as address is required")
|
||||
}
|
||||
|
||||
content := types.InstantiateContractProposal{
|
||||
Title: proposalTitle,
|
||||
Description: proposalDescr,
|
||||
RunAs: runAs,
|
||||
Admin: src.Admin,
|
||||
CodeID: src.CodeID,
|
||||
Label: src.Label,
|
||||
Msg: src.Msg,
|
||||
Funds: src.Funds,
|
||||
}
|
||||
|
||||
msg, err := govtypes.NewMsgSubmitProposal(&content, deposit, clientCtx.GetFromAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
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 or key name of an admin")
|
||||
cmd.Flags().String(flagRunAs, "", "The address that pays the init funds. It is the creator of the contract and passed to the contract as sender on proposal execution")
|
||||
cmd.Flags().Bool(flagNoAdmin, false, "You must set this explicitly if you don't want an admin")
|
||||
|
||||
// proposal flags
|
||||
cmd.Flags().String(cli.FlagTitle, "", "Title of proposal")
|
||||
cmd.Flags().String(cli.FlagDescription, "", "Description of proposal")
|
||||
cmd.Flags().String(cli.FlagDeposit, "", "Deposit of proposal")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func ProposalInstantiateContract2Cmd() *cobra.Command {
|
||||
decoder := newArgDecoder(hex.DecodeString)
|
||||
cmd := &cobra.Command{
|
||||
Use: "instantiate-contract-2 [code_id_int64] [json_encoded_init_args] [salt] --label [text] --title [text] --description [text] --run-as [address] --admin [address,optional] --amount [coins,optional] --fix-msg [bool,optional]",
|
||||
Short: "Submit an instantiate wasm contract proposal with predictable address",
|
||||
Args: cobra.ExactArgs(3),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, proposalTitle, proposalDescr, deposit, err := getProposalInfo(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
src, err := parseInstantiateArgs(args[0], args[1], clientCtx.Keyring, clientCtx.FromAddress, cmd.Flags())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
runAs, err := cmd.Flags().GetString(flagRunAs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("run-as: %s", err)
|
||||
}
|
||||
if len(runAs) == 0 {
|
||||
return errors.New("run-as address is required")
|
||||
}
|
||||
|
||||
salt, err := decoder.DecodeString(args[2])
|
||||
if err != nil {
|
||||
return fmt.Errorf("salt: %w", err)
|
||||
}
|
||||
|
||||
fixMsg, err := cmd.Flags().GetBool(flagFixMsg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fix msg: %w", err)
|
||||
}
|
||||
|
||||
content := types.NewInstantiateContract2Proposal(proposalTitle, proposalDescr, runAs, src.Admin, src.CodeID, src.Label, src.Msg, src.Funds, salt, fixMsg)
|
||||
|
||||
msg, err := govtypes.NewMsgSubmitProposal(content, deposit, clientCtx.GetFromAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
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().String(flagRunAs, "", "The address that pays the init funds. It is the creator of the contract and passed to the contract as sender on proposal execution")
|
||||
cmd.Flags().Bool(flagNoAdmin, false, "You must set this explicitly if you don't want an admin")
|
||||
cmd.Flags().Bool(flagFixMsg, false, "An optional flag to include the json_encoded_init_args for the predictable address generation mode")
|
||||
decoder.RegisterFlags(cmd.PersistentFlags(), "salt")
|
||||
|
||||
// proposal flags
|
||||
cmd.Flags().String(cli.FlagTitle, "", "Title of proposal")
|
||||
cmd.Flags().String(cli.FlagDescription, "", "Description of proposal")
|
||||
cmd.Flags().String(cli.FlagDeposit, "", "Deposit of proposal")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func ProposalStoreAndInstantiateContractCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "store-instantiate [wasm file] [json_encoded_init_args] --label [text] --title [text] --description [text] --run-as [address]" +
|
||||
"--unpin-code [unpin_code,optional] --source [source,optional] --builder [builder,optional] --code-hash [code_hash,optional] --admin [address,optional] --amount [coins,optional]",
|
||||
Short: "Submit and instantiate a wasm contract proposal",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, proposalTitle, proposalDescr, deposit, err := getProposalInfo(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
src, err := parseStoreCodeArgs(args[0], clientCtx.FromAddress, cmd.Flags())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
runAs, err := cmd.Flags().GetString(flagRunAs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("run-as: %s", err)
|
||||
}
|
||||
if len(runAs) == 0 {
|
||||
return errors.New("run-as address is required")
|
||||
}
|
||||
|
||||
unpinCode, err := cmd.Flags().GetBool(flagUnpinCode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
source, builder, codeHash, err := parseVerificationFlags(src.WASMByteCode, cmd.Flags())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
amountStr, err := cmd.Flags().GetString(flagAmount)
|
||||
if err != nil {
|
||||
return fmt.Errorf("amount: %s", err)
|
||||
}
|
||||
amount, err := sdk.ParseCoinsNormalized(amountStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("amount: %s", err)
|
||||
}
|
||||
label, err := cmd.Flags().GetString(flagLabel)
|
||||
if err != nil {
|
||||
return fmt.Errorf("label: %s", err)
|
||||
}
|
||||
if label == "" {
|
||||
return errors.New("label is required on all contracts")
|
||||
}
|
||||
adminStr, err := cmd.Flags().GetString(flagAdmin)
|
||||
if err != nil {
|
||||
return fmt.Errorf("admin: %s", err)
|
||||
}
|
||||
noAdmin, err := cmd.Flags().GetBool(flagNoAdmin)
|
||||
if err != nil {
|
||||
return fmt.Errorf("no-admin: %s", err)
|
||||
}
|
||||
|
||||
// ensure sensible admin is set (or explicitly immutable)
|
||||
if adminStr == "" && !noAdmin {
|
||||
return fmt.Errorf("you must set an admin or explicitly pass --no-admin to make it immutible (wasmd issue #719)")
|
||||
}
|
||||
if adminStr != "" && noAdmin {
|
||||
return fmt.Errorf("you set an admin and passed --no-admin, those cannot both be true")
|
||||
}
|
||||
|
||||
if adminStr != "" {
|
||||
addr, err := sdk.AccAddressFromBech32(adminStr)
|
||||
if err != nil {
|
||||
info, err := clientCtx.Keyring.Key(adminStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("admin %s", err)
|
||||
}
|
||||
adminStr = info.GetAddress().String()
|
||||
} else {
|
||||
adminStr = addr.String()
|
||||
}
|
||||
}
|
||||
|
||||
content := types.StoreAndInstantiateContractProposal{
|
||||
Title: proposalTitle,
|
||||
Description: proposalDescr,
|
||||
RunAs: runAs,
|
||||
WASMByteCode: src.WASMByteCode,
|
||||
InstantiatePermission: src.InstantiatePermission,
|
||||
UnpinCode: unpinCode,
|
||||
Source: source,
|
||||
Builder: builder,
|
||||
CodeHash: codeHash,
|
||||
Admin: adminStr,
|
||||
Label: label,
|
||||
Msg: []byte(args[1]),
|
||||
Funds: amount,
|
||||
}
|
||||
|
||||
msg, err := govtypes.NewMsgSubmitProposal(&content, deposit, clientCtx.GetFromAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
cmd.Flags().String(flagRunAs, "", "The address that is stored as code creator. It is the creator of the contract and passed to the contract as sender on proposal execution")
|
||||
cmd.Flags().Bool(flagUnpinCode, false, "Unpin code on upload, 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, nil, "CodeHash is the sha256 hash of the wasm code")
|
||||
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 or key name of an admin")
|
||||
cmd.Flags().Bool(flagNoAdmin, false, "You must set this explicitly if you don't want an admin")
|
||||
addInstantiatePermissionFlags(cmd)
|
||||
// proposal flags
|
||||
cmd.Flags().String(cli.FlagTitle, "", "Title of proposal")
|
||||
cmd.Flags().String(cli.FlagDescription, "", "Description of proposal")
|
||||
cmd.Flags().String(cli.FlagDeposit, "", "Deposit of proposal")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func ProposalMigrateContractCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "migrate-contract [contract_addr_bech32] [new_code_id_int64] [json_encoded_migration_args]",
|
||||
Short: "Submit a migrate wasm contract to a new code version proposal",
|
||||
Args: cobra.ExactArgs(3),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, proposalTitle, proposalDescr, deposit, err := getProposalInfo(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
src, err := parseMigrateContractArgs(args, clientCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content := types.MigrateContractProposal{
|
||||
Title: proposalTitle,
|
||||
Description: proposalDescr,
|
||||
Contract: src.Contract,
|
||||
CodeID: src.CodeID,
|
||||
Msg: src.Msg,
|
||||
}
|
||||
|
||||
msg, err := govtypes.NewMsgSubmitProposal(&content, deposit, clientCtx.GetFromAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
// proposal flags
|
||||
cmd.Flags().String(cli.FlagTitle, "", "Title of proposal")
|
||||
cmd.Flags().String(cli.FlagDescription, "", "Description of proposal")
|
||||
cmd.Flags().String(cli.FlagDeposit, "", "Deposit of proposal")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func ProposalExecuteContractCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "execute-contract [contract_addr_bech32] [json_encoded_migration_args]",
|
||||
Short: "Submit a execute wasm contract proposal (run by any address)",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, proposalTitle, proposalDescr, deposit, err := getProposalInfo(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contract := args[0]
|
||||
execMsg := []byte(args[1])
|
||||
amountStr, err := cmd.Flags().GetString(flagAmount)
|
||||
if err != nil {
|
||||
return fmt.Errorf("amount: %s", err)
|
||||
}
|
||||
funds, err := sdk.ParseCoinsNormalized(amountStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("amount: %s", err)
|
||||
}
|
||||
runAs, err := cmd.Flags().GetString(flagRunAs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("run-as: %s", err)
|
||||
}
|
||||
|
||||
if len(runAs) == 0 {
|
||||
return errors.New("run-as address is required")
|
||||
}
|
||||
|
||||
content := types.ExecuteContractProposal{
|
||||
Title: proposalTitle,
|
||||
Description: proposalDescr,
|
||||
Contract: contract,
|
||||
Msg: execMsg,
|
||||
RunAs: runAs,
|
||||
Funds: funds,
|
||||
}
|
||||
|
||||
msg, err := govtypes.NewMsgSubmitProposal(&content, deposit, clientCtx.GetFromAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
cmd.Flags().String(flagRunAs, "", "The address that is passed as sender to the contract on proposal execution")
|
||||
cmd.Flags().String(flagAmount, "", "Coins to send to the contract during instantiation")
|
||||
|
||||
// proposal flags
|
||||
cmd.Flags().String(cli.FlagTitle, "", "Title of proposal")
|
||||
cmd.Flags().String(cli.FlagDescription, "", "Description of proposal")
|
||||
cmd.Flags().String(cli.FlagDeposit, "", "Deposit of proposal")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func ProposalSudoContractCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "sudo-contract [contract_addr_bech32] [json_encoded_migration_args]",
|
||||
Short: "Submit a sudo wasm contract proposal (to call privileged commands)",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, proposalTitle, proposalDescr, deposit, err := getProposalInfo(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contract := args[0]
|
||||
sudoMsg := []byte(args[1])
|
||||
|
||||
content := types.SudoContractProposal{
|
||||
Title: proposalTitle,
|
||||
Description: proposalDescr,
|
||||
Contract: contract,
|
||||
Msg: sudoMsg,
|
||||
}
|
||||
|
||||
msg, err := govtypes.NewMsgSubmitProposal(&content, deposit, clientCtx.GetFromAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
// proposal flagsExecute
|
||||
cmd.Flags().String(cli.FlagTitle, "", "Title of proposal")
|
||||
cmd.Flags().String(cli.FlagDescription, "", "Description of proposal")
|
||||
cmd.Flags().String(cli.FlagDeposit, "", "Deposit of proposal")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func ProposalUpdateContractAdminCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "set-contract-admin [contract_addr_bech32] [new_admin_addr_bech32]",
|
||||
Short: "Submit a new admin for a contract proposal",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, proposalTitle, proposalDescr, deposit, err := getProposalInfo(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
src, err := parseUpdateContractAdminArgs(args, clientCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content := types.UpdateAdminProposal{
|
||||
Title: proposalTitle,
|
||||
Description: proposalDescr,
|
||||
Contract: src.Contract,
|
||||
NewAdmin: src.NewAdmin,
|
||||
}
|
||||
|
||||
msg, err := govtypes.NewMsgSubmitProposal(&content, deposit, clientCtx.GetFromAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
// proposal flags
|
||||
cmd.Flags().String(cli.FlagTitle, "", "Title of proposal")
|
||||
cmd.Flags().String(cli.FlagDescription, "", "Description of proposal")
|
||||
cmd.Flags().String(cli.FlagDeposit, "", "Deposit of proposal")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func ProposalClearContractAdminCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "clear-contract-admin [contract_addr_bech32]",
|
||||
Short: "Submit a clear admin for a contract to prevent further migrations proposal",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, proposalTitle, proposalDescr, deposit, err := getProposalInfo(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content := types.ClearAdminProposal{
|
||||
Title: proposalTitle,
|
||||
Description: proposalDescr,
|
||||
Contract: args[0],
|
||||
}
|
||||
|
||||
msg, err := govtypes.NewMsgSubmitProposal(&content, deposit, clientCtx.GetFromAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
// proposal flags
|
||||
cmd.Flags().String(cli.FlagTitle, "", "Title of proposal")
|
||||
cmd.Flags().String(cli.FlagDescription, "", "Description of proposal")
|
||||
cmd.Flags().String(cli.FlagDeposit, "", "Deposit of proposal")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func ProposalPinCodesCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "pin-codes [code-ids]",
|
||||
Short: "Submit a pin code proposal for pinning a code to cache",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, proposalTitle, proposalDescr, deposit, err := getProposalInfo(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
codeIds, err := parsePinCodesArgs(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content := types.PinCodesProposal{
|
||||
Title: proposalTitle,
|
||||
Description: proposalDescr,
|
||||
CodeIDs: codeIds,
|
||||
}
|
||||
|
||||
msg, err := govtypes.NewMsgSubmitProposal(&content, deposit, clientCtx.GetFromAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
// proposal flags
|
||||
cmd.Flags().String(cli.FlagTitle, "", "Title of proposal")
|
||||
cmd.Flags().String(cli.FlagDescription, "", "Description of proposal")
|
||||
cmd.Flags().String(cli.FlagDeposit, "", "Deposit of proposal")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func parsePinCodesArgs(args []string) ([]uint64, error) {
|
||||
codeIDs := make([]uint64, len(args))
|
||||
for i, c := range args {
|
||||
codeID, err := strconv.ParseUint(c, 10, 64)
|
||||
if err != nil {
|
||||
return codeIDs, fmt.Errorf("code IDs: %s", err)
|
||||
}
|
||||
codeIDs[i] = codeID
|
||||
}
|
||||
return codeIDs, nil
|
||||
}
|
||||
|
||||
func ProposalUnpinCodesCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "unpin-codes [code-ids]",
|
||||
Short: "Submit a unpin code proposal for unpinning a code to cache",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, proposalTitle, proposalDescr, deposit, err := getProposalInfo(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
codeIds, err := parsePinCodesArgs(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content := types.UnpinCodesProposal{
|
||||
Title: proposalTitle,
|
||||
Description: proposalDescr,
|
||||
CodeIDs: codeIds,
|
||||
}
|
||||
|
||||
msg, err := govtypes.NewMsgSubmitProposal(&content, deposit, clientCtx.GetFromAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
// proposal flags
|
||||
cmd.Flags().String(cli.FlagTitle, "", "Title of proposal")
|
||||
cmd.Flags().String(cli.FlagDescription, "", "Description of proposal")
|
||||
cmd.Flags().String(cli.FlagDeposit, "", "Deposit of proposal")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func parseAccessConfig(raw string) (c types.AccessConfig, err error) {
|
||||
switch raw {
|
||||
case "nobody":
|
||||
return types.AllowNobody, nil
|
||||
case "everybody":
|
||||
return types.AllowEverybody, nil
|
||||
default:
|
||||
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
|
||||
}
|
||||
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(es)
|
||||
parts := strings.Split(c, ":")
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid format")
|
||||
}
|
||||
|
||||
codeID, err := strconv.ParseUint(parts[0], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid code ID: %s", err)
|
||||
}
|
||||
|
||||
accessConfig, err := parseAccessConfig(parts[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updates[i] = types.AccessConfigUpdate{
|
||||
CodeID: codeID,
|
||||
InstantiatePermission: accessConfig,
|
||||
}
|
||||
}
|
||||
return updates, nil
|
||||
}
|
||||
|
||||
func ProposalUpdateInstantiateConfigCmd() *cobra.Command {
|
||||
bech32Prefix := sdk.GetConfig().GetBech32AccountAddrPrefix()
|
||||
cmd := &cobra.Command{
|
||||
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,%s1vx8knpllrj7n963p9ttd80w47kpacrhuts497x
|
||||
`, version.AppName, bech32Prefix, bech32Prefix)),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, proposalTitle, proposalDescr, deposit, err := getProposalInfo(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updates, err := parseAccessConfigUpdates(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content := types.UpdateInstantiateConfigProposal{
|
||||
Title: proposalTitle,
|
||||
Description: proposalDescr,
|
||||
AccessConfigUpdates: updates,
|
||||
}
|
||||
msg, err := govtypes.NewMsgSubmitProposal(&content, deposit, clientCtx.GetFromAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
// proposal flags
|
||||
cmd.Flags().String(cli.FlagTitle, "", "Title of proposal")
|
||||
cmd.Flags().String(cli.FlagDescription, "", "Description of proposal")
|
||||
cmd.Flags().String(cli.FlagDeposit, "", "Deposit of proposal")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func getProposalInfo(cmd *cobra.Command) (client.Context, string, string, sdk.Coins, error) {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return client.Context{}, "", "", nil, err
|
||||
}
|
||||
|
||||
proposalTitle, err := cmd.Flags().GetString(cli.FlagTitle)
|
||||
if err != nil {
|
||||
return clientCtx, proposalTitle, "", nil, err
|
||||
}
|
||||
|
||||
proposalDescr, err := cmd.Flags().GetString(cli.FlagDescription)
|
||||
if err != nil {
|
||||
return client.Context{}, proposalTitle, proposalDescr, nil, err
|
||||
}
|
||||
|
||||
depositArg, err := cmd.Flags().GetString(cli.FlagDeposit)
|
||||
if err != nil {
|
||||
return client.Context{}, proposalTitle, proposalDescr, nil, err
|
||||
}
|
||||
|
||||
deposit, err := sdk.ParseCoinsNormalized(depositArg)
|
||||
if err != nil {
|
||||
return client.Context{}, proposalTitle, proposalDescr, deposit, err
|
||||
}
|
||||
|
||||
return clientCtx, proposalTitle, proposalDescr, deposit, nil
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cerc-io/laconicd/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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 := "beb3de5e9b93b52e514c74ce87ccddb594b9bcd33b7f1af1bb6da63fc883917b"
|
||||
|
||||
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 := parseVerificationFlags(wasmBin, flags)
|
||||
if spec.expErr {
|
||||
require.Error(t, gotErr)
|
||||
return
|
||||
}
|
||||
require.NoError(t, gotErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cerc-io/laconicd/x/wasm/types"
|
||||
)
|
||||
|
||||
// MigrateContractCmd will migrate a contract to a new code version
|
||||
func MigrateContractCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "migrate [contract_addr_bech32] [new_code_id_int64] [json_encoded_migration_args]",
|
||||
Short: "Migrate a wasm contract to a new code version",
|
||||
Aliases: []string{"update", "mig", "m"},
|
||||
Args: cobra.ExactArgs(3),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg, err := parseMigrateContractArgs(args, clientCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
return nil
|
||||
}
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func parseMigrateContractArgs(args []string, cliCtx client.Context) (types.MsgMigrateContract, error) {
|
||||
// get the id of the code to instantiate
|
||||
codeID, err := strconv.ParseUint(args[1], 10, 64)
|
||||
if err != nil {
|
||||
return types.MsgMigrateContract{}, sdkerrors.Wrap(err, "code id")
|
||||
}
|
||||
|
||||
migrateMsg := args[2]
|
||||
|
||||
msg := types.MsgMigrateContract{
|
||||
Sender: cliCtx.GetFromAddress().String(),
|
||||
Contract: args[0],
|
||||
CodeID: codeID,
|
||||
Msg: []byte(migrateMsg),
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// UpdateContractAdminCmd sets an new admin for a contract
|
||||
func UpdateContractAdminCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "set-contract-admin [contract_addr_bech32] [new_admin_addr_bech32]",
|
||||
Short: "Set new admin for a contract",
|
||||
Aliases: []string{"new-admin", "admin", "set-adm", "sa"},
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg, err := parseUpdateContractAdminArgs(args, clientCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func parseUpdateContractAdminArgs(args []string, cliCtx client.Context) (types.MsgUpdateAdmin, error) {
|
||||
msg := types.MsgUpdateAdmin{
|
||||
Sender: cliCtx.GetFromAddress().String(),
|
||||
Contract: args[0],
|
||||
NewAdmin: args[1],
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// ClearContractAdminCmd clears an admin for a contract
|
||||
func ClearContractAdminCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "clear-contract-admin [contract_addr_bech32]",
|
||||
Short: "Clears admin for a contract to prevent further migrations",
|
||||
Aliases: []string{"clear-admin", "clr-adm"},
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := types.MsgClearAdmin{
|
||||
Sender: clientCtx.GetFromAddress().String(),
|
||||
Contract: args[0],
|
||||
}
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// UpdateInstantiateConfigCmd updates instantiate config for a smart contract.
|
||||
func UpdateInstantiateConfigCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "update-instantiate-config [code_id_int64]",
|
||||
Short: "Update instantiate config for a codeID",
|
||||
Aliases: []string{"update-instantiate-config"},
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
codeID, err := strconv.ParseUint(args[0], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
perm, err := parseAccessConfigFlags(cmd.Flags())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := types.MsgUpdateInstantiateConfig{
|
||||
Sender: string(clientCtx.GetFromAddress()),
|
||||
CodeID: codeID,
|
||||
NewInstantiatePermission: perm,
|
||||
}
|
||||
if err = msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
addInstantiatePermissionFlags(cmd)
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,674 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
wasmvm "github.com/CosmWasm/wasmvm"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/spf13/cobra"
|
||||
flag "github.com/spf13/pflag"
|
||||
|
||||
"github.com/cerc-io/laconicd/x/wasm/keeper"
|
||||
"github.com/cerc-io/laconicd/x/wasm/types"
|
||||
)
|
||||
|
||||
func GetQueryCmd() *cobra.Command {
|
||||
queryCmd := &cobra.Command{
|
||||
Use: types.ModuleName,
|
||||
Short: "Querying commands for the wasm module",
|
||||
DisableFlagParsing: true,
|
||||
SuggestionsMinimumDistance: 2,
|
||||
RunE: client.ValidateCmd,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
queryCmd.AddCommand(
|
||||
GetCmdListCode(),
|
||||
GetCmdListContractByCode(),
|
||||
GetCmdQueryCode(),
|
||||
GetCmdQueryCodeInfo(),
|
||||
GetCmdGetContractInfo(),
|
||||
GetCmdGetContractHistory(),
|
||||
GetCmdGetContractState(),
|
||||
GetCmdListPinnedCode(),
|
||||
GetCmdLibVersion(),
|
||||
GetCmdQueryParams(),
|
||||
GetCmdBuildAddress(),
|
||||
GetCmdListContractsByCreator(),
|
||||
)
|
||||
return queryCmd
|
||||
}
|
||||
|
||||
// GetCmdLibVersion gets current libwasmvm version.
|
||||
func GetCmdLibVersion() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "libwasmvm-version",
|
||||
Short: "Get libwasmvm version",
|
||||
Long: "Get libwasmvm version",
|
||||
Aliases: []string{"lib-version"},
|
||||
Args: cobra.ExactArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
version, err := wasmvm.LibwasmvmVersion()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving libwasmvm version: %w", err)
|
||||
}
|
||||
fmt.Println(version)
|
||||
return nil
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdBuildAddress build a contract address
|
||||
func GetCmdBuildAddress() *cobra.Command {
|
||||
decoder := newArgDecoder(hex.DecodeString)
|
||||
cmd := &cobra.Command{
|
||||
Use: "build-address [code-hash] [creator-address] [salt-hex-encoded] [json_encoded_init_args (required when set as fixed)]",
|
||||
Short: "build contract address",
|
||||
Aliases: []string{"address"},
|
||||
Args: cobra.RangeArgs(3, 4),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
codeHash, err := hex.DecodeString(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("code-hash: %s", err)
|
||||
}
|
||||
creator, err := sdk.AccAddressFromBech32(args[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("creator: %s", err)
|
||||
}
|
||||
salt, err := hex.DecodeString(args[2])
|
||||
switch {
|
||||
case err != nil:
|
||||
return fmt.Errorf("salt: %s", err)
|
||||
case len(salt) == 0:
|
||||
return errors.New("empty salt")
|
||||
}
|
||||
|
||||
if len(args) == 3 {
|
||||
cmd.Println(keeper.BuildContractAddressPredictable(codeHash, creator, salt, []byte{}).String())
|
||||
return nil
|
||||
}
|
||||
msg := types.RawContractMessage(args[3])
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
return fmt.Errorf("init message: %s", err)
|
||||
}
|
||||
cmd.Println(keeper.BuildContractAddressPredictable(codeHash, creator, salt, msg).String())
|
||||
return nil
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
decoder.RegisterFlags(cmd.PersistentFlags(), "salt")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdListCode lists all wasm code uploaded
|
||||
func GetCmdListCode() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "list-code",
|
||||
Short: "List all wasm bytecode on the chain",
|
||||
Long: "List all wasm bytecode on the chain",
|
||||
Aliases: []string{"list-codes", "codes", "lco"},
|
||||
Args: cobra.ExactArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pageReq, err := client.ReadPageRequest(withPageKeyDecoded(cmd.Flags()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.Codes(
|
||||
context.Background(),
|
||||
&types.QueryCodesRequest{
|
||||
Pagination: pageReq,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
flags.AddPaginationFlagsToCmd(cmd, "list codes")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdListContractByCode lists all wasm code uploaded for given code id
|
||||
func GetCmdListContractByCode() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "list-contract-by-code [code_id]",
|
||||
Short: "List wasm all bytecode on the chain for given code id",
|
||||
Long: "List wasm all bytecode on the chain for given code id",
|
||||
Aliases: []string{"list-contracts-by-code", "list-contracts", "contracts", "lca"},
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
codeID, err := strconv.ParseUint(args[0], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if codeID == 0 {
|
||||
return errors.New("empty code id")
|
||||
}
|
||||
|
||||
pageReq, err := client.ReadPageRequest(withPageKeyDecoded(cmd.Flags()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.ContractsByCode(
|
||||
context.Background(),
|
||||
&types.QueryContractsByCodeRequest{
|
||||
CodeId: codeID,
|
||||
Pagination: pageReq,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
flags.AddPaginationFlagsToCmd(cmd, "list contracts by code")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdQueryCode returns the bytecode for a given contract
|
||||
func GetCmdQueryCode() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "code [code_id] [output filename]",
|
||||
Short: "Downloads wasm bytecode for given code id",
|
||||
Long: "Downloads wasm bytecode for given code id",
|
||||
Aliases: []string{"source-code", "source"},
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
codeID, err := strconv.ParseUint(args[0], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.Code(
|
||||
context.Background(),
|
||||
&types.QueryCodeRequest{
|
||||
CodeId: codeID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(res.Data) == 0 {
|
||||
return fmt.Errorf("contract not found")
|
||||
}
|
||||
|
||||
fmt.Printf("Downloading wasm code to %s\n", args[1])
|
||||
return os.WriteFile(args[1], res.Data, 0o600)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdQueryCodeInfo returns the code info for a given code id
|
||||
func GetCmdQueryCodeInfo() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "code-info [code_id]",
|
||||
Short: "Prints out metadata of a code id",
|
||||
Long: "Prints out metadata of a code id",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
codeID, err := strconv.ParseUint(args[0], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.Code(
|
||||
context.Background(),
|
||||
&types.QueryCodeRequest{
|
||||
CodeId: codeID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.CodeInfoResponse == nil {
|
||||
return fmt.Errorf("contract not found")
|
||||
}
|
||||
|
||||
return clientCtx.PrintProto(res.CodeInfoResponse)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdGetContractInfo gets details about a given contract
|
||||
func GetCmdGetContractInfo() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "contract [bech32_address]",
|
||||
Short: "Prints out metadata of a contract given its address",
|
||||
Long: "Prints out metadata of a contract given its address",
|
||||
Aliases: []string{"meta", "c"},
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sdk.AccAddressFromBech32(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.ContractInfo(
|
||||
context.Background(),
|
||||
&types.QueryContractInfoRequest{
|
||||
Address: args[0],
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdGetContractState dumps full internal state of a given contract
|
||||
func GetCmdGetContractState() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "contract-state",
|
||||
Short: "Querying commands for the wasm module",
|
||||
Aliases: []string{"state", "cs", "s"},
|
||||
DisableFlagParsing: true,
|
||||
SuggestionsMinimumDistance: 2,
|
||||
RunE: client.ValidateCmd,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
cmd.AddCommand(
|
||||
GetCmdGetContractStateAll(),
|
||||
GetCmdGetContractStateRaw(),
|
||||
GetCmdGetContractStateSmart(),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func GetCmdGetContractStateAll() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "all [bech32_address]",
|
||||
Short: "Prints out all internal state of a contract given its address",
|
||||
Long: "Prints out all internal state of a contract given its address",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sdk.AccAddressFromBech32(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pageReq, err := client.ReadPageRequest(withPageKeyDecoded(cmd.Flags()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.AllContractState(
|
||||
context.Background(),
|
||||
&types.QueryAllContractStateRequest{
|
||||
Address: args[0],
|
||||
Pagination: pageReq,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
flags.AddPaginationFlagsToCmd(cmd, "contract state")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func GetCmdGetContractStateRaw() *cobra.Command {
|
||||
decoder := newArgDecoder(hex.DecodeString)
|
||||
cmd := &cobra.Command{
|
||||
Use: "raw [bech32_address] [key]",
|
||||
Short: "Prints out internal state for key of a contract given its address",
|
||||
Long: "Prints out internal state for of a contract given its address",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sdk.AccAddressFromBech32(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queryData, err := decoder.DecodeString(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.RawContractState(
|
||||
context.Background(),
|
||||
&types.QueryRawContractStateRequest{
|
||||
Address: args[0],
|
||||
QueryData: queryData,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
decoder.RegisterFlags(cmd.PersistentFlags(), "key argument")
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func GetCmdGetContractStateSmart() *cobra.Command {
|
||||
decoder := newArgDecoder(asciiDecodeString)
|
||||
cmd := &cobra.Command{
|
||||
Use: "smart [bech32_address] [query]",
|
||||
Short: "Calls contract with given address with query data and prints the returned result",
|
||||
Long: "Calls contract with given address with query data and prints the returned result",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sdk.AccAddressFromBech32(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if args[1] == "" {
|
||||
return errors.New("query data must not be empty")
|
||||
}
|
||||
|
||||
queryData, err := decoder.DecodeString(args[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode query: %s", err)
|
||||
}
|
||||
if !json.Valid(queryData) {
|
||||
return errors.New("query data must be json")
|
||||
}
|
||||
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.SmartContractState(
|
||||
context.Background(),
|
||||
&types.QuerySmartContractStateRequest{
|
||||
Address: args[0],
|
||||
QueryData: queryData,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
decoder.RegisterFlags(cmd.PersistentFlags(), "query argument")
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdGetContractHistory prints the code history for a given contract
|
||||
func GetCmdGetContractHistory() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "contract-history [bech32_address]",
|
||||
Short: "Prints out the code history for a contract given its address",
|
||||
Long: "Prints out the code history for a contract given its address",
|
||||
Aliases: []string{"history", "hist", "ch"},
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sdk.AccAddressFromBech32(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pageReq, err := client.ReadPageRequest(withPageKeyDecoded(cmd.Flags()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.ContractHistory(
|
||||
context.Background(),
|
||||
&types.QueryContractHistoryRequest{
|
||||
Address: args[0],
|
||||
Pagination: pageReq,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
flags.AddPaginationFlagsToCmd(cmd, "contract history")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdListPinnedCode lists all wasm code ids that are pinned
|
||||
func GetCmdListPinnedCode() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "pinned",
|
||||
Short: "List all pinned code ids",
|
||||
Long: "List all pinned code ids",
|
||||
Args: cobra.ExactArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pageReq, err := client.ReadPageRequest(withPageKeyDecoded(cmd.Flags()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.PinnedCodes(
|
||||
context.Background(),
|
||||
&types.QueryPinnedCodesRequest{
|
||||
Pagination: pageReq,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
flags.AddPaginationFlagsToCmd(cmd, "list codes")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdListContractsByCreator lists all contracts by creator
|
||||
func GetCmdListContractsByCreator() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "list-contracts-by-creator [creator]",
|
||||
Short: "List all contracts by creator",
|
||||
Long: "List all contracts by creator",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = sdk.AccAddressFromBech32(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pageReq, err := client.ReadPageRequest(withPageKeyDecoded(cmd.Flags()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.ContractsByCreator(
|
||||
context.Background(),
|
||||
&types.QueryContractsByCreatorRequest{
|
||||
CreatorAddress: args[0],
|
||||
Pagination: pageReq,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type argumentDecoder struct {
|
||||
// dec is the default decoder
|
||||
dec func(string) ([]byte, error)
|
||||
asciiF, hexF, b64F bool
|
||||
}
|
||||
|
||||
func newArgDecoder(def func(string) ([]byte, error)) *argumentDecoder {
|
||||
return &argumentDecoder{dec: def}
|
||||
}
|
||||
|
||||
func (a *argumentDecoder) RegisterFlags(f *flag.FlagSet, argName string) {
|
||||
f.BoolVar(&a.asciiF, "ascii", false, "ascii encoded "+argName)
|
||||
f.BoolVar(&a.hexF, "hex", false, "hex encoded "+argName)
|
||||
f.BoolVar(&a.b64F, "b64", false, "base64 encoded "+argName)
|
||||
}
|
||||
|
||||
func (a *argumentDecoder) DecodeString(s string) ([]byte, error) {
|
||||
found := -1
|
||||
for i, v := range []*bool{&a.asciiF, &a.hexF, &a.b64F} {
|
||||
if !*v {
|
||||
continue
|
||||
}
|
||||
if found != -1 {
|
||||
return nil, errors.New("multiple decoding flags used")
|
||||
}
|
||||
found = i
|
||||
}
|
||||
switch found {
|
||||
case 0:
|
||||
return asciiDecodeString(s)
|
||||
case 1:
|
||||
return hex.DecodeString(s)
|
||||
case 2:
|
||||
return base64.StdEncoding.DecodeString(s)
|
||||
default:
|
||||
return a.dec(s)
|
||||
}
|
||||
}
|
||||
|
||||
func asciiDecodeString(s string) ([]byte, error) {
|
||||
return []byte(s), nil
|
||||
}
|
||||
|
||||
// sdk ReadPageRequest expects binary but we encoded to base64 in our marshaller
|
||||
func withPageKeyDecoded(flagSet *flag.FlagSet) *flag.FlagSet {
|
||||
encoded, err := flagSet.GetString(flags.FlagPageKey)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
raw, err := base64.StdEncoding.DecodeString(encoded)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
err = flagSet.Set(flags.FlagPageKey, string(raw))
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return flagSet
|
||||
}
|
||||
|
||||
// GetCmdQueryParams implements a command to return the current wasm
|
||||
// parameters.
|
||||
func GetCmdQueryParams() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "params",
|
||||
Short: "Query the current wasm parameters",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
|
||||
params := &types.QueryParamsRequest{}
|
||||
res, err := queryClient.Params(cmd.Context(), params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return clientCtx.PrintProto(&res.Params)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,544 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
"github.com/cosmos/cosmos-sdk/x/authz"
|
||||
"github.com/spf13/cobra"
|
||||
flag "github.com/spf13/pflag"
|
||||
|
||||
"github.com/cerc-io/laconicd/x/wasm/ioutils"
|
||||
"github.com/cerc-io/laconicd/x/wasm/types"
|
||||
)
|
||||
|
||||
const (
|
||||
flagAmount = "amount"
|
||||
flagLabel = "label"
|
||||
flagSource = "code-source-url"
|
||||
flagBuilder = "builder"
|
||||
flagCodeHash = "code-hash"
|
||||
flagAdmin = "admin"
|
||||
flagNoAdmin = "no-admin"
|
||||
flagFixMsg = "fix-msg"
|
||||
flagRunAs = "run-as"
|
||||
flagInstantiateByEverybody = "instantiate-everybody"
|
||||
flagInstantiateNobody = "instantiate-nobody"
|
||||
flagInstantiateByAddress = "instantiate-only-address"
|
||||
flagInstantiateByAnyOfAddress = "instantiate-anyof-addresses"
|
||||
flagUnpinCode = "unpin-code"
|
||||
flagAllowedMsgKeys = "allow-msg-keys"
|
||||
flagAllowedRawMsgs = "allow-raw-msgs"
|
||||
flagExpiration = "expiration"
|
||||
flagMaxCalls = "max-calls"
|
||||
flagMaxFunds = "max-funds"
|
||||
flagAllowAllMsgs = "allow-all-messages"
|
||||
flagNoTokenTransfer = "no-token-transfer" //nolint:gosec
|
||||
)
|
||||
|
||||
// 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,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
txCmd.AddCommand(
|
||||
StoreCodeCmd(),
|
||||
InstantiateContractCmd(),
|
||||
InstantiateContract2Cmd(),
|
||||
ExecuteContractCmd(),
|
||||
MigrateContractCmd(),
|
||||
UpdateContractAdminCmd(),
|
||||
ClearContractAdminCmd(),
|
||||
GrantAuthorizationCmd(),
|
||||
UpdateInstantiateConfigCmd(),
|
||||
)
|
||||
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)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
addInstantiatePermissionFlags(cmd)
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func parseStoreCodeArgs(file string, sender sdk.AccAddress, flags *flag.FlagSet) (types.MsgStoreCode, error) {
|
||||
wasm, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return types.MsgStoreCode{}, err
|
||||
}
|
||||
|
||||
// gzip the wasm file
|
||||
if ioutils.IsWasm(wasm) {
|
||||
wasm, err = ioutils.GzipIt(wasm)
|
||||
|
||||
if err != nil {
|
||||
return types.MsgStoreCode{}, err
|
||||
}
|
||||
} else if !ioutils.IsGzip(wasm) {
|
||||
return types.MsgStoreCode{}, fmt.Errorf("invalid input file. Use wasm binary or gzip")
|
||||
}
|
||||
|
||||
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 nil, fmt.Errorf("instantiate by address: %s", err)
|
||||
}
|
||||
if onlyAddrStr != "" {
|
||||
return nil, fmt.Errorf("not supported anymore. Use: %s", flagInstantiateByAnyOfAddress)
|
||||
}
|
||||
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 nil, fmt.Errorf("boolean value expected for instantiate by everybody: %s", err)
|
||||
}
|
||||
if ok {
|
||||
return &types.AllowEverybody, nil
|
||||
}
|
||||
}
|
||||
|
||||
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 nil, fmt.Errorf("boolean value expected for instantiate by nobody: %s", err)
|
||||
}
|
||||
if ok {
|
||||
return &types.AllowNobody, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func addInstantiatePermissionFlags(cmd *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, "", fmt.Sprintf("Removed: use %s instead", flagInstantiateByAnyOfAddress))
|
||||
cmd.Flags().StringSlice(flagInstantiateByAnyOfAddress, []string{}, "Any of the addresses can instantiate a contract from the code, optional")
|
||||
}
|
||||
|
||||
// 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",
|
||||
Long: fmt.Sprintf(`Creates a new instance of an uploaded wasm code with the given 'constructor' message.
|
||||
Each contract instance has a unique address assigned.
|
||||
Example:
|
||||
$ %s tx wasm instantiate 1 '{"foo":"bar"}' --admin="$(%s keys show mykey -a)" \
|
||||
--from mykey --amount="100ustake" --label "local0.1.0"
|
||||
`, version.AppName, version.AppName),
|
||||
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.Keyring, clientCtx.GetFromAddress(), cmd.Flags())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
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 or key name 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
|
||||
}
|
||||
|
||||
// InstantiateContract2Cmd will instantiate a contract from previously uploaded code with predicable address generated
|
||||
func InstantiateContract2Cmd() *cobra.Command {
|
||||
decoder := newArgDecoder(hex.DecodeString)
|
||||
cmd := &cobra.Command{
|
||||
Use: "instantiate2 [code_id_int64] [json_encoded_init_args] [salt] --label [text] --admin [address,optional] --amount [coins,optional] " +
|
||||
"--fix-msg [bool,optional]",
|
||||
Short: "Instantiate a wasm contract with predictable address",
|
||||
Long: fmt.Sprintf(`Creates a new instance of an uploaded wasm code with the given 'constructor' message.
|
||||
Each contract instance has a unique address assigned. They are assigned automatically but in order to have predictable addresses
|
||||
for special use cases, the given 'salt' argument and '--fix-msg' parameters can be used to generate a custom address.
|
||||
|
||||
Predictable address example (also see '%s query wasm build-address -h'):
|
||||
$ %s tx wasm instantiate2 1 '{"foo":"bar"}' $(echo -n "testing" | xxd -ps) --admin="$(%s keys show mykey -a)" \
|
||||
--from mykey --amount="100ustake" --label "local0.1.0" \
|
||||
--fix-msg
|
||||
`, version.AppName, version.AppName, version.AppName),
|
||||
Aliases: []string{"start", "init", "inst", "i"},
|
||||
Args: cobra.ExactArgs(3),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
salt, err := decoder.DecodeString(args[2])
|
||||
if err != nil {
|
||||
return fmt.Errorf("salt: %w", err)
|
||||
}
|
||||
fixMsg, err := cmd.Flags().GetBool(flagFixMsg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fix msg: %w", err)
|
||||
}
|
||||
data, err := parseInstantiateArgs(args[0], args[1], clientCtx.Keyring, clientCtx.GetFromAddress(), cmd.Flags())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := &types.MsgInstantiateContract2{
|
||||
Sender: data.Sender,
|
||||
Admin: data.Admin,
|
||||
CodeID: data.CodeID,
|
||||
Label: data.Label,
|
||||
Msg: data.Msg,
|
||||
Funds: data.Funds,
|
||||
Salt: salt,
|
||||
FixMsg: fixMsg,
|
||||
}
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
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 or key name of an admin")
|
||||
cmd.Flags().Bool(flagNoAdmin, false, "You must set this explicitly if you don't want an admin")
|
||||
cmd.Flags().Bool(flagFixMsg, false, "An optional flag to include the json_encoded_init_args for the predictable address generation mode")
|
||||
decoder.RegisterFlags(cmd.PersistentFlags(), "salt")
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func parseInstantiateArgs(rawCodeID, initMsg string, kr keyring.Keyring, 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 nil, err
|
||||
}
|
||||
|
||||
amountStr, err := flags.GetString(flagAmount)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("amount: %s", err)
|
||||
}
|
||||
amount, err := sdk.ParseCoinsNormalized(amountStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("amount: %s", err)
|
||||
}
|
||||
label, err := flags.GetString(flagLabel)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("label: %s", err)
|
||||
}
|
||||
if label == "" {
|
||||
return nil, errors.New("label is required on all contracts")
|
||||
}
|
||||
adminStr, err := flags.GetString(flagAdmin)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("admin: %s", err)
|
||||
}
|
||||
|
||||
noAdmin, err := flags.GetBool(flagNoAdmin)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("no-admin: %s", err)
|
||||
}
|
||||
|
||||
// ensure sensible admin is set (or explicitly immutable)
|
||||
if adminStr == "" && !noAdmin {
|
||||
return nil, fmt.Errorf("you must set an admin or explicitly pass --no-admin to make it immutible (wasmd issue #719)")
|
||||
}
|
||||
if adminStr != "" && noAdmin {
|
||||
return nil, fmt.Errorf("you set an admin and passed --no-admin, those cannot both be true")
|
||||
}
|
||||
|
||||
if adminStr != "" {
|
||||
addr, err := sdk.AccAddressFromBech32(adminStr)
|
||||
if err != nil {
|
||||
info, err := kr.Key(adminStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("admin %s", err)
|
||||
}
|
||||
adminStr = info.GetAddress().String()
|
||||
} else {
|
||||
adminStr = addr.String()
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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",
|
||||
Long: fmt.Sprintf(`Grant authorization to an address.
|
||||
Examples:
|
||||
$ %s tx grant <grantee_addr> execution <contract_addr> --allow-all-messages --max-calls 1 --no-token-transfer --expiration 1667979596
|
||||
|
||||
$ %s tx grant <grantee_addr> execution <contract_addr> --allow-all-messages --max-funds 100000uwasm --expiration 1667979596
|
||||
|
||||
$ %s tx grant <grantee_addr> execution <contract_addr> --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 {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
grantee, err := sdk.AccAddressFromBech32(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contract, err := sdk.AccAddressFromBech32(args[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgKeys, err := cmd.Flags().GetStringSlice(flagAllowedMsgKeys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rawMsgs, err := cmd.Flags().GetStringSlice(flagAllowedRawMsgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
maxFundsStr, err := cmd.Flags().GetString(flagMaxFunds)
|
||||
if err != nil {
|
||||
return fmt.Errorf("max funds: %s", err)
|
||||
}
|
||||
|
||||
maxCalls, err := cmd.Flags().GetUint64(flagMaxCalls)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exp, err := cmd.Flags().GetInt64(flagExpiration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exp == 0 {
|
||||
return errors.New("expiration must be set")
|
||||
}
|
||||
|
||||
allowAllMsgs, err := cmd.Flags().GetBool(flagAllowAllMsgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
noTokenTransfer, err := cmd.Flags().GetBool(flagNoTokenTransfer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var limit types.ContractAuthzLimitX
|
||||
switch {
|
||||
case maxFundsStr != "" && maxCalls != 0 && !noTokenTransfer:
|
||||
maxFunds, err := sdk.ParseCoinsNormalized(maxFundsStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("max funds: %s", err)
|
||||
}
|
||||
limit = types.NewCombinedLimit(maxCalls, maxFunds...)
|
||||
case maxFundsStr != "" && maxCalls == 0 && !noTokenTransfer:
|
||||
maxFunds, err := sdk.ParseCoinsNormalized(maxFundsStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("max funds: %s", err)
|
||||
}
|
||||
limit = types.NewMaxFundsLimit(maxFunds...)
|
||||
case maxCalls != 0 && noTokenTransfer && maxFundsStr == "":
|
||||
limit = types.NewMaxCallsLimit(maxCalls)
|
||||
default:
|
||||
return errors.New("invalid limit setup")
|
||||
}
|
||||
|
||||
var filter types.ContractAuthzFilterX
|
||||
switch {
|
||||
case allowAllMsgs && len(msgKeys) != 0 || allowAllMsgs && len(rawMsgs) != 0 || len(msgKeys) != 0 && len(rawMsgs) != 0:
|
||||
return errors.New("cannot set more than one filter within one grant")
|
||||
case allowAllMsgs:
|
||||
filter = types.NewAllowAllMessagesFilter()
|
||||
case len(msgKeys) != 0:
|
||||
filter = types.NewAcceptedMessageKeysFilter(msgKeys...)
|
||||
case len(rawMsgs) != 0:
|
||||
msgs := make([]types.RawContractMessage, len(rawMsgs))
|
||||
for i, msg := range rawMsgs {
|
||||
msgs[i] = types.RawContractMessage(msg)
|
||||
}
|
||||
filter = types.NewAcceptedMessagesFilter(msgs...)
|
||||
default:
|
||||
return errors.New("invalid filter setup")
|
||||
}
|
||||
|
||||
grant, err := types.NewContractGrant(contract, limit, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var authorization authz.Authorization
|
||||
switch args[1] {
|
||||
case "execution":
|
||||
authorization = types.NewContractExecutionAuthorization(*grant)
|
||||
case "migration":
|
||||
authorization = types.NewContractMigrationAuthorization(*grant)
|
||||
default:
|
||||
return fmt.Errorf("%s authorization type not supported", args[1])
|
||||
}
|
||||
|
||||
grantMsg, err := authz.NewMsgGrant(clientCtx.GetFromAddress(), grantee, authorization, time.Unix(0, exp))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), grantMsg)
|
||||
},
|
||||
}
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
cmd.Flags().StringSlice(flagAllowedMsgKeys, []string{}, "Allowed msg keys")
|
||||
cmd.Flags().StringSlice(flagAllowedRawMsgs, []string{}, "Allowed raw msgs")
|
||||
cmd.Flags().Uint64(flagMaxCalls, 0, "Maximal number of calls to the contract")
|
||||
cmd.Flags().String(flagMaxFunds, "", "Maximal amount of tokens transferable to the contract.")
|
||||
cmd.Flags().Int64(flagExpiration, 0, "The Unix timestamp.")
|
||||
cmd.Flags().Bool(flagAllowAllMsgs, false, "Allow all messages")
|
||||
cmd.Flags().Bool(flagNoTokenTransfer, false, "Don't allow token transfer")
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cerc-io/laconicd/x/wasm/types"
|
||||
)
|
||||
|
||||
func TestParseAccessConfigFlags(t *testing.T) {
|
||||
specs := map[string]struct {
|
||||
args []string
|
||||
expCfg *types.AccessConfig
|
||||
expErr bool
|
||||
}{
|
||||
"nobody": {
|
||||
args: []string{"--instantiate-nobody=true"},
|
||||
expCfg: &types.AccessConfig{Permission: types.AccessTypeNobody},
|
||||
},
|
||||
"everybody": {
|
||||
args: []string{"--instantiate-everybody=true"},
|
||||
expCfg: &types.AccessConfig{Permission: types.AccessTypeEverybody},
|
||||
},
|
||||
"only address": {
|
||||
args: []string{"--instantiate-only-address=cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x"},
|
||||
expErr: true,
|
||||
},
|
||||
"only address - invalid": {
|
||||
args: []string{"--instantiate-only-address=foo"},
|
||||
expErr: true,
|
||||
},
|
||||
"any of address": {
|
||||
args: []string{"--instantiate-anyof-addresses=cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x,cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr"},
|
||||
expCfg: &types.AccessConfig{Permission: types.AccessTypeAnyOfAddresses, Addresses: []string{"cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x", "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr"}},
|
||||
},
|
||||
"any of address - invalid": {
|
||||
args: []string{"--instantiate-anyof-addresses=cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x,foo"},
|
||||
expErr: true,
|
||||
},
|
||||
"not set": {
|
||||
args: []string{},
|
||||
},
|
||||
}
|
||||
for name, spec := range specs {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
flags := StoreCodeCmd().Flags()
|
||||
require.NoError(t, flags.Parse(spec.args))
|
||||
gotCfg, gotErr := parseAccessConfigFlags(flags)
|
||||
if spec.expErr {
|
||||
require.Error(t, gotErr)
|
||||
return
|
||||
}
|
||||
require.NoError(t, gotErr)
|
||||
assert.Equal(t, spec.expCfg, gotCfg)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user