Implement CLI/REST server support for new messages (#131)
* Cleanup ContractInfo type * Add admin to contract instanciation * Add cli commands for new TX * Add rest support for new TX * Update changelog * Make optional admin flag for better UX * Add flag to not accidentally clear admin on update
This commit is contained in:
@@ -50,7 +50,8 @@ Base64 encoded transactions.
|
||||
- `raw-bytes` convert raw-bytes to hex
|
||||
* (wasmcli) [\#191](https://github.com/cosmwasm/wasmd/pull/191) Add cmd `decode-tx`, decodes a tx from hex or base64
|
||||
* (wasmd) [\#9](https://github.com/cosmwasm/wasmd/pull/9) Allow gzip data in tx body on Create
|
||||
* (wasmd)[\#124](https://github.com/CosmWasm/wasmd/pull/124) Update contract admin
|
||||
* (wasmd) [\#124](https://github.com/CosmWasm/wasmd/pull/124) Update contract admin
|
||||
* (wasmd) [\#131](https://github.com/CosmWasm/wasmd/pull/131) Implement REST server support for new messages
|
||||
|
||||
## [v2.0.3] - 2019-11-04
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ type (
|
||||
Model = types.Model
|
||||
CodeInfo = types.CodeInfo
|
||||
ContractInfo = types.ContractInfo
|
||||
CreatedAt = types.CreatedAt
|
||||
CreatedAt = types.AbsoluteTxPosition
|
||||
WasmConfig = types.WasmConfig
|
||||
MessageHandler = keeper.MessageHandler
|
||||
BankEncoder = keeper.BankEncoder
|
||||
|
||||
94
x/wasm/client/cli/new_tx.go
Normal file
94
x/wasm/client/cli/new_tx.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"github.com/CosmWasm/wasmd/x/wasm/internal/types"
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// MigrateContractCmd will migrate a contract to a new code version
|
||||
func MigrateContractCmd(cdc *codec.Codec) *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",
|
||||
Args: cobra.ExactArgs(3),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
inBuf := bufio.NewReader(cmd.InOrStdin())
|
||||
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
|
||||
cliCtx := context.NewCLIContextWithInput(inBuf).WithCodec(cdc)
|
||||
|
||||
contractAddr, err := sdk.AccAddressFromBech32(args[0])
|
||||
if err != nil {
|
||||
return sdkerrors.Wrap(err, "contract")
|
||||
}
|
||||
|
||||
// get the id of the code to instantiate
|
||||
codeID, err := strconv.ParseUint(args[1], 10, 64)
|
||||
if err != nil {
|
||||
return sdkerrors.Wrap(err, "code id")
|
||||
}
|
||||
|
||||
migrateMsg := args[2]
|
||||
|
||||
msg := types.MsgMigrateContract{
|
||||
Sender: cliCtx.GetFromAddress(),
|
||||
Contract: contractAddr,
|
||||
Code: codeID,
|
||||
MigrateMsg: []byte(migrateMsg),
|
||||
}
|
||||
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// UpdateContractAdminCmd sets or clears an admin for a contract
|
||||
func UpdateContractAdminCmd(cdc *codec.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "set-contract-admin [contract_addr_bech32] [new_admin_addr_bech32]",
|
||||
Short: "Set new admin for a contract. Can be empty to prevent further migrations",
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
inBuf := bufio.NewReader(cmd.InOrStdin())
|
||||
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
|
||||
cliCtx := context.NewCLIContextWithInput(inBuf).WithCodec(cdc)
|
||||
|
||||
contractAddr, err := sdk.AccAddressFromBech32(args[0])
|
||||
if err != nil {
|
||||
return sdkerrors.Wrap(err, "contract")
|
||||
}
|
||||
var newAdmin sdk.AccAddress
|
||||
if len(args) > 1 && len(args[1]) != 0 {
|
||||
newAdmin, err = sdk.AccAddressFromBech32(args[1])
|
||||
if err != nil {
|
||||
return sdkerrors.Wrap(err, "new admin")
|
||||
}
|
||||
} else {
|
||||
// safety net to not accidentally clear an admin
|
||||
clearAdmin := viper.GetBool(flagNoAdmin)
|
||||
if !clearAdmin {
|
||||
return errors.New("new admin address required or no admin flag")
|
||||
}
|
||||
}
|
||||
|
||||
msg := types.MsgUpdateAdministrator{
|
||||
Sender: cliCtx.GetFromAddress(),
|
||||
Contract: contractAddr,
|
||||
NewAdmin: newAdmin,
|
||||
}
|
||||
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
|
||||
},
|
||||
}
|
||||
cmd.Flags().Bool(flagNoAdmin, false, "Remove admin which disables future admin updates and migrations")
|
||||
return cmd
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
|
||||
|
||||
@@ -27,6 +28,8 @@ const (
|
||||
flagSource = "source"
|
||||
flagBuilder = "builder"
|
||||
flagLabel = "label"
|
||||
flagAdmin = "admin"
|
||||
flagNoAdmin = "no-admin"
|
||||
)
|
||||
|
||||
// GetTxCmd returns the transaction commands for this module
|
||||
@@ -42,6 +45,8 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||
StoreCodeCmd(cdc),
|
||||
InstantiateContractCmd(cdc),
|
||||
ExecuteContractCmd(cdc),
|
||||
MigrateContractCmd(cdc),
|
||||
UpdateContractAdminCmd(cdc),
|
||||
)...)
|
||||
return txCmd
|
||||
}
|
||||
@@ -106,7 +111,7 @@ func InstantiateContractCmd(cdc *codec.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "instantiate [code_id_int64] [json_encoded_init_args]",
|
||||
Short: "Instantiate a wasm contract",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Args: cobra.RangeArgs(2, 3),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
inBuf := bufio.NewReader(cmd.InOrStdin())
|
||||
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
|
||||
@@ -131,6 +136,15 @@ func InstantiateContractCmd(cdc *codec.Codec) *cobra.Command {
|
||||
|
||||
initMsg := args[1]
|
||||
|
||||
adminStr := viper.GetString(flagAdmin)
|
||||
var adminAddr sdk.AccAddress
|
||||
if len(adminStr) != 0 {
|
||||
adminAddr, err = sdk.AccAddressFromBech32(adminStr)
|
||||
if err != nil {
|
||||
return sdkerrors.Wrap(err, "admin")
|
||||
}
|
||||
}
|
||||
|
||||
// build and sign the transaction, then broadcast to Tendermint
|
||||
msg := types.MsgInstantiateContract{
|
||||
Sender: cliCtx.GetFromAddress(),
|
||||
@@ -138,6 +152,7 @@ func InstantiateContractCmd(cdc *codec.Codec) *cobra.Command {
|
||||
Label: label,
|
||||
InitFunds: amount,
|
||||
InitMsg: []byte(initMsg),
|
||||
Admin: adminAddr,
|
||||
}
|
||||
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
|
||||
},
|
||||
@@ -145,6 +160,7 @@ func InstantiateContractCmd(cdc *codec.Codec) *cobra.Command {
|
||||
|
||||
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")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
||||
97
x/wasm/client/rest/new_tx.go
Normal file
97
x/wasm/client/rest/new_tx.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/CosmWasm/wasmd/x/wasm/internal/types"
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func registerNewTxRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||
r.HandleFunc("/wasm/contract/{contractAddr}/admin", setContractAdminHandlerFn(cliCtx)).Methods("PUT")
|
||||
r.HandleFunc("/wasm/contract/{contractAddr}/code", migrateContractHandlerFn(cliCtx)).Methods("PUT")
|
||||
}
|
||||
|
||||
type migrateContractReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
Admin sdk.AccAddress `json:"admin,omitempty" yaml:"admin"`
|
||||
codeID uint64 `json:"code_id" yaml:"code_id"`
|
||||
MigrateMsg []byte `json:"migrate_msg,omitempty" yaml:"migrate_msg"`
|
||||
}
|
||||
type updateContractAdministrateReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
Admin sdk.AccAddress `json:"admin,omitempty" yaml:"admin"`
|
||||
}
|
||||
|
||||
func setContractAdminHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req updateContractAdministrateReq
|
||||
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
contractAddr := vars["contractAddr"]
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
contractAddress, err := sdk.AccAddressFromBech32(contractAddr)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
msg := types.MsgUpdateAdministrator{
|
||||
Sender: cliCtx.GetFromAddress(),
|
||||
NewAdmin: req.Admin,
|
||||
Contract: contractAddress,
|
||||
}
|
||||
if err = msg.ValidateBasic(); err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg})
|
||||
}
|
||||
}
|
||||
|
||||
func migrateContractHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req migrateContractReq
|
||||
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
contractAddr := vars["contractAddr"]
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
contractAddress, err := sdk.AccAddressFromBech32(contractAddr)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
msg := types.MsgMigrateContract{
|
||||
Sender: cliCtx.GetFromAddress(),
|
||||
Contract: contractAddress,
|
||||
Code: req.codeID,
|
||||
MigrateMsg: req.MigrateMsg,
|
||||
}
|
||||
if err = msg.ValidateBasic(); err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg})
|
||||
}
|
||||
}
|
||||
@@ -10,4 +10,5 @@ import (
|
||||
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||
registerQueryRoutes(cliCtx, r)
|
||||
registerTxRoutes(cliCtx, r)
|
||||
registerNewTxRoutes(cliCtx, r)
|
||||
}
|
||||
|
||||
@@ -29,9 +29,10 @@ type storeCodeReq struct {
|
||||
}
|
||||
|
||||
type instantiateContractReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
Deposit sdk.Coins `json:"deposit" yaml:"deposit"`
|
||||
InitMsg []byte `json:"init_msg" yaml:"init_msg"`
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
Deposit sdk.Coins `json:"deposit" yaml:"deposit"`
|
||||
Admin sdk.AccAddress `json:"admin,omitempty" yaml:"admin"`
|
||||
InitMsg []byte `json:"init_msg" yaml:"init_msg"`
|
||||
}
|
||||
|
||||
type executeContractReq struct {
|
||||
@@ -117,6 +118,7 @@ func instantiateContractHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
Code: codeID,
|
||||
InitFunds: req.Deposit,
|
||||
InitMsg: req.InitMsg,
|
||||
Admin: req.Admin,
|
||||
}
|
||||
|
||||
err = msg.ValidateBasic()
|
||||
|
||||
@@ -71,6 +71,8 @@ func queryContractInfo(ctx sdk.Context, bech string, req abci.RequestQuery, keep
|
||||
}
|
||||
// redact the Created field (just used for sorting, not part of public API)
|
||||
info.Created = nil
|
||||
info.LastUpdated = nil
|
||||
info.PreviousCodeID = 0
|
||||
|
||||
infoWithAddress := ContractInfoWithAddress{
|
||||
Address: addr,
|
||||
@@ -104,7 +106,7 @@ func queryContractListByCode(ctx sdk.Context, codeIDstr string, req abci.Request
|
||||
return false
|
||||
})
|
||||
|
||||
// now we sort them by CreatedAt
|
||||
// now we sort them by AbsoluteTxPosition
|
||||
sort.Slice(contracts, func(i, j int) bool {
|
||||
return contracts[i].ContractInfo.Created.LessThan(contracts[j].ContractInfo.Created)
|
||||
})
|
||||
|
||||
@@ -47,10 +47,9 @@ type ContractInfo struct {
|
||||
InitMsg json.RawMessage `json:"init_msg,omitempty"`
|
||||
// never show this in query results, just use for sorting
|
||||
// (Note: when using json tag "-" amino refused to serialize it...)
|
||||
Created *CreatedAt `json:"created,omitempty"`
|
||||
// TODO: type CreatedAt is not an accurate name. how about renaming to BlockPosition?
|
||||
LastUpdated *CreatedAt `json:"last_updated,omitempty"`
|
||||
PreviousCodeID uint64 `json:"previous_code_id,omitempty"`
|
||||
Created *AbsoluteTxPosition `json:"created,omitempty"`
|
||||
LastUpdated *AbsoluteTxPosition `json:"last_updated,omitempty"`
|
||||
PreviousCodeID uint64 `json:"previous_code_id,omitempty"`
|
||||
}
|
||||
|
||||
func (c *ContractInfo) UpdateCodeID(ctx sdk.Context, newCodeID uint64) {
|
||||
@@ -59,8 +58,8 @@ func (c *ContractInfo) UpdateCodeID(ctx sdk.Context, newCodeID uint64) {
|
||||
c.LastUpdated = NewCreatedAt(ctx)
|
||||
}
|
||||
|
||||
// CreatedAt can be used to sort contracts
|
||||
type CreatedAt struct {
|
||||
// AbsoluteTxPosition can be used to sort contracts
|
||||
type AbsoluteTxPosition struct {
|
||||
// BlockHeight is the block the contract was created at
|
||||
BlockHeight int64
|
||||
// TxIndex is a monotonic counter within the block (actual transaction index, or gas consumed)
|
||||
@@ -68,7 +67,7 @@ type CreatedAt struct {
|
||||
}
|
||||
|
||||
// LessThan can be used to sort
|
||||
func (a *CreatedAt) LessThan(b *CreatedAt) bool {
|
||||
func (a *AbsoluteTxPosition) LessThan(b *AbsoluteTxPosition) bool {
|
||||
if a == nil {
|
||||
return true
|
||||
}
|
||||
@@ -79,21 +78,21 @@ func (a *CreatedAt) LessThan(b *CreatedAt) bool {
|
||||
}
|
||||
|
||||
// NewCreatedAt gets a timestamp from the context
|
||||
func NewCreatedAt(ctx sdk.Context) *CreatedAt {
|
||||
func NewCreatedAt(ctx sdk.Context) *AbsoluteTxPosition {
|
||||
// we must safely handle nil gas meters
|
||||
var index uint64
|
||||
meter := ctx.BlockGasMeter()
|
||||
if meter != nil {
|
||||
index = meter.GasConsumed()
|
||||
}
|
||||
return &CreatedAt{
|
||||
return &AbsoluteTxPosition{
|
||||
BlockHeight: ctx.BlockHeight(),
|
||||
TxIndex: index,
|
||||
}
|
||||
}
|
||||
|
||||
// NewContractInfo creates a new instance of a given WASM contract info
|
||||
func NewContractInfo(codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, createdAt *CreatedAt) ContractInfo {
|
||||
func NewContractInfo(codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, createdAt *AbsoluteTxPosition) ContractInfo {
|
||||
return ContractInfo{
|
||||
CodeID: codeID,
|
||||
Creator: creator,
|
||||
|
||||
Reference in New Issue
Block a user