Skip to content

Commit

Permalink
Implement CLI/REST server support for new messages (#131)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
alpe authored Jun 5, 2020
1 parent 9a16d58 commit ebac9aa
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 17 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion x/wasm/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
94 changes: 94 additions & 0 deletions x/wasm/client/cli/new_tx.go
Original file line number Diff line number Diff line change
@@ -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
}
18 changes: 17 additions & 1 deletion x/wasm/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -27,6 +28,8 @@ const (
flagSource = "source"
flagBuilder = "builder"
flagLabel = "label"
flagAdmin = "admin"
flagNoAdmin = "no-admin"
)

// GetTxCmd returns the transaction commands for this module
Expand All @@ -42,6 +45,8 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command {
StoreCodeCmd(cdc),
InstantiateContractCmd(cdc),
ExecuteContractCmd(cdc),
MigrateContractCmd(cdc),
UpdateContractAdminCmd(cdc),
)...)
return txCmd
}
Expand Down Expand Up @@ -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))
Expand All @@ -131,20 +136,31 @@ 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(),
Code: codeID,
Label: label,
InitFunds: amount,
InitMsg: []byte(initMsg),
Admin: adminAddr,
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}

cmd.Flags().String(flagAmount, "", "Coins to send to the contract during instantiation")
cmd.Flags().String(flagLabel, "", "A human-readable name for this contract in lists")
cmd.Flags().String(flagAdmin, "", "Address of an admin")
return cmd
}

Expand Down
97 changes: 97 additions & 0 deletions x/wasm/client/rest/new_tx.go
Original file line number Diff line number Diff line change
@@ -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})
}
}
1 change: 1 addition & 0 deletions x/wasm/client/rest/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ import (
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
registerQueryRoutes(cliCtx, r)
registerTxRoutes(cliCtx, r)
registerNewTxRoutes(cliCtx, r)
}
8 changes: 5 additions & 3 deletions x/wasm/client/rest/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
Expand Down
4 changes: 3 additions & 1 deletion x/wasm/internal/keeper/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
})
Expand Down
19 changes: 9 additions & 10 deletions x/wasm/internal/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -59,16 +58,16 @@ 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)
TxIndex uint64
}

// 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
}
Expand All @@ -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,
Expand Down

0 comments on commit ebac9aa

Please sign in to comment.